Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +49 −28 Original line number Diff line number Diff line Loading @@ -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 /** Loading Loading @@ -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. * Loading @@ -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) Loading packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt 0 → 100644 +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 Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +49 −28 Original line number Diff line number Diff line Loading @@ -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 /** Loading Loading @@ -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. * Loading @@ -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) Loading
packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt 0 → 100644 +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