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 
PayloadSpecs()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      */
forNonStreaming(File packageFile)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      */
forStreaming(String updateUrl, long offset, long size, File propertiesFile)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      */
specToString(PayloadSpec payloadSpec)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