Add ImageArhive
It's a new class that models an image archive (images.tar.gz) either in
the sdcard or in the internet. The class is responsible for instaling
the archive.
Bug: N/A
Test: N/A
Change-Id: I30fe088f6b27a9a1969e98974fae222b74869e92
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.java b/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.java
new file mode 100644
index 0000000..12f485a
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 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.android.virtualization.terminal;
+
+import android.os.Build;
+import android.os.Environment;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.function.Function;
+
+/**
+ * ImageArchive models the archive file (images.tar.gz) where VM payload files are in. This class
+ * provides methods for handling the archive file, most importantly installing it.
+ */
+class ImageArchive {
+ private static final String DIR_IN_SDCARD = "linux";
+ private static final String ARCHIVE_NAME = "images.tar.gz";
+ private static final String HOST_URL = "https://dl.google.com/android/ferrochrome/latest";
+
+ // Only one can be non-null
+ private final URL mUrl;
+ private final Path mPath;
+
+ private ImageArchive(URL url) {
+ mUrl = url;
+ mPath = null;
+ }
+
+ private ImageArchive(Path path) {
+ mUrl = null;
+ mPath = path;
+ }
+
+ /** Creates ImageArchive which is located in the sdcard. This archive is for testing only. */
+ public static ImageArchive fromSdCard() {
+ Path dir = Environment.getExternalStoragePublicDirectory(DIR_IN_SDCARD).toPath();
+ Path file = dir.resolve(ARCHIVE_NAME);
+ return new ImageArchive(file);
+ }
+
+ /** Creates ImageArchive which is hosted in the Google server. This is the official archive. */
+ public static ImageArchive fromInternet() {
+ String arch = Arrays.asList(Build.SUPPORTED_ABIS).contains("x86_64") ? "x86_64" : "aarch64";
+ try {
+ URL url = new URL(HOST_URL + "/" + arch + "/" + ARCHIVE_NAME);
+ return new ImageArchive(url);
+ } catch (MalformedURLException e) {
+ // cannot happen
+ throw new RuntimeException(e);
+ }
+ }
+
+ private InputStream getInputStream(Function<InputStream, InputStream> filter)
+ throws IOException {
+ InputStream is = mPath != null ? new FileInputStream(mPath.toFile()) : mUrl.openStream();
+ BufferedInputStream bufStream = new BufferedInputStream(is);
+ return filter == null ? bufStream : filter.apply(bufStream);
+ }
+
+ /**
+ * Installs this ImageArchive to a directory pointed by path. filter can be supplied to provide
+ * an additional input stream which will be used during the installation.
+ */
+ public void installTo(Path path, Function<InputStream, InputStream> filter) throws IOException {
+ try (InputStream stream = getInputStream(filter);
+ GzipCompressorInputStream gzStream = new GzipCompressorInputStream(stream);
+ TarArchiveInputStream tarStream = new TarArchiveInputStream(gzStream)) {
+
+ Files.createDirectories(path);
+ ArchiveEntry entry;
+ while ((entry = tarStream.getNextEntry()) != null) {
+ Path to = path.resolve(entry.getName());
+ if (Files.isDirectory(to)) {
+ Files.createDirectories(to);
+ } else {
+ Files.copy(tarStream, to, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ }
+ postInstall();
+ }
+
+ // To save storage, delete the source archive on the disk.
+ private void postInstall() throws IOException {
+ if (mPath != null) {
+ Files.deleteIfExists(mPath);
+ }
+ }
+}