summaryrefslogtreecommitdiffstats
path: root/updater_sample/src/com/example/android/systemupdatersample/util/PayloadSpecs.java
blob: 4db448a31ad37f0f90003499e21fbe31b07bca32 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.systemupdatersample.util;

import com.example.android.systemupdatersample.PayloadSpec;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/** The helper class that creates {@link PayloadSpec}. */
public final class PayloadSpecs {

    /**
     * The payload PAYLOAD_ENTRY is stored in the zip package to comply with the Android OTA package
     * format. We want to find out the offset of the entry, so that we can pass it over to the A/B
     * updater without making an extra copy of the payload.
     *
     * <p>According to Android docs, the entries are listed in the order in which they appear in the
     * zip file. So we enumerate the entries to identify the offset of the payload file.
     * http://developer.android.com/reference/java/util/zip/ZipFile.html#entries()
     */
    public static PayloadSpec forNonStreaming(File packageFile) throws IOException {
        boolean payloadFound = false;
        long payloadOffset = 0;
        long payloadSize = 0;

        List<String> properties = new ArrayList<>();
        try (ZipFile zip = new ZipFile(packageFile)) {
            Enumeration<? extends ZipEntry> entries = zip.entries();
            long offset = 0;
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                String name = entry.getName();
                // Zip local file header has 30 bytes + filename + sizeof extra field.
                // https://en.wikipedia.org/wiki/Zip_(file_format)
                long extraSize = entry.getExtra() == null ? 0 : entry.getExtra().length;
                offset += 30 + name.length() + extraSize;

                if (entry.isDirectory()) {
                    continue;
                }

                long length = entry.getCompressedSize();
                if (PackageFiles.PAYLOAD_BINARY_FILE_NAME.equals(name)) {
                    if (entry.getMethod() != ZipEntry.STORED) {
                        throw new IOException("Invalid compression method.");
                    }
                    payloadFound = true;
                    payloadOffset = offset;
                    payloadSize = length;
                } else if (PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME.equals(name)) {
                    InputStream inputStream = zip.getInputStream(entry);
                    if (inputStream != null) {
                        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
                        String line;
                        while ((line = br.readLine()) != null) {
                            properties.add(line);
                        }
                    }
                }
                offset += length;
            }
        }

        if (!payloadFound) {
            throw new IOException("Failed to find payload entry in the given package.");
        }
        return PayloadSpec.newBuilder()
                        .url("file://" + packageFile.getAbsolutePath())
                        .offset(payloadOffset)
                        .size(payloadSize)
                        .properties(properties)
                        .build();
    }

    /**
     * Creates a {@link PayloadSpec} for streaming update.
     */
    public static PayloadSpec forStreaming(String updateUrl,
                                           long offset,
                                           long size,
                                           File propertiesFile) throws IOException {
        return PayloadSpec.newBuilder()
                .url(updateUrl)
                .offset(offset)
                .size(size)
                .properties(Files.readAllLines(propertiesFile.toPath()))
                .build();
    }

    /**
     * Converts an {@link PayloadSpec} to a string.
     */
    public static String toString(PayloadSpec payloadSpec) {
        return "<PayloadSpec url=" + payloadSpec.getUrl()
                + ", offset=" + payloadSpec.getOffset()
                + ", size=" + payloadSpec.getSize()
                + ", properties=" + Arrays.toString(
                        payloadSpec.getProperties().toArray(new String[0]))
                + ">";
    }

    private PayloadSpecs() {}

}