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

Commit 4bd9ef2a authored by Will Leshner's avatar Will Leshner
Browse files

Don't allow UMO to shrink to 1/3 size.

The current UMO really only supports two sizes: full-size and half-size.
Anything smaller causes controls within the UMO to overlap.

Bug: 360756056
Test: atest CommunalInteractorTest
Test: atest CommunalViewModelTest
Flag: EXEMPT bugfix

Change-Id: Ia0e39b16cfa0b1eccabcb87d9f11fc4df170621b
parent 35c5d358
Loading
Loading
Loading
Loading
+46 −58
Original line number Diff line number Diff line
@@ -244,10 +244,7 @@ class CommunalInteractorTest : SysuiTestCase() {

            val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
            userRepository.setUserInfos(userInfos)
            userTracker.set(
                userInfos = userInfos,
                selectedUserIndex = 0,
            )
            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
            runCurrent()

            // Widgets available.
@@ -267,21 +264,14 @@ class CommunalInteractorTest : SysuiTestCase() {
    fun smartspaceDynamicSizing_oneCard_fullSize() =
        testSmartspaceDynamicSizing(
            totalTargets = 1,
            expectedSizes =
                listOf(
                    CommunalContentSize.FULL,
                )
            expectedSizes = listOf(CommunalContentSize.FULL),
        )

    @Test
    fun smartspace_dynamicSizing_twoCards_halfSize() =
        testSmartspaceDynamicSizing(
            totalTargets = 2,
            expectedSizes =
                listOf(
                    CommunalContentSize.HALF,
                    CommunalContentSize.HALF,
                )
            expectedSizes = listOf(CommunalContentSize.HALF, CommunalContentSize.HALF),
        )

    @Test
@@ -293,34 +283,34 @@ class CommunalInteractorTest : SysuiTestCase() {
                    CommunalContentSize.THIRD,
                    CommunalContentSize.THIRD,
                    CommunalContentSize.THIRD,
                )
                ),
        )

    @Test
    fun smartspace_dynamicSizing_fourCards_oneFullAndThreeThirdSize() =
    fun smartspace_dynamicSizing_fourCards_threeThirdSizeAndOneFullSize() =
        testSmartspaceDynamicSizing(
            totalTargets = 4,
            expectedSizes =
                listOf(
                    CommunalContentSize.FULL,
                    CommunalContentSize.THIRD,
                    CommunalContentSize.THIRD,
                    CommunalContentSize.THIRD,
                )
                    CommunalContentSize.FULL,
                ),
        )

    @Test
    fun smartspace_dynamicSizing_fiveCards_twoHalfAndThreeThirdSize() =
    fun smartspace_dynamicSizing_fiveCards_threeThirdAndTwoHalfSize() =
        testSmartspaceDynamicSizing(
            totalTargets = 5,
            expectedSizes =
                listOf(
                    CommunalContentSize.HALF,
                    CommunalContentSize.HALF,
                    CommunalContentSize.THIRD,
                    CommunalContentSize.THIRD,
                    CommunalContentSize.THIRD,
                )
                    CommunalContentSize.HALF,
                    CommunalContentSize.HALF,
                ),
        )

    @Test
@@ -335,7 +325,7 @@ class CommunalInteractorTest : SysuiTestCase() {
                    CommunalContentSize.THIRD,
                    CommunalContentSize.THIRD,
                    CommunalContentSize.THIRD,
                )
                ),
        )

    private fun testSmartspaceDynamicSizing(
@@ -355,7 +345,7 @@ class CommunalInteractorTest : SysuiTestCase() {

            smartspaceRepository.setTimers(targets)

            val smartspaceContent by collectLastValue(underTest.ongoingContent)
            val smartspaceContent by collectLastValue(underTest.ongoingContent(false))
            assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
            for (index in 0 until totalTargets) {
                assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
@@ -371,13 +361,26 @@ class CommunalInteractorTest : SysuiTestCase() {
            // Media is playing.
            mediaRepository.mediaActive()

            val umoContent by collectLastValue(underTest.ongoingContent)
            val umoContent by collectLastValue(underTest.ongoingContent(true))

            assertThat(umoContent?.size).isEqualTo(1)
            assertThat(umoContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
            assertThat(umoContent?.get(0)?.key).isEqualTo(CommunalContentModel.KEY.umo())
        }

    @Test
    fun umo_mediaPlaying_mediaHostNotVisible_hidesUmo() =
        testScope.runTest {
            // Tutorial completed.
            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)

            // Media is playing.
            mediaRepository.mediaActive()

            val umoContent by collectLastValue(underTest.ongoingContent(false))
            assertThat(umoContent?.size).isEqualTo(0)
        }

    @Test
    fun ongoing_shouldOrderAndSizeByTimestamp() =
        testScope.runTest {
@@ -401,19 +404,19 @@ class CommunalInteractorTest : SysuiTestCase() {
            val timer3 = smartspaceTimer("timer3", timestamp = 4L)
            smartspaceRepository.setTimers(listOf(timer1, timer2, timer3))

            val ongoingContent by collectLastValue(underTest.ongoingContent)
            val ongoingContent by collectLastValue(underTest.ongoingContent(true))
            assertThat(ongoingContent?.size).isEqualTo(4)
            assertThat(ongoingContent?.get(0)?.key)
                .isEqualTo(CommunalContentModel.KEY.smartspace("timer3"))
            assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.FULL)
            assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.HALF)
            assertThat(ongoingContent?.get(1)?.key)
                .isEqualTo(CommunalContentModel.KEY.smartspace("timer2"))
            assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.THIRD)
            assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.HALF)
            assertThat(ongoingContent?.get(2)?.key).isEqualTo(CommunalContentModel.KEY.umo())
            assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.THIRD)
            assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.HALF)
            assertThat(ongoingContent?.get(3)?.key)
                .isEqualTo(CommunalContentModel.KEY.smartspace("timer1"))
            assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.THIRD)
            assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.HALF)
        }

    @Test
@@ -435,10 +438,7 @@ class CommunalInteractorTest : SysuiTestCase() {
        testScope.runTest {
            // Set to main user, so we can dismiss the tile for the main user.
            val user = userRepository.asMainUser()
            userTracker.set(
                userInfos = listOf(user),
                selectedUserIndex = 0,
            )
            userTracker.set(userInfos = listOf(user), selectedUserIndex = 0)
            runCurrent()

            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
@@ -816,10 +816,7 @@ class CommunalInteractorTest : SysuiTestCase() {
            // Only main user exists.
            val userInfos = listOf(MAIN_USER_INFO)
            userRepository.setUserInfos(userInfos)
            userTracker.set(
                userInfos = userInfos,
                selectedUserIndex = 0,
            )
            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
            runCurrent()

            val widgetContent by collectLastValue(underTest.widgetContent)
@@ -853,10 +850,7 @@ class CommunalInteractorTest : SysuiTestCase() {
            // Work profile is set up.
            val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
            userRepository.setUserInfos(userInfos)
            userTracker.set(
                userInfos = userInfos,
                selectedUserIndex = 0,
            )
            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
            runCurrent()

            // When work profile is paused.
@@ -899,10 +893,7 @@ class CommunalInteractorTest : SysuiTestCase() {

            val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
            userRepository.setUserInfos(userInfos)
            userTracker.set(
                userInfos = userInfos,
                selectedUserIndex = 0,
            )
            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
            runCurrent()

@@ -914,7 +905,7 @@ class CommunalInteractorTest : SysuiTestCase() {

            setKeyguardFeaturesDisabled(
                USER_INFO_WORK,
                DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
                DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL,
            )

            // Widgets under work profile are filtered out. Only the regular widget remains.
@@ -932,10 +923,7 @@ class CommunalInteractorTest : SysuiTestCase() {

            val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
            userRepository.setUserInfos(userInfos)
            userTracker.set(
                userInfos = userInfos,
                selectedUserIndex = 0,
            )
            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
            runCurrent()

@@ -947,7 +935,7 @@ class CommunalInteractorTest : SysuiTestCase() {

            setKeyguardFeaturesDisabled(
                USER_INFO_WORK,
                DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
                DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE,
            )

            // Widgets under work profile are available.
@@ -967,7 +955,7 @@ class CommunalInteractorTest : SysuiTestCase() {
            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.GLANCEABLE_HUB,
                to = KeyguardState.OCCLUDED,
                testScope
                testScope,
            )

            assertThat(showCommunalFromOccluded).isTrue()
@@ -983,7 +971,7 @@ class CommunalInteractorTest : SysuiTestCase() {
            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.LOCKSCREEN,
                to = KeyguardState.OCCLUDED,
                testScope
                testScope,
            )

            assertThat(showCommunalFromOccluded).isFalse()
@@ -999,7 +987,7 @@ class CommunalInteractorTest : SysuiTestCase() {
            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.GLANCEABLE_HUB,
                to = KeyguardState.OCCLUDED,
                testScope
                testScope,
            )
            runCurrent()
            kosmos.setCommunalAvailable(false)
@@ -1017,13 +1005,13 @@ class CommunalInteractorTest : SysuiTestCase() {
            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.GLANCEABLE_HUB,
                to = KeyguardState.OCCLUDED,
                testScope
                testScope,
            )
            runCurrent()
            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.OCCLUDED,
                to = KeyguardState.PRIMARY_BOUNCER,
                testScope
                testScope,
            )

            assertThat(showCommunalFromOccluded).isTrue()
@@ -1039,7 +1027,7 @@ class CommunalInteractorTest : SysuiTestCase() {
            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.DREAMING,
                to = KeyguardState.OCCLUDED,
                testScope
                testScope,
            )

            assertThat(showCommunalFromOccluded).isTrue()
@@ -1049,7 +1037,7 @@ class CommunalInteractorTest : SysuiTestCase() {
        return CommunalSmartspaceTimer(
            smartspaceTargetId = id,
            createdTimestampMillis = timestamp,
            remoteViews = mock(RemoteViews::class.java)
            remoteViews = mock(RemoteViews::class.java),
        )
    }

+93 −14
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import com.android.systemui.communal.domain.interactor.communalSettingsInteracto
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
@@ -150,10 +151,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
        mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)

        kosmos.fakeUserTracker.set(
            userInfos = listOf(MAIN_USER_INFO),
            selectedUserIndex = 0,
        )
        kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
        whenever(mediaHost.visible).thenReturn(true)

        kosmos.powerInteractor.setAwakeForTest()
@@ -248,6 +246,87 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
        }

    @Test
    fun ongoingContent_umoAndOneTimer_sizedAppropriately() =
        testScope.runTest {
            // Widgets available.
            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
            widgetRepository.addWidget(appWidgetId = 1, rank = 20)

            // Smartspace available.
            smartspaceRepository.setTimers(
                listOf(
                    CommunalSmartspaceTimer(
                        smartspaceTargetId = "target",
                        createdTimestampMillis = 0L,
                        remoteViews = Mockito.mock(RemoteViews::class.java),
                    )
                )
            )

            // Media playing.
            mediaRepository.mediaActive()

            val communalContent by collectLastValue(underTest.communalContent)

            // One timer, UMO, two widgets, and cta.
            assertThat(communalContent?.size).isEqualTo(5)

            val timer = communalContent?.get(0)
            val umo = communalContent?.get(1)

            assertThat(timer).isInstanceOf(CommunalContentModel.Smartspace::class.java)
            assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java)

            assertThat(timer?.size).isEqualTo(CommunalContentSize.HALF)
            assertThat(umo?.size).isEqualTo(CommunalContentSize.HALF)
        }

    @Test
    fun ongoingContent_umoAndTwoTimers_sizedAppropriately() =
        testScope.runTest {
            // Widgets available.
            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
            widgetRepository.addWidget(appWidgetId = 1, rank = 20)

            // Smartspace available.
            smartspaceRepository.setTimers(
                listOf(
                    CommunalSmartspaceTimer(
                        smartspaceTargetId = "target",
                        createdTimestampMillis = 0L,
                        remoteViews = Mockito.mock(RemoteViews::class.java),
                    ),
                    CommunalSmartspaceTimer(
                        smartspaceTargetId = "target",
                        createdTimestampMillis = 0L,
                        remoteViews = Mockito.mock(RemoteViews::class.java),
                    ),
                )
            )

            // Media playing.
            mediaRepository.mediaActive()

            val communalContent by collectLastValue(underTest.communalContent)

            // Two timers, UMO, two widgets, and cta.
            assertThat(communalContent?.size).isEqualTo(6)

            val timer1 = communalContent?.get(0)
            val timer2 = communalContent?.get(1)
            val umo = communalContent?.get(2)

            assertThat(timer1).isInstanceOf(CommunalContentModel.Smartspace::class.java)
            assertThat(timer2).isInstanceOf(CommunalContentModel.Smartspace::class.java)
            assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java)

            // One full-sized timer and a half-sized timer and half-sized UMO.
            assertThat(timer1?.size).isEqualTo(CommunalContentSize.HALF)
            assertThat(timer2?.size).isEqualTo(CommunalContentSize.HALF)
            assertThat(umo?.size).isEqualTo(CommunalContentSize.FULL)
        }

    @Test
    fun communalContent_mediaHostVisible_umoIncluded() =
        testScope.runTest {
@@ -497,7 +576,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                    TransitionStep(
                        from = KeyguardState.LOCKSCREEN,
                        to = KeyguardState.GLANCEABLE_HUB,
                    )
                    ),
            )
            // Shade not expanded.
            if (!SceneContainerFlag.isEnabled) shadeTestUtil.setLockscreenShadeExpansion(0f)
@@ -550,8 +629,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                stateTransition =
                    TransitionStep(
                        from = KeyguardState.DREAMING,
                        to = KeyguardState.GLANCEABLE_HUB,
                    )
                        to = KeyguardState.GLANCEABLE_HUB
                    ),
            )

            // Then flow is not frozen
@@ -570,8 +649,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                stateTransition =
                    TransitionStep(
                        from = KeyguardState.GLANCEABLE_HUB,
                        to = KeyguardState.OCCLUDED,
                    )
                        to = KeyguardState.OCCLUDED
                    ),
            )

            // Then flow is not frozen
@@ -595,7 +674,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                    TransitionStep(
                        from = KeyguardState.LOCKSCREEN,
                        to = KeyguardState.GLANCEABLE_HUB,
                    )
                    ),
            )

            // Then flow is not frozen
@@ -614,7 +693,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                        to = KeyguardState.OCCLUDED,
                        transitionState = TransitionState.STARTED,
                        value = 0f,
                    )
                    ),
            )

            // Then flow is frozen
@@ -629,7 +708,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                        to = KeyguardState.OCCLUDED,
                        transitionState = TransitionState.FINISHED,
                        value = 1f,
                    )
                    ),
            )

            // Then flow is not frozen
@@ -658,8 +737,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                stateTransition =
                    TransitionStep(
                        from = KeyguardState.DREAMING,
                        to = KeyguardState.GLANCEABLE_HUB,
                    )
                        to = KeyguardState.GLANCEABLE_HUB
                    ),
            )

            // Widgets available
+49 −44
Original line number Diff line number Diff line
@@ -117,7 +117,7 @@ constructor(
    sceneInteractor: SceneInteractor,
    @CommunalLog logBuffer: LogBuffer,
    @CommunalTableLog tableLogBuffer: TableLogBuffer,
    private val managedProfileController: ManagedProfileController
    private val managedProfileController: ManagedProfileController,
) {
    private val logger = Logger(logBuffer, "CommunalInteractor")

@@ -154,7 +154,7 @@ constructor(
        allOf(
                communalSettingsInteractor.isCommunalEnabled,
                not(keyguardInteractor.isEncryptedOrLockdown),
                keyguardInteractor.isKeyguardShowing
                keyguardInteractor.isKeyguardShowing,
            )
            .distinctUntilChanged()
            .onEach { available ->
@@ -342,7 +342,7 @@ constructor(
    fun changeScene(
        newScene: SceneKey,
        loggingReason: String,
        transitionKey: TransitionKey? = null
        transitionKey: TransitionKey? = null,
    ) = communalSceneInteractor.changeScene(newScene, loggingReason, transitionKey)

    fun setEditModeOpen(isOpen: Boolean) {
@@ -354,9 +354,7 @@ constructor(
    }

    /** Show the widget editor Activity. */
    fun showWidgetEditor(
        shouldOpenWidgetPickerOnStart: Boolean = false,
    ) {
    fun showWidgetEditor(shouldOpenWidgetPickerOnStart: Boolean = false) {
        communalSceneInteractor.setEditModeState(EditModeState.STARTING)
        editWidgetsActivityStarter.startActivity(shouldOpenWidgetPickerOnStart)
    }
@@ -419,7 +417,7 @@ constructor(
                    IntentFilter().apply {
                        addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
                        addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
                    },
                    }
            )
            .emitOnStart()

@@ -450,7 +448,7 @@ constructor(
                            rank = widget.rank,
                            providerInfo = widget.providerInfo,
                            appWidgetHost = appWidgetHost,
                            inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
                            inQuietMode = isQuietModeEnabled(widget.providerInfo.profile),
                        )
                    }
                    is CommunalWidgetContentModel.Pending -> {
@@ -468,7 +466,7 @@ constructor(
    /** Filter widgets based on whether their associated profile is allowed by device policy. */
    private fun filterWidgetsAllowedByDevicePolicy(
        list: List<CommunalWidgetContentModel>,
        disallowedByDevicePolicyUser: UserInfo?
        disallowedByDevicePolicyUser: UserInfo?,
    ): List<CommunalWidgetContentModel> =
        if (disallowedByDevicePolicyUser == null) {
            list
@@ -507,7 +505,7 @@ constructor(
     * A flow of ongoing content, including smartspace timers and umo, ordered by creation time and
     * sized dynamically.
     */
    val ongoingContent: Flow<List<CommunalContentModel.Ongoing>> =
    fun ongoingContent(isMediaHostVisible: Boolean): Flow<List<CommunalContentModel.Ongoing>> =
        combine(smartspaceRepository.timers, mediaRepository.mediaModel) { timers, media ->
                val ongoingContent = mutableListOf<CommunalContentModel.Ongoing>()

@@ -523,22 +521,20 @@ constructor(
                )

                // Add UMO
                if (media.hasAnyMediaOrRecommendation) {
                if (isMediaHostVisible && media.hasAnyMediaOrRecommendation) {
                    ongoingContent.add(
                        CommunalContentModel.Umo(
                            createdTimestampMillis = media.createdTimestampMillis,
                            createdTimestampMillis = media.createdTimestampMillis
                        )
                    )
                }

                // Order by creation time descending
                // Order by creation time descending.
                ongoingContent.sortByDescending { it.createdTimestampMillis }
                // Resize the items.
                ongoingContent.resizeItems()

                // Dynamic sizing
                ongoingContent.forEachIndexed { index, model ->
                    model.size = dynamicContentSize(ongoingContent.size, index)
                }

                // Return the sorted and resized items.
                ongoingContent
            }
            .flowOn(bgDispatcher)
@@ -548,7 +544,7 @@ constructor(
     * stale data following user deletion.
     */
    private fun filterWidgetsByExistingUsers(
        list: List<CommunalWidgetContentModel>,
        list: List<CommunalWidgetContentModel>
    ): List<CommunalWidgetContentModel> {
        val currentUserIds = userTracker.userProfiles.map { it.id }.toSet()
        return list.filter { widget ->
@@ -560,6 +556,40 @@ constructor(
        }
    }

    // Dynamically resizes the height of items in the list of ongoing items such that they fit in
    // columns in as compact a space as possible.
    //
    // Currently there are three possible sizes. When the total number is 1, size for that  content
    // is [FULL], when the total number is 2, size for each is [HALF], and 3, size for  each is
    // [THIRD].
    //
    // This algorithm also respects each item's minimum size. All items in a column will have the
    // same size, and all items in a column will be no smaller than any item's minimum size.
    private fun List<CommunalContentModel.Ongoing>.resizeItems() {
        fun resizeColumn(c: List<CommunalContentModel.Ongoing>) {
            if (c.isEmpty()) return
            val newSize = CommunalContentSize.toSize(span = FULL.span / c.size)
            c.forEach { item -> item.size = newSize }
        }

        val column = mutableListOf<CommunalContentModel.Ongoing>()
        var available = FULL.span

        forEach { item ->
            if (available < item.minSize.span) {
                resizeColumn(column)
                column.clear()
                available = FULL.span
            }

            column.add(item)
            available -= item.minSize.span
        }

        // Make sure to resize the final column.
        resizeColumn(column)
    }

    companion object {
        const val TAG = "CommunalInteractor"

@@ -574,31 +604,6 @@ constructor(
         * of -1 means that the user's chosen screen timeout will be used instead.
         */
        const val AWAKE_INTERVAL_MS = -1

        /**
         * Calculates the content size dynamically based on the total number of contents of that
         * type.
         *
         * Contents with the same type are expected to fill each column evenly. Currently there are
         * three possible sizes. When the total number is 1, size for that content is [FULL], when
         * the total number is 2, size for each is [HALF], and 3, size for each is [THIRD].
         *
         * When dynamic contents fill in multiple columns, the first column follows the algorithm
         * above, and the remaining contents are packed in [THIRD]s. For example, when the total
         * number if 4, the first one is [FULL], filling the column, and the remaining 3 are
         * [THIRD].
         *
         * @param size The total number of contents of this type.
         * @param index The index of the current content of this type.
         */
        private fun dynamicContentSize(size: Int, index: Int): CommunalContentSize {
            val remainder = size % CommunalContentSize.entries.size
            return CommunalContentSize.toSize(
                span =
                    FULL.span /
                        if (index > remainder - 1) CommunalContentSize.entries.size else remainder
            )
        }
    }

    /**
+9 −5

File changed.

Preview size limit exceeded, changes collapsed.

+11 −20

File changed.

Preview size limit exceeded, changes collapsed.