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" />