1 /* 2 * Copyright (C) 2018 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.example.android.systemupdatersample.util; 18 19 import com.example.android.systemupdatersample.PayloadSpec; 20 21 import java.io.BufferedReader; 22 import java.io.File; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.io.InputStreamReader; 26 import java.nio.file.Files; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.Enumeration; 30 import java.util.List; 31 import java.util.zip.ZipEntry; 32 import java.util.zip.ZipFile; 33 34 /** The helper class that creates {@link PayloadSpec}. */ 35 public class PayloadSpecs { 36 37 public PayloadSpecs() {} 38 39 /** 40 * The payload PAYLOAD_ENTRY is stored in the zip package to comply with the Android OTA package 41 * format. We want to find out the offset of the entry, so that we can pass it over to the A/B 42 * updater without making an extra copy of the payload. 43 * 44 * <p>According to Android docs, the entries are listed in the order in which they appear in the 45 * zip file. So we enumerate the entries to identify the offset of the payload file. 46 * http://developer.android.com/reference/java/util/zip/ZipFile.html#entries() 47 */ 48 public PayloadSpec forNonStreaming(File packageFile) throws IOException { 49 boolean payloadFound = false; 50 long payloadOffset = 0; 51 long payloadSize = 0; 52 53 List<String> properties = new ArrayList<>(); 54 try (ZipFile zip = new ZipFile(packageFile)) { 55 Enumeration<? extends ZipEntry> entries = zip.entries(); 56 long offset = 0; 57 while (entries.hasMoreElements()) { 58 ZipEntry entry = entries.nextElement(); 59 String name = entry.getName(); 60 // Zip local file header has 30 bytes + filename + sizeof extra field. 61 // https://en.wikipedia.org/wiki/Zip_(file_format) 62 long extraSize = entry.getExtra() == null ? 0 : entry.getExtra().length; 63 offset += 30 + name.length() + extraSize; 64 65 if (entry.isDirectory()) { 66 continue; 67 } 68 69 long length = entry.getCompressedSize(); 70 if (PackageFiles.PAYLOAD_BINARY_FILE_NAME.equals(name)) { 71 if (entry.getMethod() != ZipEntry.STORED) { 72 throw new IOException("Invalid compression method."); 73 } 74 payloadFound = true; 75 payloadOffset = offset; 76 payloadSize = length; 77 } else if (PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME.equals(name)) { 78 InputStream inputStream = zip.getInputStream(entry); 79 if (inputStream != null) { 80 BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); 81 String line; 82 while ((line = br.readLine()) != null) { 83 properties.add(line); 84 } 85 } 86 } 87 offset += length; 88 } 89 } 90 91 if (!payloadFound) { 92 throw new IOException("Failed to find payload entry in the given package."); 93 } 94 return PayloadSpec.newBuilder() 95 .url("file://" + packageFile.getAbsolutePath()) 96 .offset(payloadOffset) 97 .size(payloadSize) 98 .properties(properties) 99 .build(); 100 } 101 102 /** 103 * Creates a {@link PayloadSpec} for streaming update. 104 */ 105 public PayloadSpec forStreaming(String updateUrl, 106 long offset, 107 long size, 108 File propertiesFile) throws IOException { 109 return PayloadSpec.newBuilder() 110 .url(updateUrl) 111 .offset(offset) 112 .size(size) 113 .properties(Files.readAllLines(propertiesFile.toPath())) 114 .build(); 115 } 116 117 /** 118 * Converts an {@link PayloadSpec} to a string. 119 */ 120 public String specToString(PayloadSpec payloadSpec) { 121 return "<PayloadSpec url=" + payloadSpec.getUrl() 122 + ", offset=" + payloadSpec.getOffset() 123 + ", size=" + payloadSpec.getSize() 124 + ", properties=" + Arrays.toString( 125 payloadSpec.getProperties().toArray(new String[0])) 126 + ">"; 127 } 128 129 } 130