Loading libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +3 −6 Original line number Diff line number Diff line Loading @@ -388,11 +388,6 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { } // Sync Transactions can't operate simultaneously with shell transition collection. if (isUsingShellTransitions()) { if (mTaskViewTransitions.hasPending()) { // There is already a transition in-flight. The window bounds will be synced // once it is complete. return; } mTaskViewTransitions.setTaskBounds(this, boundsOnScreen); return; } Loading Loading @@ -489,12 +484,14 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { finishTransaction.reparent(mTaskLeash, mSurfaceControl) .setPosition(mTaskLeash, 0, 0) .apply(); mTaskViewTransitions.updateBoundsState(this, mTaskViewBase.getCurrentBoundsOnScreen()); mTaskViewTransitions.updateVisibilityState(this, true /* visible */); wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen()); } else { // The surface has already been destroyed before the task has appeared, // so go ahead and hide the task entirely wct.setHidden(mTaskToken, true /* hidden */); mTaskViewTransitions.updateVisibilityState(this, false /* visible */); // listener callback is below } if (newTask) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +68 −25 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.graphics.Rect; import android.os.IBinder; import android.util.ArrayMap; import android.util.Slog; import android.view.SurfaceControl; import android.view.WindowManager; Loading @@ -33,10 +34,13 @@ import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import androidx.annotation.VisibleForTesting; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.TransitionUtil; import java.util.ArrayList; import java.util.Objects; /** * Handles Shell Transitions that involve TaskView tasks. Loading @@ -44,7 +48,8 @@ import java.util.ArrayList; public class TaskViewTransitions implements Transitions.TransitionHandler { static final String TAG = "TaskViewTransitions"; private final ArrayList<TaskViewTaskController> mTaskViews = new ArrayList<>(); private final ArrayMap<TaskViewTaskController, TaskViewRequestedState> mTaskViews = new ArrayMap<>(); private final ArrayList<PendingTransition> mPending = new ArrayList<>(); private final Transitions mTransitions; private final boolean[] mRegistered = new boolean[]{ false }; Loading @@ -54,7 +59,8 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { * in-flight (collecting) at a time (because otherwise, the operations could get merged into * a single transition). So, keep a queue here until we add a queue in server-side. */ private static class PendingTransition { @VisibleForTesting static class PendingTransition { final @WindowManager.TransitionType int mType; final WindowContainerTransaction mWct; final @NonNull TaskViewTaskController mTaskView; Loading @@ -78,6 +84,14 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { } } /** * Visibility and bounds state that has been requested for a {@link TaskViewTaskController}. */ private static class TaskViewRequestedState { boolean mVisible; Rect mBounds = new Rect(); } public TaskViewTransitions(Transitions transitions) { mTransitions = transitions; // Defer registration until the first TaskView because we want this to be the "first" in Loading @@ -92,7 +106,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { mTransitions.addHandler(this); } } mTaskViews.add(tv); mTaskViews.put(tv, new TaskViewRequestedState()); } void removeTaskView(TaskViewTaskController tv) { Loading @@ -105,25 +119,31 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { } /** * Looks through the pending transitions for one matching `taskView`. * Looks through the pending transitions for a closing transaction that matches the provided * `taskView`. * @param taskView the pending transition should be for this. * @param closing When true, this only returns a pending transition of the close/hide type. * Otherwise it selects open/show. * @param latest When true, this will only check the most-recent pending transition for the * specified taskView. If it doesn't match `closing`, this will return null even * if there is a match earlier. The idea behind this is to check the state of * the taskviews "as if all transitions already happened". */ private PendingTransition findPending(TaskViewTaskController taskView, boolean closing, boolean latest) { private PendingTransition findPendingCloseTransition(TaskViewTaskController taskView) { for (int i = mPending.size() - 1; i >= 0; --i) { if (mPending.get(i).mTaskView != taskView) continue; if (TransitionUtil.isClosingType(mPending.get(i).mType) == closing) { if (TransitionUtil.isClosingType(mPending.get(i).mType)) { return mPending.get(i); } if (latest) { } return null; } /** * Looks through the pending transitions for one matching `taskView`. * @param taskView the pending transition should be for this. * @param type the type of transition it's looking for */ PendingTransition findPending(TaskViewTaskController taskView, int type) { for (int i = mPending.size() - 1; i >= 0; --i) { if (mPending.get(i).mTaskView != taskView) continue; if (mPending.get(i).mType == type) { return mPending.get(i); } } return null; } Loading Loading @@ -152,7 +172,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { if (taskView == null) return null; // Opening types should all be initiated by shell if (!TransitionUtil.isClosingType(request.getType())) return null; PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */); PendingTransition pending = findPendingCloseTransition(taskView); if (pending == null) { pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */); } Loading @@ -166,9 +186,9 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { private TaskViewTaskController findTaskView(ActivityManager.RunningTaskInfo taskInfo) { for (int i = 0; i < mTaskViews.size(); ++i) { if (mTaskViews.get(i).getTaskInfo() == null) continue; if (taskInfo.token.equals(mTaskViews.get(i).getTaskInfo().token)) { return mTaskViews.get(i); if (mTaskViews.keyAt(i).getTaskInfo() == null) continue; if (taskInfo.token.equals(mTaskViews.keyAt(i).getTaskInfo().token)) { return mTaskViews.keyAt(i); } } return null; Loading @@ -176,30 +196,53 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { void startTaskView(@NonNull WindowContainerTransaction wct, @NonNull TaskViewTaskController taskView, @NonNull IBinder launchCookie) { updateVisibilityState(taskView, true /* visible */); mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView, launchCookie)); startNextTransition(); } void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) { PendingTransition pending = findPending(taskView, !visible, true /* latest */); if (pending != null) { // Already opening or creating a task, so no need to do anything here. return; } if (mTaskViews.get(taskView).mVisible == visible) return; if (taskView.getTaskInfo() == null) { // Nothing to update, task is not yet available return; } mTaskViews.get(taskView).mVisible = visible; final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */); pending = new PendingTransition( wct.setBounds(taskView.getTaskInfo().token, mTaskViews.get(taskView).mBounds); PendingTransition pending = new PendingTransition( visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView, null /* cookie */); mPending.add(pending); startNextTransition(); // visibility is reported in transition. } void updateBoundsState(TaskViewTaskController taskView, Rect boundsOnScreen) { TaskViewRequestedState state = mTaskViews.get(taskView); state.mBounds.set(boundsOnScreen); } void updateVisibilityState(TaskViewTaskController taskView, boolean visible) { TaskViewRequestedState state = mTaskViews.get(taskView); state.mVisible = visible; } void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) { TaskViewRequestedState state = mTaskViews.get(taskView); if (Objects.equals(boundsOnScreen, state.mBounds)) { return; } state.mBounds.set(boundsOnScreen); if (!state.mVisible) { // Task view isn't visible, the bounds will next visibility update. return; } if (hasPending()) { // There is already a transition in-flight, the window bounds will be set in // prepareOpenAnimation. return; } WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setBounds(taskView.getTaskInfo().token, boundsOnScreen); mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */)); Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java +49 −0 Original line number Diff line number Diff line Loading @@ -471,4 +471,53 @@ public class TaskViewTest extends ShellTestCase { assertThat(insetsInfo.touchableRegion.contains(20, 20)).isFalse(); assertThat(insetsInfo.touchableRegion.contains(30, 30)).isFalse(); } @Test public void testTaskViewPrepareOpenAnimationSetsBoundsAndVisibility() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); TaskViewBase taskViewBase = mock(TaskViewBase.class); Rect bounds = new Rect(0, 0, 100, 100); when(taskViewBase.getCurrentBoundsOnScreen()).thenReturn(bounds); mTaskViewTaskController.setTaskViewBase(taskViewBase); // Surface created, but task not available so bounds / visibility isn't set mTaskView.surfaceCreated(mock(SurfaceHolder.class)); verify(mTaskViewTransitions, never()).updateVisibilityState( eq(mTaskViewTaskController), eq(true)); // Make the task available / start prepareOpen WindowContainerTransaction wct = mock(WindowContainerTransaction.class); mTaskViewTaskController.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); // Bounds got set verify(wct).setBounds(any(WindowContainerToken.class), eq(bounds)); // Visibility & bounds state got set verify(mTaskViewTransitions).updateVisibilityState(eq(mTaskViewTaskController), eq(true)); verify(mTaskViewTransitions).updateBoundsState(eq(mTaskViewTaskController), eq(bounds)); } @Test public void testTaskViewPrepareOpenAnimationSetsVisibilityFalse() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); TaskViewBase taskViewBase = mock(TaskViewBase.class); Rect bounds = new Rect(0, 0, 100, 100); when(taskViewBase.getCurrentBoundsOnScreen()).thenReturn(bounds); mTaskViewTaskController.setTaskViewBase(taskViewBase); // Task is available, but the surface was never created WindowContainerTransaction wct = mock(WindowContainerTransaction.class); mTaskViewTaskController.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); // Bounds do not get set as there is no surface verify(wct, never()).setBounds(any(WindowContainerToken.class), any()); // Visibility is set to false, bounds aren't set verify(mTaskViewTransitions).updateVisibilityState(eq(mTaskViewTaskController), eq(false)); verify(mTaskViewTransitions, never()).updateBoundsState(eq(mTaskViewTaskController), any()); } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java 0 → 100644 +182 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.taskview; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assume.assumeTrue; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.graphics.Rect; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerToken; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class TaskViewTransitionsTest extends ShellTestCase { @Mock Transitions mTransitions; @Mock TaskViewTaskController mTaskViewTaskController; @Mock ActivityManager.RunningTaskInfo mTaskInfo; @Mock WindowContainerToken mToken; TaskViewTransitions mTaskViewTransitions; @Before public void setUp() { MockitoAnnotations.initMocks(this); if (Transitions.ENABLE_SHELL_TRANSITIONS) { doReturn(true).when(mTransitions).isRegistered(); } mTaskInfo = new ActivityManager.RunningTaskInfo(); mTaskInfo.token = mToken; mTaskInfo.taskId = 314; mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class); mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions)); mTaskViewTransitions.addTaskView(mTaskViewTaskController); when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo); } @Test public void testSetTaskBounds_taskNotVisible_noTransaction() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, false); mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, new Rect(0, 0, 100, 100)); assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE)) .isNull(); } @Test public void testSetTaskBounds_taskVisible_boundsChangeTransaction() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true); // Consume the pending transaction from visibility change TaskViewTransitions.PendingTransition pending = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); assertThat(pending).isNotNull(); mTaskViewTransitions.startAnimation(pending.mClaimed, mock(TransitionInfo.class), new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mock(Transitions.TransitionFinishCallback.class)); // Verify it was consumed TaskViewTransitions.PendingTransition pending2 = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); assertThat(pending2).isNull(); // Test that set bounds creates a new transaction mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, new Rect(0, 0, 100, 100)); assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE)) .isNotNull(); } @Test public void testSetTaskBounds_taskVisibleWithPending_noTransaction() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true); TaskViewTransitions.PendingTransition pending = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); assertThat(pending).isNotNull(); mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, new Rect(0, 0, 100, 100)); assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE)) .isNull(); } @Test public void testSetTaskBounds_sameBounds_noTransaction() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true); // Consume the pending transaction from visibility change TaskViewTransitions.PendingTransition pending = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); assertThat(pending).isNotNull(); mTaskViewTransitions.startAnimation(pending.mClaimed, mock(TransitionInfo.class), new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mock(Transitions.TransitionFinishCallback.class)); // Verify it was consumed TaskViewTransitions.PendingTransition pending2 = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); assertThat(pending2).isNull(); // Test that set bounds creates a new transaction mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, new Rect(0, 0, 100, 100)); TaskViewTransitions.PendingTransition pendingBounds = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE); assertThat(pendingBounds).isNotNull(); // Consume the pending bounds transaction mTaskViewTransitions.startAnimation(pendingBounds.mClaimed, mock(TransitionInfo.class), new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mock(Transitions.TransitionFinishCallback.class)); // Verify it was consumed TaskViewTransitions.PendingTransition pendingBounds1 = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE); assertThat(pendingBounds1).isNull(); // Test that setting the same bounds doesn't creates a new transaction mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, new Rect(0, 0, 100, 100)); TaskViewTransitions.PendingTransition pendingBounds2 = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE); assertThat(pendingBounds2).isNull(); } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +3 −6 Original line number Diff line number Diff line Loading @@ -388,11 +388,6 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { } // Sync Transactions can't operate simultaneously with shell transition collection. if (isUsingShellTransitions()) { if (mTaskViewTransitions.hasPending()) { // There is already a transition in-flight. The window bounds will be synced // once it is complete. return; } mTaskViewTransitions.setTaskBounds(this, boundsOnScreen); return; } Loading Loading @@ -489,12 +484,14 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { finishTransaction.reparent(mTaskLeash, mSurfaceControl) .setPosition(mTaskLeash, 0, 0) .apply(); mTaskViewTransitions.updateBoundsState(this, mTaskViewBase.getCurrentBoundsOnScreen()); mTaskViewTransitions.updateVisibilityState(this, true /* visible */); wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen()); } else { // The surface has already been destroyed before the task has appeared, // so go ahead and hide the task entirely wct.setHidden(mTaskToken, true /* hidden */); mTaskViewTransitions.updateVisibilityState(this, false /* visible */); // listener callback is below } if (newTask) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +68 −25 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.graphics.Rect; import android.os.IBinder; import android.util.ArrayMap; import android.util.Slog; import android.view.SurfaceControl; import android.view.WindowManager; Loading @@ -33,10 +34,13 @@ import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import androidx.annotation.VisibleForTesting; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.TransitionUtil; import java.util.ArrayList; import java.util.Objects; /** * Handles Shell Transitions that involve TaskView tasks. Loading @@ -44,7 +48,8 @@ import java.util.ArrayList; public class TaskViewTransitions implements Transitions.TransitionHandler { static final String TAG = "TaskViewTransitions"; private final ArrayList<TaskViewTaskController> mTaskViews = new ArrayList<>(); private final ArrayMap<TaskViewTaskController, TaskViewRequestedState> mTaskViews = new ArrayMap<>(); private final ArrayList<PendingTransition> mPending = new ArrayList<>(); private final Transitions mTransitions; private final boolean[] mRegistered = new boolean[]{ false }; Loading @@ -54,7 +59,8 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { * in-flight (collecting) at a time (because otherwise, the operations could get merged into * a single transition). So, keep a queue here until we add a queue in server-side. */ private static class PendingTransition { @VisibleForTesting static class PendingTransition { final @WindowManager.TransitionType int mType; final WindowContainerTransaction mWct; final @NonNull TaskViewTaskController mTaskView; Loading @@ -78,6 +84,14 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { } } /** * Visibility and bounds state that has been requested for a {@link TaskViewTaskController}. */ private static class TaskViewRequestedState { boolean mVisible; Rect mBounds = new Rect(); } public TaskViewTransitions(Transitions transitions) { mTransitions = transitions; // Defer registration until the first TaskView because we want this to be the "first" in Loading @@ -92,7 +106,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { mTransitions.addHandler(this); } } mTaskViews.add(tv); mTaskViews.put(tv, new TaskViewRequestedState()); } void removeTaskView(TaskViewTaskController tv) { Loading @@ -105,25 +119,31 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { } /** * Looks through the pending transitions for one matching `taskView`. * Looks through the pending transitions for a closing transaction that matches the provided * `taskView`. * @param taskView the pending transition should be for this. * @param closing When true, this only returns a pending transition of the close/hide type. * Otherwise it selects open/show. * @param latest When true, this will only check the most-recent pending transition for the * specified taskView. If it doesn't match `closing`, this will return null even * if there is a match earlier. The idea behind this is to check the state of * the taskviews "as if all transitions already happened". */ private PendingTransition findPending(TaskViewTaskController taskView, boolean closing, boolean latest) { private PendingTransition findPendingCloseTransition(TaskViewTaskController taskView) { for (int i = mPending.size() - 1; i >= 0; --i) { if (mPending.get(i).mTaskView != taskView) continue; if (TransitionUtil.isClosingType(mPending.get(i).mType) == closing) { if (TransitionUtil.isClosingType(mPending.get(i).mType)) { return mPending.get(i); } if (latest) { } return null; } /** * Looks through the pending transitions for one matching `taskView`. * @param taskView the pending transition should be for this. * @param type the type of transition it's looking for */ PendingTransition findPending(TaskViewTaskController taskView, int type) { for (int i = mPending.size() - 1; i >= 0; --i) { if (mPending.get(i).mTaskView != taskView) continue; if (mPending.get(i).mType == type) { return mPending.get(i); } } return null; } Loading Loading @@ -152,7 +172,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { if (taskView == null) return null; // Opening types should all be initiated by shell if (!TransitionUtil.isClosingType(request.getType())) return null; PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */); PendingTransition pending = findPendingCloseTransition(taskView); if (pending == null) { pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */); } Loading @@ -166,9 +186,9 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { private TaskViewTaskController findTaskView(ActivityManager.RunningTaskInfo taskInfo) { for (int i = 0; i < mTaskViews.size(); ++i) { if (mTaskViews.get(i).getTaskInfo() == null) continue; if (taskInfo.token.equals(mTaskViews.get(i).getTaskInfo().token)) { return mTaskViews.get(i); if (mTaskViews.keyAt(i).getTaskInfo() == null) continue; if (taskInfo.token.equals(mTaskViews.keyAt(i).getTaskInfo().token)) { return mTaskViews.keyAt(i); } } return null; Loading @@ -176,30 +196,53 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { void startTaskView(@NonNull WindowContainerTransaction wct, @NonNull TaskViewTaskController taskView, @NonNull IBinder launchCookie) { updateVisibilityState(taskView, true /* visible */); mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView, launchCookie)); startNextTransition(); } void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) { PendingTransition pending = findPending(taskView, !visible, true /* latest */); if (pending != null) { // Already opening or creating a task, so no need to do anything here. return; } if (mTaskViews.get(taskView).mVisible == visible) return; if (taskView.getTaskInfo() == null) { // Nothing to update, task is not yet available return; } mTaskViews.get(taskView).mVisible = visible; final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */); pending = new PendingTransition( wct.setBounds(taskView.getTaskInfo().token, mTaskViews.get(taskView).mBounds); PendingTransition pending = new PendingTransition( visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView, null /* cookie */); mPending.add(pending); startNextTransition(); // visibility is reported in transition. } void updateBoundsState(TaskViewTaskController taskView, Rect boundsOnScreen) { TaskViewRequestedState state = mTaskViews.get(taskView); state.mBounds.set(boundsOnScreen); } void updateVisibilityState(TaskViewTaskController taskView, boolean visible) { TaskViewRequestedState state = mTaskViews.get(taskView); state.mVisible = visible; } void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) { TaskViewRequestedState state = mTaskViews.get(taskView); if (Objects.equals(boundsOnScreen, state.mBounds)) { return; } state.mBounds.set(boundsOnScreen); if (!state.mVisible) { // Task view isn't visible, the bounds will next visibility update. return; } if (hasPending()) { // There is already a transition in-flight, the window bounds will be set in // prepareOpenAnimation. return; } WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setBounds(taskView.getTaskInfo().token, boundsOnScreen); mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */)); Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java +49 −0 Original line number Diff line number Diff line Loading @@ -471,4 +471,53 @@ public class TaskViewTest extends ShellTestCase { assertThat(insetsInfo.touchableRegion.contains(20, 20)).isFalse(); assertThat(insetsInfo.touchableRegion.contains(30, 30)).isFalse(); } @Test public void testTaskViewPrepareOpenAnimationSetsBoundsAndVisibility() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); TaskViewBase taskViewBase = mock(TaskViewBase.class); Rect bounds = new Rect(0, 0, 100, 100); when(taskViewBase.getCurrentBoundsOnScreen()).thenReturn(bounds); mTaskViewTaskController.setTaskViewBase(taskViewBase); // Surface created, but task not available so bounds / visibility isn't set mTaskView.surfaceCreated(mock(SurfaceHolder.class)); verify(mTaskViewTransitions, never()).updateVisibilityState( eq(mTaskViewTaskController), eq(true)); // Make the task available / start prepareOpen WindowContainerTransaction wct = mock(WindowContainerTransaction.class); mTaskViewTaskController.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); // Bounds got set verify(wct).setBounds(any(WindowContainerToken.class), eq(bounds)); // Visibility & bounds state got set verify(mTaskViewTransitions).updateVisibilityState(eq(mTaskViewTaskController), eq(true)); verify(mTaskViewTransitions).updateBoundsState(eq(mTaskViewTaskController), eq(bounds)); } @Test public void testTaskViewPrepareOpenAnimationSetsVisibilityFalse() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); TaskViewBase taskViewBase = mock(TaskViewBase.class); Rect bounds = new Rect(0, 0, 100, 100); when(taskViewBase.getCurrentBoundsOnScreen()).thenReturn(bounds); mTaskViewTaskController.setTaskViewBase(taskViewBase); // Task is available, but the surface was never created WindowContainerTransaction wct = mock(WindowContainerTransaction.class); mTaskViewTaskController.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); // Bounds do not get set as there is no surface verify(wct, never()).setBounds(any(WindowContainerToken.class), any()); // Visibility is set to false, bounds aren't set verify(mTaskViewTransitions).updateVisibilityState(eq(mTaskViewTaskController), eq(false)); verify(mTaskViewTransitions, never()).updateBoundsState(eq(mTaskViewTaskController), any()); } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java 0 → 100644 +182 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.taskview; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assume.assumeTrue; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.graphics.Rect; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerToken; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class TaskViewTransitionsTest extends ShellTestCase { @Mock Transitions mTransitions; @Mock TaskViewTaskController mTaskViewTaskController; @Mock ActivityManager.RunningTaskInfo mTaskInfo; @Mock WindowContainerToken mToken; TaskViewTransitions mTaskViewTransitions; @Before public void setUp() { MockitoAnnotations.initMocks(this); if (Transitions.ENABLE_SHELL_TRANSITIONS) { doReturn(true).when(mTransitions).isRegistered(); } mTaskInfo = new ActivityManager.RunningTaskInfo(); mTaskInfo.token = mToken; mTaskInfo.taskId = 314; mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class); mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions)); mTaskViewTransitions.addTaskView(mTaskViewTaskController); when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo); } @Test public void testSetTaskBounds_taskNotVisible_noTransaction() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, false); mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, new Rect(0, 0, 100, 100)); assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE)) .isNull(); } @Test public void testSetTaskBounds_taskVisible_boundsChangeTransaction() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true); // Consume the pending transaction from visibility change TaskViewTransitions.PendingTransition pending = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); assertThat(pending).isNotNull(); mTaskViewTransitions.startAnimation(pending.mClaimed, mock(TransitionInfo.class), new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mock(Transitions.TransitionFinishCallback.class)); // Verify it was consumed TaskViewTransitions.PendingTransition pending2 = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); assertThat(pending2).isNull(); // Test that set bounds creates a new transaction mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, new Rect(0, 0, 100, 100)); assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE)) .isNotNull(); } @Test public void testSetTaskBounds_taskVisibleWithPending_noTransaction() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true); TaskViewTransitions.PendingTransition pending = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); assertThat(pending).isNotNull(); mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, new Rect(0, 0, 100, 100)); assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE)) .isNull(); } @Test public void testSetTaskBounds_sameBounds_noTransaction() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true); // Consume the pending transaction from visibility change TaskViewTransitions.PendingTransition pending = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); assertThat(pending).isNotNull(); mTaskViewTransitions.startAnimation(pending.mClaimed, mock(TransitionInfo.class), new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mock(Transitions.TransitionFinishCallback.class)); // Verify it was consumed TaskViewTransitions.PendingTransition pending2 = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); assertThat(pending2).isNull(); // Test that set bounds creates a new transaction mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, new Rect(0, 0, 100, 100)); TaskViewTransitions.PendingTransition pendingBounds = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE); assertThat(pendingBounds).isNotNull(); // Consume the pending bounds transaction mTaskViewTransitions.startAnimation(pendingBounds.mClaimed, mock(TransitionInfo.class), new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mock(Transitions.TransitionFinishCallback.class)); // Verify it was consumed TaskViewTransitions.PendingTransition pendingBounds1 = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE); assertThat(pendingBounds1).isNull(); // Test that setting the same bounds doesn't creates a new transaction mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, new Rect(0, 0, 100, 100)); TaskViewTransitions.PendingTransition pendingBounds2 = mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE); assertThat(pendingBounds2).isNull(); } }