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

Commit a01198b7 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Allow accessibility talk back resize widgets" into main

parents 34bf1c37 b6c1e106
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
@@ -181,9 +181,11 @@ import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.ResizeInfo
import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel
import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlin.math.max
@@ -665,6 +667,7 @@ private fun ResizableItemFrameWrapper(
    maxHeightPx: Int,
    modifier: Modifier = Modifier,
    alpha: () -> Float = { 1f },
    viewModel: ResizeableItemFrameViewModel,
    onResize: (info: ResizeInfo) -> Unit = {},
    content: @Composable (modifier: Modifier) -> Unit,
) {
@@ -680,6 +683,7 @@ private fun ResizableItemFrameWrapper(
            enabled = enabled,
            alpha = alpha,
            modifier = modifier,
            viewModel = viewModel,
            onResize = onResize,
            minHeightPx = minHeightPx,
            maxHeightPx = maxHeightPx,
@@ -796,6 +800,14 @@ private fun BoxScope.CommunalHubLazyGrid(
                    false
                }

            val resizeableItemFrameViewModel =
                rememberViewModel(
                    key = item.size.span,
                    traceName = "ResizeableItemFrame.viewModel.$index",
                ) {
                    ResizeableItemFrameViewModel()
                }

            if (viewModel.isEditMode && dragDropState != null) {
                val isItemDragging = dragDropState.draggingItemKey == item.key
                val outlineAlpha by
@@ -821,6 +833,7 @@ private fun BoxScope.CommunalHubLazyGrid(
                                )
                            }
                            .thenIf(isItemDragging) { Modifier.zIndex(1f) },
                    viewModel = resizeableItemFrameViewModel,
                    onResize = { resizeInfo -> contentListState.resize(index, resizeInfo) },
                    minHeightPx = widgetSizeInfo.minHeightPx,
                    maxHeightPx = widgetSizeInfo.maxHeightPx,
@@ -843,6 +856,7 @@ private fun BoxScope.CommunalHubLazyGrid(
                            contentListState = contentListState,
                            interactionHandler = interactionHandler,
                            widgetSection = widgetSection,
                            resizeableItemFrameViewModel = resizeableItemFrameViewModel,
                        )
                    }
                }
@@ -857,6 +871,7 @@ private fun BoxScope.CommunalHubLazyGrid(
                    contentListState = contentListState,
                    interactionHandler = interactionHandler,
                    widgetSection = widgetSection,
                    resizeableItemFrameViewModel = resizeableItemFrameViewModel,
                )
            }
        }
@@ -1080,6 +1095,7 @@ private fun CommunalContent(
    contentListState: ContentListState,
    interactionHandler: RemoteViews.InteractionHandler?,
    widgetSection: CommunalAppWidgetSection,
    resizeableItemFrameViewModel: ResizeableItemFrameViewModel,
) {
    when (model) {
        is CommunalContentModel.WidgetContent.Widget ->
@@ -1093,6 +1109,7 @@ private fun CommunalContent(
                index,
                contentListState,
                widgetSection,
                resizeableItemFrameViewModel,
            )
        is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
        is CommunalContentModel.WidgetContent.DisabledWidget ->
@@ -1223,7 +1240,9 @@ private fun WidgetContent(
    index: Int,
    contentListState: ContentListState,
    widgetSection: CommunalAppWidgetSection,
    resizeableItemFrameViewModel: ResizeableItemFrameViewModel,
) {
    val coroutineScope = rememberCoroutineScope()
    val context = LocalContext.current
    val accessibilityLabel =
        remember(model, context) {
@@ -1234,6 +1253,10 @@ private fun WidgetContent(
    val placeWidgetActionLabel = stringResource(R.string.accessibility_action_label_place_widget)
    val unselectWidgetActionLabel =
        stringResource(R.string.accessibility_action_label_unselect_widget)

    val shrinkWidgetLabel = stringResource(R.string.accessibility_action_label_shrink_widget)
    val expandWidgetLabel = stringResource(R.string.accessibility_action_label_expand_widget)

    val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle()
    val selectedIndex =
        selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
@@ -1292,6 +1315,29 @@ private fun WidgetContent(
                                true
                            }
                        val actions = mutableListOf(deleteAction)

                        if (communalWidgetResizing() && resizeableItemFrameViewModel.canShrink()) {
                            actions.add(
                                CustomAccessibilityAction(shrinkWidgetLabel) {
                                    coroutineScope.launch {
                                        resizeableItemFrameViewModel.shrinkToNextAnchor()
                                    }
                                    true
                                }
                            )
                        }

                        if (communalWidgetResizing() && resizeableItemFrameViewModel.canExpand()) {
                            actions.add(
                                CustomAccessibilityAction(expandWidgetLabel) {
                                    coroutineScope.launch {
                                        resizeableItemFrameViewModel.expandToNextAnchor()
                                    }
                                    true
                                }
                            )
                        }

                        if (selectedIndex != null && selectedIndex != index) {
                            actions.add(
                                CustomAccessibilityAction(placeWidgetActionLabel) {
+1 −6
Original line number Diff line number Diff line
@@ -56,7 +56,6 @@ import com.android.compose.modifiers.thenIf
import com.android.systemui.communal.ui.viewmodel.DragHandle
import com.android.systemui.communal.ui.viewmodel.ResizeInfo
import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel
import com.android.systemui.lifecycle.rememberViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine

@@ -192,16 +191,12 @@ fun ResizableItemFrame(
    maxHeightPx: Int = Int.MAX_VALUE,
    resizeMultiple: Int = 1,
    alpha: () -> Float = { 1f },
    viewModel: ResizeableItemFrameViewModel,
    onResize: (info: ResizeInfo) -> Unit = {},
    content: @Composable () -> Unit,
) {
    val brush = SolidColor(outlineColor)
    val onResizeUpdated by rememberUpdatedState(onResize)
    val viewModel =
        rememberViewModel(key = currentSpan, traceName = "ResizeableItemFrame.viewModel") {
            ResizeableItemFrameViewModel()
        }

    val dragHandleHeight = verticalArrangement.spacing - outlinePadding * 2
    val isDragging by
        remember(viewModel) {
+100 −0
Original line number Diff line number Diff line
@@ -366,6 +366,106 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() {
            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
        }

    @Test
    fun testCanExpand_atTopPosition_withMultipleAnchors_returnsTrue() =
        testScope.runTest {
            val twoRowGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 0)

            updateGridLayout(twoRowGrid)
            assertThat(underTest.canExpand()).isTrue()
            assertThat(underTest.bottomDragState.anchors.toList())
                .containsAtLeast(0 to 0f, 1 to 45f)
        }

    @Test
    fun testCanExpand_atTopPosition_withSingleAnchors_returnsFalse() =
        testScope.runTest {
            val oneRowGrid = singleSpanGrid.copy(totalSpans = 1, currentSpan = 1, currentRow = 0)
            updateGridLayout(oneRowGrid)
            assertThat(underTest.canExpand()).isFalse()
        }

    @Test
    fun testCanExpand_atBottomPosition_withMultipleAnchors_returnsTrue() =
        testScope.runTest {
            val twoRowGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 1)
            updateGridLayout(twoRowGrid)
            assertThat(underTest.canExpand()).isTrue()
            assertThat(underTest.topDragState.anchors.toList()).containsAtLeast(0 to 0f, -1 to -45f)
        }

    @Test
    fun testCanShrink_atMinimumHeight_returnsFalse() =
        testScope.runTest {
            val oneRowGrid = singleSpanGrid.copy(totalSpans = 1, currentSpan = 1, currentRow = 0)
            updateGridLayout(oneRowGrid)
            assertThat(underTest.canShrink()).isFalse()
        }

    @Test
    fun testCanShrink_atFullSize_checksBottomDragState() = runTestWithSnapshots {
        val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 2, currentRow = 0)
        updateGridLayout(twoSpanGrid)

        assertThat(underTest.canShrink()).isTrue()
        assertThat(underTest.bottomDragState.anchors.toList()).containsAtLeast(0 to 0f, -1 to -45f)
    }

    @Test
    fun testResizeByAccessibility_expandFromBottom_usesTopDragState() = runTestWithSnapshots {
        val resizeInfo by collectLastValue(underTest.resizeInfo)

        val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 1)
        updateGridLayout(twoSpanGrid)

        underTest.expandToNextAnchor()

        assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.TOP))
    }

    @Test
    fun testResizeByAccessibility_expandFromTop_usesBottomDragState() = runTestWithSnapshots {
        val resizeInfo by collectLastValue(underTest.resizeInfo)

        val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 0)
        updateGridLayout(twoSpanGrid)

        underTest.expandToNextAnchor()

        assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.BOTTOM))
    }

    @Test
    fun testResizeByAccessibility_shrinkFromFull_usesBottomDragState() = runTestWithSnapshots {
        val resizeInfo by collectLastValue(underTest.resizeInfo)

        val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 2, currentRow = 0)
        updateGridLayout(twoSpanGrid)

        underTest.shrinkToNextAnchor()

        assertThat(resizeInfo).isEqualTo(ResizeInfo(-1, DragHandle.BOTTOM))
    }

    @Test
    fun testResizeByAccessibility_cannotResizeAtMinSize() = runTestWithSnapshots {
        val resizeInfo by collectLastValue(underTest.resizeInfo)

        // Set up grid at minimum size
        val minSizeGrid =
            singleSpanGrid.copy(
                totalSpans = 2,
                currentSpan = 1,
                minHeightPx = singleSpanGrid.minHeightPx,
                currentRow = 0,
            )
        updateGridLayout(minSizeGrid)

        underTest.shrinkToNextAnchor()

        assertThat(resizeInfo).isNull()
    }

    @Test(expected = IllegalArgumentException::class)
    fun testIllegalState_maxHeightLessThanMinHeight() =
        testScope.runTest {
+4 −0
Original line number Diff line number Diff line
@@ -1311,6 +1311,10 @@
    <string name="communal_widget_picker_description">Anyone can view widgets on your lock screen, even if your tablet\'s locked.</string>
    <!-- Label for accessibility action to unselect a widget in edit mode. [CHAR LIMIT=NONE] -->
    <string name="accessibility_action_label_unselect_widget">unselect widget</string>
    <!-- Label for accessibility action to shrink a widget in edit mode. [CHAR LIMIT=NONE] -->
    <string name="accessibility_action_label_shrink_widget">Decrease height</string>
    <!-- Label for accessibility action to expand a widget in edit mode. [CHAR LIMIT=NONE] -->
    <string name="accessibility_action_label_expand_widget">Increase height</string>
    <!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] -->
    <string name="communal_widgets_disclaimer_title">Lock screen widgets</string>
    <!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] -->
+67 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.systemui.communal.ui.viewmodel

import androidx.compose.foundation.gestures.AnchoredDraggableState
import androidx.compose.foundation.gestures.DraggableAnchors
import androidx.compose.foundation.gestures.snapTo
import androidx.compose.runtime.snapshotFlow
import com.android.app.tracing.coroutines.coroutineScopeTraced as coroutineScope
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -81,6 +82,72 @@ class ResizeableItemFrameViewModel : ExclusiveActivatable() {
            get() = roundDownToMultiple(getSpansForPx(minHeightPx))
    }

    /** Check if widget can expanded based on current drag states */
    fun canExpand(): Boolean {
        return getNextAnchor(bottomDragState, moveUp = false) != null ||
            getNextAnchor(topDragState, moveUp = true) != null
    }

    /** Check if widget can shrink based on current drag states */
    fun canShrink(): Boolean {
        return getNextAnchor(bottomDragState, moveUp = true) != null ||
            getNextAnchor(topDragState, moveUp = false) != null
    }

    /** Get the next anchor value in the specified direction */
    private fun getNextAnchor(state: AnchoredDraggableState<Int>, moveUp: Boolean): Int? {
        var nextAnchor: Int? = null
        var nextAnchorDiff = Int.MAX_VALUE
        val currentValue = state.currentValue

        for (i in 0 until state.anchors.size) {
            val anchor = state.anchors.anchorAt(i) ?: continue
            if (anchor == currentValue) continue

            val diff =
                if (moveUp) {
                    currentValue - anchor
                } else {
                    anchor - currentValue
                }

            if (diff in 1..<nextAnchorDiff) {
                nextAnchor = anchor
                nextAnchorDiff = diff
            }
        }

        return nextAnchor
    }

    /** Handle expansion to the next anchor */
    suspend fun expandToNextAnchor() {
        if (!canExpand()) return
        val bottomAnchor = getNextAnchor(state = bottomDragState, moveUp = false)
        if (bottomAnchor != null) {
            bottomDragState.snapTo(bottomAnchor)
            return
        }
        val topAnchor =
            getNextAnchor(
                state = topDragState,
                moveUp = true, // Moving up to expand
            )
        topAnchor?.let { topDragState.snapTo(it) }
    }

    /** Handle shrinking to the next anchor */
    suspend fun shrinkToNextAnchor() {
        if (!canShrink()) return
        val topAnchor = getNextAnchor(state = topDragState, moveUp = false)
        if (topAnchor != null) {
            topDragState.snapTo(topAnchor)
            return
        }
        val bottomAnchor = getNextAnchor(state = bottomDragState, moveUp = true)
        bottomAnchor?.let { bottomDragState.snapTo(it) }
    }

    /**
     * The layout information necessary in order to calculate the pixel offsets of the drag anchor
     * points.