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

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

[SB][Screen Chips] If sharing a single app, show app name in stop dialog

This CL updates the share-to-app and cast-to-other-device dialogs to
show "You will stop sharing [app name]" if you chose to share just a
single app at the beginning. We need to do this for screen recording
too, but that's more complicated because RecordingController doesn't
have this information yet, so that'll come in another CL.

Bug: 332662551
Flag: com.android.systemui.status_bar_screen_sharing_chips
Test: Start a share-to-app session with a specific app -> click chip ->
verify dialog says "You will stop sharing [app name]", with [app name]
in bold
Test: Start a cast-to-other-device session with a specific app -> click
chip -> verify dialog says "You will stop casting [app name]", with [app
name] in bold
Test: atest MediaProjectionChipInteractorTest
Test: atest EndCastToOtherDeviceDialogDelegateTest
EndShareToAppDialogDelegateTest EndMediaProjectionDialogHelperTest

Change-Id: Ia4aa6132b342127b4b9202ecd40d561c0fb882c2
parent a76d61c9
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -330,6 +330,8 @@
    <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>
    <!-- Text telling a user that they will stop sharing the contents of the specified [app_name] if they click the "Stop sharing" button. Note that the app name will appear in bold. [CHAR LIMIT=100] -->
    <string name="share_to_app_stop_dialog_message_specific_app">You will stop sharing &lt;b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g>&lt;/b></string>
    <!-- Button to stop screen sharing [CHAR LIMIT=35] -->
    <string name="share_to_app_stop_dialog_button">Stop sharing</string>

@@ -337,6 +339,8 @@
    <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>
    <!-- Text telling a user that they will stop casting the contents of the specified [app_name] to a different device if they click the "Stop casting" button. Note that the app name will appear in bold.  [CHAR LIMIT=100] -->
    <string name="cast_to_other_device_stop_dialog_message_specific_app">You will stop casting &lt;b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g>&lt;/b></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>

+21 −12
Original line number Diff line number Diff line
@@ -30,8 +30,8 @@ import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChi
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.EndMediaProjectionDialogHelper
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
@@ -60,8 +60,8 @@ constructor(
    private val mediaProjectionRepository: MediaProjectionRepository,
    private val packageManager: PackageManager,
    private val systemClock: SystemClock,
    private val dialogFactory: SystemUIDialog.Factory,
    private val dialogTransitionAnimator: DialogTransitionAnimator,
    private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
) : OngoingActivityChipInteractor {
    override val chip: StateFlow<OngoingActivityChipModel> =
        mediaProjectionRepository.mediaProjectionState
@@ -70,9 +70,9 @@ constructor(
                    is MediaProjectionState.NotProjecting -> OngoingActivityChipModel.Hidden
                    is MediaProjectionState.Projecting -> {
                        if (isProjectionToOtherDevice(state.hostPackage)) {
                            createCastToOtherDeviceChip()
                            createCastToOtherDeviceChip(state)
                        } else {
                            createShareToAppChip()
                            createShareToAppChip(state)
                        }
                    }
                }
@@ -97,7 +97,9 @@ constructor(
        return Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)
    }

    private fun createCastToOtherDeviceChip(): OngoingActivityChipModel.Shown {
    private fun createCastToOtherDeviceChip(
        state: MediaProjectionState.Projecting,
    ): OngoingActivityChipModel.Shown {
        return OngoingActivityChipModel.Shown(
            icon =
                Icon.Resource(
@@ -107,32 +109,39 @@ constructor(
            // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
            startTimeMs = systemClock.elapsedRealtime(),
            createDialogLaunchOnClickListener(
                castToOtherDeviceDialogDelegate,
                createCastToOtherDeviceDialogDelegate(state),
                dialogTransitionAnimator,
            ),
        )
    }

    private val castToOtherDeviceDialogDelegate =
    private fun createCastToOtherDeviceDialogDelegate(state: MediaProjectionState.Projecting) =
        EndCastToOtherDeviceDialogDelegate(
            dialogFactory,
            endMediaProjectionDialogHelper,
            this@MediaProjectionChipInteractor,
            state,
        )

    private fun createShareToAppChip(): OngoingActivityChipModel.Shown {
    private fun createShareToAppChip(
        state: MediaProjectionState.Projecting,
    ): OngoingActivityChipModel.Shown {
        return OngoingActivityChipModel.Shown(
            // 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(),
            createDialogLaunchOnClickListener(shareToAppDialogDelegate, dialogTransitionAnimator),
            createDialogLaunchOnClickListener(
                createShareToAppDialogDelegate(state),
                dialogTransitionAnimator
            ),
        )
    }

    private val shareToAppDialogDelegate =
    private fun createShareToAppDialogDelegate(state: MediaProjectionState.Projecting) =
        EndShareToAppDialogDelegate(
            dialogFactory,
            endMediaProjectionDialogHelper,
            this@MediaProjectionChipInteractor,
            state,
        )

    companion object {
+12 −4
Original line number Diff line number Diff line
@@ -17,25 +17,33 @@
package com.android.systemui.statusbar.chips.mediaprojection.ui.view

import android.os.Bundle
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
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 endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
    private val interactor: MediaProjectionChipInteractor,
    private val state: MediaProjectionState.Projecting,
) : SystemUIDialog.Delegate {
    override fun createDialog(): SystemUIDialog {
        return systemUIDialogFactory.create(this)
        return endMediaProjectionDialogHelper.createDialog(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)
            setMessage(
                endMediaProjectionDialogHelper.getDialogMessage(
                    state,
                    genericMessageResId = R.string.cast_to_other_device_stop_dialog_message,
                    specificAppMessageResId =
                        R.string.cast_to_other_device_stop_dialog_message_specific_app,
                )
            )
            // 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)
+84 −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.annotation.StringRes
import android.content.Context
import android.content.pm.PackageManager
import android.text.Html
import android.text.Html.FROM_HTML_MODE_LEGACY
import android.text.TextUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject

/** Helper class for showing dialogs that let users end different types of media projections. */
@SysUISingleton
class EndMediaProjectionDialogHelper
@Inject
constructor(
    private val dialogFactory: SystemUIDialog.Factory,
    private val packageManager: PackageManager,
    private val context: Context
) {
    /** Creates a new [SystemUIDialog] using the given delegate. */
    fun createDialog(delegate: SystemUIDialog.Delegate): SystemUIDialog {
        return dialogFactory.create(delegate)
    }

    /**
     * Returns the message to show in the dialog based on the specific media projection state.
     *
     * @param genericMessageResId a res ID for a more generic "end projection" message
     * @param specificAppMessageResId a res ID for an "end projection" message that also lets us
     *   specify which app is currently being projected.
     */
    fun getDialogMessage(
        state: MediaProjectionState.Projecting,
        @StringRes genericMessageResId: Int,
        @StringRes specificAppMessageResId: Int,
    ): CharSequence {
        when (state) {
            is MediaProjectionState.Projecting.EntireScreen ->
                return context.getString(genericMessageResId)
            is MediaProjectionState.Projecting.SingleTask -> {
                val packageName =
                    state.task.baseIntent.component?.packageName
                        ?: return context.getString(genericMessageResId)
                try {
                    val appInfo = packageManager.getApplicationInfo(packageName, 0)
                    val appName = appInfo.loadLabel(packageManager)
                    return getSpecificAppMessageText(specificAppMessageResId, appName)
                } catch (e: PackageManager.NameNotFoundException) {
                    // TODO(b/332662551): Log this error.
                    return context.getString(genericMessageResId)
                }
            }
        }
    }

    private fun getSpecificAppMessageText(
        @StringRes specificAppMessageResId: Int,
        appName: CharSequence,
    ): CharSequence {
        // https://developer.android.com/guide/topics/resources/string-resource#StylingWithHTML
        val escapedAppName = TextUtils.htmlEncode(appName.toString())
        val text = context.getString(specificAppMessageResId, escapedAppName)
        return Html.fromHtml(text, FROM_HTML_MODE_LEGACY)
    }
}
+11 −4
Original line number Diff line number Diff line
@@ -17,25 +17,32 @@
package com.android.systemui.statusbar.chips.mediaprojection.ui.view

import android.os.Bundle
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
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 share-screen-to-app event. */
class EndShareToAppDialogDelegate(
    private val systemUIDialogFactory: SystemUIDialog.Factory,
    private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
    private val interactor: MediaProjectionChipInteractor,
    private val state: MediaProjectionState.Projecting,
) : SystemUIDialog.Delegate {
    override fun createDialog(): SystemUIDialog {
        return systemUIDialogFactory.create(this)
        return endMediaProjectionDialogHelper.createDialog(this)
    }

    override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
        with(dialog) {
            setIcon(MediaProjectionChipInteractor.SHARE_TO_APP_ICON)
            setTitle(R.string.share_to_app_stop_dialog_title)
            // TODO(b/332662551): Use a different message if they're sharing just a single app.
            setMessage(R.string.share_to_app_stop_dialog_message)
            setMessage(
                endMediaProjectionDialogHelper.getDialogMessage(
                    state,
                    genericMessageResId = R.string.share_to_app_stop_dialog_message,
                    specificAppMessageResId = R.string.share_to_app_stop_dialog_message_specific_app
                )
            )
            // 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)
Loading