Merge "Fix HUN pinning" into tm-qpr-dev am: bc1144e631
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19120487 Change-Id: I551fdc71430f6a5a6e0a2cda9edffc41e47799c2 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.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import com.android.internal.policy.SystemBarUtils;
|
import com.android.internal.policy.SystemBarUtils;
|
||||||
import com.android.keyguard.BouncerPanelExpansionCalculator;
|
import com.android.keyguard.BouncerPanelExpansionCalculator;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
@@ -65,6 +64,9 @@ public class StackScrollAlgorithm {
|
|||||||
private int mPinnedZTranslationExtra;
|
private int mPinnedZTranslationExtra;
|
||||||
private float mNotificationScrimPadding;
|
private float mNotificationScrimPadding;
|
||||||
private int mMarginBottom;
|
private int mMarginBottom;
|
||||||
|
private float mQuickQsOffsetHeight;
|
||||||
|
private float mSmallCornerRadius;
|
||||||
|
private float mLargeCornerRadius;
|
||||||
|
|
||||||
public StackScrollAlgorithm(
|
public StackScrollAlgorithm(
|
||||||
Context context,
|
Context context,
|
||||||
@@ -74,10 +76,10 @@ public class StackScrollAlgorithm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void initView(Context context) {
|
public void initView(Context context) {
|
||||||
initConstants(context);
|
updateResources(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initConstants(Context context) {
|
private void updateResources(Context context) {
|
||||||
Resources res = context.getResources();
|
Resources res = context.getResources();
|
||||||
mPaddingBetweenElements = res.getDimensionPixelSize(
|
mPaddingBetweenElements = res.getDimensionPixelSize(
|
||||||
R.dimen.notification_divider_height);
|
R.dimen.notification_divider_height);
|
||||||
@@ -93,6 +95,9 @@ public class StackScrollAlgorithm {
|
|||||||
R.dimen.notification_section_divider_height_lockscreen);
|
R.dimen.notification_section_divider_height_lockscreen);
|
||||||
mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
|
mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
|
||||||
mMarginBottom = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
|
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;
|
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
|
// TODO(b/172289889) polish shade open from HUN
|
||||||
/**
|
/**
|
||||||
* Populates the {@link ExpandableViewState} for a single child.
|
* Populates the {@link ExpandableViewState} for a single child.
|
||||||
@@ -474,14 +488,6 @@ public class StackScrollAlgorithm {
|
|||||||
: ShadeInterpolation.getContentAlpha(expansion);
|
: 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(
|
final float expansionFraction = getExpansionFractionWithoutShelf(
|
||||||
algorithmState, ambientState);
|
algorithmState, ambientState);
|
||||||
|
|
||||||
@@ -497,8 +503,15 @@ public class StackScrollAlgorithm {
|
|||||||
algorithmState.mCurrentExpandedYPosition += gap;
|
algorithmState.mCurrentExpandedYPosition += gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must set viewState.yTranslation _before_ use.
|
||||||
|
// Incoming views have yTranslation=0 by default.
|
||||||
viewState.yTranslation = algorithmState.mCurrentYPosition;
|
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) {
|
if (view instanceof FooterView) {
|
||||||
final boolean shadeClosed = !ambientState.isShadeExpanded();
|
final boolean shadeClosed = !ambientState.isShadeExpanded();
|
||||||
final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
|
final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
|
||||||
@@ -682,7 +695,8 @@ public class StackScrollAlgorithm {
|
|||||||
if (row.mustStayOnScreen() && !childState.headsUpIsVisible
|
if (row.mustStayOnScreen() && !childState.headsUpIsVisible
|
||||||
&& !row.showingPulsing()) {
|
&& !row.showingPulsing()) {
|
||||||
// Ensure that the heads up is always visible even when scrolled off
|
// 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()) {
|
if (isTopEntry && row.isAboveShelf()) {
|
||||||
// the first hun can't get off screen.
|
// the first hun can't get off screen.
|
||||||
clampHunToMaxTranslation(ambientState, row, childState);
|
clampHunToMaxTranslation(ambientState, row, childState);
|
||||||
@@ -719,27 +733,62 @@ public class StackScrollAlgorithm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
|
/**
|
||||||
ExpandableViewState childState) {
|
* When shade is open and we are scrolled to the bottom of notifications,
|
||||||
float newTranslation = Math.max(ambientState.getTopPadding()
|
* clamp incoming HUN in its collapsed form, right below qs offset.
|
||||||
+ ambientState.getStackTranslation(), childState.yTranslation);
|
* Transition pinned collapsed HUN to full height when scrolling back up.
|
||||||
childState.height = (int) Math.max(childState.height - (newTranslation
|
*/
|
||||||
- childState.yTranslation), row.getCollapsedHeight());
|
@VisibleForTesting
|
||||||
childState.yTranslation = newTranslation;
|
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,
|
private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
|
||||||
ExpandableViewState childState) {
|
ExpandableViewState childState) {
|
||||||
float newTranslation;
|
|
||||||
float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
|
float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
|
||||||
float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
|
final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
|
||||||
+ ambientState.getStackTranslation();
|
+ ambientState.getStackTranslation();
|
||||||
maxHeadsUpTranslation = Math.min(maxHeadsUpTranslation, maxShelfPosition);
|
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
|
childState.height = (int) Math.min(childState.height, maxHeadsUpTranslation
|
||||||
- newTranslation);
|
- newTranslation);
|
||||||
childState.yTranslation = 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) {
|
protected int getMaxAllowedChildHeight(View child) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
|
|||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import junit.framework.Assert.assertFalse
|
import junit.framework.Assert.assertFalse
|
||||||
import junit.framework.Assert.assertTrue
|
import junit.framework.Assert.assertTrue
|
||||||
|
import junit.framework.Assert.assertEquals
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
@@ -164,4 +165,178 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
|
|||||||
stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart)
|
stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart)
|
||||||
assertFalse(expandableViewState.hidden)
|
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