[Partial Screensharing] Handle enterprise policies

Adds a logic that shows/hides work
profile or personal profile tabs
in partial screen sharing when it
is not allowed to capture the screen.

Adds a logic that shows screen capture
disabled dialog when we can't even
start the app picker dialog (when
we can't capture the screen completely).

Bug: 233348916
Test: manual testing using TestDPC with various
  device policies (disabled screen capture on
  work profile, on fully managed device,
  disallow sharing into managed profile policy),
  starting screen sharing from work or personal
  profile host app
Test: com.android.systemui.screenrecord.RecordingControllerTest
Test: com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolverTest
Change-Id: Ib732826a977c560318ff147d630d426c0828b3bd
This commit is contained in:
Nick Chameyev
2023-01-26 17:11:06 +00:00
parent 2319065bcf
commit 3733ec3be4
8 changed files with 197 additions and 27 deletions

View File

@@ -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)

View File

@@ -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 */);

View File

@@ -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

View File

@@ -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(

View File

@@ -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);
}

View File

@@ -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!!)

View File

@@ -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);
}
}

View File

@@ -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,