TransactionExecutorHelper.java revision 77d73605979a19e377c97df1c82a2dfd8e3a71cd
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
17package android.app.servertransaction;
18
19import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
20import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY;
21import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
22import static android.app.servertransaction.ActivityLifecycleItem.ON_RESTART;
23import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
24import static android.app.servertransaction.ActivityLifecycleItem.ON_START;
25import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
26import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
27import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
28
29import android.app.ActivityThread.ActivityClientRecord;
30import android.util.IntArray;
31
32import com.android.internal.annotations.VisibleForTesting;
33
34import java.util.List;
35
36/**
37 * Helper class for {@link TransactionExecutor} that contains utils for lifecycle path resolution.
38 * @hide
39 */
40public class TransactionExecutorHelper {
41    // A penalty applied to path with destruction when looking for the shortest one.
42    private static final int DESTRUCTION_PENALTY = 10;
43
44    private static final int[] ON_RESUME_PRE_EXCUTION_STATES = new int[] { ON_START, ON_PAUSE };
45
46    // Temp holder for lifecycle path.
47    // No direct transition between two states should take more than one complete cycle of 6 states.
48    @ActivityLifecycleItem.LifecycleState
49    private IntArray mLifecycleSequence = new IntArray(6);
50
51    /**
52     * Calculate the path through main lifecycle states for an activity and fill
53     * @link #mLifecycleSequence} with values starting with the state that follows the initial
54     * state.
55     * <p>NOTE: The returned value is used internally in this class and is not a copy. It's contents
56     * may change after calling other methods of this class.</p>
57     */
58    @VisibleForTesting
59    public IntArray getLifecyclePath(int start, int finish, boolean excludeLastState) {
60        if (start == UNDEFINED || finish == UNDEFINED) {
61            throw new IllegalArgumentException("Can't resolve lifecycle path for undefined state");
62        }
63        if (start == ON_RESTART || finish == ON_RESTART) {
64            throw new IllegalArgumentException(
65                    "Can't start or finish in intermittent RESTART state");
66        }
67        if (finish == PRE_ON_CREATE && start != finish) {
68            throw new IllegalArgumentException("Can only start in pre-onCreate state");
69        }
70
71        mLifecycleSequence.clear();
72        if (finish >= start) {
73            // just go there
74            for (int i = start + 1; i <= finish; i++) {
75                mLifecycleSequence.add(i);
76            }
77        } else { // finish < start, can't just cycle down
78            if (start == ON_PAUSE && finish == ON_RESUME) {
79                // Special case when we can just directly go to resumed state.
80                mLifecycleSequence.add(ON_RESUME);
81            } else if (start <= ON_STOP && finish >= ON_START) {
82                // Restart and go to required state.
83
84                // Go to stopped state first.
85                for (int i = start + 1; i <= ON_STOP; i++) {
86                    mLifecycleSequence.add(i);
87                }
88                // Restart
89                mLifecycleSequence.add(ON_RESTART);
90                // Go to required state
91                for (int i = ON_START; i <= finish; i++) {
92                    mLifecycleSequence.add(i);
93                }
94            } else {
95                // Relaunch and go to required state
96
97                // Go to destroyed state first.
98                for (int i = start + 1; i <= ON_DESTROY; i++) {
99                    mLifecycleSequence.add(i);
100                }
101                // Go to required state
102                for (int i = ON_CREATE; i <= finish; i++) {
103                    mLifecycleSequence.add(i);
104                }
105            }
106        }
107
108        // Remove last transition in case we want to perform it with some specific params.
109        if (excludeLastState && mLifecycleSequence.size() != 0) {
110            mLifecycleSequence.remove(mLifecycleSequence.size() - 1);
111        }
112
113        return mLifecycleSequence;
114    }
115
116    /**
117     * Pick a state that goes before provided post-execution state and would require the least
118     * lifecycle transitions to get to.
119     * It will also make sure to try avoiding a path with activity destruction and relaunch if
120     * possible.
121     * @param r An activity that we're trying to resolve the transition for.
122     * @param postExecutionState Post execution state to compute for.
123     * @return One of states that precede the provided post-execution state, or
124     *         {@link ActivityLifecycleItem#UNDEFINED} if there is not path.
125     */
126    @VisibleForTesting
127    public int getClosestPreExecutionState(ActivityClientRecord r,
128            int postExecutionState) {
129        switch (postExecutionState) {
130            case UNDEFINED:
131                return UNDEFINED;
132            case ON_RESUME:
133                return getClosestOfStates(r, ON_RESUME_PRE_EXCUTION_STATES);
134            default:
135                throw new UnsupportedOperationException("Pre-execution states for state: "
136                        + postExecutionState + " is not supported.");
137        }
138    }
139
140    /**
141     * Pick a state that would require the least lifecycle transitions to get to.
142     * It will also make sure to try avoiding a path with activity destruction and relaunch if
143     * possible.
144     * @param r An activity that we're trying to resolve the transition for.
145     * @param finalStates An array of valid final states.
146     * @return One of the provided final states, or {@link ActivityLifecycleItem#UNDEFINED} if none
147     *         were provided or there is not path.
148     */
149    @VisibleForTesting
150    public int getClosestOfStates(ActivityClientRecord r, int[] finalStates) {
151        if (finalStates == null || finalStates.length == 0) {
152            return UNDEFINED;
153        }
154
155        final int currentState = r.getLifecycleState();
156        int closestState = UNDEFINED;
157        for (int i = 0, shortestPath = Integer.MAX_VALUE, pathLength; i < finalStates.length; i++) {
158            getLifecyclePath(currentState, finalStates[i], false /* excludeLastState */);
159            pathLength = mLifecycleSequence.size();
160            if (pathInvolvesDestruction(mLifecycleSequence)) {
161                pathLength += DESTRUCTION_PENALTY;
162            }
163            if (shortestPath > pathLength) {
164                shortestPath = pathLength;
165                closestState = finalStates[i];
166            }
167        }
168        return closestState;
169    }
170
171    /** Get the lifecycle state request to match the current state in the end of a transaction. */
172    public static ActivityLifecycleItem getLifecycleRequestForCurrentState(ActivityClientRecord r) {
173        final int prevState = r.getLifecycleState();
174        final ActivityLifecycleItem lifecycleItem;
175        switch (prevState) {
176            // TODO(lifecycler): Extend to support all possible states.
177            case ON_PAUSE:
178                lifecycleItem = PauseActivityItem.obtain();
179                break;
180            case ON_STOP:
181                lifecycleItem = StopActivityItem.obtain(r.isVisibleFromServer(),
182                        0 /* configChanges */);
183                break;
184            default:
185                lifecycleItem = ResumeActivityItem.obtain(false /* isForward */);
186                break;
187        }
188
189        return lifecycleItem;
190    }
191
192    /**
193     * Check if there is a destruction involved in the path. We want to avoid a lifecycle sequence
194     * that involves destruction and recreation if there is another path.
195     */
196    private static boolean pathInvolvesDestruction(IntArray lifecycleSequence) {
197        final int size = lifecycleSequence.size();
198        for (int i = 0; i < size; i++) {
199            if (lifecycleSequence.get(i) == ON_DESTROY) {
200                return true;
201            }
202        }
203        return false;
204    }
205
206    /**
207     * Return the index of the last callback that requests the state in which activity will be after
208     * execution. If there is a group of callbacks in the end that requests the same specific state
209     * or doesn't request any - we will find the first one from such group.
210     *
211     * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any
212     * specific state. If there is a sequence
213     *   Configuration - ActivityResult - Configuration - ActivityResult
214     * index 1 will be returned, because ActivityResult request on position 1 will be the last
215     * request that moves activity to the RESUMED state where it will eventually end.
216     */
217    static int lastCallbackRequestingState(ClientTransaction transaction) {
218        final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
219        if (callbacks == null || callbacks.size() == 0) {
220            return -1;
221        }
222
223        // Go from the back of the list to front, look for the request closes to the beginning that
224        // requests the state in which activity will end after all callbacks are executed.
225        int lastRequestedState = UNDEFINED;
226        int lastRequestingCallback = -1;
227        for (int i = callbacks.size() - 1; i >= 0; i--) {
228            final ClientTransactionItem callback = callbacks.get(i);
229            final int postExecutionState = callback.getPostExecutionState();
230            if (postExecutionState != UNDEFINED) {
231                // Found a callback that requests some post-execution state.
232                if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) {
233                    // It's either a first-from-end callback that requests state or it requests
234                    // the same state as the last one. In both cases, we will use it as the new
235                    // candidate.
236                    lastRequestedState = postExecutionState;
237                    lastRequestingCallback = i;
238                } else {
239                    break;
240                }
241            }
242        }
243
244        return lastRequestingCallback;
245    }
246}
247