Replace InstalUtils with ImageArchive and InstalledImage
Bug: N/A
Test: N/A
Change-Id: I7a4ef524ec90c55127f84067f5b51d577774c78d
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
index 5cf123e..ab03049 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
@@ -87,7 +87,7 @@
private static String replaceKeywords(Reader r, Context context) throws IOException {
Map<String, String> rules = new HashMap<>();
- rules.put("\\$PAYLOAD_DIR", InstallUtils.getInternalStorageDir(context).toString());
+ rules.put("\\$PAYLOAD_DIR", InstalledImage.getDefault(context).getInstallDir().toString());
rules.put("\\$USER_ID", String.valueOf(context.getUserId()));
rules.put("\\$PACKAGE_NAME", context.getPackageName());
String appDataDir = context.getDataDir().toString();
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java
deleted file mode 100644
index 1b6da6c..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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 static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.content.Context;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-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.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-
-public class InstallUtils {
- private static final String VM_CONFIG_FILENAME = "vm_config.json";
- private static final String COMPRESSED_PAYLOAD_FILENAME = "images.tar.gz";
- private static final String ROOTFS_FILENAME = "root_part";
- private static final String BACKUP_FILENAME = "root_part_backup";
- private static final String INSTALLATION_COMPLETED_FILENAME = "completed";
- private static final String PAYLOAD_DIR = "linux";
-
- public static Path getVmConfigPath(Context context) {
- return getInternalStorageDir(context).resolve(VM_CONFIG_FILENAME);
- }
-
- public static boolean isImageInstalled(Context context) {
- return Files.exists(getInstallationCompletedPath(context));
- }
-
- public static void backupRootFs(Context context) throws IOException {
- Files.move(
- getRootfsFile(context),
- getBackupFile(context),
- StandardCopyOption.REPLACE_EXISTING);
- }
-
- public static boolean createInstalledMarker(Context context) {
- try {
- Path path = getInstallationCompletedPath(context);
- Files.createFile(path);
- return true;
- } catch (IOException e) {
- Log.e(TAG, "Failed to mark install completed", e);
- return false;
- }
- }
-
- @VisibleForTesting
- public static void deleteInstallation(Context context) {
- FileUtils.deleteContentsAndDir(getInternalStorageDir(context).toFile());
- }
-
- private static Path getPayloadPath() {
- File payloadDir = Environment.getExternalStoragePublicDirectory(PAYLOAD_DIR);
- if (payloadDir == null) {
- Log.d(TAG, "no payload dir: " + payloadDir);
- return null;
- }
- Path payloadPath = payloadDir.toPath().resolve(COMPRESSED_PAYLOAD_FILENAME);
- return payloadPath;
- }
-
- public static boolean payloadFromExternalStorageExists() {
- return Files.exists(getPayloadPath());
- }
-
- public static Path getInternalStorageDir(Context context) {
- return context.getFilesDir().toPath().resolve(PAYLOAD_DIR);
- }
-
- public static Path getBackupFile(Context context) {
- return context.getFilesDir().toPath().resolve(BACKUP_FILENAME);
- }
-
- private static Path getInstallationCompletedPath(Context context) {
- return getInternalStorageDir(context).resolve(INSTALLATION_COMPLETED_FILENAME);
- }
-
- public static boolean installImageFromExternalStorage(Context context) {
- if (!payloadFromExternalStorageExists()) {
- Log.d(TAG, "no artifact file from external storage");
- return false;
- }
- Path payloadPath = getPayloadPath();
- try (BufferedInputStream inputStream =
- new BufferedInputStream(Files.newInputStream(payloadPath));
- TarArchiveInputStream tar =
- new TarArchiveInputStream(new GzipCompressorInputStream(inputStream))) {
- ArchiveEntry entry;
- Path baseDir = new File(context.getFilesDir(), PAYLOAD_DIR).toPath();
- Files.createDirectories(baseDir);
- while ((entry = tar.getNextEntry()) != null) {
- Path extractTo = baseDir.resolve(entry.getName());
- if (entry.isDirectory()) {
- Files.createDirectories(extractTo);
- } else {
- Files.copy(tar, extractTo, StandardCopyOption.REPLACE_EXISTING);
- }
- }
- } catch (IOException e) {
- Log.e(TAG, "installation failed", e);
- return false;
- }
-
- // remove payload if installation is done.
- try {
- Files.deleteIfExists(payloadPath);
- } catch (IOException e) {
- Log.d(TAG, "failed to remove installed payload", e);
- }
-
- // Create marker for installation done.
- return createInstalledMarker(context);
- }
-
- public static Path getRootfsFile(Context context) throws FileNotFoundException {
- Path path = getInternalStorageDir(context).resolve(ROOTFS_FILENAME);
- if (!Files.exists(path)) {
- Log.d(TAG, path.toString() + " - file not found");
- throw new FileNotFoundException("File not found: " + ROOTFS_FILENAME);
- }
- return path;
- }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.java
index deb0c14..623fbe4 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.java
@@ -85,7 +85,7 @@
return mBackup;
}
- public Path backupFile() {
+ public Path getBackupFile() {
return mBackup;
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
index 69b5ee7..1c62572 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
@@ -113,7 +113,7 @@
public void onResume() {
super.onResume();
- if (Build.isDebuggable() && InstallUtils.payloadFromExternalStorageExists()) {
+ if (Build.isDebuggable() && ImageArchive.fromSdCard().exists()) {
showSnackbar("Auto installing", Snackbar.LENGTH_LONG);
requestInstall();
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
index a8b4ca2..c2b3fd4 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
@@ -28,7 +28,6 @@
import android.net.NetworkCapabilities;
import android.os.Build;
import android.os.IBinder;
-import android.os.SELinux;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -36,23 +35,13 @@
import com.android.internal.annotations.GuardedBy;
-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.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.SocketException;
-import java.net.URL;
import java.net.UnknownHostException;
-import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
import java.util.Arrays;
-import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -171,15 +160,20 @@
}
private boolean downloadFromSdcard() {
+ ImageArchive archive = ImageArchive.fromSdCard();
+
// Installing from sdcard is preferred, but only supported only in debuggable build.
- if (Build.isDebuggable()) {
+ if (Build.isDebuggable() && archive.exists()) {
Log.i(TAG, "trying to install /sdcard/linux/images.tar.gz");
- if (InstallUtils.installImageFromExternalStorage(this)) {
+ Path dest = InstalledImage.getDefault(this).getInstallDir();
+ try {
+ archive.installTo(dest, null);
Log.i(TAG, "image is installed from /sdcard/linux/images.tar.gz");
return true;
+ } catch (IOException e) {
+ Log.i(TAG, "Failed to install /sdcard/linux/images.tar.gz", e);
}
- Log.i(TAG, "Failed to install /sdcard/linux/images.tar.gz");
} else {
Log.i(TAG, "Non-debuggable build doesn't support installation from /sdcard/linux");
}
@@ -205,23 +199,16 @@
return false;
}
- try (BufferedInputStream inputStream =
- new BufferedInputStream(new URL(IMAGE_URL).openStream());
- WifiCheckInputStream wifiInputStream =
- new WifiCheckInputStream(inputStream, isWifiOnly);
- TarArchiveInputStream tar =
- new TarArchiveInputStream(new GzipCompressorInputStream(wifiInputStream))) {
- ArchiveEntry entry;
- Path baseDir = InstallUtils.getInternalStorageDir(this);
- Files.createDirectories(baseDir);
- while ((entry = tar.getNextEntry()) != null) {
- Path extractTo = baseDir.resolve(entry.getName());
- if (entry.isDirectory()) {
- Files.createDirectories(extractTo);
- } else {
- Files.copy(tar, extractTo, StandardCopyOption.REPLACE_EXISTING);
- }
- }
+ Path dest = InstalledImage.getDefault(this).getInstallDir();
+ try {
+ ImageArchive.fromInternet()
+ .installTo(
+ dest,
+ is -> {
+ WifiCheckInputStream filter = new WifiCheckInputStream(is);
+ filter.setWifiOnly(isWifiOnly);
+ return filter;
+ });
} catch (WifiCheckInputStream.NoWifiException e) {
Log.e(TAG, "Install failed because of Wi-Fi is gone");
notifyError(getString(R.string.installer_error_no_wifi));
@@ -236,8 +223,7 @@
notifyError(getString(R.string.installer_error_unknown));
return false;
}
-
- return InstallUtils.createInstalledMarker(this);
+ return true;
}
private void notifyError(String displayText) {
@@ -311,7 +297,7 @@
public boolean isInstalled() {
InstallerService service = ensureServiceConnected();
synchronized (service.mLock) {
- return !service.mIsInstalling && InstallUtils.isImageInstalled(service);
+ return !service.mIsInstalling && InstalledImage.getDefault(service).isInstalled();
}
}
}
@@ -320,11 +306,14 @@
private static final int READ_BYTES = 1024;
private final InputStream mInputStream;
- private final boolean mIsWifiOnly;
+ private boolean mIsWifiOnly;
- public WifiCheckInputStream(InputStream is, boolean isWifiOnly) {
+ public WifiCheckInputStream(InputStream is) {
super();
mInputStream = is;
+ }
+
+ public void setWifiOnly(boolean isWifiOnly) {
mIsWifiOnly = isWifiOnly;
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 6740778..deef825 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -59,7 +59,6 @@
import com.google.android.material.appbar.MaterialToolbar;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
@@ -445,8 +444,8 @@
Intent tapIntent = new Intent(this, MainActivity.class);
tapIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- PendingIntent tapPendingIntent = PendingIntent.getActivity(this, 0, tapIntent,
- PendingIntent.FLAG_IMMUTABLE);
+ PendingIntent tapPendingIntent =
+ PendingIntent.getActivity(this, 0, tapIntent, PendingIntent.FLAG_IMMUTABLE);
Intent settingsIntent = new Intent(this, SettingsActivity.class);
settingsIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
index 4b6bf96..00730ff 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
@@ -56,7 +56,8 @@
dialog.show()
}
val resetBackupCard = findViewById<View>(R.id.settings_recovery_reset_backup_card)
- resetBackupCard.isVisible = Files.exists(InstallUtils.getBackupFile(this))
+
+ resetBackupCard.isVisible = InstalledImage.getDefault(this).hasBackup()
resetBackupCard.setOnClickListener {
val dialog = MaterialAlertDialogBuilder(this)
@@ -74,9 +75,8 @@
}
private fun removeBackup(): Unit {
- val file = InstallUtils.getBackupFile(this@SettingsRecoveryActivity)
try {
- Files.deleteIfExists(file)
+ InstalledImage.getDefault(this).deleteBackup()
} catch (e: IOException) {
Snackbar.make(
findViewById(android.R.id.content),
@@ -89,12 +89,14 @@
private fun uninstall(backupRootfs: Boolean): Unit {
var backupDone = false
+ val image = InstalledImage.getDefault(this)
try {
if (backupRootfs) {
- InstallUtils.backupRootFs(this@SettingsRecoveryActivity)
+ image.uninstallAndBackup()
backupDone = true
+ } else {
+ image.uninstallFully()
}
- InstallUtils.deleteInstallation(this@SettingsRecoveryActivity)
} catch (e: IOException) {
val errorMsgId = if (backupRootfs && !backupDone) R.string.settings_recovery_error_due_to_backup
else R.string.settings_recovery_error;
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
index b8a427c..c2d224a 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
@@ -153,7 +153,8 @@
}
mExecutorService = Executors.newCachedThreadPool();
- ConfigJson json = ConfigJson.from(this, InstallUtils.getVmConfigPath(this));
+ InstalledImage image = InstalledImage.getDefault(this);
+ ConfigJson json = ConfigJson.from(this, image.getConfigPath());
VirtualMachineConfig.Builder configBuilder = json.toConfigBuilder(this);
VirtualMachineCustomImageConfig.Builder customImageConfigBuilder =
json.toCustomImageConfigBuilder(this);
@@ -219,9 +220,10 @@
changed = true;
}
- Path backupFile = InstallUtils.getBackupFile(this);
- if (Files.exists(backupFile)) {
- builder.addDisk(Disk.RWDisk(backupFile.toString()));
+ InstalledImage image = InstalledImage.getDefault(this);
+ if (image.hasBackup()) {
+ Path backup = image.getBackupFile();
+ builder.addDisk(Disk.RWDisk(backup.toString()));
changed = true;
}
return changed;
diff --git a/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java b/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
index 3c0461d..42c31e3 100644
--- a/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
+++ b/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
@@ -33,6 +33,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -88,7 +89,7 @@
}
@After
- public void tearDown() {
- InstallUtils.deleteInstallation(mTargetContext);
+ public void tearDown() throws IOException {
+ InstalledImage.getDefault(mTargetContext).uninstallFully();
}
}