| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.app.servertransaction; |
| |
| import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE; |
| import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY; |
| import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; |
| import static android.app.servertransaction.ActivityLifecycleItem.ON_RESTART; |
| import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; |
| import static android.app.servertransaction.ActivityLifecycleItem.ON_START; |
| import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; |
| import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE; |
| import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; |
| |
| import android.app.Activity; |
| import android.app.ActivityThread.ActivityClientRecord; |
| import android.app.ClientTransactionHandler; |
| import android.os.IBinder; |
| import android.util.IntArray; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.List; |
| |
| /** |
| * Helper class for {@link TransactionExecutor} that contains utils for lifecycle path resolution. |
| * @hide |
| */ |
| public class TransactionExecutorHelper { |
| // A penalty applied to path with destruction when looking for the shortest one. |
| private static final int DESTRUCTION_PENALTY = 10; |
| |
| private static final int[] ON_RESUME_PRE_EXCUTION_STATES = new int[] { ON_START, ON_PAUSE }; |
| |
| // Temp holder for lifecycle path. |
| // No direct transition between two states should take more than one complete cycle of 6 states. |
| @ActivityLifecycleItem.LifecycleState |
| private IntArray mLifecycleSequence = new IntArray(6); |
| |
| /** |
| * Calculate the path through main lifecycle states for an activity and fill |
| * @link #mLifecycleSequence} with values starting with the state that follows the initial |
| * state. |
| * <p>NOTE: The returned value is used internally in this class and is not a copy. It's contents |
| * may change after calling other methods of this class.</p> |
| */ |
| @VisibleForTesting |
| public IntArray getLifecyclePath(int start, int finish, boolean excludeLastState) { |
| if (start == UNDEFINED || finish == UNDEFINED) { |
| throw new IllegalArgumentException("Can't resolve lifecycle path for undefined state"); |
| } |
| if (start == ON_RESTART || finish == ON_RESTART) { |
| throw new IllegalArgumentException( |
| "Can't start or finish in intermittent RESTART state"); |
| } |
| if (finish == PRE_ON_CREATE && start != finish) { |
| throw new IllegalArgumentException("Can only start in pre-onCreate state"); |
| } |
| |
| mLifecycleSequence.clear(); |
| if (finish >= start) { |
| // just go there |
| for (int i = start + 1; i <= finish; i++) { |
| mLifecycleSequence.add(i); |
| } |
| } else { // finish < start, can't just cycle down |
| if (start == ON_PAUSE && finish == ON_RESUME) { |
| // Special case when we can just directly go to resumed state. |
| mLifecycleSequence.add(ON_RESUME); |
| } else if (start <= ON_STOP && finish >= ON_START) { |
| // Restart and go to required state. |
| |
| // Go to stopped state first. |
| for (int i = start + 1; i <= ON_STOP; i++) { |
| mLifecycleSequence.add(i); |
| } |
| // Restart |
| mLifecycleSequence.add(ON_RESTART); |
| // Go to required state |
| for (int i = ON_START; i <= finish; i++) { |
| mLifecycleSequence.add(i); |
| } |
| } else { |
| // Relaunch and go to required state |
| |
| // Go to destroyed state first. |
| for (int i = start + 1; i <= ON_DESTROY; i++) { |
| mLifecycleSequence.add(i); |
| } |
| // Go to required state |
| for (int i = ON_CREATE; i <= finish; i++) { |
| mLifecycleSequence.add(i); |
| } |
| } |
| } |
| |
| // Remove last transition in case we want to perform it with some specific params. |
| if (excludeLastState && mLifecycleSequence.size() != 0) { |
| mLifecycleSequence.remove(mLifecycleSequence.size() - 1); |
| } |
| |
| return mLifecycleSequence; |
| } |
| |
| /** |
| * Pick a state that goes before provided post-execution state and would require the least |
| * lifecycle transitions to get to. |
| * It will also make sure to try avoiding a path with activity destruction and relaunch if |
| * possible. |
| * @param r An activity that we're trying to resolve the transition for. |
| * @param postExecutionState Post execution state to compute for. |
| * @return One of states that precede the provided post-execution state, or |
| * {@link ActivityLifecycleItem#UNDEFINED} if there is not path. |
| */ |
| @VisibleForTesting |
| public int getClosestPreExecutionState(ActivityClientRecord r, |
| int postExecutionState) { |
| switch (postExecutionState) { |
| case UNDEFINED: |
| return UNDEFINED; |
| case ON_RESUME: |
| return getClosestOfStates(r, ON_RESUME_PRE_EXCUTION_STATES); |
| default: |
| throw new UnsupportedOperationException("Pre-execution states for state: " |
| + postExecutionState + " is not supported."); |
| } |
| } |
| |
| /** |
| * Pick a state that would require the least lifecycle transitions to get to. |
| * It will also make sure to try avoiding a path with activity destruction and relaunch if |
| * possible. |
| * @param r An activity that we're trying to resolve the transition for. |
| * @param finalStates An array of valid final states. |
| * @return One of the provided final states, or {@link ActivityLifecycleItem#UNDEFINED} if none |
| * were provided or there is not path. |
| */ |
| @VisibleForTesting |
| public int getClosestOfStates(ActivityClientRecord r, int[] finalStates) { |
| if (finalStates == null || finalStates.length == 0) { |
| return UNDEFINED; |
| } |
| |
| final int currentState = r.getLifecycleState(); |
| int closestState = UNDEFINED; |
| for (int i = 0, shortestPath = Integer.MAX_VALUE, pathLength; i < finalStates.length; i++) { |
| getLifecyclePath(currentState, finalStates[i], false /* excludeLastState */); |
| pathLength = mLifecycleSequence.size(); |
| if (pathInvolvesDestruction(mLifecycleSequence)) { |
| pathLength += DESTRUCTION_PENALTY; |
| } |
| if (shortestPath > pathLength) { |
| shortestPath = pathLength; |
| closestState = finalStates[i]; |
| } |
| } |
| return closestState; |
| } |
| |
| /** Get the lifecycle state request to match the current state in the end of a transaction. */ |
| public static ActivityLifecycleItem getLifecycleRequestForCurrentState(ActivityClientRecord r) { |
| final int prevState = r.getLifecycleState(); |
| final ActivityLifecycleItem lifecycleItem; |
| switch (prevState) { |
| // TODO(lifecycler): Extend to support all possible states. |
| case ON_PAUSE: |
| lifecycleItem = PauseActivityItem.obtain(); |
| break; |
| case ON_STOP: |
| lifecycleItem = StopActivityItem.obtain(0 /* configChanges */); |
| break; |
| default: |
| lifecycleItem = ResumeActivityItem.obtain(false /* isForward */); |
| break; |
| } |
| |
| return lifecycleItem; |
| } |
| |
| /** |
| * Check if there is a destruction involved in the path. We want to avoid a lifecycle sequence |
| * that involves destruction and recreation if there is another path. |
| */ |
| private static boolean pathInvolvesDestruction(IntArray lifecycleSequence) { |
| final int size = lifecycleSequence.size(); |
| for (int i = 0; i < size; i++) { |
| if (lifecycleSequence.get(i) == ON_DESTROY) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return the index of the last callback that requests the state in which activity will be after |
| * execution. If there is a group of callbacks in the end that requests the same specific state |
| * or doesn't request any - we will find the first one from such group. |
| * |
| * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any |
| * specific state. If there is a sequence |
| * Configuration - ActivityResult - Configuration - ActivityResult |
| * index 1 will be returned, because ActivityResult request on position 1 will be the last |
| * request that moves activity to the RESUMED state where it will eventually end. |
| */ |
| static int lastCallbackRequestingState(ClientTransaction transaction) { |
| final List<ClientTransactionItem> callbacks = transaction.getCallbacks(); |
| if (callbacks == null || callbacks.size() == 0) { |
| return -1; |
| } |
| |
| // Go from the back of the list to front, look for the request closes to the beginning that |
| // requests the state in which activity will end after all callbacks are executed. |
| int lastRequestedState = UNDEFINED; |
| int lastRequestingCallback = -1; |
| for (int i = callbacks.size() - 1; i >= 0; i--) { |
| final ClientTransactionItem callback = callbacks.get(i); |
| final int postExecutionState = callback.getPostExecutionState(); |
| if (postExecutionState != UNDEFINED) { |
| // Found a callback that requests some post-execution state. |
| if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) { |
| // It's either a first-from-end callback that requests state or it requests |
| // the same state as the last one. In both cases, we will use it as the new |
| // candidate. |
| lastRequestedState = postExecutionState; |
| lastRequestingCallback = i; |
| } else { |
| break; |
| } |
| } |
| } |
| |
| return lastRequestingCallback; |
| } |
| |
| /** Dump transaction to string. */ |
| static String transactionToString(ClientTransaction transaction, |
| ClientTransactionHandler transactionHandler) { |
| final StringWriter stringWriter = new StringWriter(); |
| final PrintWriter pw = new PrintWriter(stringWriter); |
| final String prefix = tId(transaction); |
| transaction.dump(prefix, pw); |
| pw.append(prefix + "Target activity: ") |
| .println(getActivityName(transaction.getActivityToken(), transactionHandler)); |
| return stringWriter.toString(); |
| } |
| |
| /** @return A string in format "tId:<transaction hashcode> ". */ |
| static String tId(ClientTransaction transaction) { |
| return "tId:" + transaction.hashCode() + " "; |
| } |
| |
| /** Get activity string name for provided token. */ |
| static String getActivityName(IBinder token, ClientTransactionHandler transactionHandler) { |
| final Activity activity = getActivityForToken(token, transactionHandler); |
| if (activity != null) { |
| return activity.getComponentName().getClassName(); |
| } |
| return "Not found for token: " + token; |
| } |
| |
| /** Get short activity class name for provided token. */ |
| static String getShortActivityName(IBinder token, ClientTransactionHandler transactionHandler) { |
| final Activity activity = getActivityForToken(token, transactionHandler); |
| if (activity != null) { |
| return activity.getComponentName().getShortClassName(); |
| } |
| return "Not found for token: " + token; |
| } |
| |
| private static Activity getActivityForToken(IBinder token, |
| ClientTransactionHandler transactionHandler) { |
| if (token == null) { |
| return null; |
| } |
| return transactionHandler.getActivity(token); |
| } |
| |
| /** Get lifecycle state string name. */ |
| static String getStateName(int state) { |
| switch (state) { |
| case UNDEFINED: |
| return "UNDEFINED"; |
| case PRE_ON_CREATE: |
| return "PRE_ON_CREATE"; |
| case ON_CREATE: |
| return "ON_CREATE"; |
| case ON_START: |
| return "ON_START"; |
| case ON_RESUME: |
| return "ON_RESUME"; |
| case ON_PAUSE: |
| return "ON_PAUSE"; |
| case ON_STOP: |
| return "ON_STOP"; |
| case ON_DESTROY: |
| return "ON_DESTROY"; |
| case ON_RESTART: |
| return "ON_RESTART"; |
| default: |
| throw new IllegalArgumentException("Unexpected lifecycle state: " + state); |
| } |
| } |
| } |