diff options
| author | 2024-09-26 13:06:13 +0000 | |
|---|---|---|
| committer | 2024-10-18 09:05:34 +0000 | |
| commit | 52ce865f34a628a954cfce2ace2c3e4ea76d2647 (patch) | |
| tree | e8015a064871322c178c269b0a003c03201e8657 | |
| parent | 60d45114b8b1ad25545eddde510d53cb17e6c659 (diff) | |
[flexiglass] Scroll the shade with TalkBack
Populate accessibility nodes with the current scroll state, to restore
the pre-flexiglass functionality of navigating through notifications
by just swiping from left to right (with TalkBack). Before this change
TalkBack stops at the last visible notification and doesn't scroll down
to the next, if the shade has enough notifications to scroll.
Bug: 363260043
Test: use TalkBack to navigate through notifications and footer buttons in the shade
Flag: com.android.systemui.scene_container
Change-Id: I38f07e75afdb988f5aca4c81930a2dc5c7915759
11 files changed, 270 insertions, 57 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 4c6834cf6bea..834a7f5220ab 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -50,9 +50,11 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue @@ -60,6 +62,7 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer @@ -95,13 +98,17 @@ import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.ui.composable.ShadeHeader +import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel +import kotlin.math.max import kotlin.math.roundToInt +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -315,6 +322,12 @@ fun SceneScope.NotificationScrollingStack( */ val stackHeight = remember { mutableIntStateOf(0) } + /** + * Space available for the notification stack on the screen. These bounds don't scroll off the + * screen, and respect the scrim paddings, scrim clipping. + */ + val stackBoundsOnScreen = remember { mutableStateOf(Rect.Zero) } + val scrimRounding = viewModel.shadeScrimRounding.collectAsStateWithLifecycle(ShadeScrimRounding()) @@ -348,12 +361,19 @@ fun SceneScope.NotificationScrollingStack( // The top y bound of the IME. val imeTop = remember { mutableFloatStateOf(0f) } - // we are not scrolled to the top unless the scrim is at its maximum offset. - LaunchedEffect(viewModel, scrimOffset) { - snapshotFlow { scrimOffset.value >= 0f } - .collect { isScrolledToTop -> viewModel.setScrolledToTop(isScrolledToTop) } + val shadeScrollState by remember { + derivedStateOf { + ShadeScrollState( + // we are not scrolled to the top unless the scrim is at its maximum offset. + isScrolledToTop = scrimOffset.value >= 0f, + scrollPosition = scrollState.value, + maxScrollPosition = scrollState.maxValue, + ) + } } + LaunchedEffect(shadeScrollState) { viewModel.setScrollState(shadeScrollState) } + // if contentHeight drops below minimum visible scrim height while scrim is // expanded, reset scrim offset. LaunchedEffect(stackHeight, scrimOffset) { @@ -395,6 +415,38 @@ fun SceneScope.NotificationScrollingStack( } } + // TalkBack sends a scroll event, when it wants to navigate to an item that is not displayed in + // the current viewport. + LaunchedEffect(viewModel) { + viewModel.setAccessibilityScrollEventConsumer { event -> + // scroll up, or down by the height of the visible portion of the notification stack + val direction = + when (event) { + AccessibilityScrollEvent.SCROLL_UP -> -1 + AccessibilityScrollEvent.SCROLL_DOWN -> 1 + } + val viewPortHeight = stackBoundsOnScreen.value.height + val scrollStep = max(0f, viewPortHeight - stackScrollView.stackBottomInset) + val scrollPosition = scrollState.value.toFloat() + val scrollRange = scrollState.maxValue.toFloat() + val targetScroll = (scrollPosition + direction * scrollStep).coerceIn(0f, scrollRange) + coroutineScope.launch { + scrollNotificationStack( + delta = targetScroll - scrollPosition, + animate = false, + scrimOffset = scrimOffset, + minScrimOffset = minScrimOffset, + scrollState = scrollState, + ) + } + } + try { + awaitCancellation() + } finally { + viewModel.setAccessibilityScrollEventConsumer(null) + } + } + val scrimNestedScrollConnection = shadeSession.rememberSession( scrimOffset, @@ -520,6 +572,9 @@ fun SceneScope.NotificationScrollingStack( .verticalScroll(scrollState) .padding(top = topPadding) .fillMaxWidth() + .onGloballyPositioned { coordinates -> + stackBoundsOnScreen.value = coordinates.boundsInWindow() + } ) { NotificationPlaceholder( stackScrollView = stackScrollView, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index d828a6700ee6..ec1dc0a77118 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -25,6 +25,8 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_A import static com.android.systemui.Flags.notificationOverExpansionClippingFix; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; +import static com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent.SCROLL_DOWN; +import static com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent.SCROLL_UP; import static com.android.systemui.util.DumpUtilsKt.println; import static com.android.systemui.util.DumpUtilsKt.visibilityString; @@ -118,8 +120,10 @@ import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCyc import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; +import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent; import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds; import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape; +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState; import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; @@ -609,7 +613,7 @@ public class NotificationStackScrollLayout @Override public boolean isScrolledToTop() { if (SceneContainerFlag.isEnabled()) { - return mScrollViewFields.isScrolledToTop(); + return mScrollViewFields.getScrollState().isScrolledToTop(); } else { return mOwnScrollY == 0; } @@ -1247,9 +1251,25 @@ public class NotificationStackScrollLayout } @Override - public void setScrolledToTop(boolean scrolledToTop) { - if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; - mScrollViewFields.setScrolledToTop(scrolledToTop); + public void setScrollState(@NonNull ShadeScrollState scrollState) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { + return; + } + + boolean forwardScrollable = + scrollState.getScrollPosition() < scrollState.getMaxScrollPosition(); + boolean backwardScrollable = scrollState.getScrollPosition() > 0; + mScrollable = forwardScrollable || backwardScrollable; + mForwardScrollable = forwardScrollable; + mBackwardScrollable = backwardScrollable; + + boolean scrollPositionChanged = mScrollViewFields.getScrollState().getScrollPosition() + != scrollState.getScrollPosition(); + mScrollViewFields.setScrollState(scrollState); + + if (scrollPositionChanged) { + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); + } } @Override @@ -1295,6 +1315,12 @@ public class NotificationStackScrollLayout } @Override + public void setAccessibilityScrollEventConsumer( + @Nullable Consumer<AccessibilityScrollEvent> consumer) { + mScrollViewFields.setAccessibilityScrollEventConsumer(consumer); + } + + @Override public void setCurrentGestureOverscrollConsumer(@Nullable Consumer<Boolean> consumer) { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; mScrollViewFields.setCurrentGestureOverscrollConsumer(consumer); @@ -2645,6 +2671,11 @@ public class NotificationStackScrollLayout return mHeadsUpInset; } + @Override + public int getStackBottomInset() { + return mPaddingBetweenElements + mShelf.getIntrinsicHeight(); + } + /** * Calculate the gap height between two different views * @@ -4243,17 +4274,27 @@ public class NotificationStackScrollLayout */ @Override public boolean performAccessibilityActionInternal(int action, Bundle arguments) { - // Don't handle scroll accessibility events from the NSSL, when SceneContainer enabled. - if (SceneContainerFlag.isEnabled()) { - return super.performAccessibilityActionInternal(action, arguments); - } - if (super.performAccessibilityActionInternal(action, arguments)) { return true; } if (!isEnabled()) { return false; } + + if (SceneContainerFlag.isEnabled()) { + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: + case android.R.id.accessibilityActionScrollDown: + mScrollViewFields.sendAccessibilityScrollEvent(SCROLL_DOWN); + return true; + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: + case android.R.id.accessibilityActionScrollUp: + mScrollViewFields.sendAccessibilityScrollEvent(SCROLL_UP); + return true; + } + return false; + } + int direction = -1; switch (action) { case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: @@ -5029,25 +5070,21 @@ public class NotificationStackScrollLayout @Override public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { super.onInitializeAccessibilityEventInternal(event); - // Don't handle scroll accessibility events from the NSSL, when SceneContainer enabled. - if (SceneContainerFlag.isEnabled()) { - return; - } - event.setScrollable(mScrollable); event.setMaxScrollX(mScrollX); - event.setScrollY(mOwnScrollY); - event.setMaxScrollY(getScrollRange()); + + if (SceneContainerFlag.isEnabled()) { + event.setScrollY(mScrollViewFields.getScrollState().getScrollPosition()); + event.setMaxScrollY(mScrollViewFields.getScrollState().getMaxScrollPosition()); + } else { + event.setScrollY(mOwnScrollY); + event.setMaxScrollY(getScrollRange()); + } } @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); - // Don't handle scroll accessibility events from the NSSL, when SceneContainer enabled. - if (SceneContainerFlag.isEnabled()) { - return; - } - if (mScrollable) { info.setScrollable(true); if (mBackwardScrollable) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt index f6e8b8f0166b..cf6d45a8938e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.notification.stack import android.util.IndentingPrintWriter +import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState import com.android.systemui.util.printSection import com.android.systemui.util.println import java.util.function.Consumer @@ -32,8 +34,9 @@ import java.util.function.Consumer class ScrollViewFields { /** Used to produce the clipping path */ var scrimClippingShape: ShadeScrimShape? = null - /** Whether the notifications are scrolled all the way to the top (i.e. when freshly opened) */ - var isScrolledToTop: Boolean = true + + /** Scroll state of the notification shade. */ + var scrollState: ShadeScrollState = ShadeScrollState() /** * Height in view pixels at which the Notification Stack would like to be laid out, including @@ -47,6 +50,13 @@ class ScrollViewFields { * placeholder */ var syntheticScrollConsumer: Consumer<Float>? = null + + /** + * When the NSSL navigates through the notifications with TalkBack, it can send scroll events + * here, to be able to browse through the whole list of notifications in the shade. + */ + var accessibilityScrollEventConsumer: Consumer<AccessibilityScrollEvent>? = null + /** * When a gesture is consumed internally by NSSL but needs to be handled by other elements (such * as the notif scrim) as overscroll, we can notify the placeholder through here. @@ -86,10 +96,15 @@ class ScrollViewFields { fun sendRemoteInputRowBottomBound(bottomY: Float?) = remoteInputRowBottomBoundConsumer?.accept(bottomY) + /** send an [AccessibilityScrollEvent] to the [accessibilityScrollEventConsumer] if present */ + fun sendAccessibilityScrollEvent(event: AccessibilityScrollEvent) { + accessibilityScrollEventConsumer?.accept(event) + } + fun dump(pw: IndentingPrintWriter) { pw.printSection("StackViewStates") { pw.println("scrimClippingShape", scrimClippingShape) - pw.println("isScrolledToTop", isScrolledToTop) + pw.println("scrollState", scrollState) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt index c0f1a5619140..5ec4c8988697 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt @@ -17,7 +17,10 @@ package com.android.systemui.statusbar.notification.stack.data.repository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState +import java.util.function.Consumer import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow @@ -44,9 +47,9 @@ class NotificationPlaceholderRepository @Inject constructor() { /** height made available to the notifications in the size-constrained mode of lock screen. */ val constrainedAvailableSpace = MutableStateFlow(0) - /** - * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any - * further. - */ - val scrolledToTop = MutableStateFlow(true) + /** Scroll state of the notification shade. */ + val shadeScrollState = MutableStateFlow(ShadeScrollState()) + + /** A consumer of [AccessibilityScrollEvent]s. */ + var accessibilityScrollEventConsumer: Consumer<AccessibilityScrollEvent>? = null } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 32e092bcdf4d..d4dd1d4b9e0b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -23,8 +23,11 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.data.repository.NotificationPlaceholderRepository import com.android.systemui.statusbar.notification.stack.data.repository.NotificationViewHeightRepository +import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState +import java.util.function.Consumer import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -78,11 +81,9 @@ constructor( val constrainedAvailableSpace: StateFlow<Int> = placeholderRepository.constrainedAvailableSpace.asStateFlow() - /** - * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any - * further. - */ - val scrolledToTop: StateFlow<Boolean> = placeholderRepository.scrolledToTop.asStateFlow() + /** Scroll state of the notification shade. */ + val shadeScrollState: StateFlow<ShadeScrollState> = + placeholderRepository.shadeScrollState.asStateFlow() /** * The amount in px that the notification stack should scroll due to internal expansion. This @@ -123,9 +124,9 @@ constructor( placeholderRepository.shadeScrimBounds.value = bounds } - /** Sets whether the notification stack is scrolled to the top. */ - fun setScrolledToTop(scrolledToTop: Boolean) { - placeholderRepository.scrolledToTop.value = scrolledToTop + /** Updates the current scroll state of the notification shade. */ + fun setScrollState(shadeScrollState: ShadeScrollState) { + placeholderRepository.shadeScrollState.value = shadeScrollState } /** Sets the amount (px) that the notification stack should scroll due to internal expansion. */ @@ -133,6 +134,16 @@ constructor( viewHeightRepository.syntheticScroll.value = delta } + /** Sends an [AccessibilityScrollEvent] to scroll the stack up or down. */ + fun sendAccessibilityScrollEvent(accessibilityScrollEvent: AccessibilityScrollEvent) { + placeholderRepository.accessibilityScrollEventConsumer?.accept(accessibilityScrollEvent) + } + + /** Set a consumer for the [AccessibilityScrollEvent]s to be handled by the placeholder. */ + fun setAccessibilityScrollEventConsumer(consumer: Consumer<AccessibilityScrollEvent>?) { + placeholderRepository.accessibilityScrollEventConsumer = consumer + } + /** Sets whether the current touch gesture is overscroll. */ fun setCurrentGestureOverscroll(isOverscroll: Boolean) { viewHeightRepository.isCurrentGestureOverscroll.value = isOverscroll diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/AccessibilityScrollEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/AccessibilityScrollEvent.kt new file mode 100644 index 000000000000..01341e1d056d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/AccessibilityScrollEvent.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.shared.model + +/** + * An event to be sent by the NotificationStackScrollLayout to the NotificationsPlaceholder, when + * TalkBack runs out of visible notifications, and wants to scroll the shade to access more. + */ +enum class AccessibilityScrollEvent { + SCROLL_UP, + SCROLL_DOWN, +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrollState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrollState.kt new file mode 100644 index 000000000000..3963286094f8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrollState.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.shared.model + +data class ShadeScrollState( + /** + * Whether the notification stack is scrolled to the top (i.e. when freshly opened). It also + * returns true, when scrolling is not possible because all the content fits in the current + * viewport. + */ + val isScrolledToTop: Boolean = true, + + /** + * Current scroll position of the shade. 0 when scrolled to the top, [maxScrollPosition] when + * scrolled all the way to the bottom. + */ + val scrollPosition: Int = 0, + + /** + * Max scroll position of the shade. 0, when no scrolling is possible e.g. all the content fits + * in the current viewport. + */ + val maxScrollPosition: Int = 0, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index 6ad9f01ca4ff..5249a6d304ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.notification.stack.ui.view import android.view.View +import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState import java.util.function.Consumer /** @@ -35,6 +37,9 @@ interface NotificationScrollView { /** Height in pixels required to display the top HeadsUp Notification. */ val topHeadsUpHeight: Int + /** Bottom inset of the Notification Stack that us used to display the Shelf. */ + val stackBottomInset: Int + /** * Since this is an interface rather than a literal View, this provides cast-like access to the * underlying view. @@ -62,12 +67,15 @@ interface NotificationScrollView { /** set the bottom-most y position in px, where we can draw HUNs in this view's coordinates */ fun setHeadsUpBottom(headsUpBottom: Float) - /** set whether the view has been scrolled all the way to the top */ - fun setScrolledToTop(scrolledToTop: Boolean) + /** Updates the current scroll state of the notification shade. */ + fun setScrollState(scrollState: ShadeScrollState) /** Set a consumer for synthetic scroll events */ fun setSyntheticScrollConsumer(consumer: Consumer<Float>?) + /** Set a consumer for accessibility actions to be handled by the placeholder. */ + fun setAccessibilityScrollEventConsumer(consumer: Consumer<AccessibilityScrollEvent>?) + /** Set a consumer for current gesture overscroll events */ fun setCurrentGestureOverscrollConsumer(consumer: Consumer<Boolean>?) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index fb42ee7908b2..4a768714b84f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -83,9 +83,11 @@ constructor( } launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } } - launch { viewModel.scrolledToTop.collectTraced { view.setScrolledToTop(it) } } + launch { viewModel.shadeScrollState.collect { view.setScrollState(it) } } launch { - viewModel.expandFraction.collectTraced { view.setExpandFraction(it.coerceIn(0f, 1f)) } + viewModel.expandFraction.collectTraced { + view.setExpandFraction(it.coerceIn(0f, 1f)) + } } launch { viewModel.qsExpandFraction.collectTraced { view.setQsExpandFraction(it) } } launch { @@ -94,7 +96,9 @@ constructor( } } launch { - viewModel.alphaForLockscreenFadeIn.collectTraced { view.setAlphaForLockscreenFadeIn(it) } + viewModel.alphaForLockscreenFadeIn.collectTraced { + view.setAlphaForLockscreenFadeIn(it) + } } launch { viewModel.isScrollable.collectTraced { view.setScrollingEnabled(it) } } launch { viewModel.isDozing.collectTraced { isDozing -> view.setDozing(isDozing) } } @@ -109,9 +113,13 @@ constructor( .collectTraced { view.setStackTop(-(view.getHeadsUpInset().toFloat())) } } launch { - viewModel.shouldCloseGuts.filter { it }.collectTraced { view.closeGutsOnSceneTouch() } + viewModel.shouldCloseGuts + .filter { it } + .collectTraced { view.closeGutsOnSceneTouch() } + } + launch { + viewModel.suppressHeightUpdates.collectTraced { view.suppressHeightUpdates(it) } } - launch { viewModel.suppressHeightUpdates.collectTraced { view.suppressHeightUpdates(it) } } launchAndDispose { view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) @@ -120,11 +128,13 @@ constructor( view.setRemoteInputRowBottomBoundConsumer( viewModel.remoteInputRowBottomBoundConsumer ) + view.setAccessibilityScrollEventConsumer(viewModel.accessibilityScrollEventConsumer) DisposableHandle { view.setSyntheticScrollConsumer(null) view.setCurrentGestureOverscrollConsumer(null) view.setCurrentGestureInGutsConsumer(null) view.setRemoteInputRowBottomBoundConsumer(null) + view.setAccessibilityScrollEventConsumer(null) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index aec81b0241a2..574ca3ffe5b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -34,8 +34,10 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor +import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_DELAYED_STACK_FADE_IN import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA import com.android.systemui.util.kotlin.ActivatableFlowDumper @@ -255,16 +257,16 @@ constructor( val maxAlpha: Flow<Float> = stackAppearanceInteractor.alphaForBrightnessMirror.dumpValue("maxAlpha") - /** - * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any - * further. - */ - val scrolledToTop: Flow<Boolean> = - stackAppearanceInteractor.scrolledToTop.dumpValue("scrolledToTop") + /** Scroll state of the notification shade. */ + val shadeScrollState: Flow<ShadeScrollState> = stackAppearanceInteractor.shadeScrollState /** Receives the amount (px) that the stack should scroll due to internal expansion. */ val syntheticScrollConsumer: (Float) -> Unit = stackAppearanceInteractor::setSyntheticScroll + /** Receives an event to scroll the stack up or down. */ + val accessibilityScrollEventConsumer: (AccessibilityScrollEvent) -> Unit = + stackAppearanceInteractor::sendAccessibilityScrollEvent + /** * Receives whether the current touch gesture is overscroll as it has already been consumed by * the stack. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index c8e83581e831..a8ce47cf2dd3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -27,12 +27,15 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor +import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState import com.android.systemui.util.kotlin.ActivatableFlowDumper import com.android.systemui.util.kotlin.ActivatableFlowDumperImpl import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import java.util.function.Consumer import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter @@ -140,9 +143,9 @@ constructor( /** The bottom bound of the currently focused remote input notification row. */ val remoteInputRowBottomBound = remoteInputInteractor.remoteInputRowBottomBound - /** Sets whether the notification stack is scrolled to the top. */ - fun setScrolledToTop(scrolledToTop: Boolean) { - interactor.setScrolledToTop(scrolledToTop) + /** Updates the current scroll state of the notification shade. */ + fun setScrollState(scrollState: ShadeScrollState) { + interactor.setScrollState(scrollState) } /** Sets whether the heads up notification is animating away. */ @@ -155,6 +158,11 @@ constructor( headsUpNotificationInteractor.snooze() } + /** Set a consumer for accessibility events to be handled by the placeholder. */ + fun setAccessibilityScrollEventConsumer(consumer: Consumer<AccessibilityScrollEvent>?) { + interactor.setAccessibilityScrollEventConsumer(consumer) + } + @AssistedFactory interface Factory { fun create(): NotificationsPlaceholderViewModel |