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

Commit 5761d758 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge changes from topic "b241229236_stable_sort_flagged" into tm-qpr-dev am:...

Merge changes from topic "b241229236_stable_sort_flagged" into tm-qpr-dev am: 00f287b8 am: d69698aa

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19194469



Change-Id: I6cd7473f90338c04c903b26fc79b09ead315a678
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents ccf1f680 d69698aa
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -75,7 +75,9 @@ public class Flags {

    public static final UnreleasedFlag STABILITY_INDEX_FIX = new UnreleasedFlag(114, true);

    // next id: 115
    public static final UnreleasedFlag SEMI_STABLE_SORT = new UnreleasedFlag(115, true);

    // next id: 116

    /***************************************/
    // 200 - keyguard/lockscreen
+4 −0
Original line number Diff line number Diff line
@@ -57,4 +57,8 @@ class NotifPipelineFlags @Inject constructor(
    val isStabilityIndexFixEnabled: Boolean by lazy {
        featureFlags.isEnabled(Flags.STABILITY_INDEX_FIX)
    }

    val isSemiStableSortEnabled: Boolean by lazy {
        featureFlags.isEnabled(Flags.SEMI_STABLE_SORT)
    }
}
+7 −2
Original line number Diff line number Diff line
@@ -68,6 +68,9 @@ data class ListAttachState private constructor(
     */
    var stableIndex: Int = -1

    /** Access the index of the [section] or -1 if the entry does not have one */
    val sectionIndex: Int get() = section?.index ?: -1

    /** Copies the state of another instance. */
    fun clone(other: ListAttachState) {
        parent = other.parent
@@ -95,11 +98,13 @@ data class ListAttachState private constructor(
     * This can happen if the entry is removed from a group that was broken up or if the entry was
     * filtered out during any of the filtering steps.
     */
    fun detach() {
    fun detach(includingStableIndex: Boolean) {
        parent = null
        section = null
        promoter = null
        // stableIndex = -1  // TODO(b/241229236): Clear this once we fix the stability fragility
        if (includingStableIndex) {
            stableIndex = -1
        }
    }

    companion object {
+76 −9
Original line number Diff line number Diff line
@@ -54,6 +54,9 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState;
import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort;
import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort.StableOrder;
import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper;
import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
@@ -96,12 +99,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
    // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated
    // TODO replace temp with collection pool for readability
    private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>();
    private final NotifPipelineFlags mFlags;
    private NotifPipelineFlags mFlags;
    private final boolean mAlwaysLogList;

    private List<ListEntry> mNotifList = new ArrayList<>();
    private List<ListEntry> mNewNotifList = new ArrayList<>();

    private final SemiStableSort mSemiStableSort = new SemiStableSort();
    private final StableOrder<ListEntry> mStableOrder = this::getStableOrderRank;
    private final PipelineState mPipelineState = new PipelineState();
    private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
    private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
@@ -960,7 +965,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
     * filtered out during any of the filtering steps.
     */
    private void annulAddition(ListEntry entry) {
        entry.getAttachState().detach();
        // NOTE(b/241229236): Don't clear stableIndex until we fix stability fragility
        entry.getAttachState().detach(/* includingStableIndex= */ mFlags.isSemiStableSortEnabled());
    }

    private void assignSections() {
@@ -980,7 +986,16 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {

    private void sortListAndGroups() {
        Trace.beginSection("ShadeListBuilder.sortListAndGroups");
        // Assign sections to top-level elements and sort their children
        if (mFlags.isSemiStableSortEnabled()) {
            sortWithSemiStableSort();
        } else {
            sortWithLegacyStability();
        }
        Trace.endSection();
    }

    private void sortWithLegacyStability() {
        // Sort all groups and the top level list
        for (ListEntry entry : mNotifList) {
            if (entry instanceof GroupEntry) {
                GroupEntry parent = (GroupEntry) entry;
@@ -993,16 +1008,15 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
        // Check for suppressed order changes
        if (!getStabilityManager().isEveryChangeAllowed()) {
            mForceReorderable = true;
            boolean isSorted = isShadeSorted();
            boolean isSorted = isShadeSortedLegacy();
            mForceReorderable = false;
            if (!isSorted) {
                getStabilityManager().onEntryReorderSuppressed();
            }
        }
        Trace.endSection();
    }

    private boolean isShadeSorted() {
    private boolean isShadeSortedLegacy() {
        if (!isSorted(mNotifList, mTopLevelComparator)) {
            return false;
        }
@@ -1016,6 +1030,43 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
        return true;
    }

    private void sortWithSemiStableSort() {
        // Sort each group's children
        boolean allSorted = true;
        for (ListEntry entry : mNotifList) {
            if (entry instanceof GroupEntry) {
                GroupEntry parent = (GroupEntry) entry;
                allSorted &= sortGroupChildren(parent.getRawChildren());
            }
        }
        // Sort each section within the top level list
        mNotifList.sort(mTopLevelComparator);
        if (!getStabilityManager().isEveryChangeAllowed()) {
            for (List<ListEntry> subList : getSectionSubLists(mNotifList)) {
                allSorted &= mSemiStableSort.stabilizeTo(subList, mStableOrder, mNewNotifList);
            }
            applyNewNotifList();
        }
        assignIndexes(mNotifList);
        if (!allSorted) {
            // Report suppressed order changes
            getStabilityManager().onEntryReorderSuppressed();
        }
    }

    private Iterable<List<ListEntry>> getSectionSubLists(List<ListEntry> entries) {
        return ShadeListBuilderHelper.INSTANCE.getSectionSubLists(entries);
    }

    private boolean sortGroupChildren(List<NotificationEntry> entries) {
        if (getStabilityManager().isEveryChangeAllowed()) {
            entries.sort(mGroupChildrenComparator);
            return true;
        } else {
            return mSemiStableSort.sort(entries, mStableOrder, mGroupChildrenComparator);
        }
    }

    /** Determine whether the items in the list are sorted according to the comparator */
    @VisibleForTesting
    public static <T> boolean isSorted(List<T> items, Comparator<? super T> comparator) {
@@ -1212,7 +1263,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
                o2.getSectionIndex());
        if (cmp != 0) return cmp;

        cmp = Integer.compare(
        cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(
                getStableOrderIndex(o1),
                getStableOrderIndex(o2));
        if (cmp != 0) return cmp;
@@ -1241,7 +1292,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {


    private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
        int cmp = Integer.compare(
        int cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(
                getStableOrderIndex(o1),
                getStableOrderIndex(o2));
        if (cmp != 0) return cmp;
@@ -1272,9 +1323,25 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
            // let the stability manager constrain or allow reordering
            return -1;
        }
        // NOTE(b/241229236): Can't use cleared section index until we fix stability fragility
        return entry.getPreviousAttachState().getStableIndex();
    }

    @Nullable
    private Integer getStableOrderRank(ListEntry entry) {
        if (getStabilityManager().isEntryReorderingAllowed(entry)) {
            // let the stability manager constrain or allow reordering
            return null;
        }
        if (entry.getAttachState().getSectionIndex()
                != entry.getPreviousAttachState().getSectionIndex()) {
            // stable index is only valid within the same section; otherwise we allow reordering
            return null;
        }
        final int stableIndex = entry.getPreviousAttachState().getStableIndex();
        return stableIndex == -1 ? null : stableIndex;
    }

    private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
        final NotifFilter filter = findRejectingFilter(entry, now, filters);
        entry.getAttachState().setExcludingFilter(filter);
+200 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.listbuilder

import androidx.annotation.VisibleForTesting
import kotlin.math.sign

class SemiStableSort {
    val preallocatedWorkspace by lazy { ArrayList<Any>() }
    val preallocatedAdditions by lazy { ArrayList<Any>() }
    val preallocatedMapToIndex by lazy { HashMap<Any, Int>() }
    val preallocatedMapToIndexComparator: Comparator<Any> by lazy {
        Comparator.comparingInt { item -> preallocatedMapToIndex[item] ?: -1 }
    }

    /**
     * Sort the given [items] such that items which have a [stableOrder] will all be in that order,
     * items without a [stableOrder] will be sorted according to the comparator, and the two sets of
     * items will be combined to have the fewest elements out of order according to the [comparator]
     * . The result will be placed into the original [items] list.
     */
    fun <T : Any> sort(
        items: MutableList<T>,
        stableOrder: StableOrder<in T>,
        comparator: Comparator<in T>,
    ): Boolean =
        withWorkspace<T, Boolean> { workspace ->
            val ordered =
                sortTo(
                    items,
                    stableOrder,
                    comparator,
                    workspace,
                )
            items.clear()
            items.addAll(workspace)
            return ordered
        }

    /**
     * Sort the given [items] such that items which have a [stableOrder] will all be in that order,
     * items without a [stableOrder] will be sorted according to the comparator, and the two sets of
     * items will be combined to have the fewest elements out of order according to the [comparator]
     * . The result will be put into [output].
     */
    fun <T : Any> sortTo(
        items: Iterable<T>,
        stableOrder: StableOrder<in T>,
        comparator: Comparator<in T>,
        output: MutableList<T>,
    ): Boolean {
        if (DEBUG) println("\n> START from ${items.map { it to stableOrder.getRank(it) }}")
        // If array already has elements, use subList to ensure we only append
        val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size)
        items.filterTo(result) { stableOrder.getRank(it) != null }
        result.sortBy { stableOrder.getRank(it)!! }
        val isOrdered = result.isSorted(comparator)
        withAdditions<T> { additions ->
            items.filterTo(additions) { stableOrder.getRank(it) == null }
            additions.sortWith(comparator)
            insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator)
        }
        return isOrdered
    }

    /**
     * Rearrange the [sortedItems] to enforce that items are in the [stableOrder], and store the
     * result in [output]. Items with a [stableOrder] will be in that order, items without a
     * [stableOrder] will remain in same relative order as the input, and the two sets of items will
     * be combined to have the fewest elements moved from their locations in the original.
     */
    fun <T : Any> stabilizeTo(
        sortedItems: Iterable<T>,
        stableOrder: StableOrder<in T>,
        output: MutableList<T>,
    ): Boolean {
        // Append to the output array if present
        val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size)
        sortedItems.filterTo(result) { stableOrder.getRank(it) != null }
        val stableRankComparator = compareBy<T> { stableOrder.getRank(it)!! }
        val isOrdered = result.isSorted(stableRankComparator)
        if (!isOrdered) {
            result.sortWith(stableRankComparator)
        }
        if (result.isEmpty()) {
            sortedItems.filterTo(result) { stableOrder.getRank(it) == null }
            return isOrdered
        }
        withAdditions<T> { additions ->
            sortedItems.filterTo(additions) { stableOrder.getRank(it) == null }
            if (additions.isNotEmpty()) {
                withIndexOfComparator(sortedItems) { comparator ->
                    insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator)
                }
            }
        }
        return isOrdered
    }

    private inline fun <T : Any, R> withWorkspace(block: (ArrayList<T>) -> R): R {
        preallocatedWorkspace.clear()
        val result = block(preallocatedWorkspace as ArrayList<T>)
        preallocatedWorkspace.clear()
        return result
    }

    private inline fun <T : Any> withAdditions(block: (ArrayList<T>) -> Unit) {
        preallocatedAdditions.clear()
        block(preallocatedAdditions as ArrayList<T>)
        preallocatedAdditions.clear()
    }

    private inline fun <T : Any> withIndexOfComparator(
        sortedItems: Iterable<T>,
        block: (Comparator<in T>) -> Unit
    ) {
        preallocatedMapToIndex.clear()
        sortedItems.forEachIndexed { i, item -> preallocatedMapToIndex[item] = i }
        block(preallocatedMapToIndexComparator as Comparator<in T>)
        preallocatedMapToIndex.clear()
    }

    companion object {

        /**
         * This is the core of the algorithm.
         *
         * Insert [preSortedAdditions] (the elements to be inserted) into [existing] without
         * changing the relative order of any elements already in [existing], even though those
         * elements may be mis-ordered relative to the [comparator], such that the total number of
         * elements which are ordered incorrectly according to the [comparator] is fewest.
         */
        private fun <T> insertPreSortedElementsWithFewestMisOrderings(
            existing: MutableList<T>,
            preSortedAdditions: Iterable<T>,
            comparator: Comparator<in T>,
        ) {
            if (DEBUG) println("  To $existing insert $preSortedAdditions with fewest misordering")
            var iStart = 0
            preSortedAdditions.forEach { toAdd ->
                if (DEBUG) println("    need to add $toAdd to $existing, starting at $iStart")
                var cmpSum = 0
                var cmpSumMax = 0
                var iCmpSumMax = iStart
                if (DEBUG) print("      ")
                for (i in iCmpSumMax until existing.size) {
                    val cmp = comparator.compare(toAdd, existing[i]).sign
                    cmpSum += cmp
                    if (cmpSum > cmpSumMax) {
                        cmpSumMax = cmpSum
                        iCmpSumMax = i + 1
                    }
                    if (DEBUG) print("sum[$i]=$cmpSum, ")
                }
                if (DEBUG) println("inserting $toAdd at $iCmpSumMax")
                existing.add(iCmpSumMax, toAdd)
                iStart = iCmpSumMax + 1
            }
        }

        /** Determines if a list is correctly sorted according to the given comparator */
        @VisibleForTesting
        fun <T> List<T>.isSorted(comparator: Comparator<T>): Boolean {
            if (this.size <= 1) {
                return true
            }
            val iterator = this.iterator()
            var previous = iterator.next()
            var current: T?
            while (iterator.hasNext()) {
                current = iterator.next()
                if (comparator.compare(previous, current) > 0) {
                    return false
                }
                previous = current
            }
            return true
        }
    }

    fun interface StableOrder<T> {
        fun getRank(item: T): Int?
    }
}

val DEBUG = false
Loading