diff options
8 files changed, 197 insertions, 27 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index a692ad74615f..52d417140e04 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -28,12 +28,15 @@ import android.os.ResultReceiver import android.os.UserHandle import android.view.ViewGroup import com.android.internal.annotations.VisibleForTesting +import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider import com.android.internal.app.ChooserActivity import com.android.internal.app.ResolverListController import com.android.internal.app.chooser.NotSelectableTargetInfo import com.android.internal.app.chooser.TargetInfo import com.android.systemui.R +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler @@ -47,6 +50,7 @@ import javax.inject.Inject class MediaProjectionAppSelectorActivity( private val componentFactory: MediaProjectionAppSelectorComponent.Factory, private val activityLauncher: AsyncActivityLauncher, + private val featureFlags: FeatureFlags, /** This is used to override the dependency in a screenshot test */ @VisibleForTesting private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)? @@ -56,7 +60,8 @@ class MediaProjectionAppSelectorActivity( constructor( componentFactory: MediaProjectionAppSelectorComponent.Factory, activityLauncher: AsyncActivityLauncher, - ) : this(componentFactory, activityLauncher, null) + featureFlags: FeatureFlags + ) : this(componentFactory, activityLauncher, featureFlags, listControllerFactory = null) private lateinit var configurationController: ConfigurationController private lateinit var controller: MediaProjectionAppSelectorController @@ -91,6 +96,13 @@ class MediaProjectionAppSelectorActivity( override fun appliedThemeResId(): Int = R.style.Theme_SystemUI_MediaProjectionAppSelector + override fun createBlockerEmptyStateProvider(): EmptyStateProvider = + if (featureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) { + component.emptyStateProvider + } else { + super.createBlockerEmptyStateProvider() + } + override fun createListController(userHandle: UserHandle): ResolverListController = listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index d830fc4a5fb5..c4e76b203f19 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -45,33 +45,46 @@ import android.text.style.StyleSpan; import android.util.Log; import android.view.Window; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; import com.android.systemui.screenrecord.MediaProjectionPermissionDialog; import com.android.systemui.screenrecord.ScreenShareOption; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.Utils; +import javax.inject.Inject; + +import dagger.Lazy; + public class MediaProjectionPermissionActivity extends Activity implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener { private static final String TAG = "MediaProjectionPermissionActivity"; private static final float MAX_APP_NAME_SIZE_PX = 500f; private static final String ELLIPSIS = "\u2026"; + private final FeatureFlags mFeatureFlags; + private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver; + private String mPackageName; private int mUid; private IMediaProjectionManager mService; - private FeatureFlags mFeatureFlags; private AlertDialog mDialog; + @Inject + public MediaProjectionPermissionActivity(FeatureFlags featureFlags, + Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) { + mFeatureFlags = featureFlags; + mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver; + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - mFeatureFlags = Dependency.get(FeatureFlags.class); mPackageName = getCallingPackage(); IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE); mService = IMediaProjectionManager.Stub.asInterface(b); @@ -104,6 +117,12 @@ public class MediaProjectionPermissionActivity extends Activity return; } + if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) { + if (showScreenCaptureDisabledDialogIfNeeded()) { + return; + } + } + TextPaint paint = new TextPaint(); paint.setTextSize(42); @@ -171,16 +190,7 @@ public class MediaProjectionPermissionActivity extends Activity mDialog = dialogBuilder.create(); } - SystemUIDialog.registerDismissListener(mDialog); - SystemUIDialog.applyFlags(mDialog); - SystemUIDialog.setDialogSize(mDialog); - - mDialog.setOnCancelListener(this); - mDialog.create(); - mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); - - final Window w = mDialog.getWindow(); - w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + setUpDialog(mDialog); mDialog.show(); } @@ -200,6 +210,32 @@ public class MediaProjectionPermissionActivity extends Activity } } + private void setUpDialog(AlertDialog dialog) { + SystemUIDialog.registerDismissListener(dialog); + SystemUIDialog.applyFlags(dialog); + SystemUIDialog.setDialogSize(dialog); + + dialog.setOnCancelListener(this); + dialog.create(); + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); + + final Window w = dialog.getWindow(); + w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + } + + private boolean showScreenCaptureDisabledDialogIfNeeded() { + final UserHandle hostUserHandle = getHostUserHandle(); + if (mScreenCaptureDevicePolicyResolver.get() + .isScreenCaptureCompletelyDisabled(hostUserHandle)) { + AlertDialog dialog = new ScreenCaptureDisabledDialog(this); + setUpDialog(dialog); + dialog.show(); + return true; + } + + return false; + } + private void grantMediaProjectionPermission(int screenShareMode) { try { if (screenShareMode == ENTIRE_SCREEN) { @@ -211,7 +247,7 @@ public class MediaProjectionPermissionActivity extends Activity intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder()); intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE, - UserHandle.getUserHandleForUid(getLaunchedFromUid())); + getHostUserHandle()); intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); // Start activity from the current foreground user to avoid creating a separate @@ -230,6 +266,10 @@ public class MediaProjectionPermissionActivity extends Activity } } + private UserHandle getHostUserHandle() { + return UserHandle.getUserHandleForUid(getLaunchedFromUid()); + } + private IMediaProjection createProjection(int uid, String packageName) throws RemoteException { return mService.createProjection(uid, packageName, MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */); diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index e665d832a568..1678c6e6b7a9 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -24,6 +24,7 @@ import com.android.launcher3.icons.IconFactory import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.media.MediaProjectionAppSelectorActivity import com.android.systemui.media.MediaProjectionAppSelectorActivity.Companion.EXTRA_HOST_APP_USER_HANDLE +import com.android.systemui.media.MediaProjectionPermissionActivity import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader import com.android.systemui.mediaprojection.appselector.data.AppIconLoader @@ -45,10 +46,10 @@ import dagger.Provides import dagger.Subcomponent import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap -import javax.inject.Qualifier -import javax.inject.Scope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob +import javax.inject.Qualifier +import javax.inject.Scope @Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector @@ -67,6 +68,11 @@ interface MediaProjectionModule { fun provideMediaProjectionAppSelectorActivity( activity: MediaProjectionAppSelectorActivity ): Activity + + @Binds + @IntoMap + @ClassKey(MediaProjectionPermissionActivity::class) + fun bindsMediaProjectionPermissionActivity(impl: MediaProjectionPermissionActivity): Activity } /** @@ -149,6 +155,7 @@ interface MediaProjectionAppSelectorComponent { val controller: MediaProjectionAppSelectorController val recentsViewController: MediaProjectionRecentsViewController + val emptyStateProvider: MediaProjectionBlockerEmptyStateProvider @get:HostUserHandle val hostUserHandle: UserHandle @get:PersonalProfile val personalProfileUserHandle: UserHandle diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 64a8a146031b..ad000690a354 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -171,8 +171,9 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> getHost().collapsePanels(); }; - Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags, + final Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags, mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked); + ActivityStarter.OnDismissAction dismissAction = () -> { if (shouldAnimateFromView) { mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj( diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index b8684ee30b9a..db2e62bc95ad 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -36,6 +36,8 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; @@ -46,6 +48,8 @@ import java.util.concurrent.Executor; import javax.inject.Inject; +import dagger.Lazy; + /** * Helper class to initiate a screen recording */ @@ -60,6 +64,8 @@ public class RecordingController private CountDownTimer mCountDownTimer = null; private final Executor mMainExecutor; private final BroadcastDispatcher mBroadcastDispatcher; + private final Context mContext; + private final FeatureFlags mFlags; private final UserContextProvider mUserContextProvider; private final UserTracker mUserTracker; @@ -70,6 +76,8 @@ public class RecordingController private CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners = new CopyOnWriteArrayList<>(); + private final Lazy<ScreenCaptureDevicePolicyResolver> mDevicePolicyResolver; + @VisibleForTesting final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { @@ -100,22 +108,44 @@ public class RecordingController @Inject public RecordingController(@Main Executor mainExecutor, BroadcastDispatcher broadcastDispatcher, + Context context, + FeatureFlags flags, UserContextProvider userContextProvider, + Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver, UserTracker userTracker) { mMainExecutor = mainExecutor; + mContext = context; + mFlags = flags; + mDevicePolicyResolver = devicePolicyResolver; mBroadcastDispatcher = broadcastDispatcher; mUserContextProvider = userContextProvider; mUserTracker = userTracker; } - /** Create a dialog to show screen recording options to the user. */ + /** + * MediaProjection host is SystemUI for the screen recorder, so return 'my user handle' + */ + private UserHandle getHostUserHandle() { + return UserHandle.of(UserHandle.myUserId()); + } + + /** Create a dialog to show screen recording options to the user. + * If screen capturing is currently not allowed it will return a dialog + * that warns users about it. */ public Dialog createScreenRecordDialog(Context context, FeatureFlags flags, DialogLaunchAnimator dialogLaunchAnimator, ActivityStarter activityStarter, @Nullable Runnable onStartRecordingClicked) { + if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) + && mDevicePolicyResolver.get() + .isScreenCaptureCompletelyDisabled(getHostUserHandle())) { + return new ScreenCaptureDisabledDialog(mContext); + } + return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) - ? new ScreenRecordPermissionDialog(context, this, activityStarter, - dialogLaunchAnimator, mUserContextProvider, onStartRecordingClicked) + ? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this, + activityStarter, dialogLaunchAnimator, mUserContextProvider, + onStartRecordingClicked) : new ScreenRecordDialog(context, this, activityStarter, mUserContextProvider, flags, dialogLaunchAnimator, onStartRecordingClicked); } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index 68e3dcdb63ea..dd21be971b36 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -42,6 +42,7 @@ import com.android.systemui.settings.UserContextProvider /** Dialog to select screen recording options */ class ScreenRecordPermissionDialog( context: Context?, + private val hostUserHandle: UserHandle, private val controller: RecordingController, private val activityStarter: ActivityStarter, private val dialogLaunchAnimator: DialogLaunchAnimator, @@ -79,11 +80,9 @@ class ScreenRecordPermissionDialog( CaptureTargetResultReceiver() ) - // Send SystemUI's user handle as the host app user handle because SystemUI - // is the 'host app' (the app that receives screen capture data) intent.putExtra( MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE, - UserHandle.of(UserHandle.myUserId()) + hostUserHandle ) val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index 69f3e987ec1d..33aaa3f1cde9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -16,13 +16,15 @@ package com.android.systemui.screenrecord; +import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.Dialog; import android.app.PendingIntent; import android.content.Intent; import android.os.Looper; @@ -31,7 +33,13 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.concurrency.FakeExecutor; @@ -61,8 +69,15 @@ public class RecordingControllerTest extends SysuiTestCase { @Mock private UserContextProvider mUserContextProvider; @Mock + private ScreenCaptureDevicePolicyResolver mDevicePolicyResolver; + @Mock + private DialogLaunchAnimator mDialogLaunchAnimator; + @Mock + private ActivityStarter mActivityStarter; + @Mock private UserTracker mUserTracker; + private FakeFeatureFlags mFeatureFlags; private RecordingController mController; private static final int USER_ID = 10; @@ -70,8 +85,9 @@ public class RecordingControllerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, - mUserContextProvider, mUserTracker); + mFeatureFlags = new FakeFeatureFlags(); + mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext, + mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker); mController.addCallback(mCallback); } @@ -190,4 +206,67 @@ public class RecordingControllerTest extends SysuiTestCase { verify(mCallback).onRecordingEnd(); assertFalse(mController.isRecording()); } + + @Test + public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false); + when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true); + + Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags, + mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); + + assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class); + } + + @Test + public void testPartialScreenSharingDisabled_returnsLegacyDialog() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false); + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false); + + Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags, + mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); + + assertThat(dialog).isInstanceOf(ScreenRecordDialog.class); + } + + @Test + public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); + when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true); + + Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags, + mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); + + assertThat(dialog).isInstanceOf(ScreenCaptureDisabledDialog.class); + } + + @Test + public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); + when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); + + Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags, + mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); + + assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt index 0aa36218b3a7..5b094c93ea9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.screenrecord +import android.os.UserHandle import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -59,6 +60,7 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { dialog = ScreenRecordPermissionDialog( context, + UserHandle.of(0), controller, starter, dialogLaunchAnimator, |