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

Commit 069649d8 authored by Luca Zuccarini's avatar Luca Zuccarini Committed by Android (Google) Code Review
Browse files

Merge changes I2f7a60e7,Ia09a2b34,I7917c395 into main

* changes:
  Merge InCall and InCallWithVisibleApp.
  Extract chip creation outside of the main chip flow.
  Reorganize activity chip so only the chip itself animates.
parents 746327f6 6975150c
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
@@ -46,7 +46,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
@@ -71,7 +70,7 @@ class CallChipViewModelTest : SysuiTestCase() {
    private val mockExpandable: Expandable =
        mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }

    private val underTest by lazy { kosmos.callChipViewModel }
    private val Kosmos.underTest by Kosmos.Fixture { callChipViewModel }

    @Test
    fun chip_noCall_isHidden() =
+17 −8
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
            assertThat(model.intent).isSameInstanceAs(testIntent)
            assertThat(model.notificationKey).isEqualTo(key)
            assertThat(model.promotedContent).isSameInstanceAs(testPromotedContent)
            assertThat(model.isAppVisible).isFalse()
        }

    @Test
@@ -114,15 +115,16 @@ class OngoingCallInteractorTest : SysuiTestCase() {
                promotedContent = testPromotedContent,
            )

            // Verify model is InCallWithVisibleApp and has the correct icon, intent, and promoted
            // content.
            assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
            val model = latest as OngoingCallModel.InCallWithVisibleApp
            // Verify model is InCall with visible app and has the correct icon, intent, and
            // promoted content.
            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
            val model = latest as OngoingCallModel.InCall
            assertThat(model.startTimeMs).isEqualTo(startTimeMs)
            assertThat(model.notificationIconView).isSameInstanceAs(testIconView)
            assertThat(model.intent).isSameInstanceAs(testIntent)
            assertThat(model.notificationKey).isEqualTo(key)
            assertThat(model.promotedContent).isSameInstanceAs(testPromotedContent)
            assertThat(model.isAppVisible).isTrue()
        }

    @Test
@@ -144,7 +146,8 @@ class OngoingCallInteractorTest : SysuiTestCase() {

            addOngoingCallState(uid = UID)

            assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
            assertThat((latest as OngoingCallModel.InCall).isAppVisible).isTrue()
        }

    @Test
@@ -156,6 +159,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
            addOngoingCallState(uid = UID)

            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
            assertThat((latest as OngoingCallModel.InCall).isAppVisible).isFalse()
        }

    @Test
@@ -167,14 +171,17 @@ class OngoingCallInteractorTest : SysuiTestCase() {
            kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
            addOngoingCallState(uid = UID)
            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
            assertThat((latest as OngoingCallModel.InCall).isAppVisible).isFalse()

            // App becomes visible
            kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true)
            assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
            assertThat((latest as OngoingCallModel.InCall).isAppVisible).isTrue()

            // App becomes invisible again
            kosmos.activityManagerRepository.fake.setIsAppVisible(UID, false)
            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
            assertThat((latest as OngoingCallModel.InCall).isAppVisible).isFalse()
        }

    @Test
@@ -243,13 +250,14 @@ class OngoingCallInteractorTest : SysuiTestCase() {
            addOngoingCallState(uid = UID)

            assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
            assertThat((ongoingCallState as OngoingCallModel.InCall).isAppVisible).isFalse()
            assertThat(requiresStatusBarVisibleInRepository).isTrue()
            assertThat(requiresStatusBarVisibleInWindowController).isTrue()

            kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true)

            assertThat(ongoingCallState)
                .isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
            assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
            assertThat((ongoingCallState as OngoingCallModel.InCall).isAppVisible).isTrue()
            assertThat(requiresStatusBarVisibleInRepository).isFalse()
            assertThat(requiresStatusBarVisibleInWindowController).isFalse()
        }
@@ -265,6 +273,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
            addOngoingCallState()

            assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
            assertThat((ongoingCallState as OngoingCallModel.InCall).isAppVisible).isFalse()
            verify(kosmos.swipeStatusBarAwayGestureHandler, never())
                .addOnGestureDetectedCallback(any(), any())
        }
+60 −58
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.statusbar.chips.call.ui.viewmodel

import android.app.PendingIntent
import android.content.Context
import android.view.View
import com.android.internal.jank.Cuj
@@ -63,9 +64,22 @@ constructor(
        interactor.ongoingCallState
            .map { state ->
                when (state) {
                    is OngoingCallModel.NoCall,
                    is OngoingCallModel.InCallWithVisibleApp -> OngoingActivityChipModel.Inactive()
                    is OngoingCallModel.InCall -> {
                    is OngoingCallModel.NoCall -> OngoingActivityChipModel.Inactive()
                    is OngoingCallModel.InCall ->
                        if (state.isAppVisible) {
                            OngoingActivityChipModel.Inactive()
                        } else {
                            prepareChip(state, systemClock)
                        }
                }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Inactive())

    /** Builds an [OngoingActivityChipModel.Active] from all the relevant information. */
    private fun prepareChip(
        state: OngoingCallModel.InCall,
        systemClock: SystemClock,
    ): OngoingActivityChipModel.Active {
        val key = state.notificationKey
        val contentDescription = getContentDescription(state.appName)
        val icon =
@@ -88,38 +102,31 @@ constructor(

        // This block mimics OngoingCallController#updateChip.
        if (state.startTimeMs <= 0L) {
                            // If the start time is invalid, don't show a timer and show just an
                            // icon. See b/192379214.
                            OngoingActivityChipModel.Active.IconOnly(
            // If the start time is invalid, don't show a timer and show just an icon.
            // See b/192379214.
            return OngoingActivityChipModel.Active.IconOnly(
                key = key,
                icon = icon,
                colors = colors,
                                onClickListenerLegacy = getOnClickListener(state),
                                clickBehavior = getClickBehavior(state),
                onClickListenerLegacy = getOnClickListener(state.intent),
                clickBehavior = getClickBehavior(state.intent),
            )
        } else {
            val startTimeInElapsedRealtime =
                                state.startTimeMs - systemClock.currentTimeMillis() +
                                    systemClock.elapsedRealtime()
                            OngoingActivityChipModel.Active.Timer(
                state.startTimeMs - systemClock.currentTimeMillis() + systemClock.elapsedRealtime()
            return OngoingActivityChipModel.Active.Timer(
                key = key,
                icon = icon,
                colors = colors,
                startTimeMs = startTimeInElapsedRealtime,
                                onClickListenerLegacy = getOnClickListener(state),
                                clickBehavior = getClickBehavior(state),
                onClickListenerLegacy = getOnClickListener(state.intent),
                clickBehavior = getClickBehavior(state.intent),
            )
        }
    }
                }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Inactive())

    private fun getOnClickListener(state: OngoingCallModel.InCall): View.OnClickListener? {
        if (state.intent == null) {
            return null
        }

    private fun getOnClickListener(intent: PendingIntent?): View.OnClickListener? {
        if (intent == null) return null
        return View.OnClickListener { view ->
            StatusBarChipsModernization.assertInLegacyMode()
            logger.log(TAG, LogLevel.INFO, {}, { "Chip clicked" })
@@ -127,7 +134,7 @@ constructor(
                view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
            // This mimics OngoingCallController#updateChipClickListener.
            activityStarter.postStartActivityDismissingKeyguard(
                state.intent,
                intent,
                ActivityTransitionAnimator.Controller.fromView(
                    backgroundView,
                    Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
@@ -136,10 +143,8 @@ constructor(
        }
    }

    private fun getClickBehavior(
        state: OngoingCallModel.InCall
    ): OngoingActivityChipModel.ClickBehavior =
        if (state.intent == null) {
    private fun getClickBehavior(intent: PendingIntent?): OngoingActivityChipModel.ClickBehavior =
        if (intent == null) {
            OngoingActivityChipModel.ClickBehavior.None
        } else {
            OngoingActivityChipModel.ClickBehavior.ExpandAction(
@@ -149,10 +154,7 @@ constructor(
                        expandable.activityTransitionController(
                            Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP
                        )
                    activityStarter.postStartActivityDismissingKeyguard(
                        state.intent,
                        animationController,
                    )
                    activityStarter.postStartActivityDismissingKeyguard(intent, animationController)
                }
            )
        }
+64 −107
Original line number Diff line number Diff line
@@ -18,24 +18,18 @@ package com.android.systemui.statusbar.chips.ui.compose

import android.content.res.ColorStateList
import android.view.ViewGroup
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
@@ -43,7 +37,6 @@ import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.Expandable
import com.android.compose.modifiers.thenIf
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
@@ -60,32 +53,45 @@ fun OngoingActivityChip(
    iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
    modifier: Modifier = Modifier,
) {
    val contentDescription =
        when (val icon = model.icon) {
            is OngoingActivityChipModel.ChipIcon.StatusBarView -> icon.contentDescription.load()
            is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon ->
                icon.contentDescription.load()
            is OngoingActivityChipModel.ChipIcon.SingleColorIcon,
            null -> null
        }

    val borderStroke =
        model.colors.outline(LocalContext.current)?.let {
            BorderStroke(dimensionResource(R.dimen.ongoing_activity_chip_outline_width), Color(it))
        }

    val onClick =
        when (val clickBehavior = model.clickBehavior) {
        is OngoingActivityChipModel.ClickBehavior.ExpandAction -> {
            // Wrap the chip in an Expandable so we can animate the expand transition.
            ExpandableChip(
                color = { Color.Transparent },
                shape =
                    RoundedCornerShape(
                        dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)
                    ),
                modifier = modifier,
            ) { expandable ->
                ChipBody(model, iconViewStore, onClick = { clickBehavior.onClick(expandable) })
            is OngoingActivityChipModel.ClickBehavior.ExpandAction -> { expandable: Expandable ->
                    clickBehavior.onClick(expandable)
                }
            is OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification -> { _ ->
                    clickBehavior.onClick()
                }
        is OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification -> {
            ChipBody(
                model,
                iconViewStore,
                onClick = { clickBehavior.onClick() },
                modifier = modifier,
            )
            is OngoingActivityChipModel.ClickBehavior.None -> null
        }

        is OngoingActivityChipModel.ClickBehavior.None -> {
            ChipBody(model, iconViewStore, modifier = modifier)
    Expandable(
        color = Color(model.colors.background(LocalContext.current).defaultColor),
        shape =
            RoundedCornerShape(dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)),
        modifier =
            modifier.height(dimensionResource(R.dimen.ongoing_appops_chip_height)).semantics {
                if (contentDescription != null) {
                    this.contentDescription = contentDescription
                }
            },
        borderStroke = borderStroke,
        onClick = onClick,
    ) {
        ChipBody(model, iconViewStore, isClickable = onClick != null)
    }
}

@@ -93,22 +99,13 @@ fun OngoingActivityChip(
private fun ChipBody(
    model: OngoingActivityChipModel.Active,
    iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
    isClickable: Boolean,
    modifier: Modifier = Modifier,
    onClick: (() -> Unit)? = null,
) {
    val context = LocalContext.current
    val isClickable = onClick != null
    val hasEmbeddedIcon =
        model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView ||
            model.icon is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
    val contentDescription =
        when (val icon = model.icon) {
            is OngoingActivityChipModel.ChipIcon.StatusBarView -> icon.contentDescription.load()
            is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon ->
                icon.contentDescription.load()
            is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> null
            null -> null
        }

    val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
    val minWidth =
        if (isClickable) {
@@ -119,32 +116,12 @@ private fun ChipBody(
            dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding
        }

    val outline = model.colors.outline(context)
    val outlineWidth = dimensionResource(R.dimen.ongoing_activity_chip_outline_width)

    val shape =
        RoundedCornerShape(dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius))

    // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
    // height of the chip is determined by the height of the background of the Row below.
    Box(
        contentAlignment = Alignment.Center,
        modifier =
            modifier
                .fillMaxHeight()
                .clickable(enabled = isClickable, onClick = onClick ?: {})
                .semantics {
                    if (contentDescription != null) {
                        this.contentDescription = contentDescription
                    }
                },
    ) {
    Row(
        horizontalArrangement = Arrangement.Center,
        verticalAlignment = Alignment.CenterVertically,
        modifier =
                Modifier.height(dimensionResource(R.dimen.ongoing_appops_chip_height))
                    .thenIf(isClickable) { Modifier.widthIn(min = minWidth) }
            modifier
                .fillMaxHeight()
                .layout { measurable, constraints ->
                    val placeable = measurable.measure(constraints)
                    layout(placeable.width, placeable.height) {
@@ -153,20 +130,11 @@ private fun ChipBody(
                        }
                    }
                }
                    .background(Color(model.colors.background(context).defaultColor), shape = shape)
                    .thenIf(outline != null) {
                        Modifier.border(
                            width = outlineWidth,
                            color = Color(outline!!),
                            shape = shape,
                        )
                    }
                .padding(
                    horizontal =
                        if (hasEmbeddedIcon) {
                            dimensionResource(
                                    R.dimen
                                        .ongoing_activity_chip_side_padding_for_embedded_padding_icon
                                R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon
                            )
                        } else {
                            dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
@@ -183,7 +151,6 @@ private fun ChipBody(
        }
    }
}
}

@Composable
private fun ChipIcon(
@@ -244,13 +211,3 @@ private fun StatusBarIcon(
        update = { iconView -> iconView.imageTintList = colorTintList },
    )
}

@Composable
private fun ExpandableChip(
    color: () -> Color,
    shape: Shape,
    modifier: Modifier = Modifier,
    content: @Composable (Expandable) -> Unit,
) {
    Expandable(color = color(), shape = shape, modifier = modifier.clip(shape)) { content(it) }
}
+2 −1
Original line number Diff line number Diff line
@@ -267,7 +267,8 @@ constructor(
                        if (StatusBarChipsModernization.isEnabled) {
                            ongoingProcessRequiresStatusBarVisible
                        } else {
                            ongoingCallStateLegacy is OngoingCallModel.InCall
                            ongoingCallStateLegacy is OngoingCallModel.InCall &&
                                !ongoingCallStateLegacy.isAppVisible
                        }
                    val statusBarMode =
                        toBarMode(
Loading