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 final TransactionRecord lastTransaction = mCurrentTransaction; 81 mCurrentTransaction = null; 82 throw new IllegalStateException( 83 "The previous transaction:" + lastTransaction + " has not been applied or " 84 + "aborted."); 85 } 86 mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken); 87 return mCurrentTransaction; 88 } 89 90 /** 91 * Gets the current {@link TransactionRecord} started from {@link #startNewTransaction}. 92 */ 93 @NonNull getCurrentTransactionRecord()94 TransactionRecord getCurrentTransactionRecord() { 95 if (mCurrentTransaction == null) { 96 throw new IllegalStateException("startNewTransaction() is not invoked before calling" 97 + " getCurrentTransactionRecord()."); 98 } 99 return mCurrentTransaction; 100 } 101 102 /** The current transaction. The manager should only handle one transaction at a time. */ 103 class TransactionRecord { 104 /** 105 * {@link WindowContainerTransaction} containing the current change. 106 * @see #startNewTransaction(IBinder) 107 * @see #apply (boolean) 108 */ 109 @NonNull 110 private final WindowContainerTransaction mTransaction = new WindowContainerTransaction(); 111 112 /** 113 * If the current transaction is a response to a 114 * {@link android.window.TaskFragmentTransaction}, this is the 115 * {@link android.window.TaskFragmentTransaction#getTransactionToken()}. 116 * @see #startNewTransaction(IBinder) 117 */ 118 @Nullable 119 private final IBinder mTaskFragmentTransactionToken; 120 121 /** 122 * To track of the origin type of the current {@link #mTransaction}. When 123 * {@link #apply (boolean)} to start a new transition, this is the type to request. 124 * @see #setOriginType(int) 125 * @see #getTransactionTransitionType() 126 */ 127 @TaskFragmentTransitionType 128 private int mOriginType = TASK_FRAGMENT_TRANSIT_NONE; 129 TransactionRecord(@ullable IBinder taskFragmentTransactionToken)130 TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) { 131 mTaskFragmentTransactionToken = taskFragmentTransactionToken; 132 } 133 134 @NonNull getTransaction()135 WindowContainerTransaction getTransaction() { 136 ensureCurrentTransaction(); 137 return mTransaction; 138 } 139 140 /** 141 * Sets the {@link TaskFragmentTransitionType} that triggers this transaction. If there are 142 * multiple calls, only the first call will be respected as the "origin" type. 143 */ setOriginType(@askFragmentTransitionType int type)144 void setOriginType(@TaskFragmentTransitionType int type) { 145 ensureCurrentTransaction(); 146 if (mOriginType != TASK_FRAGMENT_TRANSIT_NONE) { 147 // Skip if the origin type has already been set. 148 return; 149 } 150 mOriginType = type; 151 } 152 153 /** 154 * Requests the system server to apply the current transaction started from 155 * {@link #startNewTransaction}. 156 * @param shouldApplyIndependently If {@code true}, the {@link #mCurrentTransaction} will 157 * request a new transition, which will be queued until the 158 * sync engine is free if there is any other active sync. 159 * If {@code false}, the {@link #startNewTransaction} will 160 * be directly applied to the active sync. 161 */ apply(boolean shouldApplyIndependently)162 void apply(boolean shouldApplyIndependently) { 163 ensureCurrentTransaction(); 164 if (mTaskFragmentTransactionToken != null) { 165 // If this is a response to a TaskFragmentTransaction. 166 mOrganizer.onTransactionHandled(mTaskFragmentTransactionToken, mTransaction, 167 getTransactionTransitionType(), shouldApplyIndependently); 168 } else { 169 mOrganizer.applyTransaction(mTransaction, getTransactionTransitionType(), 170 shouldApplyIndependently); 171 } 172 dispose(); 173 } 174 175 /** Called when there is no need to {@link #apply(boolean)} the current transaction. */ abort()176 void abort() { 177 ensureCurrentTransaction(); 178 dispose(); 179 } 180 dispose()181 private void dispose() { 182 TransactionManager.this.mCurrentTransaction = null; 183 } 184 ensureCurrentTransaction()185 private void ensureCurrentTransaction() { 186 if (TransactionManager.this.mCurrentTransaction != this) { 187 throw new IllegalStateException( 188 "This transaction has already been apply() or abort()."); 189 } 190 } 191 192 /** 193 * Gets the {@link TaskFragmentTransitionType} that we will request transition with for the 194 * current {@link WindowContainerTransaction}. 195 */ 196 @VisibleForTesting 197 @TaskFragmentTransitionType getTransactionTransitionType()198 int getTransactionTransitionType() { 199 // Use TASK_FRAGMENT_TRANSIT_CHANGE as default if there is not opening/closing window. 200 return mOriginType != TASK_FRAGMENT_TRANSIT_NONE 201 ? mOriginType 202 : TASK_FRAGMENT_TRANSIT_CHANGE; 203 } 204 205 @Override 206 @NonNull toString()207 public String toString() { 208 return TransactionRecord.class.getSimpleName() + "{" 209 + "token=" + mTaskFragmentTransactionToken 210 + ", type=" + getTransactionTransitionType() 211 + ", transaction=" + mTransaction 212 + "}"; 213 } 214 } 215 } 216