diff options
8 files changed, 214 insertions, 25 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 82934d10a927..9400e7a82cfe 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -413,6 +413,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 diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index ceb48458a1d3..a692ad74615f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -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) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index bfa67a89baca..d830fc4a5fb5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -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); 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 6c41caab38a8..1d863435fa6e 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -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 } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt index d744a40b60d8..52c7ca3bb3d4 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt @@ -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 + } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt index cd994b857e95..41e22860d0ad 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -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? diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index 44b18ec4639b..68e3dcdb63ea 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -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() diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt index 19d2d334b884..1042ea714936 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -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 ) } |