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

Commit 0e398f63 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov Committed by Android (Google) Code Review
Browse files

Merge "[SB][Chips] Add custom a11y click label for notification chips." into main

parents 207537e9 c8a455c9
Loading
Loading
Loading
Loading
+20 −5
Original line number Diff line number Diff line
@@ -127,6 +127,8 @@ import kotlin.math.min
 *
 * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
 * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
 * @param onClickLabel semantic / accessibility label for the onClick action. See
 *   [Modifier.clickable].
 * @param defaultMinSize true if a default minimum size should be enforced even if this Expandable
 *   isn't currently clickable and false otherwise.
 */
@@ -138,6 +140,7 @@ fun Expandable(
    contentColor: Color = contentColorFor(color),
    borderStroke: BorderStroke? = null,
    onClick: ((Expandable) -> Unit)? = null,
    onClickLabel: String? = null,
    interactionSource: MutableInteractionSource? = null,
    // TODO(b/285250939): Default this to true then remove once the Compose QS expandables have
    // proven that the new implementation is robust.
@@ -156,6 +159,7 @@ fun Expandable(
        ),
        modifier,
        onClick,
        onClickLabel,
        interactionSource,
        useModifierBasedImplementation,
        defaultMinSize,
@@ -186,6 +190,8 @@ fun Expandable(
 *
 * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
 * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
 * @param onClickLabel semantic / accessibility label for the onClick action. See
 *   [Modifier.clickable].
 * @param defaultMinSize true if a default minimum size should be enforced even if this Expandable
 *   isn't currently clickable and false otherwise.
 */
@@ -194,6 +200,7 @@ fun Expandable(
    controller: ExpandableController,
    modifier: Modifier = Modifier,
    onClick: ((Expandable) -> Unit)? = null,
    onClickLabel: String? = null,
    interactionSource: MutableInteractionSource? = null,
    // TODO(b/285250939): Default this to true then remove once the Compose QS expandables have
    // proven that the new implementation is robust.
@@ -215,7 +222,7 @@ fun Expandable(
    }

    if (useModifierBasedImplementation) {
        Box(modifier.expandable(controller, onClick, interactionSource)) {
        Box(modifier.expandable(controller, onClick, onClickLabel, interactionSource)) {
            WrappedContent(
                controller.expandable,
                controller.contentColor,
@@ -303,7 +310,7 @@ fun Expandable(
                modifier
                    .updateExpandableSize()
                    .then(minInteractiveSizeModifier)
                    .then(clickModifier(controller, onClick, interactionSource))
                    .then(clickModifier(controller, onClick, onClickLabel, interactionSource))
                    .animatedBackground(color, shape = shape)
                    .border(controller)
                    .onGloballyPositioned { controller.boundsInComposeViewRoot = it.boundsInRoot() }
@@ -352,6 +359,7 @@ private fun WrappedContent(
private fun Modifier.expandable(
    controller: ExpandableController,
    onClick: ((Expandable) -> Unit)? = null,
    onClickLabel: String? = null,
    interactionSource: MutableInteractionSource? = null,
): Modifier {
    val controller = controller as ExpandableControllerImpl
@@ -368,7 +376,7 @@ private fun Modifier.expandable(
    return this.thenIf(onClick != null) { Modifier.minimumInteractiveComponentSize() }
        .thenIf(drawContent) {
            Modifier.border(controller)
                .then(clickModifier(controller, onClick, interactionSource))
                .then(clickModifier(controller, onClick, onClickLabel, interactionSource))
                .animatedBackground(controller.color, shape = controller.shape)
        }
        .onPlaced { controller.boundsInComposeViewRoot = it.boundsInRoot() }
@@ -471,6 +479,7 @@ private class DrawExpandableInOverlayNode(
private fun clickModifier(
    controller: ExpandableControllerImpl,
    onClick: ((Expandable) -> Unit)?,
    onClickLabel: String? = null,
    interactionSource: MutableInteractionSource?,
): Modifier {
    if (onClick == null) {
@@ -480,14 +489,20 @@ private fun clickModifier(
    if (interactionSource != null) {
        // If the caller provided an interaction source, then that means that they will draw the
        // click indication themselves.
        return Modifier.clickable(interactionSource, indication = null) {
        return Modifier.clickable(
            interactionSource,
            indication = null,
            onClickLabel = onClickLabel,
        ) {
            onClick(controller.expandable)
        }
    }

    // If no interaction source is provided, we draw the default indication (a ripple) and make sure
    // it's clipped by the expandable shape.
    return Modifier.clip(controller.shape).clickable { onClick(controller.expandable) }
    return Modifier.clip(controller.shape).clickable(onClickLabel = onClickLabel) {
        onClick(controller.expandable)
    }
}

/** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */
+107 −0
Original line number Diff line number Diff line
@@ -1331,6 +1331,113 @@ class NotifChipsViewModelTest : SysuiTestCase() {
            assertThat(latestChipTapKey).isEqualTo(key)
        }

    @Test
    fun chips_noHun_clickBehaviorIsShowHun() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chips)

            setNotifs(
                listOf(
                    activeNotificationModel(
                        "notif",
                        statusBarChipIcon = createStatusBarIconViewOrNull(),
                        promotedContent = PromotedNotificationContentBuilder("notif").build(),
                    )
                )
            )

            headsUpNotificationRepository.setNotifications(emptyList())

            assertThat(latest!![0].clickBehavior)
                .isInstanceOf(
                    OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification::class.java
                )
        }

    @Test
    fun chip_hun_pinnedBySystem_clickBehaviorIsShowHun() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chips)

            setNotifs(
                listOf(
                    activeNotificationModel(
                        "notif",
                        statusBarChipIcon = createStatusBarIconViewOrNull(),
                        promotedContent = PromotedNotificationContentBuilder("notif").build(),
                    )
                )
            )

            headsUpNotificationRepository.setNotifications(
                UnconfinedFakeHeadsUpRowRepository(
                    key = "systemNotif",
                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
                )
            )

            assertThat(latest!![0].clickBehavior)
                .isInstanceOf(
                    OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification::class.java
                )
        }

    @Test
    fun chip_hun_pinnedByUser_forDifferentChip_clickBehaviorIsShowHun() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chips)

            setNotifs(
                listOf(
                    activeNotificationModel(
                        "notif",
                        statusBarChipIcon = createStatusBarIconViewOrNull(),
                        promotedContent = PromotedNotificationContentBuilder("notif").build(),
                    )
                )
            )

            headsUpNotificationRepository.setNotifications(
                UnconfinedFakeHeadsUpRowRepository(
                    key = "otherNotifPinnedByUser",
                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
                )
            )

            assertThat(latest!![0].clickBehavior)
                .isInstanceOf(
                    OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification::class.java
                )
        }

    @Test
    fun chip_hun_pinnedByUser_forThisChip_clickBehaviorIsHideHun() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chips)

            setNotifs(
                listOf(
                    activeNotificationModel(
                        "notif",
                        statusBarChipIcon = createStatusBarIconViewOrNull(),
                        promotedContent = PromotedNotificationContentBuilder("notif").build(),
                    )
                )
            )

            headsUpNotificationRepository.setNotifications(
                UnconfinedFakeHeadsUpRowRepository(
                    key = "notif",
                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
                )
            )

            assertThat(latest!![0].clickBehavior)
                .isInstanceOf(
                    OngoingActivityChipModel.ClickBehavior.HideHeadsUpNotification::class.java
                )
        }

    private fun setNotifs(notifs: List<ActiveNotificationModel>) {
        activeNotificationListRepository.activeNotifications.value =
            ActiveNotificationsStore.Builder()
+4 −0
Original line number Diff line number Diff line
@@ -3507,6 +3507,10 @@
    <string name="ongoing_call_content_description">Ongoing call</string>
    <!-- Content description for a chip in the status bar showing that the user currently has an ongoing activity. [CHAR LIMIT=NONE]-->
    <string name="ongoing_notification_extra_content_description">Ongoing</string>
    <!-- Custom accessibility action text to tell that user that tapping on the status bar chip will show/expand the notification associated with that chip. [CHAR LIMIT=NONE]-->
    <string name="status_bar_chip_custom_a11y_action_expand_notification">expand notification</string>
    <!-- Custom accessibility action text to tell that user that tapping on the status bar chip will hide/collapse the notification associated with that chip. [CHAR LIMIT=NONE]-->
    <string name="status_bar_chip_custom_a11y_action_collapse_notification">collapse notification</string>

    <!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
    <string name="mobile_data_settings_title">Mobile data</string>
+17 −9
Original line number Diff line number Diff line
@@ -162,21 +162,29 @@ constructor(
        // because information between the status bar chip and the app itself could be
        // out-of-sync (like a timer that's slightly off)
        val isHidden = this.isAppVisible

        val isShowingHeadsUpFromChipTap =
            headsUpState is TopPinnedState.Pinned &&
                headsUpState.status == PinnedStatus.PinnedByUser &&
                headsUpState.key == this.key

        val onClickListenerLegacy =
            View.OnClickListener {
                StatusBarChipsModernization.assertInLegacyMode()
                clickListener.invoke()
            }
        val clickBehavior =
            if (isShowingHeadsUpFromChipTap) {
                OngoingActivityChipModel.ClickBehavior.HideHeadsUpNotification({
                    /* check if */ StatusBarChipsModernization.isUnexpectedlyInLegacyMode()
                    clickListener.invoke()
                })
            } else {
                OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification({
                StatusBarChipsModernization.unsafeAssertInNewMode()
                    /* check if */ StatusBarChipsModernization.isUnexpectedlyInLegacyMode()
                    clickListener.invoke()
                })

        val isShowingHeadsUpFromChipTap =
            headsUpState is TopPinnedState.Pinned &&
                headsUpState.status == PinnedStatus.PinnedByUser &&
                headsUpState.key == this.key
            }

        val content: OngoingActivityChipModel.Content =
            when {
+6 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.LiveRegionMode
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.liveRegion
@@ -87,8 +88,12 @@ fun OngoingActivityChip(
            is OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification -> { _ ->
                    clickBehavior.onClick()
                }
            is OngoingActivityChipModel.ClickBehavior.HideHeadsUpNotification -> { _ ->
                    clickBehavior.onClick()
                }
            is OngoingActivityChipModel.ClickBehavior.None -> null
        }
    val onClickLabel = model.clickBehavior.customOnClickLabel?.let { stringResource(it) }
    val isClickable = onClick != null

    val chipSidePaddingTotal = 20.dp
@@ -140,6 +145,7 @@ fun OngoingActivityChip(
                ),
        borderStroke = borderStroke,
        onClick = onClick,
        onClickLabel = onClickLabel,
        useModifierBasedImplementation = StatusBarChipsReturnAnimations.isEnabled,
        // Some chips like the 3-2-1 countdown chip should be very small, smaller than a
        // reasonable minimum size.
Loading