Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit def520df authored by Chris Li's avatar Chris Li
Browse files

Synchronize window config updates (7/n)

Allow ClientTransaction to take more than one ActivityLifecycleItem.

To keep the client side behavior unchanged, this CL refactors to allow
ClientTransaction to take lifecycle request in the same way as
non-lifecyle request, so that they are queued in order.

As a refactor, this should be no-op until replacing the server side
deprecated methods to use #addTransactionItem.

Bug: 260873529
Test: atest FrameworksCoreTests:TransactionExecutorTests
Change-Id: Ida33994bc78673a548c137be50a51daed4e873db
parent 36d818fb
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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();
+97 −8
Original line number Diff line number Diff line
@@ -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;
@@ -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<>();
@@ -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;
    }
@@ -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) {
@@ -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();
@@ -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);
@@ -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);
        }
    }

@@ -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;
    }
@@ -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);
@@ -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;
+7 −0
Original line number Diff line number Diff line
@@ -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) {
+105 −64
Original line number Diff line number Diff line
@@ -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;

@@ -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;
@@ -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()) {
@@ -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);

@@ -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);
@@ -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);
            }
@@ -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());
            }
        }

@@ -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) {
@@ -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) {
+72 −7
Original line number Diff line number Diff line
@@ -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
@@ -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