Merge "[Partial Screensharing] Handle enterprise policies" into tm-qpr-dev am: f3ef9103dd

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21116546

Change-Id: Ice6336f52798358abc2d950382597707148236a4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Nick Chameyev
2023-02-02 20:58:59 +00:00
committed by Automerger Merge Worker
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,