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 c64d1134a4ab8..31596f304cb9c 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 @@ -55,6 +55,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.freeform.FreeformTaskTransitionHandler; +import com.android.wm.shell.freeform.FreeformTaskTransitionObserver; import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; @@ -207,8 +208,10 @@ public abstract class WMShellModule { @DynamicOverride static FreeformComponents provideFreeformComponents( FreeformTaskListener taskListener, - FreeformTaskTransitionHandler transitionHandler) { - return new FreeformComponents(taskListener, Optional.of(transitionHandler)); + FreeformTaskTransitionHandler transitionHandler, + FreeformTaskTransitionObserver transitionObserver) { + return new FreeformComponents( + taskListener, Optional.of(transitionHandler), Optional.of(transitionObserver)); } @WMSingleton @@ -230,19 +233,22 @@ public abstract class WMShellModule { @WMSingleton @Provides static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler( + ShellInit shellInit, + Transitions transitions, + WindowDecorViewModel windowDecorViewModel) { + return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel); + } + + @WMSingleton + @Provides + static FreeformTaskTransitionObserver provideFreeformTaskTransitionObserver( Context context, ShellInit shellInit, Transitions transitions, - WindowDecorViewModel windowDecorViewModel, FullscreenTaskListener fullscreenTaskListener, FreeformTaskListener freeformTaskListener) { - // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic - // override for this controller from the base module - ShellInit init = FreeformComponents.isFreeformEnabled(context) - ? shellInit - : null; - return new FreeformTaskTransitionHandler(init, transitions, - windowDecorViewModel, fullscreenTaskListener, freeformTaskListener); + return new FreeformTaskTransitionObserver( + context, shellInit, transitions, fullscreenTaskListener, freeformTaskListener); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java index 41e1b1de25463..eee5aaee3ec35 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java @@ -33,16 +33,19 @@ import java.util.Optional; */ public class FreeformComponents { public final ShellTaskOrganizer.TaskListener mTaskListener; - public final Optional mTaskTransitionHandler; + public final Optional mTransitionHandler; + public final Optional mTransitionObserver; /** * Creates an instance with the given components. */ public FreeformComponents( ShellTaskOrganizer.TaskListener taskListener, - Optional taskTransitionHandler) { + Optional transitionHandler, + Optional transitionObserver) { mTaskListener = taskListener; - mTaskTransitionHandler = taskTransitionHandler; + mTransitionHandler = transitionHandler; + mTransitionObserver = transitionObserver; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 30f625e241180..dd50fa0817c2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.os.IBinder; -import android.util.Log; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; @@ -32,7 +31,6 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -41,17 +39,13 @@ import java.util.ArrayList; import java.util.List; /** - * The {@link Transitions.TransitionHandler} that handles freeform task launches, closes, - * maximizing and restoring transitions. It also reports transitions so that window decorations can - * be a part of transitions. + * The {@link Transitions.TransitionHandler} that handles freeform task maximizing and restoring + * transitions. */ public class FreeformTaskTransitionHandler implements Transitions.TransitionHandler, FreeformTaskTransitionStarter { - private static final String TAG = "FreeformTH"; private final Transitions mTransitions; - private final FreeformTaskListener mFreeformTaskListener; - private final FullscreenTaskListener mFullscreenTaskListener; private final WindowDecorViewModel mWindowDecorViewModel; private final List mPendingTransitionTokens = new ArrayList<>(); @@ -59,21 +53,16 @@ public class FreeformTaskTransitionHandler public FreeformTaskTransitionHandler( ShellInit shellInit, Transitions transitions, - WindowDecorViewModel windowDecorViewModel, - FullscreenTaskListener fullscreenTaskListener, - FreeformTaskListener freeformTaskListener) { + WindowDecorViewModel windowDecorViewModel) { mTransitions = transitions; - mFullscreenTaskListener = fullscreenTaskListener; - mFreeformTaskListener = freeformTaskListener; mWindowDecorViewModel = windowDecorViewModel; - if (shellInit != null && Transitions.ENABLE_SHELL_TRANSITIONS) { + if (Transitions.ENABLE_SHELL_TRANSITIONS) { shellInit.addInitCallback(this::onInit, this); } } private void onInit() { mWindowDecorViewModel.setFreeformTaskTransitionStarter(this); - mTransitions.addHandler(this); } @Override @@ -101,13 +90,13 @@ public class FreeformTaskTransitionHandler mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this)); } + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean transitionHandled = false; - final ArrayList windowDecorsInCloseTransitions = new ArrayList<>(); for (TransitionInfo.Change change : info.getChanges()) { if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) { continue; @@ -119,22 +108,13 @@ public class FreeformTaskTransitionHandler } switch (change.getMode()) { - case WindowManager.TRANSIT_OPEN: - transitionHandled |= startOpenTransition(change, startT, finishT); - break; - case WindowManager.TRANSIT_CLOSE: - transitionHandled |= startCloseTransition( - change, windowDecorsInCloseTransitions, startT, finishT); - break; case WindowManager.TRANSIT_CHANGE: transitionHandled |= startChangeTransition( - transition, info.getType(), change, startT, finishT); + transition, info.getType(), change); break; case WindowManager.TRANSIT_TO_BACK: transitionHandled |= startMinimizeTransition(transition); break; - case WindowManager.TRANSIT_TO_FRONT: - break; } } @@ -146,139 +126,43 @@ public class FreeformTaskTransitionHandler startT.apply(); mTransitions.getMainExecutor().execute( - () -> finishTransition(windowDecorsInCloseTransitions, finishCallback)); - return true; - } - - private boolean startOpenTransition( - TransitionInfo.Change change, - SurfaceControl.Transaction startT, - SurfaceControl.Transaction finishT) { - switch (change.getTaskInfo().getWindowingMode()){ - case WINDOWING_MODE_FREEFORM: - mFreeformTaskListener.createWindowDecoration(change, startT, finishT); - break; - case WINDOWING_MODE_FULLSCREEN: - mFullscreenTaskListener.createWindowDecoration(change, startT, finishT); - break; - default: - return false; - } - - // Intercepted transition to manage the window decorations. Let other handlers animate. - return false; - } - - private boolean startCloseTransition( - TransitionInfo.Change change, - ArrayList windowDecors, - SurfaceControl.Transaction startT, - SurfaceControl.Transaction finishT) { - final AutoCloseable windowDecor; - switch (change.getTaskInfo().getWindowingMode()) { - case WINDOWING_MODE_FREEFORM: - windowDecor = mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(), - startT, finishT); - break; - case WINDOWING_MODE_FULLSCREEN: - windowDecor = mFullscreenTaskListener.giveWindowDecoration(change.getTaskInfo(), - startT, finishT); - break; - default: - windowDecor = null; - } - if (windowDecor != null) { - windowDecors.add(windowDecor); - } - // Intercepted transition to manage the window decorations. Let other handlers animate. - return false; - } - - private boolean startMinimizeTransition(IBinder transition) { - if (!mPendingTransitionTokens.contains(transition)) { - return false; - } + () -> finishCallback.onTransitionFinished(null, null)); return true; } private boolean startChangeTransition( IBinder transition, int type, - TransitionInfo.Change change, - SurfaceControl.Transaction startT, - SurfaceControl.Transaction finishT) { - AutoCloseable windowDecor = null; - + TransitionInfo.Change change) { if (!mPendingTransitionTokens.contains(transition)) { return false; } boolean handled = false; - boolean adopted = false; final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (type == Transitions.TRANSIT_MAXIMIZE && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + // TODO: Add maximize animations handled = true; - windowDecor = mFreeformTaskListener.giveWindowDecoration( - change.getTaskInfo(), startT, finishT); - adopted = mFullscreenTaskListener.adoptWindowDecoration(change, - startT, finishT, windowDecor); } if (type == Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + // TODO: Add restore animations handled = true; - windowDecor = mFullscreenTaskListener.giveWindowDecoration( - change.getTaskInfo(), startT, finishT); - adopted = mFreeformTaskListener.adoptWindowDecoration(change, - startT, finishT, windowDecor); } - if (!adopted) { - releaseWindowDecor(windowDecor); - } return handled; } - private void finishTransition( - ArrayList windowDecorsInCloseTransitions, - Transitions.TransitionFinishCallback finishCallback) { - for (AutoCloseable windowDecor : windowDecorsInCloseTransitions) { - releaseWindowDecor(windowDecor); - } - mFreeformTaskListener.onTaskTransitionFinished(); - mFullscreenTaskListener.onTaskTransitionFinished(); - finishCallback.onTransitionFinished(null, null); - } - - private void releaseWindowDecor(AutoCloseable windowDecor) { - if (windowDecor == null) { - return; - } - try { - windowDecor.close(); - } catch (Exception e) { - Log.e(TAG, "Failed to release window decoration.", e); - } + private boolean startMinimizeTransition(IBinder transition) { + return mPendingTransitionTokens.contains(transition); } @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - final ActivityManager.RunningTaskInfo taskInfo = request.getTriggerTask(); - if (taskInfo == null || taskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) { - return null; - } - switch (request.getType()) { - case WindowManager.TRANSIT_OPEN: - case WindowManager.TRANSIT_CLOSE: - case WindowManager.TRANSIT_TO_FRONT: - case WindowManager.TRANSIT_TO_BACK: - return new WindowContainerTransaction(); - default: - return null; - } + return null; } - } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java new file mode 100644 index 0000000000000..a780ec102ea98 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2022 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.freeform; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import android.app.ActivityManager; +import android.content.Context; +import android.os.IBinder; +import android.util.Log; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.android.wm.shell.fullscreen.FullscreenTaskListener; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The {@link Transitions.TransitionHandler} that handles freeform task launches, closes, + * maximizing and restoring transitions. It also reports transitions so that window decorations can + * be a part of transitions. + */ +public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver { + private static final String TAG = "FreeformTO"; + + private final Transitions mTransitions; + private final FreeformTaskListener mFreeformTaskListener; + private final FullscreenTaskListener mFullscreenTaskListener; + + private final Map> mTransitionToWindowDecors = new HashMap<>(); + + public FreeformTaskTransitionObserver( + Context context, + ShellInit shellInit, + Transitions transitions, + FullscreenTaskListener fullscreenTaskListener, + FreeformTaskListener freeformTaskListener) { + mTransitions = transitions; + mFreeformTaskListener = freeformTaskListener; + mFullscreenTaskListener = fullscreenTaskListener; + if (Transitions.ENABLE_SHELL_TRANSITIONS && FreeformComponents.isFreeformEnabled(context)) { + shellInit.addInitCallback(this::onInit, this); + } + } + + @VisibleForTesting + void onInit() { + mTransitions.registerObserver(this); + } + + @Override + public void onTransitionReady( + @NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT) { + final ArrayList windowDecors = new ArrayList<>(); + for (TransitionInfo.Change change : info.getChanges()) { + if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) { + continue; + } + + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo == null || taskInfo.taskId == -1) { + continue; + } + + switch (change.getMode()) { + case WindowManager.TRANSIT_OPEN: + onOpenTransitionReady(change, startT, finishT); + break; + case WindowManager.TRANSIT_CLOSE: { + onCloseTransitionReady(change, windowDecors, startT, finishT); + break; + } + case WindowManager.TRANSIT_CHANGE: + onChangeTransitionReady(info.getType(), change, startT, finishT); + break; + } + } + if (!windowDecors.isEmpty()) { + mTransitionToWindowDecors.put(transition, windowDecors); + } + } + + private void onOpenTransitionReady( + TransitionInfo.Change change, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT) { + switch (change.getTaskInfo().getWindowingMode()){ + case WINDOWING_MODE_FREEFORM: + mFreeformTaskListener.createWindowDecoration(change, startT, finishT); + break; + case WINDOWING_MODE_FULLSCREEN: + mFullscreenTaskListener.createWindowDecoration(change, startT, finishT); + break; + } + } + + private void onCloseTransitionReady( + TransitionInfo.Change change, + ArrayList windowDecors, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT) { + final AutoCloseable windowDecor; + switch (change.getTaskInfo().getWindowingMode()) { + case WINDOWING_MODE_FREEFORM: + windowDecor = mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(), + startT, finishT); + break; + case WINDOWING_MODE_FULLSCREEN: + windowDecor = mFullscreenTaskListener.giveWindowDecoration(change.getTaskInfo(), + startT, finishT); + break; + default: + windowDecor = null; + } + if (windowDecor != null) { + windowDecors.add(windowDecor); + } + } + + private void onChangeTransitionReady( + int type, + TransitionInfo.Change change, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT) { + AutoCloseable windowDecor = null; + + boolean adopted = false; + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (type == Transitions.TRANSIT_MAXIMIZE + && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + windowDecor = mFreeformTaskListener.giveWindowDecoration( + change.getTaskInfo(), startT, finishT); + adopted = mFullscreenTaskListener.adoptWindowDecoration( + change, startT, finishT, windowDecor); + } + + if (type == Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE + && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + windowDecor = mFullscreenTaskListener.giveWindowDecoration( + change.getTaskInfo(), startT, finishT); + adopted = mFreeformTaskListener.adoptWindowDecoration( + change, startT, finishT, windowDecor); + } + + if (!adopted) { + releaseWindowDecor(windowDecor); + } + } + + @Override + public void onTransitionStarting(@NonNull IBinder transition) {} + + @Override + public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) { + final List windowDecorsOfMerged = mTransitionToWindowDecors.get(merged); + if (windowDecorsOfMerged == null) { + // We are adding window decorations of the merged transition to them of the playing + // transition so if there is none of them there is nothing to do. + return; + } + mTransitionToWindowDecors.remove(merged); + + final List windowDecorsOfPlaying = mTransitionToWindowDecors.get(playing); + if (windowDecorsOfPlaying != null) { + windowDecorsOfPlaying.addAll(windowDecorsOfMerged); + } else { + mTransitionToWindowDecors.put(playing, windowDecorsOfMerged); + } + } + + @Override + public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) { + final List windowDecors = mTransitionToWindowDecors.getOrDefault( + transition, Collections.emptyList()); + mTransitionToWindowDecors.remove(transition); + + for (AutoCloseable windowDecor : windowDecors) { + releaseWindowDecor(windowDecor); + } + mFullscreenTaskListener.onTaskTransitionFinished(); + mFreeformTaskListener.onTaskTransitionFinished(); + } + + private static void releaseWindowDecor(AutoCloseable windowDecor) { + if (windowDecor == null) { + return; + } + try { + windowDecor.close(); + } catch (Exception e) { + Log.e(TAG, "Failed to release window decoration.", e); + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java new file mode 100644 index 0000000000000..0fd5cb081ea9f --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2022 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.freeform; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; + +import static com.android.wm.shell.transition.Transitions.TRANSIT_MAXIMIZE; +import static com.android.wm.shell.transition.Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.IWindowContainerToken; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.fullscreen.FullscreenTaskListener; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests of {@link FreeformTaskTransitionObserver} + */ +@SmallTest +public class FreeformTaskTransitionObserverTest { + + @Mock + private ShellInit mShellInit; + @Mock + private Transitions mTransitions; + @Mock + private FullscreenTaskListener mFullscreenTaskListener; + @Mock + private FreeformTaskListener mFreeformTaskListener; + + private FreeformTaskTransitionObserver mTransitionObserver; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + PackageManager pm = mock(PackageManager.class); + doReturn(true).when(pm).hasSystemFeature( + PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT); + final Context context = mock(Context.class); + doReturn(pm).when(context).getPackageManager(); + + mTransitionObserver = new FreeformTaskTransitionObserver( + context, mShellInit, mTransitions, mFullscreenTaskListener, mFreeformTaskListener); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + final ArgumentCaptor initRunnableCaptor = ArgumentCaptor.forClass( + Runnable.class); + verify(mShellInit).addInitCallback(initRunnableCaptor.capture(), + same(mTransitionObserver)); + initRunnableCaptor.getValue().run(); + } else { + mTransitionObserver.onInit(); + } + } + + @Test + public void testRegistersObserverAtInit() { + verify(mTransitions).registerObserver(same(mTransitionObserver)); + } + + @Test + public void testCreatesWindowDecorOnOpenTransition_freeform() { + final TransitionInfo.Change change = + createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + info.addChange(change); + + final IBinder transition = mock(IBinder.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + mTransitionObserver.onTransitionStarting(transition); + + verify(mFreeformTaskListener).createWindowDecoration(change, startT, finishT); + } + + @Test + public void testObtainsWindowDecorOnCloseTransition_freeform() { + final TransitionInfo.Change change = + createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0); + info.addChange(change); + + final IBinder transition = mock(IBinder.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + mTransitionObserver.onTransitionStarting(transition); + + verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT); + } + + @Test + public void testDoesntCloseWindowDecorDuringCloseTransition() throws Exception { + final TransitionInfo.Change change = + createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0); + info.addChange(change); + + final AutoCloseable windowDecor = mock(AutoCloseable.class); + doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration( + eq(change.getTaskInfo()), any(), any()); + + final IBinder transition = mock(IBinder.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + mTransitionObserver.onTransitionStarting(transition); + + verify(windowDecor, never()).close(); + } + + @Test + public void testClosesWindowDecorAfterCloseTransition() throws Exception { + final TransitionInfo.Change change = + createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0); + info.addChange(change); + + final AutoCloseable windowDecor = mock(AutoCloseable.class); + doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration( + eq(change.getTaskInfo()), any(), any()); + + final IBinder transition = mock(IBinder.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + mTransitionObserver.onTransitionStarting(transition); + mTransitionObserver.onTransitionFinished(transition, false); + + verify(windowDecor).close(); + } + + @Test + public void testClosesMergedWindowDecorationAfterTransitionFinishes() throws Exception { + // The playing transition + final TransitionInfo.Change change1 = + createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info1 = new TransitionInfo(TRANSIT_OPEN, 0); + info1.addChange(change1); + + final IBinder transition1 = mock(IBinder.class); + final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition1, info1, startT1, finishT1); + mTransitionObserver.onTransitionStarting(transition1); + + // The merged transition + final TransitionInfo.Change change2 = + createChange(TRANSIT_CLOSE, 2, WINDOWING_MODE_FREEFORM); + final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0); + info2.addChange(change2); + + final AutoCloseable windowDecor2 = mock(AutoCloseable.class); + doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration( + eq(change2.getTaskInfo()), any(), any()); + + final IBinder transition2 = mock(IBinder.class); + final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition2, info2, startT2, finishT2); + mTransitionObserver.onTransitionMerged(transition2, transition1); + + mTransitionObserver.onTransitionFinished(transition1, false); + + verify(windowDecor2).close(); + } + + @Test + public void testClosesAllWindowDecorsOnTransitionMergeAfterCloseTransitions() throws Exception { + // The playing transition + final TransitionInfo.Change change1 = + createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info1 = new TransitionInfo(TRANSIT_CLOSE, 0); + info1.addChange(change1); + + final AutoCloseable windowDecor1 = mock(AutoCloseable.class); + doReturn(windowDecor1).when(mFreeformTaskListener).giveWindowDecoration( + eq(change1.getTaskInfo()), any(), any()); + + final IBinder transition1 = mock(IBinder.class); + final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition1, info1, startT1, finishT1); + mTransitionObserver.onTransitionStarting(transition1); + + // The merged transition + final TransitionInfo.Change change2 = + createChange(TRANSIT_CLOSE, 2, WINDOWING_MODE_FREEFORM); + final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0); + info2.addChange(change2); + + final AutoCloseable windowDecor2 = mock(AutoCloseable.class); + doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration( + eq(change2.getTaskInfo()), any(), any()); + + final IBinder transition2 = mock(IBinder.class); + final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition2, info2, startT2, finishT2); + mTransitionObserver.onTransitionMerged(transition2, transition1); + + mTransitionObserver.onTransitionFinished(transition1, false); + + verify(windowDecor1).close(); + verify(windowDecor2).close(); + } + + @Test + public void testTransfersWindowDecorOnMaximize() { + final TransitionInfo.Change change = + createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FULLSCREEN); + final TransitionInfo info = new TransitionInfo(TRANSIT_MAXIMIZE, 0); + info.addChange(change); + + final AutoCloseable windowDecor = mock(AutoCloseable.class); + doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration( + eq(change.getTaskInfo()), any(), any()); + + final IBinder transition = mock(IBinder.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + mTransitionObserver.onTransitionStarting(transition); + + verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT); + verify(mFullscreenTaskListener).adoptWindowDecoration( + eq(change), same(startT), same(finishT), any()); + } + + @Test + public void testTransfersWindowDecorOnRestoreFromMaximize() { + final TransitionInfo.Change change = + createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info = new TransitionInfo(TRANSIT_RESTORE_FROM_MAXIMIZE, 0); + info.addChange(change); + + final IBinder transition = mock(IBinder.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + mTransitionObserver.onTransitionStarting(transition); + + verify(mFullscreenTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT); + verify(mFreeformTaskListener).adoptWindowDecoration( + eq(change), same(startT), same(finishT), any()); + } + + private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) { + final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); + + final TransitionInfo.Change change = new TransitionInfo.Change( + new WindowContainerToken(mock(IWindowContainerToken.class)), + mock(SurfaceControl.class)); + change.setMode(mode); + change.setTaskInfo(taskInfo); + return change; + } +}