Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +35 −2 Original line number Original line Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.app.ActivityManager import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.ActivityOptions import android.app.AppOpsManager import android.app.KeyguardManager import android.app.KeyguardManager import android.app.PendingIntent import android.app.PendingIntent import android.app.TaskInfo import android.app.TaskInfo Loading @@ -33,6 +34,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.app.WindowConfiguration.WindowingMode import android.content.Context import android.content.Context import android.content.Intent import android.content.Intent import android.content.pm.PackageManager import android.graphics.Point import android.graphics.Point import android.graphics.PointF import android.graphics.PointF import android.graphics.Rect import android.graphics.Rect Loading Loading @@ -1024,10 +1026,11 @@ class DesktopTasksController( } else { } else { taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId) taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId) } } val isMinimizingToPip = val isMinimizingToPip = DesktopExperienceFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue && DesktopExperienceFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue && (taskInfo.pictureInPictureParams?.isAutoEnterEnabled ?: false) (taskInfo.pictureInPictureParams?.isAutoEnterEnabled ?: false) && isPipAllowedInAppOps(taskInfo) // If task is going to PiP, start a PiP transition instead of a minimize transition // If task is going to PiP, start a PiP transition instead of a minimize transition if (isMinimizingToPip) { if (isMinimizingToPip) { val requestInfo = val requestInfo = Loading Loading @@ -1100,6 +1103,36 @@ class DesktopTasksController( ) ) } } /** Checks whether the given [taskInfo] is allowed to enter PiP in AppOps. */ private fun isPipAllowedInAppOps(taskInfo: RunningTaskInfo): Boolean { val packageName = taskInfo.baseActivity?.packageName ?: taskInfo.topActivity?.packageName ?: taskInfo.origActivity?.packageName ?: taskInfo.realActivity?.packageName ?: return false val appOpsManager = checkNotNull(context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager) try { val appInfo = context.packageManager.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId) return appOpsManager.checkOpNoThrow( AppOpsManager.OP_PICTURE_IN_PICTURE, appInfo.uid, packageName, ) == AppOpsManager.MODE_ALLOWED } catch (_: PackageManager.NameNotFoundException) { logW( "isPipAllowedInAppOps: Failed to find applicationInfo for packageName=%s " + "and userId=%d", packageName, userId, ) } return false } /** Move or launch a task with given [taskId] to fullscreen */ /** Move or launch a task with given [taskId] to fullscreen */ @JvmOverloads @JvmOverloads fun moveToFullscreen( fun moveToFullscreen( Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +55 −6 Original line number Original line Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.ActivityOptions import android.app.AppOpsManager import android.app.KeyguardManager import android.app.KeyguardManager import android.app.PendingIntent import android.app.PendingIntent import android.app.PictureInPictureParams import android.app.PictureInPictureParams Loading Loading @@ -276,6 +277,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler @Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler @Mock @Mock private lateinit var moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler private lateinit var moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler @Mock private lateinit var mockAppOpsManager: AppOpsManager private lateinit var controller: DesktopTasksController private lateinit var controller: DesktopTasksController private lateinit var shellInit: ShellInit private lateinit var shellInit: ShellInit Loading Loading @@ -3800,10 +3802,23 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0)) .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0)) } } private fun minimizePipTask(task: RunningTaskInfo) { private fun minimizePipTask(task: RunningTaskInfo, appOpsAllowed: Boolean = true) { val handler = mock(TransitionHandler::class.java) val handler = mock(TransitionHandler::class.java) whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) mContext.addMockSystemService(Context.APP_OPS_SERVICE, mockAppOpsManager) mContext.setMockPackageManager(packageManager) whenever( mockAppOpsManager.checkOpNoThrow( eq(AppOpsManager.OP_PICTURE_IN_PICTURE), any(), any(), ) ) .thenReturn( if (appOpsAllowed) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_IGNORED ) controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) } } Loading Loading @@ -3840,6 +3855,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) } } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) fun onPipTaskMinimize_pipNotAllowedInAppOps_startMinimizeTransition() { val task = setUpPipTask(autoEnterEnabled = true) whenever( freeformTaskTransitionStarter.startMinimizedModeTransition( any(), anyInt(), anyBoolean(), ) ) .thenReturn(Binder()) whenever( mockAppOpsManager.checkOpNoThrow( eq(AppOpsManager.OP_PICTURE_IN_PICTURE), any(), any(), ) ) .thenReturn(AppOpsManager.MODE_IGNORED) minimizePipTask(task, appOpsAllowed = false) verify(freeformTaskTransitionStarter) .startMinimizedModeTransition(any(), eq(task.taskId), anyBoolean()) verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) } @Test @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) fun onPipTaskMinimize_autoEnterEnabled_sendsTaskbarRoundingUpdate() { fun onPipTaskMinimize_autoEnterEnabled_sendsTaskbarRoundingUpdate() { Loading Loading @@ -8841,10 +8884,16 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() autoEnterEnabled: Boolean, autoEnterEnabled: Boolean, displayId: Int = DEFAULT_DISPLAY, displayId: Int = DEFAULT_DISPLAY, deskId: Int = DEFAULT_DISPLAY, deskId: Int = DEFAULT_DISPLAY, ): RunningTaskInfo = ): RunningTaskInfo { val task = setUpFreeformTask(displayId = displayId, deskId = deskId).apply { setUpFreeformTask(displayId = displayId, deskId = deskId).apply { pictureInPictureParams = pictureInPictureParams = PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build() PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build() baseActivity = ComponentName("com.test.dummypackage", "TestClass") } whenever(packageManager.getApplicationInfoAsUser(any(), anyInt(), anyInt())) .thenReturn(task.topActivityInfo?.applicationInfo) return task } } private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +35 −2 Original line number Original line Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.app.ActivityManager import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.ActivityOptions import android.app.AppOpsManager import android.app.KeyguardManager import android.app.KeyguardManager import android.app.PendingIntent import android.app.PendingIntent import android.app.TaskInfo import android.app.TaskInfo Loading @@ -33,6 +34,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.app.WindowConfiguration.WindowingMode import android.content.Context import android.content.Context import android.content.Intent import android.content.Intent import android.content.pm.PackageManager import android.graphics.Point import android.graphics.Point import android.graphics.PointF import android.graphics.PointF import android.graphics.Rect import android.graphics.Rect Loading Loading @@ -1024,10 +1026,11 @@ class DesktopTasksController( } else { } else { taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId) taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId) } } val isMinimizingToPip = val isMinimizingToPip = DesktopExperienceFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue && DesktopExperienceFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue && (taskInfo.pictureInPictureParams?.isAutoEnterEnabled ?: false) (taskInfo.pictureInPictureParams?.isAutoEnterEnabled ?: false) && isPipAllowedInAppOps(taskInfo) // If task is going to PiP, start a PiP transition instead of a minimize transition // If task is going to PiP, start a PiP transition instead of a minimize transition if (isMinimizingToPip) { if (isMinimizingToPip) { val requestInfo = val requestInfo = Loading Loading @@ -1100,6 +1103,36 @@ class DesktopTasksController( ) ) } } /** Checks whether the given [taskInfo] is allowed to enter PiP in AppOps. */ private fun isPipAllowedInAppOps(taskInfo: RunningTaskInfo): Boolean { val packageName = taskInfo.baseActivity?.packageName ?: taskInfo.topActivity?.packageName ?: taskInfo.origActivity?.packageName ?: taskInfo.realActivity?.packageName ?: return false val appOpsManager = checkNotNull(context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager) try { val appInfo = context.packageManager.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId) return appOpsManager.checkOpNoThrow( AppOpsManager.OP_PICTURE_IN_PICTURE, appInfo.uid, packageName, ) == AppOpsManager.MODE_ALLOWED } catch (_: PackageManager.NameNotFoundException) { logW( "isPipAllowedInAppOps: Failed to find applicationInfo for packageName=%s " + "and userId=%d", packageName, userId, ) } return false } /** Move or launch a task with given [taskId] to fullscreen */ /** Move or launch a task with given [taskId] to fullscreen */ @JvmOverloads @JvmOverloads fun moveToFullscreen( fun moveToFullscreen( Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +55 −6 Original line number Original line Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.ActivityOptions import android.app.AppOpsManager import android.app.KeyguardManager import android.app.KeyguardManager import android.app.PendingIntent import android.app.PendingIntent import android.app.PictureInPictureParams import android.app.PictureInPictureParams Loading Loading @@ -276,6 +277,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler @Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler @Mock @Mock private lateinit var moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler private lateinit var moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler @Mock private lateinit var mockAppOpsManager: AppOpsManager private lateinit var controller: DesktopTasksController private lateinit var controller: DesktopTasksController private lateinit var shellInit: ShellInit private lateinit var shellInit: ShellInit Loading Loading @@ -3800,10 +3802,23 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0)) .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0)) } } private fun minimizePipTask(task: RunningTaskInfo) { private fun minimizePipTask(task: RunningTaskInfo, appOpsAllowed: Boolean = true) { val handler = mock(TransitionHandler::class.java) val handler = mock(TransitionHandler::class.java) whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) mContext.addMockSystemService(Context.APP_OPS_SERVICE, mockAppOpsManager) mContext.setMockPackageManager(packageManager) whenever( mockAppOpsManager.checkOpNoThrow( eq(AppOpsManager.OP_PICTURE_IN_PICTURE), any(), any(), ) ) .thenReturn( if (appOpsAllowed) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_IGNORED ) controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) } } Loading Loading @@ -3840,6 +3855,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) } } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) fun onPipTaskMinimize_pipNotAllowedInAppOps_startMinimizeTransition() { val task = setUpPipTask(autoEnterEnabled = true) whenever( freeformTaskTransitionStarter.startMinimizedModeTransition( any(), anyInt(), anyBoolean(), ) ) .thenReturn(Binder()) whenever( mockAppOpsManager.checkOpNoThrow( eq(AppOpsManager.OP_PICTURE_IN_PICTURE), any(), any(), ) ) .thenReturn(AppOpsManager.MODE_IGNORED) minimizePipTask(task, appOpsAllowed = false) verify(freeformTaskTransitionStarter) .startMinimizedModeTransition(any(), eq(task.taskId), anyBoolean()) verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) } @Test @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) fun onPipTaskMinimize_autoEnterEnabled_sendsTaskbarRoundingUpdate() { fun onPipTaskMinimize_autoEnterEnabled_sendsTaskbarRoundingUpdate() { Loading Loading @@ -8841,10 +8884,16 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() autoEnterEnabled: Boolean, autoEnterEnabled: Boolean, displayId: Int = DEFAULT_DISPLAY, displayId: Int = DEFAULT_DISPLAY, deskId: Int = DEFAULT_DISPLAY, deskId: Int = DEFAULT_DISPLAY, ): RunningTaskInfo = ): RunningTaskInfo { val task = setUpFreeformTask(displayId = displayId, deskId = deskId).apply { setUpFreeformTask(displayId = displayId, deskId = deskId).apply { pictureInPictureParams = pictureInPictureParams = PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build() PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build() baseActivity = ComponentName("com.test.dummypackage", "TestClass") } whenever(packageManager.getApplicationInfoAsUser(any(), anyInt(), anyInt())) .thenReturn(task.topActivityInfo?.applicationInfo) return task } } private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { Loading