Loading packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt +15 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -31,6 +34,18 @@ import android.util.Log */ 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( Loading packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +10 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -104,6 +109,7 @@ public class MediaProjectionPermissionActivity extends Activity } @Override @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE}) public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Loading Loading @@ -231,6 +237,9 @@ 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, Loading @@ -242,6 +251,7 @@ public class MediaProjectionPermissionActivity extends Activity }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName, overrideDisableSingleAppOption, mUid, mMediaProjectionMetricsLogger); mDialog = Loading packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt +9 −2 Original line number Diff line number Diff line Loading @@ -30,11 +30,12 @@ class MediaProjectionPermissionDialogDelegate( 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 Loading Loading @@ -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) { Loading @@ -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 Loading packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt 0 → 100644 +141 −0 Original line number Diff line number Diff line /* * 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() } } Loading
packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt +15 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -31,6 +34,18 @@ import android.util.Log */ 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( Loading
packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +10 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -104,6 +109,7 @@ public class MediaProjectionPermissionActivity extends Activity } @Override @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE}) public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Loading Loading @@ -231,6 +237,9 @@ 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, Loading @@ -242,6 +251,7 @@ public class MediaProjectionPermissionActivity extends Activity }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName, overrideDisableSingleAppOption, mUid, mMediaProjectionMetricsLogger); mDialog = Loading
packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt +9 −2 Original line number Diff line number Diff line Loading @@ -30,11 +30,12 @@ class MediaProjectionPermissionDialogDelegate( 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 Loading Loading @@ -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) { Loading @@ -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 Loading
packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt 0 → 100644 +141 −0 Original line number Diff line number Diff line /* * 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() } }