Home | History | Annotate | Download | only in zfile
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tools.build.apkzlib.zfile;
     18 
     19 import com.android.tools.build.apkzlib.zip.AlignmentRule;
     20 import com.android.tools.build.apkzlib.zip.AlignmentRules;
     21 import com.android.tools.build.apkzlib.zip.StoredEntry;
     22 import com.android.tools.build.apkzlib.zip.ZFile;
     23 import com.android.tools.build.apkzlib.zip.ZFileOptions;
     24 import com.google.common.base.Preconditions;
     25 import com.google.common.io.Closer;
     26 import java.io.File;
     27 import java.io.FileInputStream;
     28 import java.io.IOException;
     29 import java.io.InputStream;
     30 import java.util.function.Function;
     31 import java.util.function.Predicate;
     32 import javax.annotation.Nonnull;
     33 import javax.annotation.Nullable;
     34 
     35 /**
     36  * {@link ApkCreator} that uses {@link ZFileOptions} to generate the APK.
     37  */
     38 class ApkZFileCreator implements ApkCreator {
     39 
     40     /**
     41      * Suffix for native libraries.
     42      */
     43     private static final String NATIVE_LIBRARIES_SUFFIX = ".so";
     44 
     45     /**
     46      * Shared libraries are alignment at 4096 boundaries.
     47      */
     48     private static final AlignmentRule SO_RULE =
     49             AlignmentRules.constantForSuffix(NATIVE_LIBRARIES_SUFFIX, 4096);
     50 
     51     /**
     52      * The zip file.
     53      */
     54     @Nonnull
     55     private final ZFile zip;
     56 
     57     /**
     58      * Has the zip file been closed?
     59      */
     60     private boolean closed;
     61 
     62     /**
     63      * Predicate defining which files should not be compressed.
     64      */
     65     @Nonnull
     66     private final Predicate<String> noCompressPredicate;
     67 
     68     /**
     69      * Creates a new creator.
     70      *
     71      * @param creationData the data needed to create the APK
     72      * @param options zip file options
     73      * @throws IOException failed to create the zip
     74      */
     75     ApkZFileCreator(
     76             @Nonnull ApkCreatorFactory.CreationData creationData,
     77             @Nonnull ZFileOptions options)
     78             throws IOException {
     79 
     80         switch (creationData.getNativeLibrariesPackagingMode()) {
     81             case COMPRESSED:
     82                 noCompressPredicate = creationData.getNoCompressPredicate();
     83                 break;
     84             case UNCOMPRESSED_AND_ALIGNED:
     85                 noCompressPredicate =
     86                         creationData.getNoCompressPredicate().or(
     87                                 name -> name.endsWith(NATIVE_LIBRARIES_SUFFIX));
     88                 options.setAlignmentRule(
     89                         AlignmentRules.compose(SO_RULE, options.getAlignmentRule()));
     90                 break;
     91             default:
     92                 throw new AssertionError();
     93         }
     94 
     95         zip = ZFiles.apk(
     96                 creationData.getApkPath(),
     97                 options,
     98                 creationData.getPrivateKey(),
     99                 creationData.getCertificate(),
    100                 creationData.isV1SigningEnabled(),
    101                 creationData.isV2SigningEnabled(),
    102                 creationData.getBuiltBy(),
    103                 creationData.getCreatedBy(),
    104                 creationData.getMinSdkVersion());
    105         closed = false;
    106     }
    107 
    108     @Override
    109     public void writeZip(@Nonnull File zip, @Nullable Function<String, String> transform,
    110             @Nullable Predicate<String> isIgnored) throws IOException {
    111         Preconditions.checkState(!closed, "closed == true");
    112         Preconditions.checkArgument(zip.isFile(), "!zip.isFile()");
    113 
    114         Closer closer = Closer.create();
    115         try {
    116             ZFile toMerge = closer.register(new ZFile(zip));
    117 
    118             Predicate<String> ignorePredicate;
    119             if (isIgnored == null) {
    120                 ignorePredicate = s -> false;
    121             } else {
    122                 ignorePredicate = isIgnored;
    123             }
    124 
    125             // Files that *must* be uncompressed in the result should not be merged and should be
    126             // added after. This is just very slightly less efficient than ignoring just the ones
    127             // that were compressed and must be uncompressed, but it is a lot simpler :)
    128             Predicate<String> noMergePredicate = ignorePredicate.or(noCompressPredicate);
    129 
    130             this.zip.mergeFrom(toMerge, noMergePredicate);
    131 
    132             for (StoredEntry toMergeEntry : toMerge.entries()) {
    133                 String path = toMergeEntry.getCentralDirectoryHeader().getName();
    134                 if (noCompressPredicate.test(path) && !ignorePredicate.test(path)) {
    135                     // This entry *must* be uncompressed so it was ignored in the merge and should
    136                     // now be added to the apk.
    137                     try (InputStream ignoredData = toMergeEntry.open()) {
    138                         this.zip.add(path, ignoredData, false);
    139                     }
    140                 }
    141             }
    142         } catch (Throwable t) {
    143             throw closer.rethrow(t);
    144         } finally {
    145             closer.close();
    146         }
    147     }
    148 
    149     @Override
    150     public void writeFile(@Nonnull File inputFile, @Nonnull String apkPath) throws IOException {
    151         Preconditions.checkState(!closed, "closed == true");
    152 
    153         boolean mayCompress = !noCompressPredicate.test(apkPath);
    154 
    155         Closer closer = Closer.create();
    156         try {
    157             FileInputStream inputFileStream = closer.register(new FileInputStream(inputFile));
    158             zip.add(apkPath, inputFileStream, mayCompress);
    159         } catch (IOException e) {
    160             throw closer.rethrow(e, IOException.class);
    161         } catch (Throwable t) {
    162             throw closer.rethrow(t);
    163         } finally {
    164             closer.close();
    165         }
    166     }
    167 
    168     @Override
    169     public void deleteFile(@Nonnull String apkPath) throws IOException {
    170         Preconditions.checkState(!closed, "closed == true");
    171 
    172         StoredEntry entry = zip.get(apkPath);
    173         if (entry != null) {
    174             entry.delete();
    175         }
    176     }
    177 
    178     @Override
    179     public boolean hasPendingChangesWithWait() throws IOException {
    180         return zip.hasPendingChangesWithWait();
    181     }
    182 
    183     @Override
    184     public void close() throws IOException {
    185         if (closed) {
    186             return;
    187         }
    188 
    189         zip.close();
    190         closed = true;
    191     }
    192 }
    193