| /* |
| * 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; |
| } |
| } |
| } |