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 android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
20 import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY;
21 import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
22 import static android.app.servertransaction.ActivityLifecycleItem.ON_RESTART;
23 import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
24 import static android.app.servertransaction.ActivityLifecycleItem.ON_START;
25 import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
26 import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
27 import static android.app.servertransaction.TransactionExecutorHelper.getShortActivityName;
28 import static android.app.servertransaction.TransactionExecutorHelper.getStateName;
29 import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState;
30 import static android.app.servertransaction.TransactionExecutorHelper.tId;
31 import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
32 
33 import android.app.ActivityThread.ActivityClientRecord;
34 import android.app.ClientTransactionHandler;
35 import android.os.IBinder;
36 import android.util.IntArray;
37 import android.util.Slog;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 
41 import java.util.List;
42 import java.util.Map;
43 
44 /**
45  * Class that manages transaction execution in the correct order.
46  * @hide
47  */
48 public class TransactionExecutor {
49 
50     private static final boolean DEBUG_RESOLVER = false;
51     private static final String TAG = "TransactionExecutor";
52 
53     private ClientTransactionHandler mTransactionHandler;
54     private PendingTransactionActions mPendingActions = new PendingTransactionActions();
55     private TransactionExecutorHelper mHelper = new TransactionExecutorHelper();
56 
57     /** Initialize an instance with transaction handler, that will execute all requested actions. */
TransactionExecutor(ClientTransactionHandler clientTransactionHandler)58     public TransactionExecutor(ClientTransactionHandler clientTransactionHandler) {
59         mTransactionHandler = clientTransactionHandler;
60     }
61 
62     /**
63      * Resolve transaction.
64      * First all callbacks will be executed in the order they appear in the list. If a callback
65      * requires a certain pre- or post-execution state, the client will be transitioned accordingly.
66      * Then the client will cycle to the final lifecycle state if provided. Otherwise, it will
67      * either remain in the initial state, or last state needed by a callback.
68      */
execute(ClientTransaction transaction)69     public void execute(ClientTransaction transaction) {
70         if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Start resolving transaction");
71 
72         final IBinder token = transaction.getActivityToken();
73         if (token != null) {
74             final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed =
75                     mTransactionHandler.getActivitiesToBeDestroyed();
76             final ClientTransactionItem destroyItem = activitiesToBeDestroyed.get(token);
77             if (destroyItem != null) {
78                 if (transaction.getLifecycleStateRequest() == destroyItem) {
79                     // It is going to execute the transaction that will destroy activity with the
80                     // token, so the corresponding to-be-destroyed record can be removed.
81                     activitiesToBeDestroyed.remove(token);
82                 }
83                 if (mTransactionHandler.getActivityClient(token) == null) {
84                     // The activity has not been created but has been requested to destroy, so all
85                     // transactions for the token are just like being cancelled.
86                     Slog.w(TAG, tId(transaction) + "Skip pre-destroyed transaction:\n"
87                             + transactionToString(transaction, mTransactionHandler));
88                     return;
89                 }
90             }
91         }
92 
93         if (DEBUG_RESOLVER) Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
94 
95         executeCallbacks(transaction);
96 
97         executeLifecycleState(transaction);
98         mPendingActions.clear();
99         if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
100     }
101 
102     /** Cycle through all states requested by callbacks and execute them at proper times. */
103     @VisibleForTesting
executeCallbacks(ClientTransaction transaction)104     public void executeCallbacks(ClientTransaction transaction) {
105         final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
106         if (callbacks == null || callbacks.isEmpty()) {
107             // No callbacks to execute, return early.
108             return;
109         }
110         if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callbacks in transaction");
111 
112         final IBinder token = transaction.getActivityToken();
113         ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
114 
115         // In case when post-execution state of the last callback matches the final state requested
116         // for the activity in this transaction, we won't do the last transition here and do it when
117         // moving to final state instead (because it may contain additional parameters from server).
118         final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest();
119         final int finalState = finalStateRequest != null ? finalStateRequest.getTargetState()
120                 : UNDEFINED;
121         // Index of the last callback that requests some post-execution state.
122         final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);
123 
124         final int size = callbacks.size();
125         for (int i = 0; i < size; ++i) {
126             final ClientTransactionItem item = callbacks.get(i);
127             if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
128             final int postExecutionState = item.getPostExecutionState();
129             final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
130                     item.getPostExecutionState());
131             if (closestPreExecutionState != UNDEFINED) {
132                 cycleToPath(r, closestPreExecutionState, transaction);
133             }
134 
135             item.execute(mTransactionHandler, token, mPendingActions);
136             item.postExecute(mTransactionHandler, token, mPendingActions);
137             if (r == null) {
138                 // Launch activity request will create an activity record.
139                 r = mTransactionHandler.getActivityClient(token);
140             }
141 
142             if (postExecutionState != UNDEFINED && r != null) {
143                 // Skip the very last transition and perform it by explicit state request instead.
144                 final boolean shouldExcludeLastTransition =
145                         i == lastCallbackRequestingState && finalState == postExecutionState;
146                 cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction);
147             }
148         }
149     }
150 
151     /** Transition to the final state if requested by the transaction. */
executeLifecycleState(ClientTransaction transaction)152     private void executeLifecycleState(ClientTransaction transaction) {
153         final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
154         if (lifecycleItem == null) {
155             // No lifecycle request, return early.
156             return;
157         }
158 
159         final IBinder token = transaction.getActivityToken();
160         final ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
161         if (DEBUG_RESOLVER) {
162             Slog.d(TAG, tId(transaction) + "Resolving lifecycle state: "
163                     + lifecycleItem + " for activity: "
164                     + getShortActivityName(token, mTransactionHandler));
165         }
166 
167         if (r == null) {
168             // Ignore requests for non-existent client records for now.
169             return;
170         }
171 
172         // Cycle to the state right before the final requested state.
173         cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */, transaction);
174 
175         // Execute the final transition with proper parameters.
176         lifecycleItem.execute(mTransactionHandler, token, mPendingActions);
177         lifecycleItem.postExecute(mTransactionHandler, token, mPendingActions);
178     }
179 
180     /** Transition the client between states. */
181     @VisibleForTesting
cycleToPath(ActivityClientRecord r, int finish, ClientTransaction transaction)182     public void cycleToPath(ActivityClientRecord r, int finish, ClientTransaction transaction) {
183         cycleToPath(r, finish, false /* excludeLastState */, transaction);
184     }
185 
186     /**
187      * Transition the client between states with an option not to perform the last hop in the
188      * sequence. This is used when resolving lifecycle state request, when the last transition must
189      * be performed with some specific parameters.
190      */
cycleToPath(ActivityClientRecord r, int finish, boolean excludeLastState, ClientTransaction transaction)191     private void cycleToPath(ActivityClientRecord r, int finish, boolean excludeLastState,
192             ClientTransaction transaction) {
193         final int start = r.getLifecycleState();
194         if (DEBUG_RESOLVER) {
195             Slog.d(TAG, tId(transaction) + "Cycle activity: "
196                     + getShortActivityName(r.token, mTransactionHandler)
197                     + " from: " + getStateName(start) + " to: " + getStateName(finish)
198                     + " excludeLastState: " + excludeLastState);
199         }
200         final IntArray path = mHelper.getLifecyclePath(start, finish, excludeLastState);
201         performLifecycleSequence(r, path, transaction);
202     }
203 
204     /** Transition the client through previously initialized state sequence. */
performLifecycleSequence(ActivityClientRecord r, IntArray path, ClientTransaction transaction)205     private void performLifecycleSequence(ActivityClientRecord r, IntArray path,
206             ClientTransaction transaction) {
207         final int size = path.size();
208         for (int i = 0, state; i < size; i++) {
209             state = path.get(i);
210             if (DEBUG_RESOLVER) {
211                 Slog.d(TAG, tId(transaction) + "Transitioning activity: "
212                         + getShortActivityName(r.token, mTransactionHandler)
213                         + " to state: " + getStateName(state));
214             }
215             switch (state) {
216                 case ON_CREATE:
217                     mTransactionHandler.handleLaunchActivity(r, mPendingActions,
218                             null /* customIntent */);
219                     break;
220                 case ON_START:
221                     mTransactionHandler.handleStartActivity(r, mPendingActions);
222                     break;
223                 case ON_RESUME:
224                     mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */,
225                             r.isForward, "LIFECYCLER_RESUME_ACTIVITY");
226                     break;
227                 case ON_PAUSE:
228                     mTransactionHandler.handlePauseActivity(r.token, false /* finished */,
229                             false /* userLeaving */, 0 /* configChanges */, mPendingActions,
230                             "LIFECYCLER_PAUSE_ACTIVITY");
231                     break;
232                 case ON_STOP:
233                     mTransactionHandler.handleStopActivity(r.token, false /* show */,
234                             0 /* configChanges */, mPendingActions, false /* finalStateRequest */,
235                             "LIFECYCLER_STOP_ACTIVITY");
236                     break;
237                 case ON_DESTROY:
238                     mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */,
239                             0 /* configChanges */, false /* getNonConfigInstance */,
240                             "performLifecycleSequence. cycling to:" + path.get(size - 1));
241                     break;
242                 case ON_RESTART:
243                     mTransactionHandler.performRestartActivity(r.token, false /* start */);
244                     break;
245                 default:
246                     throw new IllegalArgumentException("Unexpected lifecycle state: " + state);
247             }
248         }
249     }
250 }
251