Merge "Fix occasional bad placement of Conversation header" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
c744ad246e
@@ -68,7 +68,7 @@ public class LogModule {
|
||||
public static LogBuffer provideNotificationSectionLogBuffer(
|
||||
LogcatEchoTracker bufferFilter,
|
||||
DumpManager dumpManager) {
|
||||
LogBuffer buffer = new LogBuffer("NotifSectionLog", 500, 10, bufferFilter);
|
||||
LogBuffer buffer = new LogBuffer("NotifSectionLog", 1000, 10, bufferFilter);
|
||||
buffer.attach(dumpManager);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.row.StackScrollerDecorView
|
||||
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.util.children
|
||||
import com.android.systemui.util.takeUntil
|
||||
import com.android.systemui.util.foldToSparseArray
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -197,7 +198,7 @@ class NotificationSectionsManager @Inject internal constructor(
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun logShadeContents() = parent.children.forEachIndexed { i, child ->
|
||||
private fun logShadeChild(i: Int, child: View) {
|
||||
when {
|
||||
child === incomingHeaderView -> logger.logIncomingHeader(i)
|
||||
child === mediaControlsView -> logger.logMediaControls(i)
|
||||
@@ -216,6 +217,7 @@ class NotificationSectionsManager @Inject internal constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun logShadeContents() = parent.children.forEachIndexed(::logShadeChild)
|
||||
|
||||
private val isUsingMultipleSections: Boolean
|
||||
get() = sectionsFeatureManager.getNumberOfBuckets() > 1
|
||||
@@ -223,6 +225,57 @@ class NotificationSectionsManager @Inject internal constructor(
|
||||
@VisibleForTesting
|
||||
fun updateSectionBoundaries() = updateSectionBoundaries("test")
|
||||
|
||||
private interface SectionUpdateState<out T : ExpandableView> {
|
||||
val header: T
|
||||
var currentPosition: Int?
|
||||
var targetPosition: Int?
|
||||
fun adjustViewPosition()
|
||||
}
|
||||
|
||||
private fun <T : ExpandableView> expandableViewHeaderState(header: T): SectionUpdateState<T> =
|
||||
object : SectionUpdateState<T> {
|
||||
override val header = header
|
||||
override var currentPosition: Int? = null
|
||||
override var targetPosition: Int? = null
|
||||
|
||||
override fun adjustViewPosition() {
|
||||
val target = targetPosition
|
||||
val current = currentPosition
|
||||
if (target == null) {
|
||||
if (current != null) {
|
||||
parent.removeView(header)
|
||||
}
|
||||
} else {
|
||||
if (current == null) {
|
||||
// If the header is animating away, it will still have a parent, so
|
||||
// detach it first
|
||||
// TODO: We should really cancel the active animations here. This will
|
||||
// happen automatically when the view's intro animation starts, but
|
||||
// it's a fragile link.
|
||||
header.transientContainer?.removeTransientView(header)
|
||||
header.transientContainer = null
|
||||
parent.addView(header, target)
|
||||
} else {
|
||||
parent.changeViewPosition(header, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : StackScrollerDecorView> decorViewHeaderState(
|
||||
header: T
|
||||
): SectionUpdateState<T> {
|
||||
val inner = expandableViewHeaderState(header)
|
||||
return object : SectionUpdateState<T> by inner {
|
||||
override fun adjustViewPosition() {
|
||||
inner.adjustViewPosition()
|
||||
if (targetPosition != null && currentPosition == null) {
|
||||
header.isContentVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called whenever notifs are added, removed, or updated. Updates section boundary
|
||||
* bookkeeping and adds/moves/removes section headers if appropriate.
|
||||
@@ -238,233 +291,136 @@ class NotificationSectionsManager @Inject internal constructor(
|
||||
// Then, once we find the start of a new section, we track that position as the "target" for
|
||||
// the section header, adjusted for the case where existing headers are in front of that
|
||||
// target, but won't be once they are moved / removed after the pass has completed.
|
||||
|
||||
val showHeaders = statusBarStateController.state != StatusBarState.KEYGUARD
|
||||
val usingPeopleFiltering = sectionsFeatureManager.isFilteringEnabled()
|
||||
val usingMediaControls = sectionsFeatureManager.isMediaControlsEnabled()
|
||||
|
||||
val mediaState = mediaControlsView?.let(::expandableViewHeaderState)
|
||||
val incomingState = incomingHeaderView?.let(::decorViewHeaderState)
|
||||
val peopleState = peopleHeaderView?.let(::decorViewHeaderState)
|
||||
val alertingState = alertingHeaderView?.let(::decorViewHeaderState)
|
||||
val gentleState = silentHeaderView?.let(::decorViewHeaderState)
|
||||
|
||||
fun getSectionState(view: View): SectionUpdateState<ExpandableView>? = when {
|
||||
view === mediaControlsView -> mediaState
|
||||
view === incomingHeaderView -> incomingState
|
||||
view === peopleHeaderView -> peopleState
|
||||
view === alertingHeaderView -> alertingState
|
||||
view === silentHeaderView -> gentleState
|
||||
else -> null
|
||||
}
|
||||
|
||||
val headersOrdered = sequenceOf(
|
||||
mediaState, incomingState, peopleState, alertingState, gentleState
|
||||
).filterNotNull()
|
||||
|
||||
var peopleNotifsPresent = false
|
||||
var currentMediaControlsIdx = -1
|
||||
val mediaControlsTarget = if (usingMediaControls) 0 else -1
|
||||
var currentIncomingHeaderIdx = -1
|
||||
var incomingHeaderTarget = -1
|
||||
var currentPeopleHeaderIdx = -1
|
||||
var peopleHeaderTarget = -1
|
||||
var currentAlertingHeaderIdx = -1
|
||||
var alertingHeaderTarget = -1
|
||||
var currentGentleHeaderIdx = -1
|
||||
var gentleHeaderTarget = -1
|
||||
|
||||
var lastNotifIndex = 0
|
||||
var lastIncomingIndex = -1
|
||||
var prev: ExpandableNotificationRow? = null
|
||||
var nextBucket: Int? = null
|
||||
var inIncomingSection = false
|
||||
|
||||
for ((i, child) in parent.children.withIndex()) {
|
||||
when {
|
||||
// Track the existing positions of the headers
|
||||
child === incomingHeaderView -> {
|
||||
logger.logIncomingHeader(i)
|
||||
currentIncomingHeaderIdx = i
|
||||
}
|
||||
child === mediaControlsView -> {
|
||||
logger.logMediaControls(i)
|
||||
currentMediaControlsIdx = i
|
||||
}
|
||||
child === peopleHeaderView -> {
|
||||
logger.logConversationsHeader(i)
|
||||
currentPeopleHeaderIdx = i
|
||||
}
|
||||
child === alertingHeaderView -> {
|
||||
logger.logAlertingHeader(i)
|
||||
currentAlertingHeaderIdx = i
|
||||
}
|
||||
child === silentHeaderView -> {
|
||||
logger.logSilentHeader(i)
|
||||
currentGentleHeaderIdx = i
|
||||
}
|
||||
child !is ExpandableNotificationRow -> logger.logOther(i, child.javaClass)
|
||||
else -> {
|
||||
lastNotifIndex = i
|
||||
// Is there a section discontinuity? This usually occurs due to HUNs
|
||||
if (prev?.entry?.bucket?.let { it > child.entry.bucket } == true) {
|
||||
// Remove existing headers, and move the Incoming header if necessary
|
||||
incomingHeaderTarget = when {
|
||||
!showHeaders -> -1
|
||||
incomingHeaderTarget != -1 -> incomingHeaderTarget
|
||||
peopleHeaderTarget != -1 -> peopleHeaderTarget
|
||||
alertingHeaderTarget != -1 -> alertingHeaderTarget
|
||||
gentleHeaderTarget != -1 -> gentleHeaderTarget
|
||||
else -> 0
|
||||
}
|
||||
peopleHeaderTarget = -1
|
||||
alertingHeaderTarget = -1
|
||||
gentleHeaderTarget = -1
|
||||
// Walk backwards changing all previous notifications to the Incoming
|
||||
// section
|
||||
for (j in i - 1 downTo lastIncomingIndex + 1) {
|
||||
val prevChild = parent.getChildAt(j)
|
||||
if (prevChild is ExpandableNotificationRow) {
|
||||
prevChild.entry.bucket = BUCKET_HEADS_UP
|
||||
}
|
||||
}
|
||||
// Track the new bottom of the Incoming section
|
||||
lastIncomingIndex = i - 1
|
||||
}
|
||||
val isHeadsUp = child.isHeadsUp
|
||||
when (child.entry.bucket) {
|
||||
BUCKET_FOREGROUND_SERVICE -> logger.logForegroundService(i, isHeadsUp)
|
||||
BUCKET_PEOPLE -> {
|
||||
logger.logConversation(i, isHeadsUp)
|
||||
peopleNotifsPresent = true
|
||||
if (showHeaders && peopleHeaderTarget == -1) {
|
||||
peopleHeaderTarget = i
|
||||
// Offset the target if there are other headers before this that
|
||||
// will be moved.
|
||||
if (currentIncomingHeaderIdx != -1 && incomingHeaderTarget == -1) {
|
||||
peopleHeaderTarget--
|
||||
}
|
||||
if (currentPeopleHeaderIdx != -1) {
|
||||
peopleHeaderTarget--
|
||||
}
|
||||
if (currentAlertingHeaderIdx != -1) {
|
||||
peopleHeaderTarget--
|
||||
}
|
||||
if (currentGentleHeaderIdx != -1) {
|
||||
peopleHeaderTarget--
|
||||
}
|
||||
}
|
||||
}
|
||||
BUCKET_ALERTING -> {
|
||||
logger.logAlerting(i, isHeadsUp)
|
||||
if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
|
||||
alertingHeaderTarget = i
|
||||
// Offset the target if there are other headers before this that
|
||||
// will be moved.
|
||||
if (currentIncomingHeaderIdx != -1 && incomingHeaderTarget == -1) {
|
||||
alertingHeaderTarget--
|
||||
}
|
||||
if (currentPeopleHeaderIdx != -1 && peopleHeaderTarget == -1) {
|
||||
// People header will be removed
|
||||
alertingHeaderTarget--
|
||||
}
|
||||
if (currentAlertingHeaderIdx != -1) {
|
||||
alertingHeaderTarget--
|
||||
}
|
||||
if (currentGentleHeaderIdx != -1) {
|
||||
alertingHeaderTarget--
|
||||
}
|
||||
}
|
||||
}
|
||||
BUCKET_SILENT -> {
|
||||
logger.logSilent(i, isHeadsUp)
|
||||
if (showHeaders && gentleHeaderTarget == -1) {
|
||||
gentleHeaderTarget = i
|
||||
// Offset the target if there are other headers before this that
|
||||
// will be moved.
|
||||
if (currentIncomingHeaderIdx != -1 && incomingHeaderTarget == -1) {
|
||||
gentleHeaderTarget--
|
||||
}
|
||||
if (currentPeopleHeaderIdx != -1 && peopleHeaderTarget == -1) {
|
||||
// People header will be removed
|
||||
gentleHeaderTarget--
|
||||
}
|
||||
if (currentAlertingHeaderIdx != -1 && alertingHeaderTarget == -1) {
|
||||
// Alerting header will be removed
|
||||
gentleHeaderTarget--
|
||||
}
|
||||
if (currentGentleHeaderIdx != -1) {
|
||||
gentleHeaderTarget--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prev = child
|
||||
// Iterating backwards allows for easier construction of the Incoming section, as opposed
|
||||
// to backtracking when a discontinuity in the sections is discovered.
|
||||
// Iterating to -1 in order to support the case where a header is at the very top of the
|
||||
// shade.
|
||||
for (i in parent.childCount - 1 downTo -1) {
|
||||
val child: View? = parent.getChildAt(i)
|
||||
child?.let {
|
||||
logShadeChild(i, child)
|
||||
// If this child is a header, update the tracked positions
|
||||
getSectionState(child)?.let { state ->
|
||||
state.currentPosition = i
|
||||
// If headers that should appear above this one in the shade already have a
|
||||
// target index, then we need to decrement them in order to account for this one
|
||||
// being either removed, or moved below them.
|
||||
headersOrdered.takeUntil { it === state }
|
||||
.forEach { it.targetPosition = it.targetPosition?.minus(1) }
|
||||
}
|
||||
}
|
||||
|
||||
val row = child as? ExpandableNotificationRow
|
||||
|
||||
// Is there a section discontinuity? This usually occurs due to HUNs
|
||||
inIncomingSection = inIncomingSection || nextBucket?.let { next ->
|
||||
row?.entry?.bucket?.let { curr -> next < curr }
|
||||
} == true
|
||||
|
||||
if (inIncomingSection) {
|
||||
// Update the bucket to reflect that it's being placed in the Incoming section
|
||||
row?.entry?.bucket = BUCKET_HEADS_UP
|
||||
}
|
||||
|
||||
// Insert a header in front of the next row, if there's a boundary between it and this
|
||||
// row, or if it is the topmost row.
|
||||
val isSectionBoundary = nextBucket != null &&
|
||||
(child == null || row != null && nextBucket != row.entry.bucket)
|
||||
if (isSectionBoundary && showHeaders) {
|
||||
when (nextBucket) {
|
||||
BUCKET_HEADS_UP -> incomingState?.targetPosition = i + 1
|
||||
BUCKET_PEOPLE -> peopleState?.targetPosition = i + 1
|
||||
BUCKET_ALERTING -> alertingState?.targetPosition = i + 1
|
||||
BUCKET_SILENT -> gentleState?.targetPosition = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
row ?: continue
|
||||
|
||||
// Check if there are any people notifications
|
||||
peopleNotifsPresent = peopleNotifsPresent || row.entry.bucket == BUCKET_PEOPLE
|
||||
|
||||
if (nextBucket == null) {
|
||||
lastNotifIndex = i
|
||||
}
|
||||
nextBucket = row.entry.bucket
|
||||
}
|
||||
|
||||
if (showHeaders && usingPeopleFiltering && peopleHubVisible && peopleHeaderTarget == -1) {
|
||||
// Insert the people header even if there are no people visible, in order to show
|
||||
// the hub. Put it directly above the next header.
|
||||
peopleHeaderTarget = when {
|
||||
alertingHeaderTarget != -1 -> alertingHeaderTarget
|
||||
gentleHeaderTarget != -1 -> gentleHeaderTarget
|
||||
else -> lastNotifIndex // Put it at the end of the list.
|
||||
}
|
||||
if (showHeaders && usingPeopleFiltering && peopleHubVisible) {
|
||||
peopleState?.targetPosition = peopleState?.targetPosition
|
||||
// Insert the people header even if there are no people visible, in order to
|
||||
// show the hub. Put it directly above the next header.
|
||||
?: alertingState?.targetPosition
|
||||
?: gentleState?.targetPosition
|
||||
// Put it at the end of the list.
|
||||
?: lastNotifIndex
|
||||
|
||||
// Offset the target to account for the current position of the people header.
|
||||
if (currentPeopleHeaderIdx != -1 && currentPeopleHeaderIdx < peopleHeaderTarget) {
|
||||
peopleHeaderTarget--
|
||||
peopleState?.targetPosition = peopleState?.currentPosition?.let { current ->
|
||||
peopleState?.targetPosition?.let { target ->
|
||||
if (current < target) target - 1 else target
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mediaState?.targetPosition = if (usingMediaControls) 0 else null
|
||||
|
||||
logger.logStr("New header target positions:")
|
||||
logger.logIncomingHeader(incomingHeaderTarget)
|
||||
logger.logMediaControls(mediaControlsTarget)
|
||||
logger.logConversationsHeader(peopleHeaderTarget)
|
||||
logger.logAlertingHeader(alertingHeaderTarget)
|
||||
logger.logSilentHeader(gentleHeaderTarget)
|
||||
logger.logMediaControls(mediaState?.targetPosition ?: -1)
|
||||
logger.logIncomingHeader(incomingState?.targetPosition ?: -1)
|
||||
logger.logConversationsHeader(peopleState?.targetPosition ?: -1)
|
||||
logger.logAlertingHeader(alertingState?.targetPosition ?: -1)
|
||||
logger.logSilentHeader(gentleState?.targetPosition ?: -1)
|
||||
|
||||
// Add headers in reverse order to preserve indices
|
||||
silentHeaderView?.let {
|
||||
adjustHeaderVisibilityAndPosition(gentleHeaderTarget, it, currentGentleHeaderIdx)
|
||||
}
|
||||
alertingHeaderView?.let {
|
||||
adjustHeaderVisibilityAndPosition(alertingHeaderTarget, it, currentAlertingHeaderIdx)
|
||||
}
|
||||
peopleHeaderView?.let {
|
||||
adjustHeaderVisibilityAndPosition(peopleHeaderTarget, it, currentPeopleHeaderIdx)
|
||||
}
|
||||
incomingHeaderView?.let {
|
||||
adjustHeaderVisibilityAndPosition(incomingHeaderTarget, it, currentIncomingHeaderIdx)
|
||||
}
|
||||
mediaControlsView?.let {
|
||||
adjustViewPosition(mediaControlsTarget, it, currentMediaControlsIdx)
|
||||
}
|
||||
// Update headers in reverse order to preserve indices, otherwise movements earlier in the
|
||||
// list will affect the target indices of the headers later in the list.
|
||||
headersOrdered.asIterable().reversed().forEach { it.adjustViewPosition() }
|
||||
|
||||
logger.logStr("Final order:")
|
||||
logShadeContents()
|
||||
logger.logStr("Section boundary update complete")
|
||||
|
||||
// Update headers to reflect state of section contents
|
||||
silentHeaderView?.setAreThereDismissableGentleNotifs(
|
||||
parent.hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE)
|
||||
)
|
||||
peopleHeaderView?.canSwipe = showHeaders && peopleHubVisible && !peopleNotifsPresent
|
||||
if (peopleHeaderTarget != currentPeopleHeaderIdx) {
|
||||
peopleHeaderView?.resetTranslation()
|
||||
silentHeaderView?.run {
|
||||
val hasActiveClearableNotifications = this@NotificationSectionsManager.parent
|
||||
.hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE)
|
||||
setAreThereDismissableGentleNotifs(hasActiveClearableNotifications)
|
||||
}
|
||||
}
|
||||
|
||||
private fun adjustHeaderVisibilityAndPosition(
|
||||
targetPosition: Int,
|
||||
header: StackScrollerDecorView,
|
||||
currentPosition: Int
|
||||
) {
|
||||
adjustViewPosition(targetPosition, header, currentPosition)
|
||||
if (targetPosition != -1 && currentPosition == -1) {
|
||||
header.isContentVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun adjustViewPosition(
|
||||
targetPosition: Int,
|
||||
view: ExpandableView,
|
||||
currentPosition: Int
|
||||
) {
|
||||
if (targetPosition == -1) {
|
||||
if (currentPosition != -1) {
|
||||
parent.removeView(view)
|
||||
}
|
||||
} else {
|
||||
if (currentPosition == -1) {
|
||||
// If the header is animating away, it will still have a parent, so detach it first
|
||||
// TODO: We should really cancel the active animations here. This will happen
|
||||
// automatically when the view's intro animation starts, but it's a fragile link.
|
||||
view.transientContainer?.removeTransientView(view)
|
||||
view.transientContainer = null
|
||||
parent.addView(view, targetPosition)
|
||||
} else {
|
||||
parent.changeViewPosition(view, targetPosition)
|
||||
peopleHeaderView?.run {
|
||||
canSwipe = showHeaders && peopleHubVisible && !peopleNotifsPresent
|
||||
peopleState?.targetPosition?.let { targetPosition ->
|
||||
if (targetPosition != peopleState.currentPosition) {
|
||||
resetTranslation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,4 +22,14 @@ import android.view.ViewGroup
|
||||
val ViewGroup.children
|
||||
get() = sequence {
|
||||
for (i in 0 until childCount) yield(getChildAt(i))
|
||||
}
|
||||
}
|
||||
|
||||
/** Inclusive version of [Iterable.takeWhile] */
|
||||
fun <T> Sequence<T>.takeUntil(pred: (T) -> Boolean): Sequence<T> = sequence {
|
||||
for (x in this@takeUntil) {
|
||||
yield(x)
|
||||
if (pred(x)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,22 @@ package com.android.systemui.util
|
||||
|
||||
import android.util.SparseArray
|
||||
|
||||
/**
|
||||
* Transforms a sequence of Key/Value pairs into a SparseArray.
|
||||
*
|
||||
* See [kotlin.collections.toMap].
|
||||
*/
|
||||
fun <T> Sequence<Pair<Int, T>>.toSparseArray(size: Int = -1): SparseArray<T> {
|
||||
val sparseArray = when {
|
||||
size < 0 -> SparseArray<T>()
|
||||
else -> SparseArray<T>(size)
|
||||
}
|
||||
for ((i, v) in this) {
|
||||
sparseArray.put(i, v)
|
||||
}
|
||||
return sparseArray
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an [Array] into a [SparseArray], by applying each element to [keySelector] in order to
|
||||
* generate the index at which it will be placed. If two elements produce the same index, the latter
|
||||
|
||||
@@ -403,11 +403,11 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
||||
enablePeopleFiltering();
|
||||
|
||||
setupMockStack(
|
||||
PERSON.headsUp(), // personHeaderTarget = 0
|
||||
INCOMING_HEADER, // currentIncomingHeaderIdx = 1
|
||||
ALERTING.headsUp(), // alertingHeaderTarget = 1
|
||||
PEOPLE_HEADER, // currentPeopleHeaderIdx = 3
|
||||
PERSON //
|
||||
PERSON.headsUp(),
|
||||
INCOMING_HEADER,
|
||||
ALERTING.headsUp(),
|
||||
PEOPLE_HEADER,
|
||||
PERSON
|
||||
);
|
||||
mSectionsManager.updateSectionBoundaries();
|
||||
|
||||
@@ -520,6 +520,70 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
||||
ChildType.GENTLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveIncomingHeader() {
|
||||
enablePeopleFiltering();
|
||||
enableMediaControls();
|
||||
|
||||
setupMockStack(
|
||||
MEDIA_CONTROLS,
|
||||
INCOMING_HEADER,
|
||||
PERSON,
|
||||
ALERTING,
|
||||
PEOPLE_HEADER,
|
||||
ALERTING_HEADER,
|
||||
ALERTING,
|
||||
ALERTING,
|
||||
GENTLE_HEADER,
|
||||
GENTLE,
|
||||
GENTLE
|
||||
);
|
||||
|
||||
mSectionsManager.updateSectionBoundaries();
|
||||
|
||||
verifyMockStack(
|
||||
ChildType.MEDIA_CONTROLS,
|
||||
ChildType.PEOPLE_HEADER,
|
||||
ChildType.PERSON,
|
||||
ChildType.ALERTING_HEADER,
|
||||
ChildType.ALERTING,
|
||||
ChildType.ALERTING,
|
||||
ChildType.ALERTING,
|
||||
ChildType.GENTLE_HEADER,
|
||||
ChildType.GENTLE,
|
||||
ChildType.GENTLE
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpandIncomingSection() {
|
||||
enablePeopleFiltering();
|
||||
|
||||
setupMockStack(
|
||||
INCOMING_HEADER,
|
||||
PERSON,
|
||||
ALERTING,
|
||||
PEOPLE_HEADER,
|
||||
ALERTING,
|
||||
PERSON,
|
||||
ALERTING_HEADER,
|
||||
ALERTING
|
||||
);
|
||||
|
||||
mSectionsManager.updateSectionBoundaries();
|
||||
|
||||
verifyMockStack(
|
||||
ChildType.INCOMING_HEADER,
|
||||
ChildType.HEADS_UP,
|
||||
ChildType.HEADS_UP,
|
||||
ChildType.HEADS_UP,
|
||||
ChildType.PEOPLE_HEADER,
|
||||
ChildType.PERSON,
|
||||
ChildType.ALERTING_HEADER,
|
||||
ChildType.ALERTING
|
||||
);
|
||||
}
|
||||
|
||||
private void enablePeopleFiltering() {
|
||||
when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
|
||||
}
|
||||
@@ -657,7 +721,13 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
||||
final List<View> children = new ArrayList<>();
|
||||
when(mNssl.getChildCount()).thenAnswer(invocation -> children.size());
|
||||
when(mNssl.getChildAt(anyInt()))
|
||||
.thenAnswer(invocation -> children.get(invocation.getArgument(0)));
|
||||
.thenAnswer(invocation -> {
|
||||
Integer index = invocation.getArgument(0);
|
||||
if (index == null || index < 0 || index >= children.size()) {
|
||||
return null;
|
||||
}
|
||||
return children.get(index);
|
||||
});
|
||||
when(mNssl.indexOfChild(any()))
|
||||
.thenAnswer(invocation -> children.indexOf(invocation.getArgument(0)));
|
||||
doAnswer(invocation -> {
|
||||
|
||||
Reference in New Issue
Block a user