Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9b210737 authored by Evan Rosky's avatar Evan Rosky
Browse files

Create a type for tracking chains of Actions requested of WM

Introduces a new type ActionChain which, when linked together,
represent a sequence of related actions WM performed as part
of a Transition.

When performing a larger "logical" operation (eg. launching
an activity), it is made up of several asynchronous actions (eg.
ask current activity to pause; on paused, start new activity and
wait for drawn, ask Shell if more work needs to be done, etc.).

Because these are async, we need to track these sets of actions
independent of actions from another "logical" operation otherwise
WM is unable to distinguish them. Today this DOESN'T happen and
thus we currently occasionally combine multiple logical operations
into one Transition which both confuses Transition clients (shell)
and can contribute to latency (unrelated operations waiting for
eachother).

This type is the primary component used to solve this problem. A
new "Action" will started at every IPC entry-point into WM. This
will be linked to a prior Action which caused this one to exist.
These linked actions form a chain of actions tracking the causes
and effects of each step in executing a Transition. This object
is then plumbed from the entry-point down (into synchronous
logic), so that any resulting changes to WM can be associated
with the that action chain.

In this CL, only the type was added and then used to wrap the
direct transition entry-points. Functionally, nothing changes
as the "default" behavior when starting an action chain is
equivalent to the current behavior (use collecting transition
if available, otherwise use nothing).

Bug: 325114242
Test: New class added but no functional changes, so existing tests
Flag: com.android.window.flags.transit_tracker_plumbing
Change-Id: I7a7e0730437fc1a15e44a55a84fea33aa72f1301
parent 0893341d
Loading
Loading
Loading
Loading
+239 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.server.wm;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Slog;

import com.android.window.flags.Flags;

/**
 * Represents a chain of WM actions where each action is "caused by" the prior action (except the
 * first one of course). A whole chain is associated with one Transition (in fact, the purpose
 * of this object is to communicate, to all callees, which transition they are part of).
 *
 * A single action is defined as "one logical thing requested of WM". This usually corresponds to
 * each ingress-point into the process. For example, when starting an activity:
 *   * the first action is to pause the current/top activity.
 *       At this point, control leaves the process while the activity pauses.
 *   * Then WM receives completePause (a new ingress). This is a new action that gets linked
 *       to the prior action. This action involves resuming the next activity, at which point,
 *       control leaves the process again.
 *   * Eventually, when everything is done, we will have formed a chain of actions.
 *
 * We don't technically need to hold onto each prior action in the chain once a new action has
 * been linked to the same transition; however, keeping the whole chain enables improved
 * debugging and the ability to detect anomalies.
 */
public class ActionChain {
    private static final String TAG = "TransitionChain";

    /**
     * Normal link type. This means the action was expected and is properly linked to the
     * current chain.
     */
    static final int TYPE_NORMAL = 0;

    /**
     * This is the "default" link. It means we haven't done anything to properly track this case
     * so it may or may not be correct. It represents the behavior as if there was no tracking.
     *
     * Any type that has "default" behavior uses the global "collecting transition" if it exists,
     * otherwise it doesn't use any transition.
     */
    static final int TYPE_DEFAULT = 1;

    /**
     * This means the action was performed via a legacy code-path. These should be removed
     * eventually. This will have the "default" behavior.
     */
    static final int TYPE_LEGACY = 2;

    /** This is for a test. */
    static final int TYPE_TEST = 3;

    /** This is finishing a transition. Collection isn't supported during this. */
    static final int TYPE_FINISH = 4;

    /**
     * Something unexpected happened so this action was started to recover from the unexpected
     * state. This means that a "real" chain-link couldn't be determined. For now, the behavior of
     * this is the same as "default".
     */
    static final int TYPE_FAILSAFE = 5;

    /**
     * Types of chain links (ie. how is this action associated with the chain it is linked to).
     * @hide
     */
    @IntDef(prefix = { "TYPE_" }, value = {
            TYPE_NORMAL,
            TYPE_DEFAULT,
            TYPE_LEGACY,
            TYPE_TEST,
            TYPE_FINISH,
            TYPE_FAILSAFE
    })
    public @interface LinkType {}

    /** Identifies the entry-point of this action. */
    @NonNull
    final String mSource;

    /** Reference to ATMS. TEMPORARY! ONLY USE THIS WHEN tracker_plumbing flag is DISABLED! */
    @Nullable
    ActivityTaskManagerService mTmpAtm;

    /** The transition that this chain's changes belong to. */
    @Nullable
    Transition mTransition;

    /** The previous action in the chain. */
    @Nullable
    ActionChain mPrevious = null;

    /** Classification of how this action is connected to the chain. */
    @LinkType int mType = TYPE_NORMAL;

    /** When this Action started. */
    long mCreateTimeMs;

    private ActionChain(String source, @LinkType int type, Transition transit) {
        mSource = source;
        mCreateTimeMs = System.currentTimeMillis();
        mType = type;
        mTransition = transit;
        if (mTransition != null) {
            mTransition.recordChain(this);
        }
    }

    private Transition getTransition() {
        if (!Flags.transitTrackerPlumbing()) {
            return mTmpAtm.getTransitionController().getCollectingTransition();
        }
        return mTransition;
    }

    boolean isFinishing() {
        return mType == TYPE_FINISH;
    }

    /**
     * Some common checks to determine (and report) whether this chain has a collecting transition.
     */
    private boolean expectCollecting() {
        if (getTransition() == null) {
            Slog.e(TAG, "Can't collect into a chain with no transition");
            return false;
        }
        if (isFinishing()) {
            Slog.e(TAG, "Trying to collect into a finished transition");
            return false;
        }
        if (mTransition.mController.getCollectingTransition() != mTransition) {
            Slog.e(TAG, "Mismatch between current collecting ("
                    + mTransition.mController.getCollectingTransition() + ") and chain ("
                    + mTransition + ")");
            return false;
        }
        return true;
    }

    /**
     * Helper to collect a container into the associated transition. This will automatically do
     * nothing if the chain isn't associated with a collecting transition.
     */
    void collect(@NonNull WindowContainer wc) {
        if (!wc.mTransitionController.isShellTransitionsEnabled()) return;
        if (!expectCollecting()) return;
        getTransition().collect(wc);
    }

    /**
     * An interface for creating and tracking action chains.
     */
    static class Tracker {
        private final ActivityTaskManagerService mAtm;

        Tracker(ActivityTaskManagerService atm) {
            mAtm = atm;
        }

        private ActionChain makeChain(String source, @LinkType int type, Transition transit) {
            final ActionChain out = new ActionChain(source, type, transit);
            if (!Flags.transitTrackerPlumbing()) {
                out.mTmpAtm = mAtm;
            }
            return out;
        }

        private ActionChain makeChain(String source, @LinkType int type) {
            return makeChain(source, type,
                    mAtm.getTransitionController().getCollectingTransition());
        }

        /**
         * Starts tracking a normal action.
         * @see #TYPE_NORMAL
         */
        @NonNull
        ActionChain start(String source, Transition transit) {
            return makeChain(source, TYPE_NORMAL, transit);
        }

        /** @see #TYPE_DEFAULT */
        @NonNull
        ActionChain startDefault(String source) {
            return makeChain(source, TYPE_DEFAULT);
        }

        /**
         * Starts tracking an action that finishes a transition.
         * @see #TYPE_NORMAL
         */
        @NonNull
        ActionChain startFinish(String source, Transition finishTransit) {
            return makeChain(source, TYPE_FINISH, finishTransit);
        }

        /** @see #TYPE_LEGACY */
        @NonNull
        ActionChain startLegacy(String source) {
            return makeChain(source, TYPE_LEGACY, null);
        }

        /** @see #TYPE_FAILSAFE */
        @NonNull
        ActionChain startFailsafe(String source) {
            return makeChain(source, TYPE_FAILSAFE);
        }
    }

    /** Helpers for usage in tests. */
    @NonNull
    static ActionChain test() {
        return new ActionChain("test", TYPE_TEST, null /* transition */);
    }

    @NonNull
    static ActionChain testFinish(Transition toFinish) {
        return new ActionChain("test", TYPE_FINISH, toFinish);
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -795,6 +795,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
    WindowOrganizerController mWindowOrganizerController;
    TaskOrganizerController mTaskOrganizerController;
    TaskFragmentOrganizerController mTaskFragmentOrganizerController;
    ActionChain.Tracker mChainTracker;

    @Nullable
    private BackgroundActivityStartCallback mBackgroundActivityStartCallback;
@@ -869,6 +870,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
        mInternal = new LocalService();
        GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED);
        mWindowOrganizerController = new WindowOrganizerController(this);
        mChainTracker = new ActionChain.Tracker(this);
        mTaskOrganizerController = mWindowOrganizerController.mTaskOrganizerController;
        mTaskFragmentOrganizerController =
                mWindowOrganizerController.mTaskFragmentOrganizerController;
+15 −3
Original line number Diff line number Diff line
@@ -181,7 +181,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
    final @TransitionType int mType;
    private int mSyncId = -1;
    private @TransitionFlags int mFlags;
    private final TransitionController mController;
    final TransitionController mController;
    private final BLASTSyncEngine mSyncEngine;
    private final Token mToken;

@@ -329,6 +329,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
     */
    ArrayList<ActivityRecord> mConfigAtEndActivities = null;

    /** The current head of the chain of actions related to this transition. */
    ActionChain mChainHead = null;

    @VisibleForTesting
    Transition(@TransitionType int type, @TransitionFlags int flags,
            TransitionController controller, BLASTSyncEngine syncEngine) {
@@ -1207,10 +1210,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
     * The transition has finished animating and is ready to finalize WM state. This should not
     * be called directly; use {@link TransitionController#finishTransition} instead.
     */
    void finishTransition() {
    void finishTransition(@NonNull ActionChain chain) {
        if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
            asyncTraceEnd(System.identityHashCode(this));
        }
        if (!chain.isFinishing()) {
            throw new IllegalStateException("Can't finish on a non-finishing transition "
                    + chain.mTransition);
        }
        mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos();
        mController.mLoggerHandler.post(mLogger::logOnFinish);
        mController.mTransitionTracer.logFinishedTransition(this);
@@ -2163,7 +2170,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        if (mFinishTransaction != null) {
            mFinishTransaction.apply();
        }
        mController.finishTransition(this);
        mController.finishTransition(mController.mAtm.mChainTracker.startFinish("clean-up", this));
    }

    private void cleanUpInternal() {
@@ -3379,6 +3386,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        return false;
    }

    void recordChain(@NonNull ActionChain chain) {
        chain.mPrevious = mChainHead;
        mChainHead = chain;
    }

    @VisibleForTesting
    static class ChangeInfo {
        private static final int FLAG_NONE = 0;
+8 −3
Original line number Diff line number Diff line
@@ -52,8 +52,8 @@ import android.window.WindowContainerTransaction;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.server.FgThread;
import com.android.window.flags.Flags;

@@ -921,7 +921,12 @@ class TransitionController {
    }

    /** @see Transition#finishTransition */
    void finishTransition(Transition record) {
    void finishTransition(@NonNull ActionChain chain) {
        if (!chain.isFinishing()) {
            throw new IllegalStateException("Can't finish on a non-finishing transition "
                    + chain.mTransition);
        }
        final Transition record = chain.mTransition;
        // It is usually a no-op but make sure that the metric consumer is removed.
        mTransitionMetricsReporter.reportAnimationStart(record.getToken(), 0 /* startTime */);
        // It is a no-op if the transition did not change the display.
@@ -937,7 +942,7 @@ class TransitionController {
            mTrackCount = 0;
        }
        updateRunningRemoteAnimation(record, false /* isPlaying */);
        record.finishTransition();
        record.finishTransition(chain);
        for (int i = mAnimatingExitWindows.size() - 1; i >= 0; i--) {
            final WindowState w = mAnimatingExitWindows.get(i);
            if (w.mAnimatingExit && w.mHasSurface && !w.inTransition()) {
+81 −71

File changed.

Preview size limit exceeded, changes collapsed.

Loading