Merge "Keep track of the WCT changes to determine the transition type" into tm-qpr-dev
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user