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

Commit 71a0ef16 authored by Ahmed Mehfooz's avatar Ahmed Mehfooz Committed by Android (Google) Code Review
Browse files

Merge "[SB][Chips] Add OngoingActivityChipsRefiner" into main

parents a9c30383 2b63337b
Loading
Loading
Loading
Loading
+121 −0
Original line number Diff line number Diff line
@@ -29,6 +29,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
@@ -50,6 +53,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
@@ -1954,6 +1958,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() }