Merge "Add drop shadow for HUNs when shade is opened" into tm-qpr-dev
This commit is contained in:
@@ -62,7 +62,8 @@ public class StackScrollAlgorithm {
|
|||||||
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
|
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
|
||||||
private boolean mIsExpanded;
|
private boolean mIsExpanded;
|
||||||
private boolean mClipNotificationScrollToTop;
|
private boolean mClipNotificationScrollToTop;
|
||||||
@VisibleForTesting float mHeadsUpInset;
|
@VisibleForTesting
|
||||||
|
float mHeadsUpInset;
|
||||||
private int mPinnedZTranslationExtra;
|
private int mPinnedZTranslationExtra;
|
||||||
private float mNotificationScrimPadding;
|
private float mNotificationScrimPadding;
|
||||||
private int mMarginBottom;
|
private int mMarginBottom;
|
||||||
@@ -504,6 +505,7 @@ public class StackScrollAlgorithm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
*
|
*
|
||||||
@@ -862,30 +864,53 @@ public class StackScrollAlgorithm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate and update the Z positions for a given child. We currently only give shadows to
|
||||||
|
* HUNs to distinguish a HUN from its surroundings.
|
||||||
|
*
|
||||||
|
* @param isTopHun Whether the child is a top HUN. A top HUN means a HUN that shows on the
|
||||||
|
* vertically top of screen. Top HUNs should have drop shadows
|
||||||
|
* @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated
|
||||||
|
* @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height
|
||||||
|
* that overlaps with QQS Panel. The integer part represents the count of
|
||||||
|
* previous HUNs whose Z positions are greater than 0.
|
||||||
|
*/
|
||||||
protected float updateChildZValue(int i, float childrenOnTop,
|
protected float updateChildZValue(int i, float childrenOnTop,
|
||||||
StackScrollAlgorithmState algorithmState,
|
StackScrollAlgorithmState algorithmState,
|
||||||
AmbientState ambientState,
|
AmbientState ambientState,
|
||||||
boolean shouldElevateHun) {
|
boolean isTopHun) {
|
||||||
ExpandableView child = algorithmState.visibleChildren.get(i);
|
ExpandableView child = algorithmState.visibleChildren.get(i);
|
||||||
ExpandableViewState childViewState = child.getViewState();
|
ExpandableViewState childViewState = child.getViewState();
|
||||||
int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
|
|
||||||
float baseZ = ambientState.getBaseZHeight();
|
float baseZ = ambientState.getBaseZHeight();
|
||||||
|
|
||||||
|
// Handles HUN shadow when Shade is opened
|
||||||
|
|
||||||
if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
|
if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
|
||||||
&& !ambientState.isDozingAndNotPulsing(child)
|
&& !ambientState.isDozingAndNotPulsing(child)
|
||||||
&& childViewState.getYTranslation() < ambientState.getTopPadding()
|
&& childViewState.getYTranslation() < ambientState.getTopPadding()
|
||||||
+ ambientState.getStackTranslation()) {
|
+ ambientState.getStackTranslation()) {
|
||||||
|
// Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
|
||||||
|
// Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
|
||||||
|
// When scrolling down shade to make HUN back to in-position in Notification Panel,
|
||||||
|
// The over-lapping fraction goes to 0, and shadows hides gradually.
|
||||||
if (childrenOnTop != 0.0f) {
|
if (childrenOnTop != 0.0f) {
|
||||||
|
// To elevate the later HUN over previous HUN
|
||||||
childrenOnTop++;
|
childrenOnTop++;
|
||||||
} else {
|
} else {
|
||||||
float overlap = ambientState.getTopPadding()
|
float overlap = ambientState.getTopPadding()
|
||||||
+ ambientState.getStackTranslation() - childViewState.getYTranslation();
|
+ ambientState.getStackTranslation() - childViewState.getYTranslation();
|
||||||
childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
|
// To prevent over-shadow during HUN entry
|
||||||
|
childrenOnTop += Math.min(
|
||||||
|
1.0f,
|
||||||
|
overlap / childViewState.height
|
||||||
|
);
|
||||||
|
MathUtils.saturate(childrenOnTop);
|
||||||
}
|
}
|
||||||
childViewState.setZTranslation(baseZ
|
childViewState.setZTranslation(baseZ
|
||||||
+ childrenOnTop * zDistanceBetweenElements);
|
+ childrenOnTop * mPinnedZTranslationExtra);
|
||||||
} else if (shouldElevateHun) {
|
} else if (isTopHun) {
|
||||||
// In case this is a new view that has never been measured before, we don't want to
|
// In case this is a new view that has never been measured before, we don't want to
|
||||||
// elevate if we are currently expanded more then the notification
|
// elevate if we are currently expanded more than the notification
|
||||||
int shelfHeight = ambientState.getShelf() == null ? 0 :
|
int shelfHeight = ambientState.getShelf() == null ? 0 :
|
||||||
ambientState.getShelf().getIntrinsicHeight();
|
ambientState.getShelf().getIntrinsicHeight();
|
||||||
float shelfStart = ambientState.getInnerHeight()
|
float shelfStart = ambientState.getInnerHeight()
|
||||||
@@ -894,23 +919,28 @@ public class StackScrollAlgorithm {
|
|||||||
float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight()
|
float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight()
|
||||||
+ mPaddingBetweenElements;
|
+ mPaddingBetweenElements;
|
||||||
if (shelfStart > notificationEnd) {
|
if (shelfStart > notificationEnd) {
|
||||||
|
// When the notification doesn't overlap with Notification Shelf, there's no shadow
|
||||||
childViewState.setZTranslation(baseZ);
|
childViewState.setZTranslation(baseZ);
|
||||||
} else {
|
} else {
|
||||||
|
// Give shadow to the notification if it overlaps with Notification Shelf
|
||||||
float factor = (notificationEnd - shelfStart) / shelfHeight;
|
float factor = (notificationEnd - shelfStart) / shelfHeight;
|
||||||
if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
|
if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
|
||||||
factor = 1.0f;
|
factor = 1.0f;
|
||||||
}
|
}
|
||||||
factor = Math.min(factor, 1.0f);
|
factor = Math.min(factor, 1.0f);
|
||||||
childViewState.setZTranslation(baseZ + factor * zDistanceBetweenElements);
|
childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
childViewState.setZTranslation(baseZ);
|
childViewState.setZTranslation(baseZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to scrim the notification more from its surrounding content when we are pinned,
|
// Handles HUN shadow when shade is closed.
|
||||||
// and we therefore elevate it higher.
|
// While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
|
||||||
// We can use the headerVisibleAmount for this, since the value nicely goes from 0 to 1 when
|
// During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes
|
||||||
// expanding after which we have a normal elevation again.
|
// gradually from 0 to 1, shadow hides gradually.
|
||||||
|
// Header visibility is a deprecated concept, we are using headerVisibleAmount only because
|
||||||
|
// this value nicely goes from 0 to 1 during the HUN-to-Shade process.
|
||||||
|
|
||||||
childViewState.setZTranslation(childViewState.getZTranslation()
|
childViewState.setZTranslation(childViewState.getZTranslation()
|
||||||
+ (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra);
|
+ (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra);
|
||||||
return childrenOnTop;
|
return childrenOnTop;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import com.android.systemui.statusbar.StatusBarState
|
|||||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
|
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
|
||||||
import com.android.systemui.statusbar.notification.row.ExpandableView
|
import com.android.systemui.statusbar.notification.row.ExpandableView
|
||||||
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
|
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import junit.framework.Assert.assertEquals
|
import junit.framework.Assert.assertEquals
|
||||||
import junit.framework.Assert.assertFalse
|
import junit.framework.Assert.assertFalse
|
||||||
@@ -31,10 +32,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
|
|||||||
|
|
||||||
private val hostView = FrameLayout(context)
|
private val hostView = FrameLayout(context)
|
||||||
private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
|
private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
|
||||||
private val notificationRow = mock(ExpandableNotificationRow::class.java)
|
private val notificationRow = mock<ExpandableNotificationRow>()
|
||||||
private val dumpManager = mock(DumpManager::class.java)
|
private val dumpManager = mock<DumpManager>()
|
||||||
private val mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager::class.java)
|
private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
|
||||||
private val notificationShelf = mock(NotificationShelf::class.java)
|
private val notificationShelf = mock<NotificationShelf>()
|
||||||
private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
|
private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
|
||||||
layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
|
layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
|
||||||
}
|
}
|
||||||
@@ -46,7 +47,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
|
|||||||
mStatusBarKeyguardViewManager
|
mStatusBarKeyguardViewManager
|
||||||
)
|
)
|
||||||
|
|
||||||
private val testableResources = mContext.orCreateTestableResources
|
private val testableResources = mContext.getOrCreateTestableResources()
|
||||||
|
|
||||||
private fun px(@DimenRes id: Int): Float =
|
private fun px(@DimenRes id: Int): Float =
|
||||||
testableResources.resources.getDimensionPixelSize(id).toFloat()
|
testableResources.resources.getDimensionPixelSize(id).toFloat()
|
||||||
@@ -507,6 +508,192 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
|
|||||||
assertEquals(1f, currentRoundness)
|
assertEquals(1f, currentRoundness)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shadeOpened_hunFullyOverlapsQqsPanel_hunShouldHaveFullShadow() {
|
||||||
|
// Given: shade is opened, yTranslation of HUN is 0,
|
||||||
|
// the height of HUN equals to the height of QQS Panel,
|
||||||
|
// and HUN fully overlaps with QQS Panel
|
||||||
|
ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
|
||||||
|
px(R.dimen.qqs_layout_padding_bottom)
|
||||||
|
val childHunView = createHunViewMock(
|
||||||
|
isShadeOpen = true,
|
||||||
|
fullyVisible = false,
|
||||||
|
headerVisibleAmount = 1f
|
||||||
|
)
|
||||||
|
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
|
||||||
|
algorithmState.visibleChildren.add(childHunView)
|
||||||
|
|
||||||
|
// When: updateChildZValue() is called for the top HUN
|
||||||
|
stackScrollAlgorithm.updateChildZValue(
|
||||||
|
/* i= */ 0,
|
||||||
|
/* childrenOnTop= */ 0.0f,
|
||||||
|
/* StackScrollAlgorithmState= */ algorithmState,
|
||||||
|
/* ambientState= */ ambientState,
|
||||||
|
/* shouldElevateHun= */ true
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then: full shadow would be applied
|
||||||
|
assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shadeOpened_hunPartiallyOverlapsQQS_hunShouldHavePartialShadow() {
|
||||||
|
// Given: shade is opened, yTranslation of HUN is greater than 0,
|
||||||
|
// the height of HUN is equal to the height of QQS Panel,
|
||||||
|
// and HUN partially overlaps with QQS Panel
|
||||||
|
ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
|
||||||
|
px(R.dimen.qqs_layout_padding_bottom)
|
||||||
|
val childHunView = createHunViewMock(
|
||||||
|
isShadeOpen = true,
|
||||||
|
fullyVisible = false,
|
||||||
|
headerVisibleAmount = 1f
|
||||||
|
)
|
||||||
|
// Use half of the HUN's height as overlap
|
||||||
|
childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat()
|
||||||
|
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
|
||||||
|
algorithmState.visibleChildren.add(childHunView)
|
||||||
|
|
||||||
|
// When: updateChildZValue() is called for the top HUN
|
||||||
|
stackScrollAlgorithm.updateChildZValue(
|
||||||
|
/* i= */ 0,
|
||||||
|
/* childrenOnTop= */ 0.0f,
|
||||||
|
/* StackScrollAlgorithmState= */ algorithmState,
|
||||||
|
/* ambientState= */ ambientState,
|
||||||
|
/* shouldElevateHun= */ true
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then: HUN should have shadow, but not as full size
|
||||||
|
assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
|
||||||
|
assertThat(childHunView.viewState.zTranslation)
|
||||||
|
.isLessThan(px(R.dimen.heads_up_pinned_elevation))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shadeOpened_hunDoesNotOverlapQQS_hunShouldHaveNoShadow() {
|
||||||
|
// Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height,
|
||||||
|
// the height of HUN is equal to the height of QQS Panel,
|
||||||
|
// and HUN doesn't overlap with QQS Panel
|
||||||
|
ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
|
||||||
|
px(R.dimen.qqs_layout_padding_bottom)
|
||||||
|
// Mock the height of shade
|
||||||
|
ambientState.setLayoutMinHeight(1000)
|
||||||
|
val childHunView = createHunViewMock(
|
||||||
|
isShadeOpen = true,
|
||||||
|
fullyVisible = true,
|
||||||
|
headerVisibleAmount = 1f
|
||||||
|
)
|
||||||
|
// HUN doesn't overlap with QQS Panel
|
||||||
|
childHunView.viewState.yTranslation = ambientState.topPadding +
|
||||||
|
ambientState.stackTranslation
|
||||||
|
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
|
||||||
|
algorithmState.visibleChildren.add(childHunView)
|
||||||
|
|
||||||
|
// When: updateChildZValue() is called for the top HUN
|
||||||
|
stackScrollAlgorithm.updateChildZValue(
|
||||||
|
/* i= */ 0,
|
||||||
|
/* childrenOnTop= */ 0.0f,
|
||||||
|
/* StackScrollAlgorithmState= */ algorithmState,
|
||||||
|
/* ambientState= */ ambientState,
|
||||||
|
/* shouldElevateHun= */ true
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then: HUN should not have shadow
|
||||||
|
assertEquals(0f, childHunView.viewState.zTranslation)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shadeClosed_hunShouldHaveFullShadow() {
|
||||||
|
// Given: shade is closed, ambientState.stackTranslation == -ambientState.topPadding,
|
||||||
|
// the height of HUN is equal to the height of QQS Panel,
|
||||||
|
ambientState.stackTranslation = -ambientState.topPadding
|
||||||
|
// Mock the height of shade
|
||||||
|
ambientState.setLayoutMinHeight(1000)
|
||||||
|
val childHunView = createHunViewMock(
|
||||||
|
isShadeOpen = false,
|
||||||
|
fullyVisible = false,
|
||||||
|
headerVisibleAmount = 0f
|
||||||
|
)
|
||||||
|
childHunView.viewState.yTranslation = 0f
|
||||||
|
// Shade is closed, thus childHunView's headerVisibleAmount is 0
|
||||||
|
childHunView.headerVisibleAmount = 0f
|
||||||
|
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
|
||||||
|
algorithmState.visibleChildren.add(childHunView)
|
||||||
|
|
||||||
|
// When: updateChildZValue() is called for the top HUN
|
||||||
|
stackScrollAlgorithm.updateChildZValue(
|
||||||
|
/* i= */ 0,
|
||||||
|
/* childrenOnTop= */ 0.0f,
|
||||||
|
/* StackScrollAlgorithmState= */ algorithmState,
|
||||||
|
/* ambientState= */ ambientState,
|
||||||
|
/* shouldElevateHun= */ true
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then: HUN should have full shadow
|
||||||
|
assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun draggingHunToOpenShade_hunShouldHavePartialShadow() {
|
||||||
|
// Given: shade is closed when HUN pops up,
|
||||||
|
// now drags down the HUN to open shade
|
||||||
|
ambientState.stackTranslation = -ambientState.topPadding
|
||||||
|
// Mock the height of shade
|
||||||
|
ambientState.setLayoutMinHeight(1000)
|
||||||
|
val childHunView = createHunViewMock(
|
||||||
|
isShadeOpen = false,
|
||||||
|
fullyVisible = false,
|
||||||
|
headerVisibleAmount = 0.5f
|
||||||
|
)
|
||||||
|
childHunView.viewState.yTranslation = 0f
|
||||||
|
// Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1
|
||||||
|
// use 0.5 as headerVisibleAmount here
|
||||||
|
childHunView.headerVisibleAmount = 0.5f
|
||||||
|
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
|
||||||
|
algorithmState.visibleChildren.add(childHunView)
|
||||||
|
|
||||||
|
// When: updateChildZValue() is called for the top HUN
|
||||||
|
stackScrollAlgorithm.updateChildZValue(
|
||||||
|
/* i= */ 0,
|
||||||
|
/* childrenOnTop= */ 0.0f,
|
||||||
|
/* StackScrollAlgorithmState= */ algorithmState,
|
||||||
|
/* ambientState= */ ambientState,
|
||||||
|
/* shouldElevateHun= */ true
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then: HUN should have shadow, but not as full size
|
||||||
|
assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
|
||||||
|
assertThat(childHunView.viewState.zTranslation)
|
||||||
|
.isLessThan(px(R.dimen.heads_up_pinned_elevation))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createHunViewMock(
|
||||||
|
isShadeOpen: Boolean,
|
||||||
|
fullyVisible: Boolean,
|
||||||
|
headerVisibleAmount: Float
|
||||||
|
) =
|
||||||
|
mock<ExpandableNotificationRow>().apply {
|
||||||
|
val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
|
||||||
|
whenever(this.viewState).thenReturn(childViewStateMock)
|
||||||
|
|
||||||
|
whenever(this.mustStayOnScreen()).thenReturn(true)
|
||||||
|
whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
|
||||||
|
ExpandableViewState().apply {
|
||||||
|
// Mock the HUN's height with ambientState.topPadding +
|
||||||
|
// ambientState.stackTranslation
|
||||||
|
height = (ambientState.topPadding + ambientState.stackTranslation).toInt()
|
||||||
|
if (isShadeOpen && fullyVisible) {
|
||||||
|
yTranslation =
|
||||||
|
ambientState.topPadding + ambientState.stackTranslation
|
||||||
|
} else {
|
||||||
|
yTranslation = 0f
|
||||||
|
}
|
||||||
|
headsUpIsVisible = fullyVisible
|
||||||
|
}
|
||||||
|
|
||||||
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
|
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
|
||||||
expansionFraction: Float,
|
expansionFraction: Float,
|
||||||
expectedAlpha: Float
|
expectedAlpha: Float
|
||||||
|
|||||||
Reference in New Issue
Block a user