Merge "Split window decor lifecycle to a TransitionObserver" into tm-qpr-dev

This commit is contained in:
Garfield Tan
2022-08-27 00:43:47 +00:00
committed by Android (Google) Code Review
5 changed files with 559 additions and 142 deletions

View File

@@ -55,6 +55,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler; 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.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.Pip;
@@ -207,8 +208,10 @@ public abstract class WMShellModule {
@DynamicOverride @DynamicOverride
static FreeformComponents provideFreeformComponents( static FreeformComponents provideFreeformComponents(
FreeformTaskListener<?> taskListener, FreeformTaskListener<?> taskListener,
FreeformTaskTransitionHandler transitionHandler) { FreeformTaskTransitionHandler transitionHandler,
return new FreeformComponents(taskListener, Optional.of(transitionHandler)); FreeformTaskTransitionObserver transitionObserver) {
return new FreeformComponents(
taskListener, Optional.of(transitionHandler), Optional.of(transitionObserver));
} }
@WMSingleton @WMSingleton
@@ -230,19 +233,22 @@ public abstract class WMShellModule {
@WMSingleton @WMSingleton
@Provides @Provides
static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler( static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
ShellInit shellInit,
Transitions transitions,
WindowDecorViewModel<?> windowDecorViewModel) {
return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel);
}
@WMSingleton
@Provides
static FreeformTaskTransitionObserver provideFreeformTaskTransitionObserver(
Context context, Context context,
ShellInit shellInit, ShellInit shellInit,
Transitions transitions, Transitions transitions,
WindowDecorViewModel<?> windowDecorViewModel,
FullscreenTaskListener<?> fullscreenTaskListener, FullscreenTaskListener<?> fullscreenTaskListener,
FreeformTaskListener<?> freeformTaskListener) { FreeformTaskListener<?> freeformTaskListener) {
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic return new FreeformTaskTransitionObserver(
// override for this controller from the base module context, shellInit, transitions, fullscreenTaskListener, freeformTaskListener);
ShellInit init = FreeformComponents.isFreeformEnabled(context)
? shellInit
: null;
return new FreeformTaskTransitionHandler(init, transitions,
windowDecorViewModel, fullscreenTaskListener, freeformTaskListener);
} }
// //

View File

@@ -33,16 +33,19 @@ import java.util.Optional;
*/ */
public class FreeformComponents { public class FreeformComponents {
public final ShellTaskOrganizer.TaskListener mTaskListener; public final ShellTaskOrganizer.TaskListener mTaskListener;
public final Optional<Transitions.TransitionHandler> mTaskTransitionHandler; public final Optional<Transitions.TransitionHandler> mTransitionHandler;
public final Optional<Transitions.TransitionObserver> mTransitionObserver;
/** /**
* Creates an instance with the given components. * Creates an instance with the given components.
*/ */
public FreeformComponents( public FreeformComponents(
ShellTaskOrganizer.TaskListener taskListener, ShellTaskOrganizer.TaskListener taskListener,
Optional<Transitions.TransitionHandler> taskTransitionHandler) { Optional<Transitions.TransitionHandler> transitionHandler,
Optional<Transitions.TransitionObserver> transitionObserver) {
mTaskListener = taskListener; mTaskListener = taskListener;
mTaskTransitionHandler = taskTransitionHandler; mTransitionHandler = transitionHandler;
mTransitionObserver = transitionObserver;
} }
/** /**

View File

@@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.WindowConfiguration; import android.app.WindowConfiguration;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.WindowManager; import android.view.WindowManager;
import android.window.TransitionInfo; import android.window.TransitionInfo;
@@ -32,7 +31,6 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -41,17 +39,13 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* The {@link Transitions.TransitionHandler} that handles freeform task launches, closes, * The {@link Transitions.TransitionHandler} that handles freeform task maximizing and restoring
* maximizing and restoring transitions. It also reports transitions so that window decorations can * transitions.
* be a part of transitions.
*/ */
public class FreeformTaskTransitionHandler public class FreeformTaskTransitionHandler
implements Transitions.TransitionHandler, FreeformTaskTransitionStarter { implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
private static final String TAG = "FreeformTH";
private final Transitions mTransitions; private final Transitions mTransitions;
private final FreeformTaskListener<?> mFreeformTaskListener;
private final FullscreenTaskListener<?> mFullscreenTaskListener;
private final WindowDecorViewModel<?> mWindowDecorViewModel; private final WindowDecorViewModel<?> mWindowDecorViewModel;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
@@ -59,21 +53,16 @@ public class FreeformTaskTransitionHandler
public FreeformTaskTransitionHandler( public FreeformTaskTransitionHandler(
ShellInit shellInit, ShellInit shellInit,
Transitions transitions, Transitions transitions,
WindowDecorViewModel<?> windowDecorViewModel, WindowDecorViewModel<?> windowDecorViewModel) {
FullscreenTaskListener<?> fullscreenTaskListener,
FreeformTaskListener<?> freeformTaskListener) {
mTransitions = transitions; mTransitions = transitions;
mFullscreenTaskListener = fullscreenTaskListener;
mFreeformTaskListener = freeformTaskListener;
mWindowDecorViewModel = windowDecorViewModel; mWindowDecorViewModel = windowDecorViewModel;
if (shellInit != null && Transitions.ENABLE_SHELL_TRANSITIONS) { if (Transitions.ENABLE_SHELL_TRANSITIONS) {
shellInit.addInitCallback(this::onInit, this); shellInit.addInitCallback(this::onInit, this);
} }
} }
private void onInit() { private void onInit() {
mWindowDecorViewModel.setFreeformTaskTransitionStarter(this); mWindowDecorViewModel.setFreeformTaskTransitionStarter(this);
mTransitions.addHandler(this);
} }
@Override @Override
@@ -101,13 +90,13 @@ public class FreeformTaskTransitionHandler
mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this)); mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this));
} }
@Override @Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction startT,
@NonNull SurfaceControl.Transaction finishT, @NonNull SurfaceControl.Transaction finishT,
@NonNull Transitions.TransitionFinishCallback finishCallback) { @NonNull Transitions.TransitionFinishCallback finishCallback) {
boolean transitionHandled = false; boolean transitionHandled = false;
final ArrayList<AutoCloseable> windowDecorsInCloseTransitions = new ArrayList<>();
for (TransitionInfo.Change change : info.getChanges()) { for (TransitionInfo.Change change : info.getChanges()) {
if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) { if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
continue; continue;
@@ -119,22 +108,13 @@ public class FreeformTaskTransitionHandler
} }
switch (change.getMode()) { 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: case WindowManager.TRANSIT_CHANGE:
transitionHandled |= startChangeTransition( transitionHandled |= startChangeTransition(
transition, info.getType(), change, startT, finishT); transition, info.getType(), change);
break; break;
case WindowManager.TRANSIT_TO_BACK: case WindowManager.TRANSIT_TO_BACK:
transitionHandled |= startMinimizeTransition(transition); transitionHandled |= startMinimizeTransition(transition);
break; break;
case WindowManager.TRANSIT_TO_FRONT:
break;
} }
} }
@@ -146,139 +126,43 @@ public class FreeformTaskTransitionHandler
startT.apply(); startT.apply();
mTransitions.getMainExecutor().execute( mTransitions.getMainExecutor().execute(
() -> finishTransition(windowDecorsInCloseTransitions, finishCallback)); () -> finishCallback.onTransitionFinished(null, null));
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<AutoCloseable> 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;
}
return true; return true;
} }
private boolean startChangeTransition( private boolean startChangeTransition(
IBinder transition, IBinder transition,
int type, int type,
TransitionInfo.Change change, TransitionInfo.Change change) {
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
AutoCloseable windowDecor = null;
if (!mPendingTransitionTokens.contains(transition)) { if (!mPendingTransitionTokens.contains(transition)) {
return false; return false;
} }
boolean handled = false; boolean handled = false;
boolean adopted = false;
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (type == Transitions.TRANSIT_MAXIMIZE if (type == Transitions.TRANSIT_MAXIMIZE
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
// TODO: Add maximize animations
handled = true; handled = true;
windowDecor = mFreeformTaskListener.giveWindowDecoration(
change.getTaskInfo(), startT, finishT);
adopted = mFullscreenTaskListener.adoptWindowDecoration(change,
startT, finishT, windowDecor);
} }
if (type == Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE if (type == Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
// TODO: Add restore animations
handled = true; handled = true;
windowDecor = mFullscreenTaskListener.giveWindowDecoration(
change.getTaskInfo(), startT, finishT);
adopted = mFreeformTaskListener.adoptWindowDecoration(change,
startT, finishT, windowDecor);
} }
if (!adopted) {
releaseWindowDecor(windowDecor);
}
return handled; return handled;
} }
private void finishTransition( private boolean startMinimizeTransition(IBinder transition) {
ArrayList<AutoCloseable> windowDecorsInCloseTransitions, return mPendingTransitionTokens.contains(transition);
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);
}
} }
@Nullable @Nullable
@Override @Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition, public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) { @NonNull TransitionRequestInfo request) {
final ActivityManager.RunningTaskInfo taskInfo = request.getTriggerTask(); return null;
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;
}
} }
} }

View File

@@ -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<IBinder, List<AutoCloseable>> 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<AutoCloseable> 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<AutoCloseable> 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<AutoCloseable> 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<AutoCloseable> 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<AutoCloseable> 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);
}
}
}

View File

@@ -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<Runnable> 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;
}
}