blob: 396956e56bb5550ee422a689493b21a55754a019 [file] [log] [blame]
/*
* 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.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE;
import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_NONE;
import android.os.IBinder;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentOrganizer.TaskFragmentTransitionType;
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()
*/
@TaskFragmentTransitionType
private int mOriginType = TASK_FRAGMENT_TRANSIT_NONE;
TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) {
mTaskFragmentTransactionToken = taskFragmentTransactionToken;
}
@NonNull
WindowContainerTransaction getTransaction() {
ensureCurrentTransaction();
return mTransaction;
}
/**
* Sets the {@link TaskFragmentTransitionType} that triggers this transaction. If there are
* multiple calls, only the first call will be respected as the "origin" type.
*/
void setOriginType(@TaskFragmentTransitionType int type) {
ensureCurrentTransaction();
if (mOriginType != TASK_FRAGMENT_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 TaskFragmentTransitionType} that we will request transition with for the
* current {@link WindowContainerTransaction}.
*/
@VisibleForTesting
@TaskFragmentTransitionType
int getTransactionTransitionType() {
// Use TASK_FRAGMENT_TRANSIT_CHANGE as default if there is not opening/closing window.
return mOriginType != TASK_FRAGMENT_TRANSIT_NONE
? mOriginType
: TASK_FRAGMENT_TRANSIT_CHANGE;
}
}
}