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