summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/OWNERS6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt143
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java29
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt217
8 files changed, 458 insertions, 9 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 991fbafed296..609e5af5c5b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -87,6 +87,7 @@ import com.android.wm.shell.performance.PerfHintController;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.recents.TaskStackTransitionObserver;
import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
@@ -619,12 +620,13 @@ public abstract class WMShellBaseModule {
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
RecentTasksController.create(context, shellInit, shellController,
shellCommandHandler, taskStackListener, activityTaskManager,
- desktopModeTaskRepository, mainExecutor));
+ desktopModeTaskRepository, taskStackTransitionObserver, mainExecutor));
}
@BindsOptionalOf
@@ -924,6 +926,19 @@ public abstract class WMShellBaseModule {
}
//
+ // Task Stack
+ //
+
+ @WMSingleton
+ @Provides
+ static TaskStackTransitionObserver provideTaskStackTransitionObserver(
+ Lazy<Transitions> transitions,
+ ShellInit shellInit
+ ) {
+ return new TaskStackTransitionObserver(transitions, shellInit);
+ }
+
+ //
// Misc
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 12bbd51b968d..87bd84017dee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -121,9 +121,9 @@ import java.util.Optional;
*/
@Module(
includes = {
- WMShellBaseModule.class,
- PipModule.class,
- ShellBackAnimationModule.class,
+ WMShellBaseModule.class,
+ PipModule.class,
+ ShellBackAnimationModule.class,
})
public abstract class WMShellModule {
@@ -664,7 +664,8 @@ public abstract class WMShellModule {
@Provides
static Object provideIndependentShellComponentsToCreate(
DragAndDropController dragAndDropController,
- Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional) {
+ Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional
+ ) {
return new Object();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index 62d195efb381..245829ecafb3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -42,4 +42,7 @@ oneway interface IRecentTasksListener {
* Called when a running task changes.
*/
void onRunningTaskChanged(in RunningTaskInfo taskInfo);
-}
+
+ /** A task has moved to front. */
+ oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo);
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/OWNERS
new file mode 100644
index 000000000000..452644b05a2a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/OWNERS
@@ -0,0 +1,6 @@
+# WM shell sub-module task stack owners
+uysalorhan@google.com
+samcackett@google.com
+alexchau@google.com
+silvajordan@google.com
+uwaisashraf@google.com \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 9d162462830f..03c8cf8cc795 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.pm.PackageManager.FEATURE_PC;
import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
+import static com.android.window.flags.Flags.enableTaskStackObserverInShell;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
import android.app.ActivityManager;
@@ -57,6 +58,7 @@ import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -73,7 +75,8 @@ import java.util.function.Consumer;
* Manages the recent task list from the system, caching it as necessary.
*/
public class RecentTasksController implements TaskStackListenerCallback,
- RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener {
+ RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener,
+ TaskStackTransitionObserver.TaskStackTransitionObserverListener {
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
@@ -84,6 +87,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasksImpl mImpl = new RecentTasksImpl();
private final ActivityTaskManager mActivityTaskManager;
+ private final TaskStackTransitionObserver mTaskStackTransitionObserver;
private RecentsTransitionHandler mTransitionHandler = null;
private IRecentTasksListener mListener;
private final boolean mPcFeatureEnabled;
@@ -112,13 +116,15 @@ public class RecentTasksController implements TaskStackListenerCallback,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
return null;
}
return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopModeTaskRepository, mainExecutor);
+ taskStackListener, activityTaskManager, desktopModeTaskRepository,
+ taskStackTransitionObserver, mainExecutor);
}
RecentTasksController(Context context,
@@ -128,6 +134,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ TaskStackTransitionObserver taskStackTransitionObserver,
ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
@@ -136,6 +143,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
mDesktopModeTaskRepository = desktopModeTaskRepository;
+ mTaskStackTransitionObserver = taskStackTransitionObserver;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
}
@@ -154,6 +162,10 @@ public class RecentTasksController implements TaskStackListenerCallback,
mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this,
+ mMainExecutor);
+ }
}
void setTransitionHandler(RecentsTransitionHandler handler) {
@@ -267,6 +279,12 @@ public class RecentTasksController implements TaskStackListenerCallback,
notifyRecentTasksChanged();
}
+ @Override
+ public void onTaskMovedToFrontThroughTransition(
+ ActivityManager.RunningTaskInfo runningTaskInfo) {
+ notifyTaskMovedToFront(runningTaskInfo);
+ }
+
@VisibleForTesting
void notifyRecentTasksChanged() {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Notify recent tasks changed");
@@ -328,6 +346,19 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
}
+ private void notifyTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mListener == null
+ || !enableTaskStackObserverInShell()
+ || taskInfo.realActivity == null) {
+ return;
+ }
+ try {
+ mListener.onTaskMovedToFront(taskInfo);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed call onTaskMovedToFront", e);
+ }
+ }
+
private boolean shouldEnableRunningTasksForDesktopMode() {
return mPcFeatureEnabled
|| (DesktopModeStatus.canEnterDesktopMode(mContext)
@@ -464,6 +495,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
return null;
}
+
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
@@ -547,6 +579,11 @@ public class RecentTasksController implements TaskStackListenerCallback,
public void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
mListener.call(l -> l.onRunningTaskChanged(taskInfo));
}
+
+ @Override
+ public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+ mListener.call(l -> l.onTaskMovedToFront(taskInfo));
+ }
};
public IRecentTasksImpl(RecentTasksController controller) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
new file mode 100644
index 000000000000..7c5f10a5bcca
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.recents
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.IBinder
+import android.util.ArrayMap
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import com.android.window.flags.Flags.enableTaskStackObserverInShell
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import dagger.Lazy
+import java.util.concurrent.Executor
+
+/**
+ * A [Transitions.TransitionObserver] that observes shell transitions and sends updates to listeners
+ * about task stack changes.
+ *
+ * TODO(346588978) Move split/pip signals here as well so that launcher don't need to handle it
+ */
+class TaskStackTransitionObserver(
+ private val transitions: Lazy<Transitions>,
+ shellInit: ShellInit
+) : Transitions.TransitionObserver {
+
+ private val transitionToTransitionChanges: MutableMap<IBinder, TransitionChanges> =
+ mutableMapOf()
+ private val taskStackTransitionObserverListeners =
+ ArrayMap<TaskStackTransitionObserverListener, Executor>()
+
+ init {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ shellInit.addInitCallback(::onInit, this)
+ }
+ }
+
+ fun onInit() {
+ transitions.get().registerObserver(this)
+ }
+
+ override fun onTransitionReady(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ if (enableTaskStackObserverInShell()) {
+ val taskInfoList = mutableListOf<RunningTaskInfo>()
+ val transitionTypeList = mutableListOf<Int>()
+
+ for (change in info.changes) {
+ if (change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) {
+ continue
+ }
+
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue
+ }
+
+ if (change.mode == WindowManager.TRANSIT_OPEN) {
+ change.taskInfo?.let { taskInfoList.add(it) }
+ transitionTypeList.add(change.mode)
+ }
+ }
+ transitionToTransitionChanges.put(
+ transition,
+ TransitionChanges(taskInfoList, transitionTypeList)
+ )
+ }
+ }
+
+ override fun onTransitionStarting(transition: IBinder) {}
+
+ override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
+
+ override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
+ val taskInfoList =
+ transitionToTransitionChanges.getOrDefault(transition, TransitionChanges()).taskInfoList
+ val typeList =
+ transitionToTransitionChanges
+ .getOrDefault(transition, TransitionChanges())
+ .transitionTypeList
+ transitionToTransitionChanges.remove(transition)
+
+ for ((index, taskInfo) in taskInfoList.withIndex()) {
+ if (
+ TransitionUtil.isOpeningType(typeList[index]) &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+ ) {
+ notifyTaskStackTransitionObserverListeners(taskInfo)
+ }
+ }
+ }
+
+ fun addTaskStackTransitionObserverListener(
+ taskStackTransitionObserverListener: TaskStackTransitionObserverListener,
+ executor: Executor
+ ) {
+ taskStackTransitionObserverListeners[taskStackTransitionObserverListener] = executor
+ }
+
+ fun removeTaskStackTransitionObserverListener(
+ taskStackTransitionObserverListener: TaskStackTransitionObserverListener
+ ) {
+ taskStackTransitionObserverListeners.remove(taskStackTransitionObserverListener)
+ }
+
+ private fun notifyTaskStackTransitionObserverListeners(taskInfo: RunningTaskInfo) {
+ taskStackTransitionObserverListeners.forEach { (listener, executor) ->
+ executor.execute { listener.onTaskMovedToFrontThroughTransition(taskInfo) }
+ }
+ }
+
+ /** Listener to use to get updates regarding task stack from this observer */
+ interface TaskStackTransitionObserverListener {
+ /** Called when a task is moved to front. */
+ fun onTaskMovedToFrontThroughTransition(taskInfo: RunningTaskInfo) {}
+ }
+
+ private data class TransitionChanges(
+ val taskInfoList: MutableList<RunningTaskInfo> = ArrayList(),
+ val transitionTypeList: MutableList<Int> = ArrayList()
+ )
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 56c4ceacc8ab..e291c0e1a151 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -113,6 +113,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
private DisplayInsetsController mDisplayInsetsController;
@Mock
private IRecentTasksListener mRecentTasksListener;
+ @Mock
+ private TaskStackTransitionObserver mTaskStackTransitionObserver;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -139,7 +141,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
- Optional.of(mDesktopModeTaskRepository), mMainExecutor);
+ Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+ mMainExecutor);
mRecentTasksController = spy(mRecentTasksControllerReal);
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
@@ -557,6 +560,30 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ public void onTaskMovedToFront_TaskStackObserverEnabled_triggersOnTaskMovedToFront()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
+
+ verify(mRecentTasksListener).onTaskMovedToFront(taskInfo);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ public void onTaskMovedToFront_TaskStackObserverEnabled_doesNotTriggersOnTaskMovedToFront()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskMovedToFront(taskInfo);
+
+ verify(mRecentTasksListener, never()).onTaskMovedToFront(any());
+ }
+
+ @Test
public void getNullSplitBoundsNonSplitTask() {
SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(3);
assertNull("splitBounds should be null for non-split task", sb);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
new file mode 100644
index 000000000000..f9599702e763
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.recents
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.os.IBinder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.IWindowContainerToken
+import android.window.TransitionInfo
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.TransitionInfoBuilder
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.same
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+
+/**
+ * Test class for {@link TaskStackTransitionObserver}
+ *
+ * Usage: atest WMShellUnitTests:TaskStackTransitionObserverTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TaskStackTransitionObserverTest {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ @Mock private lateinit var shellInit: ShellInit
+ @Mock lateinit var testExecutor: ShellExecutor
+ @Mock private lateinit var transitionsLazy: Lazy<Transitions>
+ @Mock private lateinit var transitions: Transitions
+ @Mock private lateinit var mockTransitionBinder: IBinder
+
+ private lateinit var transitionObserver: TaskStackTransitionObserver
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ shellInit = Mockito.spy(ShellInit(testExecutor))
+ whenever(transitionsLazy.get()).thenReturn(transitions)
+ transitionObserver = TaskStackTransitionObserver(transitionsLazy, shellInit)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(shellInit)
+ .addInitCallback(initRunnableCaptor.capture(), same(transitionObserver))
+ initRunnableCaptor.value.run()
+ } else {
+ transitionObserver.onInit()
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun testRegistersObserverAtInit() {
+ verify(transitions).registerObserver(same(transitionObserver))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun taskCreated_freeformWindow_listenerNotified() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+ val change =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId)
+ assertThat(listener.taskInfoToBeNotified.windowingMode)
+ .isEqualTo(change.taskInfo?.windowingMode)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun taskCreated_fullscreenWindow_listenerNotNotified() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+ val change =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(0)
+ assertThat(listener.taskInfoToBeNotified.windowingMode)
+ .isEqualTo(WindowConfiguration.WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun taskCreated_freeformWindowOnTopOfFreeform_listenerNotified() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+ val freeformOpenChange =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val freeformReorderChange =
+ createChange(
+ WindowManager.TRANSIT_TO_BACK,
+ createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0)
+ .addChange(freeformOpenChange)
+ .addChange(freeformReorderChange)
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoToBeNotified.taskId)
+ .isEqualTo(freeformOpenChange.taskInfo?.taskId)
+ assertThat(listener.taskInfoToBeNotified.windowingMode)
+ .isEqualTo(freeformOpenChange.taskInfo?.windowingMode)
+ }
+
+ class TestListener : TaskStackTransitionObserver.TaskStackTransitionObserverListener {
+ var taskInfoToBeNotified = ActivityManager.RunningTaskInfo()
+
+ override fun onTaskMovedToFrontThroughTransition(
+ taskInfo: ActivityManager.RunningTaskInfo
+ ) {
+ taskInfoToBeNotified = taskInfo
+ }
+ }
+
+ /** Simulate calling the onTransitionReady() method */
+ private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+ val startT = Mockito.mock(SurfaceControl.Transaction::class.java)
+ val finishT = Mockito.mock(SurfaceControl.Transaction::class.java)
+
+ transitionObserver.onTransitionReady(mockTransitionBinder, transitionInfo, startT, finishT)
+ }
+
+ /** Simulate calling the onTransitionFinished() method */
+ private fun callOnTransitionFinished() {
+ transitionObserver.onTransitionFinished(mockTransitionBinder, false)
+ }
+
+ companion object {
+ fun createTaskInfo(taskId: Int, windowingMode: Int): ActivityManager.RunningTaskInfo {
+ val taskInfo = ActivityManager.RunningTaskInfo()
+ taskInfo.taskId = taskId
+ taskInfo.configuration.windowConfiguration.windowingMode = windowingMode
+
+ return taskInfo
+ }
+
+ fun createChange(
+ mode: Int,
+ taskInfo: ActivityManager.RunningTaskInfo
+ ): TransitionInfo.Change {
+ val change =
+ TransitionInfo.Change(
+ WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java)),
+ Mockito.mock(SurfaceControl::class.java)
+ )
+ change.mode = mode
+ change.taskInfo = taskInfo
+ return change
+ }
+ }
+}