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

Commit d0cc94af authored by dakinola's avatar dakinola
Browse files

PSS Override

Add new override to force users of MediaProjection API to show Partial Screenshare as an option

Bug: 316897322
Flag: None
Test: atest CompatChangesValidConfigTest
Test: atest MediaProjectionPermissionDialogDelegateTest
Test: atest ScreenRecordPermissionDialogDelegateTest
Test: atest CtsMediaProjectionTestCases:MediaProjectionCompatChangeTests
Test: atest PlatformScenarioTests:MediaProjectionPermissionTest
Change-Id: I81c2ad3c0e253834676f561a9033ff54dafaa0ea
parent 15b52863
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -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;
@@ -65,6 +68,18 @@ import java.util.Map;
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
+11 −0
Original line number Diff line number Diff line
@@ -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 =
+9 −2
Original line number Diff line number Diff line
@@ -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

+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()
    }
}