Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 27c4e7be authored by Ibrahim Yilmaz's avatar Ibrahim Yilmaz
Browse files

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
parent 82472631
Loading
Loading
Loading
Loading
+108 −0
Original line number Diff line number Diff line
/*
 * 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
    }
}
+27 −25
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ class NotifCoordinatorsImpl @Inject constructor(
        conversationCoordinator: ConversationCoordinator,
        debugModeCoordinator: DebugModeCoordinator,
        groupCountCoordinator: GroupCountCoordinator,
        groupWhenCoordinator: GroupWhenCoordinator,
        mediaCoordinator: MediaCoordinator,
        preparationCoordinator: PreparationCoordinator,
        remoteInputCoordinator: RemoteInputCoordinator,
@@ -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)
+3 −0
Original line number Diff line number Diff line
@@ -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)
}
 No newline at end of file
+13 −0
Original line number Diff line number Diff line
@@ -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
     */
+9 −0
Original line number Diff line number Diff line
@@ -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);
Loading