Merge "Keep track of the WCT changes to determine the transition type" into tm-qpr-dev

This commit is contained in:
Chris Li
2022-10-18 11:50:45 +00:00
committed by Android (Google) Code Review
4 changed files with 468 additions and 23 deletions

View File

@@ -20,10 +20,11 @@ import static android.app.ActivityManager.START_SUCCESS;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
import static android.window.TaskFragmentOrganizer.getTransitionType;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -76,6 +77,7 @@ import androidx.annotation.Nullable;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.extensions.WindowExtensionsProvider;
import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.android.internal.annotations.VisibleForTesting;
@@ -100,6 +102,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
final SplitPresenter mPresenter;
@VisibleForTesting
@GuardedBy("mLock")
final TransactionManager mTransactionManager;
// Currently applied split configuration.
@GuardedBy("mLock")
private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
@@ -150,6 +156,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final MainThreadExecutor executor = new MainThreadExecutor();
mHandler = executor.mHandler;
mPresenter = new SplitPresenter(executor, this);
mTransactionManager = new TransactionManager(mPresenter);
final ActivityThread activityThread = ActivityThread.currentActivityThread();
final Application application = activityThread.getApplication();
// Register a callback to be notified about activities being created.
@@ -167,7 +174,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void accept(List<CommonFoldingFeature> foldingFeatures) {
synchronized (mLock) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
final TransactionRecord transactionRecord = mTransactionManager
.startNewTransaction();
final WindowContainerTransaction wct = transactionRecord.getTransaction();
for (int i = 0; i < mTaskContainers.size(); i++) {
final TaskContainer taskContainer = mTaskContainers.valueAt(i);
if (!taskContainer.isVisible()) {
@@ -186,7 +195,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
updateContainersInTask(wct, taskContainer);
updateAnimationOverride(taskContainer);
}
mPresenter.applyTransaction(wct);
// The WCT should be applied and merged to the device state change transition if
// there is one.
transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
}
@@ -256,7 +267,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
synchronized (mLock) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(
transaction.getTransactionToken());
final WindowContainerTransaction wct = transactionRecord.getTransaction();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
for (TaskFragmentTransaction.Change change : changes) {
final int taskId = change.getTaskId();
@@ -307,8 +320,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Notify the server, and the server should apply and merge the
// WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction.
mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct,
getTransitionType(wct), false /* shouldApplyIndependently */);
transactionRecord.apply(false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
}
}
@@ -333,6 +345,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
container.setInfo(wct, taskFragmentInfo);
if (container.isFinished()) {
mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else {
// Update with the latest Task configuration.
@@ -368,15 +381,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Do not finish the dependents if the last activity is reparented to PiP.
// Instead, the original split should be cleanup, and the dependent may be
// expanded to fullscreen.
mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
cleanupForEnterPip(wct, container);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (taskFragmentInfo.isTaskClearedForReuse()) {
// Do not finish the dependents if this TaskFragment was cleared due to
// launching activity in the Task.
mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (!container.isWaitingActivityAppear()) {
// Do not finish the container before the expected activity appear until
// timeout.
mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
}
} else if (wasInPip && isInPip) {
@@ -571,6 +587,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
container.setInfo(wct, taskFragmentInfo);
container.clearPendingAppearedActivities();
if (container.isEmpty()) {
mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
break;
@@ -1009,11 +1026,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
onTaskFragmentAppearEmptyTimeout(wct, container);
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
onTaskFragmentAppearEmptyTimeout(transactionRecord.getTransaction(), container);
// Can be applied independently as a timeout callback.
mPresenter.applyTransaction(wct, getTransitionType(wct),
true /* shouldApplyIndependently */);
transactionRecord.apply(true /* shouldApplyIndependently */);
}
/**
@@ -1023,6 +1039,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
@@ -1562,6 +1579,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @param isOnCreated whether this happens during the primary activity onCreated.
*/
@VisibleForTesting
@GuardedBy("mLock")
@Nullable
Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) {
// Setting avoid move to front will also skip the animation. We only want to do that when
@@ -1569,6 +1587,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Check if the primary is resumed or if this is called when the primary is onCreated
// (not resumed yet).
if (isOnCreated || primaryActivity.isResumed()) {
// Only set trigger type if the launch happens in foreground.
mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_OPEN);
return null;
}
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1595,6 +1615,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
false /* shouldFinishDependent */);
return true;
@@ -1905,23 +1927,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// that we don't launch it if an activity itself already requested something to be
// launched to side.
synchronized (mLock) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
SplitController.this.onActivityCreated(wct, activity);
final TransactionRecord transactionRecord = mTransactionManager
.startNewTransaction();
transactionRecord.setOriginType(TRANSIT_OPEN);
SplitController.this.onActivityCreated(transactionRecord.getTransaction(),
activity);
// The WCT should be applied and merged to the activity launch transition.
mPresenter.applyTransaction(wct, getTransitionType(wct),
false /* shouldApplyIndependently */);
transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
@Override
public void onActivityConfigurationChanged(@NonNull Activity activity) {
synchronized (mLock) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
SplitController.this.onActivityConfigurationChanged(wct, activity);
final TransactionRecord transactionRecord = mTransactionManager
.startNewTransaction();
SplitController.this.onActivityConfigurationChanged(
transactionRecord.getTransaction(), activity);
// The WCT should be applied and merged to the Task change transition so that the
// placeholder is launched in the same transition.
mPresenter.applyTransaction(wct, getTransitionType(wct),
false /* shouldApplyIndependently */);
transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
@@ -1977,7 +2002,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
synchronized (mLock) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
final TransactionRecord transactionRecord = mTransactionManager
.startNewTransaction();
transactionRecord.setOriginType(TRANSIT_OPEN);
final WindowContainerTransaction wct = transactionRecord.getTransaction();
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
final int taskId = getTaskId(launchingActivity);
@@ -1990,13 +2018,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (launchedInTaskFragment != null) {
// Make sure the WCT is applied immediately instead of being queued so that the
// TaskFragment will be ready before activity attachment.
mPresenter.applyTransaction(wct, getTransitionType(wct),
false /* shouldApplyIndependently */);
transactionRecord.apply(false /* shouldApplyIndependently */);
// Amend the request to let the WM know that the activity should be placed in
// the dedicated container.
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
launchedInTaskFragment.getTaskFragmentToken());
mCurrentIntent = intent;
} else {
transactionRecord.abort();
}
}

View File

@@ -0,0 +1,201 @@
/*
* 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 android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_NONE;
import android.os.IBinder;
import android.view.WindowManager.TransitionType;
import android.window.TaskFragmentOrganizer;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
/**
* Responsible for managing the current {@link WindowContainerTransaction} as a response to device
* state changes and app interactions.
*
* A typical use flow:
* 1. Call {@link #startNewTransaction} to start tracking the changes.
* 2. Use {@link TransactionRecord#setOriginType(int)} (int)} to record the type of operation that
* will start a new transition on system server.
* 3. Use {@link #getCurrentTransactionRecord()} to get current {@link TransactionRecord} for
* changes.
* 4. Call {@link TransactionRecord#apply(boolean)} to request the system server to apply changes in
* the current {@link WindowContainerTransaction}, or call {@link TransactionRecord#abort()} to
* dispose the current one.
*
* Note:
* There should be only one transaction at a time. The caller should not call
* {@link #startNewTransaction} again before calling {@link TransactionRecord#apply(boolean)} or
* {@link TransactionRecord#abort()} to the previous transaction.
*/
class TransactionManager {
@NonNull
private final TaskFragmentOrganizer mOrganizer;
@Nullable
private TransactionRecord mCurrentTransaction;
TransactionManager(@NonNull TaskFragmentOrganizer organizer) {
mOrganizer = organizer;
}
@NonNull
TransactionRecord startNewTransaction() {
return startNewTransaction(null /* taskFragmentTransactionToken */);
}
/**
* Starts tracking the changes in a new {@link WindowContainerTransaction}. Caller can call
* {@link #getCurrentTransactionRecord()} later to continue adding change to the current
* transaction until {@link TransactionRecord#apply(boolean)} or
* {@link TransactionRecord#abort()} is called.
* @param taskFragmentTransactionToken {@link android.window.TaskFragmentTransaction
* #getTransactionToken()} if this is a response to a
* {@link android.window.TaskFragmentTransaction}.
*/
@NonNull
TransactionRecord startNewTransaction(@Nullable IBinder taskFragmentTransactionToken) {
if (mCurrentTransaction != null) {
mCurrentTransaction = null;
throw new IllegalStateException(
"The previous transaction has not been applied or aborted,");
}
mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken);
return mCurrentTransaction;
}
/**
* Gets the current {@link TransactionRecord} started from {@link #startNewTransaction}.
*/
@NonNull
TransactionRecord getCurrentTransactionRecord() {
if (mCurrentTransaction == null) {
throw new IllegalStateException("startNewTransaction() is not invoked before calling"
+ " getCurrentTransactionRecord().");
}
return mCurrentTransaction;
}
/** The current transaction. The manager should only handle one transaction at a time. */
class TransactionRecord {
/**
* {@link WindowContainerTransaction} containing the current change.
* @see #startNewTransaction(IBinder)
* @see #apply (boolean)
*/
@NonNull
private final WindowContainerTransaction mTransaction = new WindowContainerTransaction();
/**
* If the current transaction is a response to a
* {@link android.window.TaskFragmentTransaction}, this is the
* {@link android.window.TaskFragmentTransaction#getTransactionToken()}.
* @see #startNewTransaction(IBinder)
*/
@Nullable
private final IBinder mTaskFragmentTransactionToken;
/**
* To track of the origin type of the current {@link #mTransaction}. When
* {@link #apply (boolean)} to start a new transition, this is the type to request.
* @see #setOriginType(int)
* @see #getTransactionTransitionType()
*/
@TransitionType
private int mOriginType = TRANSIT_NONE;
TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) {
mTaskFragmentTransactionToken = taskFragmentTransactionToken;
}
@NonNull
WindowContainerTransaction getTransaction() {
ensureCurrentTransaction();
return mTransaction;
}
/**
* Sets the {@link TransitionType} that triggers this transaction. If there are multiple
* calls, only the first call will be respected as the "origin" type.
*/
void setOriginType(@TransitionType int type) {
ensureCurrentTransaction();
if (mOriginType != TRANSIT_NONE) {
// Skip if the origin type has already been set.
return;
}
mOriginType = type;
}
/**
* Requests the system server to apply the current transaction started from
* {@link #startNewTransaction}.
* @param shouldApplyIndependently If {@code true}, the {@link #mCurrentTransaction} will
* request a new transition, which will be queued until the
* sync engine is free if there is any other active sync.
* If {@code false}, the {@link #startNewTransaction} will
* be directly applied to the active sync.
*/
void apply(boolean shouldApplyIndependently) {
ensureCurrentTransaction();
if (mTaskFragmentTransactionToken != null) {
// If this is a response to a TaskFragmentTransaction.
mOrganizer.onTransactionHandled(mTaskFragmentTransactionToken, mTransaction,
getTransactionTransitionType(), shouldApplyIndependently);
} else {
mOrganizer.applyTransaction(mTransaction, getTransactionTransitionType(),
shouldApplyIndependently);
}
dispose();
}
/** Called when there is no need to {@link #apply(boolean)} the current transaction. */
void abort() {
ensureCurrentTransaction();
dispose();
}
private void dispose() {
TransactionManager.this.mCurrentTransaction = null;
}
private void ensureCurrentTransaction() {
if (TransactionManager.this.mCurrentTransaction != this) {
throw new IllegalStateException(
"This transaction has already been apply() or abort().");
}
}
/**
* Gets the {@link TransitionType} that we will request transition with for the
* current {@link WindowContainerTransaction}.
*/
@VisibleForTesting
@TransitionType
int getTransactionTransitionType() {
// Use TRANSIT_CHANGE as default if there is not opening/closing window.
return mOriginType != TRANSIT_NONE ? mOriginType : TRANSIT_CHANGE;
}
}
}

View File

@@ -132,6 +132,7 @@ public class SplitControllerTest {
private SplitController mSplitController;
private SplitPresenter mSplitPresenter;
private TransactionManager mTransactionManager;
@Before
public void setUp() {
@@ -140,8 +141,10 @@ public class SplitControllerTest {
.getCurrentWindowLayoutInfo(anyInt(), any());
mSplitController = new SplitController(mWindowLayoutComponent);
mSplitPresenter = mSplitController.mPresenter;
mTransactionManager = mSplitController.mTransactionManager;
spyOn(mSplitController);
spyOn(mSplitPresenter);
spyOn(mTransactionManager);
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
@@ -212,6 +215,8 @@ public class SplitControllerTest {
@Test
public void testOnTaskFragmentAppearEmptyTimeout() {
// Setup to make sure a transaction record is started.
mTransactionManager.startNewTransaction();
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any());
mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
@@ -615,6 +620,8 @@ public class SplitControllerTest {
@Test
public void testResolveActivityToContainer_placeholderRule_notInTaskFragment() {
// Setup to make sure a transaction record is started.
mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -647,6 +654,8 @@ public class SplitControllerTest {
@Test
public void testResolveActivityToContainer_placeholderRule_inTopMostTaskFragment() {
// Setup to make sure a transaction record is started.
mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -679,6 +688,8 @@ public class SplitControllerTest {
@Test
public void testResolveActivityToContainer_placeholderRule_inSecondarySplit() {
// Setup to make sure a transaction record is started.
mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -961,6 +972,8 @@ public class SplitControllerTest {
@Test
public void testGetPlaceholderOptions() {
// Setup to make sure a transaction record is started.
mTransactionManager.startNewTransaction();
doReturn(true).when(mActivity).isResumed();
assertNull(mSplitController.getPlaceholderOptions(mActivity, false /* isOnCreated */));
@@ -1147,8 +1160,6 @@ public class SplitControllerTest {
+ "of other properties",
SplitController.haveSamePresentation(splitRule1, splitRule2,
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
}
/** Creates a mock activity in the organizer process. */

View File

@@ -0,0 +1,204 @@
/*
* 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 android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.clearInvocations;
import android.os.Binder;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentOrganizer;
import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
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 TransactionManager}.
*
* Build/Install/Run:
* atest WMJetpackUnitTests:TransactionManagerTest
*/
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TransactionManagerTest {
@Mock
private TaskFragmentOrganizer mOrganizer;
private TransactionManager mTransactionManager;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mTransactionManager = new TransactionManager(mOrganizer);
}
@Test
public void testStartNewTransaction() {
mTransactionManager.startNewTransaction();
// Throw exception if #startNewTransaction is called twice without #apply() or #abort().
assertThrows(IllegalStateException.class, mTransactionManager::startNewTransaction);
// Allow to start new after #apply() the last transaction.
TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
transactionRecord.apply(false /* shouldApplyIndependently */);
transactionRecord = mTransactionManager.startNewTransaction();
// Allow to start new after #abort() the last transaction.
transactionRecord.abort();
mTransactionManager.startNewTransaction();
}
@Test
public void testSetTransactionOriginType() {
// Return TRANSIT_CHANGE if there is no trigger type set.
TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
// Return the first set type.
mTransactionManager.getCurrentTransactionRecord().abort();
transactionRecord = mTransactionManager.startNewTransaction();
transactionRecord.setOriginType(TRANSIT_OPEN);
assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
transactionRecord.setOriginType(TRANSIT_CLOSE);
assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
// Reset when #startNewTransaction().
transactionRecord.abort();
transactionRecord = mTransactionManager.startNewTransaction();
assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
}
@Test
public void testGetCurrentTransactionRecord() {
// Throw exception if #getTransaction is called without calling #startNewTransaction().
assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
assertNotNull(transactionRecord);
// Same WindowContainerTransaction should be returned.
assertSame(transactionRecord, mTransactionManager.getCurrentTransactionRecord());
// Reset after #abort().
transactionRecord.abort();
assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
// New WindowContainerTransaction after #startNewTransaction().
mTransactionManager.startNewTransaction();
assertNotEquals(transactionRecord, mTransactionManager.getCurrentTransactionRecord());
// Reset after #apply().
mTransactionManager.getCurrentTransactionRecord().apply(
false /* shouldApplyIndependently */);
assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
}
@Test
public void testApply() {
// #applyTransaction(false)
TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
int transitionType = transactionRecord.getTransactionTransitionType();
WindowContainerTransaction wct = transactionRecord.getTransaction();
transactionRecord.apply(false /* shouldApplyIndependently */);
verify(mOrganizer).applyTransaction(wct, transitionType,
false /* shouldApplyIndependently */);
// #applyTransaction(true)
clearInvocations(mOrganizer);
transactionRecord = mTransactionManager.startNewTransaction();
transitionType = transactionRecord.getTransactionTransitionType();
wct = transactionRecord.getTransaction();
transactionRecord.apply(true /* shouldApplyIndependently */);
verify(mOrganizer).applyTransaction(wct, transitionType,
true /* shouldApplyIndependently */);
// #onTransactionHandled(false)
clearInvocations(mOrganizer);
IBinder token = new Binder();
transactionRecord = mTransactionManager.startNewTransaction(token);
transitionType = transactionRecord.getTransactionTransitionType();
wct = transactionRecord.getTransaction();
transactionRecord.apply(false /* shouldApplyIndependently */);
verify(mOrganizer).onTransactionHandled(token, wct, transitionType,
false /* shouldApplyIndependently */);
// #onTransactionHandled(true)
clearInvocations(mOrganizer);
token = new Binder();
transactionRecord = mTransactionManager.startNewTransaction(token);
transitionType = transactionRecord.getTransactionTransitionType();
wct = transactionRecord.getTransaction();
transactionRecord.apply(true /* shouldApplyIndependently */);
verify(mOrganizer).onTransactionHandled(token, wct, transitionType,
true /* shouldApplyIndependently */);
// Throw exception if there is any more interaction.
final TransactionRecord record = transactionRecord;
assertThrows(IllegalStateException.class,
() -> record.apply(false /* shouldApplyIndependently */));
assertThrows(IllegalStateException.class,
() -> record.apply(true /* shouldApplyIndependently */));
assertThrows(IllegalStateException.class,
record::abort);
}
@Test
public void testAbort() {
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
transactionRecord.abort();
// Throw exception if there is any more interaction.
verifyNoMoreInteractions(mOrganizer);
assertThrows(IllegalStateException.class,
() -> transactionRecord.apply(false /* shouldApplyIndependently */));
assertThrows(IllegalStateException.class,
() -> transactionRecord.apply(true /* shouldApplyIndependently */));
assertThrows(IllegalStateException.class,
transactionRecord::abort);
}
}