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

Commit a76d61c9 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB][Screen Chips] Show a stop dialog for screen share and screen cast.

Bug:  332662551
Flag: com.android.systemui.status_bar_screen_sharing_chips
Test: Start sharing your screen to another app on your phone -> click on
chip -> verify dialog is shown. Click "Stop sharing" -> verify screen
sharing stops
Test: Start casting your screen to a different device -> click on chip
-> verify dialog is shown. Click "Stop casting" -> verify screen casting
stops
Test: atest statusbar.chips package & mediaprojection package

Change-Id: I9d8c2997c9962c05e6d903c226ede5d57670a495
parent 4bdaab3f
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -326,6 +326,20 @@
    <!-- Button to stop a screen recording [CHAR LIMIT=35] -->
    <string name="screenrecord_stop_dialog_button">Stop recording</string>

    <!-- Title for a dialog shown to the user that will let them stop sharing their screen to another app on the device [CHAR LIMIT=50] -->
    <string name="share_to_app_stop_dialog_title">Stop sharing screen?</string>
    <!-- Text telling a user that they will stop sharing their screen if they click the "Stop sharing" button [CHAR LIMIT=100] -->
    <string name="share_to_app_stop_dialog_message">You will stop sharing your screen</string>
    <!-- Button to stop screen sharing [CHAR LIMIT=35] -->
    <string name="share_to_app_stop_dialog_button">Stop sharing</string>

    <!-- Title for a dialog shown to the user that will let them stop casting their screen to a different device [CHAR LIMIT=50] -->
    <string name="cast_to_other_device_stop_dialog_title">Stop casting screen?</string>
    <!-- Text telling a user that they will stop casting their screen to a different device if they click the "Stop casting" button [CHAR LIMIT=100] -->
    <string name="cast_to_other_device_stop_dialog_message">You will stop casting your screen</string>
    <!-- Button to stop screen casting to a different device [CHAR LIMIT=35] -->
    <string name="cast_to_other_device_stop_dialog_button">Stop casting</string>

    <!-- Button to close a dialog without doing any action [CHAR LIMIT=20] -->
    <string name="close_dialog_button">Close</string>

+4 −0
Original line number Diff line number Diff line
@@ -64,6 +64,10 @@ constructor(
        }
    }

    override suspend fun stopProjecting() {
        withContext(backgroundDispatcher) { mediaProjectionManager.stopActiveProjection() }
    }

    override val mediaProjectionState: Flow<MediaProjectionState> =
        conflatedCallbackFlow {
                val callback =
+3 −0
Original line number Diff line number Diff line
@@ -26,6 +26,9 @@ interface MediaProjectionRepository {
    /** Switches the task that should be projected. */
    suspend fun switchProjectedTask(task: RunningTaskInfo)

    /** Stops the currently active projection. */
    suspend fun stopProjecting()

    /** Represents the current [MediaProjectionState]. */
    val mediaProjectionState: Flow<MediaProjectionState>
}
+46 −17
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.chips.mediaprojection.domain.interactor

import android.content.pm.PackageManager
import androidx.annotation.DrawableRes
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
@@ -25,7 +27,11 @@ import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChipInteractor
import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChipInteractor.Companion.createDialogLaunchOnClickListener
import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndCastToOtherDeviceDialogDelegate
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndShareToAppDialogDelegate
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.Utils
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -34,6 +40,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/**
 * Interactor for media-projection-related chips in the status bar.
@@ -49,10 +56,12 @@ import kotlinx.coroutines.flow.stateIn
class MediaProjectionChipInteractor
@Inject
constructor(
    @Application scope: CoroutineScope,
    mediaProjectionRepository: MediaProjectionRepository,
    @Application private val scope: CoroutineScope,
    private val mediaProjectionRepository: MediaProjectionRepository,
    private val packageManager: PackageManager,
    private val systemClock: SystemClock,
    private val dialogFactory: SystemUIDialog.Factory,
    private val dialogTransitionAnimator: DialogTransitionAnimator,
) : OngoingActivityChipInteractor {
    override val chip: StateFlow<OngoingActivityChipModel> =
        mediaProjectionRepository.mediaProjectionState
@@ -70,6 +79,11 @@ constructor(
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)

    /** Stops the currently active projection. */
    fun stopProjecting() {
        scope.launch { mediaProjectionRepository.stopProjecting() }
    }

    /**
     * Returns true iff projecting to the given [packageName] means that we're projecting to a
     * *different* device (as opposed to projecting to some application on *this* device).
@@ -87,28 +101,43 @@ constructor(
        return OngoingActivityChipModel.Shown(
            icon =
                Icon.Resource(
                    R.drawable.ic_cast_connected,
                    CAST_TO_OTHER_DEVICE_ICON,
                    ContentDescription.Resource(R.string.accessibility_casting)
                ),
            // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
            startTimeMs = systemClock.elapsedRealtime()
        ) {
            // TODO(b/332662551): Implement the pause dialog.
        }
            startTimeMs = systemClock.elapsedRealtime(),
            createDialogLaunchOnClickListener(
                castToOtherDeviceDialogDelegate,
                dialogTransitionAnimator,
            ),
        )
    }

    private val castToOtherDeviceDialogDelegate =
        EndCastToOtherDeviceDialogDelegate(
            dialogFactory,
            this@MediaProjectionChipInteractor,
        )

    private fun createShareToAppChip(): OngoingActivityChipModel.Shown {
        return OngoingActivityChipModel.Shown(
            icon =
                Icon.Resource(
                    // TODO(b/332662551): Use the right icon and content description.
                    R.drawable.ic_screenshot_share,
                    contentDescription = null,
                ),
            // TODO(b/332662551): Use the right content description.
            icon = Icon.Resource(SHARE_TO_APP_ICON, contentDescription = null),
            // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
            startTimeMs = systemClock.elapsedRealtime()
        ) {
            // TODO(b/332662551): Implement the pause dialog.
            startTimeMs = systemClock.elapsedRealtime(),
            createDialogLaunchOnClickListener(shareToAppDialogDelegate, dialogTransitionAnimator),
        )
    }

    private val shareToAppDialogDelegate =
        EndShareToAppDialogDelegate(
            dialogFactory,
            this@MediaProjectionChipInteractor,
        )

    companion object {
        // TODO(b/332662551): Use the right icon.
        @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_screenshot_share
        @DrawableRes val CAST_TO_OTHER_DEVICE_ICON = R.drawable.ic_cast_connected
    }
}
+47 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.mediaprojection.ui.view

import android.os.Bundle
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog

/** A dialog that lets the user stop an ongoing cast-screen-to-other-device event. */
class EndCastToOtherDeviceDialogDelegate(
    private val systemUIDialogFactory: SystemUIDialog.Factory,
    private val interactor: MediaProjectionChipInteractor,
) : SystemUIDialog.Delegate {
    override fun createDialog(): SystemUIDialog {
        return systemUIDialogFactory.create(this)
    }

    override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
        with(dialog) {
            setIcon(MediaProjectionChipInteractor.CAST_TO_OTHER_DEVICE_ICON)
            setTitle(R.string.cast_to_other_device_stop_dialog_title)
            // TODO(b/332662551): Use a different message if they're sharing just a single app.
            setMessage(R.string.cast_to_other_device_stop_dialog_message)
            // No custom on-click, because the dialog will automatically be dismissed when the
            // button is clicked anyway.
            setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
            setPositiveButton(R.string.cast_to_other_device_stop_dialog_button) { _, _ ->
                interactor.stopProjecting()
            }
        }
    }
}
Loading