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

Commit 61e3eb3f authored by Matías Hernández's avatar Matías Hernández
Browse files

Make ZenModeRepository.modes a (single) StateFlow

Using regular Flows per caller means that every client of ZenModeRepository/ZenModeInteractor causes a new ContentObserver to be registered (and ZenModesBackend.getModes() to be called on each of those changes). With the current clients of the interactor, this could easily become 12 or more Binder calls per each zen change!

Bug: 403434908
Test: atest ZenModeRepositoryTest + manual
Flag: EXEMPT Simple (and urgent) performance improvement
Change-Id: I428ef6ef03eb331115d53f878e742542963a11a8
parent d4d65b17
Loading
Loading
Loading
Loading
+12 −13
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.content.IntentFilter
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings
import com.android.settingslib.flags.Flags
import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModesBackend
import java.time.Duration
@@ -35,6 +34,7 @@ import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
@@ -72,7 +72,7 @@ class ZenModeRepositoryImpl(
    private val notificationManager: NotificationManager,
    private val backend: ZenModesBackend,
    private val contentResolver: ContentResolver,
    val scope: CoroutineScope,
    val applicationScope: CoroutineScope,
    val backgroundCoroutineContext: CoroutineContext,
    // This is nullable just to simplify testing, since SettingsLib doesn't have a good way
    // to create a fake handler.
@@ -104,7 +104,7 @@ class ZenModeRepositoryImpl(
                awaitClose { context.unregisterReceiver(receiver) }
            }
            .flowOn(backgroundCoroutineContext)
            .shareIn(started = SharingStarted.WhileSubscribed(), scope = scope)
            .shareIn(started = SharingStarted.WhileSubscribed(), scope = applicationScope)
    }

    override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
@@ -129,14 +129,11 @@ class ZenModeRepositoryImpl(
            .map { mapper(it) }
            .onStart { emit(mapper(null)) }
            .flowOn(backgroundCoroutineContext)
            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
            .stateIn(applicationScope, SharingStarted.WhileSubscribed(), null)

    private val zenConfigChanged by lazy {
        if (android.app.Flags.modesUi()) {
            callbackFlow {
                    // emit an initial value
                    trySend(Unit)

                    val observer =
                        object : ContentObserver(backgroundHandler) {
                            override fun onChange(selfChange: Boolean) {
@@ -163,16 +160,18 @@ class ZenModeRepositoryImpl(
        }
    }

    override val modes: Flow<List<ZenMode>> by lazy {
        if (android.app.Flags.modesUi()) {
    override val modes: StateFlow<List<ZenMode>> =
        if (android.app.Flags.modesUi())
            zenConfigChanged
                .map { backend.modes }
                .distinctUntilChanged()
                .flowOn(backgroundCoroutineContext)
        } else {
            flowOf(emptyList())
        }
    }
                .stateIn(
                    scope = applicationScope,
                    started = SharingStarted.Eagerly,
                    initialValue = backend.modes,
                )
        else MutableStateFlow<List<ZenMode>>(emptyList())

    /**
     * Gets the current list of [ZenMode] instances according to the backend.
+7 −3
Original line number Diff line number Diff line
@@ -75,10 +75,14 @@ class ZenModeRepositoryTest {

    private val testScope: TestScope = TestScope()

    private val initialModes = listOf(TestModeBuilder().setId("Built-in").build())

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)

        `when`(zenModesBackend.modes).thenReturn(initialModes)

        underTest =
            ZenModeRepositoryImpl(
                context,
@@ -151,8 +155,8 @@ class ZenModeRepositoryTest {
    fun modesListEmitsOnSettingsChange() {
        testScope.runTest {
            val values = mutableListOf<List<ZenMode>>()
            val modes1 = listOf(TestModeBuilder().setId("One").build())
            `when`(zenModesBackend.modes).thenReturn(modes1)

            // an initial list of modes is read when the stateflow is created
            underTest.modes.onEach { values.add(it) }.launchIn(backgroundScope)
            runCurrent()

@@ -172,7 +176,7 @@ class ZenModeRepositoryTest {
            triggerZenModeSettingUpdate()
            runCurrent()

            assertThat(values).containsExactly(modes1, modes2, modes3).inOrder()
            assertThat(values).containsExactly(initialModes, modes2, modes3).inOrder()
        }
    }

+0 −1
Original line number Diff line number Diff line
@@ -83,7 +83,6 @@ import com.android.systemui.statusbar.notification.logging.dagger.NotificationsL
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
import com.android.systemui.statusbar.notification.row.NotificationActionClickManager;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactory;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactoryLooperImpl;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;