VmTerminalApp: Fix a11y for resize slider

Material Slider has following limitation for a11y supports:

- Switch access gives focus twice -- one for the whole slider
  and one another for the slider thumb.
- Its contents description can't be customized and locale is
  hardcoded to Locale.US.

This fixes the a11y issues by migrating material slider to SeekBar.

Bug: 376793362
Bug: 376813681
Bug: 376799783
Test: Manually with talkback and switch access \
  - switch access doesn't give focus twice \
  - talkback reads 11 Gigabytes instead of 11 GB.
Change-Id: I6d1aa269500ef1a4b6411db55d187111dd402edd
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
index 442f896..580d20c 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
@@ -17,23 +17,31 @@
 
 import android.content.Context
 import android.content.Intent
+import android.icu.text.MeasureFormat
+import android.icu.text.NumberFormat
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
 import android.os.Bundle
-import android.os.FileUtils
 import android.text.SpannableString
 import android.text.Spanned
 import android.text.TextUtils
-import android.text.format.Formatter
 import android.text.style.RelativeSizeSpan
+import android.widget.SeekBar
 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.Locale
 import java.util.regex.Pattern
 
 class SettingsDiskResizeActivity : AppCompatActivity() {
-    private val maxDiskSizeMb: Float = (16 shl 10).toFloat()
-    private val numberPattern: Pattern = Pattern.compile("[\\d]*[\\٫.,]?[\\d]+");
+    private val maxDiskSizeMb: Long = 16 shl 10
+    private val numberPattern: Pattern = Pattern.compile("[\\d]*[\\٫.,]?[\\d]+")
+
+    private var diskSizeStepMb: Long = 0
+    private var diskSizeMb: Long = 0
+    private lateinit var diskSizeText: TextView
+    private lateinit var diskSizeSlider: SeekBar
 
     private fun bytesToMb(bytes: Long): Long {
         return bytes shr 20;
@@ -43,68 +51,73 @@
         return bytes shl 20;
     }
 
+    private fun mbToProgress(bytes: Long): Int {
+        return (bytes / diskSizeStepMb).toInt()
+    }
+
+    private fun progressToMb(progress: Int): Long {
+        return progress * diskSizeStepMb
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.settings_disk_resize)
+        diskSizeStepMb = 1L shl resources.getInteger(R.integer.disk_size_round_up_step_size_in_mb)
+
         val sharedPref =
             this.getSharedPreferences(getString(R.string.preference_file_key), Context.MODE_PRIVATE)
-        var diskSizeMb =
-            bytesToMb(
-                sharedPref.getLong(
+        diskSizeMb = bytesToMb(sharedPref.getLong(
                     getString(R.string.preference_disk_size_key),
-                    0
-                )
-            ).toFloat();
+                    /* defValue= */ 0))
         val image = InstalledImage.getDefault(this)
         val minDiskSizeMb =
-            bytesToMb(image.getSmallestSizePossible()).toFloat()
+            bytesToMb(image.getSmallestSizePossible())
                 .coerceAtMost(diskSizeMb)
 
-        val diskSizeText = findViewById<TextView>(R.id.settings_disk_resize_resize_gb_assigned)
+        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(maxDiskSizeMb)
+            localizedFileSize(maxDiskSizeMb, /* isShort= */ true)
         );
 
-        val diskSizeSlider = findViewById<Slider>(R.id.settings_disk_resize_disk_size_slider)
-        diskSizeSlider.setValueTo(maxDiskSizeMb)
+        diskSizeSlider = findViewById<SeekBar>(R.id.settings_disk_resize_disk_size_slider)!!
         val cancelButton = findViewById<MaterialButton>(R.id.settings_disk_resize_cancel_button)
         val resizeButton = findViewById<MaterialButton>(R.id.settings_disk_resize_resize_button)
-        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(diskSizeMb)
-            )
-        )
+        diskSizeSlider.min = mbToProgress(minDiskSizeMb)
+        diskSizeSlider.max = mbToProgress(maxDiskSizeMb)
+        diskSizeSlider.progress = mbToProgress(diskSizeMb)
+        updateSliderText(diskSizeMb)
 
-        diskSizeSlider.addOnChangeListener { _, value, _ ->
-            diskSizeText.text = enlargeFontOfNumber(
-                getString(R.string.settings_disk_resize_resize_gb_assigned_format,
-                localizedFileSize(value)))
-            cancelButton.isVisible = true
-            resizeButton.isVisible = true
-        }
+        diskSizeSlider.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
+            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+                updateSliderText(progressToMb(progress))
+                cancelButton.isVisible = true
+                resizeButton.isVisible = true
+            }
+
+            override fun onStartTrackingTouch(seekBar: SeekBar?) {
+                // no-op
+            }
+
+            override fun onStopTrackingTouch(seekBar: SeekBar?) {
+                // no-op
+            }
+        })
+
         cancelButton.setOnClickListener {
-            diskSizeSlider.value = diskSizeMb
+            diskSizeSlider.progress = mbToProgress(diskSizeMb)
             cancelButton.isVisible = false
             resizeButton.isVisible = false
         }
 
         resizeButton.setOnClickListener {
-            diskSizeMb = diskSizeSlider.value
+            diskSizeMb = progressToMb(diskSizeSlider.progress)
             cancelButton.isVisible = false
             resizeButton.isVisible = false
             val editor = sharedPref.edit()
             editor.putLong(
                 getString(R.string.preference_disk_size_key),
-                mbToBytes(diskSizeMb.toLong())
+                mbToBytes(diskSizeMb)
             )
             editor.apply()
 
@@ -117,11 +130,28 @@
         }
     }
 
-    fun localizedFileSize(sizeMb: Float): String {
-        // formatShortFileSize() uses SI unit (i.e. kB = 1000 bytes),
-        // so covert sizeMb with "MB" instead of "MIB".
-        val bytes = FileUtils.parseSize(sizeMb.toLong().toString() + "MB")
-        return Formatter.formatShortFileSize(this, bytes)
+    fun updateSliderText(sizeMb: Long) {
+        diskSizeText.text = enlargeFontOfNumber(
+            getString(R.string.settings_disk_resize_resize_gb_assigned_format,
+                localizedFileSize(sizeMb, /* isShort= */ true)))
+        diskSizeSlider.stateDescription =
+            getString(R.string.settings_disk_resize_resize_gb_assigned_format,
+                localizedFileSize(sizeMb, /* isShort= */ false))
+    }
+
+    fun localizedFileSize(sizeMb: Long, isShort: Boolean): String {
+        val sizeGb = sizeMb / (1 shl 10).toFloat()
+        val measure = Measure(sizeGb, MeasureUnit.GIGABYTE)
+
+        val localeFromContext: Locale = resources.configuration.locales[0]
+        val numberFormatter: NumberFormat = NumberFormat.getInstance(localeFromContext)
+        numberFormatter.minimumFractionDigits = 1
+        numberFormatter.maximumFractionDigits = 1
+
+        val formatWidth = if (isShort) MeasureFormat.FormatWidth.SHORT else MeasureFormat.FormatWidth.WIDE
+        val measureFormat: MeasureFormat =
+            MeasureFormat.getInstance(localeFromContext, formatWidth, numberFormatter)
+        return measureFormat.format(measure)
     }
 
     fun enlargeFontOfNumber(summary: CharSequence): CharSequence {
diff --git a/android/TerminalApp/res/layout/settings_disk_resize.xml b/android/TerminalApp/res/layout/settings_disk_resize.xml
index 7b8b9fc..fb7f85b 100644
--- a/android/TerminalApp/res/layout/settings_disk_resize.xml
+++ b/android/TerminalApp/res/layout/settings_disk_resize.xml
@@ -57,12 +57,11 @@
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintBottom_toTopOf="@+id/settings_disk_resize_disk_size_slider"/>
 
-        <com.google.android.material.slider.Slider
+        <SeekBar
             android:id="@+id/settings_disk_resize_disk_size_slider"
-            android:layout_height="wrap_content"
+            android:layout_height="40dp"
             android:layout_width="match_parent"
             android:layout_marginBottom="36dp"
-            app:tickVisible="false"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent" />