1 /*
2  * Copyright 2017 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 android.app.servertransaction;
18 
19 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.ClientTransactionHandler;
24 import android.app.IApplicationThread;
25 import android.compat.annotation.UnsupportedAppUsage;
26 import android.os.Build;
27 import android.os.IBinder;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.os.RemoteException;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.window.flags.Flags;
34 
35 import java.io.PrintWriter;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Objects;
39 
40 /**
41  * A container that holds a sequence of messages, which may be sent to a client.
42  * This includes a list of callbacks and a final lifecycle state.
43  *
44  * @see com.android.server.wm.ClientLifecycleManager
45  * @see ClientTransactionItem
46  * @see ActivityLifecycleItem
47  * @hide
48  */
49 public class ClientTransaction implements Parcelable, ObjectPoolItem {
50 
51     /**
52      * List of transaction items that should be executed in order. Including both
53      * {@link ActivityLifecycleItem} and other {@link ClientTransactionItem}.
54      */
55     @Nullable
56     private List<ClientTransactionItem> mTransactionItems;
57 
58     /** @deprecated use {@link #getTransactionItems} instead. */
59     @Nullable
60     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
61             trackingBug = 324203798,
62             publicAlternatives = "Use {@code #getTransactionItems()}")
63     @Deprecated
64     private List<ClientTransactionItem> mActivityCallbacks;
65 
66     /**
67      * Final lifecycle state in which the client activity should be after the transaction is
68      * executed.
69      */
70     // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
71     @Nullable
72     private ActivityLifecycleItem mLifecycleStateRequest;
73 
74     /** Only kept for unsupportedAppUsage {@link #getActivityToken()}. Must not be used. */
75     // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
76     @Nullable
77     private IBinder mActivityToken;
78 
79     /** Target client. */
80     private IApplicationThread mClient;
81 
82     /** Get the target client of the transaction. */
getClient()83     public IApplicationThread getClient() {
84         return mClient;
85     }
86 
87     /**
88      * Adds a message to the end of the sequence of transaction items.
89      * @param item A single message that can contain a client activity/window request/callback.
90      */
addTransactionItem(@onNull ClientTransactionItem item)91     public void addTransactionItem(@NonNull ClientTransactionItem item) {
92         if (Flags.bundleClientTransactionFlag()) {
93             if (mTransactionItems == null) {
94                 mTransactionItems = new ArrayList<>();
95             }
96             mTransactionItems.add(item);
97         }
98 
99         // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
100         // Populate even if mTransactionItems is set to support the UnsupportedAppUsage.
101         if (item.isActivityLifecycleItem()) {
102             setLifecycleStateRequest((ActivityLifecycleItem) item);
103         } else {
104             addCallback(item);
105         }
106     }
107 
108     /**
109      * Gets the list of client window requests/callbacks.
110      * TODO(b/260873529): must be non null after remove the deprecated methods.
111      */
112     @Nullable
getTransactionItems()113     public List<ClientTransactionItem> getTransactionItems() {
114         return mTransactionItems;
115     }
116 
117     /**
118      * Adds a message to the end of the sequence of callbacks.
119      * @param activityCallback A single message that can contain a lifecycle request/callback.
120      * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
121      */
122     // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
123     @Deprecated
addCallback(@onNull ClientTransactionItem activityCallback)124     private void addCallback(@NonNull ClientTransactionItem activityCallback) {
125         if (mActivityCallbacks == null) {
126             mActivityCallbacks = new ArrayList<>();
127         }
128         mActivityCallbacks.add(activityCallback);
129         setActivityTokenIfNotSet(activityCallback);
130     }
131 
132     /** @deprecated use {@link #getTransactionItems()} instead. */
133     @VisibleForTesting
134     @Nullable
135     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
136             trackingBug = 324203798,
137             publicAlternatives = "Use {@code #getTransactionItems()}")
138     @Deprecated
getCallbacks()139     public List<ClientTransactionItem> getCallbacks() {
140         return mActivityCallbacks;
141     }
142 
143     /**
144      * A transaction can contain {@link ClientTransactionItem} of different activities,
145      * this must not be used. For any unsupported app usages, please be aware that this is set to
146      * the activity of the first item in {@link #getTransactionItems()}.
147      *
148      * @deprecated use {@link ClientTransactionItem#getActivityToken()} instead.
149      */
150     @VisibleForTesting
151     @Nullable
152     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
153             trackingBug = 324203798,
154             publicAlternatives = "Use {@code android.app.servertransaction"
155                     + ".ClientTransactionItem#getActivityToken()}")
156     @Deprecated
getActivityToken()157     public IBinder getActivityToken() {
158         return mActivityToken;
159     }
160 
161     /** @deprecated use {@link #getTransactionItems()} instead. */
162     @VisibleForTesting(visibility = PACKAGE)
163     @Nullable
164     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
165             trackingBug = 324203798,
166             publicAlternatives = "Use {@code #getTransactionItems()}")
167     @Deprecated
getLifecycleStateRequest()168     public ActivityLifecycleItem getLifecycleStateRequest() {
169         return mLifecycleStateRequest;
170     }
171 
172     /**
173      * Sets the lifecycle state in which the client should be after executing the transaction.
174      * @param stateRequest A lifecycle request initialized with right parameters.
175      * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
176      */
177     // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
178     @Deprecated
setLifecycleStateRequest(@onNull ActivityLifecycleItem stateRequest)179     private void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
180         if (mLifecycleStateRequest != null) {
181             return;
182         }
183         mLifecycleStateRequest = stateRequest;
184         setActivityTokenIfNotSet(stateRequest);
185     }
186 
187     // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
setActivityTokenIfNotSet(@ullable ClientTransactionItem item)188     private void setActivityTokenIfNotSet(@Nullable ClientTransactionItem item) {
189         if (mActivityToken == null && item != null) {
190             mActivityToken = item.getActivityToken();
191         }
192     }
193 
194     /**
195      * Do what needs to be done while the transaction is being scheduled on the client side.
196      * @param clientTransactionHandler Handler on the client side that will executed all operations
197      *                                 requested by transaction items.
198      */
preExecute(@onNull ClientTransactionHandler clientTransactionHandler)199     public void preExecute(@NonNull ClientTransactionHandler clientTransactionHandler) {
200         if (mTransactionItems != null) {
201             final int size = mTransactionItems.size();
202             for (int i = 0; i < size; ++i) {
203                 mTransactionItems.get(i).preExecute(clientTransactionHandler);
204             }
205             return;
206         }
207 
208         if (mActivityCallbacks != null) {
209             final int size = mActivityCallbacks.size();
210             for (int i = 0; i < size; ++i) {
211                 mActivityCallbacks.get(i).preExecute(clientTransactionHandler);
212             }
213         }
214         if (mLifecycleStateRequest != null) {
215             mLifecycleStateRequest.preExecute(clientTransactionHandler);
216         }
217     }
218 
219     /**
220      * Schedule the transaction after it was initialized. It will be send to client and all its
221      * individual parts will be applied in the following sequence:
222      * 1. The client calls {@link #preExecute(ClientTransactionHandler)}, which triggers all work
223      *    that needs to be done before actually scheduling the transaction for callbacks and
224      *    lifecycle state request.
225      * 2. The transaction message is scheduled.
226      * 3. The client calls {@link TransactionExecutor#execute(ClientTransaction)}, which executes
227      *    all callbacks and necessary lifecycle transitions.
228      */
schedule()229     public void schedule() throws RemoteException {
230         mClient.scheduleTransaction(this);
231     }
232 
233 
234     // ObjectPoolItem implementation
235 
ClientTransaction()236     private ClientTransaction() {}
237 
238     /** Obtains an instance initialized with provided params. */
239     @NonNull
obtain(@ullable IApplicationThread client)240     public static ClientTransaction obtain(@Nullable IApplicationThread client) {
241         ClientTransaction instance = ObjectPool.obtain(ClientTransaction.class);
242         if (instance == null) {
243             instance = new ClientTransaction();
244         }
245         instance.mClient = client;
246 
247         return instance;
248     }
249 
250     @Override
recycle()251     public void recycle() {
252         if (Flags.disableObjectPool()) {
253             return;
254         }
255         if (mTransactionItems != null) {
256             int size = mTransactionItems.size();
257             for (int i = 0; i < size; i++) {
258                 mTransactionItems.get(i).recycle();
259             }
260             mTransactionItems = null;
261             mActivityCallbacks = null;
262             mLifecycleStateRequest = null;
263         } else {
264             // Only needed when mTransactionItems is null, otherwise these will have the same
265             // reference as mTransactionItems to support UnsupportedAppUsage.
266             if (mActivityCallbacks != null) {
267                 int size = mActivityCallbacks.size();
268                 for (int i = 0; i < size; i++) {
269                     mActivityCallbacks.get(i).recycle();
270                 }
271                 mActivityCallbacks = null;
272             }
273             if (mLifecycleStateRequest != null) {
274                 mLifecycleStateRequest.recycle();
275                 mLifecycleStateRequest = null;
276             }
277         }
278         mClient = null;
279         mActivityToken = null;
280         ObjectPool.recycle(this);
281     }
282 
283     // Parcelable implementation
284 
285     /** Write to Parcel. */
286     @SuppressWarnings("AndroidFrameworkEfficientParcelable") // Item class is not final.
287     @Override
writeToParcel(@onNull Parcel dest, int flags)288     public void writeToParcel(@NonNull Parcel dest, int flags) {
289         final boolean writeTransactionItems = mTransactionItems != null;
290         dest.writeBoolean(writeTransactionItems);
291         if (writeTransactionItems) {
292             dest.writeParcelableList(mTransactionItems, flags);
293         } else {
294             // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
295             // Only write mLifecycleStateRequest and mActivityCallbacks when mTransactionItems is
296             // null
297             dest.writeParcelable(mLifecycleStateRequest, flags);
298             final boolean writeActivityCallbacks = mActivityCallbacks != null;
299             dest.writeBoolean(writeActivityCallbacks);
300             if (writeActivityCallbacks) {
301                 dest.writeParcelableList(mActivityCallbacks, flags);
302             }
303         }
304     }
305 
306     /** Read from Parcel. */
ClientTransaction(@onNull Parcel in)307     private ClientTransaction(@NonNull Parcel in) {
308         final boolean readTransactionItems = in.readBoolean();
309         if (readTransactionItems) {
310             mTransactionItems = new ArrayList<>();
311             in.readParcelableList(mTransactionItems, getClass().getClassLoader(),
312                     ClientTransactionItem.class);
313 
314             // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
315             // Populate mLifecycleStateRequest and mActivityCallbacks from mTransactionItems so
316             // that they have the same reference when there is UnsupportedAppUsage to those fields.
317             final int size = mTransactionItems.size();
318             for (int i = 0; i < size; i++) {
319                 final ClientTransactionItem item = mTransactionItems.get(i);
320                 if (item.isActivityLifecycleItem()) {
321                     setLifecycleStateRequest((ActivityLifecycleItem) item);
322                 } else {
323                     addCallback(item);
324                 }
325             }
326         } else {
327             // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
328             // Only read mLifecycleStateRequest and mActivityCallbacks when mTransactionItems is
329             // null
330             mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(),
331                     ActivityLifecycleItem.class);
332             setActivityTokenIfNotSet(mLifecycleStateRequest);
333             final boolean readActivityCallbacks = in.readBoolean();
334             if (readActivityCallbacks) {
335                 mActivityCallbacks = new ArrayList<>();
336                 in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(),
337                         ClientTransactionItem.class);
338                 final int size = mActivityCallbacks.size();
339                 for (int i = 0; mActivityToken == null && i < size; i++) {
340                     final ClientTransactionItem item = mActivityCallbacks.get(i);
341                     setActivityTokenIfNotSet(item);
342                 }
343             }
344         }
345     }
346 
347     public static final @NonNull Creator<ClientTransaction> CREATOR = new Creator<>() {
348         public ClientTransaction createFromParcel(@NonNull Parcel in) {
349             return new ClientTransaction(in);
350         }
351 
352         public ClientTransaction[] newArray(int size) {
353             return new ClientTransaction[size];
354         }
355     };
356 
357     @Override
describeContents()358     public int describeContents() {
359         return 0;
360     }
361 
362     @Override
equals(@ullable Object o)363     public boolean equals(@Nullable Object o) {
364         if (this == o) {
365             return true;
366         }
367         if (o == null || getClass() != o.getClass()) {
368             return false;
369         }
370         final ClientTransaction other = (ClientTransaction) o;
371         return Objects.equals(mTransactionItems, other.mTransactionItems)
372                 && Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
373                 && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest)
374                 && mClient == other.mClient
375                 && Objects.equals(mActivityToken, other.mActivityToken);
376     }
377 
378     @Override
hashCode()379     public int hashCode() {
380         int result = 17;
381         result = 31 * result + Objects.hashCode(mTransactionItems);
382         result = 31 * result + Objects.hashCode(mActivityCallbacks);
383         result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
384         result = 31 * result + Objects.hashCode(mClient);
385         result = 31 * result + Objects.hashCode(mActivityToken);
386         return result;
387     }
388 
389     @Override
toString()390     public String toString() {
391         final StringBuilder sb = new StringBuilder();
392         sb.append("ClientTransaction{");
393         if (mTransactionItems != null) {
394             // #addTransactionItem
395             sb.append("\n  transactionItems=[");
396             final int size = mTransactionItems.size();
397             for (int i = 0; i < size; i++) {
398                 sb.append("\n    ").append(mTransactionItems.get(i));
399             }
400             sb.append("\n  ]");
401         } else {
402             // #addCallback
403             sb.append("\n  callbacks=[");
404             final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
405             for (int i = 0; i < size; i++) {
406                 sb.append("\n    ").append(mActivityCallbacks.get(i));
407             }
408             sb.append("\n  ]");
409             // #setLifecycleStateRequest
410             sb.append("\n  stateRequest=").append(mLifecycleStateRequest);
411         }
412         sb.append("\n}");
413         return sb.toString();
414     }
415 
416     /** Dump transaction items callback items and final lifecycle state request. */
dump(@onNull String prefix, @NonNull PrintWriter pw, @NonNull ClientTransactionHandler transactionHandler)417     void dump(@NonNull String prefix, @NonNull PrintWriter pw,
418             @NonNull ClientTransactionHandler transactionHandler) {
419         pw.append(prefix).println("ClientTransaction{");
420         if (mTransactionItems != null) {
421             pw.append(prefix).print("  transactionItems=[");
422             final String itemPrefix = prefix + "    ";
423             final int size = mTransactionItems.size();
424             if (size > 0) {
425                 pw.println();
426                 for (int i = 0; i < size; i++) {
427                     mTransactionItems.get(i).dump(itemPrefix, pw, transactionHandler);
428                 }
429                 pw.append(prefix).println("  ]");
430             } else {
431                 pw.println("]");
432             }
433             pw.append(prefix).println("}");
434             return;
435         }
436         pw.append(prefix).print("  callbacks=[");
437         final String itemPrefix = prefix + "    ";
438         final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
439         if (size > 0) {
440             pw.println();
441             for (int i = 0; i < size; i++) {
442                 mActivityCallbacks.get(i).dump(itemPrefix, pw, transactionHandler);
443             }
444             pw.append(prefix).println("  ]");
445         } else {
446             pw.println("]");
447         }
448 
449         pw.append(prefix).println("  stateRequest=");
450         if (mLifecycleStateRequest != null) {
451             mLifecycleStateRequest.dump(itemPrefix, pw, transactionHandler);
452         } else {
453             pw.append(itemPrefix).println("null");
454         }
455         pw.append(prefix).println("}");
456     }
457 }
458