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.app.ActivityThread.ActivityClientRecord; 30 import android.util.IntArray; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 34 import java.util.List; 35 36 /** 37 * Helper class for {@link TransactionExecutor} that contains utils for lifecycle path resolution. 38 * @hide 39 */ 40 public 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 getLifecyclePath(int start, int finish, boolean excludeLastState)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 getClosestPreExecutionState(ActivityClientRecord r, int postExecutionState)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 getClosestOfStates(ActivityClientRecord r, int[] finalStates)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. */ getLifecycleRequestForCurrentState(ActivityClientRecord r)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 */ pathInvolvesDestruction(IntArray lifecycleSequence)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 */ lastCallbackRequestingState(ClientTransaction transaction)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