summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackViewFields.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimShape.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStackView.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java3
21 files changed, 470 insertions, 103 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 cda43476610e..cc8ffe2c35ef 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
@@ -397,8 +397,8 @@ private fun Modifier.debugBackground(
}
fun ShadeScrimRounding.toRoundedCornerShape(radius: Dp): RoundedCornerShape {
- val topRadius = if (roundTop) radius else 0.dp
- val bottomRadius = if (roundBottom) radius else 0.dp
+ val topRadius = if (isTopRounded) radius else 0.dp
+ val bottomRadius = if (isBottomRounded) radius else 0.dp
return RoundedCornerShape(
topStart = topRadius,
topEnd = topRadius,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 53522e276112..c90b2319aae0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -31,6 +31,8 @@ import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
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.ViewPosition
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationStackAppearanceViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
@@ -64,20 +66,38 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
@Test
fun updateBounds() =
testScope.runTest {
- val clipping by collectLastValue(appearanceViewModel.shadeScrimClipping)
+ val radius = MutableStateFlow(32)
+ val viewPosition = MutableStateFlow(ViewPosition(0, 0))
+ val shape by collectLastValue(appearanceViewModel.shadeScrimShape(radius, viewPosition))
+
+ placeholderViewModel.onBoundsChanged(left = 0f, top = 200f, right = 100f, bottom = 550f)
+ assertThat(shape)
+ .isEqualTo(
+ ShadeScrimShape(
+ bounds =
+ ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f),
+ topRadius = 32,
+ bottomRadius = 0
+ )
+ )
- val top = 200f
- val left = 0f
- val bottom = 550f
- val right = 100f
+ viewPosition.value = ViewPosition(200, 15)
+ radius.value = 24
placeholderViewModel.onBoundsChanged(
- left = left,
- top = top,
- right = right,
- bottom = bottom
+ left = 210f,
+ top = 200f,
+ right = 300f,
+ bottom = 550f
)
- assertThat(clipping?.bounds)
- .isEqualTo(ShadeScrimBounds(left = left, top = top, right = right, bottom = bottom))
+ assertThat(shape)
+ .isEqualTo(
+ ShadeScrimShape(
+ bounds =
+ ShadeScrimBounds(left = 10f, top = 185f, right = 100f, bottom = 535f),
+ topRadius = 24,
+ bottomRadius = 0
+ )
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
index dc928c49fbd3..50b77dcf9468 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -68,11 +68,11 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
assertThat(stackRounding)
- .isEqualTo(ShadeScrimRounding(roundTop = true, roundBottom = false))
+ .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = false))
kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
assertThat(stackRounding)
- .isEqualTo(ShadeScrimRounding(roundTop = true, roundBottom = true))
+ .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = true))
}
@Test(expected = IllegalStateException::class)
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
index ce798ba3912f..7dd883b89db7 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -17,6 +17,7 @@
package com.android.systemui.common.ui.view
import android.view.View
+import kotlinx.coroutines.DisposableHandle
/**
* Set this view's [View#importantForAccessibility] to [View#IMPORTANT_FOR_ACCESSIBILITY_YES] or
@@ -43,3 +44,10 @@ inline fun <reified T : View> View.getNearestParent(): T? {
}
return null
}
+
+/** Adds a [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */
+fun View.onLayoutChanged(onLayoutChanged: (v: View) -> Unit): DisposableHandle {
+ val listener = View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> onLayoutChanged(v) }
+ addOnLayoutChangeListener(listener)
+ return DisposableHandle { removeOnLayoutChangeListener(listener) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 343f37744ce9..c9a4433fc5e5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1588,7 +1588,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
* @param forceClockUpdate Should the clock be updated even when not on keyguard
*/
private void positionClockAndNotifications(boolean forceClockUpdate) {
- boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
+ boolean animate = !SceneContainerFlag.isEnabled()
+ && mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
int stackScrollerPadding;
boolean onKeyguard = isKeyguardShowing();
@@ -1675,7 +1676,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mClockPositionResult.clockX, mClockPositionResult.clockY);
}
- boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
+ boolean animate = !SceneContainerFlag.isEnabled()
+ && mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
if (!MigrateClocksToBlueprint.isEnabled()) {
@@ -3963,7 +3965,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mShadeRepository.setLegacyShadeExpansion(mExpandedFraction);
mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction);
mExpansionDragDownAmountPx = h;
- mAmbientState.setExpansionFraction(mExpandedFraction);
+ if (!SceneContainerFlag.isEnabled()) {
+ mAmbientState.setExpansionFraction(mExpandedFraction);
+ }
onHeightUpdated(mExpandedHeight);
updateExpansionAndVisibility();
});
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 2cb9f9acca26..fc84fa2051d0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -43,6 +43,7 @@ import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationInsetsController
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStackView
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
import com.android.systemui.statusbar.phone.StatusBarLocation
@@ -51,6 +52,7 @@ import com.android.systemui.statusbar.phone.TapAgainView
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.tuner.TunerService
+import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Named
@@ -59,6 +61,14 @@ import javax.inject.Provider
/** Module for providing views related to the shade. */
@Module
abstract class ShadeViewProviderModule {
+
+ @Binds
+ @SysUISingleton
+ // TODO(b/277762009): Only allow this view's binder to inject the view.
+ abstract fun bindsNotificationStackView(
+ notificationStackScrollLayout: NotificationStackScrollLayout
+ ): NotificationStackView
+
companion object {
const val SHADE_HEADER = "large_screen_shade_header"
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 3367dc427f43..a9a968fcdc07 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
@@ -113,6 +113,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStackView;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -143,7 +144,9 @@ import java.util.function.Consumer;
/**
* A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
*/
-public class NotificationStackScrollLayout extends ViewGroup implements Dumpable {
+public class NotificationStackScrollLayout
+ extends ViewGroup
+ implements Dumpable, NotificationStackView {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
@@ -218,6 +221,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
private final StackScrollAlgorithm mStackScrollAlgorithm;
private final AmbientState mAmbientState;
+ private final StackViewFields mStackViewFields = new StackViewFields();
private final GroupMembershipManager mGroupMembershipManager;
private final GroupExpansionManager mGroupExpansionManager;
@@ -589,7 +593,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@Override
public boolean isScrolledToTop() {
if (SceneContainerFlag.isEnabled()) {
- return mController.isPlaceholderScrolledToTop();
+ return mStackViewFields.isScrolledToTop();
} else {
return mOwnScrollY == 0;
}
@@ -1130,6 +1134,48 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
+ @Override
+ public View asView() {
+ return this;
+ }
+
+ @Override
+ public void setScrolledToTop(boolean scrolledToTop) {
+ mStackViewFields.setScrolledToTop(scrolledToTop);
+ }
+
+ @Override
+ public void setStackTop(float stackTop) {
+ mStackViewFields.setStackTop(stackTop);
+ // TODO(b/332574413): replace the following with using stackTop
+ updateTopPadding(stackTop, isAddOrRemoveAnimationPending());
+ }
+
+ @Override
+ public void setStackBottom(float stackBottom) {
+ mStackViewFields.setStackBottom(stackBottom);
+ }
+
+ @Override
+ public void setHeadsUpTop(float headsUpTop) {
+ mStackViewFields.setHeadsUpTop(headsUpTop);
+ }
+
+ @Override
+ public void setSyntheticScrollConsumer(@Nullable Consumer<Float> consumer) {
+ mStackViewFields.setSyntheticScrollConsumer(consumer);
+ }
+
+ @Override
+ public void setStackHeightConsumer(@Nullable Consumer<Float> consumer) {
+ mStackViewFields.setStackHeightConsumer(consumer);
+ }
+
+ @Override
+ public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) {
+ mStackViewFields.setHeadsUpHeightConsumer(consumer);
+ }
+
/**
* @param listener to be notified after the location of Notification children might have
* changed.
@@ -1380,6 +1426,31 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mOnStackYChanged = onStackYChanged;
}
+ @Override
+ public void setExpandFraction(float expandFraction) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ final float oldFraction = mAmbientState.getExpansionFraction();
+ final boolean wasExpanding = oldFraction != 0f && oldFraction != 1f;
+ final boolean nowExpanding = expandFraction != 0f && expandFraction != 1f;
+
+ // need to enter 'expanding' state before handling the new expand fraction, and then
+ if (nowExpanding && !wasExpanding) {
+ onExpansionStarted();
+ mController.checkSnoozeLeavebehind();
+ }
+
+ // Update the expand progress between started/stopped events
+ mAmbientState.setExpansionFraction(expandFraction);
+ // TODO(b/332577544): don't convert to height which then converts to the fraction again
+ setExpandedHeight(expandFraction * getHeight());
+
+ // expansion stopped event requires that the expandFraction has already been updated
+ if (!nowExpanding && wasExpanding) {
+ setCheckForLeaveBehind(false);
+ onExpansionStopped();
+ }
+ }
+
/**
* Update the height of the panel.
*
@@ -2314,7 +2385,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
shelfIntrinsicHeight);
mIntrinsicContentHeight = height;
- mController.setIntrinsicContentHeight(mIntrinsicContentHeight);
+ mStackViewFields.sendStackHeight(height);
// The topPadding can be bigger than the regular padding when qs is expanded, in that
// state the maxPanelHeight and the contentHeight should be bigger
@@ -3553,7 +3624,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
protected boolean isInsideQsHeader(MotionEvent ev) {
if (SceneContainerFlag.isEnabled()) {
- return ev.getY() < mController.getPlaceholderTop();
+ return ev.getY() < mStackViewFields.getShadeScrimClipping().getBounds().getTop();
}
mQsHeader.getBoundsOnScreen(mQsHeaderBound);
@@ -4086,7 +4157,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// to it so that it can scroll the stack and scrim accordingly.
if (SceneContainerFlag.isEnabled()) {
float diff = endPosition - layoutEnd;
- mController.sendSyntheticScrollToSceneFramework(diff);
+ mStackViewFields.sendSyntheticScroll(diff);
}
setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
mDisallowScrollingInThisMotion = true;
@@ -4969,6 +5040,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
elapsedRealtime - mLastUpdateSidePaddingElapsedRealtime);
println(pw, "isSmallLandscapeLockscreenEnabled", mIsSmallLandscapeLockscreenEnabled);
mNotificationStackSizeCalculator.dump(pw, args);
+ mStackViewFields.dump(pw);
});
pw.println();
pw.println("Contents:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 59901aca0425..06479e5a8e0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -83,6 +83,7 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
@@ -125,7 +126,6 @@ import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
@@ -189,7 +189,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
private final ShadeController mShadeController;
private final Provider<WindowRootView> mWindowRootView;
- private final NotificationStackAppearanceInteractor mStackAppearanceInteractor;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final KeyguardBypassController mKeyguardBypassController;
@@ -742,7 +741,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
NotificationListViewBinder viewBinder,
ShadeController shadeController,
Provider<WindowRootView> windowRootView,
- NotificationStackAppearanceInteractor stackAppearanceInteractor,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
@@ -795,7 +793,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
mWindowRootView = windowRootView;
- mStackAppearanceInteractor = stackAppearanceInteractor;
mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
mSecureSettings = secureSettings;
@@ -1124,6 +1121,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public boolean isAddOrRemoveAnimationPending() {
+ SceneContainerFlag.assertInLegacyMode();
return mView != null && mView.isAddOrRemoveAnimationPending();
}
@@ -1163,29 +1161,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
}
- /** Send internal notification expansion to the scene container framework. */
- public void sendSyntheticScrollToSceneFramework(Float delta) {
- mStackAppearanceInteractor.setSyntheticScroll(delta);
- }
-
- /** Get the y-coordinate of the top bound of the stack. */
- public float getPlaceholderTop() {
- return mStackAppearanceInteractor.getShadeScrimBounds().getValue().getTop();
- }
-
- /**
- * Returns whether the notification stack is scrolled to the top; i.e., it cannot be scrolled
- * down any further.
- */
- public boolean isPlaceholderScrolledToTop() {
- return mStackAppearanceInteractor.getScrolledToTop().getValue();
- }
-
- /** Set the intrinsic height of the stack content without additional padding. */
- public void setIntrinsicContentHeight(float intrinsicContentHeight) {
- mStackAppearanceInteractor.setStackHeight(intrinsicContentHeight);
- }
-
public void setIntrinsicPadding(int intrinsicPadding) {
mView.setIntrinsicPadding(intrinsicPadding);
}
@@ -1264,6 +1239,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public void setScrollingEnabled(boolean enabled) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setScrollingEnabled(enabled);
}
@@ -1284,6 +1260,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public void updateTopPadding(float qsHeight, boolean animate) {
+ SceneContainerFlag.assertInLegacyMode();
mView.updateTopPadding(qsHeight, animate);
}
@@ -1386,11 +1363,13 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public void onExpansionStarted() {
+ SceneContainerFlag.assertInLegacyMode();
mView.onExpansionStarted();
checkSnoozeLeavebehind();
}
public void onExpansionStopped() {
+ SceneContainerFlag.assertInLegacyMode();
mView.setCheckForLeaveBehind(false);
mView.onExpansionStopped();
}
@@ -1519,6 +1498,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public void setExpandedHeight(float expandedHeight) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setExpandedHeight(expandedHeight);
}
@@ -1794,6 +1774,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
*/
public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
int bottomRadius) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackViewFields.kt
new file mode 100644
index 000000000000..5f62ae7b99f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackViewFields.kt
@@ -0,0 +1,81 @@
+/*
+ * 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
+
+import android.util.IndentingPrintWriter
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping
+import com.android.systemui.util.printSection
+import com.android.systemui.util.println
+import java.util.function.Consumer
+
+/**
+ * This is a state holder object used by [NSSL][NotificationStackScrollLayout] to contain states
+ * provided by the `NotificationStackViewBinder` to the [NotificationStackView]
+ *
+ * Unlike AmbientState, no class other than NSSL should ever have access to this class in any way.
+ * These fields are effectively NSSL's private fields.
+ */
+class StackViewFields {
+ /** Used to produce the clipping path */
+ var shadeScrimClipping: ShadeScrimClipping = ShadeScrimClipping()
+ /** Y coordinate in view pixels of the top of the notification stack */
+ var stackTop: Float = 0f
+ /**
+ * Y coordinate in view pixels above which the bottom of the notification stack / shelf / footer
+ * must be.
+ */
+ var stackBottom: Float = 0f
+ /** Y coordinate in view pixels of the top of the HUN */
+ var headsUpTop: Float = 0f
+ /** Whether the notifications are scrolled all the way to the top (i.e. when freshly opened) */
+ var isScrolledToTop: Boolean = true
+
+ /**
+ * When internal NSSL expansion requires the stack to be scrolled (e.g. to keep an expanding
+ * notification in view), that scroll amount can be sent here and it will be handled by the
+ * placeholder
+ */
+ var syntheticScrollConsumer: Consumer<Float>? = null
+ /**
+ * Any time the stack height is recalculated, it should be updated here to be used by the
+ * placeholder
+ */
+ var stackHeightConsumer: Consumer<Float>? = null
+ /**
+ * Any time the heads up height is recalculated, it should be updated here to be used by the
+ * placeholder
+ */
+ var headsUpHeightConsumer: Consumer<Float>? = null
+
+ /** send the [syntheticScroll] to the [syntheticScrollConsumer], if present. */
+ fun sendSyntheticScroll(syntheticScroll: Float) =
+ syntheticScrollConsumer?.accept(syntheticScroll)
+ /** send the [stackHeight] to the [stackHeightConsumer], if present. */
+ fun sendStackHeight(stackHeight: Float) = stackHeightConsumer?.accept(stackHeight)
+ /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */
+ fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight)
+
+ fun dump(pw: IndentingPrintWriter) {
+ pw.printSection("StackViewStates") {
+ pw.println("shadeScrimClipping", shadeScrimClipping)
+ pw.println("stackTop", stackTop)
+ pw.println("stackBottom", stackBottom)
+ pw.println("headsUpTop", headsUpTop)
+ pw.println("isScrolledToTop", isScrolledToTop)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
index 938e43e4a2d4..8a9da69079d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
@@ -35,7 +35,7 @@ class NotificationViewHeightRepository @Inject constructor() {
val stackHeight = MutableStateFlow(0f)
/** The height in px of the current heads up notification. */
- val activeHeadsUpRowHeight = MutableStateFlow(0f)
+ val headsUpHeight = MutableStateFlow(0f)
/**
* The amount in px that the notification stack should scroll due to internal expansion. This
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 32562f109252..5753d612c9ec 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
@@ -59,8 +59,8 @@ constructor(
isExpandingFromHeadsUp,
) { shadeMode, isExpandingFromHeadsUp ->
ShadeScrimRounding(
- roundTop = !(shadeMode == ShadeMode.Split && isExpandingFromHeadsUp),
- roundBottom = shadeMode != ShadeMode.Single,
+ isTopRounded = !(shadeMode == ShadeMode.Split && isExpandingFromHeadsUp),
+ isBottomRounded = shadeMode != ShadeMode.Single,
)
}
.distinctUntilChanged()
@@ -75,12 +75,18 @@ constructor(
/** The y-coordinate in px of top of the contents of the notification stack. */
val stackTop: StateFlow<Float> = placeholderRepository.stackTop.asStateFlow()
+ /** The y-coordinate in px of bottom of the contents of the notification stack. */
+ val stackBottom: StateFlow<Float> = placeholderRepository.stackBottom.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()
+ /** The y-coordinate in px of bottom of the contents of the HUN. */
+ val headsUpTop: StateFlow<Float> = placeholderRepository.headsUpTop.asStateFlow()
+
/**
* The amount in px that the notification stack should scroll due to internal expansion. This
* should only happen when a notification expansion hits the bottom of the screen, so it is
@@ -99,6 +105,11 @@ constructor(
viewHeightRepository.stackHeight.value = height
}
+ /** Sets the height of heads up notification. */
+ fun setHeadsUpHeight(height: Float) {
+ viewHeightRepository.headsUpHeight.value = height
+ }
+
/** Sets the y-coord in px of the top of the contents of the notification stack. */
fun setStackTop(startY: Float) {
placeholderRepository.stackTop.value = startY
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
index 448127a55b89..7e3e724bfed9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
@@ -29,4 +29,12 @@ data class ShadeScrimBounds(
) {
/** The current height of the notification container. */
val height: Float = bottom - top
+
+ operator fun minus(position: ViewPosition) =
+ ShadeScrimBounds(
+ left = left - position.left,
+ top = top - position.top,
+ right = right - position.left,
+ bottom = bottom - position.top,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt
index 621dd0c49f54..e86fd1c7cdc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack.shared.model
-/** Models the clipping rounded rectangle of the notification stack */
+/** Models the clipping rounded rectangle of the notification stack, where the rounding is binary */
data class ShadeScrimClipping(
val bounds: ShadeScrimBounds = ShadeScrimBounds(),
val rounding: ShadeScrimRounding = ShadeScrimRounding()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt
index 2fe265f35604..9d4231c8967b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt
@@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.stack.shared.model
/** Models the corner rounds of the notification stack. */
data class ShadeScrimRounding(
/** Whether the top corners of the notification stack should be rounded. */
- val roundTop: Boolean = false,
+ val isTopRounded: Boolean = false,
/** Whether the bottom corners of the notification stack should be rounded. */
- val roundBottom: Boolean = false,
+ val isBottomRounded: Boolean = false,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimShape.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimShape.kt
new file mode 100644
index 000000000000..e6f0647a059d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimShape.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
+
+/** Models the clipping rounded rectangle of the notification stack, with corner radii in px */
+data class ShadeScrimShape(
+ val bounds: ShadeScrimBounds = ShadeScrimBounds(),
+ /** radius in px of the top corners */
+ val topRadius: Int,
+ /** radius in px of the bottom corners */
+ val bottomRadius: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
new file mode 100644
index 000000000000..5d2b0ad2b0a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
@@ -0,0 +1,20 @@
+/*
+ * 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 offset of view, used to adjust bounds. */
+data class ViewPosition(val left: Int = 0, val top: Int = 0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStackView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStackView.kt
new file mode 100644
index 000000000000..73f572d87af6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStackView.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.ui.view
+
+import android.view.View
+import java.util.function.Consumer
+
+/**
+ * This view (interface) is the view which scrolls and positions the heads up notification and
+ * notification stack, but is otherwise agnostic to the content.
+ */
+interface NotificationStackView {
+ /**
+ * Since this is an interface rather than a literal View, this provides cast-like access to the
+ * underlying view.
+ */
+ fun asView(): View
+
+ /** Set the clipping bounds used when drawing */
+ fun setRoundedClippingBounds(
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ topRadius: Int,
+ bottomRadius: Int
+ )
+
+ /** set the y position in px of the top of the stack in this view's coordinates */
+ fun setStackTop(stackTop: Float)
+
+ /** set the y position in px of the bottom of the stack in this view's coordinates */
+ fun setStackBottom(stackBottom: Float)
+
+ /** set the y position in px of the top of the HUN in this view's coordinates */
+ fun setHeadsUpTop(headsUpTop: Float)
+
+ /** set whether the view has been scrolled all the way to the top */
+ fun setScrolledToTop(scrolledToTop: Boolean)
+
+ /** Set a consumer for synthetic scroll events */
+ fun setSyntheticScrollConsumer(consumer: Consumer<Float>?)
+ /** Set a consumer for stack height changed events */
+ fun setStackHeightConsumer(consumer: Consumer<Float>?)
+ /** Set a consumer for heads up height changed events */
+ fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?)
+
+ /** sets that scrolling is allowed */
+ fun setScrollingEnabled(enabled: Boolean)
+
+ /** sets the current expand fraction */
+ fun setExpandFraction(expandFraction: Float)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt
index d6d31db86e61..a51e796db33e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt
@@ -19,35 +19,44 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.view.onLayoutChanged
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.stack.AmbientState
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStackView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import com.android.systemui.util.kotlin.launchAndDispose
import javax.inject.Inject
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-/** Binds the NSSL/Controller/AmbientState to their ViewModel. */
+/** Binds the NotificationStackView. */
@SysUISingleton
class NotificationStackViewBinder
@Inject
constructor(
+ dumpManager: DumpManager,
@Main private val mainImmediateDispatcher: CoroutineDispatcher,
- private val ambientState: AmbientState,
- private val view: NotificationStackScrollLayout,
- private val controller: NotificationStackScrollLayoutController,
+ private val stack: NotificationStackView,
private val viewModel: NotificationStackAppearanceViewModel,
private val configuration: ConfigurationState,
-) {
+) : FlowDumperImpl(dumpManager) {
+ private val view = stack.asView()
+
+ private val viewPosition = MutableStateFlow(ViewPosition()).dumpValue("viewPosition")
+ private val viewTopOffset = viewPosition.map { it.top }.distinctUntilChanged()
fun bindWhileAttached(): DisposableHandle {
return view.repeatWhenAttached(mainImmediateDispatcher) {
@@ -56,48 +65,48 @@ constructor(
}
suspend fun bind() = coroutineScope {
- launch {
- combine(viewModel.shadeScrimClipping, clipRadius, ::Pair).collect {
- (clipping, clipRadius) ->
- val (bounds, rounding) = clipping
- val viewLeft = controller.view.left
- val viewTop = controller.view.top
- controller.setRoundedClippingBounds(
- bounds.left.roundToInt() - viewLeft,
- bounds.top.roundToInt() - viewTop,
- bounds.right.roundToInt() - viewLeft,
- bounds.bottom.roundToInt() - viewTop,
- if (rounding.roundTop) clipRadius else 0,
- if (rounding.roundBottom) clipRadius else 0,
- )
- }
+ launchAndDispose {
+ viewPosition.value = ViewPosition(view.left, view.top)
+ view.onLayoutChanged { viewPosition.value = ViewPosition(it.left, it.top) }
}
launch {
- viewModel.stackTop.collect {
- controller.updateTopPadding(it, controller.isAddOrRemoveAnimationPending)
+ viewModel.shadeScrimShape(scrimRadius, viewPosition).collect { clipping ->
+ stack.setRoundedClippingBounds(
+ clipping.bounds.left.roundToInt(),
+ clipping.bounds.top.roundToInt(),
+ clipping.bounds.right.roundToInt(),
+ clipping.bounds.bottom.roundToInt(),
+ clipping.topRadius,
+ clipping.bottomRadius,
+ )
}
}
- launch {
- var wasExpanding = false
- viewModel.expandFraction.collect { expandFraction ->
- val nowExpanding = expandFraction != 0f && expandFraction != 1f
- if (nowExpanding && !wasExpanding) {
- controller.onExpansionStarted()
- }
- ambientState.expansionFraction = expandFraction
- controller.expandedHeight = expandFraction * controller.view.height
- if (!nowExpanding && wasExpanding) {
- controller.onExpansionStopped()
- }
- wasExpanding = nowExpanding
+ launch { viewModel.stackTop.minusTopOffset().collect { stack.setStackTop(it) } }
+ launch { viewModel.stackBottom.minusTopOffset().collect { stack.setStackBottom(it) } }
+ launch { viewModel.scrolledToTop.collect { stack.setScrolledToTop(it) } }
+ launch { viewModel.headsUpTop.minusTopOffset().collect { stack.setHeadsUpTop(it) } }
+ launch { viewModel.expandFraction.collect { stack.setExpandFraction(it) } }
+ launch { viewModel.isScrollable.collect { stack.setScrollingEnabled(it) } }
+
+ launchAndDispose {
+ stack.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
+ stack.setStackHeightConsumer(viewModel.stackHeightConsumer)
+ stack.setHeadsUpHeightConsumer(viewModel.headsUpHeightConsumer)
+ DisposableHandle {
+ stack.setSyntheticScrollConsumer(null)
+ stack.setStackHeightConsumer(null)
+ stack.setHeadsUpHeightConsumer(null)
}
}
-
- launch { viewModel.isScrollable.collect { controller.setScrollingEnabled(it) } }
}
- private val clipRadius: Flow<Int>
+ /** Combine with the topOffset flow and subtract that value from this flow's value */
+ private fun Flow<Float>.minusTopOffset() =
+ combine(viewTopOffset) { y, topOffset -> y - topOffset }
+
+ /** flow of the scrim clipping radius */
+ private val scrimRadius: Flow<Int>
get() = configuration.getDimensionPixelOffset(R.dimen.notification_scrim_corner_radius)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 071127c03202..b5776f30d938 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -26,10 +26,11 @@ import com.android.systemui.scene.shared.model.Scenes.Shade
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
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.ViewPosition
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -80,7 +81,7 @@ constructor(
.dumpWhileCollecting("expandFraction")
/** The bounds of the notification stack in the current scene. */
- val shadeScrimClipping: Flow<ShadeScrimClipping> =
+ private val shadeScrimClipping: Flow<ShadeScrimClipping> =
combine(
stackAppearanceInteractor.shadeScrimBounds,
stackAppearanceInteractor.shadeScrimRounding,
@@ -88,8 +89,35 @@ constructor(
)
.dumpWhileCollecting("stackClipping")
+ fun shadeScrimShape(cornerRadius: Flow<Int>, viewPosition: Flow<ViewPosition>) =
+ combine(shadeScrimClipping, cornerRadius, viewPosition) { clipping, radius, position ->
+ ShadeScrimShape(
+ bounds = clipping.bounds - position,
+ topRadius = radius.takeIf { clipping.rounding.isTopRounded } ?: 0,
+ bottomRadius = radius.takeIf { clipping.rounding.isBottomRounded } ?: 0
+ )
+ }
+ .dumpWhileCollecting("shadeScrimShape")
+
/** The y-coordinate in px of top of the contents of the notification stack. */
- val stackTop: StateFlow<Float> = stackAppearanceInteractor.stackTop.dumpValue("stackTop")
+ val stackTop: Flow<Float> = stackAppearanceInteractor.stackTop.dumpValue("stackTop")
+ /** The y-coordinate in px of bottom of the contents of the notification stack. */
+ val stackBottom: Flow<Float> = stackAppearanceInteractor.stackBottom.dumpValue("stackBottom")
+ /**
+ * 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")
+ /** The y-coordinate in px of bottom of the contents of the HUN. */
+ val headsUpTop: Flow<Float> = stackAppearanceInteractor.headsUpTop.dumpValue("headsUpTop")
+
+ /** Receives the amount (px) that the stack should scroll due to internal expansion. */
+ val syntheticScrollConsumer: (Float) -> Unit = stackAppearanceInteractor::setSyntheticScroll
+ /** Receives the height of the contents of the notification stack. */
+ val stackHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setStackHeight
+ /** Receives the height of the heads up notification. */
+ val headsUpHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setHeadsUpHeight
/** Whether the notification stack is scrollable or not. */
val isScrollable: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt
index 909a18be4b9e..97d957dde01b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt
@@ -17,8 +17,14 @@
package com.android.systemui.util.kotlin
import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.Job
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.launch
/**
* Suspends to keep getting updates until cancellation. Once cancelled, mark this as eligible for
@@ -42,3 +48,22 @@ suspend fun DisposableHandle.awaitCancellationThenDispose() {
dispose()
}
}
+
+/**
+ * This will [launch], run [onLaunch] to get a [DisposableHandle], and finally
+ * [awaitCancellationThenDispose][DisposableHandle.awaitCancellationThenDispose]. This can be used
+ * to structure self-disposing code which attaches listeners, for example in ViewBinders:
+ * ```
+ * suspend fun bind(view: MyView, viewModel: MyViewModel) = coroutineScope {
+ * launchAndDispose {
+ * view.setOnClickListener { viewModel.handleClick() }
+ * DisposableHandle { view.setOnClickListener(null) }
+ * }
+ * }
+ * ```
+ */
+inline fun CoroutineScope.launchAndDispose(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ crossinline onLaunch: () -> DisposableHandle
+): Job = launch(context, start) { onLaunch().awaitCancellationThenDispose() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index cd8be573a2ac..912ecb340c3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -94,7 +94,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -161,7 +160,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
@Mock private ShadeController mShadeController;
@Mock private Provider<WindowRootView> mWindowRootView;
- @Mock private NotificationStackAppearanceInteractor mNotificationStackAppearanceInteractor;
private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(),
logcatLogBuffer());
private final NotificationStackScrollLogger mLogger = new NotificationStackScrollLogger(
@@ -1016,7 +1014,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mViewBinder,
mShadeController,
mWindowRootView,
- mNotificationStackAppearanceInteractor,
mKosmos.getInteractionJankMonitor(),
mStackLogger,
mLogger,