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

com.reandroid.apk.xmlencoder.XMLTableBlockEncoder Maven / Gradle / Ivy

/*
 *  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.xmlencoder;

import com.reandroid.apk.*;
import com.reandroid.archive.BlockInputSource;
import com.reandroid.archive.ZipEntryMap;
import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.chunk.TableBlock;
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
import com.reandroid.arsc.coder.ReferenceString;
import com.reandroid.arsc.coder.xml.XmlCoder;
import com.reandroid.arsc.pool.TableStringPool;
import com.reandroid.utils.HexUtil;
import com.reandroid.utils.io.FileUtil;
import com.reandroid.utils.io.IOUtil;
import com.reandroid.json.JSONObject;
import com.reandroid.xml.StyleDocument;
import com.reandroid.xml.XMLDocument;
import com.reandroid.xml.XMLElement;
import com.reandroid.xml.XMLFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.zip.ZipEntry;

public class XMLTableBlockEncoder {
    private APKLogger apkLogger;
    private final TableBlock tableBlock;
    private final Set parsedFiles = new HashSet<>();
    private final ApkModule apkModule;
    private Integer mMainPackageId;

    public XMLTableBlockEncoder(ApkModule apkModule, TableBlock tableBlock){
        this.apkModule = apkModule;
        this.tableBlock = tableBlock;
        if(!apkModule.hasTableBlock()){
            BlockInputSource inputSource =
                    new BlockInputSource<>(TableBlock.FILE_NAME, tableBlock);
            inputSource.setMethod(ZipEntry.STORED);
            inputSource.setSort(1);
            this.apkModule.setTableBlock(this.tableBlock);
            apkModule.setLoadDefaultFramework(true);
        }
        apkLogger = apkModule.getApkLogger();
    }
    public XMLTableBlockEncoder(){
        this(new ApkModule("encoded",
                new ZipEntryMap()), new TableBlock());
    }

    public Integer getMainPackageId() {
        return mMainPackageId;
    }

    public TableBlock getTableBlock(){
        return tableBlock;
    }
    public ApkModule getApkModule(){
        return apkModule;
    }
    public void scanMainDirectory(File mainDirectory) throws IOException {
        File resourcesDirectory = new File(mainDirectory, TableBlock.DIRECTORY_NAME);
        scanResourcesDirectory(resourcesDirectory);
    }
    public void scanResourcesDirectory(File resourcesDirectory) throws IOException {
        try {
            scanResourceFiles(resourcesDirectory);
        } catch (XmlPullParserException ex) {
            throw new IOException(ex);
        }
    }
    private void scanResourceFiles(File resourcesDirectory) throws IOException, XmlPullParserException {
        List pubXmlFileList = ApkUtil.listPublicXmlFiles(resourcesDirectory);
        if(pubXmlFileList.size() == 0){
            throw new IOException("No .*/values/"
                    + PackageBlock.PUBLIC_XML
                    + "  file found in '" +resourcesDirectory + "'");
        }
        loadPublicXmlFiles(pubXmlFileList);

        initializeFrameworkFromManifest(pubXmlFileList);

        encodeAttrs(pubXmlFileList);

        encodeValues(pubXmlFileList);

        tableBlock.refresh();

    }
    private void loadPublicXmlFiles(List pubXmlFileList) throws IOException {
        for(File pubXmlFile:pubXmlFileList){
            loadPublicXmlFile(pubXmlFile);
        }
    }
    private void loadPublicXmlFile(File pubXmlFile) throws IOException {
        try {
            XmlPullParser parser = XMLFactory.newPullParser(pubXmlFile);
            PackageBlock packageBlock = tableBlock.parsePublicXml(parser);
            packageBlock.setTag(pubXmlFile);
            loadPackageJson(packageBlock, pubXmlFile);
            IOUtil.close(parser);
        } catch (XmlPullParserException ex) {
           throw new IOException(ex);
        }
    }
    private void loadPackageJson(PackageBlock packageBlock, File publicXml) throws IOException {
        File json = toPackageJson(publicXml);
        if(json == null){
            return;
        }
        packageBlock.fromJson(new JSONObject(json));
    }
    private File toPackageJson(File publicXml){
        File dir = publicXml.getParentFile();
        //values
        if(dir == null || !"values".equals(dir.getName())){
            return null;
        }
        dir = dir.getParentFile();
        //res
        if(dir == null){
            return null;
        }
        dir = dir.getParentFile();
        if(dir == null){
            return null;
        }
        File json = new File(dir, "package.json");
        if(!json.isFile()){
            return null;
        }
        return json;
    }
    private void initializeFrameworkFromManifest(List pubXmlFileList) throws  IOException {
        for(File pubXmlFile:pubXmlFileList){
            File manifestFile = toAndroidManifest(pubXmlFile);
            if(!manifestFile.isFile()){
                continue;
            }
            initializeFrameworkFromManifest(manifestFile);
            return;
        }
    }
    private void encodeValues(List pubXmlFileList) throws IOException, XmlPullParserException {
        logMessage("Encoding values ...");
        FilePathEncoder filePathEncoder = new FilePathEncoder(getApkModule());
        TableBlock tableBlock = getTableBlock();

        for(File pubXmlFile:pubXmlFileList){
            addParsedFiles(pubXmlFile);
            PackageBlock packageBlock = tableBlock.getPackageBlockByTag(pubXmlFile);
            tableBlock.setCurrentPackage(packageBlock);

            File resDir = toResDirectory(pubXmlFile);
            encodeResDir(resDir);

            filePathEncoder.setApkLogger(getApkLogger());
            filePathEncoder.encodePackageResDir(packageBlock, resDir);

            packageBlock.sortTypes();
            packageBlock.refresh();
        }
    }
    private void encodeAttrs(List pubXmlFileList) throws IOException, XmlPullParserException {
        logMessage("Encoding attrs ...");

        TableBlock tableBlock = getTableBlock();

        for(File pubXmlFile : pubXmlFileList){
            addParsedFiles(pubXmlFile);

            PackageBlock packageBlock = tableBlock.getPackageBlockByTag(pubXmlFile);
            tableBlock.setCurrentPackage(packageBlock);

            List attrFiles = listAttrs(pubXmlFile);
            if(attrFiles.size() == 0){
                continue;
            }
            for(File file : attrFiles){
                logVerbose("Encoding: " + FileUtil.shortPath(file, 4));
                XmlCoder xmlCoder = XmlCoder.getInstance();
                xmlCoder.VALUES_XML.encode(file, packageBlock);
                addParsedFiles(file);
            }
            packageBlock.sortTypes();
        }
    }
    private void excludeIds(List pubXmlFileList){
        for(File pubXmlFile : pubXmlFileList){
            addParsedFiles(pubXmlFile);
        }
    }
    private void initializeFrameworkFromManifest(File manifestFile) throws IOException {
        if(AndroidManifestBlock.FILE_NAME_BIN.equals(manifestFile.getName())){
            initializeFrameworkFromBinaryManifest();
            return;
        }
        XMLDocument xmlDocument;
        try {
            xmlDocument = XMLDocument.load(manifestFile);
        } catch (XmlPullParserException ex) {
            throw new IOException(ex);
        }
        TableBlock tableBlock = getTableBlock();
        FrameworkApk frameworkApk = getApkModule().initializeAndroidFramework(xmlDocument);
        if(frameworkApk != null){
            tableBlock.addFramework(frameworkApk.getTableBlock());
        }
        initializeMainPackageId(xmlDocument);
    }
    private void initializeFrameworkFromBinaryManifest() throws IOException {
        ApkModule apkModule = getApkModule();
        if(!apkModule.hasTableBlock() || !apkModule.hasAndroidManifest()){
            return;
        }
        logMessage("Initialize framework from binary manifest ...");
        FrameworkApk frameworkApk = apkModule.initializeAndroidFramework(
                apkModule.getAndroidFrameworkVersion());
        getTableBlock().addFramework(frameworkApk.getTableBlock());
    }
    private void initializeMainPackageId(XMLDocument xmlDocument) {
        XMLElement manifestRoot = xmlDocument.getDocumentElement();
        if(manifestRoot == null){
            return;
        }
        XMLElement application = manifestRoot.getElement(AndroidManifestBlock.TAG_application);
        if(application == null){
            return;
        }

        String iconReference = application.getAttributeValue(AndroidManifestBlock.NAME_icon);
        if(iconReference == null){
            return;
        }
        logMessage("Set main package id from manifest: " + iconReference);
        ReferenceString ref = ReferenceString.parseReference(iconReference);
        if(ref == null){
            logMessage("Something wrong on : " + AndroidManifestBlock.NAME_icon);
            return;
        }
        TableBlock tableBlock = getTableBlock();
        int resourceId = tableBlock.resolveResourceId(ref.packageName, ref.type, ref.name);
        if(resourceId == 0){
            logMessage("WARN: failed to resolve: " + ref);
            return;
        }
        int packageId = (resourceId >> 24 ) & 0xff;
        this.mMainPackageId = packageId;
        logMessage("Main package id initialized: id = "
                + HexUtil.toHex2((byte)packageId) + ", from: " + ref );
    }
    private void encodeResDir(File resDir) throws IOException, XmlPullParserException {
        preloadStyledStrings(resDir);
        List valuesDirList = ApkUtil.listValuesDirectory(resDir);
        for(File valuesDir : valuesDirList){
            encodeValuesDir(valuesDir);
        }
    }
    private void preloadStyledStrings(File resDir) throws IOException, XmlPullParserException {
        logVerbose("Preloading styled strings ...");
        List valuesDirList = ApkUtil.listValuesDirectory(resDir);
        for(File valuesDir : valuesDirList){
            List xmlFiles = ApkUtil.listFiles(valuesDir, "strings.xml");
            for(File file : xmlFiles){
                preloadStyledStringsXml(file);
            }
        }
    }
    private void preloadStyledStringsXml(File file) throws IOException, XmlPullParserException {
        XMLDocument document = XMLDocument.load(file);
        XMLElement root = document.getDocumentElement();
        Iterator iterator = root.getElements();
        TableStringPool stringPool = getTableBlock().getStringPool();
        while (iterator.hasNext()) {
            XMLElement element = iterator.next();
            if(element.hasChildElements()) {
                stringPool.getOrCreate(StyleDocument.copyInner(element));
            }
        }
    }
    private void encodeValuesDir(File valuesDir) throws IOException, XmlPullParserException {
        List xmlFiles = ApkUtil.listFiles(valuesDir, ".xml");
        EncodeUtil.sortValuesXml(xmlFiles);
        for(File file:xmlFiles){
            if(isAlreadyParsed(file)){
                continue;
            }
            addParsedFiles(file);
            logVerbose("Encoding: " + FileUtil.shortPath(file, 4));
            XmlCoder xmlCoder = XmlCoder.getInstance();
            xmlCoder.VALUES_XML.encode(file, getTableBlock().getCurrentPackage());
        }
    }
    private File toAndroidManifest(File pubXmlFile){
        File resDirectory = toResDirectory(pubXmlFile);
        File packageDirectory = resDirectory.getParentFile();
        File resourcesDir = packageDirectory.getParentFile();
        File root = resourcesDir.getParentFile();
        File file = new File(root, AndroidManifestBlock.FILE_NAME_BIN);
        if(!file.isFile()){
            file = new File(root, AndroidManifestBlock.FILE_NAME);
        }
        return file;
    }
    private File toResDirectory(File pubXmlFile){
        return pubXmlFile
                .getParentFile()
                .getParentFile();
    }
    private List listAttrs(File pubXmlFile){
        return listValuesXml(pubXmlFile, "attr");
    }
    private List listValuesXml(File pubXmlFile, String type){
        List results = new ArrayList<>();
        File resDir = toResDirectory(pubXmlFile);
        List valuesDirList = ApkUtil.listValuesDirectory(resDir);
        for(File valuesDir : valuesDirList){
            results.addAll(findValuesXml(valuesDir, type));
        }
        return results;
    }
    private List findValuesXml(File valuesDir, String type){
        List results = new ArrayList<>();
        File[] xmlFiles = valuesDir.listFiles();
        if(xmlFiles == null){
            return results;
        }
        for(File file : xmlFiles){
            if(!file.isFile()){
                continue;
            }
            String name = file.getName();
            if(!name.endsWith(".xml")){
                continue;
            }
            name = EncodeUtil.sanitizeType(name);
            if(name.equals(type)){
                results.add(file);
            }
        }
        return results;
    }
    private boolean isAlreadyParsed(File file){
        return parsedFiles.contains(file);
    }
    private void addParsedFiles(File file){
        parsedFiles.add(file);
    }

    public APKLogger getApkLogger() {
        return apkLogger;
    }
    public void setApkLogger(APKLogger logger) {
        this.apkLogger = logger;
        if(logger != null && apkModule.getApkLogger() == null){
            this.apkModule.setAPKLogger(logger);
        }
    }
    private void logMessage(String msg) {
        APKLogger apkLogger = getApkLogger();
        if(apkLogger!=null){
            apkLogger.logMessage(msg);
        }
    }
    private void logVerbose(String msg) {
        APKLogger apkLogger = getApkLogger();
        if(apkLogger!=null){
            apkLogger.logVerbose(msg);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy