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

Commit 7c133319 authored by Steve Elliott's avatar Steve Elliott
Browse files

Fix TODO in #updateFirstAndLastViewsForAllSections

Bug: 153554168
Test: atest, manual
Change-Id: Ibd3f73bc837d13975857288648fe14d817e14190
parent 981cca11
Loading
Loading
Loading
Loading
+49 −28
Original line number Diff line number Diff line
@@ -42,6 +42,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.foldToSparseArray
import javax.inject.Inject

/**
@@ -447,6 +448,38 @@ class NotificationSectionsManager @Inject internal constructor(
        }
    }

    private sealed class SectionBounds {

        data class Many(
            val first: ActivatableNotificationView,
            val last: ActivatableNotificationView
        ) : SectionBounds()

        data class One(val lone: ActivatableNotificationView) : SectionBounds()
        object None : SectionBounds()

        fun addNotif(notif: ActivatableNotificationView): SectionBounds = when (this) {
            is None -> One(notif)
            is One -> Many(lone, notif)
            is Many -> copy(last = notif)
        }

        fun updateSection(section: NotificationSection): Boolean = when (this) {
            is None -> section.setFirstAndLastVisibleChildren(null, null)
            is One -> section.setFirstAndLastVisibleChildren(lone, lone)
            is Many -> section.setFirstAndLastVisibleChildren(first, last)
        }

        private fun NotificationSection.setFirstAndLastVisibleChildren(
            first: ActivatableNotificationView?,
            last: ActivatableNotificationView?
        ): Boolean {
            val firstChanged = setFirstVisibleChild(first)
            val lastChanged = setLastVisibleChild(last)
            return firstChanged || lastChanged
        }
    }

    /**
     * Updates the boundaries (as tracked by their first and last views) of the priority sections.
     *
@@ -456,35 +489,23 @@ class NotificationSectionsManager @Inject internal constructor(
        sections: Array<NotificationSection>,
        children: List<ActivatableNotificationView>
    ): Boolean {
        if (sections.isEmpty() || children.isEmpty()) {
            for (s in sections) {
                s.firstVisibleChild = null
                s.lastVisibleChild = null
            }
            return false
        }
        var changed = false
        val viewsInBucket = mutableListOf<ActivatableNotificationView>()
        for (s in sections) {
            val filter = s.bucket
            viewsInBucket.clear()

            // TODO: do this in a single pass, and more better
            for (v in children) {
                val bucket = getBucket(v)
        // Create mapping of bucket to section
        val sectionBounds = children.asSequence()
                // Group children by bucket
                .groupingBy {
                    getBucket(it)
                            ?: throw IllegalArgumentException("Cannot find section bucket for view")
                if (bucket == filter) {
                    viewsInBucket.add(v)
                }
                if (viewsInBucket.size >= 1) {
                    changed = changed or s.setFirstVisibleChild(viewsInBucket[0])
                    changed = changed or
                            s.setLastVisibleChild(viewsInBucket[viewsInBucket.size - 1])
                } else {
                    changed = changed or s.setFirstVisibleChild(null)
                    changed = changed or s.setLastVisibleChild(null)
                }
                }
                // Combine each bucket into a SectionBoundary
                .foldToSparseArray(
                        SectionBounds.None,
                        size = sections.size,
                        operation = SectionBounds::addNotif
                )
        // Update each section with the associated boundary, tracking if there was a change
        val changed = sections.fold(false) { changed, section ->
            val bounds = sectionBounds[section.bucket] ?: SectionBounds.None
            bounds.updateSection(section) || changed
        }
        if (DEBUG) {
            logSections(sections)
+136 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.util

import android.util.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
 * replaces the former in the final result.
 *
 * See [Array.associateBy].
 */
inline fun <T> Array<T>.associateByToSparseArray(
    crossinline keySelector: (T) -> Int
): SparseArray<T> {
    val sparseArray = SparseArray<T>(size)
    for (value in this) {
        sparseArray.put(keySelector(value), value)
    }
    return sparseArray
}

/**
 * Folds a [Grouping] into a [SparseArray]. See [Grouping.fold].
 */
inline fun <T, R> Grouping<T, Int>.foldToSparseArray(
    initial: R,
    size: Int = -1,
    crossinline operation: (R, T) -> R
): SparseArray<R> {
    val sparseArray = when {
        size < 0 -> SparseArray<R>()
        else -> SparseArray<R>(size)
    }
    sourceIterator().forEach { elem ->
        val key = keyOf(elem)
        val acc = sparseArray.get(key) ?: initial
        sparseArray.put(key, operation(acc, elem))
    }
    return sparseArray
}

/**
 * Wraps this [SparseArray] into an immutable [Map], the methods of which forward to this
 * [SparseArray].
 */
fun <T> SparseArray<T>.asMap(): Map<Int, T> = SparseArrayMapWrapper(this)

private class SparseArrayMapWrapper<T>(
    private val sparseArray: SparseArray<T>
) : Map<Int, T> {

    private data class Entry<T>(override val key: Int, override val value: T) : Map.Entry<Int, T>

    private val entrySequence = sequence {
        val size = sparseArray.size()
        for (i in 0 until size) {
            val key = sparseArray.keyAt(i)
            val value = sparseArray.get(key)
            yield(Entry(key, value))
        }
    }

    override val entries: Set<Map.Entry<Int, T>>
        get() = object : Set<Map.Entry<Int, T>> {
            override val size: Int
                get() = this@SparseArrayMapWrapper.size

            override fun contains(element: Map.Entry<Int, T>): Boolean =
                    sparseArray[element.key]?.let { it == element.value } == true

            override fun containsAll(elements: Collection<Map.Entry<Int, T>>): Boolean =
                    elements.all { contains(it) }

            override fun isEmpty(): Boolean = size == 0

            override fun iterator(): Iterator<Map.Entry<Int, T>> = entrySequence.iterator()
        }

    override val keys: Set<Int> = object : Set<Int> {
        private val keySequence = entrySequence.map { it.key }

        override val size: Int
            get() = this@SparseArrayMapWrapper.size

        override fun contains(element: Int): Boolean = containsKey(element)

        override fun containsAll(elements: Collection<Int>): Boolean =
                elements.all { contains(it) }

        override fun isEmpty(): Boolean = size == 0

        override fun iterator(): Iterator<Int> = keySequence.iterator()
    }
    override val size: Int
        get() = sparseArray.size()
    override val values: Collection<T>
        get() = object : Collection<T> {
            private val valueSequence = entrySequence.map { it.value }

            override val size: Int
                get() = this@SparseArrayMapWrapper.size

            override fun contains(element: T): Boolean = containsValue(element)

            override fun containsAll(elements: Collection<T>): Boolean =
                    elements.all { contains(it) }

            override fun isEmpty(): Boolean = this@SparseArrayMapWrapper.isEmpty()

            override fun iterator(): Iterator<T> = valueSequence.iterator()
        }

    override fun containsKey(key: Int): Boolean = sparseArray.contains(key)

    override fun containsValue(value: T): Boolean = sparseArray.indexOfValue(value) >= 0

    override fun get(key: Int): T? = sparseArray.get(key)

    override fun isEmpty(): Boolean = sparseArray.size() == 0
}
 No newline at end of file