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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -33,16 +33,19 @@ import java.util.Optional;
|
||||
*/
|
||||
public class FreeformComponents {
|
||||
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.
|
||||
*/
|
||||
public FreeformComponents(
|
||||
ShellTaskOrganizer.TaskListener taskListener,
|
||||
Optional<Transitions.TransitionHandler> taskTransitionHandler) {
|
||||
Optional<Transitions.TransitionHandler> transitionHandler,
|
||||
Optional<Transitions.TransitionObserver> transitionObserver) {
|
||||
mTaskListener = taskListener;
|
||||
mTaskTransitionHandler = taskTransitionHandler;
|
||||
mTransitionHandler = transitionHandler;
|
||||
mTransitionObserver = transitionObserver;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<IBinder> 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<AutoCloseable> 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<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;
|
||||
}
|
||||
() -> 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<AutoCloseable> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user