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

Commit ff3b69cd authored by dakinola's avatar dakinola Committed by Daniel Akinola
Browse files

Update string logic for disabling MediaProjection single app option

Previously, the single app option for MediaProjection could only be disabled if there was an app name provided when building the dialog. Additionally, we only attempted to extract an app name if the requesting package didn't have cast permission. Now we always attempt to extract an app name (using the package name as a fallback), remove the condition of there being an app name to disabling the single app option and no longer rely on the app name being null to indicate if a package has cast permissions.

Bug: 329362311
Bug: 348323406
Flag: EXEMPT bugfix
Test: atest MediaProjectionPermissionDialogDelegateTest
Change-Id: Ica761c7145856ef7e701cc1fc6ef4c5e72938915
parent 983f5c5e
Loading
Loading
Loading
Loading
+48 −33
Original line number Original line Diff line number Diff line
@@ -187,44 +187,17 @@ public class MediaProjectionPermissionActivity extends Activity
            }
            }
        }
        }


        TextPaint paint = new TextPaint();
        paint.setTextSize(42);

        CharSequence dialogText = null;
        CharSequence dialogText = null;
        CharSequence dialogTitle = null;
        CharSequence dialogTitle = null;
        String appName = null;

        if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
        final String appName = extractAppName(aInfo, packageManager);
        final boolean hasCastingCapabilities =
                Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName);

        if (hasCastingCapabilities) {
            dialogText = getString(R.string.media_projection_sys_service_dialog_warning);
            dialogText = getString(R.string.media_projection_sys_service_dialog_warning);
            dialogTitle = getString(R.string.media_projection_sys_service_dialog_title);
            dialogTitle = getString(R.string.media_projection_sys_service_dialog_title);
        } else {
        } else {
            String label = aInfo.loadLabel(packageManager).toString();

            // If the label contains new line characters it may push the security
            // message below the fold of the dialog. Labels shouldn't have new line
            // characters anyways, so just truncate the message the first time one
            // is seen.
            final int labelLength = label.length();
            int offset = 0;
            while (offset < labelLength) {
                final int codePoint = label.codePointAt(offset);
                final int type = Character.getType(codePoint);
                if (type == Character.LINE_SEPARATOR
                        || type == Character.CONTROL
                        || type == Character.PARAGRAPH_SEPARATOR) {
                    label = label.substring(0, offset) + ELLIPSIS;
                    break;
                }
                offset += Character.charCount(codePoint);
            }

            if (label.isEmpty()) {
                label = mPackageName;
            }

            String unsanitizedAppName = TextUtils.ellipsize(label,
                    paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
            appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);

            String actionText = getString(R.string.media_projection_dialog_warning, appName);
            String actionText = getString(R.string.media_projection_dialog_warning, appName);
            SpannableString message = new SpannableString(actionText);
            SpannableString message = new SpannableString(actionText);


@@ -255,6 +228,7 @@ public class MediaProjectionPermissionActivity extends Activity
                                grantMediaProjectionPermission(selectedOption.getMode());
                                grantMediaProjectionPermission(selectedOption.getMode());
                            },
                            },
                            () -> finish(RECORD_CANCEL, /* projection= */ null),
                            () -> finish(RECORD_CANCEL, /* projection= */ null),
                            hasCastingCapabilities,
                            appName,
                            appName,
                            overrideDisableSingleAppOption,
                            overrideDisableSingleAppOption,
                            mUid,
                            mUid,
@@ -289,6 +263,47 @@ public class MediaProjectionPermissionActivity extends Activity
        }
        }
    }
    }


    private String extractAppName(ApplicationInfo applicationInfo, PackageManager packageManager) {
        String label = applicationInfo.loadLabel(packageManager).toString();

        // If the label contains new line characters it may push the security
        // message below the fold of the dialog. Labels shouldn't have new line
        // characters anyways, so just truncate the message the first time one
        // is seen.
        final int labelLength = label.length();
        int offset = 0;
        while (offset < labelLength) {
            final int codePoint = label.codePointAt(offset);
            final int type = Character.getType(codePoint);
            if (type == Character.LINE_SEPARATOR
                    || type == Character.CONTROL
                    || type == Character.PARAGRAPH_SEPARATOR) {
                label = label.substring(0, offset) + ELLIPSIS;
                break;
            }
            offset += Character.charCount(codePoint);
        }

        if (label.isEmpty()) {
            label = mPackageName;
        }

        TextPaint paint = new TextPaint();
        paint.setTextSize(42);

        String unsanitizedAppName = TextUtils.ellipsize(label,
                paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
        String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);

        // Have app name be the package name as a default fallback, if specific app name can't be
        // extracted
        if (appName == null || appName.isEmpty()) {
            return mPackageName;
        }

        return appName;
    }

    @Override
    @Override
    protected void onDestroy() {
    protected void onDestroy() {
        super.onDestroy();
        super.onDestroy();
+18 −12
Original line number Original line Diff line number Diff line
@@ -29,13 +29,20 @@ class MediaProjectionPermissionDialogDelegate(
    mediaProjectionConfig: MediaProjectionConfig?,
    mediaProjectionConfig: MediaProjectionConfig?,
    private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
    private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
    private val onCancelClicked: Runnable,
    private val onCancelClicked: Runnable,
    private val appName: String?,
    private val hasCastingCapabilities: Boolean,
    appName: String,
    forceShowPartialScreenshare: Boolean,
    forceShowPartialScreenshare: Boolean,
    hostUid: Int,
    hostUid: Int,
    mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
    mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
) :
    BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
    BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
        createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
        createOptionList(
            context,
            appName,
            hasCastingCapabilities,
            mediaProjectionConfig,
            forceShowPartialScreenshare
        ),
        appName,
        appName,
        hostUid,
        hostUid,
        mediaProjectionMetricsLogger
        mediaProjectionMetricsLogger
@@ -43,7 +50,7 @@ class MediaProjectionPermissionDialogDelegate(
    override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
    override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
        super.onCreate(dialog, savedInstanceState)
        super.onCreate(dialog, savedInstanceState)
        // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
        // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
        if (appName == null) {
        if (hasCastingCapabilities) {
            setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title)
            setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title)
            setStartButtonText(R.string.media_projection_entry_cast_permission_dialog_continue)
            setStartButtonText(R.string.media_projection_entry_cast_permission_dialog_continue)
        } else {
        } else {
@@ -65,29 +72,28 @@ class MediaProjectionPermissionDialogDelegate(
    companion object {
    companion object {
        private fun createOptionList(
        private fun createOptionList(
            context: Context,
            context: Context,
            appName: String?,
            appName: String,
            hasCastingCapabilities: Boolean,
            mediaProjectionConfig: MediaProjectionConfig?,
            mediaProjectionConfig: MediaProjectionConfig?,
            overrideDisableSingleAppOption: Boolean = false,
            overrideDisableSingleAppOption: Boolean = false,
        ): List<ScreenShareOption> {
        ): List<ScreenShareOption> {
            val singleAppWarningText =
            val singleAppWarningText =
                if (appName == null) {
                if (hasCastingCapabilities) {
                    R.string.media_projection_entry_cast_permission_dialog_warning_single_app
                    R.string.media_projection_entry_cast_permission_dialog_warning_single_app
                } else {
                } else {
                    R.string.media_projection_entry_app_permission_dialog_warning_single_app
                    R.string.media_projection_entry_app_permission_dialog_warning_single_app
                }
                }
            val entireScreenWarningText =
            val entireScreenWarningText =
                if (appName == null) {
                if (hasCastingCapabilities) {
                    R.string.media_projection_entry_cast_permission_dialog_warning_entire_screen
                    R.string.media_projection_entry_cast_permission_dialog_warning_entire_screen
                } else {
                } else {
                    R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
                    R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
                }
                }


            // The single app option should only be disabled if there is an app name provided,
            // The single app option should only be disabled if the client has setup a
            // the client has setup a MediaProjection with
            // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
            // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
            // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
            // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
            val singleAppOptionDisabled =
            val singleAppOptionDisabled =
                appName != null &&
                !overrideDisableSingleAppOption &&
                !overrideDisableSingleAppOption &&
                    mediaProjectionConfig?.regionToCapture ==
                    mediaProjectionConfig?.regionToCapture ==
                        MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
                        MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
+60 −23
Original line number Original line Diff line number Diff line
@@ -47,13 +47,7 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
    private lateinit var dialog: AlertDialog
    private lateinit var dialog: AlertDialog


    private val flags = mock<FeatureFlagsClassic>()
    private val flags = mock<FeatureFlagsClassic>()
    private val onStartRecordingClicked = mock<Runnable>()
    private val appName = "Test App"
    private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()

    private val mediaProjectionConfig: MediaProjectionConfig =
        MediaProjectionConfig.createConfigForDefaultDisplay()
    private val appName: String = "testApp"
    private val hostUid: Int = 12345


    private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
    private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
    private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
    private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
@@ -73,14 +67,31 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    fun showDialog_forceShowPartialScreenShareFalse() {
    fun showDefaultDialog() {
        // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
        setUpAndShowDialog()
        // overrideDisableSingleAppOption = false
        val overrideDisableSingleAppOption = false
        setUpAndShowDialog(overrideDisableSingleAppOption)


        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
        val secondOptionText =
        val secondOptionText =
            spinner.adapter
                .getDropDownView(1, null, spinner)
                .findViewById<TextView>(android.R.id.text1)
                ?.text

        // check that the first option is single app and enabled
        assertEquals(context.getString(resIdSingleApp), spinner.selectedItem)

        // check that the second option is full screen and enabled
        assertEquals(context.getString(resIdFullScreen), secondOptionText)
    }

    @Test
    fun showDialog_disableSingleApp() {
        setUpAndShowDialog(
            mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay()
        )

        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
        val secondOptionWarningText =
            spinner.adapter
            spinner.adapter
                .getDropDownView(1, null, spinner)
                .getDropDownView(1, null, spinner)
                .findViewById<TextView>(android.R.id.text2)
                .findViewById<TextView>(android.R.id.text2)
@@ -90,15 +101,15 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
        assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
        assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)


        // check that the second option is single app and disabled
        // check that the second option is single app and disabled
        assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText)
        assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
    }
    }


    @Test
    @Test
    fun showDialog_forceShowPartialScreenShareTrue() {
    fun showDialog_disableSingleApp_forceShowPartialScreenShareTrue() {
        // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
        setUpAndShowDialog(
        // overrideDisableSingleAppOption = true
            mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
        val overrideDisableSingleAppOption = true
            overrideDisableSingleAppOption = true
        setUpAndShowDialog(overrideDisableSingleAppOption)
        )


        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
        val secondOptionText =
        val secondOptionText =
@@ -114,17 +125,43 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
        assertEquals(context.getString(resIdFullScreen), secondOptionText)
        assertEquals(context.getString(resIdFullScreen), secondOptionText)
    }
    }


    private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
    @Test
    fun showDialog_disableSingleApp_hasCastingCapabilities() {
        setUpAndShowDialog(
            mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
            hasCastingCapabilities = true
        )

        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
        val secondOptionWarningText =
            spinner.adapter
                .getDropDownView(1, null, spinner)
                .findViewById<TextView>(android.R.id.text2)
                ?.text

        // check that the first option is full screen and enabled
        assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)

        // check that the second option is single app and disabled
        assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
    }

    private fun setUpAndShowDialog(
        mediaProjectionConfig: MediaProjectionConfig? = null,
        overrideDisableSingleAppOption: Boolean = false,
        hasCastingCapabilities: Boolean = false,
    ) {
        val delegate =
        val delegate =
            MediaProjectionPermissionDialogDelegate(
            MediaProjectionPermissionDialogDelegate(
                context,
                context,
                mediaProjectionConfig,
                mediaProjectionConfig,
                {},
                onStartRecordingClicked = {},
                onStartRecordingClicked,
                onCancelClicked = {},
                hasCastingCapabilities,
                appName,
                appName,
                overrideDisableSingleAppOption,
                overrideDisableSingleAppOption,
                hostUid,
                hostUid = 12345,
                mediaProjectionMetricsLogger
                mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
            )
            )


        dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
        dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)