From 5959b4cd9668b06256f6e7d16db00b50c138d662 Mon Sep 17 00:00:00 2001 From: Garfield Tan Date: Mon, 15 Aug 2022 13:57:00 -0700 Subject: [PATCH] Split window decor lifecycle to a TransitionObserver There are several transitions involving window decorations that are handled by Launcher, but we still need to tie window decorations to the lifecycle of such transitions to release window decorations at the right time. Thus we move this part of logic into a new class. Bug: 241975249 Test: atest FreeformTaskTransitionObserverTest Change-Id: Iff14be6f1fcb0b8ee7f634d5d41567348fff5c06 --- .../wm/shell/dagger/WMShellModule.java | 26 +- .../wm/shell/freeform/FreeformComponents.java | 9 +- .../FreeformTaskTransitionHandler.java | 142 +------- .../FreeformTaskTransitionObserver.java | 222 +++++++++++++ .../FreeformTaskTransitionObserverTest.java | 302 ++++++++++++++++++ 5 files changed, 559 insertions(+), 142 deletions(-) create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java 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; + } +}