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