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

Commit b6732216 authored by Ned Burns's avatar Ned Burns
Browse files

Spin off node generation logic into testable class

The existing node logic _works_, but it's not the easiest to understand.
Rewrites that logic, spins it off into a testable class, and adds tests.

Also cleans up a few things in the name of testability:
- Changes NotifViewBarn to deal with bare NodeControllers
- Exposes the ability for tests to modify the attachstate of
notifentries
- Fixes some formatting in treeSpecToStr

Test: atest
Bug: 196408434
Change-Id: Ie6b6298fb228e59906d34a9906b0f43472100682
parent feed5a8d
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -78,7 +78,7 @@ fun treeSpecToStr(tree: NodeSpec): String {
}

private fun treeSpecToStrHelper(tree: NodeSpec, sb: StringBuilder, indent: String) {
    sb.append("${indent}ns{${tree.controller.nodeLabel}")
    sb.append("${indent}{${tree.controller.nodeLabel}}\n")
    if (tree.children.isNotEmpty()) {
        val childIndent = "$indent  "
        for (child in tree.children) {
+73 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.render

import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection

/**
 * Converts a notif list (the output of the ShadeListBuilder) into a NodeSpec, an abstract
 * representation of which views should be present in the shade. This spec will later be consumed
 * by the ViewDiffer, which will add and remove views until the shade matches the spec. Up until
 * this point, the pipeline has dealt with pure data representations of notifications (in the
 * form of NotificationEntries). In this step, NotificationEntries finally become associated with
 * the views that will represent them. In addition, we add in any non-notification views that also
 * need to present in the shade, notably the section headers.
 */
class NodeSpecBuilder(
    private val viewBarn: NotifViewBarn
) {
    fun buildNodeSpec(
        rootController: NodeController,
        notifList: List<ListEntry>
    ): NodeSpec {
        val root = NodeSpecImpl(null, rootController)
        var currentSection: NotifSection? = null
        val prevSections = mutableSetOf<NotifSection?>()

        for (entry in notifList) {
            val section = entry.section!!

            if (prevSections.contains(section)) {
                throw java.lang.RuntimeException("Section ${section.label} has been duplicated")
            }

            // If this notif begins a new section, first add the section's header view
            if (section != currentSection) {
                section.headerController?.let { headerController ->
                    root.children.add(NodeSpecImpl(root, headerController))
                }
                prevSections.add(currentSection)
                currentSection = section
            }

            // Finally, add the actual notif node!
            root.children.add(buildNotifNode(root, entry))
        }

        return root
    }

    private fun buildNotifNode(parent: NodeSpec, entry: ListEntry): NodeSpec = when (entry) {
        is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireView(entry))
        is GroupEntry -> NodeSpecImpl(parent, viewBarn.requireView(checkNotNull(entry.summary)))
                .apply { entry.children.forEach { children.add(buildNotifNode(this, it)) } }
        else -> throw RuntimeException("Unexpected entry: $entry")
    }
}
+4 −6
Original line number Diff line number Diff line
@@ -19,18 +19,16 @@ package com.android.systemui.statusbar.notification.collection.render
import android.view.textclassifier.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController
import javax.inject.Inject

/**
 * The ViewBarn is just a map from [ListEntry] to an instance of an
 * [ExpandableNotificationRowController].
 * The ViewBarn is just a map from [ListEntry] to an instance of a [NodeController].
 */
@SysUISingleton
class NotifViewBarn @Inject constructor() {
    private val rowMap = mutableMapOf<String, ExpandableNotificationRowController>()
    private val rowMap = mutableMapOf<String, NodeController>()

    fun requireView(forEntry: ListEntry): ExpandableNotificationRowController {
    fun requireView(forEntry: ListEntry): NodeController {
        if (DEBUG) {
            Log.d(TAG, "requireView: $forEntry.key")
        }
@@ -42,7 +40,7 @@ class NotifViewBarn @Inject constructor() {
        return li
    }

    fun registerViewForEntry(entry: ListEntry, controller: ExpandableNotificationRowController) {
    fun registerViewForEntry(entry: ListEntry, controller: NodeController) {
        if (DEBUG) {
            Log.d(TAG, "registerViewForEntry: $entry.key")
        }
+4 −30
Original line number Diff line number Diff line
@@ -18,9 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render

import android.content.Context
import android.view.View
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.NotificationIconAreaController
@@ -34,45 +32,21 @@ class ShadeViewManager constructor(
    context: Context,
    listContainer: NotificationListContainer,
    logger: ShadeViewDifferLogger,
    private val viewBarn: NotifViewBarn,
    viewBarn: NotifViewBarn,
    private val notificationIconAreaController: NotificationIconAreaController
) {
    // We pass a shim view here because the listContainer may not actually have a view associated
    // with it and the differ never actually cares about the root node's view.
    private val rootController = RootNodeController(listContainer, View(context))
    private val specBuilder = NodeSpecBuilder(viewBarn)
    private val viewDiffer = ShadeViewDiffer(rootController, logger)

    fun attach(listBuilder: ShadeListBuilder) =
            listBuilder.setOnRenderListListener(::onNewNotifTree)

    private fun onNewNotifTree(tree: List<ListEntry>) = viewDiffer.applySpec(buildTree(tree))

    private fun buildTree(notifList: List<ListEntry>): NodeSpec {
        val root = NodeSpecImpl(null, rootController).apply {
            // Insert first section header, if present
            notifList.firstOrNull()?.section?.headerController?.let {
                children.add(NodeSpecImpl(this, it))
            }
            notifList.firstOrNull()?.let {
                children.add(buildNotifNode(it, this))
            }
            notifList.asSequence().zipWithNext().forEach { (prev, entry) ->
                // Insert new header if the section has changed between two entries
                entry.section.takeIf { it != prev.section }?.headerController?.let {
                    children.add(NodeSpecImpl(this, it))
                }
                children.add(buildNotifNode(entry, this))
            }
        }
    private fun onNewNotifTree(notifList: List<ListEntry>) {
        viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
        notificationIconAreaController.updateNotificationIcons(notifList)
        return root
    }

    private fun buildNotifNode(entry: ListEntry, parent: NodeSpec): NodeSpec = when (entry) {
        is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireView(entry))
        is GroupEntry -> NodeSpecImpl(parent, viewBarn.requireView(checkNotNull(entry.summary)))
                .apply { entry.children.forEach { children.add(buildNotifNode(it, this)) } }
        else -> throw RuntimeException("Unexpected entry: $entry")
    }
}

+4 −0
Original line number Diff line number Diff line
@@ -30,3 +30,7 @@ inline fun modifyEntry(
    modifier(builder)
    builder.apply(entry)
}

fun getAttachState(entry: ListEntry): ListAttachState {
    return entry.attachState
}
Loading