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

com.reandroid.archive.writer.ApkWriter 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.archive.writer;

import com.reandroid.apk.APKLogger;
import com.reandroid.archive.RenamedInputSource;
import com.reandroid.archive.InputSource;
import com.reandroid.archive.WriteProgress;
import com.reandroid.archive.ZipSignature;
import com.reandroid.archive.block.*;
import com.reandroid.archive.io.ArchiveFileEntrySource;
import com.reandroid.archive.io.ZipFileOutput;
import com.reandroid.arsc.chunk.TableBlock;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

public class ApkWriter extends ZipFileOutput {
    private final Object mLock = new Object();
    private final InputSource[] sourceList;
    private ZipAligner zipAligner;
    private ApkSignatureBlock apkSignatureBlock;
    private APKLogger apkLogger;
    private WriteProgress writeProgress;

    public ApkWriter(File file, InputSource[] sourceList) throws IOException {
        super(file);
        this.sourceList = sourceList;
        this.zipAligner = ZipAligner.apkAligner();
    }
    public void write()throws IOException {
        synchronized (mLock){
            OutputSource[] outputList = buildOutputEntries();
            logMessage("Buffering compress changed files ...");
            BufferFileInput buffer = writeBuffer(outputList);
            buffer.unlock();
            if(getZipAligner() != null){
                logMessage("Zip align ON");
            }
            writeApk(outputList);
            buffer.close();

            writeSignatureBlock();

            writeCEH(outputList);
            this.close();
        }
    }
    public void setApkSignatureBlock(ApkSignatureBlock apkSignatureBlock) {
        this.apkSignatureBlock = apkSignatureBlock;
    }
    public ZipAligner getZipAligner() {
        return zipAligner;
    }
    public void setZipAligner(ZipAligner zipAligner) {
        this.zipAligner = zipAligner;
    }

    private void writeCEH(OutputSource[] outputList) throws IOException{
        EndRecord endRecord = new EndRecord();
        endRecord.setSignature(ZipSignature.END_RECORD);
        long offset = position();
        endRecord.setOffsetOfCentralDirectory(offset);
        int count = outputList.length;
        endRecord.setNumberOfDirectories(count);
        endRecord.setTotalNumberOfDirectories(count);
        for(int i = 0; i < count; i++){
            OutputSource outputSource = outputList[i];
            outputSource.writeCEH(this);
        }
        long cedLength = position() - offset;
        endRecord.setLengthOfCentralDirectory(cedLength);
        OutputStream outputStream = getOutputStream();
        Zip64Record zip64Record = endRecord.getZip64Record();
        if(zip64Record != null){
            long offsetOfRecord = position();
            logMessage("ZIP64: " + zip64Record);
            zip64Record.writeBytes(outputStream);
            Zip64Locator zip64Locator = endRecord.getZip64Locator();
            zip64Locator.setOffsetZip64Record(offsetOfRecord);
            logMessage("ZIP64: " + zip64Locator);
            zip64Locator.writeBytes(outputStream);
        }
        endRecord.writeBytes(getOutputStream());
    }
    private void writeApk(OutputSource[] outputList) throws IOException{
        int length = outputList.length;
        logMessage("Writing files: " + length);
        APKLogger logger = this.apkLogger;
        for(int i = 0; i < length; i++){
            OutputSource outputSource = outputList[i];
            outputSource.setAPKLogger(logger);
            outputSource.writeApk( this);
            if(i % 100 == 0){
                outputSource.logFileWrite();
            }
        }
    }
    private void writeSignatureBlock() throws IOException {
        ApkSignatureBlock signatureBlock = this.apkSignatureBlock;
        if(signatureBlock == null){
            return;
        }
        logMessage("Writing signature block ...");
        long offset = position();
        if(ZipHeader.isZip64Length(offset)){
            logMessage("ZIP64 mode, skip writing signature block!");
            return;
        }
        int alignment = 4096;
        int filesPadding = (int) ((alignment - (offset % alignment)) % alignment);
        OutputStream outputStream = getOutputStream();
        if(filesPadding > 0){
            outputStream.write(new byte[filesPadding]);
        }
        signatureBlock.updatePadding();
        signatureBlock.writeBytes(outputStream);
    }
    private BufferFileInput writeBuffer(OutputSource[] outputList) throws IOException {
        File bufferFile = getBufferFile();
        BufferFileOutput output = new BufferFileOutput(bufferFile);
        BufferFileInput input = new BufferFileInput(bufferFile);
        OutputSource tableSource = null;
        int length = outputList.length;
        for(int i = 0; i < length; i++){
            OutputSource outputSource = outputList[i];
            InputSource inputSource = outputSource.getInputSource();
            if(tableSource == null && TableBlock.FILE_NAME.equals(inputSource.getAlias())){
                tableSource = outputSource;
                continue;
            }
            onCompressFileProgress(inputSource.getAlias(),
                    inputSource.getMethod(),
                    output.position());
            outputSource.makeBuffer(input, output);
        }
        if(tableSource != null){
            tableSource.makeBuffer(input, output);
        }
        output.close();
        return input;
    }
    private File getBufferFile(){
        File file = getFile();
        File dir = file.getParentFile();
        String name = file.getAbsolutePath();
        name = "tmp" + name.hashCode();
        File bufFile;
        if(dir != null){
            bufFile = new File(dir, name);
        }else {
            bufFile = new File(name);
        }
        bufFile.deleteOnExit();
        return bufFile;
    }
    private OutputSource[] buildOutputEntries(){
        InputSource[] sourceList = this.sourceList;
        int length = sourceList.length;
        OutputSource[] results = new OutputSource[length];
        for(int i = 0; i < length; i++){
            InputSource inputSource = sourceList[i];
            results[i] = toOutputSource(inputSource);
        }
        return results;
    }
    private OutputSource toOutputSource(InputSource inputSource){
        if(inputSource instanceof ArchiveFileEntrySource){
            return new ArchiveOutputSource(inputSource);
        }
        if(inputSource instanceof RenamedInputSource){
            RenamedInputSource renamedInputSource = ((RenamedInputSource) inputSource);
            if(renamedInputSource.getParentInputSource(ArchiveFileEntrySource.class) != null){
                return new RenamedArchiveSource(renamedInputSource);
            }
        }
        return new OutputSource(inputSource);
    }

    public void setWriteProgress(WriteProgress writeProgress){
        this.writeProgress = writeProgress;
    }

    private void onCompressFileProgress(String path, int mode, long writtenBytes) {
        if(writeProgress!=null){
            writeProgress.onCompressFile(path, mode, writtenBytes);
        }
    }

    APKLogger getApkLogger(){
        return apkLogger;
    }
    public void setAPKLogger(APKLogger logger) {
        this.apkLogger = logger;
    }
    private 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);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy