Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dd81be69 authored by Olivier St-Onge's avatar Olivier St-Onge
Browse files

Fix placement mode a11y actions for Talkback and Switch Access

Flag: com.android.systemui.qs_ui_refactor_compose_fragment
Fixes: 413286465
Fixes: 413304456
Fixes: 413309598
Test: manually with Talkback/Switch Access
Test: MutableSelectionStateTest.kt
Test: EditModeTest.kt
Change-Id: Ic1bc2ce4076e3bff61461a2182f2189b55b1011e
parent 7a9e9072
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -146,6 +146,41 @@ class MutableSelectionStateTest : SysuiTestCase() {
            .isEqualTo(None)
    }

    @Test
    fun toggleSelection_correctlyUpdatesSelection() {
        // Select the first spec
        underTest.toggleSelection(TEST_SPEC)
        assertThat(underTest.selection).isEqualTo(TEST_SPEC)

        // Select a second spec
        underTest.toggleSelection(TEST_SPEC_2)
        assertThat(underTest.selection).isEqualTo(TEST_SPEC_2)

        // Toggle on the same spec and assert the selection is now null
        underTest.toggleSelection(TEST_SPEC_2)
        assertThat(underTest.selection).isNull()
    }

    @Test
    fun placeTileAt_onlyWhenPlacementEnabled() {
        // Without enabling placement mode, attempt a placement
        underTest.placeTileAt(TEST_SPEC)

        // Assert nothing happened
        assertThat(underTest.placementEvent).isNull()
    }

    @Test
    fun placeTileAt_createsCorrectPlacementEvent() {
        underTest.enterPlacementMode(TEST_SPEC)
        underTest.placeTileAt(TEST_SPEC_2)

        assertThat(underTest.placementEnabled).isFalse()
        val event = underTest.placementEvent as PlacementEvent.PlaceToTileSpec
        assertThat(event.movingSpec).isEqualTo(TEST_SPEC)
        assertThat(event.targetSpec).isEqualTo(TEST_SPEC_2)
    }

    companion object {
        private val TEST_SPEC = TileSpec.create("testSpec")
        private val TEST_SPEC_2 = TileSpec.create("testSpec2")
+3 −0
Original line number Diff line number Diff line
@@ -2645,6 +2645,9 @@
    <!-- Accessibility description of action to remove QS tile on click. [CHAR LIMIT=NONE] -->
    <string name="accessibility_qs_edit_remove_tile_action">Remove tile</string>

    <!-- Accessibility description of action to place the QS tile on click. [CHAR LIMIT=NONE] -->
    <string name="accessibility_qs_edit_place_tile_action">Place tile</string>

    <!-- Accessibility description of action to select the QS tile to place on click. It will read as "Double-tap to toggle placement mode" in screen readers [CHAR LIMIT=NONE] -->
    <string name="accessibility_qs_edit_toggle_placement_mode">toggle placement mode</string>

+30 −7
Original line number Diff line number Diff line
@@ -877,24 +877,47 @@ private fun LazyGridItemScope.TileGridCell(
                selectionState::unSelect,
            )

        val toggleSelectionLabel = stringResource(R.string.accessibility_qs_edit_toggle_selection)
        val placeTileLabel = stringResource(R.string.accessibility_qs_edit_place_tile_action)
        Box(
            Modifier.fillMaxSize()
                .clearAndSetSemantics {
                    this.stateDescription = stateDescription
                    contentDescription = cell.tile.label.text
                    customActions =
                        listOf(
                            // TODO(b/367748260): Add final accessibility actions

                    val actions =
                        mutableListOf(
                            CustomAccessibilityAction(togglePlacementModeLabel) {
                                selectionState.togglePlacementMode(cell.tile.tileSpec)
                                true
                            }
                        )

                    if (selectionState.placementEnabled) {
                        actions.add(
                            CustomAccessibilityAction(placeTileLabel) {
                                selectionState.placeTileAt(cell.tile.tileSpec)
                                true
                            }
                        )
                    } else {
                        // Don't allow for resizing during placement mode
                        actions.add(
                            CustomAccessibilityAction(toggleSizeLabel) {
                                onResize(FinalResizeOperation(cell.tile.tileSpec, !cell.isIcon))
                                true
                            },
                            CustomAccessibilityAction(togglePlacementModeLabel) {
                                selectionState.togglePlacementMode(cell.tile.tileSpec)
                            }
                        )
                        actions.add(
                            CustomAccessibilityAction(toggleSelectionLabel) {
                                selectionState.toggleSelection(cell.tile.tileSpec)
                                true
                            },
                            }
                        )
                    }

                    customActions = actions
                }
                .selectableTile(cell.tile.tileSpec, selectionState)
                .thenIf(isReadyToDrag) { draggableModifier }
                .tileBackground { backgroundColor }
+13 −6
Original line number Diff line number Diff line
@@ -63,6 +63,10 @@ class MutableSelectionState {
        exitPlacementMode()
    }

    fun toggleSelection(tileSpec: TileSpec) {
        if (selection == tileSpec) unSelect() else select(tileSpec)
    }

    /** Selects [tileSpec] and enable placement mode. */
    fun enterPlacementMode(tileSpec: TileSpec) {
        selection = tileSpec
@@ -78,6 +82,13 @@ class MutableSelectionState {
        if (placementEnabled) exitPlacementMode() else enterPlacementMode(tileSpec)
    }

    fun placeTileAt(tileSpec: TileSpec) {
        if (!placementEnabled) return

        selection?.let { placementEvent = PlacementEvent.PlaceToTileSpec(it, tileSpec) }
        exitPlacementMode()
    }

    suspend fun tileStateFor(
        tileSpec: TileSpec,
        previousState: TileState,
@@ -114,14 +125,10 @@ class MutableSelectionState {
                exitPlacementMode()
            }
            placementEnabled -> {
                selection?.let { placementEvent = PlacementEvent.PlaceToTileSpec(it, tileSpec) }
                exitPlacementMode()
            }
            selection == tileSpec -> {
                unSelect()
                placeTileAt(tileSpec)
            }
            else -> {
                select(tileSpec)
                toggleSelection(tileSpec)
            }
        }
    }
+66 −1
Original line number Diff line number Diff line
@@ -22,12 +22,14 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.doubleClick
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performCustomAccessibilityActionWithLabel
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.text.AnnotatedString
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -43,11 +45,14 @@ import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridSnapshotViewModelFactory
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.TileCategory
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalTestApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class EditModeTest : SysuiTestCase() {
@@ -59,10 +64,13 @@ class EditModeTest : SysuiTestCase() {
    @Composable
    private fun EditTileGridUnderTest() {
        val allTiles = remember { TestEditTiles.toMutableStateList() }
        val largeTiles = remember { TestLargeTilesSpecs.toMutableStateList() }
        val currentTiles = allTiles.filter { it.isCurrent }
        val listState =
            EditTileListState(currentTiles, TestLargeTilesSpecs, columns = 4, largeTilesSpan = 2)
        LaunchedEffect(currentTiles) { listState.updateTiles(currentTiles, TestLargeTilesSpecs) }
        LaunchedEffect(currentTiles, largeTiles) {
            listState.updateTiles(currentTiles, largeTiles.toSet())
        }

        val snapshotViewModel = remember { snapshotViewModelFactory.create() }

@@ -92,6 +100,13 @@ class EditModeTest : SysuiTestCase() {
                        val index = allTiles.indexOfFirst { it.tileSpec == action.tileSpec }
                        allTiles[index] = allTiles[index].copy(isCurrent = false)
                    }
                    is EditAction.ResizeTile -> {
                        if (action.toIcon) {
                            largeTiles.remove(action.tileSpec)
                        } else {
                            largeTiles.add(action.tileSpec)
                        }
                    }
                    else -> error("Not expecting action $action from test")
                }
            }
@@ -128,6 +143,56 @@ class EditModeTest : SysuiTestCase() {
        )
    }

    @Test
    fun resizingAction_dependsOnPlacementMode() {
        composeRule.setContent { EditTileGridUnderTest() }
        composeRule.waitForIdle()

        // Use the toggle size action
        composeRule
            .onNodeWithContentDescription("tileE")
            .performCustomAccessibilityActionWithLabel(
                context.getString(R.string.accessibility_qs_edit_toggle_tile_size_action)
            )

        // Double tap "tileA" to enable placement mode
        composeRule.onNodeWithContentDescription("tileA").performTouchInput { doubleClick() }

        // Assert the toggle size action is missing
        assertThrows(AssertionError::class.java) {
            composeRule
                .onNodeWithContentDescription("tileE")
                .performCustomAccessibilityActionWithLabel(
                    context.getString(R.string.accessibility_qs_edit_toggle_tile_size_action)
                )
        }
    }

    @Test
    fun placementAction_dependsOnPlacementMode() {
        composeRule.setContent { EditTileGridUnderTest() }
        composeRule.waitForIdle()

        // Assert the placement action is missing
        assertThrows(AssertionError::class.java) {
            composeRule
                .onNodeWithContentDescription("tileE")
                .performCustomAccessibilityActionWithLabel(
                    context.getString(R.string.accessibility_qs_edit_place_tile_action)
                )
        }

        // Double tap "tileA" to enable placement mode
        composeRule.onNodeWithContentDescription("tileA").performTouchInput { doubleClick() }

        // Use the placement action
        composeRule
            .onNodeWithContentDescription("tileE")
            .performCustomAccessibilityActionWithLabel(
                context.getString(R.string.accessibility_qs_edit_place_tile_action)
            )
    }

    @Test
    fun performAction_undoAppears() {
        composeRule.setContent { EditTileGridUnderTest() }