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