1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.window.extensions.embedding;
18 
19 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE;
20 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_NONE;
21 
22 import android.os.IBinder;
23 import android.window.TaskFragmentOrganizer;
24 import android.window.TaskFragmentOrganizer.TaskFragmentTransitionType;
25 import android.window.WindowContainerTransaction;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 /**
33  * Responsible for managing the current {@link WindowContainerTransaction} as a response to device
34  * state changes and app interactions.
35  *
36  * A typical use flow:
37  * 1. Call {@link #startNewTransaction} to start tracking the changes.
38  * 2. Use {@link TransactionRecord#setOriginType(int)} (int)} to record the type of operation that
39  *    will start a new transition on system server.
40  * 3. Use {@link #getCurrentTransactionRecord()} to get current {@link TransactionRecord} for
41  *    changes.
42  * 4. Call {@link TransactionRecord#apply(boolean)} to request the system server to apply changes in
43  *    the current {@link WindowContainerTransaction}, or call {@link TransactionRecord#abort()} to
44  *    dispose the current one.
45  *
46  * Note:
47  * There should be only one transaction at a time. The caller should not call
48  * {@link #startNewTransaction} again before calling {@link TransactionRecord#apply(boolean)} or
49  * {@link TransactionRecord#abort()} to the previous transaction.
50  */
51 class TransactionManager {
52 
53     @NonNull
54     private final TaskFragmentOrganizer mOrganizer;
55 
56     @Nullable
57     private TransactionRecord mCurrentTransaction;
58 
TransactionManager(@onNull TaskFragmentOrganizer organizer)59     TransactionManager(@NonNull TaskFragmentOrganizer organizer) {
60         mOrganizer = organizer;
61     }
62 
63     @NonNull
startNewTransaction()64     TransactionRecord startNewTransaction() {
65         return startNewTransaction(null /* taskFragmentTransactionToken */);
66     }
67 
68     /**
69      * Starts tracking the changes in a new {@link WindowContainerTransaction}. Caller can call
70      * {@link #getCurrentTransactionRecord()} later to continue adding change to the current
71      * transaction until {@link TransactionRecord#apply(boolean)} or
72      * {@link TransactionRecord#abort()} is called.
73      * @param taskFragmentTransactionToken  {@link android.window.TaskFragmentTransaction
74      *                                      #getTransactionToken()} if this is a response to a
75      *                                      {@link android.window.TaskFragmentTransaction}.
76      */
77     @NonNull
startNewTransaction(@ullable IBinder taskFragmentTransactionToken)78     TransactionRecord startNewTransaction(@Nullable IBinder taskFragmentTransactionToken) {
79         if (mCurrentTransaction != null) {
80             mCurrentTransaction = null;
81             throw new IllegalStateException(
82                     "The previous transaction has not been applied or aborted,");
83         }
84         mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken);
85         return mCurrentTransaction;
86     }
87 
88     /**
89      * Gets the current {@link TransactionRecord} started from {@link #startNewTransaction}.
90      */
91     @NonNull
getCurrentTransactionRecord()92     TransactionRecord getCurrentTransactionRecord() {
93         if (mCurrentTransaction == null) {
94             throw new IllegalStateException("startNewTransaction() is not invoked before calling"
95                     + " getCurrentTransactionRecord().");
96         }
97         return mCurrentTransaction;
98     }
99 
100     /** The current transaction. The manager should only handle one transaction at a time. */
101     class TransactionRecord {
102         /**
103          * {@link WindowContainerTransaction} containing the current change.
104          * @see #startNewTransaction(IBinder)
105          * @see #apply (boolean)
106          */
107         @NonNull
108         private final WindowContainerTransaction mTransaction = new WindowContainerTransaction();
109 
110         /**
111          * If the current transaction is a response to a
112          * {@link android.window.TaskFragmentTransaction}, this is the
113          * {@link android.window.TaskFragmentTransaction#getTransactionToken()}.
114          * @see #startNewTransaction(IBinder)
115          */
116         @Nullable
117         private final IBinder mTaskFragmentTransactionToken;
118 
119         /**
120          * To track of the origin type of the current {@link #mTransaction}. When
121          * {@link #apply (boolean)} to start a new transition, this is the type to request.
122          * @see #setOriginType(int)
123          * @see #getTransactionTransitionType()
124          */
125         @TaskFragmentTransitionType
126         private int mOriginType = TASK_FRAGMENT_TRANSIT_NONE;
127 
TransactionRecord(@ullable IBinder taskFragmentTransactionToken)128         TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) {
129             mTaskFragmentTransactionToken = taskFragmentTransactionToken;
130         }
131 
132         @NonNull
getTransaction()133         WindowContainerTransaction getTransaction() {
134             ensureCurrentTransaction();
135             return mTransaction;
136         }
137 
138         /**
139          * Sets the {@link TaskFragmentTransitionType} that triggers this transaction. If there are
140          * multiple calls, only the first call will be respected as the "origin" type.
141          */
setOriginType(@askFragmentTransitionType int type)142         void setOriginType(@TaskFragmentTransitionType int type) {
143             ensureCurrentTransaction();
144             if (mOriginType != TASK_FRAGMENT_TRANSIT_NONE) {
145                 // Skip if the origin type has already been set.
146                 return;
147             }
148             mOriginType = type;
149         }
150 
151         /**
152          * Requests the system server to apply the current transaction started from
153          * {@link #startNewTransaction}.
154          * @param shouldApplyIndependently  If {@code true}, the {@link #mCurrentTransaction} will
155          *                                  request a new transition, which will be queued until the
156          *                                  sync engine is free if there is any other active sync.
157          *                                  If {@code false}, the {@link #startNewTransaction} will
158          *                                  be directly applied to the active sync.
159          */
apply(boolean shouldApplyIndependently)160         void apply(boolean shouldApplyIndependently) {
161             ensureCurrentTransaction();
162             if (mTaskFragmentTransactionToken != null) {
163                 // If this is a response to a TaskFragmentTransaction.
164                 mOrganizer.onTransactionHandled(mTaskFragmentTransactionToken, mTransaction,
165                         getTransactionTransitionType(), shouldApplyIndependently);
166             } else {
167                 mOrganizer.applyTransaction(mTransaction, getTransactionTransitionType(),
168                         shouldApplyIndependently);
169             }
170             dispose();
171         }
172 
173         /** Called when there is no need to {@link #apply(boolean)} the current transaction. */
abort()174         void abort() {
175             ensureCurrentTransaction();
176             dispose();
177         }
178 
dispose()179         private void dispose() {
180             TransactionManager.this.mCurrentTransaction = null;
181         }
182 
ensureCurrentTransaction()183         private void ensureCurrentTransaction() {
184             if (TransactionManager.this.mCurrentTransaction != this) {
185                 throw new IllegalStateException(
186                         "This transaction has already been apply() or abort().");
187             }
188         }
189 
190         /**
191          * Gets the {@link TaskFragmentTransitionType} that we will request transition with for the
192          * current {@link WindowContainerTransaction}.
193          */
194         @VisibleForTesting
195         @TaskFragmentTransitionType
getTransactionTransitionType()196         int getTransactionTransitionType() {
197             // Use TASK_FRAGMENT_TRANSIT_CHANGE as default if there is not opening/closing window.
198             return mOriginType != TASK_FRAGMENT_TRANSIT_NONE
199                     ? mOriginType
200                     : TASK_FRAGMENT_TRANSIT_CHANGE;
201         }
202     }
203 }
204