Merge "virtmgr: Remove writeback=true and CachePolicy options" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 1212525..416f4c9 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.content.SharedPreferences;
 import android.graphics.drawable.Icon;
 import android.graphics.fonts.FontStyle;
 import android.net.http.SslError;
@@ -85,15 +86,6 @@
         super.onCreate(savedInstanceState);
 
         boolean launchInstaller = installIfNecessary();
-        try {
-            // No resize for now.
-            long newSizeInBytes = 0;
-            diskResize(this, newSizeInBytes);
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to resize disk", e);
-            Toast.makeText(this, "Error resizing disk: " + e.getMessage(), Toast.LENGTH_LONG)
-                    .show();
-        }
 
         NotificationManager notificationManager = getSystemService(NotificationManager.class);
         if (notificationManager.getNotificationChannel(TAG) == null) {
@@ -247,12 +239,11 @@
                 .start();
     }
 
-    private void diskResize(Context context, long sizeInBytes) throws IOException {
+    private void diskResize(File file, long sizeInBytes) throws IOException {
         try {
             if (sizeInBytes == 0) {
                 return;
             }
-            File file = getPartitionFile(context, "root_part");
             String filePath = file.getAbsolutePath();
             Log.d(TAG, "Disk-resize in progress for partition: " + filePath);
 
@@ -276,7 +267,7 @@
             throws FileNotFoundException {
         File file = new File(context.getFilesDir(), fileName);
         if (!file.exists()) {
-            Log.d(TAG, fileName + " - file not found");
+            Log.d(TAG, file.getAbsolutePath() + " - file not found");
             throw new FileNotFoundException("File not found: " + fileName);
         }
         return file;
@@ -321,10 +312,11 @@
         }
     }
 
-    private static void runCommand(String... command) throws IOException {
+    private static String runCommand(String... command) throws IOException {
         try {
             Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
             process.waitFor();
+            return new String(process.getInputStream().readAllBytes());
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
             throw new IOException("Command interrupted", e);
@@ -428,6 +420,9 @@
         if (!InstallUtils.isImageInstalled(this)) {
             return;
         }
+
+        resizeDiskIfNecessary();
+
         // TODO: implement intent for setting, close and tap to the notification
         // Currently mock a PendingIntent for notification.
         Intent intent = new Intent();
@@ -453,4 +448,57 @@
         android.os.Trace.beginAsyncSection("executeTerminal", 0);
         VmLauncherServices.startVmLauncherService(this, this, notification);
     }
+
+    private long roundUpDiskSize(long diskSize) {
+        // Round up every disk_size_round_up_step_size_in_mb MB
+        int disk_size_step = getResources().getInteger(
+                R.integer.disk_size_round_up_step_size_in_mb) * 1024 * 1024;
+        return (long) Math.ceil(((double) diskSize) / disk_size_step) * disk_size_step;
+    }
+
+    private long getMinFilesystemSize(File file) throws IOException, NumberFormatException {
+        try {
+            String result = runCommand("/system/bin/resize2fs", "-P", file.getAbsolutePath());
+            // The return value is the number of 4k block
+            long minSize = Long.parseLong(
+                    result.lines().toArray(String[]::new)[1].substring(42)) * 4 * 1024;
+            return roundUpDiskSize(minSize);
+        } catch (IOException | NumberFormatException e) {
+            Log.e(TAG, "Failed to get filesystem size", e);
+            throw e;
+        }
+    }
+
+    private static long getFilesystemSize(File file) throws ErrnoException {
+        return Os.stat(file.getAbsolutePath()).st_size;
+    }
+
+    private void resizeDiskIfNecessary() {
+        try {
+            File file = getPartitionFile(this, "root_part");
+            SharedPreferences sharedPref = this.getSharedPreferences(
+                    getString(R.string.preference_file_key), Context.MODE_PRIVATE);
+            SharedPreferences.Editor editor = sharedPref.edit();
+
+            long minDiskSize = getMinFilesystemSize(file);
+            editor.putLong(getString(R.string.preference_min_disk_size_key), minDiskSize);
+
+            long currentDiskSize = getFilesystemSize(file);
+            long newSizeInBytes = sharedPref.getLong(getString(R.string.preference_disk_size_key),
+                    roundUpDiskSize(currentDiskSize));
+            editor.putLong(getString(R.string.preference_disk_size_key), newSizeInBytes);
+            editor.apply();
+
+            Log.d(TAG, "Current disk size: " + currentDiskSize);
+            Log.d(TAG, "Targeting disk size: " + newSizeInBytes);
+
+            if (newSizeInBytes != currentDiskSize) {
+                diskResize(file, newSizeInBytes);
+            }
+        } catch (FileNotFoundException e) {
+            Log.d(TAG, "No partition file");
+        } catch (IOException | ErrnoException | NumberFormatException e) {
+            Log.e(TAG, "Failed to resize disk", e);
+        }
+    }
 }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
index 1b14ef2..54e8ab2 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
@@ -15,45 +15,77 @@
  */
 package com.android.virtualization.terminal
 
+import android.content.Context
+import android.content.Intent
 import android.os.Bundle
 import android.os.FileUtils
-import android.widget.TextView
-import android.widget.Toast
-import android.text.style.RelativeSizeSpan
-import android.text.Spannable
 import android.text.SpannableString
 import android.text.Spanned
-import android.text.format.Formatter
 import android.text.TextUtils
+import android.text.format.Formatter
+import android.text.style.RelativeSizeSpan
+import android.widget.TextView
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.isVisible
 import com.google.android.material.button.MaterialButton
 import com.google.android.material.slider.Slider
-
-import java.util.regex.Matcher
 import java.util.regex.Pattern
 
 class SettingsDiskResizeActivity : AppCompatActivity() {
-    private val maxDiskSize: Float = 256F
+    private val maxDiskSizeMb: Float = (16 shl 10).toFloat()
     private val numberPattern: Pattern = Pattern.compile("[\\d]*[\\٫.,]?[\\d]+");
-    private var diskSize: Float = 104F
+
+    private fun bytesToMb(bytes: Long): Long {
+        return bytes shr 20;
+    }
+
+    private fun mbToBytes(bytes: Long): Long {
+        return bytes shl 20;
+    }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.settings_disk_resize)
+        val sharedPref =
+            this.getSharedPreferences(getString(R.string.preference_file_key), Context.MODE_PRIVATE)
+        var diskSizeMb =
+            bytesToMb(
+                sharedPref.getLong(
+                    getString(R.string.preference_disk_size_key),
+                    0
+                )
+            ).toFloat();
+        val minDiskSizeMb =
+            bytesToMb(
+                    sharedPref.getLong(
+                    getString(R.string.preference_min_disk_size_key),
+                    0
+                )
+            ).toFloat();
+
         val diskSizeText = findViewById<TextView>(R.id.settings_disk_resize_resize_gb_assigned)
         val diskMaxSizeText = findViewById<TextView>(R.id.settings_disk_resize_resize_gb_max)
         diskMaxSizeText.text = getString(R.string.settings_disk_resize_resize_gb_max_format,
-            localizedFileSize(maxDiskSize));
+            localizedFileSize(maxDiskSizeMb)
+        );
 
         val diskSizeSlider = findViewById<Slider>(R.id.settings_disk_resize_disk_size_slider)
-        diskSizeSlider.setValueTo(maxDiskSize)
+        diskSizeSlider.setValueTo(maxDiskSizeMb)
         val cancelButton = findViewById<MaterialButton>(R.id.settings_disk_resize_cancel_button)
         val resizeButton = findViewById<MaterialButton>(R.id.settings_disk_resize_resize_button)
-        diskSizeSlider.value = diskSize
+        diskSizeSlider.valueFrom = minDiskSizeMb
+        diskSizeSlider.valueTo = maxDiskSizeMb
+        diskSizeSlider.value = diskSizeMb
+        diskSizeSlider.stepSize =
+            resources.getInteger(R.integer.disk_size_round_up_step_size_in_mb).toFloat()
+        diskSizeSlider.setLabelFormatter { value: Float ->
+            localizedFileSize(value)
+        }
         diskSizeText.text = enlargeFontOfNumber(
             getString(R.string.settings_disk_resize_resize_gb_assigned_format,
-            localizedFileSize(diskSize)))
+                localizedFileSize(diskSizeMb)
+            )
+        )
 
         diskSizeSlider.addOnChangeListener { _, value, _ ->
             diskSizeText.text = enlargeFontOfNumber(
@@ -63,24 +95,35 @@
             resizeButton.isVisible = true
         }
         cancelButton.setOnClickListener {
-            diskSizeSlider.value = diskSize
+            diskSizeSlider.value = diskSizeMb
             cancelButton.isVisible = false
             resizeButton.isVisible = false
         }
 
         resizeButton.setOnClickListener {
-            diskSize = diskSizeSlider.value
+            diskSizeMb = diskSizeSlider.value
             cancelButton.isVisible = false
             resizeButton.isVisible = false
-            Toast.makeText(this@SettingsDiskResizeActivity, R.string.settings_disk_resize_resize_message, Toast.LENGTH_SHORT)
-                .show()
+            val editor = sharedPref.edit()
+            editor.putLong(
+                getString(R.string.preference_disk_size_key),
+                mbToBytes(diskSizeMb.toLong())
+            )
+            editor.apply()
+
+            // Restart terminal
+            val intent =
+                baseContext.packageManager.getLaunchIntentForPackage(baseContext.packageName)
+            intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+            finish()
+            startActivity(intent)
         }
     }
 
-    fun localizedFileSize(sizeGb: Float): String {
+    fun localizedFileSize(sizeMb: Float): String {
         // formatShortFileSize() uses SI unit (i.e. kB = 1000 bytes),
-        // so covert sizeGb with "GB" instead of "GIB".
-        val bytes = FileUtils.parseSize(sizeGb.toLong().toString() + "GB")
+        // so covert sizeMb with "MB" instead of "MIB".
+        val bytes = FileUtils.parseSize(sizeMb.toLong().toString() + "MB")
         return Formatter.formatShortFileSize(this, bytes)
     }
 
diff --git a/android/TerminalApp/res/layout/settings_disk_resize.xml b/android/TerminalApp/res/layout/settings_disk_resize.xml
index f868b28..a41b580 100644
--- a/android/TerminalApp/res/layout/settings_disk_resize.xml
+++ b/android/TerminalApp/res/layout/settings_disk_resize.xml
@@ -42,8 +42,6 @@
             android:layout_width="match_parent"
             android:layout_marginBottom="36dp"
             app:tickVisible="false"
-            android:valueFrom="0"
-            android:stepSize="4"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent" />
 
@@ -53,6 +51,7 @@
             android:layout_height="wrap_content"
             android:text="@string/settings_disk_resize_resize_cancel"
             android:visibility="invisible"
+            android:layout_marginVertical="48dp"
             android:layout_marginHorizontal="8dp"
             app:layout_constraintTop_toTopOf="@+id/settings_disk_resize_disk_size_slider"
             app:layout_constraintBottom_toBottomOf="parent"
@@ -64,7 +63,6 @@
             android:layout_height="wrap_content"
             android:text="@string/settings_disk_resize_resize_restart_vm_to_apply"
             android:visibility="invisible"
-            android:layout_marginHorizontal="8dp"
             app:layout_constraintTop_toTopOf="@+id/settings_disk_resize_disk_size_slider"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent" />
diff --git a/android/TerminalApp/res/values/integers.xml b/android/TerminalApp/res/values/integers.xml
index 0c7d2b9..e20987c 100644
--- a/android/TerminalApp/res/values/integers.xml
+++ b/android/TerminalApp/res/values/integers.xml
@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <integer name="split_min_width">720</integer>
+    <integer name="disk_size_round_up_step_size_in_mb">4</integer>
 </resources>
\ No newline at end of file
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index f8350a0..dfe7b95 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -98,4 +98,9 @@
     <string name="service_notification_content">Click to open the terminal.</string>
     <!-- Notification action button for closing the virtual machine [CHAR LIMIT=none] -->
     <string name="service_notification_quit_action">Close</string>
+
+    <!-- Preference Keys -->
+    <string name="preference_file_key">com.android.virtualization.terminal.PREFERENCE_FILE_KEY</string>
+    <string name="preference_disk_size_key">PREFERENCE_DISK_SIZE_KEY</string>
+    <string name="preference_min_disk_size_key">PREFERENCE_MIN_DISK_SIZE_KEY</string>
 </resources>