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