diff options
4 files changed, 176 insertions, 2 deletions
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java index 2a0648d87c85..7ed67dcde913 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -23,6 +23,9 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.app.Activity; import android.app.ActivityOptions.LaunchCookie; +import android.compat.annotation.ChangeId; +import android.compat.annotation.Disabled; +import android.compat.annotation.Overridable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -66,6 +69,18 @@ public final class MediaProjectionManager { private static final String TAG = "MediaProjectionManager"; /** + * 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 + public static final long OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION = 316897322L; + + /** * Intent extra to customize the permission dialog based on the host app's preferences. * @hide */ 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 da9e00ddb6c2..e861ddf69aa6 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -16,8 +16,11 @@ 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.MediaProjectionManager.OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION; 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; @@ -26,11 +29,13 @@ import static com.android.systemui.mediaprojection.permission.ScreenShareOptionK 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; @@ -108,6 +113,7 @@ public class MediaProjectionPermissionActivity extends Activity } @Override + @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE}) public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -235,6 +241,10 @@ public class MediaProjectionPermissionActivity extends Activity // 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, @@ -246,6 +256,7 @@ public class MediaProjectionPermissionActivity extends Activity }, () -> 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 0f54e934f3cf..8858041ae529 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 @@ class MediaProjectionPermissionDialogDelegate( private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>, private val onCancelClicked: Runnable, private val appName: String?, + private val forceShowPartialScreenshare: Boolean, hostUid: Int, mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, ) : BaseMediaProjectionPermissionDialogDelegate<AlertDialog>( - createOptionList(context, appName, mediaProjectionConfig), + createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare), appName, hostUid, mediaProjectionMetricsLogger @@ -65,7 +66,8 @@ class MediaProjectionPermissionDialogDelegate( 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 @@ class MediaProjectionPermissionDialogDelegate( 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 000000000000..e044eeca8303 --- /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() + } +} |