From e35d11269354ea2c1cd4d686c6ad75b7b5b7ef17 Mon Sep 17 00:00:00 2001 From: Jeremy Sim Date: Thu, 23 Feb 2023 14:40:58 -0800 Subject: [PATCH] Fix bug with Taskbar not differentiating between user profiles This patch fixes a bug where Taskbar would not differentiate between user profiles when selecting an app to launch from Overview. The bug occurred because findLastActiveTaskAndRunCallback(), which checks for already-running tasks when launching an app from the Taskbar, only checks for a ComponentName match and not a userId match. Fixed by making the findLastActiveTaskAndRunCallback() also check for a userId match. Fixes: 270456926 Test: Manual Change-Id: I43ff06083a5dce775fdbd0b0ed951beaae34c0ab --- .../taskbar/TaskbarActivityContext.java | 5 +- .../taskbar/TaskbarUIController.java | 12 +- .../uioverrides/QuickstepLauncher.java | 6 +- .../util/SplitSelectStateController.java | 13 +- .../util/SplitSelectStateControllerTest.kt | 140 +++++++++++++++++- 5 files changed, 160 insertions(+), 16 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index ab52adb774..dde7d94298 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -88,6 +88,7 @@ import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy; +import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.NavigationMode; import com.android.launcher3.util.PackageManagerHelper; @@ -105,7 +106,6 @@ import com.android.systemui.unfold.updates.RotationChangeProvider; import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider; import java.io.PrintWriter; -import java.util.function.Consumer; /** * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements @@ -877,8 +877,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext { * (potentially breaking a split pair). */ private void launchFromTaskbarPreservingSplitIfVisible(RecentsView recents, ItemInfo info) { + ComponentKey componentToBeLaunched = new ComponentKey(info.getTargetComponent(), info.user); recents.getSplitSelectController().findLastActiveTaskAndRunCallback( - info.getTargetComponent(), + componentToBeLaunched, foundTask -> { if (foundTask != null) { TaskView foundTaskView = diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index 34792555b3..b552e9bd18 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -30,16 +30,15 @@ import androidx.annotation.Nullable; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; +import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.quickstep.util.GroupTask; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.quickstep.views.TaskView.TaskIdAttributeContainer; -import com.android.systemui.shared.recents.model.Task; import java.io.PrintWriter; -import java.util.function.Consumer; /** * Base class for providing different taskbar UI @@ -189,8 +188,12 @@ public class TaskbarUIController { if (recentsView == null) { return; } + + ComponentKey componentToBeStaged = new ComponentKey( + splitSelectSource.itemInfo.getTargetComponent(), + splitSelectSource.itemInfo.user); recentsView.getSplitSelectController().findLastActiveTaskAndRunCallback( - splitSelectSource.intent.getComponent(), + componentToBeStaged, foundTask -> { splitSelectSource.alreadyRunningTaskId = foundTask == null ? INVALID_TASK_ID @@ -206,8 +209,9 @@ public class TaskbarUIController { */ public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) { RecentsView recents = getRecentsView(); + ComponentKey secondAppComponent = new ComponentKey(info.getTargetComponent(), info.user); recents.getSplitSelectController().findLastActiveTaskAndRunCallback( - info.getTargetComponent(), + secondAppComponent, foundTask -> { if (foundTask != null) { TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id); diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 951a9b61dc..93baf5b724 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -136,6 +136,7 @@ import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControlle import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController; import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController; import com.android.launcher3.util.ActivityOptionsWrapper; +import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.NavigationMode; @@ -574,10 +575,13 @@ public class QuickstepLauncher extends Launcher { @Override public void startSplitSelection(SplitSelectSource splitSelectSource) { RecentsView recentsView = getOverviewPanel(); + ComponentKey componentToBeStaged = new ComponentKey( + splitSelectSource.itemInfo.getTargetComponent(), + splitSelectSource.itemInfo.user); // Check if there is already an instance of this app running, if so, initiate the split // using that. mSplitSelectStateController.findLastActiveTaskAndRunCallback( - splitSelectSource.intent.getComponent(), + componentToBeStaged, foundTask -> { splitSelectSource.alreadyRunningTaskId = foundTask == null ? INVALID_TASK_ID diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index d1100c709b..5214f7c68c 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -29,7 +29,6 @@ import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.PendingIntent; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -58,6 +57,7 @@ import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; +import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; import com.android.quickstep.RecentsModel; @@ -162,7 +162,7 @@ public class SplitSelectStateController { * Used in various task-switching or splitscreen operations when we need to check if there is a * currently running Task of a certain type and use the most recent one. */ - public void findLastActiveTaskAndRunCallback(ComponentName componentName, + public void findLastActiveTaskAndRunCallback(ComponentKey componentKey, Consumer callback) { mRecentTasksModel.getTasks(taskGroups -> { Task lastActiveTask = null; @@ -170,12 +170,12 @@ public class SplitSelectStateController { for (int i = taskGroups.size() - 1; i >= 0; i--) { GroupTask groupTask = taskGroups.get(i); Task task1 = groupTask.task1; - if (isInstanceOfComponent(task1, componentName)) { + if (isInstanceOfComponent(task1, componentKey)) { lastActiveTask = task1; break; } Task task2 = groupTask.task2; - if (isInstanceOfComponent(task2, componentName)) { + if (isInstanceOfComponent(task2, componentKey)) { lastActiveTask = task2; break; } @@ -189,13 +189,14 @@ public class SplitSelectStateController { * Checks if a given Task is the most recently-active Task of type componentName. Used for * selecting already-running Tasks for splitscreen. */ - public boolean isInstanceOfComponent(@Nullable Task task, ComponentName componentName) { + public boolean isInstanceOfComponent(@Nullable Task task, ComponentKey componentKey) { // Exclude the task that is already staged if (task == null || task.key.id == mInitialTaskId) { return false; } - return task.key.baseIntent.getComponent().equals(componentName); + return task.key.baseIntent.getComponent().equals(componentKey.componentName) + && task.key.userId == componentKey.user.getIdentifier(); } /** diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt index 5abdc93fb7..512df8e3d0 100644 --- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt +++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt @@ -23,12 +23,14 @@ import android.content.Context import android.content.Intent import android.graphics.Rect import android.os.Handler +import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.launcher3.LauncherState import com.android.launcher3.logging.StatsLogManager import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.statehandlers.DepthController import com.android.launcher3.statemanager.StateManager +import com.android.launcher3.util.ComponentKey import com.android.launcher3.util.SplitConfigurationOptions import com.android.launcher3.util.withArgCaptor import com.android.quickstep.RecentsModel @@ -60,6 +62,9 @@ class SplitSelectStateControllerTest { lateinit var splitSelectStateController: SplitSelectStateController + private val primaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId) + private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10) + @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -77,6 +82,7 @@ class SplitSelectStateControllerTest { @Test fun activeTasks_noMatchingTasks() { + val nonMatchingComponent = ComponentKey(ComponentName("no", "match"), primaryUserHandle) val groupTask1 = generateGroupTask( ComponentName("pomegranate", "juice"), @@ -100,7 +106,7 @@ class SplitSelectStateControllerTest { val consumer = withArgCaptor>> { splitSelectStateController.findLastActiveTaskAndRunCallback( - ComponentName("no", "match"), + nonMatchingComponent, taskConsumer ) verify(recentsModel).getTasks(capture()) @@ -114,6 +120,8 @@ class SplitSelectStateControllerTest { fun activeTasks_singleMatchingTask() { val matchingPackage = "hotdog" val matchingClass = "juice" + val matchingComponent = + ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = generateGroupTask( ComponentName(matchingPackage, matchingClass), @@ -149,7 +157,100 @@ class SplitSelectStateControllerTest { val consumer = withArgCaptor>> { splitSelectStateController.findLastActiveTaskAndRunCallback( - ComponentName(matchingPackage, matchingClass), + matchingComponent, + taskConsumer + ) + verify(recentsModel).getTasks(capture()) + } + + // Send our mocked tasks + consumer.accept(tasks) + } + + @Test + fun activeTasks_skipTaskWithDifferentUser() { + val matchingPackage = "hotdog" + val matchingClass = "juice" + val nonPrimaryUserComponent = + ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle) + val groupTask1 = + generateGroupTask( + ComponentName(matchingPackage, matchingClass), + ComponentName("pomegranate", "juice") + ) + val groupTask2 = + generateGroupTask( + ComponentName("pumpkin", "pie"), + ComponentName("personal", "computer") + ) + val tasks: ArrayList = ArrayList() + tasks.add(groupTask1) + tasks.add(groupTask2) + + // Assertions happen in the callback we get from what we pass into + // #findLastActiveTaskAndRunCallback + val taskConsumer = + Consumer { assertNull("No tasks should have matched", it /*task*/) } + + // Capture callback from recentsModel#getTasks() + val consumer = + withArgCaptor>> { + splitSelectStateController.findLastActiveTaskAndRunCallback( + nonPrimaryUserComponent, + taskConsumer + ) + verify(recentsModel).getTasks(capture()) + } + + // Send our mocked tasks + consumer.accept(tasks) + } + + @Test + fun activeTasks_findTaskAsNonPrimaryUser() { + val matchingPackage = "hotdog" + val matchingClass = "juice" + val nonPrimaryUserComponent = + ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle) + val groupTask1 = + generateGroupTask( + ComponentName(matchingPackage, matchingClass), + nonPrimaryUserHandle, + ComponentName("pomegranate", "juice"), + nonPrimaryUserHandle + ) + val groupTask2 = + generateGroupTask( + ComponentName("pumpkin", "pie"), + ComponentName("personal", "computer") + ) + val tasks: ArrayList = ArrayList() + tasks.add(groupTask1) + tasks.add(groupTask2) + + // Assertions happen in the callback we get from what we pass into + // #findLastActiveTaskAndRunCallback + val taskConsumer = + Consumer { + assertEquals( + "ComponentName package mismatched", + it.key.baseIntent.component.packageName, + matchingPackage + ) + assertEquals( + "ComponentName class mismatched", + it.key.baseIntent.component.className, + matchingClass + ) + assertEquals("userId mismatched", it.key.userId, nonPrimaryUserHandle.identifier) + assertEquals(it, groupTask1.task1) + } + + // Capture callback from recentsModel#getTasks() + val consumer = + withArgCaptor>> { + splitSelectStateController.findLastActiveTaskAndRunCallback( + nonPrimaryUserComponent, taskConsumer ) verify(recentsModel).getTasks(capture()) @@ -163,6 +264,8 @@ class SplitSelectStateControllerTest { fun activeTasks_multipleMatchMostRecentTask() { val matchingPackage = "hotdog" val matchingClass = "juice" + val matchingComponent = + ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = generateGroupTask( ComponentName(matchingPackage, matchingClass), @@ -198,7 +301,7 @@ class SplitSelectStateControllerTest { val consumer = withArgCaptor>> { splitSelectStateController.findLastActiveTaskAndRunCallback( - ComponentName(matchingPackage, matchingClass), + matchingComponent, taskConsumer ) verify(recentsModel).getTasks(capture()) @@ -245,6 +348,7 @@ class SplitSelectStateControllerTest { assertFalse(splitSelectStateController.isSplitSelectActive) } + // Generate GroupTask with default userId. private fun generateGroupTask( task1ComponentName: ComponentName, task2ComponentName: ComponentName @@ -268,4 +372,34 @@ class SplitSelectStateControllerTest { SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1) ) } + + // Generate GroupTask with custom user handles. + private fun generateGroupTask( + task1ComponentName: ComponentName, + userHandle1: UserHandle, + task2ComponentName: ComponentName, + userHandle2: UserHandle + ): GroupTask { + val task1 = Task() + var taskInfo = ActivityManager.RunningTaskInfo() + // Apply custom userHandle1 + taskInfo.userId = userHandle1.identifier + var intent = Intent() + intent.component = task1ComponentName + taskInfo.baseIntent = intent + task1.key = Task.TaskKey(taskInfo) + val task2 = Task() + taskInfo = ActivityManager.RunningTaskInfo() + // Apply custom userHandle2 + taskInfo.userId = userHandle2.identifier + intent = Intent() + intent.component = task2ComponentName + taskInfo.baseIntent = intent + task2.key = Task.TaskKey(taskInfo) + return GroupTask( + task1, + task2, + SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1) + ) + } }