/* * 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. * *

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 properties = new ArrayList<>(); try (ZipFile zip = new ZipFile(packageFile)) { Enumeration 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 ""; } private PayloadSpecs() {} }