Merge "Register remote animation per Task" into tm-dev

This commit is contained in:
Chris Li
2022-04-02 10:36:24 +00:00
committed by Android (Google) Code Review
13 changed files with 363 additions and 106 deletions

View File

@@ -35,15 +35,15 @@ interface ITaskFragmentOrganizerController {
/** /**
* Registers remote animations per transition type for the organizer. It will override the * Registers remote animations per transition type for the organizer. It will override the
* animations if the transition only contains windows that belong to the organized * animations if the transition only contains windows that belong to the organized
* TaskFragments. * TaskFragments in the given Task.
*/ */
void registerRemoteAnimations(in ITaskFragmentOrganizer organizer, void registerRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId,
in RemoteAnimationDefinition definition); in RemoteAnimationDefinition definition);
/** /**
* Unregisters remote animations per transition type for the organizer. * Unregisters remote animations per transition type for the organizer.
*/ */
void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer); void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId);
/** /**
* Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and

View File

@@ -94,13 +94,16 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
/** /**
* Registers remote animations per transition type for the organizer. It will override the * Registers remote animations per transition type for the organizer. It will override the
* animations if the transition only contains windows that belong to the organized * animations if the transition only contains windows that belong to the organized
* TaskFragments. * TaskFragments in the given Task.
*
* @param taskId overrides if the transition only contains windows belonging to this Task.
* @hide * @hide
*/ */
@CallSuper @CallSuper
public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) { public void registerRemoteAnimations(int taskId,
@NonNull RemoteAnimationDefinition definition) {
try { try {
getController().registerRemoteAnimations(mInterface, definition); getController().registerRemoteAnimations(mInterface, taskId, definition);
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); throw e.rethrowFromSystemServer();
} }
@@ -111,9 +114,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
* @hide * @hide
*/ */
@CallSuper @CallSuper
public void unregisterRemoteAnimations() { public void unregisterRemoteAnimations(int taskId) {
try { try {
getController().unregisterRemoteAnimations(mInterface); getController().unregisterRemoteAnimations(mInterface, taskId);
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); throw e.rethrowFromSystemServer();
} }

View File

@@ -35,6 +35,8 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -56,7 +58,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
final Map<IBinder, Configuration> mFragmentParentConfigs = new ArrayMap<>(); final Map<IBinder, Configuration> mFragmentParentConfigs = new ArrayMap<>();
private final TaskFragmentCallback mCallback; private final TaskFragmentCallback mCallback;
private TaskFragmentAnimationController mAnimationController; @VisibleForTesting
TaskFragmentAnimationController mAnimationController;
/** /**
* Callback that notifies the controller about changes to task fragments. * Callback that notifies the controller about changes to task fragments.
@@ -80,21 +83,25 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
@Override @Override
public void unregisterOrganizer() { public void unregisterOrganizer() {
stopOverrideSplitAnimation(); if (mAnimationController != null) {
mAnimationController = null; mAnimationController.unregisterAllRemoteAnimations();
mAnimationController = null;
}
super.unregisterOrganizer(); super.unregisterOrganizer();
} }
void startOverrideSplitAnimation() { /** Overrides the animation if the transition is on the given Task. */
void startOverrideSplitAnimation(int taskId) {
if (mAnimationController == null) { if (mAnimationController == null) {
mAnimationController = new TaskFragmentAnimationController(this); mAnimationController = new TaskFragmentAnimationController(this);
} }
mAnimationController.registerRemoteAnimations(); mAnimationController.registerRemoteAnimations(taskId);
} }
void stopOverrideSplitAnimation() { /** No longer overrides the animation if the transition is on the given Task. */
void stopOverrideSplitAnimation(int taskId) {
if (mAnimationController != null) { if (mAnimationController != null) {
mAnimationController.unregisterRemoteAnimations(); mAnimationController.unregisterRemoteAnimations(taskId);
} }
} }

View File

@@ -37,6 +37,7 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.util.ArraySet;
import android.util.SparseArray; import android.util.SparseArray;
import android.window.TaskFragmentInfo; import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
@@ -75,10 +76,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private Consumer<List<SplitInfo>> mEmbeddingCallback; private Consumer<List<SplitInfo>> mEmbeddingCallback;
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
// We currently only support split activity embedding within the one root Task.
// TODO(b/207720388): move to TaskContainer
private final Rect mParentBounds = new Rect();
public SplitController() { public SplitController() {
mPresenter = new SplitPresenter(new MainThreadExecutor(), this); mPresenter = new SplitPresenter(new MainThreadExecutor(), this);
ActivityThread activityThread = ActivityThread.currentActivityThread(); ActivityThread activityThread = ActivityThread.currentActivityThread();
@@ -95,7 +92,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) { public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
mSplitRules.clear(); mSplitRules.clear();
mSplitRules.addAll(rules); mSplitRules.addAll(rules);
updateAnimationOverride(); for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
updateAnimationOverride(mTaskContainers.keyAt(i));
}
} }
@NonNull @NonNull
@@ -163,38 +162,49 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override @Override
public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) { if (container != null) {
return; // Cleanup if the TaskFragment vanished is not requested by the organizer.
mPresenter.cleanupContainer(container, true /* shouldFinishDependent */);
updateCallbackIfNecessary();
} }
cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
mPresenter.cleanupContainer(container, true /* shouldFinishDependent */);
updateCallbackIfNecessary();
} }
@Override @Override
public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
@NonNull Configuration parentConfig) { @NonNull Configuration parentConfig) {
onParentBoundsMayChange(parentConfig.windowConfiguration.getBounds());
TaskFragmentContainer container = getContainer(fragmentToken); TaskFragmentContainer container = getContainer(fragmentToken);
if (container != null) { if (container != null) {
onTaskBoundsMayChange(container.getTaskId(),
parentConfig.windowConfiguration.getBounds());
mPresenter.updateContainer(container); mPresenter.updateContainer(container);
updateCallbackIfNecessary(); updateCallbackIfNecessary();
} }
} }
private void onParentBoundsMayChange(Activity activity) { /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
if (activity.isFinishing()) { private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final TaskContainer taskContainer = mTaskContainers.valueAt(i);
if (!taskContainer.mFinishedContainer.remove(taskFragmentToken)) {
continue;
}
if (taskContainer.isEmpty()) {
// Cleanup the TaskContainer if it becomes empty.
mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId);
mTaskContainers.remove(taskContainer.mTaskId);
}
return; return;
} }
onParentBoundsMayChange(mPresenter.getParentContainerBounds(activity));
} }
private void onParentBoundsMayChange(Rect parentBounds) { private void onTaskBoundsMayChange(int taskId, @NonNull Rect taskBounds) {
if (!parentBounds.isEmpty() && !mParentBounds.equals(parentBounds)) { final TaskContainer taskContainer = mTaskContainers.get(taskId);
mParentBounds.set(parentBounds); if (taskContainer != null && !taskBounds.isEmpty()
updateAnimationOverride(); && !taskContainer.mTaskBounds.equals(taskBounds)) {
taskContainer.mTaskBounds.set(taskBounds);
updateAnimationOverride(taskId);
} }
} }
@@ -202,9 +212,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Updates if we should override transition animation. We only want to override if the Task * Updates if we should override transition animation. We only want to override if the Task
* bounds is large enough for at least one split rule. * bounds is large enough for at least one split rule.
*/ */
private void updateAnimationOverride() { private void updateAnimationOverride(int taskId) {
if (mParentBounds.isEmpty()) { final TaskContainer taskContainer = mTaskContainers.get(taskId);
// We don't know about the parent bounds yet. if (taskContainer == null || !taskContainer.isTaskBoundsInitialized()) {
// We don't know about the Task bounds yet.
return; return;
} }
@@ -214,7 +225,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (!(rule instanceof SplitRule)) { if (!(rule instanceof SplitRule)) {
continue; continue;
} }
if (mPresenter.shouldShowSideBySide(mParentBounds, (SplitRule) rule)) { if (mPresenter.shouldShowSideBySide(taskContainer.mTaskBounds, (SplitRule) rule)) {
supportSplit = true; supportSplit = true;
break; break;
} }
@@ -222,9 +233,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// We only want to override if it supports split. // We only want to override if it supports split.
if (supportSplit) { if (supportSplit) {
mPresenter.startOverrideSplitAnimation(); mPresenter.startOverrideSplitAnimation(taskId);
} else { } else {
mPresenter.stopOverrideSplitAnimation(); mPresenter.stopOverrideSplitAnimation(taskId);
} }
} }
@@ -243,11 +254,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskFragmentContainer currentContainer = getContainerWithActivity( final TaskFragmentContainer currentContainer = getContainerWithActivity(
launchedActivity.getActivityToken()); launchedActivity.getActivityToken());
if (currentContainer == null) {
// Initial check before any TaskFragment is created.
onParentBoundsMayChange(launchedActivity);
}
// Check if the activity is configured to always be expanded. // Check if the activity is configured to always be expanded.
if (shouldExpand(launchedActivity, null, splitRules)) { if (shouldExpand(launchedActivity, null, splitRules)) {
if (shouldContainerBeExpanded(currentContainer)) { if (shouldContainerBeExpanded(currentContainer)) {
@@ -326,8 +332,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// onTaskFragmentParentInfoChanged // onTaskFragmentParentInfoChanged
return; return;
} }
// The bounds of the container may have been changed.
onParentBoundsMayChange(activity);
// Check if activity requires a placeholder // Check if activity requires a placeholder
launchPlaceholderIfNecessary(activity); launchPlaceholderIfNecessary(activity);
@@ -357,9 +361,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer newContainer(@Nullable Activity activity, int taskId) { TaskFragmentContainer newContainer(@Nullable Activity activity, int taskId) {
final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId); final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId);
if (!mTaskContainers.contains(taskId)) { if (!mTaskContainers.contains(taskId)) {
mTaskContainers.put(taskId, new TaskContainer()); mTaskContainers.put(taskId, new TaskContainer(taskId));
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
taskContainer.mContainers.add(container);
if (activity != null && !taskContainer.isTaskBoundsInitialized()) {
// Initial check before any TaskFragment has appeared.
onTaskBoundsMayChange(taskId, SplitPresenter.getTaskBoundsFromActivity(activity));
} }
mTaskContainers.get(taskId).mContainers.add(container);
return container; return container;
} }
@@ -391,11 +400,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return; return;
} }
taskContainer.mContainers.remove(container); taskContainer.mContainers.remove(container);
if (taskContainer.mContainers.isEmpty()) { // Marked as a pending removal which will be removed after it is actually removed on the
mTaskContainers.remove(taskId); // server side (#onTaskFragmentVanished).
// No more TaskFragment in this Task, so no need to check split container. // In this way, we can keep track of the Task bounds until we no longer have any
return; // TaskFragment there.
} taskContainer.mFinishedContainer.add(container.getTaskFragmentToken());
final List<SplitContainer> containersToRemove = new ArrayList<>(); final List<SplitContainer> containersToRemove = new ArrayList<>();
for (SplitContainer splitContainer : taskContainer.mSplitContainers) { for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
@@ -543,7 +552,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
boolean launchPlaceholderIfNecessary(@NonNull Activity activity) { boolean launchPlaceholderIfNecessary(@NonNull Activity activity) {
final TaskFragmentContainer container = getContainerWithActivity( final TaskFragmentContainer container = getContainerWithActivity(
activity.getActivityToken()); activity.getActivityToken());
// Don't launch placeholder if the container is occluded. // Don't launch placeholder if the container is occluded.
if (container != null && container != getTopActiveContainer(container.getTaskId())) { if (container != null && container != getTopActiveContainer(container.getTaskId())) {
@@ -1035,7 +1044,30 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** Represents TaskFragments and split pairs below a Task. */ /** Represents TaskFragments and split pairs below a Task. */
@VisibleForTesting @VisibleForTesting
static class TaskContainer { static class TaskContainer {
/** The unique task id. */
final int mTaskId;
/** Active TaskFragments in this Task. */
final List<TaskFragmentContainer> mContainers = new ArrayList<>(); final List<TaskFragmentContainer> mContainers = new ArrayList<>();
/** Active split pairs in this Task. */
final List<SplitContainer> mSplitContainers = new ArrayList<>(); final List<SplitContainer> mSplitContainers = new ArrayList<>();
/**
* TaskFragments that the organizer has requested to be closed. They should be removed when
* the organizer receives {@link #onTaskFragmentVanished(TaskFragmentInfo)} event for them.
*/
final Set<IBinder> mFinishedContainer = new ArraySet<>();
/** Available window bounds of this Task. */
final Rect mTaskBounds = new Rect();
TaskContainer(int taskId) {
mTaskId = taskId;
}
boolean isEmpty() {
return mContainers.isEmpty() && mFinishedContainer.isEmpty();
}
boolean isTaskBoundsInitialized() {
return !mTaskBounds.isEmpty();
}
} }
} }

View File

@@ -419,7 +419,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
} }
} }
// TODO(b/190433398): Check if the client-side available info about parent bounds is enough. return getTaskBoundsFromActivity(activity);
}
@NonNull
static Rect getTaskBoundsFromActivity(@NonNull Activity activity) {
if (!activity.isInMultiWindowMode()) { if (!activity.isInMultiWindowMode()) {
// In fullscreen mode the max bounds should correspond to the task bounds. // In fullscreen mode the max bounds should correspond to the task bounds.
return activity.getResources().getConfiguration().windowConfiguration.getMaxBounds(); return activity.getResources().getConfiguration().windowConfiguration.getMaxBounds();

View File

@@ -24,11 +24,14 @@ import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN; import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import android.util.ArraySet;
import android.util.Log; import android.util.Log;
import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationDefinition;
import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentOrganizer;
import com.android.internal.annotations.VisibleForTesting;
/** Controls the TaskFragment remote animations. */ /** Controls the TaskFragment remote animations. */
class TaskFragmentAnimationController { class TaskFragmentAnimationController {
@@ -37,8 +40,10 @@ class TaskFragmentAnimationController {
private final TaskFragmentOrganizer mOrganizer; private final TaskFragmentOrganizer mOrganizer;
private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner(); private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
private final RemoteAnimationDefinition mDefinition; @VisibleForTesting
private boolean mIsRegister; final RemoteAnimationDefinition mDefinition;
/** Task Ids that we have registered for remote animation. */
private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
TaskFragmentAnimationController(TaskFragmentOrganizer organizer) { TaskFragmentAnimationController(TaskFragmentOrganizer organizer) {
mOrganizer = organizer; mOrganizer = organizer;
@@ -54,25 +59,32 @@ class TaskFragmentAnimationController {
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter); mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
} }
void registerRemoteAnimations() { void registerRemoteAnimations(int taskId) {
if (DEBUG) { if (DEBUG) {
Log.v(TAG, "registerRemoteAnimations"); Log.v(TAG, "registerRemoteAnimations");
} }
if (mIsRegister) { if (mRegisterTasks.contains(taskId)) {
return; return;
} }
mOrganizer.registerRemoteAnimations(mDefinition); mOrganizer.registerRemoteAnimations(taskId, mDefinition);
mIsRegister = true; mRegisterTasks.add(taskId);
} }
void unregisterRemoteAnimations() { void unregisterRemoteAnimations(int taskId) {
if (DEBUG) { if (DEBUG) {
Log.v(TAG, "unregisterRemoteAnimations"); Log.v(TAG, "unregisterRemoteAnimations");
} }
if (!mIsRegister) { if (!mRegisterTasks.contains(taskId)) {
return; return;
} }
mOrganizer.unregisterRemoteAnimations(); mOrganizer.unregisterRemoteAnimations(taskId);
mIsRegister = false; mRegisterTasks.remove(taskId);
}
void unregisterAllRemoteAnimations() {
final ArraySet<Integer> tasks = new ArraySet<>(mRegisterTasks);
for (int taskId : tasks) {
unregisterRemoteAnimations(taskId);
}
} }
} }

View File

@@ -0,0 +1,91 @@
/*
* 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 androidx.window.extensions.embedding;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Test class for {@link JetpackTaskFragmentOrganizer}.
*
* Build/Install/Run:
* atest WMJetpackUnitTests:JetpackTaskFragmentOrganizerTest
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class JetpackTaskFragmentOrganizerTest {
private static final int TASK_ID = 10;
@Mock
private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
private JetpackTaskFragmentOrganizer mOrganizer;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback);
mOrganizer.registerOrganizer();
spyOn(mOrganizer);
}
@Test
public void testUnregisterOrganizer() {
mOrganizer.startOverrideSplitAnimation(TASK_ID);
mOrganizer.startOverrideSplitAnimation(TASK_ID + 1);
mOrganizer.unregisterOrganizer();
verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
}
@Test
public void testStartOverrideSplitAnimation() {
assertNull(mOrganizer.mAnimationController);
mOrganizer.startOverrideSplitAnimation(TASK_ID);
assertNotNull(mOrganizer.mAnimationController);
verify(mOrganizer).registerRemoteAnimations(TASK_ID,
mOrganizer.mAnimationController.mDefinition);
}
@Test
public void testStopOverrideSplitAnimation() {
mOrganizer.stopOverrideSplitAnimation(TASK_ID);
verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
mOrganizer.startOverrideSplitAnimation(TASK_ID);
mOrganizer.stopOverrideSplitAnimation(TASK_ID);
verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
}
}

View File

@@ -31,6 +31,12 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/**
* Test class for {@link SplitController}.
*
* Build/Install/Run:
* atest WMJetpackUnitTests:SplitController
*/
@SmallTest @SmallTest
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class SplitControllerTest { public class SplitControllerTest {
@@ -46,7 +52,7 @@ public class SplitControllerTest {
@Test @Test
public void testGetTopActiveContainer() { public void testGetTopActiveContainer() {
TaskContainer taskContainer = new TaskContainer(); TaskContainer taskContainer = new TaskContainer(TASK_ID);
// tf3 is finished so is not active. // tf3 is finished so is not active.
TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class); TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
doReturn(true).when(tf3).isFinished(); doReturn(true).when(tf3).isFinished();

View File

@@ -0,0 +1,95 @@
/*
* 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 androidx.window.extensions.embedding;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import android.window.TaskFragmentOrganizer;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Test class for {@link TaskFragmentAnimationController}.
*
* Build/Install/Run:
* atest WMJetpackUnitTests:TaskFragmentAnimationControllerTest
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TaskFragmentAnimationControllerTest {
private static final int TASK_ID = 10;
@Mock
private TaskFragmentOrganizer mOrganizer;
private TaskFragmentAnimationController mAnimationController;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mAnimationController = new TaskFragmentAnimationController(mOrganizer);
}
@Test
public void testRegisterRemoteAnimations() {
mAnimationController.registerRemoteAnimations(TASK_ID);
verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
mAnimationController.registerRemoteAnimations(TASK_ID);
// No extra call if it has been registered.
verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
}
@Test
public void testUnregisterRemoteAnimations() {
mAnimationController.unregisterRemoteAnimations(TASK_ID);
// No call if it is not registered.
verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
mAnimationController.registerRemoteAnimations(TASK_ID);
mAnimationController.unregisterRemoteAnimations(TASK_ID);
verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
mAnimationController.unregisterRemoteAnimations(TASK_ID);
// No extra call if it has been unregistered.
verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
}
@Test
public void testUnregisterAllRemoteAnimations() {
mAnimationController.registerRemoteAnimations(TASK_ID);
mAnimationController.registerRemoteAnimations(TASK_ID + 1);
mAnimationController.unregisterAllRemoteAnimations();
verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
}
}

View File

@@ -653,7 +653,7 @@ public class AppTransitionController {
final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task); final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
final RemoteAnimationDefinition definition = organizer != null final RemoteAnimationDefinition definition = organizer != null
? mDisplayContent.mAtmService.mTaskFragmentOrganizerController ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
.getRemoteAnimationDefinition(organizer) .getRemoteAnimationDefinition(organizer, task.mTaskId)
: null; : null;
final RemoteAnimationAdapter adapter = definition != null final RemoteAnimationAdapter adapter = definition != null
? definition.getAdapter(transit, activityTypes) ? definition.getAdapter(transit, activityTypes)

View File

@@ -32,6 +32,7 @@ import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.Slog; import android.util.Slog;
import android.util.SparseArray;
import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationDefinition;
import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController; import android.window.ITaskFragmentOrganizerController;
@@ -83,11 +84,12 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
new WeakHashMap<>(); new WeakHashMap<>();
/** /**
* @see android.window.TaskFragmentOrganizer#registerRemoteAnimations( * Map from Task Id to {@link RemoteAnimationDefinition}.
* RemoteAnimationDefinition) * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations(int,
* RemoteAnimationDefinition) )
*/ */
@Nullable private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions =
private RemoteAnimationDefinition mRemoteAnimationDefinition; new SparseArray<>();
TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer) { TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer) {
mOrganizer = organizer; mOrganizer = organizer;
@@ -251,7 +253,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
} }
@Override @Override
public void registerRemoteAnimations(ITaskFragmentOrganizer organizer, public void registerRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId,
RemoteAnimationDefinition definition) { RemoteAnimationDefinition definition) {
final int pid = Binder.getCallingPid(); final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid(); final int uid = Binder.getCallingUid();
@@ -264,19 +266,20 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
if (organizerState == null) { if (organizerState == null) {
throw new IllegalStateException("The organizer hasn't been registered."); throw new IllegalStateException("The organizer hasn't been registered.");
} }
if (organizerState.mRemoteAnimationDefinition != null) { if (organizerState.mRemoteAnimationDefinitions.contains(taskId)) {
throw new IllegalStateException( throw new IllegalStateException(
"The organizer has already registered remote animations=" "The organizer has already registered remote animations="
+ organizerState.mRemoteAnimationDefinition); + organizerState.mRemoteAnimationDefinitions.get(taskId)
+ " for TaskId=" + taskId);
} }
definition.setCallingPidUid(pid, uid); definition.setCallingPidUid(pid, uid);
organizerState.mRemoteAnimationDefinition = definition; organizerState.mRemoteAnimationDefinitions.put(taskId, definition);
} }
} }
@Override @Override
public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer) { public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId) {
final int pid = Binder.getCallingPid(); final int pid = Binder.getCallingPid();
final long uid = Binder.getCallingUid(); final long uid = Binder.getCallingUid();
synchronized (mGlobalLock) { synchronized (mGlobalLock) {
@@ -290,7 +293,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
return; return;
} }
organizerState.mRemoteAnimationDefinition = null; organizerState.mRemoteAnimationDefinitions.remove(taskId);
} }
} }
@@ -300,11 +303,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
*/ */
@Nullable @Nullable
public RemoteAnimationDefinition getRemoteAnimationDefinition( public RemoteAnimationDefinition getRemoteAnimationDefinition(
ITaskFragmentOrganizer organizer) { ITaskFragmentOrganizer organizer, int taskId) {
synchronized (mGlobalLock) { synchronized (mGlobalLock) {
final TaskFragmentOrganizerState organizerState = final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder()); mTaskFragmentOrganizerState.get(organizer.asBinder());
return organizerState != null ? organizerState.mRemoteAnimationDefinition : null; return organizerState != null
? organizerState.mRemoteAnimationDefinitions.get(taskId)
: null;
} }
} }

View File

@@ -861,13 +861,13 @@ public class AppTransitionControllerTest extends WindowTestsBase {
@Test @Test
public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() { public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() {
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
// Create a TaskFragment with embedded activity. // Create a TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity( final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
createTask(mDisplayContent), organizer);
final ActivityRecord activity = taskFragment.getTopMostActivity(); final ActivityRecord activity = taskFragment.getTopMostActivity();
prepareActivityForAppTransition(activity); prepareActivityForAppTransition(activity);
spyOn(mDisplayContent.mAppTransition); spyOn(mDisplayContent.mAppTransition);
@@ -882,11 +882,11 @@ public class AppTransitionControllerTest extends WindowTestsBase {
@Test @Test
public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() { public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
final Task task = createTask(mDisplayContent);
// Closing non-embedded activity. // Closing non-embedded activity.
final ActivityRecord closingActivity = createActivityRecord(task); final ActivityRecord closingActivity = createActivityRecord(task);
prepareActivityForAppTransition(closingActivity); prepareActivityForAppTransition(closingActivity);
@@ -907,11 +907,11 @@ public class AppTransitionControllerTest extends WindowTestsBase {
@Test @Test
public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() { public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() {
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
final Task task = createTask(mDisplayContent);
// Closing TaskFragment with embedded activity. // Closing TaskFragment with embedded activity.
final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer); final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
final ActivityRecord closingActivity = taskFragment1.getTopMostActivity(); final ActivityRecord closingActivity = taskFragment1.getTopMostActivity();
@@ -934,16 +934,16 @@ public class AppTransitionControllerTest extends WindowTestsBase {
@Test @Test
public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() { public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() {
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
// Closing activity in Task1. // Closing activity in Task1.
final ActivityRecord closingActivity = createActivityRecord(mDisplayContent); final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
prepareActivityForAppTransition(closingActivity); prepareActivityForAppTransition(closingActivity);
// Opening TaskFragment with embedded activity in Task2. // Opening TaskFragment with embedded activity in Task2.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity( final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
createTask(mDisplayContent), organizer);
final ActivityRecord openingActivity = taskFragment.getTopMostActivity(); final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
prepareActivityForAppTransition(openingActivity); prepareActivityForAppTransition(openingActivity);
spyOn(mDisplayContent.mAppTransition); spyOn(mDisplayContent.mAppTransition);
@@ -958,11 +958,11 @@ public class AppTransitionControllerTest extends WindowTestsBase {
@Test @Test
public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() { public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() {
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
final Task task = createTask(mDisplayContent);
// Closing TaskFragment with embedded activity. // Closing TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer); final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
final ActivityRecord closingActivity = taskFragment.getTopMostActivity(); final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
@@ -986,13 +986,13 @@ public class AppTransitionControllerTest extends WindowTestsBase {
@Test @Test
public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() { public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
// Create a TaskFragment with embedded activity. // Create a TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity( final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
createTask(mDisplayContent), organizer);
final ActivityRecord activity = taskFragment.getTopMostActivity(); final ActivityRecord activity = taskFragment.getTopMostActivity();
prepareActivityForAppTransition(activity); prepareActivityForAppTransition(activity);
// Set wallpaper as visible. // Set wallpaper as visible.
@@ -1012,13 +1012,13 @@ public class AppTransitionControllerTest extends WindowTestsBase {
@Test @Test
public void testOverrideTaskFragmentAdapter_inputProtectedForUntrustedAnimation() { public void testOverrideTaskFragmentAdapter_inputProtectedForUntrustedAnimation() {
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
// Create a TaskFragment with embedded activities, one is trusted embedded, and the other // Create a TaskFragment with embedded activities, one is trusted embedded, and the other
// one is untrusted embedded. // one is untrusted embedded.
final Task task = createTask(mDisplayContent);
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task) .setParentTask(task)
.createActivityCount(2) .createActivityCount(2)
@@ -1071,12 +1071,12 @@ public class AppTransitionControllerTest extends WindowTestsBase {
*/ */
@Test @Test
public void testOverrideTaskFragmentAdapter_inputProtectedForTrustedAnimation() { public void testOverrideTaskFragmentAdapter_inputProtectedForTrustedAnimation() {
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
// Create a TaskFragment with only trusted embedded activity // Create a TaskFragment with only trusted embedded activity
final Task task = createTask(mDisplayContent);
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task) .setParentTask(task)
.createActivityCount(1) .createActivityCount(1)
@@ -1170,7 +1170,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
} }
/** Registers remote animation for the organizer. */ /** Registers remote animation for the organizer. */
private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer, private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer, int taskId,
TestRemoteAnimationRunner remoteAnimationRunner) { TestRemoteAnimationRunner remoteAnimationRunner) {
final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
remoteAnimationRunner, 10, 1); remoteAnimationRunner, 10, 1);
@@ -1181,7 +1181,8 @@ public class AppTransitionControllerTest extends WindowTestsBase {
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter); definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter); definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer); mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition); mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, taskId,
definition);
} }
private void prepareAndTriggerAppTransition(@Nullable ActivityRecord openingActivity, private void prepareAndTriggerAppTransition(@Nullable ActivityRecord openingActivity,

View File

@@ -67,6 +67,7 @@ import org.junit.runner.RunWith;
@Presubmit @Presubmit
@RunWith(WindowTestRunner.class) @RunWith(WindowTestRunner.class)
public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
private static final int TASK_ID = 10;
private TaskFragmentOrganizerController mController; private TaskFragmentOrganizerController mController;
private TaskFragmentOrganizer mOrganizer; private TaskFragmentOrganizer mOrganizer;
@@ -219,13 +220,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test @Test
public void testRegisterRemoteAnimations() { public void testRegisterRemoteAnimations() {
mController.registerOrganizer(mIOrganizer); mController.registerOrganizer(mIOrganizer);
mController.registerRemoteAnimations(mIOrganizer, mDefinition); mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition);
assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer)); assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
mController.unregisterRemoteAnimations(mIOrganizer); mController.unregisterRemoteAnimations(mIOrganizer, TASK_ID);
assertNull(mController.getRemoteAnimationDefinition(mIOrganizer)); assertNull(mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
} }
@Test @Test