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

Commit 4b095d86 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB][Screen Chips] Immediately hide chip after stopping via dialog.

When the user taps the screen share chips to open the stop dialog, the
chip gets hidden (`DialogTransitionAnimator` hides it). If the user
chooses to click "Stop" in the dialog, the chip re-shows for a few
frames for two reasons:
1) It takes some time for the system APIs to notify SysUI that the
   screen share has officially stopped.
2) Even if the system API info comes back quickly,
  `DialogTransitionAnimator` immediately re-shows the chip and then
  CollapsedStatusBarFragment keeps the chip view around for a bit so
  that it can nicely animate it away (see b/353249803#comment4).

We should instead immediately hide the chip as soon as the user clicks
"Stop" and not have CollapsedStatusBarFragment animate it so that the
chip doesn't re-show for a few frames.

This CL achieves this by:
1) Creating a `ChipTransitionHelper` class that immediately sets the
   chip model to `Hidden` when the user clicks "Stop".
2) Updating `OngoingActivityChipModel.Hidden` to have a `shouldAnimate`
   boolean, specifying whether CSBF should animate the chip hide or not.
3) Updating `OngoingActivityChipsViewModel` to correctly use the right
   `Hidden` model when transitioning from showing a chip to hiding that
   chip.

Fixes: 350891338
Bug: 353249803
Bug: 332662551

Flag: com.android.systemui.status_bar_screen_sharing_chips

Test: Share to app, click on chip, click stop -> verify chip does not
re-appear for a few frames
Test: Cast to other device, click on chip, click stop -> verify chip
does not re-appear for a few frames
Test: Screen record, click on chip, click stop -> verify chip does not
re-appear for a few frames (in particular, verify the share-to-app chip
doesn't show up ever, to verify b/350891338 is fixed)

Test: Start a call, then start a share. Click on share chip, click stop
-> verify call chip immediately re-appears.

Test: all tests in statusbar.chips package & subpackages
Change-Id: I4ea037436cca7032eebec64b32f617bdb27b5685
parent afa8cc15
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -57,7 +57,7 @@ constructor(
        interactor.ongoingCallState
            .map { state ->
                when (state) {
                    is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden
                    is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden()
                    is OngoingCallModel.InCall -> {
                        // This block mimics OngoingCallController#updateChip.
                        if (state.startTimeMs <= 0L) {
@@ -82,7 +82,7 @@ constructor(
                    }
                }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())

    private fun getOnClickListener(state: OngoingCallModel.InCall): View.OnClickListener? {
        if (state.intent == null) {
+22 −14
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.model.Project
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
import com.android.systemui.util.time.SystemClock
@@ -78,18 +79,18 @@ constructor(
        mediaProjectionChipInteractor.projection
            .map { projectionModel ->
                when (projectionModel) {
                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden
                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden()
                    is ProjectionChipModel.Projecting -> {
                        if (projectionModel.type != ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE) {
                            OngoingActivityChipModel.Hidden
                            OngoingActivityChipModel.Hidden()
                        } else {
                            createCastScreenToOtherDeviceChip(projectionModel)
                        }
                    }
                }
            }
            // See b/347726238.
            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
            // See b/347726238 for [SharingStarted.Lazily] reasoning.
            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())

    /**
     * The cast chip to show, based only on MediaRouter API events.
@@ -113,7 +114,7 @@ constructor(
        mediaRouterChipInteractor.mediaRouterCastingState
            .map { routerModel ->
                when (routerModel) {
                    is MediaRouterCastModel.DoingNothing -> OngoingActivityChipModel.Hidden
                    is MediaRouterCastModel.DoingNothing -> OngoingActivityChipModel.Hidden()
                    is MediaRouterCastModel.Casting -> {
                        // A consequence of b/269975671 is that MediaRouter will mark a device as
                        // casting before casting has actually started. To alleviate this bug a bit,
@@ -127,9 +128,9 @@ constructor(
                    }
                }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())

    override val chip: StateFlow<OngoingActivityChipModel> =
    private val internalChip: StateFlow<OngoingActivityChipModel> =
        combine(projectionChip, routerChip) { projection, router ->
                logger.log(
                    TAG,
@@ -163,17 +164,24 @@ constructor(
                    router
                }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())

    private val hideChipDuringDialogTransitionHelper = ChipTransitionHelper(scope)

    override val chip: StateFlow<OngoingActivityChipModel> =
        hideChipDuringDialogTransitionHelper.createChipFlow(internalChip)

    /** Stops the currently active projection. */
    private fun stopProjecting() {
        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (projection)" })
    private fun stopProjectingFromDialog() {
        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested from dialog (projection)" })
        hideChipDuringDialogTransitionHelper.onActivityStoppedFromDialog()
        mediaProjectionChipInteractor.stopProjecting()
    }

    /** Stops the currently active media route. */
    private fun stopMediaRouterCasting() {
        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (router)" })
    private fun stopMediaRouterCastingFromDialog() {
        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested from dialog (router)" })
        hideChipDuringDialogTransitionHelper.onActivityStoppedFromDialog()
        mediaRouterChipInteractor.stopCasting()
    }

@@ -230,7 +238,7 @@ constructor(
        EndCastScreenToOtherDeviceDialogDelegate(
            endMediaProjectionDialogHelper,
            context,
            stopAction = this::stopProjecting,
            stopAction = this::stopProjectingFromDialog,
            state,
        )

@@ -239,7 +247,7 @@ constructor(
            endMediaProjectionDialogHelper,
            context,
            deviceName,
            stopAction = this::stopMediaRouterCasting,
            stopAction = this::stopMediaRouterCastingFromDialog,
        )

    companion object {
+18 −7
Original line number Diff line number Diff line
@@ -35,8 +35,10 @@ import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProj
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
import com.android.systemui.util.time.SystemClock
@@ -55,16 +57,18 @@ constructor(
    @Application private val scope: CoroutineScope,
    private val context: Context,
    private val interactor: ScreenRecordChipInteractor,
    private val shareToAppChipViewModel: ShareToAppChipViewModel,
    private val systemClock: SystemClock,
    private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
    private val dialogTransitionAnimator: DialogTransitionAnimator,
    @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
    override val chip: StateFlow<OngoingActivityChipModel> =

    private val internalChip =
        interactor.screenRecordState
            .map { state ->
                when (state) {
                    is ScreenRecordChipModel.DoingNothing -> OngoingActivityChipModel.Hidden
                    is ScreenRecordChipModel.DoingNothing -> OngoingActivityChipModel.Hidden()
                    is ScreenRecordChipModel.Starting -> {
                        OngoingActivityChipModel.Shown.Countdown(
                            colors = ColorsModel.Red,
@@ -96,8 +100,13 @@ constructor(
                    }
                }
            }
            // See b/347726238.
            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
            // See b/347726238 for [SharingStarted.Lazily] reasoning.
            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())

    private val chipTransitionHelper = ChipTransitionHelper(scope)

    override val chip: StateFlow<OngoingActivityChipModel> =
        chipTransitionHelper.createChipFlow(internalChip)

    private fun createDelegate(
        recordedTask: ActivityManager.RunningTaskInfo?
@@ -105,13 +114,15 @@ constructor(
        return EndScreenRecordingDialogDelegate(
            endMediaProjectionDialogHelper,
            context,
            stopAction = this::stopRecording,
            stopAction = this::stopRecordingFromDialog,
            recordedTask,
        )
    }

    private fun stopRecording() {
        logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested" })
    private fun stopRecordingFromDialog() {
        logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested from dialog" })
        chipTransitionHelper.onActivityStoppedFromDialog()
        shareToAppChipViewModel.onRecordingStoppedFromDialog()
        interactor.stopRecording()
    }

+29 −8
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProj
import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
import com.android.systemui.util.time.SystemClock
@@ -61,26 +62,46 @@ constructor(
    private val dialogTransitionAnimator: DialogTransitionAnimator,
    @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
    override val chip: StateFlow<OngoingActivityChipModel> =
    private val internalChip =
        mediaProjectionChipInteractor.projection
            .map { projectionModel ->
                when (projectionModel) {
                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden
                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden()
                    is ProjectionChipModel.Projecting -> {
                        if (projectionModel.type != ProjectionChipModel.Type.SHARE_TO_APP) {
                            OngoingActivityChipModel.Hidden
                            OngoingActivityChipModel.Hidden()
                        } else {
                            createShareToAppChip(projectionModel)
                        }
                    }
                }
            }
            // See b/347726238.
            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
            // See b/347726238 for [SharingStarted.Lazily] reasoning.
            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())

    private val chipTransitionHelper = ChipTransitionHelper(scope)

    override val chip: StateFlow<OngoingActivityChipModel> =
        chipTransitionHelper.createChipFlow(internalChip)

    /**
     * Notifies this class that the user just stopped a screen recording from the dialog that's
     * shown when you tap the recording chip.
     */
    fun onRecordingStoppedFromDialog() {
        // When a screen recording is active, share-to-app is also active (screen recording is just
        // a special case of share-to-app, where the specific app receiving the share is System UI).
        // When a screen recording is stopped, we immediately hide the screen recording chip in
        // [com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel].
        // We *also* need to immediately hide the share-to-app chip so it doesn't briefly show.
        // See b/350891338.
        chipTransitionHelper.onActivityStoppedFromDialog()
    }

    /** Stops the currently active projection. */
    private fun stopProjecting() {
        logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested" })
    private fun stopProjectingFromDialog() {
        logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested from dialog" })
        chipTransitionHelper.onActivityStoppedFromDialog()
        mediaProjectionChipInteractor.stopProjecting()
    }

@@ -113,7 +134,7 @@ constructor(
        EndShareToAppDialogDelegate(
            endMediaProjectionDialogHelper,
            context,
            stopAction = this::stopProjecting,
            stopAction = this::stopProjectingFromDialog,
            state,
        )

+9 −3
Original line number Diff line number Diff line
@@ -24,9 +24,15 @@ sealed class OngoingActivityChipModel {
    /** Condensed name representing the model, used for logs. */
    abstract val logName: String

    /** This chip shouldn't be shown. */
    data object Hidden : OngoingActivityChipModel() {
        override val logName = "Hidden"
    /**
     * This chip shouldn't be shown.
     *
     * @property shouldAnimate true if the transition from [Shown] to [Hidden] should be animated,
     *   and false if that transition should *not* be animated (i.e. the chip view should
     *   immediately disappear).
     */
    data class Hidden(val shouldAnimate: Boolean = true) : OngoingActivityChipModel() {
        override val logName = "Hidden(anim=$shouldAnimate)"
    }

    /** This chip should be shown with the given information. */
Loading