| 930 | } |
| 931 | |
| 932 | fun recompressZip( |
| 933 | inputZip: File, |
| 934 | outputZip: File, |
| 935 | noCompressExtensions: Set<String>, |
| 936 | ) { |
| 937 | ZipInputStream(BufferedInputStream(FileInputStream(inputZip))).use { zis -> |
| 938 | ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZip))).use { zos -> |
| 939 | zos.setLevel(Deflater.BEST_COMPRESSION) |
| 940 | |
| 941 | var entry = zis.nextEntry |
| 942 | while (entry != null) { |
| 943 | if (!entry.isDirectory) { |
| 944 | val extension = entry.name.substringAfterLast('.', "").lowercase() |
| 945 | val newEntry = ZipEntry(entry.name) |
| 946 | |
| 947 | // Remove timestamps for deterministic output (-X) |
| 948 | newEntry.time = 0L |
| 949 | try { |
| 950 | newEntry.creationTime = FileTime.fromMillis(0) |
| 951 | newEntry.lastModifiedTime = FileTime.fromMillis(0) |
| 952 | newEntry.lastAccessTime = FileTime.fromMillis(0) |
| 953 | } catch (_: Throwable) { |
| 954 | } |
| 955 | |
| 956 | // Force STORE method for no-compress extensions |
| 957 | if (extension in noCompressExtensions) { |
| 958 | val byteArray = zis.readBytes() |
| 959 | newEntry.method = ZipEntry.STORED |
| 960 | newEntry.size = byteArray.size.toLong() |
| 961 | newEntry.crc = CRC32().apply { update(byteArray) }.value |
| 962 | zos.putNextEntry(newEntry) |
| 963 | zos.write(byteArray) |
| 964 | } else { |
| 965 | newEntry.method = ZipEntry.DEFLATED |
| 966 | zos.putNextEntry(newEntry) |
| 967 | zis.copyTo(zos) |
| 968 | } |
| 969 | |
| 970 | zos.closeEntry() |
| 971 | } |
| 972 | entry = zis.nextEntry |
| 973 | } |
| 974 | } |
| 975 | } |
| 976 | } |
| 977 | |
| 978 | fun signApk(apkFile: File) { |
| 979 | project.logger.lifecycle("Signing APK: ${apkFile.name}") |
no test coverage detected