/* * Copyright 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app.servertransaction; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ClientTransactionHandler; import android.app.IApplicationThread; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * A container that holds a sequence of messages, which may be sent to a client. * This includes a list of callbacks and a final lifecycle state. * * @see com.android.server.wm.ClientLifecycleManager * @see ClientTransactionItem * @see ActivityLifecycleItem * @hide */ 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 mTransactionItems; /** @deprecated use {@link #getTransactionItems} instead. */ @Nullable @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, trackingBug = 324203798, publicAlternatives = "Use {@code #getTransactionItems()}") @Deprecated private List mActivityCallbacks; /** * Final lifecycle state in which the client activity should be after the transaction is * executed. */ // TODO(b/324203798): cleanup after remove UnsupportedAppUsage @Nullable private ActivityLifecycleItem mLifecycleStateRequest; /** Only kept for unsupportedAppUsage {@link #getActivityToken()}. Must not be used. */ // TODO(b/324203798): cleanup after remove UnsupportedAppUsage @Nullable private IBinder mActivityToken; /** Target client. */ private IApplicationThread mClient; /** Get the target client of the transaction. */ public IApplicationThread getClient() { return mClient; } /** * 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. */ public void addTransactionItem(@NonNull ClientTransactionItem item) { if (Flags.bundleClientTransactionFlag()) { if (mTransactionItems == null) { mTransactionItems = new ArrayList<>(); } mTransactionItems.add(item); } // TODO(b/324203798): cleanup after remove UnsupportedAppUsage // Populate even if mTransactionItems is set to support the UnsupportedAppUsage. if (item.isActivityLifecycleItem()) { setLifecycleStateRequest((ActivityLifecycleItem) item); } else { addCallback(item); } } /** * Gets the list of client window requests/callbacks. * TODO(b/260873529): must be non null after remove the deprecated methods. */ @Nullable public List 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. */ // TODO(b/324203798): cleanup after remove UnsupportedAppUsage @Deprecated private void addCallback(@NonNull ClientTransactionItem activityCallback) { if (mActivityCallbacks == null) { mActivityCallbacks = new ArrayList<>(); } mActivityCallbacks.add(activityCallback); setActivityTokenIfNotSet(activityCallback); } /** @deprecated use {@link #getTransactionItems()} instead. */ @VisibleForTesting @Nullable @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, trackingBug = 324203798, publicAlternatives = "Use {@code #getTransactionItems()}") @Deprecated public List getCallbacks() { return mActivityCallbacks; } /** * A transaction can contain {@link ClientTransactionItem} of different activities, * this must not be used. For any unsupported app usages, please be aware that this is set to * the activity of the first item in {@link #getTransactionItems()}. * * @deprecated use {@link ClientTransactionItem#getActivityToken()} instead. */ @VisibleForTesting @Nullable @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, trackingBug = 324203798, publicAlternatives = "Use {@code android.app.servertransaction" + ".ClientTransactionItem#getActivityToken()}") @Deprecated public IBinder getActivityToken() { return mActivityToken; } /** @deprecated use {@link #getTransactionItems()} instead. */ @VisibleForTesting(visibility = PACKAGE) @Nullable @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, trackingBug = 324203798, publicAlternatives = "Use {@code #getTransactionItems()}") @Deprecated public ActivityLifecycleItem getLifecycleStateRequest() { return mLifecycleStateRequest; } /** * 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. */ // TODO(b/324203798): cleanup after remove UnsupportedAppUsage @Deprecated private void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) { if (mLifecycleStateRequest != null) { return; } mLifecycleStateRequest = stateRequest; setActivityTokenIfNotSet(stateRequest); } // TODO(b/324203798): cleanup after remove UnsupportedAppUsage private void setActivityTokenIfNotSet(@Nullable ClientTransactionItem item) { if (mActivityToken == null && item != null) { mActivityToken = item.getActivityToken(); } } /** * Do what needs to be done while the transaction is being scheduled on the client side. * @param clientTransactionHandler Handler on the client side that will executed all operations * 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) { mActivityCallbacks.get(i).preExecute(clientTransactionHandler); } } if (mLifecycleStateRequest != null) { mLifecycleStateRequest.preExecute(clientTransactionHandler); } } /** * Schedule the transaction after it was initialized. It will be send to client and all its * individual parts will be applied in the following sequence: * 1. The client calls {@link #preExecute(ClientTransactionHandler)}, which triggers all work * that needs to be done before actually scheduling the transaction for callbacks and * lifecycle state request. * 2. The transaction message is scheduled. * 3. The client calls {@link TransactionExecutor#execute(ClientTransaction)}, which executes * all callbacks and necessary lifecycle transitions. */ public void schedule() throws RemoteException { mClient.scheduleTransaction(this); } // ObjectPoolItem implementation private ClientTransaction() {} /** Obtains an instance initialized with provided params. */ @NonNull public static ClientTransaction obtain(@Nullable IApplicationThread client) { ClientTransaction instance = ObjectPool.obtain(ClientTransaction.class); if (instance == null) { instance = new ClientTransaction(); } instance.mClient = client; return instance; } @Override public void recycle() { if (mTransactionItems != null) { int size = mTransactionItems.size(); for (int i = 0; i < size; i++) { mTransactionItems.get(i).recycle(); } mTransactionItems = null; mActivityCallbacks = null; mLifecycleStateRequest = null; } else { // Only needed when mTransactionItems is null, otherwise these will have the same // reference as mTransactionItems to support UnsupportedAppUsage. if (mActivityCallbacks != null) { int size = mActivityCallbacks.size(); for (int i = 0; i < size; i++) { mActivityCallbacks.get(i).recycle(); } mActivityCallbacks = null; } if (mLifecycleStateRequest != null) { mLifecycleStateRequest.recycle(); mLifecycleStateRequest = null; } } mClient = null; mActivityToken = null; ObjectPool.recycle(this); } // 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); } else { // TODO(b/324203798): cleanup after remove UnsupportedAppUsage // Only write mLifecycleStateRequest and mActivityCallbacks when mTransactionItems is // null dest.writeParcelable(mLifecycleStateRequest, flags); final boolean writeActivityCallbacks = mActivityCallbacks != null; dest.writeBoolean(writeActivityCallbacks); if (writeActivityCallbacks) { dest.writeParcelableList(mActivityCallbacks, flags); } } } /** Read from Parcel. */ private ClientTransaction(@NonNull Parcel in) { final boolean readTransactionItems = in.readBoolean(); if (readTransactionItems) { mTransactionItems = new ArrayList<>(); in.readParcelableList(mTransactionItems, getClass().getClassLoader(), ClientTransactionItem.class); // TODO(b/324203798): cleanup after remove UnsupportedAppUsage // Populate mLifecycleStateRequest and mActivityCallbacks from mTransactionItems so // that they have the same reference when there is UnsupportedAppUsage to those fields. final int size = mTransactionItems.size(); for (int i = 0; i < size; i++) { final ClientTransactionItem item = mTransactionItems.get(i); if (item.isActivityLifecycleItem()) { setLifecycleStateRequest((ActivityLifecycleItem) item); } else { addCallback(item); } } } else { // TODO(b/324203798): cleanup after remove UnsupportedAppUsage // Only read mLifecycleStateRequest and mActivityCallbacks when mTransactionItems is // null mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), ActivityLifecycleItem.class); setActivityTokenIfNotSet(mLifecycleStateRequest); final boolean readActivityCallbacks = in.readBoolean(); if (readActivityCallbacks) { mActivityCallbacks = new ArrayList<>(); in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), ClientTransactionItem.class); final int size = mActivityCallbacks.size(); for (int i = 0; mActivityToken == null && i < size; i++) { final ClientTransactionItem item = mActivityCallbacks.get(i); setActivityTokenIfNotSet(item); } } } } public static final @NonNull Creator CREATOR = new Creator<>() { public ClientTransaction createFromParcel(@NonNull Parcel in) { return new ClientTransaction(in); } public ClientTransaction[] newArray(int size) { return new ClientTransaction[size]; } }; @Override public int describeContents() { return 0; } @Override public boolean equals(@Nullable Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final ClientTransaction other = (ClientTransaction) o; return Objects.equals(mTransactionItems, other.mTransactionItems) && Objects.equals(mActivityCallbacks, other.mActivityCallbacks) && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest) && mClient == other.mClient && Objects.equals(mActivityToken, other.mActivityToken); } @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); result = 31 * result + Objects.hashCode(mActivityToken); return result; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("ClientTransaction{"); if (mTransactionItems != null) { // #addTransactionItem sb.append("\n transactionItems=["); final int size = mTransactionItems.size(); for (int i = 0; i < size; i++) { sb.append("\n ").append(mTransactionItems.get(i)); } sb.append("\n ]"); } else { // #addCallback sb.append("\n callbacks=["); final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0; for (int i = 0; i < size; i++) { sb.append("\n ").append(mActivityCallbacks.get(i)); } sb.append("\n ]"); // #setLifecycleStateRequest sb.append("\n stateRequest=").append(mLifecycleStateRequest); } sb.append("\n}"); return sb.toString(); } /** Dump transaction items callback items and final lifecycle state request. */ 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; if (size > 0) { pw.println(); for (int i = 0; i < size; i++) { mActivityCallbacks.get(i).dump(itemPrefix, pw, transactionHandler); } pw.append(prefix).println(" ]"); } else { pw.println("]"); } pw.append(prefix).println(" stateRequest="); if (mLifecycleStateRequest != null) { mLifecycleStateRequest.dump(itemPrefix, pw, transactionHandler); } else { pw.append(itemPrefix).println("null"); } pw.append(prefix).println("}"); } }