Merge "Fix HUN pinning" into tm-qpr-dev am: bc1144e631 am: 7df96aa95f
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19120487 Change-Id: I86affd19fae4a4bae2f93c8f13506bb2f82e903d Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -24,8 +24,7 @@ import android.util.MathUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.policy.SystemBarUtils;
|
||||
import com.android.keyguard.BouncerPanelExpansionCalculator;
|
||||
import com.android.systemui.R;
|
||||
@@ -65,6 +64,9 @@ public class StackScrollAlgorithm {
|
||||
private int mPinnedZTranslationExtra;
|
||||
private float mNotificationScrimPadding;
|
||||
private int mMarginBottom;
|
||||
private float mQuickQsOffsetHeight;
|
||||
private float mSmallCornerRadius;
|
||||
private float mLargeCornerRadius;
|
||||
|
||||
public StackScrollAlgorithm(
|
||||
Context context,
|
||||
@@ -74,10 +76,10 @@ public class StackScrollAlgorithm {
|
||||
}
|
||||
|
||||
public void initView(Context context) {
|
||||
initConstants(context);
|
||||
updateResources(context);
|
||||
}
|
||||
|
||||
private void initConstants(Context context) {
|
||||
private void updateResources(Context context) {
|
||||
Resources res = context.getResources();
|
||||
mPaddingBetweenElements = res.getDimensionPixelSize(
|
||||
R.dimen.notification_divider_height);
|
||||
@@ -93,6 +95,9 @@ public class StackScrollAlgorithm {
|
||||
R.dimen.notification_section_divider_height_lockscreen);
|
||||
mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
|
||||
mMarginBottom = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
|
||||
mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(context);
|
||||
mSmallCornerRadius = res.getDimension(R.dimen.notification_corner_radius_small);
|
||||
mLargeCornerRadius = res.getDimension(R.dimen.notification_corner_radius);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -441,6 +446,15 @@ public class StackScrollAlgorithm {
|
||||
return false;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void maybeUpdateHeadsUpIsVisible(ExpandableViewState viewState, boolean isShadeExpanded,
|
||||
boolean mustStayOnScreen, boolean topVisible, float viewEnd, float hunMax) {
|
||||
|
||||
if (isShadeExpanded && mustStayOnScreen && topVisible) {
|
||||
viewState.headsUpIsVisible = viewEnd < hunMax;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/172289889) polish shade open from HUN
|
||||
/**
|
||||
* Populates the {@link ExpandableViewState} for a single child.
|
||||
@@ -474,14 +488,6 @@ public class StackScrollAlgorithm {
|
||||
: ShadeInterpolation.getContentAlpha(expansion);
|
||||
}
|
||||
|
||||
if (ambientState.isShadeExpanded() && view.mustStayOnScreen()
|
||||
&& viewState.yTranslation >= 0) {
|
||||
// Even if we're not scrolled away we're in view and we're also not in the
|
||||
// shelf. We can relax the constraints and let us scroll off the top!
|
||||
float end = viewState.yTranslation + viewState.height + ambientState.getStackY();
|
||||
viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
|
||||
}
|
||||
|
||||
final float expansionFraction = getExpansionFractionWithoutShelf(
|
||||
algorithmState, ambientState);
|
||||
|
||||
@@ -497,8 +503,15 @@ public class StackScrollAlgorithm {
|
||||
algorithmState.mCurrentExpandedYPosition += gap;
|
||||
}
|
||||
|
||||
// Must set viewState.yTranslation _before_ use.
|
||||
// Incoming views have yTranslation=0 by default.
|
||||
viewState.yTranslation = algorithmState.mCurrentYPosition;
|
||||
|
||||
maybeUpdateHeadsUpIsVisible(viewState, ambientState.isShadeExpanded(),
|
||||
view.mustStayOnScreen(), /* topVisible */ viewState.yTranslation >= 0,
|
||||
/* viewEnd */ viewState.yTranslation + viewState.height + ambientState.getStackY(),
|
||||
/* hunMax */ ambientState.getMaxHeadsUpTranslation()
|
||||
);
|
||||
if (view instanceof FooterView) {
|
||||
final boolean shadeClosed = !ambientState.isShadeExpanded();
|
||||
final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
|
||||
@@ -682,7 +695,8 @@ public class StackScrollAlgorithm {
|
||||
if (row.mustStayOnScreen() && !childState.headsUpIsVisible
|
||||
&& !row.showingPulsing()) {
|
||||
// Ensure that the heads up is always visible even when scrolled off
|
||||
clampHunToTop(ambientState, row, childState);
|
||||
clampHunToTop(mQuickQsOffsetHeight, ambientState.getStackTranslation(),
|
||||
row.getCollapsedHeight(), childState);
|
||||
if (isTopEntry && row.isAboveShelf()) {
|
||||
// the first hun can't get off screen.
|
||||
clampHunToMaxTranslation(ambientState, row, childState);
|
||||
@@ -719,27 +733,62 @@ public class StackScrollAlgorithm {
|
||||
}
|
||||
}
|
||||
|
||||
private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
|
||||
ExpandableViewState childState) {
|
||||
float newTranslation = Math.max(ambientState.getTopPadding()
|
||||
+ ambientState.getStackTranslation(), childState.yTranslation);
|
||||
childState.height = (int) Math.max(childState.height - (newTranslation
|
||||
- childState.yTranslation), row.getCollapsedHeight());
|
||||
childState.yTranslation = newTranslation;
|
||||
/**
|
||||
* When shade is open and we are scrolled to the bottom of notifications,
|
||||
* clamp incoming HUN in its collapsed form, right below qs offset.
|
||||
* Transition pinned collapsed HUN to full height when scrolling back up.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void clampHunToTop(float quickQsOffsetHeight, float stackTranslation, float collapsedHeight,
|
||||
ExpandableViewState viewState) {
|
||||
|
||||
final float newTranslation = Math.max(quickQsOffsetHeight + stackTranslation,
|
||||
viewState.yTranslation);
|
||||
|
||||
// Transition from collapsed pinned state to fully expanded state
|
||||
// when the pinned HUN approaches its actual location (when scrolling back to top).
|
||||
final float distToRealY = newTranslation - viewState.yTranslation;
|
||||
viewState.height = (int) Math.max(viewState.height - distToRealY, collapsedHeight);
|
||||
viewState.yTranslation = newTranslation;
|
||||
}
|
||||
|
||||
// Pin HUN to bottom of expanded QS
|
||||
// while the rest of notifications are scrolled offscreen.
|
||||
private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
|
||||
ExpandableViewState childState) {
|
||||
float newTranslation;
|
||||
float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
|
||||
float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
|
||||
final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
|
||||
+ ambientState.getStackTranslation();
|
||||
maxHeadsUpTranslation = Math.min(maxHeadsUpTranslation, maxShelfPosition);
|
||||
float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
|
||||
newTranslation = Math.min(childState.yTranslation, bottomPosition);
|
||||
|
||||
final float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
|
||||
final float newTranslation = Math.min(childState.yTranslation, bottomPosition);
|
||||
childState.height = (int) Math.min(childState.height, maxHeadsUpTranslation
|
||||
- newTranslation);
|
||||
childState.yTranslation = newTranslation;
|
||||
|
||||
// Animate pinned HUN bottom corners to and from original roundness.
|
||||
final float originalCornerRadius =
|
||||
row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
|
||||
final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
|
||||
ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
|
||||
row.setBottomRoundness(roundness, /* animate= */ false);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
|
||||
float viewMaxHeight, float originalCornerRadius) {
|
||||
|
||||
// Compute y where corner roundness should be in its original unpinned state.
|
||||
// We use view max height because the pinned collapsed HUN expands to max height
|
||||
// when it becomes unpinned.
|
||||
final float originalRoundnessY = hostViewHeight - viewMaxHeight;
|
||||
|
||||
final float distToOriginalRoundness = Math.max(0f, stackY - originalRoundnessY);
|
||||
final float progressToPinnedRoundness = Math.min(1f,
|
||||
distToOriginalRoundness / viewMaxHeight);
|
||||
|
||||
return MathUtils.lerp(originalCornerRadius, 1f, progressToPinnedRoundness);
|
||||
}
|
||||
|
||||
protected int getMaxAllowedChildHeight(View child) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import junit.framework.Assert.assertFalse
|
||||
import junit.framework.Assert.assertTrue
|
||||
import junit.framework.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.mock
|
||||
@@ -164,4 +165,178 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
|
||||
stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart)
|
||||
assertFalse(expandableViewState.hidden)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun maybeUpdateHeadsUpIsVisible_endVisible_true() {
|
||||
val expandableViewState = ExpandableViewState()
|
||||
expandableViewState.headsUpIsVisible = false
|
||||
|
||||
stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
|
||||
/* isShadeExpanded= */ true,
|
||||
/* mustStayOnScreen= */ true,
|
||||
/* isViewEndVisible= */ true,
|
||||
/* viewEnd= */ 0f,
|
||||
/* maxHunY= */ 10f)
|
||||
|
||||
assertTrue(expandableViewState.headsUpIsVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun maybeUpdateHeadsUpIsVisible_endHidden_false() {
|
||||
val expandableViewState = ExpandableViewState()
|
||||
expandableViewState.headsUpIsVisible = true
|
||||
|
||||
stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
|
||||
/* isShadeExpanded= */ true,
|
||||
/* mustStayOnScreen= */ true,
|
||||
/* isViewEndVisible= */ true,
|
||||
/* viewEnd= */ 10f,
|
||||
/* maxHunY= */ 0f)
|
||||
|
||||
assertFalse(expandableViewState.headsUpIsVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun maybeUpdateHeadsUpIsVisible_shadeClosed_noUpdate() {
|
||||
val expandableViewState = ExpandableViewState()
|
||||
expandableViewState.headsUpIsVisible = true
|
||||
|
||||
stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
|
||||
/* isShadeExpanded= */ false,
|
||||
/* mustStayOnScreen= */ true,
|
||||
/* isViewEndVisible= */ true,
|
||||
/* viewEnd= */ 10f,
|
||||
/* maxHunY= */ 1f)
|
||||
|
||||
assertTrue(expandableViewState.headsUpIsVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun maybeUpdateHeadsUpIsVisible_notHUN_noUpdate() {
|
||||
val expandableViewState = ExpandableViewState()
|
||||
expandableViewState.headsUpIsVisible = true
|
||||
|
||||
stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
|
||||
/* isShadeExpanded= */ true,
|
||||
/* mustStayOnScreen= */ false,
|
||||
/* isViewEndVisible= */ true,
|
||||
/* viewEnd= */ 10f,
|
||||
/* maxHunY= */ 1f)
|
||||
|
||||
assertTrue(expandableViewState.headsUpIsVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun maybeUpdateHeadsUpIsVisible_topHidden_noUpdate() {
|
||||
val expandableViewState = ExpandableViewState()
|
||||
expandableViewState.headsUpIsVisible = true
|
||||
|
||||
stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
|
||||
/* isShadeExpanded= */ true,
|
||||
/* mustStayOnScreen= */ true,
|
||||
/* isViewEndVisible= */ false,
|
||||
/* viewEnd= */ 10f,
|
||||
/* maxHunY= */ 1f)
|
||||
|
||||
assertTrue(expandableViewState.headsUpIsVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clampHunToTop_viewYGreaterThanQqs_viewYUnchanged() {
|
||||
val expandableViewState = ExpandableViewState()
|
||||
expandableViewState.yTranslation = 50f
|
||||
|
||||
stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
|
||||
/* stackTranslation= */ 0f,
|
||||
/* collapsedHeight= */ 1f, expandableViewState)
|
||||
|
||||
// qqs (10 + 0) < viewY (50)
|
||||
assertEquals(50f, expandableViewState.yTranslation)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clampHunToTop_viewYLessThanQqs_viewYChanged() {
|
||||
val expandableViewState = ExpandableViewState()
|
||||
expandableViewState.yTranslation = -10f
|
||||
|
||||
stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
|
||||
/* stackTranslation= */ 0f,
|
||||
/* collapsedHeight= */ 1f, expandableViewState)
|
||||
|
||||
// qqs (10 + 0) > viewY (-10)
|
||||
assertEquals(10f, expandableViewState.yTranslation)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun clampHunToTop_viewYFarAboveVisibleStack_heightCollapsed() {
|
||||
val expandableViewState = ExpandableViewState()
|
||||
expandableViewState.height = 20
|
||||
expandableViewState.yTranslation = -100f
|
||||
|
||||
stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
|
||||
/* stackTranslation= */ 0f,
|
||||
/* collapsedHeight= */ 10f, expandableViewState)
|
||||
|
||||
// newTranslation = max(10, -100) = 10
|
||||
// distToRealY = 10 - (-100f) = 110
|
||||
// height = max(20 - 110, 10f)
|
||||
assertEquals(10, expandableViewState.height)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clampHunToTop_viewYNearVisibleStack_heightTallerThanCollapsed() {
|
||||
val expandableViewState = ExpandableViewState()
|
||||
expandableViewState.height = 20
|
||||
expandableViewState.yTranslation = 5f
|
||||
|
||||
stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
|
||||
/* stackTranslation= */ 0f,
|
||||
/* collapsedHeight= */ 10f, expandableViewState)
|
||||
|
||||
// newTranslation = max(10, 5) = 10
|
||||
// distToRealY = 10 - 5 = 5
|
||||
// height = max(20 - 5, 10) = 15
|
||||
assertEquals(15, expandableViewState.height)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeCornerRoundnessForPinnedHun_stackBelowScreen_round() {
|
||||
val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
|
||||
/* hostViewHeight= */ 100f,
|
||||
/* stackY= */ 110f,
|
||||
/* viewMaxHeight= */ 20f,
|
||||
/* originalCornerRoundness= */ 0f)
|
||||
assertEquals(1f, currentRoundness)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeCornerRoundnessForPinnedHun_stackAboveScreenBelowPinPoint_halfRound() {
|
||||
val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
|
||||
/* hostViewHeight= */ 100f,
|
||||
/* stackY= */ 90f,
|
||||
/* viewMaxHeight= */ 20f,
|
||||
/* originalCornerRoundness= */ 0f)
|
||||
assertEquals(0.5f, currentRoundness)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeCornerRoundnessForPinnedHun_stackAbovePinPoint_notRound() {
|
||||
val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
|
||||
/* hostViewHeight= */ 100f,
|
||||
/* stackY= */ 0f,
|
||||
/* viewMaxHeight= */ 20f,
|
||||
/* originalCornerRoundness= */ 0f)
|
||||
assertEquals(0f, currentRoundness)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeCornerRoundnessForPinnedHun_originallyRoundAndStackAbovePinPoint_round() {
|
||||
val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
|
||||
/* hostViewHeight= */ 100f,
|
||||
/* stackY= */ 0f,
|
||||
/* viewMaxHeight= */ 20f,
|
||||
/* originalCornerRoundness= */ 1f)
|
||||
assertEquals(1f, currentRoundness)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user