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

Commit feace42e authored by Steve Elliott's avatar Steve Elliott
Browse files

Reduce NICRefactor load on main dispatcher

Main thread load is reduced via three tacks:

1. Offload as much computation as possible to the bg thread dispatcher.

2. Share Flows where possible. This is performed in the view-binder
where we know that states will be observed by multiple collectors, so we
can optimize by sharing using stateIn.

3. Remove any unnecessary upstream Flows, to reduce excessive
recomputation.

Tracing has also been added to help diagnose future performance issues,
if any arise.

Bug: 317509049
Flag: ACONFIG com.android.systemui.notifications_icon_container_refactor TEAMFOOD
Test: atest SystemUITests
Change-Id: I90e1cfc3ab24c75ec15a1e14952b880829f17471
parent 2d1d2cce
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -323,9 +323,14 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
     * Update the icon dimens and drawable with current resources
     */
    public void updateIconDimens() {
        Trace.beginSection("StatusBarIconView#updateIconDimens");
        try {
            reloadDimens();
            updateDrawable();
            maybeUpdateIconScaleDimens();
        } finally {
            Trace.endSection();
        }
    }

    private void reloadDimens() {
+26 −17
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@

package com.android.systemui.statusbar.notification.icon.domain.interactor

import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
@@ -26,11 +27,13 @@ import com.android.systemui.statusbar.notification.shared.ActiveNotificationMode
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.optionals.getOrNull
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn

/** Domain logic related to notification icons. */
class NotificationIconsInteractor
@@ -103,11 +106,13 @@ constructor(
class AlwaysOnDisplayNotificationIconsInteractor
@Inject
constructor(
    @Background bgContext: CoroutineContext,
    deviceEntryInteractor: DeviceEntryInteractor,
    iconsInteractor: NotificationIconsInteractor,
) {
    val aodNotifs: Flow<Set<ActiveNotificationModel>> =
        deviceEntryInteractor.isBypassEnabled.flatMapLatest { isBypassEnabled ->
        deviceEntryInteractor.isBypassEnabled
            .flatMapLatest { isBypassEnabled ->
                iconsInteractor.filteredNotifSet(
                    showAmbient = false,
                    showDismissed = false,
@@ -115,17 +120,20 @@ constructor(
                    showPulsing = !isBypassEnabled,
                )
            }
            .flowOn(bgContext)
}

/** Domain logic related to notification icons shown in the status bar. */
class StatusBarNotificationIconsInteractor
@Inject
constructor(
    @Background bgContext: CoroutineContext,
    iconsInteractor: NotificationIconsInteractor,
    settingsRepository: NotificationListenerSettingsRepository,
) {
    val statusBarNotifs: Flow<Set<ActiveNotificationModel>> =
        settingsRepository.showSilentStatusIcons.flatMapLatest { showSilentIcons ->
        settingsRepository.showSilentStatusIcons
            .flatMapLatest { showSilentIcons ->
                iconsInteractor.filteredNotifSet(
                    forceShowHeadsUp = true,
                    showAmbient = false,
@@ -134,4 +142,5 @@ constructor(
                    showRepliedMessages = false,
                )
            }
            .flowOn(bgContext)
}
+22 −19
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.icon.ui.viewbinder

import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.traceSection
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -44,7 +45,8 @@ constructor(
    private val viewStore: AlwaysOnDisplayNotificationIconViewStore,
) {
    fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle {
        return view.repeatWhenAttached {
        return traceSection("NICAlwaysOnDisplay#bindWhileAttached") {
            view.repeatWhenAttached {
                lifecycleScope.launch {
                    launch {
                        NotificationIconContainerViewBinder.bind(
@@ -68,6 +70,7 @@ constructor(
            }
        }
    }
}

/** [IconViewStore] for the always-on display. */
class AlwaysOnDisplayNotificationIconViewStore
+13 −10
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.icon.ui.viewbinder

import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.traceSection
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.collection.NotifCollection
@@ -39,7 +40,8 @@ constructor(
    private val viewStore: StatusBarNotificationIconViewStore,
) {
    fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle {
        return view.repeatWhenAttached {
        return traceSection("NICStatusBar#bindWhileAttached") {
            view.repeatWhenAttached {
                lifecycleScope.launch {
                    NotificationIconContainerViewBinder.bind(
                        view = view,
@@ -53,6 +55,7 @@ constructor(
            }
        }
    }
}

/** [IconViewStore] for the status bar. */
class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
+79 −69
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import android.widget.FrameLayout
import androidx.annotation.ColorInt
import androidx.collection.ArrayMap
import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.traceSection
import com.android.internal.R as RInternal
import com.android.internal.statusbar.StatusBarIcon
import com.android.internal.util.ContrastColorUtil
import com.android.systemui.common.ui.ConfigurationState
@@ -48,8 +50,10 @@ import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/** Binds a view-model to a [NotificationIconContainer]. */
@@ -65,8 +69,8 @@ object NotificationIconContainerViewBinder {
    ): Unit = coroutineScope {
        launch {
            val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
            val iconColors: Flow<NotificationIconColors> =
                viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) }
            val iconColors: StateFlow<NotificationIconColors> =
                viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) }.stateIn(this)
            viewModel.icons.bindIcons(
                view,
                configuration,
@@ -111,6 +115,14 @@ object NotificationIconContainerViewBinder {
    ): Unit = coroutineScope {
        view.setUseIncreasedIconScale(true)
        launch {
            // Collect state shared across all icon views, so that we are not duplicating collects
            // for each individual icon.
            val color: StateFlow<Int> =
                configuration
                    .getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR)
                    .stateIn(this)
            val tintAlpha = viewModel.tintAlpha.stateIn(this)
            val animsEnabled = viewModel.areIconAnimationsEnabled.stateIn(this)
            viewModel.icons.bindIcons(
                view,
                configuration,
@@ -118,29 +130,15 @@ object NotificationIconContainerViewBinder {
                notifyBindingFailures = { failureTracker.aodFailures = it },
                viewStore,
            ) { _, sbiv ->
                viewModel.bindAodStatusBarIconView(sbiv, configuration)
            }
        }
        launch { viewModel.areContainerChangesAnimated.bindAnimationsEnabled(view) }
    }

    private suspend fun NotificationIconContainerAlwaysOnDisplayViewModel.bindAodStatusBarIconView(
        sbiv: StatusBarIconView,
        configuration: ConfigurationState,
    ) {
                coroutineScope {
            launch {
                val color: Flow<Int> =
                    configuration.getColorAttr(
                        R.attr.wallpaperTextColor,
                        DEFAULT_AOD_ICON_COLOR,
                    )
                StatusBarIconViewBinder.bindColor(sbiv, color)
            }
                    launch { StatusBarIconViewBinder.bindColor(sbiv, color) }
                    launch { StatusBarIconViewBinder.bindTintAlpha(sbiv, tintAlpha) }
            launch { StatusBarIconViewBinder.bindAnimationsEnabled(sbiv, areIconAnimationsEnabled) }
                    launch { StatusBarIconViewBinder.bindAnimationsEnabled(sbiv, animsEnabled) }
                }
            }
        }
        launch { viewModel.areContainerChangesAnimated.bindAnimationsEnabled(view) }
    }

    /** Binds to [NotificationIconContainer.setAnimationsEnabled] */
    private suspend fun Flow<Boolean>.bindAnimationsEnabled(view: NotificationIconContainer) {
@@ -184,11 +182,9 @@ object NotificationIconContainerViewBinder {
        notifyBindingFailures: (Collection<String>) -> Unit,
        viewStore: IconViewStore,
        bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit = { _, _ -> },
    ) {
    ): Unit = coroutineScope {
        val iconSizeFlow: Flow<Int> =
            configuration.getDimensionPixelSize(
                com.android.internal.R.dimen.status_bar_icon_size_sp,
            )
            configuration.getDimensionPixelSize(RInternal.dimen.status_bar_icon_size_sp)
        val iconHorizontalPaddingFlow: Flow<Int> =
            configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
        val layoutParams: Flow<FrameLayout.LayoutParams> =
@@ -199,6 +195,7 @@ object NotificationIconContainerViewBinder {
                    ->
                    FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight)
                }
                .stateIn(this)
        try {
            bindIcons(view, layoutParams, notifyBindingFailures, viewStore, bindIcon)
        } finally {
@@ -217,7 +214,7 @@ object NotificationIconContainerViewBinder {
        val failedBindings = mutableSetOf<String>()
        val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>()
        var prevIcons = NotificationIconsViewData()
        collect { iconsData: NotificationIconsViewData ->
        collectTracingEach("NotifIconContainer#bindIcons") { iconsData: NotificationIconsViewData ->
            val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
            prevIcons = iconsData

@@ -231,9 +228,11 @@ object NotificationIconContainerViewBinder {
                for (notifKey in iconsDiff.removed) {
                    failedBindings.remove(notifKey)
                    val (child, job) = boundViewsByNotifKey.remove(notifKey) ?: continue
                    traceSection("removeIcon") {
                        view.removeView(child)
                        job.cancel()
                    }
                }

                // Add and bind.
                val toAdd: Sequence<String> = iconsDiff.added.asSequence() + failedBindings.toList()
@@ -245,12 +244,16 @@ object NotificationIconContainerViewBinder {
                        continue
                    }
                    failedBindings.remove(notifKey)
                    traceSection("addIcon") {
                        (sbiv.parent as? ViewGroup)?.run {
                            if (this !== view) {
                            Log.wtf(TAG, "StatusBarIconView($notifKey) has an unexpected parent")
                                Log.wtf(
                                    TAG,
                                    "StatusBarIconView($notifKey) has an unexpected parent",
                                )
                            }
                        // If the container was re-inflated and re-bound, then SBIVs might still be
                        // attached to the prior view.
                            // If the container was re-inflated and re-bound, then SBIVs might still
                            // be attached to the prior view.
                            removeView(sbiv)
                            // The view might still be transiently added if it was just removed and
                            // added again.
@@ -267,16 +270,17 @@ object NotificationIconContainerViewBinder {
                                },
                            )
                    }
                }

                // Set the maximum number of icons to show in the container. Any icons over this
                // amount will render as an "overflow dot".
                val maxIconsAmount: Int =
                    when (iconsData.limitType) {
                        LimitType.MaximumIndex -> {
                            iconsData.visibleIcons
                                .asSequence()
                                .take(iconsData.iconLimit)
                                .count { info -> info.notifKey in boundViewsByNotifKey }
                            iconsData.visibleIcons.asSequence().take(iconsData.iconLimit).count {
                                info ->
                                info.notifKey in boundViewsByNotifKey
                            }
                        }
                        LimitType.MaximumAmount -> {
                            iconsData.iconLimit
@@ -289,6 +293,7 @@ object NotificationIconContainerViewBinder {

                // Re-sort notification icons
                view.changeViewPositions {
                    traceSection("re-sort") {
                        val expectedChildren: List<StatusBarIconView> =
                            iconsData.visibleIcons.mapNotNull {
                                boundViewsByNotifKey[it.notifKey]?.first
@@ -307,6 +312,7 @@ object NotificationIconContainerViewBinder {
                }
            }
        }
    }

    /**
     * Track which groups are being replaced with a different icon instance, but with the same
@@ -362,3 +368,7 @@ private val View.viewBounds: Rect
            /* bottom = */ top + height,
        )
    }

private suspend fun <T> Flow<T>.collectTracingEach(tag: String, collector: (T) -> Unit) {
    collect { traceSection(tag) { collector(it) } }
}
Loading