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 static java.util.Objects.requireNonNull; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.ClientTransactionHandler; 26 import android.app.IApplicationThread; 27 import android.compat.annotation.UnsupportedAppUsage; 28 import android.os.Build; 29 import android.os.IBinder; 30 import android.os.Parcel; 31 import android.os.Parcelable; 32 import android.os.RemoteException; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 36 import java.io.PrintWriter; 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Objects; 40 41 /** 42 * A container that holds a sequence of messages, which may be sent to a client. 43 * This includes a list of callbacks and a final lifecycle state. 44 * 45 * @see com.android.server.wm.ClientLifecycleManager 46 * @see ClientTransactionItem 47 * @see ActivityLifecycleItem 48 * @hide 49 */ 50 public class ClientTransaction implements Parcelable { 51 52 /** 53 * List of transaction items that should be executed in order. Including both 54 * {@link ActivityLifecycleItem} and other {@link ClientTransactionItem}. 55 */ 56 @NonNull 57 private final List<ClientTransactionItem> mTransactionItems = new ArrayList<>(); 58 59 /** @deprecated use {@link #getTransactionItems} instead. */ 60 @Nullable 61 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, 62 trackingBug = 324203798, 63 publicAlternatives = "Use {@code #getTransactionItems()}") 64 @Deprecated 65 private List<ClientTransactionItem> mActivityCallbacks; 66 67 /** 68 * Final lifecycle state in which the client activity should be after the transaction is 69 * executed. 70 */ 71 // TODO(b/324203798): cleanup after remove UnsupportedAppUsage 72 @Nullable 73 private ActivityLifecycleItem mLifecycleStateRequest; 74 75 /** Only kept for unsupportedAppUsage {@link #getActivityToken()}. Must not be used. */ 76 // TODO(b/324203798): cleanup after remove UnsupportedAppUsage 77 @Nullable 78 private IBinder mActivityToken; 79 80 /** 81 * The target client. 82 * <p> 83 * This field is null only if the object is: 84 * - Read from a Parcel on the client side. 85 * - Constructed for testing purposes. 86 * <p> 87 * When created directly on the server, this field represents the server's connection to the 88 * target client's application thread. It is omitted during parceling and not sent to the 89 * client. On the client side, this field becomes unnecessary. 90 */ 91 @Nullable 92 private final IApplicationThread mClient; 93 94 @VisibleForTesting ClientTransaction()95 public ClientTransaction() { 96 mClient = null; 97 } 98 ClientTransaction(@onNull IApplicationThread client)99 public ClientTransaction(@NonNull IApplicationThread client) { 100 mClient = requireNonNull(client); 101 } 102 103 /** 104 * Gets the target client associated with this transaction. 105 * <p> 106 * This method is intended for server-side use only. Calling it from the client side 107 * will always return {@code null}. 108 * 109 * @return the {@link IApplicationThread} representing the target client, or {@code null} if 110 * called from the client side. 111 * @see #mClient 112 */ getClient()113 public IApplicationThread getClient() { 114 return mClient; 115 } 116 117 /** 118 * Adds a message to the end of the sequence of transaction items. 119 * @param item A single message that can contain a client activity/window request/callback. 120 */ addTransactionItem(@onNull ClientTransactionItem item)121 public void addTransactionItem(@NonNull ClientTransactionItem item) { 122 mTransactionItems.add(item); 123 124 // TODO(b/324203798): cleanup after remove UnsupportedAppUsage 125 // Populate even if mTransactionItems is set to support the UnsupportedAppUsage. 126 if (item.isActivityLifecycleItem()) { 127 setLifecycleStateRequest((ActivityLifecycleItem) item); 128 } else { 129 addCallback(item); 130 } 131 } 132 133 /** 134 * Gets the list of client window requests/callbacks. 135 */ 136 @NonNull getTransactionItems()137 public List<ClientTransactionItem> getTransactionItems() { 138 return mTransactionItems; 139 } 140 141 /** 142 * Adds a message to the end of the sequence of callbacks. 143 * @param activityCallback A single message that can contain a lifecycle request/callback. 144 * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead. 145 */ 146 // TODO(b/324203798): cleanup after remove UnsupportedAppUsage 147 @Deprecated addCallback(@onNull ClientTransactionItem activityCallback)148 private void addCallback(@NonNull ClientTransactionItem activityCallback) { 149 if (mActivityCallbacks == null) { 150 mActivityCallbacks = new ArrayList<>(); 151 } 152 mActivityCallbacks.add(activityCallback); 153 setActivityTokenIfNotSet(activityCallback); 154 } 155 156 /** @deprecated use {@link #getTransactionItems()} instead. */ 157 @VisibleForTesting 158 @Nullable 159 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, 160 trackingBug = 324203798, 161 publicAlternatives = "Use {@code #getTransactionItems()}") 162 @Deprecated getCallbacks()163 public List<ClientTransactionItem> getCallbacks() { 164 return mActivityCallbacks; 165 } 166 167 /** 168 * A transaction can contain {@link ClientTransactionItem} of different activities, 169 * this must not be used. For any unsupported app usages, please be aware that this is set to 170 * the activity of the first item in {@link #getTransactionItems()}. 171 * 172 * @deprecated use {@link ClientTransactionItem#getActivityToken()} instead. 173 */ 174 @VisibleForTesting 175 @Nullable 176 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, 177 trackingBug = 324203798, 178 publicAlternatives = "Use {@code android.app.servertransaction" 179 + ".ClientTransactionItem#getActivityToken()}") 180 @Deprecated getActivityToken()181 public IBinder getActivityToken() { 182 return mActivityToken; 183 } 184 185 /** @deprecated use {@link #getTransactionItems()} instead. */ 186 @VisibleForTesting(visibility = PACKAGE) 187 @Nullable 188 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, 189 trackingBug = 324203798, 190 publicAlternatives = "Use {@code #getTransactionItems()}") 191 @Deprecated getLifecycleStateRequest()192 public ActivityLifecycleItem getLifecycleStateRequest() { 193 return mLifecycleStateRequest; 194 } 195 196 /** 197 * Sets the lifecycle state in which the client should be after executing the transaction. 198 * @param stateRequest A lifecycle request initialized with right parameters. 199 * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead. 200 */ 201 // TODO(b/324203798): cleanup after remove UnsupportedAppUsage 202 @Deprecated setLifecycleStateRequest(@onNull ActivityLifecycleItem stateRequest)203 private void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) { 204 if (mLifecycleStateRequest != null) { 205 return; 206 } 207 mLifecycleStateRequest = stateRequest; 208 setActivityTokenIfNotSet(stateRequest); 209 } 210 211 // TODO(b/324203798): cleanup after remove UnsupportedAppUsage setActivityTokenIfNotSet(@ullable ClientTransactionItem item)212 private void setActivityTokenIfNotSet(@Nullable ClientTransactionItem item) { 213 if (mActivityToken == null && item != null) { 214 mActivityToken = item.getActivityToken(); 215 } 216 } 217 218 /** 219 * Do what needs to be done while the transaction is being scheduled on the client side. 220 * @param clientTransactionHandler Handler on the client side that will executed all operations 221 * requested by transaction items. 222 */ preExecute(@onNull ClientTransactionHandler clientTransactionHandler)223 public void preExecute(@NonNull ClientTransactionHandler clientTransactionHandler) { 224 final int size = mTransactionItems.size(); 225 for (int i = 0; i < size; ++i) { 226 mTransactionItems.get(i).preExecute(clientTransactionHandler); 227 } 228 } 229 230 /** 231 * Schedule the transaction after it was initialized. It will be send to client and all its 232 * individual parts will be applied in the following sequence: 233 * 1. The client calls {@link #preExecute(ClientTransactionHandler)}, which triggers all work 234 * that needs to be done before actually scheduling the transaction for callbacks and 235 * lifecycle state request. 236 * 2. The transaction message is scheduled. 237 * 3. The client calls {@link TransactionExecutor#execute(ClientTransaction)}, which executes 238 * all callbacks and necessary lifecycle transitions. 239 * 240 * @return {@link RemoteException} if the transaction failed. 241 */ 242 @Nullable schedule()243 public RemoteException schedule() { 244 try { 245 mClient.scheduleTransaction(this); 246 return null; 247 } catch (RemoteException e) { 248 return e; 249 } 250 } 251 252 // Parcelable implementation 253 254 /** Writes to Parcel. */ 255 @SuppressWarnings("AndroidFrameworkEfficientParcelable") // Item class is not final. 256 @Override writeToParcel(@onNull Parcel dest, int flags)257 public void writeToParcel(@NonNull Parcel dest, int flags) { 258 dest.writeParcelableList(mTransactionItems, flags); 259 } 260 261 /** Reads from Parcel. */ ClientTransaction(@onNull Parcel in)262 private ClientTransaction(@NonNull Parcel in) { 263 mClient = null; // This field is unnecessary on the client side. 264 in.readParcelableList(mTransactionItems, getClass().getClassLoader(), 265 ClientTransactionItem.class); 266 267 // TODO(b/324203798): cleanup after remove UnsupportedAppUsage 268 // Populate mLifecycleStateRequest and mActivityCallbacks from mTransactionItems so 269 // that they have the same reference when there is UnsupportedAppUsage to those fields. 270 final int size = mTransactionItems.size(); 271 for (int i = 0; i < size; i++) { 272 final ClientTransactionItem item = mTransactionItems.get(i); 273 if (item.isActivityLifecycleItem()) { 274 setLifecycleStateRequest((ActivityLifecycleItem) item); 275 } else { 276 addCallback(item); 277 } 278 } 279 } 280 281 public static final @NonNull Creator<ClientTransaction> CREATOR = new Creator<>() { 282 public ClientTransaction createFromParcel(@NonNull Parcel in) { 283 return new ClientTransaction(in); 284 } 285 286 public ClientTransaction[] newArray(int size) { 287 return new ClientTransaction[size]; 288 } 289 }; 290 291 @Override describeContents()292 public int describeContents() { 293 return 0; 294 } 295 296 @Override equals(@ullable Object o)297 public boolean equals(@Nullable Object o) { 298 if (this == o) { 299 return true; 300 } 301 if (o == null || getClass() != o.getClass()) { 302 return false; 303 } 304 final ClientTransaction other = (ClientTransaction) o; 305 return Objects.equals(mTransactionItems, other.mTransactionItems) 306 && Objects.equals(mActivityCallbacks, other.mActivityCallbacks) 307 && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest) 308 && mClient == other.mClient 309 && Objects.equals(mActivityToken, other.mActivityToken); 310 } 311 312 @Override hashCode()313 public int hashCode() { 314 int result = 17; 315 result = 31 * result + Objects.hashCode(mTransactionItems); 316 result = 31 * result + Objects.hashCode(mActivityCallbacks); 317 result = 31 * result + Objects.hashCode(mLifecycleStateRequest); 318 result = 31 * result + Objects.hashCode(mClient); 319 result = 31 * result + Objects.hashCode(mActivityToken); 320 return result; 321 } 322 323 @Override toString()324 public String toString() { 325 final StringBuilder sb = new StringBuilder(); 326 sb.append("ClientTransaction{"); 327 sb.append("\n transactionItems=["); 328 final int size = mTransactionItems.size(); 329 for (int i = 0; i < size; i++) { 330 sb.append("\n ").append(mTransactionItems.get(i)); 331 } 332 sb.append("\n ]"); 333 sb.append("\n}"); 334 return sb.toString(); 335 } 336 337 /** Dump transaction items callback items and final lifecycle state request. */ dump(@onNull String prefix, @NonNull PrintWriter pw, @NonNull ClientTransactionHandler transactionHandler)338 void dump(@NonNull String prefix, @NonNull PrintWriter pw, 339 @NonNull ClientTransactionHandler transactionHandler) { 340 pw.append(prefix).println("ClientTransaction{"); 341 pw.append(prefix).print(" transactionItems=["); 342 final String itemPrefix = prefix + " "; 343 final int size = mTransactionItems.size(); 344 if (size > 0) { 345 pw.println(); 346 for (int i = 0; i < size; i++) { 347 mTransactionItems.get(i).dump(itemPrefix, pw, transactionHandler); 348 } 349 pw.append(prefix).println(" ]"); 350 } else { 351 pw.println("]"); 352 } 353 pw.append(prefix).println("}"); 354 } 355 } 356