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

Commit 2b63337b authored by amehfooz's avatar amehfooz
Browse files

[SB][Chips] Add OngoingActivityChipsRefiner

This CL adds a refiner feature for RON chips. An
OngoingActivityChipsRefiner takes the entire set of chips produced by
the OngoingActivityChipsViewModel and can apply a transform on them to
produce a modified set of chips.

This change itself does not result in
any change in behavior. Specific refiners that actually change chips
behavior will be added as part of new features.

Bug: 413216082
Test: OngoingActivityChipsViewModelTest
Flag: EXEMPT because this change doesn't have any impact on
chip behavior.

Change-Id: I8cb8542cdf0bdf3050c6a31b39fdcdd9f56aa65b
parent 906bc5ea
Loading
Loading
Loading
Loading
+121 −0
Original line number Diff line number Diff line
@@ -28,6 +28,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.display.data.repository.displayStateRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
@@ -49,6 +52,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip
import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModelLegacy
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -1798,6 +1802,123 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
            assertThat(latest).isEqualTo(OngoingActivityChipModel.Inactive(shouldAnimate = false))
        }

    @Test
    @EnableChipsModernization
    fun chips_singleRefiner_hidesSpecificChip() =
        kosmos.runTest {
            chipsRefinerSet.add(RemoveChipRefiner(ShareToAppChipViewModel.KEY))

            val latestChips by collectLastValue(underTest.chips)

            val callNotificationKey = "call"
            addOngoingCallState(key = callNotificationKey, isAppVisible = false)
            mediaProjectionState.value =
                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)

            val activeChips = latestChips?.active

            assertThat(activeChips?.size).isEqualTo(1)
            assertThat(activeChips?.any { it.key == ShareToAppChipViewModel.KEY }).isFalse()
            assertThat(activeChips?.any { it.key.startsWith(CallChipViewModel.KEY_PREFIX) })
                .isTrue()

            val inactiveChips = latestChips?.inactive
            assertThat(
                    inactiveChips?.any {
                        it == OngoingActivityChipModel.Inactive(shouldAnimate = false)
                    }
                )
                .isTrue()
        }

    @Test
    @EnableChipsModernization
    fun chips_multipleRefiners_bothApplied() =
        kosmos.runTest {
            chipsRefinerSet.add(RemoveChipRefiner(ShareToAppChipViewModel.KEY))
            val changeFirstChipIconRefiner = ChangeFirstChipIconRefiner()
            chipsRefinerSet.add(changeFirstChipIconRefiner)

            val latestChips by collectLastValue(underTest.chips)

            val callNotificationKey = "callChip"
            addOngoingCallState(key = callNotificationKey, isAppVisible = false)
            mediaProjectionState.value =
                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
            screenRecordState.value = ScreenRecordModel.DoingNothing

            val activeChips = latestChips?.active
            assertThat(activeChips).isNotNull()
            assertThat(activeChips).isNotEmpty()

            assertThat(activeChips?.any { it.key == ShareToAppChipViewModel.KEY }).isFalse()

            val firstChip = activeChips?.firstOrNull()
            assertThat(firstChip!!.key).startsWith(CallChipViewModel.KEY_PREFIX)

            val icon = firstChip.icon
            assertThat(icon)
                .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
            val singleColorIcon = icon as OngoingActivityChipModel.ChipIcon.SingleColorIcon
            val resourceIcon = singleColorIcon.impl as Icon.Resource
            assertThat(resourceIcon.res).isEqualTo(changeFirstChipIconRefiner.newIconRes)
            assertThat(resourceIcon.contentDescription?.loadContentDescription(context))
                .isEqualTo(changeFirstChipIconRefiner.newIconDesc)
        }

    companion object {
        class RemoveChipRefiner(private val keyToRemove: String) : OngoingActivityChipsRefiner {
            override fun transform(
                input: MultipleOngoingActivityChipsModel
            ): MultipleOngoingActivityChipsModel {
                val chipWasInActive = input.active.any { it.key == keyToRemove }
                val chipWasInOverflow = input.overflow.any { it.key == keyToRemove }

                val newActive = input.active.filterNot { it.key == keyToRemove }
                val newOverflow = input.overflow.filterNot { it.key == keyToRemove }

                val currentInactive = input.inactive.toMutableList()
                if (chipWasInActive || chipWasInOverflow) {
                    // Add an Inactive model if the chip key was found in active or overflow lists.
                    // This signifies that the refiner made this chip inactive.
                    currentInactive.add(OngoingActivityChipModel.Inactive(shouldAnimate = false))
                }

                return input.copy(
                    active = newActive,
                    overflow = newOverflow,
                    inactive = currentInactive.toList(),
                )
            }
        }

        class ChangeFirstChipIconRefiner : OngoingActivityChipsRefiner {
            val newIconRes = android.R.drawable.ic_dialog_info // A standard Android resource
            val newIconDesc = "Test Changed Icon"

            override fun transform(
                input: MultipleOngoingActivityChipsModel
            ): MultipleOngoingActivityChipsModel {
                if (input.active.isNotEmpty()) {
                    val firstChip = input.active.first()
                    val modifiedFirstChip =
                        firstChip.copy(
                            icon =
                                OngoingActivityChipModel.ChipIcon.SingleColorIcon(
                                    Icon.Resource(
                                        newIconRes,
                                        ContentDescription.Loaded(newIconDesc),
                                    )
                                )
                        )
                    val newActive = listOf(modifiedFirstChip) + input.active.drop(1)
                    return input.copy(active = newActive)
                }
                return input
            }
        }
    }

    private fun setNotifs(notifs: List<ActiveNotificationModel>) {
        activeNotificationListRepository.activeNotifications.value =
            ActiveNotificationsStore.Builder()
+6 −0
Original line number Diff line number Diff line
@@ -21,12 +21,14 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsRefiner
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.Multibinds

@Module
abstract class StatusBarChipsModule {
@@ -52,4 +54,8 @@ abstract class StatusBarChipsModule {
            }
        }
    }

    /** Provides the base empty set for [OngoingActivityChipsRefiner]. */
    @Multibinds
    abstract fun provideOngoingActivityChipRefinerSet(): Set<OngoingActivityChipsRefiner>
}
+28 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.chips.ui.viewmodel

import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel

/**
 * An [OngoingActivityChipsRefiner] takes the entire set of chips produced by the
 * [OngoingActivityChipsViewModel] and can apply a transform on them to produce a modified set of
 * chips.
 */
interface OngoingActivityChipsRefiner {
    fun transform(input: MultipleOngoingActivityChipsModel): MultipleOngoingActivityChipsModel
}
+40 −31
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ constructor(
    callChipViewModel: CallChipViewModel,
    notifChipsViewModel: NotifChipsViewModel,
    displayStateInteractor: DisplayStateInteractor,
    private val chipsRefiners: Set<@JvmSuppressWildcards OngoingActivityChipsRefiner>,
    @StatusBarChipsLog private val logger: LogBuffer,
) {
    private enum class ChipType {
@@ -255,7 +256,7 @@ constructor(
     * A flow modeling the active and inactive chips as well as which should be shown in the status
     * bar after accounting for possibly multiple ongoing activities and animation requirements.
     */
    val chips: StateFlow<MultipleOngoingActivityChipsModel> =
    private val unrefinedChips =
        if (StatusBarChipsModernization.isEnabled) {
            combine(
                incomingChipBundle.map { bundle -> rankChips(bundle) },
@@ -289,11 +290,19 @@ constructor(
                    rankedChips
                }
            }
                .stateIn(scope, SharingStarted.Lazily, MultipleOngoingActivityChipsModel())
        } else {
            MutableStateFlow(MultipleOngoingActivityChipsModel()).asStateFlow()
            MutableStateFlow(MultipleOngoingActivityChipsModel())
        }

    val chips: StateFlow<MultipleOngoingActivityChipsModel> =
        unrefinedChips
            .map { unrefinedChips ->
                chipsRefiners.fold(unrefinedChips) { currentOutput, refiner ->
                    refiner.transform(currentOutput)
                }
            }
            .stateIn(scope, SharingStarted.Lazily, MultipleOngoingActivityChipsModel())

    /**
     * A flow modeling the primary chip that should be shown in the status bar after accounting for
     * possibly multiple ongoing activities and animation requirements.
+4 −0
Original line number Diff line number Diff line
@@ -36,6 +36,10 @@ val Kosmos.ongoingActivityChipsViewModel: OngoingActivityChipsViewModel by
            callChipViewModel = callChipViewModel,
            notifChipsViewModel = notifChipsViewModel,
            displayStateInteractor = displayStateInteractor,
            chipsRefiners = chipsRefinerSet,
            logger = statusBarChipsLogger,
        )
    }

val Kosmos.chipsRefinerSet: MutableSet<OngoingActivityChipsRefiner> by
    Kosmos.Fixture { mutableSetOf() }