Loading core/java/android/app/servertransaction/ActivityLifecycleItem.java +5 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,11 @@ public abstract class ActivityLifecycleItem extends ActivityTransactionItem { super(in); } @Override boolean isActivityLifecycleItem() { return true; } /** A final lifecycle state that an activity should reach. */ @LifecycleState public abstract int getTargetState(); Loading core/java/android/app/servertransaction/ClientTransaction.java +97 −8 Original line number Diff line number Diff line Loading @@ -45,6 +45,13 @@ import java.util.Objects; */ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** * List of transaction items that should be executed in order. Including both * {@link ActivityLifecycleItem} and other {@link ClientTransactionItem}. */ @Nullable private List<ClientTransactionItem> mTransactionItems; /** A list of individual callbacks to a client. */ @UnsupportedAppUsage private List<ClientTransactionItem> mActivityCallbacks; Loading @@ -64,9 +71,32 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { } /** * Add a message to the end of the sequence of callbacks. * Adds a message to the end of the sequence of transaction items. * @param item A single message that can contain a client activity/window request/callback. * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}. */ public void addTransactionItem(@NonNull ClientTransactionItem item) { if (mTransactionItems == null) { mTransactionItems = new ArrayList<>(); } mTransactionItems.add(item); } /** * Gets the list of client window requests/callbacks. * TODO(b/260873529): must be non null after remove the deprecated methods. */ @Nullable public List<ClientTransactionItem> getTransactionItems() { return mTransactionItems; } /** * Adds a message to the end of the sequence of callbacks. * @param activityCallback A single message that can contain a lifecycle request/callback. * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead. */ @Deprecated public void addCallback(@NonNull ClientTransactionItem activityCallback) { if (mActivityCallbacks == null) { mActivityCallbacks = new ArrayList<>(); Loading @@ -74,25 +104,35 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { mActivityCallbacks.add(activityCallback); } /** Get the list of callbacks. */ /** * Gets the list of callbacks. * @deprecated use {@link #getTransactionItems()} instead. */ @Nullable @VisibleForTesting @UnsupportedAppUsage @Deprecated public List<ClientTransactionItem> getCallbacks() { return mActivityCallbacks; } /** Get the target state lifecycle request. */ /** * Gets the target state lifecycle request. * @deprecated use {@link #getTransactionItems()} instead. */ @VisibleForTesting(visibility = PACKAGE) @UnsupportedAppUsage @Deprecated public ActivityLifecycleItem getLifecycleStateRequest() { return mLifecycleStateRequest; } /** * Set the lifecycle state in which the client should be after executing the transaction. * Sets the lifecycle state in which the client should be after executing the transaction. * @param stateRequest A lifecycle request initialized with right parameters. * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead. */ @Deprecated public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) { mLifecycleStateRequest = stateRequest; } Loading @@ -103,6 +143,14 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { * requested by transaction items. */ public void preExecute(@NonNull ClientTransactionHandler clientTransactionHandler) { if (mTransactionItems != null) { final int size = mTransactionItems.size(); for (int i = 0; i < size; ++i) { mTransactionItems.get(i).preExecute(clientTransactionHandler); } return; } if (mActivityCallbacks != null) { final int size = mActivityCallbacks.size(); for (int i = 0; i < size; ++i) { Loading Loading @@ -147,12 +195,19 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { @Override public void recycle() { if (mTransactionItems != null) { int size = mTransactionItems.size(); for (int i = 0; i < size; i++) { mTransactionItems.get(i).recycle(); } mTransactionItems = null; } if (mActivityCallbacks != null) { int size = mActivityCallbacks.size(); for (int i = 0; i < size; i++) { mActivityCallbacks.get(i).recycle(); } mActivityCallbacks.clear(); mActivityCallbacks = null; } if (mLifecycleStateRequest != null) { mLifecycleStateRequest.recycle(); Loading @@ -165,8 +220,15 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { // Parcelable implementation /** Write to Parcel. */ @SuppressWarnings("AndroidFrameworkEfficientParcelable") // Item class is not final. @Override public void writeToParcel(@NonNull Parcel dest, int flags) { final boolean writeTransactionItems = mTransactionItems != null; dest.writeBoolean(writeTransactionItems); if (writeTransactionItems) { dest.writeParcelableList(mTransactionItems, flags); } dest.writeParcelable(mLifecycleStateRequest, flags); final boolean writeActivityCallbacks = mActivityCallbacks != null; dest.writeBoolean(writeActivityCallbacks); Loading @@ -177,11 +239,20 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** Read from Parcel. */ private ClientTransaction(@NonNull Parcel in) { mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class); final boolean readTransactionItems = in.readBoolean(); if (readTransactionItems) { mTransactionItems = new ArrayList<>(); in.readParcelableList(mTransactionItems, getClass().getClassLoader(), ClientTransactionItem.class); } mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), ActivityLifecycleItem.class); final boolean readActivityCallbacks = in.readBoolean(); if (readActivityCallbacks) { mActivityCallbacks = new ArrayList<>(); in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class); in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), ClientTransactionItem.class); } } Loading Loading @@ -209,7 +280,8 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { return false; } final ClientTransaction other = (ClientTransaction) o; return Objects.equals(mActivityCallbacks, other.mActivityCallbacks) return Objects.equals(mTransactionItems, other.mTransactionItems) && Objects.equals(mActivityCallbacks, other.mActivityCallbacks) && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest) && mClient == other.mClient; } Loading @@ -217,6 +289,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { @Override public int hashCode() { int result = 17; result = 31 * result + Objects.hashCode(mTransactionItems); result = 31 * result + Objects.hashCode(mActivityCallbacks); result = 31 * result + Objects.hashCode(mLifecycleStateRequest); result = 31 * result + Objects.hashCode(mClient); Loading @@ -227,6 +300,22 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { void dump(@NonNull String prefix, @NonNull PrintWriter pw, @NonNull ClientTransactionHandler transactionHandler) { pw.append(prefix).println("ClientTransaction{"); if (mTransactionItems != null) { pw.append(prefix).print(" transactionItems=["); final String itemPrefix = prefix + " "; final int size = mTransactionItems.size(); if (size > 0) { pw.println(); for (int i = 0; i < size; i++) { mTransactionItems.get(i).dump(itemPrefix, pw, transactionHandler); } pw.append(prefix).println(" ]"); } else { pw.println("]"); } pw.append(prefix).println("}"); return; } pw.append(prefix).print(" callbacks=["); final String itemPrefix = prefix + " "; final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0; Loading core/java/android/app/servertransaction/ClientTransactionItem.java +7 −0 Original line number Diff line number Diff line Loading @@ -72,6 +72,13 @@ public abstract class ClientTransactionItem implements BaseClientRequest, Parcel return null; } /** * Whether this is a {@link ActivityLifecycleItem}. */ boolean isActivityLifecycleItem() { return false; } /** Dumps this transaction item. */ void dump(@NonNull String prefix, @NonNull PrintWriter pw, @NonNull ClientTransactionHandler transactionHandler) { Loading core/java/android/app/servertransaction/TransactionExecutor.java +105 −64 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; import static android.app.servertransaction.TransactionExecutorHelper.getShortActivityName; import static android.app.servertransaction.TransactionExecutorHelper.getStateName; import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState; import static android.app.servertransaction.TransactionExecutorHelper.shouldExcludeLastLifecycleState; import static android.app.servertransaction.TransactionExecutorHelper.tId; import static android.app.servertransaction.TransactionExecutorHelper.transactionToString; Loading Loading @@ -61,6 +62,9 @@ public class TransactionExecutor { private final PendingTransactionActions mPendingActions = new PendingTransactionActions(); private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper(); /** Keeps track of display ids whose Configuration got updated within a transaction. */ private final ArraySet<Integer> mConfigUpdatedDisplayIds = new ArraySet<>(); /** Initialize an instance with transaction handler, that will execute all requested actions. */ public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) { mTransactionHandler = clientTransactionHandler; Loading @@ -79,15 +83,52 @@ public class TransactionExecutor { Slog.d(TAG, transactionToString(transaction, mTransactionHandler)); } if (transaction.getTransactionItems() != null) { executeTransactionItems(transaction); } else { // TODO(b/260873529): cleanup after launch. executeCallbacks(transaction); executeLifecycleState(transaction); } if (!mConfigUpdatedDisplayIds.isEmpty()) { // Whether this transaction should trigger DisplayListener#onDisplayChanged. final ClientTransactionListenerController controller = ClientTransactionListenerController.getInstance(); final int displayCount = mConfigUpdatedDisplayIds.size(); for (int i = 0; i < displayCount; i++) { final int displayId = mConfigUpdatedDisplayIds.valueAt(i); controller.onDisplayChanged(displayId); } mConfigUpdatedDisplayIds.clear(); } mPendingActions.clear(); if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction"); } /** Cycle through all states requested by callbacks and execute them at proper times. */ /** Cycles through all transaction items and execute them at proper times. */ @VisibleForTesting public void executeTransactionItems(@NonNull ClientTransaction transaction) { final List<ClientTransactionItem> items = transaction.getTransactionItems(); final int size = items.size(); for (int i = 0; i < size; i++) { final ClientTransactionItem item = items.get(i); if (item.isActivityLifecycleItem()) { executeLifecycleItem(transaction, (ActivityLifecycleItem) item); } else { executeNonLifecycleItem(transaction, item, shouldExcludeLastLifecycleState(items, i)); } } } /** * Cycle through all states requested by callbacks and execute them at proper times. * @deprecated use {@link #executeTransactionItems} instead. */ @VisibleForTesting @Deprecated public void executeCallbacks(@NonNull ClientTransaction transaction) { final List<ClientTransactionItem> callbacks = transaction.getCallbacks(); if (callbacks == null || callbacks.isEmpty()) { Loading @@ -105,12 +146,20 @@ public class TransactionExecutor { // Index of the last callback that requests some post-execution state. final int lastCallbackRequestingState = lastCallbackRequestingState(transaction); // Keep track of display ids whose Configuration got updated with this transaction. ArraySet<Integer> configUpdatedDisplays = null; final int size = callbacks.size(); for (int i = 0; i < size; ++i) { final ClientTransactionItem item = callbacks.get(i); // Skip the very last transition and perform it by explicit state request instead. final int postExecutionState = item.getPostExecutionState(); final boolean shouldExcludeLastLifecycleState = postExecutionState != UNDEFINED && i == lastCallbackRequestingState && finalState == postExecutionState; executeNonLifecycleItem(transaction, item, shouldExcludeLastLifecycleState); } } private void executeNonLifecycleItem(@NonNull ClientTransaction transaction, @NonNull ClientTransactionItem item, boolean shouldExcludeLastLifecycleState) { final IBinder token = item.getActivityToken(); ActivityClientRecord r = mTransactionHandler.getActivityClient(token); Loading @@ -119,7 +168,7 @@ public class TransactionExecutor { // The activity has not been created but has been requested to destroy, so all // transactions for the token are just like being cancelled. Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item); continue; return; } if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item); Loading @@ -127,7 +176,7 @@ public class TransactionExecutor { if (item.shouldHaveDefinedPreExecutionState()) { final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r, item.getPostExecutionState()); postExecutionState); if (closestPreExecutionState != UNDEFINED) { cycleToPath(r, closestPreExecutionState, transaction); } Loading @@ -149,10 +198,7 @@ public class TransactionExecutor { final Configuration postExecutedConfig = configUpdatedContext.getResources() .getConfiguration(); if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) { if (configUpdatedDisplays == null) { configUpdatedDisplays = new ArraySet<>(); } configUpdatedDisplays.add(configUpdatedContext.getDisplayId()); mConfigUpdatedDisplayIds.add(configUpdatedContext.getDisplayId()); } } Loading @@ -163,25 +209,15 @@ public class TransactionExecutor { } if (postExecutionState != UNDEFINED && r != null) { // Skip the very last transition and perform it by explicit state request instead. final boolean shouldExcludeLastTransition = i == lastCallbackRequestingState && finalState == postExecutionState; cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction); cycleToPath(r, postExecutionState, shouldExcludeLastLifecycleState, transaction); } } if (configUpdatedDisplays != null) { final ClientTransactionListenerController controller = ClientTransactionListenerController.getInstance(); final int displayCount = configUpdatedDisplays.size(); for (int i = 0; i < displayCount; i++) { final int displayId = configUpdatedDisplays.valueAt(i); controller.onDisplayChanged(displayId); } } } /** Transition to the final state if requested by the transaction. */ /** * Transition to the final state if requested by the transaction. * @deprecated use {@link #executeTransactionItems} instead */ @Deprecated private void executeLifecycleState(@NonNull ClientTransaction transaction) { final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest(); if (lifecycleItem == null) { Loading @@ -189,6 +225,11 @@ public class TransactionExecutor { return; } executeLifecycleItem(transaction, lifecycleItem); } private void executeLifecycleItem(@NonNull ClientTransaction transaction, @NonNull ActivityLifecycleItem lifecycleItem) { final IBinder token = lifecycleItem.getActivityToken(); final ActivityClientRecord r = mTransactionHandler.getActivityClient(token); if (DEBUG_RESOLVER) { Loading core/java/android/app/servertransaction/TransactionExecutorHelper.java +72 −7 Original line number Diff line number Diff line Loading @@ -236,21 +236,39 @@ public class TransactionExecutorHelper { * index 1 will be returned, because ActivityResult request on position 1 will be the last * request that moves activity to the RESUMED state where it will eventually end. */ static int lastCallbackRequestingState(ClientTransaction transaction) { static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) { final List<ClientTransactionItem> callbacks = transaction.getCallbacks(); if (callbacks == null || callbacks.size() == 0) { if (callbacks == null || callbacks.isEmpty() || transaction.getLifecycleStateRequest() == null) { return -1; } return lastCallbackRequestingStateIndex(callbacks, 0, callbacks.size() - 1, transaction.getLifecycleStateRequest().getActivityToken()); } /** * Returns the index of the last callback between the start index and last index that requests * the state for the given activity token in which that activity will be after execution. * If there is a group of callbacks in the end that requests the same specific state or doesn't * request any - we will find the first one from such group. * * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any * specific state. If there is a sequence * Configuration - ActivityResult - Configuration - ActivityResult * index 1 will be returned, because ActivityResult request on position 1 will be the last * request that moves activity to the RESUMED state where it will eventually end. */ private static int lastCallbackRequestingStateIndex(@NonNull List<ClientTransactionItem> items, int startIndex, int lastIndex, @NonNull IBinder activityToken) { // Go from the back of the list to front, look for the request closes to the beginning that // requests the state in which activity will end after all callbacks are executed. int lastRequestedState = UNDEFINED; int lastRequestingCallback = -1; for (int i = callbacks.size() - 1; i >= 0; i--) { final ClientTransactionItem callback = callbacks.get(i); final int postExecutionState = callback.getPostExecutionState(); if (postExecutionState != UNDEFINED) { // Found a callback that requests some post-execution state. for (int i = lastIndex; i >= startIndex; i--) { final ClientTransactionItem item = items.get(i); final int postExecutionState = item.getPostExecutionState(); if (postExecutionState != UNDEFINED && activityToken.equals(item.getActivityToken())) { // Found a callback that requests some post-execution state for the given activity. if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) { // It's either a first-from-end callback that requests state or it requests // the same state as the last one. In both cases, we will use it as the new Loading @@ -266,6 +284,53 @@ public class TransactionExecutorHelper { return lastRequestingCallback; } /** * For the transaction item at {@code currentIndex}, if it is requesting post execution state, * whether or not to exclude the last state. This only returns {@code true} when there is a * following explicit {@link ActivityLifecycleItem} requesting the same state for the same * activity, so that last state will be covered by the following {@link ActivityLifecycleItem}. */ static boolean shouldExcludeLastLifecycleState(@NonNull List<ClientTransactionItem> items, int currentIndex) { final ClientTransactionItem item = items.get(currentIndex); final IBinder activityToken = item.getActivityToken(); final int postExecutionState = item.getPostExecutionState(); if (activityToken == null || postExecutionState == UNDEFINED) { // Not a transaction item requesting post execution state. return false; } final int nextLifecycleItemIndex = findNextLifecycleItemIndex(items, currentIndex + 1, activityToken); if (nextLifecycleItemIndex == -1) { // No following ActivityLifecycleItem for this activity token. return false; } final ActivityLifecycleItem lifecycleItem = (ActivityLifecycleItem) items.get(nextLifecycleItemIndex); if (postExecutionState != lifecycleItem.getTargetState()) { // The explicit ActivityLifecycleItem is not requesting the same state. return false; } // Only exclude for the first non-lifecycle item that requests the same specific state. return currentIndex == lastCallbackRequestingStateIndex(items, currentIndex, nextLifecycleItemIndex - 1, activityToken); } /** * Finds the index of the next {@link ActivityLifecycleItem} for the given activity token. */ private static int findNextLifecycleItemIndex(@NonNull List<ClientTransactionItem> items, int startIndex, @NonNull IBinder activityToken) { final int size = items.size(); for (int i = startIndex; i < size; i++) { final ClientTransactionItem item = items.get(i); if (item.isActivityLifecycleItem() && item.getActivityToken().equals(activityToken)) { return i; } } return -1; } /** Dump transaction to string. */ static String transactionToString(@NonNull ClientTransaction transaction, @NonNull ClientTransactionHandler transactionHandler) { Loading Loading
core/java/android/app/servertransaction/ActivityLifecycleItem.java +5 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,11 @@ public abstract class ActivityLifecycleItem extends ActivityTransactionItem { super(in); } @Override boolean isActivityLifecycleItem() { return true; } /** A final lifecycle state that an activity should reach. */ @LifecycleState public abstract int getTargetState(); Loading
core/java/android/app/servertransaction/ClientTransaction.java +97 −8 Original line number Diff line number Diff line Loading @@ -45,6 +45,13 @@ import java.util.Objects; */ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** * List of transaction items that should be executed in order. Including both * {@link ActivityLifecycleItem} and other {@link ClientTransactionItem}. */ @Nullable private List<ClientTransactionItem> mTransactionItems; /** A list of individual callbacks to a client. */ @UnsupportedAppUsage private List<ClientTransactionItem> mActivityCallbacks; Loading @@ -64,9 +71,32 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { } /** * Add a message to the end of the sequence of callbacks. * Adds a message to the end of the sequence of transaction items. * @param item A single message that can contain a client activity/window request/callback. * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}. */ public void addTransactionItem(@NonNull ClientTransactionItem item) { if (mTransactionItems == null) { mTransactionItems = new ArrayList<>(); } mTransactionItems.add(item); } /** * Gets the list of client window requests/callbacks. * TODO(b/260873529): must be non null after remove the deprecated methods. */ @Nullable public List<ClientTransactionItem> getTransactionItems() { return mTransactionItems; } /** * Adds a message to the end of the sequence of callbacks. * @param activityCallback A single message that can contain a lifecycle request/callback. * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead. */ @Deprecated public void addCallback(@NonNull ClientTransactionItem activityCallback) { if (mActivityCallbacks == null) { mActivityCallbacks = new ArrayList<>(); Loading @@ -74,25 +104,35 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { mActivityCallbacks.add(activityCallback); } /** Get the list of callbacks. */ /** * Gets the list of callbacks. * @deprecated use {@link #getTransactionItems()} instead. */ @Nullable @VisibleForTesting @UnsupportedAppUsage @Deprecated public List<ClientTransactionItem> getCallbacks() { return mActivityCallbacks; } /** Get the target state lifecycle request. */ /** * Gets the target state lifecycle request. * @deprecated use {@link #getTransactionItems()} instead. */ @VisibleForTesting(visibility = PACKAGE) @UnsupportedAppUsage @Deprecated public ActivityLifecycleItem getLifecycleStateRequest() { return mLifecycleStateRequest; } /** * Set the lifecycle state in which the client should be after executing the transaction. * Sets the lifecycle state in which the client should be after executing the transaction. * @param stateRequest A lifecycle request initialized with right parameters. * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead. */ @Deprecated public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) { mLifecycleStateRequest = stateRequest; } Loading @@ -103,6 +143,14 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { * requested by transaction items. */ public void preExecute(@NonNull ClientTransactionHandler clientTransactionHandler) { if (mTransactionItems != null) { final int size = mTransactionItems.size(); for (int i = 0; i < size; ++i) { mTransactionItems.get(i).preExecute(clientTransactionHandler); } return; } if (mActivityCallbacks != null) { final int size = mActivityCallbacks.size(); for (int i = 0; i < size; ++i) { Loading Loading @@ -147,12 +195,19 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { @Override public void recycle() { if (mTransactionItems != null) { int size = mTransactionItems.size(); for (int i = 0; i < size; i++) { mTransactionItems.get(i).recycle(); } mTransactionItems = null; } if (mActivityCallbacks != null) { int size = mActivityCallbacks.size(); for (int i = 0; i < size; i++) { mActivityCallbacks.get(i).recycle(); } mActivityCallbacks.clear(); mActivityCallbacks = null; } if (mLifecycleStateRequest != null) { mLifecycleStateRequest.recycle(); Loading @@ -165,8 +220,15 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { // Parcelable implementation /** Write to Parcel. */ @SuppressWarnings("AndroidFrameworkEfficientParcelable") // Item class is not final. @Override public void writeToParcel(@NonNull Parcel dest, int flags) { final boolean writeTransactionItems = mTransactionItems != null; dest.writeBoolean(writeTransactionItems); if (writeTransactionItems) { dest.writeParcelableList(mTransactionItems, flags); } dest.writeParcelable(mLifecycleStateRequest, flags); final boolean writeActivityCallbacks = mActivityCallbacks != null; dest.writeBoolean(writeActivityCallbacks); Loading @@ -177,11 +239,20 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** Read from Parcel. */ private ClientTransaction(@NonNull Parcel in) { mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class); final boolean readTransactionItems = in.readBoolean(); if (readTransactionItems) { mTransactionItems = new ArrayList<>(); in.readParcelableList(mTransactionItems, getClass().getClassLoader(), ClientTransactionItem.class); } mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), ActivityLifecycleItem.class); final boolean readActivityCallbacks = in.readBoolean(); if (readActivityCallbacks) { mActivityCallbacks = new ArrayList<>(); in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class); in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), ClientTransactionItem.class); } } Loading Loading @@ -209,7 +280,8 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { return false; } final ClientTransaction other = (ClientTransaction) o; return Objects.equals(mActivityCallbacks, other.mActivityCallbacks) return Objects.equals(mTransactionItems, other.mTransactionItems) && Objects.equals(mActivityCallbacks, other.mActivityCallbacks) && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest) && mClient == other.mClient; } Loading @@ -217,6 +289,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { @Override public int hashCode() { int result = 17; result = 31 * result + Objects.hashCode(mTransactionItems); result = 31 * result + Objects.hashCode(mActivityCallbacks); result = 31 * result + Objects.hashCode(mLifecycleStateRequest); result = 31 * result + Objects.hashCode(mClient); Loading @@ -227,6 +300,22 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { void dump(@NonNull String prefix, @NonNull PrintWriter pw, @NonNull ClientTransactionHandler transactionHandler) { pw.append(prefix).println("ClientTransaction{"); if (mTransactionItems != null) { pw.append(prefix).print(" transactionItems=["); final String itemPrefix = prefix + " "; final int size = mTransactionItems.size(); if (size > 0) { pw.println(); for (int i = 0; i < size; i++) { mTransactionItems.get(i).dump(itemPrefix, pw, transactionHandler); } pw.append(prefix).println(" ]"); } else { pw.println("]"); } pw.append(prefix).println("}"); return; } pw.append(prefix).print(" callbacks=["); final String itemPrefix = prefix + " "; final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0; Loading
core/java/android/app/servertransaction/ClientTransactionItem.java +7 −0 Original line number Diff line number Diff line Loading @@ -72,6 +72,13 @@ public abstract class ClientTransactionItem implements BaseClientRequest, Parcel return null; } /** * Whether this is a {@link ActivityLifecycleItem}. */ boolean isActivityLifecycleItem() { return false; } /** Dumps this transaction item. */ void dump(@NonNull String prefix, @NonNull PrintWriter pw, @NonNull ClientTransactionHandler transactionHandler) { Loading
core/java/android/app/servertransaction/TransactionExecutor.java +105 −64 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; import static android.app.servertransaction.TransactionExecutorHelper.getShortActivityName; import static android.app.servertransaction.TransactionExecutorHelper.getStateName; import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState; import static android.app.servertransaction.TransactionExecutorHelper.shouldExcludeLastLifecycleState; import static android.app.servertransaction.TransactionExecutorHelper.tId; import static android.app.servertransaction.TransactionExecutorHelper.transactionToString; Loading Loading @@ -61,6 +62,9 @@ public class TransactionExecutor { private final PendingTransactionActions mPendingActions = new PendingTransactionActions(); private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper(); /** Keeps track of display ids whose Configuration got updated within a transaction. */ private final ArraySet<Integer> mConfigUpdatedDisplayIds = new ArraySet<>(); /** Initialize an instance with transaction handler, that will execute all requested actions. */ public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) { mTransactionHandler = clientTransactionHandler; Loading @@ -79,15 +83,52 @@ public class TransactionExecutor { Slog.d(TAG, transactionToString(transaction, mTransactionHandler)); } if (transaction.getTransactionItems() != null) { executeTransactionItems(transaction); } else { // TODO(b/260873529): cleanup after launch. executeCallbacks(transaction); executeLifecycleState(transaction); } if (!mConfigUpdatedDisplayIds.isEmpty()) { // Whether this transaction should trigger DisplayListener#onDisplayChanged. final ClientTransactionListenerController controller = ClientTransactionListenerController.getInstance(); final int displayCount = mConfigUpdatedDisplayIds.size(); for (int i = 0; i < displayCount; i++) { final int displayId = mConfigUpdatedDisplayIds.valueAt(i); controller.onDisplayChanged(displayId); } mConfigUpdatedDisplayIds.clear(); } mPendingActions.clear(); if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction"); } /** Cycle through all states requested by callbacks and execute them at proper times. */ /** Cycles through all transaction items and execute them at proper times. */ @VisibleForTesting public void executeTransactionItems(@NonNull ClientTransaction transaction) { final List<ClientTransactionItem> items = transaction.getTransactionItems(); final int size = items.size(); for (int i = 0; i < size; i++) { final ClientTransactionItem item = items.get(i); if (item.isActivityLifecycleItem()) { executeLifecycleItem(transaction, (ActivityLifecycleItem) item); } else { executeNonLifecycleItem(transaction, item, shouldExcludeLastLifecycleState(items, i)); } } } /** * Cycle through all states requested by callbacks and execute them at proper times. * @deprecated use {@link #executeTransactionItems} instead. */ @VisibleForTesting @Deprecated public void executeCallbacks(@NonNull ClientTransaction transaction) { final List<ClientTransactionItem> callbacks = transaction.getCallbacks(); if (callbacks == null || callbacks.isEmpty()) { Loading @@ -105,12 +146,20 @@ public class TransactionExecutor { // Index of the last callback that requests some post-execution state. final int lastCallbackRequestingState = lastCallbackRequestingState(transaction); // Keep track of display ids whose Configuration got updated with this transaction. ArraySet<Integer> configUpdatedDisplays = null; final int size = callbacks.size(); for (int i = 0; i < size; ++i) { final ClientTransactionItem item = callbacks.get(i); // Skip the very last transition and perform it by explicit state request instead. final int postExecutionState = item.getPostExecutionState(); final boolean shouldExcludeLastLifecycleState = postExecutionState != UNDEFINED && i == lastCallbackRequestingState && finalState == postExecutionState; executeNonLifecycleItem(transaction, item, shouldExcludeLastLifecycleState); } } private void executeNonLifecycleItem(@NonNull ClientTransaction transaction, @NonNull ClientTransactionItem item, boolean shouldExcludeLastLifecycleState) { final IBinder token = item.getActivityToken(); ActivityClientRecord r = mTransactionHandler.getActivityClient(token); Loading @@ -119,7 +168,7 @@ public class TransactionExecutor { // The activity has not been created but has been requested to destroy, so all // transactions for the token are just like being cancelled. Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item); continue; return; } if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item); Loading @@ -127,7 +176,7 @@ public class TransactionExecutor { if (item.shouldHaveDefinedPreExecutionState()) { final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r, item.getPostExecutionState()); postExecutionState); if (closestPreExecutionState != UNDEFINED) { cycleToPath(r, closestPreExecutionState, transaction); } Loading @@ -149,10 +198,7 @@ public class TransactionExecutor { final Configuration postExecutedConfig = configUpdatedContext.getResources() .getConfiguration(); if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) { if (configUpdatedDisplays == null) { configUpdatedDisplays = new ArraySet<>(); } configUpdatedDisplays.add(configUpdatedContext.getDisplayId()); mConfigUpdatedDisplayIds.add(configUpdatedContext.getDisplayId()); } } Loading @@ -163,25 +209,15 @@ public class TransactionExecutor { } if (postExecutionState != UNDEFINED && r != null) { // Skip the very last transition and perform it by explicit state request instead. final boolean shouldExcludeLastTransition = i == lastCallbackRequestingState && finalState == postExecutionState; cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction); cycleToPath(r, postExecutionState, shouldExcludeLastLifecycleState, transaction); } } if (configUpdatedDisplays != null) { final ClientTransactionListenerController controller = ClientTransactionListenerController.getInstance(); final int displayCount = configUpdatedDisplays.size(); for (int i = 0; i < displayCount; i++) { final int displayId = configUpdatedDisplays.valueAt(i); controller.onDisplayChanged(displayId); } } } /** Transition to the final state if requested by the transaction. */ /** * Transition to the final state if requested by the transaction. * @deprecated use {@link #executeTransactionItems} instead */ @Deprecated private void executeLifecycleState(@NonNull ClientTransaction transaction) { final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest(); if (lifecycleItem == null) { Loading @@ -189,6 +225,11 @@ public class TransactionExecutor { return; } executeLifecycleItem(transaction, lifecycleItem); } private void executeLifecycleItem(@NonNull ClientTransaction transaction, @NonNull ActivityLifecycleItem lifecycleItem) { final IBinder token = lifecycleItem.getActivityToken(); final ActivityClientRecord r = mTransactionHandler.getActivityClient(token); if (DEBUG_RESOLVER) { Loading
core/java/android/app/servertransaction/TransactionExecutorHelper.java +72 −7 Original line number Diff line number Diff line Loading @@ -236,21 +236,39 @@ public class TransactionExecutorHelper { * index 1 will be returned, because ActivityResult request on position 1 will be the last * request that moves activity to the RESUMED state where it will eventually end. */ static int lastCallbackRequestingState(ClientTransaction transaction) { static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) { final List<ClientTransactionItem> callbacks = transaction.getCallbacks(); if (callbacks == null || callbacks.size() == 0) { if (callbacks == null || callbacks.isEmpty() || transaction.getLifecycleStateRequest() == null) { return -1; } return lastCallbackRequestingStateIndex(callbacks, 0, callbacks.size() - 1, transaction.getLifecycleStateRequest().getActivityToken()); } /** * Returns the index of the last callback between the start index and last index that requests * the state for the given activity token in which that activity will be after execution. * If there is a group of callbacks in the end that requests the same specific state or doesn't * request any - we will find the first one from such group. * * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any * specific state. If there is a sequence * Configuration - ActivityResult - Configuration - ActivityResult * index 1 will be returned, because ActivityResult request on position 1 will be the last * request that moves activity to the RESUMED state where it will eventually end. */ private static int lastCallbackRequestingStateIndex(@NonNull List<ClientTransactionItem> items, int startIndex, int lastIndex, @NonNull IBinder activityToken) { // Go from the back of the list to front, look for the request closes to the beginning that // requests the state in which activity will end after all callbacks are executed. int lastRequestedState = UNDEFINED; int lastRequestingCallback = -1; for (int i = callbacks.size() - 1; i >= 0; i--) { final ClientTransactionItem callback = callbacks.get(i); final int postExecutionState = callback.getPostExecutionState(); if (postExecutionState != UNDEFINED) { // Found a callback that requests some post-execution state. for (int i = lastIndex; i >= startIndex; i--) { final ClientTransactionItem item = items.get(i); final int postExecutionState = item.getPostExecutionState(); if (postExecutionState != UNDEFINED && activityToken.equals(item.getActivityToken())) { // Found a callback that requests some post-execution state for the given activity. if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) { // It's either a first-from-end callback that requests state or it requests // the same state as the last one. In both cases, we will use it as the new Loading @@ -266,6 +284,53 @@ public class TransactionExecutorHelper { return lastRequestingCallback; } /** * For the transaction item at {@code currentIndex}, if it is requesting post execution state, * whether or not to exclude the last state. This only returns {@code true} when there is a * following explicit {@link ActivityLifecycleItem} requesting the same state for the same * activity, so that last state will be covered by the following {@link ActivityLifecycleItem}. */ static boolean shouldExcludeLastLifecycleState(@NonNull List<ClientTransactionItem> items, int currentIndex) { final ClientTransactionItem item = items.get(currentIndex); final IBinder activityToken = item.getActivityToken(); final int postExecutionState = item.getPostExecutionState(); if (activityToken == null || postExecutionState == UNDEFINED) { // Not a transaction item requesting post execution state. return false; } final int nextLifecycleItemIndex = findNextLifecycleItemIndex(items, currentIndex + 1, activityToken); if (nextLifecycleItemIndex == -1) { // No following ActivityLifecycleItem for this activity token. return false; } final ActivityLifecycleItem lifecycleItem = (ActivityLifecycleItem) items.get(nextLifecycleItemIndex); if (postExecutionState != lifecycleItem.getTargetState()) { // The explicit ActivityLifecycleItem is not requesting the same state. return false; } // Only exclude for the first non-lifecycle item that requests the same specific state. return currentIndex == lastCallbackRequestingStateIndex(items, currentIndex, nextLifecycleItemIndex - 1, activityToken); } /** * Finds the index of the next {@link ActivityLifecycleItem} for the given activity token. */ private static int findNextLifecycleItemIndex(@NonNull List<ClientTransactionItem> items, int startIndex, @NonNull IBinder activityToken) { final int size = items.size(); for (int i = startIndex; i < size; i++) { final ClientTransactionItem item = items.get(i); if (item.isActivityLifecycleItem() && item.getActivityToken().equals(activityToken)) { return i; } } return -1; } /** Dump transaction to string. */ static String transactionToString(@NonNull ClientTransaction transaction, @NonNull ClientTransactionHandler transactionHandler) { Loading