| /* |
| * Copyright 2017 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.UNDEFINED; |
| import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState; |
| |
| import android.app.ActivityThread.ActivityClientRecord; |
| import android.app.ClientTransactionHandler; |
| import android.os.IBinder; |
| import android.util.IntArray; |
| import android.util.Slog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.List; |
| |
| /** |
| * Class that manages transaction execution in the correct order. |
| * @hide |
| */ |
| public class TransactionExecutor { |
| |
| private static final boolean DEBUG_RESOLVER = false; |
| private static final String TAG = "TransactionExecutor"; |
| |
| private ClientTransactionHandler mTransactionHandler; |
| private PendingTransactionActions mPendingActions = new PendingTransactionActions(); |
| private TransactionExecutorHelper mHelper = new TransactionExecutorHelper(); |
| |
| /** Initialize an instance with transaction handler, that will execute all requested actions. */ |
| public TransactionExecutor(ClientTransactionHandler clientTransactionHandler) { |
| mTransactionHandler = clientTransactionHandler; |
| } |
| |
| /** |
| * Resolve transaction. |
| * First all callbacks will be executed in the order they appear in the list. If a callback |
| * requires a certain pre- or post-execution state, the client will be transitioned accordingly. |
| * Then the client will cycle to the final lifecycle state if provided. Otherwise, it will |
| * either remain in the initial state, or last state needed by a callback. |
| */ |
| public void execute(ClientTransaction transaction) { |
| final IBinder token = transaction.getActivityToken(); |
| log("Start resolving transaction for client: " + mTransactionHandler + ", token: " + token); |
| |
| executeCallbacks(transaction); |
| |
| executeLifecycleState(transaction); |
| mPendingActions.clear(); |
| log("End resolving transaction"); |
| } |
| |
| /** Cycle through all states requested by callbacks and execute them at proper times. */ |
| @VisibleForTesting |
| public void executeCallbacks(ClientTransaction transaction) { |
| final List<ClientTransactionItem> callbacks = transaction.getCallbacks(); |
| if (callbacks == null) { |
| // No callbacks to execute, return early. |
| return; |
| } |
| log("Resolving callbacks"); |
| |
| final IBinder token = transaction.getActivityToken(); |
| ActivityClientRecord r = mTransactionHandler.getActivityClient(token); |
| |
| // In case when post-execution state of the last callback matches the final state requested |
| // for the activity in this transaction, we won't do the last transition here and do it when |
| // moving to final state instead (because it may contain additional parameters from server). |
| final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest(); |
| final int finalState = finalStateRequest != null ? finalStateRequest.getTargetState() |
| : UNDEFINED; |
| // Index of the last callback that requests some post-execution state. |
| final int lastCallbackRequestingState = lastCallbackRequestingState(transaction); |
| |
| final int size = callbacks.size(); |
| for (int i = 0; i < size; ++i) { |
| final ClientTransactionItem item = callbacks.get(i); |
| log("Resolving callback: " + item); |
| final int postExecutionState = item.getPostExecutionState(); |
| final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r, |
| item.getPostExecutionState()); |
| if (closestPreExecutionState != UNDEFINED) { |
| cycleToPath(r, closestPreExecutionState); |
| } |
| |
| item.execute(mTransactionHandler, token, mPendingActions); |
| item.postExecute(mTransactionHandler, token, mPendingActions); |
| if (r == null) { |
| // Launch activity request will create an activity record. |
| r = mTransactionHandler.getActivityClient(token); |
| } |
| |
| if (postExecutionState != UNDEFINED && r != null) { |
| // Skip the very last transition and perform it by explicit state request instead. |
| final boolean shouldExcludeLastTransition = |
| i == lastCallbackRequestingState && finalState == postExecutionState; |
| cycleToPath(r, postExecutionState, shouldExcludeLastTransition); |
| } |
| } |
| } |
| |
| /** Transition to the final state if requested by the transaction. */ |
| private void executeLifecycleState(ClientTransaction transaction) { |
| final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest(); |
| if (lifecycleItem == null) { |
| // No lifecycle request, return early. |
| return; |
| } |
| log("Resolving lifecycle state: " + lifecycleItem); |
| |
| final IBinder token = transaction.getActivityToken(); |
| final ActivityClientRecord r = mTransactionHandler.getActivityClient(token); |
| |
| // TODO(b/71506345): Remove once root cause is found. |
| if (r == null) { |
| final StringWriter stringWriter = new StringWriter(); |
| final PrintWriter pw = new PrintWriter(stringWriter); |
| final String prefix = " "; |
| |
| pw.println("Lifecycle transaction does not have valid ActivityClientRecord."); |
| pw.println("Transaction:"); |
| transaction.dump(pw, prefix); |
| pw.println("Executor:"); |
| dump(pw, prefix); |
| |
| Slog.w(TAG, stringWriter.toString()); |
| |
| // Ignore requests for non-existent client records for now. |
| return; |
| } |
| |
| // Cycle to the state right before the final requested state. |
| cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */); |
| |
| // Execute the final transition with proper parameters. |
| lifecycleItem.execute(mTransactionHandler, token, mPendingActions); |
| lifecycleItem.postExecute(mTransactionHandler, token, mPendingActions); |
| } |
| |
| /** Transition the client between states. */ |
| @VisibleForTesting |
| public void cycleToPath(ActivityClientRecord r, int finish) { |
| cycleToPath(r, finish, false /* excludeLastState */); |
| } |
| |
| /** |
| * Transition the client between states with an option not to perform the last hop in the |
| * sequence. This is used when resolving lifecycle state request, when the last transition must |
| * be performed with some specific parameters. |
| */ |
| private void cycleToPath(ActivityClientRecord r, int finish, |
| boolean excludeLastState) { |
| final int start = r.getLifecycleState(); |
| log("Cycle from: " + start + " to: " + finish + " excludeLastState:" + excludeLastState); |
| final IntArray path = mHelper.getLifecyclePath(start, finish, excludeLastState); |
| performLifecycleSequence(r, path); |
| } |
| |
| /** Transition the client through previously initialized state sequence. */ |
| private void performLifecycleSequence(ActivityClientRecord r, IntArray path) { |
| final int size = path.size(); |
| for (int i = 0, state; i < size; i++) { |
| state = path.get(i); |
| log("Transitioning to state: " + state); |
| switch (state) { |
| case ON_CREATE: |
| mTransactionHandler.handleLaunchActivity(r, mPendingActions); |
| break; |
| case ON_START: |
| mTransactionHandler.handleStartActivity(r, mPendingActions); |
| break; |
| case ON_RESUME: |
| mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */, |
| r.isForward, "LIFECYCLER_RESUME_ACTIVITY"); |
| break; |
| case ON_PAUSE: |
| mTransactionHandler.handlePauseActivity(r.token, false /* finished */, |
| false /* userLeaving */, 0 /* configChanges */, mPendingActions, |
| "LIFECYCLER_PAUSE_ACTIVITY"); |
| break; |
| case ON_STOP: |
| mTransactionHandler.handleStopActivity(r.token, false /* show */, |
| 0 /* configChanges */, mPendingActions, false /* finalStateRequest */, |
| "LIFECYCLER_STOP_ACTIVITY"); |
| break; |
| case ON_DESTROY: |
| mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */, |
| 0 /* configChanges */, false /* getNonConfigInstance */, |
| "performLifecycleSequence. cycling to:" + path.get(size - 1)); |
| break; |
| case ON_RESTART: |
| mTransactionHandler.performRestartActivity(r.token, false /* start */); |
| break; |
| default: |
| throw new IllegalArgumentException("Unexpected lifecycle state: " + state); |
| } |
| } |
| } |
| |
| private static void log(String message) { |
| if (DEBUG_RESOLVER) Slog.d(TAG, message); |
| } |
| |
| private void dump(PrintWriter pw, String prefix) { |
| pw.println(prefix + "mTransactionHandler:"); |
| mTransactionHandler.dump(pw, prefix + " "); |
| } |
| } |