• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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.PRE_ON_CREATE;
27 import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
28 
29 import android.annotation.NonNull;
30 import android.app.Activity;
31 import android.app.ActivityThread.ActivityClientRecord;
32 import android.app.ClientTransactionHandler;
33 import android.os.IBinder;
34 import android.util.IntArray;
35 import android.util.Log;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 
39 import java.io.PrintWriter;
40 import java.io.StringWriter;
41 import java.util.List;
42 
43 /**
44  * Helper class for {@link TransactionExecutor} that contains utils for lifecycle path resolution.
45  *
46  * @hide
47  */
48 public class TransactionExecutorHelper {
49     private static final String TAG = TransactionExecutorHelper.class.getSimpleName();
50     // A penalty applied to path with destruction when looking for the shortest one.
51     private static final int DESTRUCTION_PENALTY = 10;
52 
53     private static final int[] ON_RESUME_PRE_EXCUTION_STATES = new int[] { ON_START, ON_PAUSE };
54 
55     // Temp holder for lifecycle path.
56     // No direct transition between two states should take more than one complete cycle of 6 states.
57     @ActivityLifecycleItem.LifecycleState
58     private final IntArray mLifecycleSequence = new IntArray(6 /* initialCapacity */);
59 
60     /**
61      * Calculate the path through main lifecycle states for an activity and fill
62      * @link #mLifecycleSequence} with values starting with the state that follows the initial
63      * state.
64      * <p>NOTE: The returned value is used internally in this class and is not a copy. It's contents
65      * may change after calling other methods of this class.</p>
66      */
67     @VisibleForTesting
getLifecyclePath(int start, int finish, boolean excludeLastState)68     public IntArray getLifecyclePath(int start, int finish, boolean excludeLastState) {
69         if (start == UNDEFINED || finish == UNDEFINED) {
70             throw new IllegalArgumentException("Can't resolve lifecycle path for undefined state");
71         }
72         if (start == ON_RESTART || finish == ON_RESTART) {
73             throw new IllegalArgumentException(
74                     "Can't start or finish in intermittent RESTART state");
75         }
76         if (finish == PRE_ON_CREATE && start != finish) {
77             throw new IllegalArgumentException("Can only start in pre-onCreate state");
78         }
79 
80         mLifecycleSequence.clear();
81         if (finish >= start) {
82             if (start == ON_START && finish == ON_STOP) {
83                 // A case when we from start to stop state soon, we don't need to go
84                 // through the resumed, paused state.
85                 mLifecycleSequence.add(ON_STOP);
86             } else {
87                 // just go there
88                 for (int i = start + 1; i <= finish; i++) {
89                     mLifecycleSequence.add(i);
90                 }
91             }
92         } else { // finish < start, can't just cycle down
93             if (start == ON_PAUSE && finish == ON_RESUME) {
94                 // Special case when we can just directly go to resumed state.
95                 mLifecycleSequence.add(ON_RESUME);
96             } else if (start <= ON_STOP && finish >= ON_START) {
97                 // Restart and go to required state.
98 
99                 // Go to stopped state first.
100                 for (int i = start + 1; i <= ON_STOP; i++) {
101                     mLifecycleSequence.add(i);
102                 }
103                 // Restart
104                 mLifecycleSequence.add(ON_RESTART);
105                 // Go to required state
106                 for (int i = ON_START; i <= finish; i++) {
107                     mLifecycleSequence.add(i);
108                 }
109             } else {
110                 // Relaunch and go to required state
111 
112                 // Go to destroyed state first.
113                 for (int i = start + 1; i <= ON_DESTROY; i++) {
114                     mLifecycleSequence.add(i);
115                 }
116                 // Go to required state
117                 for (int i = ON_CREATE; i <= finish; i++) {
118                     mLifecycleSequence.add(i);
119                 }
120             }
121         }
122 
123         // Remove last transition in case we want to perform it with some specific params.
124         if (excludeLastState && mLifecycleSequence.size() != 0) {
125             mLifecycleSequence.remove(mLifecycleSequence.size() - 1);
126         }
127 
128         return mLifecycleSequence;
129     }
130 
131     /**
132      * Pick a state that goes before provided post-execution state and would require the least
133      * lifecycle transitions to get to.
134      * It will also make sure to try avoiding a path with activity destruction and relaunch if
135      * possible.
136      * @param r An activity that we're trying to resolve the transition for.
137      * @param postExecutionState Post execution state to compute for.
138      * @return One of states that precede the provided post-execution state, or
139      *         {@link ActivityLifecycleItem#UNDEFINED} if there is not path.
140      */
141     @VisibleForTesting
getClosestPreExecutionState(ActivityClientRecord r, int postExecutionState)142     public int getClosestPreExecutionState(ActivityClientRecord r,
143             int postExecutionState) {
144         switch (postExecutionState) {
145             case UNDEFINED:
146                 return UNDEFINED;
147             case ON_RESUME:
148                 return getClosestOfStates(r, ON_RESUME_PRE_EXCUTION_STATES);
149             default:
150                 throw new UnsupportedOperationException("Pre-execution states for state: "
151                         + postExecutionState + " is not supported.");
152         }
153     }
154 
155     /**
156      * Pick a state that would require the least lifecycle transitions to get to.
157      * It will also make sure to try avoiding a path with activity destruction and relaunch if
158      * possible.
159      * @param r An activity that we're trying to resolve the transition for.
160      * @param finalStates An array of valid final states.
161      * @return One of the provided final states, or {@link ActivityLifecycleItem#UNDEFINED} if none
162      *         were provided or there is not path.
163      */
164     @VisibleForTesting
getClosestOfStates(ActivityClientRecord r, int[] finalStates)165     public int getClosestOfStates(ActivityClientRecord r, int[] finalStates) {
166         if (finalStates == null || finalStates.length == 0) {
167             return UNDEFINED;
168         }
169         if (r == null) {
170             // Early return because the ActivityClientRecord hasn't been created or cannot be found.
171             Log.w(TAG, "ActivityClientRecord was null");
172             return UNDEFINED;
173         }
174 
175         final int currentState = r.getLifecycleState();
176         int closestState = UNDEFINED;
177         for (int i = 0, shortestPath = Integer.MAX_VALUE, pathLength; i < finalStates.length; i++) {
178             getLifecyclePath(currentState, finalStates[i], false /* excludeLastState */);
179             pathLength = mLifecycleSequence.size();
180             if (pathInvolvesDestruction(mLifecycleSequence)) {
181                 pathLength += DESTRUCTION_PENALTY;
182             }
183             if (shortestPath > pathLength) {
184                 shortestPath = pathLength;
185                 closestState = finalStates[i];
186             }
187         }
188         return closestState;
189     }
190 
191     /** Get the lifecycle state request to match the current state in the end of a transaction. */
getLifecycleRequestForCurrentState(ActivityClientRecord r)192     public static ActivityLifecycleItem getLifecycleRequestForCurrentState(ActivityClientRecord r) {
193         final int prevState = r.getLifecycleState();
194         final ActivityLifecycleItem lifecycleItem;
195         switch (prevState) {
196             // TODO(lifecycler): Extend to support all possible states.
197             case ON_START:
198                 // Fall through to return the PAUSE item to ensure the activity is properly
199                 // resumed while relaunching.
200             case ON_PAUSE:
201                 lifecycleItem = new PauseActivityItem(r.token);
202                 break;
203             case ON_STOP:
204                 lifecycleItem = new StopActivityItem(r.token);
205                 break;
206             default:
207                 lifecycleItem = new ResumeActivityItem(r.token, false /* isForward */,
208                         false /* shouldSendCompatFakeFocus */);
209                 break;
210         }
211 
212         return lifecycleItem;
213     }
214 
215     /**
216      * Check if there is a destruction involved in the path. We want to avoid a lifecycle sequence
217      * that involves destruction and recreation if there is another path.
218      */
pathInvolvesDestruction(IntArray lifecycleSequence)219     private static boolean pathInvolvesDestruction(IntArray lifecycleSequence) {
220         final int size = lifecycleSequence.size();
221         for (int i = 0; i < size; i++) {
222             if (lifecycleSequence.get(i) == ON_DESTROY) {
223                 return true;
224             }
225         }
226         return false;
227     }
228 
229     /**
230      * Returns the index of the last callback between the start index and last index that requests
231      * the state for the given activity token in which that activity will be after execution.
232      * If there is a group of callbacks in the end that requests the same specific state or doesn't
233      * request any - we will find the first one from such group.
234      *
235      * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any
236      * specific state. If there is a sequence
237      *   Configuration - ActivityResult - Configuration - ActivityResult
238      * index 1 will be returned, because ActivityResult request on position 1 will be the last
239      * request that moves activity to the RESUMED state where it will eventually end.
240      */
lastCallbackRequestingStateIndex(@onNull List<ClientTransactionItem> items, int startIndex, int lastIndex, @NonNull IBinder activityToken)241     private static int lastCallbackRequestingStateIndex(@NonNull List<ClientTransactionItem> items,
242             int startIndex, int lastIndex, @NonNull IBinder activityToken) {
243         // Go from the back of the list to front, look for the request closes to the beginning that
244         // requests the state in which activity will end after all callbacks are executed.
245         int lastRequestedState = UNDEFINED;
246         int lastRequestingCallback = -1;
247         for (int i = lastIndex; i >= startIndex; i--) {
248             final ClientTransactionItem item = items.get(i);
249             final int postExecutionState = item.getPostExecutionState();
250             if (postExecutionState != UNDEFINED && activityToken.equals(item.getActivityToken())) {
251                 // Found a callback that requests some post-execution state for the given activity.
252                 if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) {
253                     // It's either a first-from-end callback that requests state or it requests
254                     // the same state as the last one. In both cases, we will use it as the new
255                     // candidate.
256                     lastRequestedState = postExecutionState;
257                     lastRequestingCallback = i;
258                 } else {
259                     break;
260                 }
261             }
262         }
263 
264         return lastRequestingCallback;
265     }
266 
267     /**
268      * For the transaction item at {@code currentIndex}, if it is requesting post execution state,
269      * whether or not to exclude the last state. This only returns {@code true} when there is a
270      * following explicit {@link ActivityLifecycleItem} requesting the same state for the same
271      * activity, so that last state will be covered by the following {@link ActivityLifecycleItem}.
272      */
shouldExcludeLastLifecycleState(@onNull List<ClientTransactionItem> items, int currentIndex)273     static boolean shouldExcludeLastLifecycleState(@NonNull List<ClientTransactionItem> items,
274             int currentIndex) {
275         final ClientTransactionItem item = items.get(currentIndex);
276         final IBinder activityToken = item.getActivityToken();
277         final int postExecutionState = item.getPostExecutionState();
278         if (activityToken == null || postExecutionState == UNDEFINED) {
279             // Not a transaction item requesting post execution state.
280             return false;
281         }
282         final int nextLifecycleItemIndex = findNextLifecycleItemIndex(items, currentIndex + 1,
283                 activityToken);
284         if (nextLifecycleItemIndex == -1) {
285             // No following ActivityLifecycleItem for this activity token.
286             return false;
287         }
288         final ActivityLifecycleItem lifecycleItem =
289                 (ActivityLifecycleItem) items.get(nextLifecycleItemIndex);
290         if (postExecutionState != lifecycleItem.getTargetState()) {
291             // The explicit ActivityLifecycleItem is not requesting the same state.
292             return false;
293         }
294         // Only exclude for the first non-lifecycle item that requests the same specific state.
295         return currentIndex == lastCallbackRequestingStateIndex(items, currentIndex,
296                 nextLifecycleItemIndex - 1, activityToken);
297     }
298 
299     /**
300      * Finds the index of the next {@link ActivityLifecycleItem} for the given activity token.
301      */
findNextLifecycleItemIndex(@onNull List<ClientTransactionItem> items, int startIndex, @NonNull IBinder activityToken)302     private static int findNextLifecycleItemIndex(@NonNull List<ClientTransactionItem> items,
303             int startIndex, @NonNull IBinder activityToken) {
304         final int size = items.size();
305         for (int i = startIndex; i < size; i++) {
306             final ClientTransactionItem item = items.get(i);
307             if (item.isActivityLifecycleItem() && item.getActivityToken().equals(activityToken)) {
308                 return i;
309             }
310         }
311         return -1;
312     }
313 
314     /** Dump transaction to string. */
transactionToString(@onNull ClientTransaction transaction, @NonNull ClientTransactionHandler transactionHandler)315     static String transactionToString(@NonNull ClientTransaction transaction,
316             @NonNull ClientTransactionHandler transactionHandler) {
317         final StringWriter stringWriter = new StringWriter();
318         final PrintWriter pw = new PrintWriter(stringWriter);
319         final String prefix = tId(transaction);
320         transaction.dump(prefix, pw, transactionHandler);
321         return stringWriter.toString();
322     }
323 
324     /** @return A string in format "tId:<transaction hashcode> ". */
tId(ClientTransaction transaction)325     static String tId(ClientTransaction transaction) {
326         return "tId:" + transaction.hashCode() + " ";
327     }
328 
329     /** Get activity string name for provided token. */
getActivityName(IBinder token, ClientTransactionHandler transactionHandler)330     static String getActivityName(IBinder token, ClientTransactionHandler transactionHandler) {
331         final Activity activity = getActivityForToken(token, transactionHandler);
332         if (activity != null) {
333             return activity.getComponentName().getClassName();
334         }
335         return "Not found for token: " + token;
336     }
337 
338     /** Get short activity class name for provided token. */
getShortActivityName(IBinder token, ClientTransactionHandler transactionHandler)339     static String getShortActivityName(IBinder token, ClientTransactionHandler transactionHandler) {
340         final Activity activity = getActivityForToken(token, transactionHandler);
341         if (activity != null) {
342             return activity.getComponentName().getShortClassName();
343         }
344         return "Not found for token: " + token;
345     }
346 
getActivityForToken(IBinder token, ClientTransactionHandler transactionHandler)347     private static Activity getActivityForToken(IBinder token,
348             ClientTransactionHandler transactionHandler) {
349         if (token == null) {
350             return null;
351         }
352         return transactionHandler.getActivity(token);
353     }
354 
355     /** Get lifecycle state string name. */
getStateName(int state)356     static String getStateName(int state) {
357         switch (state) {
358             case UNDEFINED:
359                 return "UNDEFINED";
360             case PRE_ON_CREATE:
361                 return "PRE_ON_CREATE";
362             case ON_CREATE:
363                 return "ON_CREATE";
364             case ON_START:
365                 return "ON_START";
366             case ON_RESUME:
367                 return "ON_RESUME";
368             case ON_PAUSE:
369                 return "ON_PAUSE";
370             case ON_STOP:
371                 return "ON_STOP";
372             case ON_DESTROY:
373                 return "ON_DESTROY";
374             case ON_RESTART:
375                 return "ON_RESTART";
376             default:
377                 throw new IllegalArgumentException("Unexpected lifecycle state: " + state);
378         }
379     }
380 }
381