[Partial Screensharing] Add enterprise policies handling flag
Adds a feature flag to enable/disable enterprise policies handling in partial screen sharing feature. Also adds a logic to forbid cross-profile sharing using recent tasks when this flag is disabled. Cross-profile sharing using the app selector is already forbidden. Bug: 233348916 Test: com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorControllerTest Test: manual check that recent tasks don't have apps from a profile different from the host app profile Change-Id: I7247cbc9e45565e157ea70de77f5f7e6563723ad
This commit is contained in:
@@ -406,6 +406,17 @@ object Flags {
|
||||
val WM_DESKTOP_WINDOWING_2 =
|
||||
sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
|
||||
|
||||
// TODO(b/254513207): Tracking Bug to delete
|
||||
@Keep
|
||||
@JvmField
|
||||
val WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES =
|
||||
unreleasedFlag(
|
||||
1113,
|
||||
name = "screen_record_enterprise_policies",
|
||||
namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
|
||||
teamfood = false
|
||||
)
|
||||
|
||||
// 1200 - predictive back
|
||||
@Keep
|
||||
@JvmField
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.systemui.media
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.media.projection.IMediaProjection
|
||||
import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
|
||||
import android.os.Binder
|
||||
@@ -27,6 +28,7 @@ import android.os.ResultReceiver
|
||||
import android.os.UserHandle
|
||||
import android.view.ViewGroup
|
||||
import com.android.internal.annotations.VisibleForTesting
|
||||
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
|
||||
@@ -59,16 +61,12 @@ class MediaProjectionAppSelectorActivity(
|
||||
private lateinit var configurationController: ConfigurationController
|
||||
private lateinit var controller: MediaProjectionAppSelectorController
|
||||
private lateinit var recentsViewController: MediaProjectionRecentsViewController
|
||||
private lateinit var component: MediaProjectionAppSelectorComponent
|
||||
|
||||
override fun getLayoutResource() = R.layout.media_projection_app_selector
|
||||
|
||||
public override fun onCreate(bundle: Bundle?) {
|
||||
val component =
|
||||
componentFactory.create(
|
||||
activity = this,
|
||||
view = this,
|
||||
resultHandler = this
|
||||
)
|
||||
component = componentFactory.create(activity = this, view = this, resultHandler = this)
|
||||
|
||||
// Create a separate configuration controller for this activity as the configuration
|
||||
// might be different from the global one
|
||||
@@ -76,11 +74,12 @@ class MediaProjectionAppSelectorActivity(
|
||||
controller = component.controller
|
||||
recentsViewController = component.recentsViewController
|
||||
|
||||
val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
|
||||
intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
|
||||
intent.configureChooserIntent(
|
||||
resources,
|
||||
component.hostUserHandle,
|
||||
component.personalProfileUserHandle
|
||||
)
|
||||
|
||||
val title = getString(R.string.media_projection_permission_app_selector_title)
|
||||
intent.putExtra(Intent.EXTRA_TITLE, title)
|
||||
super.onCreate(bundle)
|
||||
controller.init()
|
||||
}
|
||||
@@ -183,6 +182,13 @@ class MediaProjectionAppSelectorActivity(
|
||||
|
||||
override fun shouldShowContentPreview() = true
|
||||
|
||||
override fun shouldShowContentPreviewWhenEmpty(): Boolean = true
|
||||
|
||||
override fun createMyUserIdProvider(): MyUserIdProvider =
|
||||
object : MyUserIdProvider() {
|
||||
override fun getMyUserId(): Int = component.hostUserHandle.identifier
|
||||
}
|
||||
|
||||
override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
|
||||
recentsViewController.createView(parent)
|
||||
|
||||
@@ -193,6 +199,34 @@ class MediaProjectionAppSelectorActivity(
|
||||
* instance through activity result.
|
||||
*/
|
||||
const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
|
||||
|
||||
/** UID of the app that originally launched the media projection flow (host app user) */
|
||||
const val EXTRA_HOST_APP_USER_HANDLE = "launched_from_user_handle"
|
||||
const val KEY_CAPTURE_TARGET = "capture_region"
|
||||
|
||||
/** Set up intent for the [ChooserActivity] */
|
||||
private fun Intent.configureChooserIntent(
|
||||
resources: Resources,
|
||||
hostUserHandle: UserHandle,
|
||||
personalProfileUserHandle: UserHandle
|
||||
) {
|
||||
// Specify the query intent to show icons for all apps on the chooser screen
|
||||
val queryIntent =
|
||||
Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
|
||||
putExtra(Intent.EXTRA_INTENT, queryIntent)
|
||||
|
||||
// Update the title of the chooser
|
||||
val title = resources.getString(R.string.media_projection_permission_app_selector_title)
|
||||
putExtra(Intent.EXTRA_TITLE, title)
|
||||
|
||||
// Select host app's profile tab by default
|
||||
val selectedProfile =
|
||||
if (hostUserHandle == personalProfileUserHandle) {
|
||||
PROFILE_PERSONAL
|
||||
} else {
|
||||
PROFILE_WORK
|
||||
}
|
||||
putExtra(EXTRA_SELECTED_PROFILE, selectedProfile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import static com.android.systemui.screenrecord.ScreenShareOptionKt.ENTIRE_SCREE
|
||||
import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
@@ -35,6 +36,7 @@ import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.text.BidiFormatter;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextPaint;
|
||||
@@ -208,8 +210,14 @@ public class MediaProjectionPermissionActivity extends Activity
|
||||
final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
|
||||
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
|
||||
projection.asBinder());
|
||||
intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
|
||||
UserHandle.getUserHandleForUid(getLaunchedFromUid()));
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
startActivity(intent);
|
||||
|
||||
// Start activity from the current foreground user to avoid creating a separate
|
||||
// SystemUI process without access to recent tasks because it won't have
|
||||
// WM Shell running inside.
|
||||
startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error granting projection permission", e);
|
||||
|
||||
@@ -19,9 +19,11 @@ package com.android.systemui.mediaprojection.appselector
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.UserHandle
|
||||
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.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
|
||||
import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
|
||||
import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
|
||||
@@ -30,6 +32,8 @@ import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnail
|
||||
import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
|
||||
import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
|
||||
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper
|
||||
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import dagger.Binds
|
||||
@@ -39,6 +43,7 @@ import dagger.Provides
|
||||
import dagger.Subcomponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import java.lang.IllegalArgumentException
|
||||
import javax.inject.Qualifier
|
||||
import javax.inject.Scope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -46,6 +51,12 @@ import kotlinx.coroutines.SupervisorJob
|
||||
|
||||
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
|
||||
|
||||
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle
|
||||
|
||||
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
|
||||
|
||||
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
|
||||
|
||||
@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
|
||||
@@ -83,7 +94,7 @@ interface MediaProjectionAppSelectorModule {
|
||||
@MediaProjectionAppSelector
|
||||
@MediaProjectionAppSelectorScope
|
||||
fun provideAppSelectorComponentName(context: Context): ComponentName =
|
||||
ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
|
||||
ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
|
||||
|
||||
@Provides
|
||||
@MediaProjectionAppSelector
|
||||
@@ -93,9 +104,32 @@ interface MediaProjectionAppSelectorModule {
|
||||
): ConfigurationController = ConfigurationControllerImpl(activity)
|
||||
|
||||
@Provides
|
||||
fun bindIconFactory(
|
||||
context: Context
|
||||
): IconFactory = IconFactory.obtain(context)
|
||||
@PersonalProfile
|
||||
@MediaProjectionAppSelectorScope
|
||||
fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
|
||||
// Current foreground user is the 'personal' profile
|
||||
return UserHandle.of(activityManagerWrapper.currentUserId)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@WorkProfile
|
||||
@MediaProjectionAppSelectorScope
|
||||
fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
|
||||
userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
|
||||
|
||||
@Provides
|
||||
@HostUserHandle
|
||||
@MediaProjectionAppSelectorScope
|
||||
fun hostUserHandle(activity: MediaProjectionAppSelectorActivity): UserHandle {
|
||||
val extras =
|
||||
activity.intent.extras
|
||||
?: error("MediaProjectionAppSelectorActivity should be launched with extras")
|
||||
return extras.getParcelable(EXTRA_HOST_APP_USER_HANDLE)
|
||||
?: error("MediaProjectionAppSelectorActivity should be provided with " +
|
||||
"$EXTRA_HOST_APP_USER_HANDLE extra")
|
||||
}
|
||||
|
||||
@Provides fun bindIconFactory(context: Context): IconFactory = IconFactory.obtain(context)
|
||||
|
||||
@Provides
|
||||
@MediaProjectionAppSelector
|
||||
@@ -124,6 +158,8 @@ interface MediaProjectionAppSelectorComponent {
|
||||
|
||||
val controller: MediaProjectionAppSelectorController
|
||||
val recentsViewController: MediaProjectionRecentsViewController
|
||||
@get:HostUserHandle val hostUserHandle: UserHandle
|
||||
@get:PersonalProfile val personalProfileUserHandle: UserHandle
|
||||
|
||||
@MediaProjectionAppSelector val configurationController: ConfigurationController
|
||||
}
|
||||
|
||||
@@ -17,24 +17,36 @@
|
||||
package com.android.systemui.mediaprojection.appselector
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.os.UserHandle
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.mediaprojection.appselector.data.RecentTask
|
||||
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@MediaProjectionAppSelectorScope
|
||||
class MediaProjectionAppSelectorController @Inject constructor(
|
||||
class MediaProjectionAppSelectorController
|
||||
@Inject
|
||||
constructor(
|
||||
private val recentTaskListProvider: RecentTaskListProvider,
|
||||
private val view: MediaProjectionAppSelectorView,
|
||||
private val flags: FeatureFlags,
|
||||
@HostUserHandle private val hostUserHandle: UserHandle,
|
||||
@MediaProjectionAppSelector private val scope: CoroutineScope,
|
||||
@MediaProjectionAppSelector private val appSelectorComponentName: ComponentName
|
||||
) {
|
||||
|
||||
fun init() {
|
||||
scope.launch {
|
||||
val tasks = recentTaskListProvider.loadRecentTasks().sortTasks()
|
||||
val recentTasks = recentTaskListProvider.loadRecentTasks()
|
||||
|
||||
val tasks = recentTasks
|
||||
.filterDevicePolicyRestrictedTasks()
|
||||
.sortedTasks()
|
||||
|
||||
view.bind(tasks)
|
||||
}
|
||||
}
|
||||
@@ -43,9 +55,20 @@ class MediaProjectionAppSelectorController @Inject constructor(
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
private fun List<RecentTask>.sortTasks(): List<RecentTask> =
|
||||
sortedBy {
|
||||
// Show normal tasks first and only then tasks with opened app selector
|
||||
it.topActivityComponent == appSelectorComponentName
|
||||
/**
|
||||
* Removes all recent tasks that are different from the profile of the host app to avoid any
|
||||
* cross-profile sharing
|
||||
*/
|
||||
private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> =
|
||||
if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
|
||||
// TODO(b/263950746): filter tasks based on the enterprise policies
|
||||
this
|
||||
} else {
|
||||
filter { UserHandle.of(it.userId) == hostUserHandle }
|
||||
}
|
||||
|
||||
private fun List<RecentTask>.sortedTasks(): List<RecentTask> = sortedBy {
|
||||
// Show normal tasks first and only then tasks with opened app selector
|
||||
it.topActivityComponent == appSelectorComponentName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
package com.android.systemui.mediaprojection.appselector.data
|
||||
|
||||
import android.annotation.ColorInt
|
||||
import android.annotation.UserIdInt
|
||||
import android.content.ComponentName
|
||||
|
||||
data class RecentTask(
|
||||
val taskId: Int,
|
||||
val userId: Int,
|
||||
@UserIdInt val userId: Int,
|
||||
val topActivityComponent: ComponentName?,
|
||||
val baseIntentComponent: ComponentName?,
|
||||
@ColorInt val colorBackground: Int?
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.ResultReceiver
|
||||
import android.os.UserHandle
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
@@ -77,6 +78,14 @@ class ScreenRecordPermissionDialog(
|
||||
MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
|
||||
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())
|
||||
)
|
||||
|
||||
val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!)
|
||||
if (animationController == null) {
|
||||
dismiss()
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package com.android.systemui.mediaprojection.appselector
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.os.UserHandle
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.mediaprojection.appselector.data.RecentTask
|
||||
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.junit.Test
|
||||
@@ -21,11 +25,17 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
|
||||
private val scope = CoroutineScope(Dispatchers.Unconfined)
|
||||
private val appSelectorComponentName = ComponentName("com.test", "AppSelector")
|
||||
|
||||
private val hostUserHandle = UserHandle.of(123)
|
||||
private val otherUserHandle = UserHandle.of(456)
|
||||
|
||||
private val view: MediaProjectionAppSelectorView = mock()
|
||||
private val featureFlags: FeatureFlags = mock()
|
||||
|
||||
private val controller = MediaProjectionAppSelectorController(
|
||||
taskListProvider,
|
||||
view,
|
||||
featureFlags,
|
||||
hostUserHandle,
|
||||
scope,
|
||||
appSelectorComponentName
|
||||
)
|
||||
@@ -98,15 +108,72 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsOnlyTasksWithHostProfile() {
|
||||
givenEnterprisePoliciesFeatureFlag(enabled = false)
|
||||
|
||||
val tasks = listOf(
|
||||
createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
|
||||
createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
|
||||
createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
|
||||
createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
|
||||
createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
|
||||
)
|
||||
taskListProvider.tasks = tasks
|
||||
|
||||
controller.init()
|
||||
|
||||
verify(view).bind(
|
||||
listOf(
|
||||
createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
|
||||
createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
|
||||
createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesEnabled_bindsAllTasks() {
|
||||
givenEnterprisePoliciesFeatureFlag(enabled = true)
|
||||
|
||||
val tasks = listOf(
|
||||
createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
|
||||
createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
|
||||
createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
|
||||
createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
|
||||
createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
|
||||
)
|
||||
taskListProvider.tasks = tasks
|
||||
|
||||
controller.init()
|
||||
|
||||
// TODO(b/233348916) should filter depending on the policies
|
||||
verify(view).bind(
|
||||
listOf(
|
||||
createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
|
||||
createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
|
||||
createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
|
||||
createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
|
||||
createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun givenEnterprisePoliciesFeatureFlag(enabled: Boolean) {
|
||||
whenever(featureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
|
||||
.thenReturn(enabled)
|
||||
}
|
||||
|
||||
private fun createRecentTask(
|
||||
taskId: Int,
|
||||
topActivityComponent: ComponentName? = null
|
||||
topActivityComponent: ComponentName? = null,
|
||||
userId: Int = hostUserHandle.identifier
|
||||
): RecentTask {
|
||||
return RecentTask(
|
||||
taskId = taskId,
|
||||
topActivityComponent = topActivityComponent,
|
||||
baseIntentComponent = ComponentName("com", "Test"),
|
||||
userId = 0,
|
||||
userId = userId,
|
||||
colorBackground = 0
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user