diff options
| author | 2024-10-18 11:11:02 +0000 | |
|---|---|---|
| committer | 2024-10-18 11:11:02 +0000 | |
| commit | 24024c72c8d27c743235d3e77f75081fc6aa4900 (patch) | |
| tree | 5d43ed8c469f4d081c800597f85241426edbf553 | |
| parent | 1d0fc968cafd437de35f50c7c8c681298dda42b5 (diff) | |
| parent | 52ce865f34a628a954cfce2ace2c3e4ea76d2647 (diff) | |
Merge "[flexiglass] Scroll the shade with TalkBack" into main
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 |