diff options
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 + } + } +} |