diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 3dbe6c8d3f..2251c34e48 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -235,7 +235,8 @@ public class QuickstepLauncher extends Launcher { RecentsView overviewPanel = getOverviewPanel(); mSplitSelectStateController = new SplitSelectStateController(this, mHandler, getStateManager(), - getDepthController(), getStatsLogManager()); + getDepthController(), getStatsLogManager(), + SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this)); overviewPanel.init(mActionsView, mSplitSelectStateController); mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this, mSplitSelectStateController); diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index f26189ce57..3f8da56de4 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -134,7 +134,8 @@ public final class RecentsActivity extends StatefulActivity { SplitSelectStateController controller = new SplitSelectStateController(this, mHandler, getStateManager(), - /* depthController */ null, getStatsLogManager()); + null /* depthController */, getStatsLogManager(), + SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this)); mDragLayer.recreateControllers(); mFallbackRecentsView.init(mActionsView, controller); diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 51afb5b1d2..d1100c709b 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -107,14 +107,15 @@ public class SplitSelectStateController { private FloatingTaskView mFirstFloatingTaskView; public SplitSelectStateController(Context context, Handler handler, StateManager stateManager, - DepthController depthController, StatsLogManager statsLogManager) { + DepthController depthController, StatsLogManager statsLogManager, + SystemUiProxy systemUiProxy, RecentsModel recentsModel) { mContext = context; mHandler = handler; mStatsLogManager = statsLogManager; - mSystemUiProxy = SystemUiProxy.INSTANCE.get(mContext); + mSystemUiProxy = systemUiProxy; mStateManager = stateManager; mDepthController = depthController; - mRecentTasksModel = RecentsModel.INSTANCE.get(context); + mRecentTasksModel = recentsModel; } /** diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt new file mode 100644 index 0000000000..9db0368bb9 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.quickstep.util + +import android.app.ActivityManager +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.graphics.Rect +import android.os.Handler +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.SplitConfigurationOptions +import com.android.launcher3.util.withArgCaptor +import com.android.quickstep.RecentsModel +import com.android.quickstep.SystemUiProxy +import com.android.systemui.shared.recents.model.Task +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.ArrayList +import java.util.function.Consumer + + +@RunWith(AndroidJUnit4::class) +class SplitSelectStateControllerTest { + + @Mock lateinit var systemUiProxy: SystemUiProxy + @Mock lateinit var depthController: DepthController + @Mock lateinit var statsLogManager: StatsLogManager + @Mock lateinit var stateManager: StateManager + @Mock lateinit var handler: Handler + @Mock lateinit var context: Context + @Mock lateinit var recentsModel: RecentsModel + + lateinit var splitSelectStateController: SplitSelectStateController + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + splitSelectStateController = SplitSelectStateController(context, handler, + stateManager, depthController, statsLogManager, systemUiProxy, recentsModel) + } + + @Test + fun activeTasks_noMatchingTasks() { + val groupTask1 = generateGroupTask( + ComponentName("pomegranate", "juice"), + ComponentName("pumpkin", "pie")) + val groupTask2 = generateGroupTask( + ComponentName("hotdog", "juice"), + 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( + ComponentName("no", "match"), taskConsumer) + verify(recentsModel).getTasks(capture()) + } + + // Send our mocked tasks + consumer.accept(tasks) + } + + @Test + fun activeTasks_singleMatchingTask() { + val matchingPackage = "hotdog" + val matchingClass = "juice" + 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 { + assertEquals("ComponentName package mismatched", + it.key.baseIntent.component.packageName, matchingPackage) + assertEquals("ComponentName class mismatched", + it.key.baseIntent.component.className, matchingClass) + assertEquals(it, groupTask1.task1) + } + + // Capture callback from recentsModel#getTasks() + val consumer = withArgCaptor>> { + splitSelectStateController.findLastActiveTaskAndRunCallback( + ComponentName(matchingPackage, matchingClass), taskConsumer) + verify(recentsModel).getTasks(capture()) + } + + // Send our mocked tasks + consumer.accept(tasks) + } + + @Test + fun activeTasks_multipleMatchMostRecentTask() { + val matchingPackage = "hotdog" + val matchingClass = "juice" + val groupTask1 = generateGroupTask( + ComponentName(matchingPackage, matchingClass), + ComponentName("pumpkin", "pie")) + val groupTask2 = generateGroupTask( + ComponentName("pomegranate", "juice"), + ComponentName(matchingPackage, matchingClass)) + val tasks: ArrayList = ArrayList() + tasks.add(groupTask2) + tasks.add(groupTask1) + + // 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(it, groupTask2.task2) + } + + // Capture callback from recentsModel#getTasks() + val consumer = withArgCaptor>> { + splitSelectStateController.findLastActiveTaskAndRunCallback( + ComponentName(matchingPackage, matchingClass), taskConsumer) + verify(recentsModel).getTasks(capture()) + } + + // Send our mocked tasks + consumer.accept(tasks) + } + + @Test + fun setInitialApp_withTaskId() { + splitSelectStateController.setInitialTaskSelect(null /*intent*/, + -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, 10 /*alreadyRunningTask*/) + assertTrue(splitSelectStateController.isSplitSelectActive) + } + + @Test + fun setInitialApp_withIntent() { + splitSelectStateController.setInitialTaskSelect(Intent() /*intent*/, + -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, -1 /*alreadyRunningTask*/) + assertTrue(splitSelectStateController.isSplitSelectActive) + } + + @Test + fun resetAfterInitial() { + splitSelectStateController.setInitialTaskSelect(Intent() /*intent*/, + -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, + -1) + splitSelectStateController.resetState() + assertFalse(splitSelectStateController.isSplitSelectActive) + } + + private fun generateGroupTask(task1ComponentName: ComponentName, + task2ComponentName: ComponentName): GroupTask { + val task1 = Task() + var taskInfo = ActivityManager.RunningTaskInfo() + var intent = Intent() + intent.component = task1ComponentName + taskInfo.baseIntent = intent + task1.key = Task.TaskKey(taskInfo) + + val task2 = Task() + taskInfo = ActivityManager.RunningTaskInfo() + intent = Intent() + intent.component = task2ComponentName + taskInfo.baseIntent = intent + task2.key = Task.TaskKey(taskInfo) + return GroupTask(task1, task2, SplitConfigurationOptions.SplitBounds( + Rect(), Rect(), -1, -1 + )) + } +} \ No newline at end of file