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

Commit b8f77af1 authored by Daniel Akinola's avatar Daniel Akinola Committed by Android (Google) Code Review
Browse files

Merge "Update string logic for disabling MediaProjection single app option" into main

parents c0110ac4 ff3b69cd
Loading
Loading
Loading
Loading
+48 −33
Original line number 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 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);
            dialogTitle = getString(R.string.media_projection_sys_service_dialog_title);
        } 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);
            SpannableString message = new SpannableString(actionText);

@@ -255,6 +228,7 @@ public class MediaProjectionPermissionActivity extends Activity
                                grantMediaProjectionPermission(selectedOption.getMode());
                            },
                            () -> finish(RECORD_CANCEL, /* projection= */ null),
                            hasCastingCapabilities,
                            appName,
                            overrideDisableSingleAppOption,
                            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
    protected void onDestroy() {
        super.onDestroy();
+18 −12
Original line number Diff line number Diff line
@@ -29,13 +29,20 @@ class MediaProjectionPermissionDialogDelegate(
    mediaProjectionConfig: MediaProjectionConfig?,
    private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
    private val onCancelClicked: Runnable,
    private val appName: String?,
    private val hasCastingCapabilities: Boolean,
    appName: String,
    forceShowPartialScreenshare: Boolean,
    hostUid: Int,
    mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
    BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
        createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
        createOptionList(
            context,
            appName,
            hasCastingCapabilities,
            mediaProjectionConfig,
            forceShowPartialScreenshare
        ),
        appName,
        hostUid,
        mediaProjectionMetricsLogger
@@ -43,7 +50,7 @@ class MediaProjectionPermissionDialogDelegate(
    override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
        super.onCreate(dialog, savedInstanceState)
        // 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)
            setStartButtonText(R.string.media_projection_entry_cast_permission_dialog_continue)
        } else {
@@ -65,29 +72,28 @@ class MediaProjectionPermissionDialogDelegate(
    companion object {
        private fun createOptionList(
            context: Context,
            appName: String?,
            appName: String,
            hasCastingCapabilities: Boolean,
            mediaProjectionConfig: MediaProjectionConfig?,
            overrideDisableSingleAppOption: Boolean = false,
        ): List<ScreenShareOption> {
            val singleAppWarningText =
                if (appName == null) {
                if (hasCastingCapabilities) {
                    R.string.media_projection_entry_cast_permission_dialog_warning_single_app
                } else {
                    R.string.media_projection_entry_app_permission_dialog_warning_single_app
                }
            val entireScreenWarningText =
                if (appName == null) {
                if (hasCastingCapabilities) {
                    R.string.media_projection_entry_cast_permission_dialog_warning_entire_screen
                } else {
                    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 client has setup a MediaProjection with
            // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
            // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
            // The single app option should only be disabled if the client has setup a
            // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
            // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
            val singleAppOptionDisabled =
                appName != null &&
                !overrideDisableSingleAppOption &&
                    mediaProjectionConfig?.regionToCapture ==
                        MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
+60 −23
Original line number Diff line number Diff line
@@ -47,13 +47,7 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
    private lateinit var dialog: AlertDialog

    private val flags = mock<FeatureFlagsClassic>()
    private val onStartRecordingClicked = mock<Runnable>()
    private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()

    private val mediaProjectionConfig: MediaProjectionConfig =
        MediaProjectionConfig.createConfigForDefaultDisplay()
    private val appName: String = "testApp"
    private val hostUid: Int = 12345
    private val appName = "Test 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
@@ -73,14 +67,31 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
    }

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

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

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

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

        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
        val secondOptionText =
@@ -114,17 +125,43 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
        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 =
            MediaProjectionPermissionDialogDelegate(
                context,
                mediaProjectionConfig,
                {},
                onStartRecordingClicked,
                onStartRecordingClicked = {},
                onCancelClicked = {},
                hasCastingCapabilities,
                appName,
                overrideDisableSingleAppOption,
                hostUid,
                mediaProjectionMetricsLogger
                hostUid = 12345,
                mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
            )

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