Show correct last notification time for grouped notifications
The time value in the current group notifications is not synced with its child notifications. That causes sub-optimal user experience for time critical contents. We decided to solve this problem with a small group coordinator(GroupTimeCoordinator): - if all notifications before now, we show the closest time value by now aka maximum value. - if all notifications after now, we show the closest time value by now aka minimum value. - if there are notifications before and after now, we show the closest the future time by now. Because, any future event is more important than even the most recent past event. Test: atest GroupWhenCoordinatorTest Bug: 181790059 Change-Id: I1bbbffb8d5c7a056f19b743b2984f86611978610
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.collection.coordinator
|
||||
|
||||
import android.util.ArrayMap
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntry
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/** A small coordinator which finds, stores, and applies the closest notification time. */
|
||||
@CoordinatorScope
|
||||
class GroupWhenCoordinator
|
||||
@Inject
|
||||
constructor(
|
||||
@Main private val delayableExecutor: DelayableExecutor,
|
||||
private val systemClock: SystemClock
|
||||
) : Coordinator {
|
||||
|
||||
private val invalidator = object : Invalidator("GroupWhenCoordinator") {}
|
||||
private val notificationGroupTimes = ArrayMap<GroupEntry, Long>()
|
||||
private var cancelInvalidateListRunnable: Runnable? = null
|
||||
|
||||
private val invalidateListRunnable: Runnable = Runnable {
|
||||
invalidator.invalidateList("future notification invalidation")
|
||||
}
|
||||
|
||||
override fun attach(pipeline: NotifPipeline) {
|
||||
pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilterListener)
|
||||
pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroupListener)
|
||||
pipeline.addPreRenderInvalidator(invalidator)
|
||||
}
|
||||
|
||||
private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) {
|
||||
cancelListInvalidation()
|
||||
notificationGroupTimes.clear()
|
||||
|
||||
val now = systemClock.currentTimeMillis()
|
||||
var closestFutureTime = Long.MAX_VALUE
|
||||
entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
|
||||
val whenMillis = calculateGroupNotificationTime(groupEntry, now)
|
||||
notificationGroupTimes[groupEntry] = whenMillis
|
||||
if (whenMillis > now) {
|
||||
closestFutureTime = min(closestFutureTime, whenMillis)
|
||||
}
|
||||
}
|
||||
|
||||
if (closestFutureTime != Long.MAX_VALUE) {
|
||||
cancelInvalidateListRunnable =
|
||||
delayableExecutor.executeDelayed(invalidateListRunnable, closestFutureTime - now)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelListInvalidation() {
|
||||
cancelInvalidateListRunnable?.run()
|
||||
cancelInvalidateListRunnable = null
|
||||
}
|
||||
|
||||
private fun onAfterRenderGroupListener(group: GroupEntry, controller: NotifGroupController) {
|
||||
notificationGroupTimes[group]?.let(controller::setNotificationGroupWhen)
|
||||
}
|
||||
|
||||
private fun calculateGroupNotificationTime(
|
||||
groupEntry: GroupEntry,
|
||||
currentTimeMillis: Long
|
||||
): Long {
|
||||
var pastTime = Long.MIN_VALUE
|
||||
var futureTime = Long.MAX_VALUE
|
||||
groupEntry.children
|
||||
.asSequence()
|
||||
.mapNotNull { child -> child.sbn.notification.`when`.takeIf { it > 0 } }
|
||||
.forEach { time ->
|
||||
val isInThePast = currentTimeMillis - time > 0
|
||||
if (isInThePast) {
|
||||
pastTime = max(pastTime, time)
|
||||
} else {
|
||||
futureTime = min(futureTime, time)
|
||||
}
|
||||
}
|
||||
|
||||
if (pastTime == Long.MIN_VALUE && futureTime == Long.MAX_VALUE) {
|
||||
return checkNotNull(groupEntry.summary).creationTime
|
||||
}
|
||||
|
||||
return if (futureTime != Long.MAX_VALUE) futureTime else pastTime
|
||||
}
|
||||
}
|
||||
@@ -31,31 +31,32 @@ interface NotifCoordinators : Coordinator, PipelineDumpable
|
||||
|
||||
@CoordinatorScope
|
||||
class NotifCoordinatorsImpl @Inject constructor(
|
||||
notifPipelineFlags: NotifPipelineFlags,
|
||||
dataStoreCoordinator: DataStoreCoordinator,
|
||||
hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
|
||||
hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
|
||||
keyguardCoordinator: KeyguardCoordinator,
|
||||
rankingCoordinator: RankingCoordinator,
|
||||
appOpsCoordinator: AppOpsCoordinator,
|
||||
deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
|
||||
bubbleCoordinator: BubbleCoordinator,
|
||||
headsUpCoordinator: HeadsUpCoordinator,
|
||||
gutsCoordinator: GutsCoordinator,
|
||||
conversationCoordinator: ConversationCoordinator,
|
||||
debugModeCoordinator: DebugModeCoordinator,
|
||||
groupCountCoordinator: GroupCountCoordinator,
|
||||
mediaCoordinator: MediaCoordinator,
|
||||
preparationCoordinator: PreparationCoordinator,
|
||||
remoteInputCoordinator: RemoteInputCoordinator,
|
||||
rowAppearanceCoordinator: RowAppearanceCoordinator,
|
||||
stackCoordinator: StackCoordinator,
|
||||
shadeEventCoordinator: ShadeEventCoordinator,
|
||||
smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
|
||||
viewConfigCoordinator: ViewConfigCoordinator,
|
||||
visualStabilityCoordinator: VisualStabilityCoordinator,
|
||||
sensitiveContentCoordinator: SensitiveContentCoordinator,
|
||||
dismissibilityCoordinator: DismissibilityCoordinator
|
||||
notifPipelineFlags: NotifPipelineFlags,
|
||||
dataStoreCoordinator: DataStoreCoordinator,
|
||||
hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
|
||||
hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
|
||||
keyguardCoordinator: KeyguardCoordinator,
|
||||
rankingCoordinator: RankingCoordinator,
|
||||
appOpsCoordinator: AppOpsCoordinator,
|
||||
deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
|
||||
bubbleCoordinator: BubbleCoordinator,
|
||||
headsUpCoordinator: HeadsUpCoordinator,
|
||||
gutsCoordinator: GutsCoordinator,
|
||||
conversationCoordinator: ConversationCoordinator,
|
||||
debugModeCoordinator: DebugModeCoordinator,
|
||||
groupCountCoordinator: GroupCountCoordinator,
|
||||
groupWhenCoordinator: GroupWhenCoordinator,
|
||||
mediaCoordinator: MediaCoordinator,
|
||||
preparationCoordinator: PreparationCoordinator,
|
||||
remoteInputCoordinator: RemoteInputCoordinator,
|
||||
rowAppearanceCoordinator: RowAppearanceCoordinator,
|
||||
stackCoordinator: StackCoordinator,
|
||||
shadeEventCoordinator: ShadeEventCoordinator,
|
||||
smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
|
||||
viewConfigCoordinator: ViewConfigCoordinator,
|
||||
visualStabilityCoordinator: VisualStabilityCoordinator,
|
||||
sensitiveContentCoordinator: SensitiveContentCoordinator,
|
||||
dismissibilityCoordinator: DismissibilityCoordinator
|
||||
) : NotifCoordinators {
|
||||
|
||||
private val mCoordinators: MutableList<Coordinator> = ArrayList()
|
||||
@@ -82,6 +83,7 @@ class NotifCoordinatorsImpl @Inject constructor(
|
||||
mCoordinators.add(debugModeCoordinator)
|
||||
mCoordinators.add(conversationCoordinator)
|
||||
mCoordinators.add(groupCountCoordinator)
|
||||
mCoordinators.add(groupWhenCoordinator)
|
||||
mCoordinators.add(mediaCoordinator)
|
||||
mCoordinators.add(rowAppearanceCoordinator)
|
||||
mCoordinators.add(stackCoordinator)
|
||||
|
||||
@@ -20,4 +20,7 @@ package com.android.systemui.statusbar.notification.collection.render
|
||||
interface NotifGroupController {
|
||||
/** Set the number of children that this group would have if not for the 8-child max */
|
||||
fun setUntruncatedChildCount(untruncatedChildCount: Int)
|
||||
|
||||
/** Set the when value of notification group that reflects most important closest notification time */
|
||||
fun setNotificationGroupWhen(whenMillis: Long)
|
||||
}
|
||||
@@ -852,6 +852,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
mChildrenContainer.setUntruncatedChildCount(childCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NotificationChildrenContainer#setNotificationGroupWhen(long)
|
||||
*/
|
||||
public void setNotificationGroupWhen(long whenMillis) {
|
||||
if (mIsSummaryWithChildren) {
|
||||
mChildrenContainer.setNotificationGroupWhen(whenMillis);
|
||||
} else {
|
||||
Log.w(TAG, "setNotificationGroupWhen( whenMillis: " + whenMillis + ")"
|
||||
+ " mIsSummaryWithChildren: false"
|
||||
+ " mChildrenContainer has not been inflated yet.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after children have been attached to set the expansion states
|
||||
*/
|
||||
|
||||
@@ -348,6 +348,15 @@ public class ExpandableNotificationRowController implements NotifViewController
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotificationGroupWhen(long whenMillis) {
|
||||
if (mView.isSummaryWithChildren()) {
|
||||
mView.setNotificationGroupWhen(whenMillis);
|
||||
} else {
|
||||
Log.w(TAG, "Called setNotificationTime(" + whenMillis + ") on a leaf row");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSystemExpanded(boolean systemExpanded) {
|
||||
mView.setSystemExpanded(systemExpanded);
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.PathInterpolator;
|
||||
import android.widget.DateTimeView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
@@ -344,6 +345,21 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
|
||||
mTransformationHelper.setVisible(visible);
|
||||
}
|
||||
|
||||
/***
|
||||
* Set Notification when value
|
||||
* @param whenMillis
|
||||
*/
|
||||
public void setNotificationWhen(long whenMillis) {
|
||||
if (mNotificationHeader == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final View timeView = mNotificationHeader.findViewById(com.android.internal.R.id.time);
|
||||
|
||||
if (timeView instanceof DateTimeView) {
|
||||
((DateTimeView) timeView).setTime(whenMillis);
|
||||
}
|
||||
}
|
||||
protected void addTransformedViews(View... views) {
|
||||
for (View view : views) {
|
||||
if (view != null) {
|
||||
|
||||
@@ -295,6 +295,19 @@ public class NotificationChildrenContainer extends ViewGroup
|
||||
updateGroupOverflow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the notification time in the group so that the view can show the latest event in the UI
|
||||
* appropriately.
|
||||
*/
|
||||
public void setNotificationGroupWhen(long whenMillis) {
|
||||
if (mNotificationHeaderWrapper != null) {
|
||||
mNotificationHeaderWrapper.setNotificationWhen(whenMillis);
|
||||
}
|
||||
if (mNotificationHeaderWrapperLowPriority != null) {
|
||||
mNotificationHeaderWrapperLowPriority.setNotificationWhen(whenMillis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a child notification to this view.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.collection.coordinator
|
||||
|
||||
import android.app.Notification
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.SbnBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.mockito.withArgCaptor
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations.initMocks
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
class GroupWhenCoordinatorTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
|
||||
private lateinit var afterRenderGroupListener: OnAfterRenderGroupListener
|
||||
|
||||
@Mock private lateinit var pipeline: NotifPipeline
|
||||
|
||||
@Mock private lateinit var delayableExecutor: DelayableExecutor
|
||||
|
||||
@Mock private lateinit var groupController: NotifGroupController
|
||||
|
||||
@Mock private lateinit var systemClock: SystemClock
|
||||
|
||||
@InjectMocks private lateinit var coordinator: GroupWhenCoordinator
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
initMocks(this)
|
||||
whenever(systemClock.currentTimeMillis()).thenReturn(NOW)
|
||||
coordinator.attach(pipeline)
|
||||
|
||||
beforeFinalizeFilterListener = withArgCaptor {
|
||||
verify(pipeline).addOnBeforeFinalizeFilterListener(capture())
|
||||
}
|
||||
afterRenderGroupListener = withArgCaptor {
|
||||
verify(pipeline).addOnAfterRenderGroupListener(capture())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreBeforeNow() {
|
||||
// GIVEN
|
||||
val summaryEntry = buildNotificationEntry(0, NOW)
|
||||
val childEntry1 = buildNotificationEntry(1, NOW - 10L)
|
||||
val childEntry2 = buildNotificationEntry(2, NOW - 100L)
|
||||
val groupEntry =
|
||||
GroupEntryBuilder()
|
||||
.setSummary(summaryEntry)
|
||||
.setChildren(listOf(childEntry1, childEntry2))
|
||||
.build()
|
||||
// WHEN
|
||||
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
|
||||
afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
|
||||
|
||||
// THEN
|
||||
verify(groupController).setNotificationGroupWhen(eq(NOW - 10L))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreAfterNow() {
|
||||
// GIVEN
|
||||
val summaryEntry = buildNotificationEntry(0, NOW)
|
||||
val childEntry1 = buildNotificationEntry(1, NOW + 10L)
|
||||
val childEntry2 = buildNotificationEntry(2, NOW + 100L)
|
||||
|
||||
val groupEntry =
|
||||
GroupEntryBuilder()
|
||||
.setSummary(summaryEntry)
|
||||
.setChildren(listOf(childEntry1, childEntry2))
|
||||
.build()
|
||||
|
||||
// WHEN
|
||||
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
|
||||
afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
|
||||
|
||||
// THEN
|
||||
verify(groupController).setNotificationGroupWhen(eq(NOW + 10L))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setNotificationGroupWhen_setClosestFutureTimeByNow_whenThereAreBothBeforeAndAfterNow() {
|
||||
// GIVEN
|
||||
val summaryEntry = buildNotificationEntry(0, NOW)
|
||||
val childEntry1 = buildNotificationEntry(1, NOW + 100L)
|
||||
val childEntry2 = buildNotificationEntry(2, NOW + 10L)
|
||||
val childEntry3 = buildNotificationEntry(3, NOW - 100L)
|
||||
val childEntry4 = buildNotificationEntry(4, NOW - 9L)
|
||||
|
||||
val groupEntry =
|
||||
GroupEntryBuilder()
|
||||
.setSummary(summaryEntry)
|
||||
.setChildren(listOf(childEntry1, childEntry2, childEntry3, childEntry4))
|
||||
.build()
|
||||
|
||||
// WHEN
|
||||
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
|
||||
afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
|
||||
|
||||
// THEN
|
||||
verify(groupController).setNotificationGroupWhen(eq(NOW + 10L))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setNotificationGroupWhen_filterInvalidNotificationTimes() {
|
||||
// GIVEN
|
||||
val summaryEntry = buildNotificationEntry(0, NOW)
|
||||
val childEntry1 = buildNotificationEntry(1, NOW + 100L)
|
||||
val childEntry2 = buildNotificationEntry(2, -20000L)
|
||||
val childEntry3 = buildNotificationEntry(4, 0)
|
||||
|
||||
val groupEntry =
|
||||
GroupEntryBuilder()
|
||||
.setSummary(summaryEntry)
|
||||
.setChildren(listOf(childEntry1, childEntry2, childEntry3))
|
||||
.build()
|
||||
|
||||
// WHEN
|
||||
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
|
||||
afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
|
||||
|
||||
// THEN
|
||||
verify(groupController).setNotificationGroupWhen(eq(NOW + 100))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setNotificationGroupWhen_setSummaryTimeWhenAllNotificationTimesAreInvalid() {
|
||||
// GIVEN
|
||||
val summaryEntry = buildNotificationEntry(0, NOW)
|
||||
val childEntry1 = buildNotificationEntry(1, 0)
|
||||
val childEntry2 = buildNotificationEntry(2, -1)
|
||||
|
||||
val groupEntry =
|
||||
GroupEntryBuilder()
|
||||
.setSummary(summaryEntry)
|
||||
.setChildren(listOf(childEntry1, childEntry2))
|
||||
.build()
|
||||
|
||||
// WHEN
|
||||
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
|
||||
afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
|
||||
|
||||
// THEN
|
||||
verify(groupController, never()).setNotificationGroupWhen(NOW)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setNotificationGroupWhen_schedulePipelineInvalidationWhenAnyNotificationIsInTheFuture() {
|
||||
// GIVEN
|
||||
val summaryEntry = buildNotificationEntry(0, NOW)
|
||||
val childEntry1 = buildNotificationEntry(1, NOW + 1000L)
|
||||
val childEntry2 = buildNotificationEntry(2, NOW + 2000L)
|
||||
val childEntry3 = buildNotificationEntry(3, NOW - 100L)
|
||||
|
||||
val groupEntry =
|
||||
GroupEntryBuilder()
|
||||
.setSummary(summaryEntry)
|
||||
.setChildren(listOf(childEntry1, childEntry2, childEntry3))
|
||||
.build()
|
||||
|
||||
// WHEN
|
||||
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
|
||||
afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
|
||||
|
||||
// THEN
|
||||
verify(delayableExecutor).executeDelayed(any(), eq(1000))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setNotificationGroupWhen_cancelPrevPipelineInvalidation() {
|
||||
// GIVEN
|
||||
val summaryEntry = buildNotificationEntry(0, NOW)
|
||||
val childEntry1 = buildNotificationEntry(1, NOW + 1L)
|
||||
val prevInvalidation = mock<Runnable>()
|
||||
whenever(delayableExecutor.executeDelayed(any(), any())).thenReturn(prevInvalidation)
|
||||
|
||||
val groupEntry =
|
||||
GroupEntryBuilder().setSummary(summaryEntry).setChildren(listOf(childEntry1)).build()
|
||||
|
||||
// WHEN
|
||||
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
|
||||
afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
|
||||
|
||||
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
|
||||
|
||||
// THEN
|
||||
verify(prevInvalidation).run()
|
||||
}
|
||||
|
||||
private fun buildNotificationEntry(id: Int, timeMillis: Long): NotificationEntry {
|
||||
val notification = Notification.Builder(mContext).setWhen(timeMillis).build()
|
||||
val sbn = SbnBuilder().setNotification(notification).build()
|
||||
return NotificationEntryBuilder().setId(id).setSbn(sbn).build()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val NOW = 1000L
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user