Partial Screenshare Opt-Out Override
Adding an per-app override that ensures that the single app option will always be shown in the MediaProjectionPermission dialog - overriding the use of MediaProjectionConfig.CreateConfigForDefaultDisplay()
Bug: 316897322
Flag: None
Test: atest packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
Test: atest packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
Change-Id: I831e4cfeedd447f17bc011bf84afcb1a05b806cd
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
index f1cade7..afe6285 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
@@ -16,6 +16,9 @@
package com.android.systemui.mediaprojection
+import android.compat.annotation.ChangeId
+import android.compat.annotation.Disabled
+import android.compat.annotation.Overridable
import android.content.Context
import android.media.projection.IMediaProjection
import android.media.projection.IMediaProjectionManager
@@ -31,6 +34,18 @@
*/
class MediaProjectionServiceHelper {
companion object {
+ /**
+ * This change id ensures that users are presented with a choice of capturing a single app
+ * or the entire screen when initiating a MediaProjection session, overriding the usage of
+ * MediaProjectionConfig#createConfigForDefaultDisplay.
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ const val OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION = 316897322L // buganizer id
+
private const val TAG = "MediaProjectionServiceHelper"
private val service =
IMediaProjectionManager.Stub.asInterface(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 8b034b2..0769731 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -16,21 +16,26 @@
package com.android.systemui.mediaprojection.permission;
+import static android.Manifest.permission.LOG_COMPAT_CHANGE;
+import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static com.android.systemui.mediaprojection.MediaProjectionServiceHelper.OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION;
import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.ENTIRE_SCREEN;
import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AlertDialog;
import android.app.StatusBarManager;
+import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -104,6 +109,7 @@
}
@Override
+ @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE})
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -231,6 +237,9 @@
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
if (isPartialScreenSharingEnabled()) {
+ final boolean overrideDisableSingleAppOption = CompatChanges.isChangeEnabled(
+ OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+ mPackageName, getHostUserHandle());
MediaProjectionPermissionDialogDelegate delegate =
new MediaProjectionPermissionDialogDelegate(
dialogContext,
@@ -242,6 +251,7 @@
},
() -> finish(RECORD_CANCEL, /* projection= */ null),
appName,
+ overrideDisableSingleAppOption,
mUid,
mMediaProjectionMetricsLogger);
mDialog =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 0f54e93..9ce8070 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -30,11 +30,12 @@
private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
private val onCancelClicked: Runnable,
private val appName: String?,
+ forceShowPartialScreenshare: Boolean,
hostUid: Int,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
- createOptionList(context, appName, mediaProjectionConfig),
+ createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
appName,
hostUid,
mediaProjectionMetricsLogger
@@ -65,7 +66,8 @@
private fun createOptionList(
context: Context,
appName: String?,
- mediaProjectionConfig: MediaProjectionConfig?
+ mediaProjectionConfig: MediaProjectionConfig?,
+ overrideDisableSingleAppOption: Boolean = false,
): List<ScreenShareOption> {
val singleAppWarningText =
if (appName == null) {
@@ -80,8 +82,13 @@
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.
val singleAppOptionDisabled =
appName != null &&
+ !overrideDisableSingleAppOption &&
mediaProjectionConfig?.regionToCapture ==
MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
new file mode 100644
index 0000000..e044eec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 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.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.media.projection.MediaProjectionConfig
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.WindowManager
+import android.widget.Spinner
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.mock
+import junit.framework.Assert.assertEquals
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+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 resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
+ private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
+ private val resIdSingleAppDisabled =
+ R.string.media_projection_entry_app_permission_dialog_single_app_disabled
+
+ @Before
+ fun setUp() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+ }
+
+ @After
+ fun teardown() {
+ if (::dialog.isInitialized) {
+ dialog.dismiss()
+ }
+ }
+
+ @Test
+ fun showDialog_forceShowPartialScreenShareFalse() {
+ // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+ // overrideDisableSingleAppOption = false
+ val overrideDisableSingleAppOption = false
+ setUpAndShowDialog(overrideDisableSingleAppOption)
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val secondOptionText =
+ 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), secondOptionText)
+ }
+
+ @Test
+ fun showDialog_forceShowPartialScreenShareTrue() {
+ // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+ // overrideDisableSingleAppOption = true
+ val overrideDisableSingleAppOption = true
+ setUpAndShowDialog(overrideDisableSingleAppOption)
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ 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)
+ }
+
+ private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
+ val delegate =
+ MediaProjectionPermissionDialogDelegate(
+ context,
+ mediaProjectionConfig,
+ {},
+ onStartRecordingClicked,
+ appName,
+ overrideDisableSingleAppOption,
+ hostUid,
+ mediaProjectionMetricsLogger
+ )
+
+ dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
+ SystemUIDialog.applyFlags(dialog)
+ SystemUIDialog.setDialogSize(dialog)
+
+ dialog.window?.addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+ )
+
+ delegate.onCreate(dialog, savedInstanceState = null)
+ dialog.show()
+ }
+}