From b2d00d076d94773b929e8125f4828f2f31df7ed0 Mon Sep 17 00:00:00 2001
From: Oliver Zimmerman <oezimmerman@gmail.com>
Date: Fri, 3 Jul 2020 11:45:09 +0100
Subject: [PATCH] Added contentDescription containing 'button' via formatters
 for screen readers (EXPOSUREAPP-1542) (#794)

* Update TracingViewModel.kt

* Update TracingViewModel.kt

* Added the word 'Button' to various contentDescriptions via formatters

Added the word 'Button' to various contentDescriptions via formatters specifically for  content without focusable subcontent that are explored by touch

* Update FormatterSettingsHelper.kt

* removed space in strings file and included in formatter instead

* Added formatter tests

Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com>
---
 .../util/formatter/FormatterRiskHelper.kt     | 28 ++++++
 .../util/formatter/FormatterSettingsHelper.kt | 28 ++++++
 .../src/main/res/layout/fragment_main.xml     |  1 +
 .../src/main/res/layout/include_risk_card.xml |  1 +
 .../src/main/res/values-de/strings.xml        |  2 +
 .../src/main/res/values-en/strings.xml        |  2 +
 .../src/main/res/values/strings.xml           |  2 +
 .../util/formatter/FormatterRiskHelperTest.kt | 80 +++++++++++++++++
 .../formatter/FormatterSettingsHelperTest.kt  | 90 +++++++++++++++++++
 9 files changed, 234 insertions(+)

diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt
index 36bdc637d..ee5848aa2 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt
@@ -281,6 +281,34 @@ fun formatNextUpdate(
     }
 }
 
+/**
+ * Formats the risk card content description of time when diagnosis keys will be updated
+ * from server again when applicable but appends the word button at the end for screen reader accessibility reasons
+ *
+ * @param riskLevelScore
+ * @param isBackgroundJobEnabled
+ * @return
+ */
+fun formatNextUpdateContentDescription(
+    riskLevelScore: Int?,
+    isBackgroundJobEnabled: Boolean?
+): String {
+    val appContext = CoronaWarnApplication.getAppContext()
+    return if (isBackgroundJobEnabled != true) {
+        ""
+    } else {
+        return when (riskLevelScore) {
+            RiskLevelConstants.UNKNOWN_RISK_INITIAL,
+            RiskLevelConstants.LOW_LEVEL_RISK,
+            RiskLevelConstants.INCREASED_RISK -> appContext.getString(
+                R.string.risk_card_body_next_update
+            ) + " " + appContext.getString(
+                R.string.accessibility_button)
+            else -> ""
+        }
+    }
+}
+
 /**
  * Formats the risk details text display for each risk level
  *
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelper.kt
index d1decae43..2bb50a259 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelper.kt
@@ -119,6 +119,34 @@ fun formatTracingDescription(tracing: Boolean, bluetooth: Boolean, connection: B
     }
 }
 
+/**
+ * Format the settings tracing content description text display depending on tracing status
+ * but appends the word button at the end for screen reader accessibility reasons
+ *
+ * @param tracing
+ * @param bluetooth
+ * @param connection
+ * @return String
+ */
+fun formatTracingContentDescription(tracing: Boolean, bluetooth: Boolean, connection: Boolean): String {
+    val appContext = CoronaWarnApplication.getAppContext()
+    return when (tracingStatusHelper(tracing, bluetooth, connection)) {
+        TracingStatusHelper.CONNECTION ->
+            appContext.getString(R.string.settings_tracing_body_connection_inactive) +
+                    " " + appContext.getString(R.string.accessibility_button)
+        TracingStatusHelper.BLUETOOTH ->
+            appContext.getString(R.string.settings_tracing_body_bluetooth_inactive) +
+                    " " + appContext.getString(R.string.accessibility_button)
+        TracingStatusHelper.TRACING_ACTIVE ->
+            appContext.getString(R.string.settings_tracing_body_active) +
+                    " " + appContext.getString(R.string.accessibility_button)
+        TracingStatusHelper.TRACING_INACTIVE ->
+            appContext.getString(R.string.settings_tracing_body_inactive) +
+                    " " + appContext.getString(R.string.accessibility_button)
+        else -> ""
+    }
+}
+
 /**
  * Formats the tracing body depending on the tracing status and the days since last exposure.
  *
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_main.xml b/Corona-Warn-App/src/main/res/layout/fragment_main.xml
index 3dc5df211..db25a3b62 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_main.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_main.xml
@@ -102,6 +102,7 @@
                     android:layout_width="@dimen/match_constraint"
                     android:layout_height="wrap_content"
                     android:focusable="false"
+                    android:contentDescription="@{FormatterSettingsHelper.formatTracingContentDescription(tracingViewModel.isTracingEnabled(), settingsViewModel.isBluetoothEnabled(), settingsViewModel.isConnectionEnabled())}"
                     android:text="@{FormatterSettingsHelper.formatTracingDescription(tracingViewModel.isTracingEnabled(), settingsViewModel.isBluetoothEnabled(), settingsViewModel.isConnectionEnabled())}"
                     app:layout_constraintBottom_toBottomOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/include_risk_card.xml b/Corona-Warn-App/src/main/res/layout/include_risk_card.xml
index 9c50cbc70..48b1e91d6 100644
--- a/Corona-Warn-App/src/main/res/layout/include_risk_card.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_risk_card.xml
@@ -227,6 +227,7 @@
                     android:layout_width="@dimen/match_constraint"
                     android:layout_height="wrap_content"
                     android:layout_marginTop="@dimen/spacing_small"
+                    android:contentDescription="@{FormatterRiskHelper.formatNextUpdateContentDescription(tracingViewModel.riskLevel, settingsViewModel.isBackgroundJobEnabled())}"
                     android:text="@{FormatterRiskHelper.formatNextUpdate(tracingViewModel.riskLevel, settingsViewModel.isBackgroundJobEnabled())}"
                     android:textColor="@{FormatterRiskHelper.formatStableTextColor(tracingViewModel.riskLevel)}"
                     app:layout_constraintEnd_toEndOf="parent"
diff --git a/Corona-Warn-App/src/main/res/values-de/strings.xml b/Corona-Warn-App/src/main/res/values-de/strings.xml
index 6d7e75c43..e9dca21e1 100644
--- a/Corona-Warn-App/src/main/res/values-de/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/strings.xml
@@ -84,6 +84,8 @@
     <string name="accessibility_close">"Schließen"</string>
     <!-- XACT: menu description for screen readers -->
     <string name="accessibility_logo">"Corona-Warn-App"</string>
+    <!-- XACT: button description for screen readers to be appended at the end of content without focusable subcontent that are explored by touch -->
+    <string name="accessibility_button">"Button"</string>
 
     <!-- ####################################
                      Menu
diff --git a/Corona-Warn-App/src/main/res/values-en/strings.xml b/Corona-Warn-App/src/main/res/values-en/strings.xml
index 56f08e8cc..4ebc2ce02 100644
--- a/Corona-Warn-App/src/main/res/values-en/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-en/strings.xml
@@ -84,6 +84,8 @@
     <string name="accessibility_close">"Close"</string>
     <!-- XACT: menu description for screen readers -->
     <string name="accessibility_logo">"Corona-Warn-App"</string>
+    <!-- XACT: button description for screen readers to be appended at the end of content without focusable subcontent that are explored by touch -->
+    <string name="accessibility_button">"Button"</string>
 
     <!-- ####################################
                      Menu
diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml
index 54beed16e..78b823898 100644
--- a/Corona-Warn-App/src/main/res/values/strings.xml
+++ b/Corona-Warn-App/src/main/res/values/strings.xml
@@ -84,6 +84,8 @@
     <string name="accessibility_close">"Close"</string>
     <!-- XACT: menu description for screen readers -->
     <string name="accessibility_logo">"Corona-Warn-App"</string>
+    <!-- XACT: button description for screen readers to be appended at the end of content without focusable subcontent that are explored by touch -->
+    <string name="accessibility_button">"Button"</string>
 
     <!-- ####################################
                      Menu
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelperTest.kt
index 3d595008e..d1830e3b4 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelperTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelperTest.kt
@@ -168,6 +168,21 @@ class FormatterRiskHelperTest {
         )
     }
 
+    private fun formatNextUpdateContentDescriptionBase(
+        iRiskLevelScore: Int?,
+        bIsBackgroundJobEnabled: Boolean?,
+        sValue: String
+    ) {
+        every { context.getString(R.string.risk_card_body_next_update) } returns R.string.risk_card_body_next_update.toString()
+        every { context.getString(R.string.accessibility_button) } returns R.string.accessibility_button.toString()
+
+        val result =
+            formatNextUpdateContentDescription(riskLevelScore = iRiskLevelScore, isBackgroundJobEnabled = bIsBackgroundJobEnabled)
+        assertThat(
+            result, `is`(sValue)
+        )
+    }
+
     private fun formatRiskDetailsRiskLevelBodyBase(
         iRiskLevelScore: Int?,
         iDaysSinceLastExposure: Int?,
@@ -955,6 +970,71 @@ class FormatterRiskHelperTest {
         )
     }
 
+    @Test
+    fun formatNextUpdateContentDescription() {
+        formatNextUpdateContentDescriptionBase(iRiskLevelScore = null, bIsBackgroundJobEnabled = null, sValue = "")
+        formatNextUpdateContentDescriptionBase(
+            iRiskLevelScore = RiskLevelConstants.INCREASED_RISK,
+            bIsBackgroundJobEnabled = null,
+            sValue = ""
+        )
+        formatNextUpdateContentDescriptionBase(
+            iRiskLevelScore = RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS,
+            bIsBackgroundJobEnabled = null,
+            sValue = ""
+        )
+        formatNextUpdateContentDescriptionBase(
+            iRiskLevelScore = RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF,
+            bIsBackgroundJobEnabled = null,
+            sValue = ""
+        )
+        formatNextUpdateContentDescriptionBase(
+            iRiskLevelScore = RiskLevelConstants.LOW_LEVEL_RISK,
+            bIsBackgroundJobEnabled = null,
+            sValue = ""
+        )
+        formatNextUpdateContentDescriptionBase(
+            iRiskLevelScore = RiskLevelConstants.UNKNOWN_RISK_INITIAL,
+            bIsBackgroundJobEnabled = null,
+            sValue = ""
+        )
+
+        formatNextUpdateContentDescriptionBase(iRiskLevelScore = null, bIsBackgroundJobEnabled = true, sValue = "")
+        formatNextUpdateContentDescriptionBase(
+            iRiskLevelScore = RiskLevelConstants.INCREASED_RISK,
+            bIsBackgroundJobEnabled = true,
+            sValue = context.getString(
+                R.string.risk_card_body_next_update
+                ) + " " + context.getString(
+            R.string.accessibility_button
+        )
+        )
+        formatNextUpdateContentDescriptionBase(
+            iRiskLevelScore = RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS,
+            bIsBackgroundJobEnabled = true,
+            sValue = ""
+        )
+
+        formatNextUpdateContentDescriptionBase(
+            iRiskLevelScore = RiskLevelConstants.LOW_LEVEL_RISK,
+            bIsBackgroundJobEnabled = true,
+            sValue = context.getString(
+                R.string.risk_card_body_next_update
+                ) + " " + context.getString(
+            R.string.accessibility_button
+        )
+        )
+        formatNextUpdateContentDescriptionBase(
+            iRiskLevelScore = RiskLevelConstants.UNKNOWN_RISK_INITIAL,
+            bIsBackgroundJobEnabled = true,
+            sValue = context.getString(
+                R.string.risk_card_body_next_update
+            ) + " " + context.getString(
+                R.string.accessibility_button
+            )
+        )
+    }
+
     @Test
     fun formatRiskDetailsRiskLevelBody() {
         formatRiskDetailsRiskLevelBodyBase(iRiskLevelScore = null, iDaysSinceLastExposure = 0, sValue = "")
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelperTest.kt
index cb6f079b1..79b94264e 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelperTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelperTest.kt
@@ -105,6 +105,29 @@ class FormatterSettingsHelperTest {
         assertThat(result, `is`((context.getString(iValue))))
     }
 
+
+    private fun formatTracingContentDescriptionBase(
+        bTracing: Boolean,
+        bBluetooth: Boolean,
+        bConnection: Boolean,
+        sValue: String
+    ) {
+        every { context.getString(R.string.settings_tracing_body_bluetooth_inactive) } returns R.string.settings_tracing_body_bluetooth_inactive.toString()
+        every { context.getString(R.string.settings_tracing_body_connection_inactive) } returns R.string.settings_tracing_body_connection_inactive.toString()
+        every { context.getString(R.string.settings_tracing_body_active) } returns R.string.settings_tracing_body_active.toString()
+        every { context.getString(R.string.settings_tracing_body_inactive) } returns R.string.settings_tracing_body_inactive.toString()
+        every { context.getString(R.string.accessibility_button) } returns R.string.accessibility_button.toString()
+
+        val result = formatTracingContentDescription(
+            tracing = bTracing,
+            bluetooth = bBluetooth,
+            connection = bConnection
+        )
+        assertThat(
+            result, `is`(sValue)
+        )
+    }
+
     private fun formatNotificationsTitleBase(bValue: Boolean) {
         val result = formatNotificationsTitle(notifications = bValue)
         assertThat(
@@ -492,6 +515,73 @@ class FormatterSettingsHelperTest {
         )
     }
 
+    @Test
+    fun formatTracingContentDescription() {
+        // When tracing is true, bluetooth is true, connection is true
+        formatTracingContentDescriptionBase(
+            bTracing = true,
+            bBluetooth = true,
+            bConnection = true,
+            sValue = R.string.settings_tracing_body_active.toString() + " " +R.string.accessibility_button.toString()
+        )
+
+        // When tracing is false, bluetooth is false, connection is false
+        formatTracingContentDescriptionBase(
+            bTracing = false,
+            bBluetooth = false,
+            bConnection = false,
+            sValue = R.string.settings_tracing_body_inactive.toString() + " " +R.string.accessibility_button.toString()
+        )
+
+        // When tracing is true, bluetooth is false, connection is false
+        formatTracingContentDescriptionBase(
+            bTracing = true,
+            bBluetooth = false,
+            bConnection = false,
+            sValue = R.string.settings_tracing_body_bluetooth_inactive.toString() + " " +R.string.accessibility_button.toString()
+        )
+
+        // When tracing is true, bluetooth is true, connection is false
+        formatTracingContentDescriptionBase(
+            bTracing = true,
+            bBluetooth = true,
+            bConnection = false,
+            sValue = R.string.settings_tracing_body_connection_inactive.toString() + " " +R.string.accessibility_button.toString()
+        )
+
+        // When tracing is false, bluetooth is true, connection is false
+        formatTracingContentDescriptionBase(
+            bTracing = false,
+            bBluetooth = true,
+            bConnection = false,
+            sValue = R.string.settings_tracing_body_inactive.toString() + " " +R.string.accessibility_button.toString()
+        )
+
+        // When tracing is false, bluetooth is true, connection is true
+        formatTracingContentDescriptionBase(
+            bTracing = false,
+            bBluetooth = true,
+            bConnection = true,
+            sValue = R.string.settings_tracing_body_inactive.toString() + " " +R.string.accessibility_button.toString()
+        )
+
+        // When tracing is true, bluetooth is false, connection is true
+        formatTracingContentDescriptionBase(
+            bTracing = true,
+            bBluetooth = false,
+            bConnection = true,
+            sValue = R.string.settings_tracing_body_bluetooth_inactive.toString() + " " +R.string.accessibility_button.toString()
+        )
+
+        // When tracing is false, bluetooth is false, connection is true
+        formatTracingContentDescriptionBase(
+            bTracing = false,
+            bBluetooth = false,
+            bConnection = true,
+            sValue = R.string.settings_tracing_body_inactive.toString() + " " +R.string.accessibility_button.toString()
+        )
+    }
+
     @Test
     fun formatNotificationsTitle() {
         // When status true
-- 
GitLab