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