Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e35d1126 authored by Jeremy Sim's avatar Jeremy Sim
Browse files

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
parent a49569a9
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -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 =
+8 −4
Original line number Diff line number Diff line
@@ -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);
+5 −1
Original line number Diff line number Diff line
@@ -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
+7 −6
Original line number Diff line number Diff line
@@ -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<Task> 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();
    }

    /**
+137 −3
Original line number Diff line number Diff line
@@ -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<Consumer<ArrayList<GroupTask>>> {
                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<Consumer<ArrayList<GroupTask>>> {
                splitSelectStateController.findLastActiveTaskAndRunCallback(
                    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<GroupTask> = ArrayList()
        tasks.add(groupTask1)
        tasks.add(groupTask2)

        // Assertions happen in the callback we get from what we pass into
        // #findLastActiveTaskAndRunCallback
        val taskConsumer =
            Consumer<Task> { assertNull("No tasks should have matched", it /*task*/) }

        // Capture callback from recentsModel#getTasks()
        val consumer =
            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
                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<GroupTask> = ArrayList()
        tasks.add(groupTask1)
        tasks.add(groupTask2)

        // Assertions happen in the callback we get from what we pass into
        // #findLastActiveTaskAndRunCallback
        val taskConsumer =
            Consumer<Task> {
                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<Consumer<ArrayList<GroupTask>>> {
                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<Consumer<ArrayList<GroupTask>>> {
                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)
        )
    }
}