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

Commit 167eeea9 authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Sort notifs chronologically within a section

SystemUI Section list: media, HUNs, RONs, conversations, alerting,
silent (low), silent (min).

System server: sort only by ranking time

The change to sort more strictly by time revealed some other issues:
- some notifications (like the 'connected to USB' ones) explicitly set
  'when' to 0. This is years old behavior, and I now understand why
  calculateRankingTime() used to ignore that value. It is now ignoring
  it again.
- we need to remove the ranking bump for 'recently made noise' to
  maintain a strict time sort
- group proxies - many group summaries (from apps, or our autogrouping)
  are underspecified in the time domain - they don't specify a 'when'
  value that matches the latest 'when' of their children. therefore we
  can no longer just sort groups where their highest ranked member is,
  because it's likely to be the underspecified summary. So now we let
  the group proxy be the group member with the most useful info.
- we need to treat user initiated jobs like FGS for ranking stability
- we need to make sure silent conversations are still hidden on the
  lockscreen if 'hide silent conversations and notifications on the
  lockscreen' is set

Test: post a HUN and then a non-HUN noisy notif. validate order while
HUNing and after HUN is complete (swaps back to newest first in
alerting section once it leaves HUN state)
Test: temp turn off autogrouping so the sort order is more visible in
the status bar, start 2 large downloads from the store, verify stability
Test: SectionStyleProviderTest
Test: ConversationCoordinatorTest
Test: ConversationCoordinatorTest
Test: FrameworkUiServiceTests
Test: CtsNotificationTestCases
Test: cts verifier notification tests
Flag: android.app.update_ranking_time DEV
Flag: android.app.sort_by_section_time DEV
Bug: 330193582
Change-Id: Id51f3955b9122e51b6b98016b36746c32a53ac66

Change-Id: I9d2fe7590c1c2d3cc75f5c1c51a8ad7d68b7badb
parent e2445de7
Loading
Loading
Loading
Loading
+7 −0
Original line number Original line Diff line number Diff line
@@ -3907,6 +3907,13 @@ public class Notification implements Parcelable
        return mLargeIcon;
        return mLargeIcon;
    }
    }
    /**
     * @hide
     */
    public boolean hasAppProvidedWhen() {
        return when != 0 && when != creationTime;
    }
    /**
    /**
     * @hide
     * @hide
     */
     */
+7 −0
Original line number Original line Diff line number Diff line
@@ -82,3 +82,10 @@ flag {
    purpose: PURPOSE_BUGFIX
    purpose: PURPOSE_BUGFIX
  }
  }
}
}

flag {
  name: "sort_section_by_time"
  namespace: "systemui"
  description: "Changes notification sort order to be by time within a section"
  bug: "330193582"
}
 No newline at end of file
+53 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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

import android.app.Flags;
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils

/**
 * Helper for android.app.Flags.FLAG_SORT_BY_SECTION_TIME
 */
@Suppress("NOTHING_TO_INLINE")
object SortBySectionTimeFlag {
    const val FLAG_NAME = Flags.FLAG_SORT_SECTION_BY_TIME

    /** A token used for dependency declaration */
    val token: FlagToken
        get() = FlagToken(FLAG_NAME, isEnabled)

    /** Are sections sorted by time? */
    @JvmStatic
    inline val isEnabled
        get() = Flags.sortSectionByTime()

    /**
     * Called to ensure code is only run when the flag is enabled. This protects users from the
     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
     * build to ensure that the refactor author catches issues in testing.
     */
    @JvmStatic
    inline fun isUnexpectedlyInLegacyMode() =
            RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)

    /**
     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
     * the flag is enabled to ensure that the refactor author catches issues in testing.
     */
    @JvmStatic
    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
}
 No newline at end of file
+25 −6
Original line number Original line Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
@@ -80,11 +81,20 @@ class ConversationCoordinator @Inject constructor(
        }
        }
    }
    }


    // TODO(b/330193582): Rename to just "People"
    val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) {
    val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) {
        override fun isInSection(entry: ListEntry): Boolean =
        override fun isInSection(entry: ListEntry): Boolean  {
               highPriorityProvider.isHighPriorityConversation(entry)
            if (SortBySectionTimeFlag.isEnabled) {
                return highPriorityProvider.isHighPriorityConversation(entry)
                        || isConversation(entry)
            } else {
                return highPriorityProvider.isHighPriorityConversation(entry)
            }
        }


        override fun getComparator(): NotifComparator = notifComparator
        override fun getComparator(): NotifComparator? {
            return if (SortBySectionTimeFlag.isEnabled) null else notifComparator
        }


        override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController
        override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController
    }
    }
@@ -92,11 +102,20 @@ class ConversationCoordinator @Inject constructor(
    val peopleSilentSectioner = object : NotifSectioner("People(silent)", BUCKET_PEOPLE) {
    val peopleSilentSectioner = object : NotifSectioner("People(silent)", BUCKET_PEOPLE) {
        // Because the peopleAlertingSectioner is above this one, it will claim all conversations that are alerting.
        // Because the peopleAlertingSectioner is above this one, it will claim all conversations that are alerting.
        // All remaining conversations must be silent.
        // All remaining conversations must be silent.
        override fun isInSection(entry: ListEntry): Boolean = isConversation(entry)
        override fun isInSection(entry: ListEntry): Boolean {
            SortBySectionTimeFlag.assertInLegacyMode()
            return isConversation(entry)
        }


        override fun getComparator(): NotifComparator = notifComparator
        override fun getComparator(): NotifComparator {
            SortBySectionTimeFlag.assertInLegacyMode()
            return notifComparator
        }


        override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController
        override fun getHeaderNodeController(): NodeController? {
            SortBySectionTimeFlag.assertInLegacyMode()
            return conversationHeaderNodeController
        }
    }
    }


    override fun attach(pipeline: NotifPipeline) {
    override fun attach(pipeline: NotifPipeline) {
+16 −6
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@ import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.PipelineDumpable
import com.android.systemui.statusbar.notification.collection.PipelineDumpable
import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
@@ -114,18 +115,27 @@ class NotifCoordinatorsImpl @Inject constructor(
        mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
        mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
        mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
        mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
        mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
        mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
        if (!SortBySectionTimeFlag.isEnabled) {
            mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
            mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
        }
        mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
        mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
        mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent
        mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent
        mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized
        mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized


        sectionStyleProvider.setMinimizedSections(setOf(rankingCoordinator.minimizedSectioner))
        sectionStyleProvider.setMinimizedSections(setOf(rankingCoordinator.minimizedSectioner))
        if (SortBySectionTimeFlag.isEnabled) {
            sectionStyleProvider.setSilentSections(listOf(
                    rankingCoordinator.silentSectioner,
                    rankingCoordinator.minimizedSectioner,
            ))
        } else {
            sectionStyleProvider.setSilentSections(listOf(
            sectionStyleProvider.setSilentSections(listOf(
                    conversationCoordinator.peopleSilentSectioner,
                    conversationCoordinator.peopleSilentSectioner,
                    rankingCoordinator.silentSectioner,
                    rankingCoordinator.silentSectioner,
                    rankingCoordinator.minimizedSectioner,
                    rankingCoordinator.minimizedSectioner,
            ))
            ))
        }
        }
    }


    /**
    /**
     * Sends the pipeline to each coordinator when the pipeline is ready to accept
     * Sends the pipeline to each coordinator when the pipeline is ready to accept
Loading