Loading packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +48 −33 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -255,6 +228,7 @@ public class MediaProjectionPermissionActivity extends Activity grantMediaProjectionPermission(selectedOption.getMode()); }, () -> finish(RECORD_CANCEL, /* projection= */ null), hasCastingCapabilities, appName, overrideDisableSingleAppOption, mUid, Loading Loading @@ -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(); Loading packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt +18 −12 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 { Loading @@ -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 Loading packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt +60 −23 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading @@ -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 = Loading @@ -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) Loading Loading
packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +48 −33 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -255,6 +228,7 @@ public class MediaProjectionPermissionActivity extends Activity grantMediaProjectionPermission(selectedOption.getMode()); }, () -> finish(RECORD_CANCEL, /* projection= */ null), hasCastingCapabilities, appName, overrideDisableSingleAppOption, mUid, Loading Loading @@ -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(); Loading
packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt +18 −12 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 { Loading @@ -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 Loading
packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt +60 −23 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading @@ -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 = Loading @@ -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) Loading