/*
 * Copyright (C) 2016 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 static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;

import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowContainerProto.CONFIGURATION_CONTAINER;
import static com.android.server.wm.WindowContainerProto.ORIENTATION;
import static com.android.server.wm.WindowContainerProto.SURFACE_ANIMATOR;
import static com.android.server.wm.WindowContainerProto.VISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.logWithStack;
import static com.android.server.wm.WindowManagerService.sHierarchicalAnimations;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;

import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
import android.os.Trace;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Pools;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Builder;
import android.view.SurfaceSession;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.protolog.common.ProtoLog;
import com.android.server.wm.SurfaceAnimator.Animatable;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;

import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * Defines common functionality for classes that can hold windows directly or through their
 * children in a hierarchy form.
 * The test class is {@link WindowContainerTests} which must be kept up-to-date and ran anytime
 * changes are made to this class.
 */
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
        implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
                   BLASTSyncEngine.TransactionReadyListener {

    private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM;

    /** Animation layer that happens above all animating {@link ActivityStack}s. */
    static final int ANIMATION_LAYER_STANDARD = 0;

    /** Animation layer that happens above all {@link ActivityStack}s. */
    static final int ANIMATION_LAYER_BOOSTED = 1;

    /**
     * Animation layer that is reserved for {@link WindowConfiguration#ACTIVITY_TYPE_HOME}
     * activities and all activities that are being controlled by the recents animation. This
     * layer is generally below all {@link ActivityStack}s.
     */
    static final int ANIMATION_LAYER_HOME = 2;

    @IntDef(prefix = { "ANIMATION_LAYER_" }, value = {
            ANIMATION_LAYER_STANDARD,
            ANIMATION_LAYER_BOOSTED,
            ANIMATION_LAYER_HOME,
    })
    @interface AnimationLayer {}

    static final int POSITION_TOP = Integer.MAX_VALUE;
    static final int POSITION_BOTTOM = Integer.MIN_VALUE;

    /**
     * The parent of this window container.
     * For removing or setting new parent {@link #setParent} should be used, because it also
     * performs configuration updates based on new parent's settings.
     */
    private WindowContainer<WindowContainer> mParent = null;

    // Set to true when we are performing a reparenting operation so we only send one
    // onParentChanged() notification.
    boolean mReparenting;

    // List of children for this window container. List is in z-order as the children appear on
    // screen with the top-most window container at the tail of the list.
    protected final WindowList<E> mChildren = new WindowList<E>();

    // The specified orientation for this window container.
    @ActivityInfo.ScreenOrientation
    protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED;

    /**
     * The window container which decides its orientation since the last time
     * {@link #getOrientation(int) was called.
     */
    protected WindowContainer mLastOrientationSource;

    private final Pools.SynchronizedPool<ForAllWindowsConsumerWrapper> mConsumerWrapperPool =
            new Pools.SynchronizedPool<>(3);

    // The display this window container is on.
    protected DisplayContent mDisplayContent;

    protected SurfaceControl mSurfaceControl;
    private int mLastLayer = 0;
    private SurfaceControl mLastRelativeToLayer = null;

    // TODO(b/132320879): Remove this from WindowContainers except DisplayContent.
    private final Transaction mPendingTransaction;

    /**
     * Windows that clients are waiting to have drawn.
     */
    final ArrayList<WindowState> mWaitingForDrawn = new ArrayList<>();

    /**
     * Applied as part of the animation pass in "prepareSurfaces".
     */
    protected final SurfaceAnimator mSurfaceAnimator;
    private boolean mAnyParentAnimating;

    final SurfaceFreezer mSurfaceFreezer;
    protected final WindowManagerService mWmService;

    /**
     * Sources which triggered a surface animation on this container. An animation target can be
     * promoted to higher level, for example, from a set of {@link ActivityRecord}s to
     * {@link ActivityStack}. In this case, {@link ActivityRecord}s are set on this variable while
     * the animation is running, and reset after finishing it.
     */
    private final ArraySet<WindowContainer> mSurfaceAnimationSources = new ArraySet<>();

    private final Point mTmpPos = new Point();
    protected final Point mLastSurfacePosition = new Point();

    /** Total number of elements in this subtree, including our own hierarchy element. */
    private int mTreeWeight = 1;

    /**
     * Indicates whether we are animating and have committed the transaction to reparent our
     * surface to the animation leash
     */
    private boolean mCommittedReparentToAnimationLeash;

    /** Interface for {@link #isAnimating} to check which cases for the container is animating. */
    public interface AnimationFlags {
        /**
         * A bit flag indicates that {@link #isAnimating} should also return {@code true}
         * even though the container is not yet animating, but the window container or its
         * relatives as specified by PARENTS or CHILDREN are part of an {@link AppTransition}
         * that is pending so an animation starts soon.
         */
        int TRANSITION = 1;

        /**
         * A bit flag indicates that {@link #isAnimating} should also check if one of the
         * ancestors of the container are animating in addition to the container itself.
         */
        int PARENTS = 2;

        /**
         * A bit flag indicates that {@link #isAnimating} should also check if one of the
         * descendants of the container are animating in addition to the container itself.
         */
        int CHILDREN = 4;
    }

    /**
     * Callback which is triggered while changing the parent, after setting up the surface but
     * before asking the parent to assign child layers.
     */
    interface PreAssignChildLayersCallback {
        void onPreAssignChildLayers();
    }

    /**
     * True if this an AppWindowToken and the activity which created this was launched with
     * ActivityOptions.setLaunchTaskBehind.
     *
     * TODO(b/142617871): We run a special animation when the activity was launched with that
     * flag, but it's not necessary anymore. Keep the window invisible until the task is explicitly
     * selected to suppress an animation, and remove this flag.
     */
    boolean mLaunchTaskBehind;

    /**
     * If we are running an animation, this determines the transition type. Must be one of
     * {@link AppTransition#TransitionFlags}.
     */
    int mTransit;

    /**
     * If we are running an animation, this determines the flags during this animation. Must be a
     * bitwise combination of AppTransition.TRANSIT_FLAG_* constants.
     */
    int mTransitFlags;

    /** Whether this container should be boosted at the top of all its siblings. */
    @VisibleForTesting boolean mNeedsZBoost;

    /** Layer used to constrain the animation to a container's stack bounds. */
    SurfaceControl mAnimationBoundsLayer;

    /** Whether this container needs to create mAnimationBoundsLayer for cropping animations. */
    boolean mNeedsAnimationBoundsLayer;

    /**
     * This gets used during some open/close transitions as well as during a change transition
     * where it represents the starting-state snapshot.
     */
    WindowContainerThumbnail mThumbnail;
    final Point mTmpPoint = new Point();
    protected final Rect mTmpRect = new Rect();
    final Rect mTmpPrevBounds = new Rect();

    private MagnificationSpec mLastMagnificationSpec;

    private boolean mIsFocusable = true;

    /**
     * Used as a unique, cross-process identifier for this Container. It also serves a minimal
     * interface to other processes.
     */
    RemoteToken mRemoteToken = null;

    BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine();
    SurfaceControl.Transaction mBLASTSyncTransaction;
    boolean mUsingBLASTSyncTransaction = false;
    BLASTSyncEngine.TransactionReadyListener mWaitingListener;
    int mWaitingSyncId;

    WindowContainer(WindowManagerService wms) {
        mWmService = wms;
        mPendingTransaction = wms.mTransactionFactory.get();
        mBLASTSyncTransaction = wms.mTransactionFactory.get();
        mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);
        mSurfaceFreezer = new SurfaceFreezer(this, wms);
    }

    @Override
    final protected WindowContainer getParent() {
        return mParent;
    }

    @Override
    protected int getChildCount() {
        return mChildren.size();
    }

    @Override
    protected E getChildAt(int index) {
        return mChildren.get(index);
    }

    @Override
    public void onConfigurationChanged(Configuration newParentConfig) {
        super.onConfigurationChanged(newParentConfig);
        updateSurfacePosition();
        scheduleAnimation();
    }

    void reparent(WindowContainer newParent, int position) {
        if (newParent == null) {
            throw new IllegalArgumentException("reparent: can't reparent to null " + this);
        }

        final WindowContainer oldParent = mParent;
        if (mParent == newParent) {
            throw new IllegalArgumentException("WC=" + this + " already child of " + mParent);
        }

        // The display object before reparenting as that might lead to old parent getting removed
        // from the display if it no longer has any child.
        final DisplayContent prevDc = oldParent.getDisplayContent();
        final DisplayContent dc = newParent.getDisplayContent();

        mReparenting = true;
        oldParent.removeChild(this);
        newParent.addChild(this, position);
        mReparenting = false;

        // Relayout display(s)
        dc.setLayoutNeeded();
        if (prevDc != dc) {
            onDisplayChanged(dc);
            prevDc.setLayoutNeeded();
        }
        getDisplayContent().layoutAndAssignWindowLayersIfNeeded();

        // Send onParentChanged notification here is we disabled sending it in setParent for
        // reparenting case.
        onParentChanged(newParent, oldParent);
    }

    final protected void setParent(WindowContainer<WindowContainer> parent) {
        final WindowContainer oldParent = mParent;
        mParent = parent;

        if (mParent != null) {
            mParent.onChildAdded(this);
        }
        if (!mReparenting) {
            if (mParent != null && mParent.mDisplayContent != null
                    && mDisplayContent != mParent.mDisplayContent) {
                onDisplayChanged(mParent.mDisplayContent);
            }
            onParentChanged(mParent, oldParent);
        }
    }

    /**
     * Callback that is triggered when @link WindowContainer#setParent(WindowContainer)} was called.
     * Supposed to be overridden and contain actions that should be executed after parent was set.
     */
    @Override
    void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
        onParentChanged(newParent, oldParent, null);
    }

    void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent,
            PreAssignChildLayersCallback callback) {
        super.onParentChanged(newParent, oldParent);
        if (mParent == null) {
            return;
        }

        if (mSurfaceControl == null) {
            // If we don't yet have a surface, but we now have a parent, we should
            // build a surface.
            createSurfaceControl(false /*force*/);
        } else {
            // If we have a surface but a new parent, we just need to perform a reparent. Go through
            // surface animator such that hierarchy is preserved when animating, i.e.
            // mSurfaceControl stays attached to the leash and we just reparent the leash to the
            // new parent.
            reparentSurfaceControl(getSyncTransaction(), mParent.mSurfaceControl);
        }

        if (callback != null) {
            callback.onPreAssignChildLayers();
        }

        // Either way we need to ask the parent to assign us a Z-order.
        mParent.assignChildLayers();
        scheduleAnimation();
    }

    void createSurfaceControl(boolean force) {
        setInitialSurfaceControlProperties(makeSurface());
    }

    void setInitialSurfaceControlProperties(SurfaceControl.Builder b) {
        setSurfaceControl(b.setCallsite("WindowContainer.setInitialSurfaceControlProperties").build());
        getSyncTransaction().show(mSurfaceControl);
        onSurfaceShown(getSyncTransaction());
        updateSurfacePosition();
    }

    /**
     * Create a new SurfaceControl for this WindowContainer and migrate all properties to the new
     * SurfaceControl. Properties include:
     * 1. Children
     * 2. Position
     * 3. Z order
     *
     * Remove the old SurfaceControl since it's no longer needed.
     *
     * This is used to revoke control of the SurfaceControl from a client process that was
     * previously organizing this WindowContainer.
     */
    void migrateToNewSurfaceControl() {
        SurfaceControl.Transaction t = getPendingTransaction();
        t.remove(mSurfaceControl);
        // Clear the last position so the new SurfaceControl will get correct position
        mLastSurfacePosition.set(0, 0);

        final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null)
                .setContainerLayer()
                .setName(getName());

        setInitialSurfaceControlProperties(b);

        // If parent is null, the layer should be placed offscreen so reparent to null. Otherwise,
        // set to the available parent.
        t.reparent(mSurfaceControl, mParent == null ? null : mParent.getSurfaceControl());

        if (mLastRelativeToLayer != null) {
            t.setRelativeLayer(mSurfaceControl, mLastRelativeToLayer, mLastLayer);
        } else {
            t.setLayer(mSurfaceControl, mLastLayer);
        }

        for (int i = 0; i < mChildren.size(); i++)  {
            SurfaceControl sc = mChildren.get(i).getSurfaceControl();
            if (sc != null) {
                t.reparent(sc, mSurfaceControl);
            }
        }
        scheduleAnimation();
    }

    /**
     * Called when the surface is shown for the first time.
     */
    void onSurfaceShown(Transaction t) {
        // do nothing
    }

    // Temp. holders for a chain of containers we are currently processing.
    private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList<>();
    private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList<>();

    /**
     * Adds the input window container has a child of this container in order based on the input
     * comparator.
     * @param child The window container to add as a child of this window container.
     * @param comparator Comparator to use in determining the position the child should be added to.
     *                   If null, the child will be added to the top.
     */
    @CallSuper
    protected void addChild(E child, Comparator<E> comparator) {
        if (!child.mReparenting && child.getParent() != null) {
            throw new IllegalArgumentException("addChild: container=" + child.getName()
                    + " is already a child of container=" + child.getParent().getName()
                    + " can't add to container=" + getName());
        }

        int positionToAdd = -1;
        if (comparator != null) {
            final int count = mChildren.size();
            for (int i = 0; i < count; i++) {
                if (comparator.compare(child, mChildren.get(i)) < 0) {
                    positionToAdd = i;
                    break;
                }
            }
        }

        if (positionToAdd == -1) {
            mChildren.add(child);
        } else {
            mChildren.add(positionToAdd, child);
        }

        // Set the parent after we've actually added a child in case a subclass depends on this.
        child.setParent(this);
    }

    /** Adds the input window container has a child of this container at the input index. */
    @CallSuper
    void addChild(E child, int index) {
        if (!child.mReparenting && child.getParent() != null) {
            throw new IllegalArgumentException("addChild: container=" + child.getName()
                    + " is already a child of container=" + child.getParent().getName()
                    + " can't add to container=" + getName()
                    + "\n callers=" + Debug.getCallers(15, "\n"));
        }

        if ((index < 0 && index != POSITION_BOTTOM)
                || (index > mChildren.size() && index != POSITION_TOP)) {
            throw new IllegalArgumentException("addChild: invalid position=" + index
                    + ", children number=" + mChildren.size());
        }

        if (index == POSITION_TOP) {
            index = mChildren.size();
        } else if (index == POSITION_BOTTOM) {
            index = 0;
        }

        mChildren.add(index, child);

        // Set the parent after we've actually added a child in case a subclass depends on this.
        child.setParent(this);
    }

    private void onChildAdded(WindowContainer child) {
        mTreeWeight += child.mTreeWeight;
        WindowContainer parent = getParent();
        while (parent != null) {
            parent.mTreeWeight += child.mTreeWeight;
            parent = parent.getParent();
        }
        onChildPositionChanged(child);
    }

    /**
     * Removes the input child container from this container which is its parent.
     *
     * @return True if the container did contain the input child and it was detached.
     */
    @CallSuper
    void removeChild(E child) {
        if (mChildren.remove(child)) {
            onChildRemoved(child);
            if (!child.mReparenting) {
                child.setParent(null);
            }
        } else {
            throw new IllegalArgumentException("removeChild: container=" + child.getName()
                    + " is not a child of container=" + getName());
        }
    }

    private void onChildRemoved(WindowContainer child) {
        mTreeWeight -= child.mTreeWeight;
        WindowContainer parent = getParent();
        while (parent != null) {
            parent.mTreeWeight -= child.mTreeWeight;
            parent = parent.getParent();
        }
        onChildPositionChanged(child);
    }

    /**
     * Removes this window container and its children with no regard for what else might be going on
     * in the system. For example, the container will be removed during animation if this method is
     * called which isn't desirable. For most cases you want to call {@link #removeIfPossible()}
     * which allows the system to defer removal until a suitable time.
     */
    @CallSuper
    void removeImmediately() {
        final DisplayContent dc = getDisplayContent();
        if (dc != null) {
            mSurfaceFreezer.unfreeze(getSyncTransaction());
            dc.mChangingContainers.remove(this);
        }
        while (!mChildren.isEmpty()) {
            final E child = mChildren.peekLast();
            child.removeImmediately();
            // Need to do this after calling remove on the child because the child might try to
            // remove/detach itself from its parent which will cause an exception if we remove
            // it before calling remove on the child.
            if (mChildren.remove(child)) {
                onChildRemoved(child);
            }
        }

        if (mSurfaceControl != null) {
            getSyncTransaction().remove(mSurfaceControl);
            setSurfaceControl(null);
            mLastSurfacePosition.set(0, 0);
            scheduleAnimation();
        }

        if (mParent != null) {
            mParent.removeChild(this);
        }
    }

    /**
     * @return The index of this element in the hierarchy tree in prefix order.
     */
    int getPrefixOrderIndex() {
        if (mParent == null) {
            return 0;
        }
        return mParent.getPrefixOrderIndex(this);
    }

    private int getPrefixOrderIndex(WindowContainer child) {
        int order = 0;
        for (int i = 0; i < mChildren.size(); i++) {
            final WindowContainer childI = mChildren.get(i);
            if (child == childI) {
                break;
            }
            order += childI.mTreeWeight;
        }
        if (mParent != null) {
            order += mParent.getPrefixOrderIndex(this);
        }

        // We also need to count ourselves.
        order++;
        return order;
    }

    /**
     * Removes this window container and its children taking care not to remove them during a
     * critical stage in the system. For example, some containers will not be removed during
     * animation if this method is called.
     */
    // TODO: figure-out implementation that works best for this.
    // E.g. when do we remove from parent list? maybe not...
    void removeIfPossible() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            wc.removeIfPossible();
        }
    }

    /** Returns true if this window container has the input child. */
    boolean hasChild(E child) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final E current = mChildren.get(i);
            if (current == child || current.hasChild(child)) {
                return true;
            }
        }
        return false;
    }

    /** @return true if this window container is a descendant of the input container. */
    boolean isDescendantOf(WindowContainer ancestor) {
        final WindowContainer parent = getParent();
        if (parent == ancestor) return true;
        return (parent != null) && parent.isDescendantOf(ancestor);
    }

    /**
     * Move a child from it's current place in siblings list to the specified position,
     * with an option to move all its parents to top.
     * @param position Target position to move the child to.
     * @param child Child to move to selected position.
     * @param includingParents Flag indicating whether we need to move the entire branch of the
     *                         hierarchy when we're moving a child to {@link #POSITION_TOP} or
     *                         {@link #POSITION_BOTTOM}. When moving to other intermediate positions
     *                         this flag will do nothing.
     */
    @CallSuper
    void positionChildAt(int position, E child, boolean includingParents) {

        if (child.getParent() != this) {
            throw new IllegalArgumentException("positionChildAt: container=" + child.getName()
                    + " is not a child of container=" + getName()
                    + " current parent=" + child.getParent());
        }

        if (position >= mChildren.size() - 1) {
            position = POSITION_TOP;
        } else if (position <= 0) {
            position = POSITION_BOTTOM;
        }

        switch (position) {
            case POSITION_TOP:
                if (mChildren.peekLast() != child) {
                    mChildren.remove(child);
                    mChildren.add(child);
                    onChildPositionChanged(child);
                }
                if (includingParents && getParent() != null) {
                    getParent().positionChildAt(POSITION_TOP, this /* child */,
                            true /* includingParents */);
                }
                break;
            case POSITION_BOTTOM:
                if (mChildren.peekFirst() != child) {
                    mChildren.remove(child);
                    mChildren.addFirst(child);
                    onChildPositionChanged(child);
                }
                if (includingParents && getParent() != null) {
                    getParent().positionChildAt(POSITION_BOTTOM, this /* child */,
                            true /* includingParents */);
                }
                break;
            default:
                // TODO: Removing the child before reinserting requires the caller to provide a
                //       position that takes into account the removed child (if the index of the
                //       child < position, then the position should be adjusted). We should consider
                //       doing this adjustment here and remove any adjustments in the callers.
                if (mChildren.indexOf(child) != position) {
                    mChildren.remove(child);
                    mChildren.add(position, child);
                    onChildPositionChanged(child);
                }
        }
    }

    /**
     * Notify that a child's position has changed. Possible changes are adding or removing a child.
     */
    void onChildPositionChanged(WindowContainer child) { }

    /**
     * Update override configuration and recalculate full config.
     * @see #mRequestedOverrideConfiguration
     * @see #mFullConfiguration
     */
    @Override
    public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
        // We must diff before the configuration is applied so that we can capture the change
        // against the existing bounds.
        final int diff = diffRequestedOverrideBounds(
                overrideConfiguration.windowConfiguration.getBounds());
        super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
        if (mParent != null) {
            mParent.onDescendantOverrideConfigurationChanged();
        }

        if (diff == BOUNDS_CHANGE_NONE) {
            return;
        }

        if ((diff & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
            onResize();
        } else {
            onMovedByResize();
        }
    }

    /**
     * Notify that a descendant's overrideConfiguration has changed.
     */
    void onDescendantOverrideConfigurationChanged() {
        if (mParent != null) {
            mParent.onDescendantOverrideConfigurationChanged();
        }
    }

    /**
     * Notify that the display this container is on has changed. This could be either this container
     * is moved to a new display, or some configurations on the display it is on changes.
     *
     * @param dc The display this container is on after changes.
     */
    void onDisplayChanged(DisplayContent dc) {
        if (mDisplayContent != null && mDisplayContent.mChangingContainers.remove(this)) {
            // Cancel any change transition queued-up for this container on the old display.
            mSurfaceFreezer.unfreeze(getPendingTransaction());
        }
        mDisplayContent = dc;
        if (dc != null && dc != this) {
            dc.getPendingTransaction().merge(mPendingTransaction);
        }
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer child = mChildren.get(i);
            child.onDisplayChanged(dc);
        }
    }

    DisplayContent getDisplayContent() {
        return mDisplayContent;
    }

    /** Get the first node of type {@link DisplayArea} above or at this node. */
    @Nullable
    DisplayArea getDisplayArea() {
        WindowContainer parent = getParent();
        return parent != null ? parent.getDisplayArea() : null;
    }

    boolean isAttached() {
        return getDisplayArea() != null;
    }

    void setWaitingForDrawnIfResizingChanged() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            wc.setWaitingForDrawnIfResizingChanged();
        }
    }

    void onResize() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            wc.onParentResize();
        }
    }

    void onParentResize() {
        // In the case this container has specified its own bounds, a parent resize will not
        // affect its bounds. Any relevant changes will be propagated through changes to the
        // Configuration override.
        if (hasOverrideBounds()) {
            return;
        }

        // Default implementation is to treat as resize on self.
        onResize();
    }

    void onMovedByResize() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            wc.onMovedByResize();
        }
    }

    void resetDragResizingChangeReported() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            wc.resetDragResizingChangeReported();
        }
    }

    void forceWindowsScaleableInTransaction(boolean force) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            wc.forceWindowsScaleableInTransaction(force);
        }
    }

    /**
     * @return {@code true} when this container or its related containers are running an
     * animation, {@code false} otherwise.
     *
     * By default this predicate only checks if this container itself is actually running an
     * animation, but you can extend the check target over its relatives, or relax the condition
     * so that this can return {@code true} if an animation starts soon by giving a combination
     * of {@link AnimationFlags}.
     *
     * Note that you can give a combination of bitmask flags to specify targets and condition for
     * checking animating status.
     * e.g. {@code isAnimating(TRANSITION | PARENT)} returns {@code true} if either this
     * container itself or one of its parents is running an animation or waiting for an app
     * transition.
     *
     * Note that TRANSITION propagates to parents and children as well.
     *
     * @param flags The combination of bitmask flags to specify targets and condition for
     *              checking animating status.
     * @param typesToCheck The combination of bitmask {@link AnimationType} to compare when
     *                     determining if animating.
     *
     * @see AnimationFlags#TRANSITION
     * @see AnimationFlags#PARENTS
     * @see AnimationFlags#CHILDREN
     */
    final boolean isAnimating(int flags, int typesToCheck) {
        return getAnimatingContainer(flags, typesToCheck) != null;
    }

    /**
     * Similar to {@link #isAnimating(int, int)} except provide a bitmask of
     * {@link AnimationType} to exclude, rather than include
     * @param flags The combination of bitmask flags to specify targets and condition for
     *              checking animating status.
     * @param typesToExclude The combination of bitmask {@link AnimationType} to exclude when
     *                     checking if animating.
     *
     * @deprecated Use {@link #isAnimating(int, int)}
     */
    @Deprecated
    final boolean isAnimatingExcluding(int flags, int typesToExclude) {
        return isAnimating(flags, ANIMATION_TYPE_ALL & ~typesToExclude);
    }

    /**
     * @deprecated Use {@link #isAnimating(int, int)}
     * TODO (b/152333373): Migrate calls to use isAnimating with specified animation type
     */
    @Deprecated
    final boolean isAnimating(int flags) {
        return isAnimating(flags, ANIMATION_TYPE_ALL);
    }

    /**
     * @return {@code true} when the container is waiting the app transition start, {@code false}
     *         otherwise.
     */
    boolean isWaitingForTransitionStart() {
        return false;
    }

    /**
     * @return {@code true} if in this subtree of the hierarchy we have an
     *         {@code ActivityRecord#isAnimating(TRANSITION)}, {@code false} otherwise.
     */
    boolean isAppTransitioning() {
        return getActivity(app -> app.isAnimating(PARENTS | TRANSITION)) != null;
    }

    /**
     * @return Whether our own container running an animation at the moment.
     */
    final boolean isAnimating() {
        return isAnimating(0 /* self only */);
    }

    /**
     * @return {@code true} if the container is in changing app transition.
     */
    boolean isChangingAppTransition() {
        return mDisplayContent != null && mDisplayContent.mChangingContainers.contains(this);
    }

    void sendAppVisibilityToClients() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            wc.sendAppVisibilityToClients();
        }
    }

    /**
     * Returns true if the container or one of its children as some content it can display or wants
     * to display (e.g. app views or saved surface).
     *
     * NOTE: While this method will return true if the there is some content to display, it doesn't
     * mean the container is visible. Use {@link #isVisible()} to determine if the container is
     * visible.
     */
    boolean hasContentToDisplay() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            if (wc.hasContentToDisplay()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if the container or one of its children is considered visible from the
     * WindowManager perspective which usually means valid surface and some other internal state
     * are true.
     *
     * NOTE: While this method will return true if the surface is visible, it doesn't mean the
     * client has actually displayed any content. Use {@link #hasContentToDisplay()} to determine if
     * the container has any content to display.
     */
    boolean isVisible() {
        // TODO: Will this be more correct if it checks the visibility of its parents?
        // It depends...For example, Tasks and Stacks are only visible if there children are visible
        // but, WindowState are not visible if there parent are not visible. Maybe have the
        // container specify which direction to traverse for visibility?
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            if (wc.isVisible()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Called when the visibility of a child is asked to change. This is before visibility actually
     * changes (eg. a transition animation might play out first).
     */
    void onChildVisibilityRequested(boolean visible) {
        // If we are changing visibility, then a snapshot isn't necessary and we are no-longer
        // part of a change transition.
        mSurfaceFreezer.unfreeze(getSyncTransaction());
        if (mDisplayContent != null) {
            mDisplayContent.mChangingContainers.remove(this);
        }
        WindowContainer parent = getParent();
        if (parent != null) {
            parent.onChildVisibilityRequested(visible);
        }
    }

    void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
        final long token = proto.start(fieldId);
        proto.write(HASH_CODE, System.identityHashCode(this));
        proto.write(USER_ID, USER_NULL);
        proto.write(TITLE, "WindowContainer");
        proto.end(token);
    }

    /**
     * Returns {@code true} if this container is focusable. Generally, if a parent is not focusable,
     * this will not be focusable either.
     */
    boolean isFocusable() {
        final WindowContainer parent = getParent();
        return (parent == null || parent.isFocusable()) && mIsFocusable;
    }

    /** Set whether this container or its children can be focusable */
    boolean setFocusable(boolean focusable) {
        if (mIsFocusable == focusable) {
            return false;
        }
        mIsFocusable = focusable;
        return true;
    }

    /**
     * @return Whether this child is on top of the window hierarchy.
     */
    boolean isOnTop() {
        final WindowContainer parent = getParent();
        return parent != null && parent.getTopChild() == this && parent.isOnTop();
    }

    /** Returns the top child container. */
    E getTopChild() {
        return mChildren.peekLast();
    }

    /**
     * Removes the containers which were deferred.
     *
     * @return {@code true} if there is still a removal being deferred.
     */
    boolean handleCompleteDeferredRemoval() {
        boolean stillDeferringRemoval = false;

        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            stillDeferringRemoval |= wc.handleCompleteDeferredRemoval();
            if (!hasChild()) {
                // All child containers of current level could be removed from a removal of
                // descendant. E.g. if a display is pending to be removed because it contains an
                // activity with {@link ActivityRecord#mIsExiting} is true, the display may be
                // removed when completing the removal of the last activity from
                // {@link ActivityRecord#checkCompleteDeferredRemoval}.
                return false;
            }
        }

        return stillDeferringRemoval;
    }

    /** Checks if all windows in an app are all drawn and shows them if needed. */
    void checkAppWindowsReadyToShow() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            wc.checkAppWindowsReadyToShow();
        }
    }

    void onAppTransitionDone() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            wc.onAppTransitionDone();
        }
    }

    /**
     * Called when this container or one of its descendants changed its requested orientation, and
     * wants this container to handle it or pass it to its parent.
     *
     * @param freezeDisplayToken freeze this app window token if display needs to freeze
     * @param requestingContainer the container which orientation request has changed
     * @return {@code true} if handled; {@code false} otherwise.
     */
    boolean onDescendantOrientationChanged(@Nullable IBinder freezeDisplayToken,
            @Nullable ConfigurationContainer requestingContainer) {
        final WindowContainer parent = getParent();
        if (parent == null) {
            return false;
        }
        return parent.onDescendantOrientationChanged(freezeDisplayToken,
                requestingContainer);
    }

    /**
     * Check if this container or its parent will handle orientation changes from descendants. It's
     * different from the return value of {@link #onDescendantOrientationChanged(IBinder,
     * ConfigurationContainer)} in the sense that the return value of this method tells if this
     * container or its parent will handle the request eventually, while the return value of the
     * other method is if it handled the request synchronously.
     *
     * @return {@code true} if it handles or will handle orientation change in the future; {@code
     *         false} if it won't handle the change at anytime.
     */
    boolean handlesOrientationChangeFromDescendant() {
        final WindowContainer parent = getParent();
        return parent != null && parent.handlesOrientationChangeFromDescendant();
    }

    /**
     * Get the configuration orientation by the requested screen orientation
     * ({@link ActivityInfo.ScreenOrientation}) of this activity.
     *
     * @return orientation in ({@link Configuration#ORIENTATION_LANDSCAPE},
     *         {@link Configuration#ORIENTATION_PORTRAIT},
     *         {@link Configuration#ORIENTATION_UNDEFINED}).
     */
    int getRequestedConfigurationOrientation() {
        if (mOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
            // NOSENSOR means the display's "natural" orientation, so return that.
            if (mDisplayContent != null) {
                return mDisplayContent.getNaturalOrientation();
            }
        } else if (mOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
            // LOCKED means the activity's orientation remains unchanged, so return existing value.
            return getConfiguration().orientation;
        } else if (isFixedOrientationLandscape(mOrientation)) {
            return ORIENTATION_LANDSCAPE;
        } else if (isFixedOrientationPortrait(mOrientation)) {
            return ORIENTATION_PORTRAIT;
        }
        return ORIENTATION_UNDEFINED;
    }

    /**
     * Calls {@link #setOrientation(int, IBinder, ActivityRecord)} with {@code null} to the last 2
     * parameters.
     *
     * @param orientation the specified orientation.
     */
    void setOrientation(int orientation) {
        setOrientation(orientation, null /* freezeDisplayToken */,
                null /* ActivityRecord */);
    }

    /**
     * Sets the specified orientation of this container. It percolates this change upward along the
     * hierarchy to let each level of the hierarchy a chance to respond to it.
     *
     * @param orientation the specified orientation. Needs to be one of {@link
     *      android.content.pm.ActivityInfo.ScreenOrientation}.
     * @param freezeDisplayToken uses this token to freeze display if orientation change is not
     *                           done. Display will not be frozen if this is {@code null}, which
     *                           should only happen in tests.
     * @param requestingContainer the container which orientation request has changed. Mostly used
     *                            to ensure it gets correct configuration.
     */
    void setOrientation(int orientation, @Nullable IBinder freezeDisplayToken,
            @Nullable ConfigurationContainer requestingContainer) {
        if (mOrientation == orientation) {
            return;
        }

        mOrientation = orientation;
        final WindowContainer parent = getParent();
        if (parent != null) {
            if (getConfiguration().orientation != getRequestedConfigurationOrientation()) {
                // Resolve the requested orientation.
                onConfigurationChanged(parent.getConfiguration());
            }
            onDescendantOrientationChanged(freezeDisplayToken, requestingContainer);
        }
    }

    @ActivityInfo.ScreenOrientation
    int getOrientation() {
        return getOrientation(mOrientation);
    }

    /**
     * Returns the specified orientation for this window container or one of its children is there
     * is one set, or {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSET} if no
     * specification is set.
     * NOTE: {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} is a
     * specification...
     *
     * @param candidate The current orientation candidate that will be returned if we don't find a
     *                  better match.
     * @return The orientation as specified by this branch or the window hierarchy.
     */
    int getOrientation(int candidate) {
        mLastOrientationSource = null;
        if (!fillsParent()) {
            // Ignore containers that don't completely fill their parents.
            return SCREEN_ORIENTATION_UNSET;
        }

        // The container fills its parent so we can use it orientation if it has one
        // specified; otherwise we prefer to use the orientation of its topmost child that has one
        // specified and fall back on this container's unset or unspecified value as a candidate
        // if none of the children have a better candidate for the orientation.
        if (mOrientation != SCREEN_ORIENTATION_UNSET
                && mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
            mLastOrientationSource = this;
            return mOrientation;
        }

        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);

            // TODO: Maybe mOrientation should default to SCREEN_ORIENTATION_UNSET vs.
            // SCREEN_ORIENTATION_UNSPECIFIED?
            final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND
                    ? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET);
            if (orientation == SCREEN_ORIENTATION_BEHIND) {
                // container wants us to use the orientation of the container behind it. See if we
                // can find one. Else return SCREEN_ORIENTATION_BEHIND so the caller can choose to
                // look behind this container.
                candidate = orientation;
                mLastOrientationSource = wc;
                continue;
            }

            if (orientation == SCREEN_ORIENTATION_UNSET) {
                continue;
            }

            if (wc.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
                // Use the orientation if the container fills its parent or requested an explicit
                // orientation that isn't SCREEN_ORIENTATION_UNSPECIFIED.
                ProtoLog.v(WM_DEBUG_ORIENTATION, "%s is requesting orientation %d (%s)",
                        wc.toString(), orientation,
                        ActivityInfo.screenOrientationToString(orientation));
                mLastOrientationSource = wc;
                return orientation;
            }
        }

        return candidate;
    }

    /**
     * @return The deepest source which decides the orientation of this window container since the
     *         last time {@link #getOrientation(int) was called.
     */
    @Nullable
    WindowContainer getLastOrientationSource() {
        final WindowContainer source = mLastOrientationSource;
        if (source != null && source != this) {
            final WindowContainer nextSource = source.getLastOrientationSource();
            if (nextSource != null) {
                return nextSource;
            }
        }
        return source;
    }

    /**
     * Returns true if this container is opaque and fills all the space made available by its parent
     * container.
     *
     * NOTE: It is possible for this container to occupy more space than the parent has (or less),
     * this is just a signal from the client to window manager stating its intent, but not what it
     * actually does.
     */
    boolean fillsParent() {
        return false;
    }

    // TODO: Users would have their own window containers under the display container?
    void switchUser(int userId) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            mChildren.get(i).switchUser(userId);
        }
    }

    boolean showToCurrentUser() {
        return true;
    }

    /**
     * For all windows at or below this container call the callback.
     * @param   callback Calls the {@link ToBooleanFunction#apply} method for each window found and
     *                   stops the search if {@link ToBooleanFunction#apply} returns true.
     * @param   traverseTopToBottom If true traverses the hierarchy from top-to-bottom in terms of
     *                              z-order, else from bottom-to-top.
     * @return  True if the search ended before we reached the end of the hierarchy due to
     *          {@link ToBooleanFunction#apply} returning true.
     */
    boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
        if (traverseTopToBottom) {
            for (int i = mChildren.size() - 1; i >= 0; --i) {
                if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
                    return true;
                }
            }
        } else {
            final int count = mChildren.size();
            for (int i = 0; i < count; i++) {
                if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
                    return true;
                }
            }
        }
        return false;
    }

    void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
        ForAllWindowsConsumerWrapper wrapper = obtainConsumerWrapper(callback);
        forAllWindows(wrapper, traverseTopToBottom);
        wrapper.release();
    }

    boolean forAllActivities(Function<ActivityRecord, Boolean> callback) {
        return forAllActivities(callback, true /*traverseTopToBottom*/);
    }

    boolean forAllActivities(
            Function<ActivityRecord, Boolean> callback, boolean traverseTopToBottom) {
        if (traverseTopToBottom) {
            for (int i = mChildren.size() - 1; i >= 0; --i) {
                if (mChildren.get(i).forAllActivities(callback, traverseTopToBottom)) return true;
            }
        } else {
            final int count = mChildren.size();
            for (int i = 0; i < count; i++) {
                if (mChildren.get(i).forAllActivities(callback, traverseTopToBottom)) return true;
            }
        }

        return false;
    }

    void forAllActivities(Consumer<ActivityRecord> callback) {
        forAllActivities(callback, true /*traverseTopToBottom*/);
    }

    void forAllActivities(Consumer<ActivityRecord> callback, boolean traverseTopToBottom) {
        if (traverseTopToBottom) {
            for (int i = mChildren.size() - 1; i >= 0; --i) {
                mChildren.get(i).forAllActivities(callback, traverseTopToBottom);
            }
        } else {
            final int count = mChildren.size();
            for (int i = 0; i < count; i++) {
                mChildren.get(i).forAllActivities(callback, traverseTopToBottom);
            }
        }
    }

    /**
     * Process all activities in this branch of the tree.
     *
     * @param callback Called for each activity found.
     * @param boundary We don't return activities via {@param callback} until we get to this node in
     *                 the tree.
     * @param includeBoundary If the boundary from be processed to return activities.
     * @param traverseTopToBottom direction to traverse the tree.
     * @return {@code true} if we ended the search before reaching the end of the tree.
     */
    final boolean forAllActivities(Function<ActivityRecord, Boolean> callback,
            WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom) {
        return forAllActivities(
                callback, boundary, includeBoundary, traverseTopToBottom, new boolean[1]);
    }

    private boolean forAllActivities(Function<ActivityRecord, Boolean> callback,
            WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
            boolean[] boundaryFound) {
        if (traverseTopToBottom) {
            for (int i = mChildren.size() - 1; i >= 0; --i) {
                if (processForAllActivitiesWithBoundary(callback, boundary, includeBoundary,
                        traverseTopToBottom, boundaryFound, mChildren.get(i))) {
                    return true;
                }
            }
        } else {
            final int count = mChildren.size();
            for (int i = 0; i < count; i++) {
                if (processForAllActivitiesWithBoundary(callback, boundary, includeBoundary,
                        traverseTopToBottom, boundaryFound, mChildren.get(i))) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean processForAllActivitiesWithBoundary(Function<ActivityRecord, Boolean> callback,
            WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
            boolean[] boundaryFound, WindowContainer wc) {
        if (wc == boundary) {
            boundaryFound[0] = true;
            if (!includeBoundary) return false;
        }

        if (boundaryFound[0]) {
            return wc.forAllActivities(callback, traverseTopToBottom);
        }

        return wc.forAllActivities(
                callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
    }

    /** @return {@code true} if this node or any of its children contains an activity. */
    boolean hasActivity() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            if (mChildren.get(i).hasActivity()) {
                return true;
            }
        }
        return false;
    }

    ActivityRecord getActivity(Predicate<ActivityRecord> callback) {
        return getActivity(callback, true /*traverseTopToBottom*/);
    }

    ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
        return getActivity(callback, traverseTopToBottom, null /*boundary*/);
    }

    ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
            ActivityRecord boundary) {
        if (traverseTopToBottom) {
            for (int i = mChildren.size() - 1; i >= 0; --i) {
                final WindowContainer wc = mChildren.get(i);
                // TODO(b/156986561): Improve the correctness of the boundary check.
                if (wc == boundary) return boundary;

                final ActivityRecord r = wc.getActivity(callback, traverseTopToBottom, boundary);
                if (r != null) {
                    return r;
                }
            }
        } else {
            final int count = mChildren.size();
            for (int i = 0; i < count; i++) {
                final WindowContainer wc = mChildren.get(i);
                // TODO(b/156986561): Improve the correctness of the boundary check.
                if (wc == boundary) return boundary;

                final ActivityRecord r = wc.getActivity(callback, traverseTopToBottom, boundary);
                if (r != null) {
                    return r;
                }
            }
        }

        return null;
    }

    /**
     * Gets an activity in a branch of the tree.
     *
     * @param callback called to test if this is the activity that should be returned.
     * @param boundary We don't return activities via {@param callback} until we get to this node in
     *                 the tree.
     * @param includeBoundary If the boundary from be processed to return activities.
     * @param traverseTopToBottom direction to traverse the tree.
     * @return The activity if found or null.
     */
    final ActivityRecord getActivity(Predicate<ActivityRecord> callback,
            WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom) {
        return getActivity(
                callback, boundary, includeBoundary, traverseTopToBottom, new boolean[1]);
    }

    private ActivityRecord getActivity(Predicate<ActivityRecord> callback,
            WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
            boolean[] boundaryFound) {
        if (traverseTopToBottom) {
            for (int i = mChildren.size() - 1; i >= 0; --i) {
                final ActivityRecord r = processGetActivityWithBoundary(callback, boundary,
                        includeBoundary, traverseTopToBottom, boundaryFound, mChildren.get(i));
                if (r != null) {
                    return r;
                }
            }
        } else {
            final int count = mChildren.size();
            for (int i = 0; i < count; i++) {
                final ActivityRecord r = processGetActivityWithBoundary(callback, boundary,
                        includeBoundary, traverseTopToBottom, boundaryFound, mChildren.get(i));
                if (r != null) {
                    return r;
                }
            }
        }

        return null;
    }

    private ActivityRecord processGetActivityWithBoundary(Predicate<ActivityRecord> callback,
            WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
            boolean[] boundaryFound, WindowContainer wc) {
        if (wc == boundary || boundary == null) {
            boundaryFound[0] = true;
            if (!includeBoundary) return null;
        }

        if (boundaryFound[0]) {
            return wc.getActivity(callback, traverseTopToBottom);
        }

        return wc.getActivity(
                callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
    }

    ActivityRecord getActivityAbove(ActivityRecord r) {
        return getActivity((above) -> true, r,
                false /*includeBoundary*/, false /*traverseTopToBottom*/);
    }

    ActivityRecord getActivityBelow(ActivityRecord r) {
        return getActivity((below) -> true, r,
                false /*includeBoundary*/, true /*traverseTopToBottom*/);
    }

    ActivityRecord getBottomMostActivity() {
        return getActivity((r) -> true, false /*traverseTopToBottom*/);
    }

    ActivityRecord getTopMostActivity() {
        return getActivity((r) -> true, true /*traverseTopToBottom*/);
    }

    ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) {
        // Break down into 4 calls to avoid object creation due to capturing input params.
        if (includeFinishing) {
            if (includeOverlays) {
                return getActivity((r) -> true);
            }
            return getActivity((r) -> !r.isTaskOverlay());
        } else if (includeOverlays) {
            return getActivity((r) -> !r.finishing);
        }

        return getActivity((r) -> !r.finishing && !r.isTaskOverlay());
    }

    void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            mChildren.get(i).forAllWallpaperWindows(callback);
        }
    }

    /**
     * For all tasks at or below this container call the callback.
     *
     * @param callback Calls the {@link ToBooleanFunction#apply} method for each task found and
     *                 stops the search if {@link ToBooleanFunction#apply} returns {@code true}.
     */
    boolean forAllTasks(Function<Task, Boolean> callback) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            if (mChildren.get(i).forAllTasks(callback)) {
                return true;
            }
        }
        return false;
    }

    boolean forAllLeafTasks(Function<Task, Boolean> callback) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            if (mChildren.get(i).forAllLeafTasks(callback)) {
                return true;
            }
        }
        return false;
    }

    /**
     * For all tasks at or below this container call the callback.
     *
     * @param callback Callback to be called for every task.
     */
    void forAllTasks(Consumer<Task> callback) {
        forAllTasks(callback, true /*traverseTopToBottom*/);
    }

    void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
        final int count = mChildren.size();
        if (traverseTopToBottom) {
            for (int i = count - 1; i >= 0; --i) {
                mChildren.get(i).forAllTasks(callback, traverseTopToBottom);
            }
        } else {
            for (int i = 0; i < count; i++) {
                mChildren.get(i).forAllTasks(callback, traverseTopToBottom);
            }
        }
    }

    void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
        final int count = mChildren.size();
        if (traverseTopToBottom) {
            for (int i = count - 1; i >= 0; --i) {
                mChildren.get(i).forAllLeafTasks(callback, traverseTopToBottom);
            }
        } else {
            for (int i = 0; i < count; i++) {
                mChildren.get(i).forAllLeafTasks(callback, traverseTopToBottom);
            }
        }
    }

    Task getTaskAbove(Task t) {
        return getTask(
                (above) -> true, t, false /*includeBoundary*/, false /*traverseTopToBottom*/);
    }

    Task getTaskBelow(Task t) {
        return getTask((below) -> true, t, false /*includeBoundary*/, true /*traverseTopToBottom*/);
    }

    Task getBottomMostTask() {
        return getTask((t) -> true, false /*traverseTopToBottom*/);
    }

    Task getTopMostTask() {
        return getTask((t) -> true, true /*traverseTopToBottom*/);
    }

    Task getTask(Predicate<Task> callback) {
        return getTask(callback, true /*traverseTopToBottom*/);
    }

    Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) {
        if (traverseTopToBottom) {
            for (int i = mChildren.size() - 1; i >= 0; --i) {
                final Task t = mChildren.get(i).getTask(callback, traverseTopToBottom);
                if (t != null) {
                    return t;
                }
            }
        } else {
            final int count = mChildren.size();
            for (int i = 0; i < count; i++) {
                final Task t = mChildren.get(i).getTask(callback, traverseTopToBottom);
                if (t != null) {
                    return t;
                }
            }
        }

        return null;
    }

    /**
     * Gets an task in a branch of the tree.
     *
     * @param callback called to test if this is the task that should be returned.
     * @param boundary We don't return tasks via {@param callback} until we get to this node in
     *                 the tree.
     * @param includeBoundary If the boundary from be processed to return tasks.
     * @param traverseTopToBottom direction to traverse the tree.
     * @return The task if found or null.
     */
    final Task getTask(Predicate<Task> callback, WindowContainer boundary, boolean includeBoundary,
            boolean traverseTopToBottom) {
        return getTask(callback, boundary, includeBoundary, traverseTopToBottom, new boolean[1]);
    }

    private Task getTask(Predicate<Task> callback,
            WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
            boolean[] boundaryFound) {
        if (traverseTopToBottom) {
            for (int i = mChildren.size() - 1; i >= 0; --i) {
                final Task t = processGetTaskWithBoundary(callback, boundary,
                        includeBoundary, traverseTopToBottom, boundaryFound, mChildren.get(i));
                if (t != null) {
                    return t;
                }
            }
        } else {
            final int count = mChildren.size();
            for (int i = 0; i < count; i++) {
                final Task t = processGetTaskWithBoundary(callback, boundary,
                        includeBoundary, traverseTopToBottom, boundaryFound, mChildren.get(i));
                if (t != null) {
                    return t;
                }
            }
        }

        return null;
    }

    private Task processGetTaskWithBoundary(Predicate<Task> callback,
            WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
            boolean[] boundaryFound, WindowContainer wc) {
        if (wc == boundary || boundary == null) {
            boundaryFound[0] = true;
            if (!includeBoundary) return null;
        }

        if (boundaryFound[0]) {
            return wc.getTask(callback, traverseTopToBottom);
        }

        return wc.getTask(
                callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
    }

    WindowState getWindow(Predicate<WindowState> callback) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowState w = mChildren.get(i).getWindow(callback);
            if (w != null) {
                return w;
            }
        }

        return null;
    }

    void forAllDisplayAreas(Consumer<DisplayArea> callback) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            mChildren.get(i).forAllDisplayAreas(callback);
        }
    }

    /**
     * Returns 1, 0, or -1 depending on if this container is greater than, equal to, or lesser than
     * the input container in terms of z-order.
     */
    @Override
    public int compareTo(WindowContainer other) {
        if (this == other) {
            return 0;
        }

        if (mParent != null && mParent == other.mParent) {
            final WindowList<WindowContainer> list = mParent.mChildren;
            return list.indexOf(this) > list.indexOf(other) ? 1 : -1;
        }

        final LinkedList<WindowContainer> thisParentChain = mTmpChain1;
        final LinkedList<WindowContainer> otherParentChain = mTmpChain2;
        try {
            getParents(thisParentChain);
            other.getParents(otherParentChain);

            // Find the common ancestor of both containers.
            WindowContainer commonAncestor = null;
            WindowContainer thisTop = thisParentChain.peekLast();
            WindowContainer otherTop = otherParentChain.peekLast();
            while (thisTop != null && otherTop != null && thisTop == otherTop) {
                commonAncestor = thisParentChain.removeLast();
                otherParentChain.removeLast();
                thisTop = thisParentChain.peekLast();
                otherTop = otherParentChain.peekLast();
            }

            // Containers don't belong to the same hierarchy???
            if (commonAncestor == null) {
                throw new IllegalArgumentException("No in the same hierarchy this="
                        + thisParentChain + " other=" + otherParentChain);
            }

            // Children are always considered greater than their parents, so if one of the containers
            // we are comparing it the parent of the other then whichever is the child is greater.
            if (commonAncestor == this) {
                return -1;
            } else if (commonAncestor == other) {
                return 1;
            }

            // The position of the first non-common ancestor in the common ancestor list determines
            // which is greater the which.
            final WindowList<WindowContainer> list = commonAncestor.mChildren;
            return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast())
                    ? 1 : -1;
        } finally {
            mTmpChain1.clear();
            mTmpChain2.clear();
        }
    }

    private void getParents(LinkedList<WindowContainer> parents) {
        parents.clear();
        WindowContainer current = this;
        do {
            parents.addLast(current);
            current = current.mParent;
        } while (current != null);
    }

    SurfaceControl.Builder makeSurface() {
        final WindowContainer p = getParent();
        return p.makeChildSurface(this);
    }

    /**
     * @param child The WindowContainer this child surface is for, or null if the Surface
     *              is not assosciated with a WindowContainer (e.g. a surface used for Dimming).
     */
    SurfaceControl.Builder makeChildSurface(WindowContainer child) {
        final WindowContainer p = getParent();
        // Give the parent a chance to set properties. In hierarchy v1 we rely
        // on this to set full-screen dimensions on all our Surface-less Layers.
        return p.makeChildSurface(child)
                .setParent(mSurfaceControl);
    }
    /*
     * @return The SurfaceControl parent for this containers SurfaceControl.
     *         The SurfaceControl must be valid if non-null.
     */
    @Override
    public SurfaceControl getParentSurfaceControl() {
        final WindowContainer parent = getParent();
        if (parent == null) {
            return null;
        }
        return parent.getSurfaceControl();
    }

    /**
     * @return Whether this WindowContainer should be magnified by the accessibility magnifier.
     */
    boolean shouldMagnify() {
        if (mSurfaceControl == null) {
            return false;
        }

        for (int i = 0; i < mChildren.size(); i++) {
            if (!mChildren.get(i).shouldMagnify()) {
                return false;
            }
        }
        return true;
    }

    SurfaceSession getSession() {
        if (getParent() != null) {
            return getParent().getSession();
        }
        return null;
    }

    void assignLayer(Transaction t, int layer) {
        final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null;
        if (mSurfaceControl != null && changed) {
            setLayer(t, layer);
            mLastLayer = layer;
            mLastRelativeToLayer = null;
        }
    }

    void assignRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
        final boolean changed = layer != mLastLayer || mLastRelativeToLayer != relativeTo;
        if (mSurfaceControl != null && changed) {
            setRelativeLayer(t, relativeTo, layer);
            mLastLayer = layer;
            mLastRelativeToLayer = relativeTo;
        }
    }

    protected void setLayer(Transaction t, int layer) {

        // Route through surface animator to accommodate that our surface control might be
        // attached to the leash, and leash is attached to parent container.
        mSurfaceAnimator.setLayer(t, layer);
    }

    protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {

        // Route through surface animator to accommodate that our surface control might be
        // attached to the leash, and leash is attached to parent container.
        mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
    }

    protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
        mSurfaceAnimator.reparent(t, newParent);
    }

    void assignChildLayers(Transaction t) {
        int layer = 0;

        // We use two passes as a way to promote children which
        // need Z-boosting to the end of the list.
        for (int j = 0; j < mChildren.size(); ++j) {
            final WindowContainer wc = mChildren.get(j);
            wc.assignChildLayers(t);
            if (!wc.needsZBoost()) {
                wc.assignLayer(t, layer++);
            }
        }
        for (int j = 0; j < mChildren.size(); ++j) {
            final WindowContainer wc = mChildren.get(j);
            if (wc.needsZBoost()) {
                wc.assignLayer(t, layer++);
            }
        }
    }

    void assignChildLayers() {
        assignChildLayers(getSyncTransaction());
        scheduleAnimation();
    }

    boolean needsZBoost() {
        if (mNeedsZBoost) return true;
        for (int i = 0; i < mChildren.size(); i++) {
            if (mChildren.get(i).needsZBoost()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Write to a protocol buffer output stream. Protocol buffer message definition is at
     * {@link com.android.server.wm.WindowContainerProto}.
     *
     * @param proto     Stream to write the WindowContainer object to.
     * @param fieldId   Field Id of the WindowContainer as defined in the parent message.
     * @param logLevel  Determines the amount of data to be written to the Protobuf.
     * @hide
     */
    @CallSuper
    @Override
    public void dumpDebug(ProtoOutputStream proto, long fieldId,
            @WindowTraceLogLevel int logLevel) {
        boolean isVisible = isVisible();
        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible) {
            return;
        }

        final long token = proto.start(fieldId);
        super.dumpDebug(proto, CONFIGURATION_CONTAINER, logLevel);
        proto.write(ORIENTATION, mOrientation);
        proto.write(VISIBLE, isVisible);
        if (mSurfaceAnimator.isAnimating()) {
            mSurfaceAnimator.dumpDebug(proto, SURFACE_ANIMATOR);
        }

        // add children to proto
        for (int i = 0; i < getChildCount(); i++) {
            final long childToken = proto.start(WindowContainerProto.CHILDREN);
            final E child = getChildAt(i);
            child.dumpDebug(proto, child.getProtoFieldId(), logLevel);
            proto.end(childToken);
        }
        proto.end(token);
    }

    /**
     * @return a proto field id to identify where to add the derived class to the generic window
     * container proto.
     */
    long getProtoFieldId() {
        return WINDOW_CONTAINER;
    }

    private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer<WindowState> consumer) {
        ForAllWindowsConsumerWrapper wrapper = mConsumerWrapperPool.acquire();
        if (wrapper == null) {
            wrapper = new ForAllWindowsConsumerWrapper();
        }
        wrapper.setConsumer(consumer);
        return wrapper;
    }

    private final class ForAllWindowsConsumerWrapper implements ToBooleanFunction<WindowState> {

        private Consumer<WindowState> mConsumer;

        void setConsumer(Consumer<WindowState> consumer) {
            mConsumer = consumer;
        }

        @Override
        public boolean apply(WindowState w) {
            mConsumer.accept(w);
            return false;
        }

        void release() {
            mConsumer = null;
            mConsumerWrapperPool.release(this);
        }
    }

    // TODO(b/68336570): Should this really be on WindowContainer since it
    // can only be used on the top-level nodes that aren't animated?
    // (otherwise we would be fighting other callers of setMatrix).
    void applyMagnificationSpec(Transaction t, MagnificationSpec spec) {
        if (shouldMagnify()) {
            t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
                    .setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
            mLastMagnificationSpec = spec;
        } else {
            clearMagnificationSpec(t);
            for (int i = 0; i < mChildren.size(); i++) {
                mChildren.get(i).applyMagnificationSpec(t, spec);
            }
        }
    }

    void clearMagnificationSpec(Transaction t) {
        if (mLastMagnificationSpec != null) {
            t.setMatrix(mSurfaceControl, 1, 0, 0, 1)
                .setPosition(mSurfaceControl, 0, 0);
        }
        mLastMagnificationSpec = null;
        for (int i = 0; i < mChildren.size(); i++) {
            mChildren.get(i).clearMagnificationSpec(t);
        }
    }

    void prepareSurfaces() {
        // If a leash has been set when the transaction was committed, then the leash reparent has
        // been committed.
        mCommittedReparentToAnimationLeash = mSurfaceAnimator.hasLeash();
        for (int i = 0; i < mChildren.size(); i++) {
            mChildren.get(i).prepareSurfaces();
        }
    }

    /**
     * @return true if the reparent to animation leash transaction has been committed, false
     * otherwise.
     */
    boolean hasCommittedReparentToAnimationLeash() {
        return mCommittedReparentToAnimationLeash;
    }

    /**
     * Trigger a call to prepareSurfaces from the animation thread, such that pending transactions
     * will be applied.
     */
    void scheduleAnimation() {
        if (mParent != null) {
            mParent.scheduleAnimation();
        }
    }

    /**
     * @return The SurfaceControl for this container.
     *         The SurfaceControl must be valid if non-null.
     */
    @Override
    public SurfaceControl getSurfaceControl() {
        return mSurfaceControl;
    }

    /**
     * Use this method instead of {@link #getPendingTransaction()} if the Transaction should be
     * synchronized with the client.
     *
     * @return {@link #mBLASTSyncTransaction} if available. Otherwise, returns
     * {@link #getPendingTransaction()}
     */
    public Transaction getSyncTransaction() {
        if (mUsingBLASTSyncTransaction) {
            return mBLASTSyncTransaction;
        }

        return getPendingTransaction();
    }

    @Override
    public Transaction getPendingTransaction() {
        final DisplayContent displayContent = getDisplayContent();
        if (displayContent != null && displayContent != this) {
            return displayContent.getPendingTransaction();
        }
        // This WindowContainer has not attached to a display yet or this is a DisplayContent, so we
        // let the caller to save the surface operations within the local mPendingTransaction.
        // If this is not a DisplayContent, we will merge it to the pending transaction of its
        // display once it attaches to it.
        return mPendingTransaction;
    }

    /**
     * Starts an animation on the container.
     *
     * @param anim The animation to run.
     * @param hidden Whether our container is currently hidden. TODO This should use isVisible at
     *               some point but the meaning is too weird to work for all containers.
     * @param type The type of animation defined as {@link AnimationType}.
     * @param animationFinishedCallback The callback being triggered when the animation finishes.
     */
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
        if (DEBUG_ANIM) {
            Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim);
        }

        // TODO: This should use isVisible() but because isVisible has a really weird meaning at
        // the moment this doesn't work for all animatable window containers.
        mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
                mSurfaceFreezer);
    }

    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type) {
        startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
    }

    void transferAnimation(WindowContainer from) {
        mSurfaceAnimator.transferAnimation(from.mSurfaceAnimator);
    }

    void cancelAnimation() {
        doAnimationFinished(mSurfaceAnimator.getAnimationType(), mSurfaceAnimator.getAnimation());
        mSurfaceAnimator.cancelAnimation();
        mSurfaceFreezer.unfreeze(getPendingTransaction());
    }

    ArraySet<WindowContainer> getAnimationSources() {
        return mSurfaceAnimationSources;
    }

    @Override
    public SurfaceControl getFreezeSnapshotTarget() {
        return null;
    }

    @Override
    public Builder makeAnimationLeash() {
        return makeSurface().setContainerLayer();
    }

    @Override
    public SurfaceControl getAnimationLeashParent() {
        return getParentSurfaceControl();
    }

    /**
     * @return The layer on which all app animations are happening.
     */
    SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) {
        final WindowContainer parent = getParent();
        if (parent != null) {
            return parent.getAppAnimationLayer(animationLayer);
        }
        return null;
    }

    // TODO: Remove this and use #getBounds() instead once we set an app transition animation
    // on TaskStack.
    Rect getAnimationBounds(int appStackClipMode) {
        return getBounds();
    }

    /** Gets the position relative to parent for animation. */
    void getAnimationPosition(Point outPosition) {
        getRelativePosition(outPosition);
    }

    /**
     * Applies the app transition animation according the given the layout properties in the
     * window hierarchy.
     *
     * @param lp The layout parameters of the window.
     * @param transit The app transition type indicates what kind of transition to be applied.
     * @param enter Whether the app transition is entering transition or not.
     * @param isVoiceInteraction Whether the container is participating in voice interaction or not.
     * @param sources {@link ActivityRecord}s which causes this app transition animation.
     *
     * @return {@code true} when the container applied the app transition, {@code false} if the
     *         app transition is disabled or skipped.
     *
     * @see #getAnimationAdapter
     */
    boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
            boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
        if (mWmService.mDisableTransitionAnimation) {
            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                    "applyAnimation: transition animation is disabled or skipped. "
                            + "container=%s", this);
            cancelAnimation();
            return false;
        }

        // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
        // to animate and it can cause strange artifacts when we unfreeze the display if some
        // different animation is running.
        try {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
            if (okToAnimate()) {
                applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
            } else {
                cancelAnimation();
            }
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }

        return isAnimating();
    }

    /**
     * Gets the {@link AnimationAdapter} according the given window layout properties in the window
     * hierarchy.
     *
     * @return The return value will always contain two elements, one for normal animations and the
     *         other for thumbnail animation, both can be {@code null}.
     *
     * @See com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord
     * @See LocalAnimationAdapter
     */
    Pair<AnimationAdapter, AnimationAdapter> getAnimationAdapter(WindowManager.LayoutParams lp,
            int transit, boolean enter, boolean isVoiceInteraction) {
        final Pair<AnimationAdapter, AnimationAdapter> resultAdapters;
        final int appStackClipMode = getDisplayContent().mAppTransition.getAppStackClipMode();

        // Separate position and size for use in animators.
        final Rect screenBounds = getAnimationBounds(appStackClipMode);
        mTmpRect.set(screenBounds);
        getAnimationPosition(mTmpPoint);
        if (!sHierarchicalAnimations) {
            // Non-hierarchical animation uses position in global coordinates.
            mTmpPoint.set(mTmpRect.left, mTmpRect.top);
        }
        mTmpRect.offsetTo(0, 0);

        final RemoteAnimationController controller =
                getDisplayContent().mAppTransition.getRemoteAnimationController();
        final boolean isChanging = AppTransition.isChangeTransit(transit) && enter
                && isChangingAppTransition();

        // Delaying animation start isn't compatible with remote animations at all.
        if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {
            final Rect localBounds = new Rect(mTmpRect);
            localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
            final RemoteAnimationController.RemoteAnimationRecord adapters =
                    controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds,
                            screenBounds, (isChanging ? mSurfaceFreezer.mFreezeBounds : null));
            resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);
        } else if (isChanging) {
            final float durationScale = mWmService.getTransitionAnimationScaleLocked();
            final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
            mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);

            final AnimationAdapter adapter = new LocalAnimationAdapter(
                    new WindowChangeAnimationSpec(mSurfaceFreezer.mFreezeBounds, mTmpRect,
                            displayInfo, durationScale, true /* isAppAnimation */,
                            false /* isThumbnail */),
                    getSurfaceAnimationRunner());

            final AnimationAdapter thumbnailAdapter = mSurfaceFreezer.mSnapshot != null
                    ? new LocalAnimationAdapter(new WindowChangeAnimationSpec(
                    mSurfaceFreezer.mFreezeBounds, mTmpRect, displayInfo, durationScale,
                    true /* isAppAnimation */, true /* isThumbnail */), getSurfaceAnimationRunner())
                    : null;
            resultAdapters = new Pair<>(adapter, thumbnailAdapter);
            mTransit = transit;
            mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
        } else {
            mNeedsAnimationBoundsLayer = (appStackClipMode == STACK_CLIP_AFTER_ANIM);
            final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);

            if (a != null) {
                // Only apply corner radius to animation if we're not in multi window mode.
                // We don't want rounded corners when in pip or split screen.
                final float windowCornerRadius = !inMultiWindowMode()
                        ? getDisplayContent().getWindowCornerRadius()
                        : 0;
                AnimationAdapter adapter = new LocalAnimationAdapter(
                        new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
                                getDisplayContent().mAppTransition.canSkipFirstFrame(),
                                appStackClipMode, true /* isAppAnimation */, windowCornerRadius),
                        getSurfaceAnimationRunner());

                resultAdapters = new Pair<>(adapter, null);
                mNeedsZBoost = a.getZAdjustment() == Animation.ZORDER_TOP
                        || AppTransition.isClosingTransit(transit);
                mTransit = transit;
                mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
            } else {
                resultAdapters = new Pair<>(null, null);
            }
        }
        return resultAdapters;
    }

    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
            int transit, boolean isVoiceInteraction,
            @Nullable ArrayList<WindowContainer> sources) {
        final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
                transit, enter, isVoiceInteraction);
        AnimationAdapter adapter = adapters.first;
        AnimationAdapter thumbnailAdapter = adapters.second;
        if (adapter != null) {
            if (sources != null) {
                mSurfaceAnimationSources.addAll(sources);
            }
            startAnimation(getPendingTransaction(), adapter, !isVisible(),
                    ANIMATION_TYPE_APP_TRANSITION);
            if (adapter.getShowWallpaper()) {
                getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
            }
            if (thumbnailAdapter != null) {
                mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(),
                        thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { });
            }
        }
    }

    final SurfaceAnimationRunner getSurfaceAnimationRunner() {
        return mWmService.mSurfaceAnimationRunner;
    }

    private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
                                    boolean isVoiceInteraction) {
        if (isOrganized()) {
            // Defer to the task organizer to run animations
            return null;
        }

        final DisplayContent displayContent = getDisplayContent();
        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
        final int width = displayInfo.appWidth;
        final int height = displayInfo.appHeight;
        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: container=%s", this);

        // Determine the visible rect to calculate the thumbnail clip with
        // getAnimationFrames.
        final Rect frame = new Rect(0, 0, width, height);
        final Rect displayFrame = new Rect(0, 0,
                displayInfo.logicalWidth, displayInfo.logicalHeight);
        final Rect insets = new Rect();
        final Rect stableInsets = new Rect();
        final Rect surfaceInsets = new Rect();
        getAnimationFrames(frame, insets, stableInsets, surfaceInsets);

        if (mLaunchTaskBehind) {
            // Differentiate the two animations. This one which is briefly on the screen
            // gets the !enter animation, and the other one which remains on the
            // screen gets the enter animation. Both appear in the mOpeningApps set.
            enter = false;
        }
        ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
                "Loading animation for app transition. transit=%s enter=%b frame=%s insets=%s "
                        + "surfaceInsets=%s",
                AppTransition.appTransitionToString(transit), enter, frame, insets, surfaceInsets);
        final Configuration displayConfig = displayContent.getConfiguration();
        final Animation a = getDisplayContent().mAppTransition.loadAnimation(lp, transit, enter,
                displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets,
                surfaceInsets, stableInsets, isVoiceInteraction, inFreeformWindowingMode(), this);
        if (a != null) {
            if (a != null) {
                // Setup the maximum app transition duration to prevent malicious app may set a long
                // animation duration or infinite repeat counts for the app transition through
                // ActivityOption#makeCustomAnimation or WindowManager#overridePendingTransition.
                a.restrictDuration(MAX_APP_TRANSITION_DURATION);
            }
            if (DEBUG_ANIM) {
                logWithStack(TAG, "Loaded animation " + a + " for " + this
                        + ", duration: " + ((a != null) ? a.getDuration() : 0));
            }
            final int containingWidth = frame.width();
            final int containingHeight = frame.height();
            a.initialize(containingWidth, containingHeight, width, height);
            a.scaleCurrentDuration(mWmService.getTransitionAnimationScaleLocked());
        }
        return a;
    }

    RemoteAnimationTarget createRemoteAnimationTarget(
            RemoteAnimationController.RemoteAnimationRecord record) {
        return null;
    }

    boolean canCreateRemoteAnimationTarget() {
        return false;
    }

    boolean okToDisplay() {
        final DisplayContent dc = getDisplayContent();
        return dc != null && dc.okToDisplay();
    }

    boolean okToAnimate() {
        return okToAnimate(false /* ignoreFrozen */);
    }

    boolean okToAnimate(boolean ignoreFrozen) {
        final DisplayContent dc = getDisplayContent();
        return dc != null && dc.okToAnimate(ignoreFrozen);
    }

    @Override
    public void commitPendingTransaction() {
        scheduleAnimation();
    }

    void reassignLayer(Transaction t) {
        final WindowContainer parent = getParent();
        if (parent != null) {
            parent.assignChildLayers(t);
        }
    }

    void resetSurfacePositionForAnimationLeash(Transaction t) {
        t.setPosition(mSurfaceControl, 0, 0);
        mLastSurfacePosition.set(0, 0);
    }

    @Override
    public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
        mLastLayer = -1;
        reassignLayer(t);

        // Leash is now responsible for position, so set our position to 0.
        resetSurfacePositionForAnimationLeash(t);
    }

    @Override
    public void onAnimationLeashLost(Transaction t) {
        mLastLayer = -1;
        mSurfaceFreezer.unfreeze(t);
        reassignLayer(t);
        updateSurfacePosition(t);
    }

    private void doAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
        for (int i = 0; i < mSurfaceAnimationSources.size(); ++i) {
            mSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim);
        }
        mSurfaceAnimationSources.clear();
    }

    /**
     * Called when an animation has finished running.
     */
    protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
        doAnimationFinished(type, anim);
        mWmService.onAnimationFinished();
        mNeedsZBoost = false;
    }

    /**
     * @return The currently running animation, if any, or {@code null} otherwise.
     */
    AnimationAdapter getAnimation() {
        return mSurfaceAnimator.getAnimation();
    }

    /**
     * @return The {@link WindowContainer} which is running an animation.
     *
     * By default this only checks if this container itself is actually running an animation, but
     * you can extend the check target over its relatives, or relax the condition so that this can
     * return {@code WindowContainer} if an animation starts soon by giving a combination
     * of {@link AnimationFlags}.
     *
     * Note that you can give a combination of bitmask flags to specify targets and condition for
     * checking animating status.
     * e.g. {@code isAnimating(TRANSITION | PARENT)} returns {@code true} if either this
     * container itself or one of its parents is running an animation or waiting for an app
     * transition.
     *
     * Note that TRANSITION propagates to parents and children as well.
     *
     * @param flags The combination of bitmask flags to specify targets and condition for
     *              checking animating status.
     * @param typesToCheck The combination of bitmask {@link AnimationType} to compare when
     *                     determining if animating.
     *
     * @see AnimationFlags#TRANSITION
     * @see AnimationFlags#PARENTS
     * @see AnimationFlags#CHILDREN
     */
    @Nullable
    WindowContainer getAnimatingContainer(int flags, int typesToCheck) {
        if (isSelfAnimating(flags, typesToCheck)) {
            return this;
        }
        if ((flags & PARENTS) != 0) {
            WindowContainer parent = getParent();
            while (parent != null) {
                if (parent.isSelfAnimating(flags, typesToCheck)) {
                    return parent;
                }
                parent = parent.getParent();
            }
        }
        if ((flags & CHILDREN) != 0) {
            for (int i = 0; i < mChildren.size(); ++i) {
                final WindowContainer wc = mChildren.get(i).getAnimatingContainer(
                        flags & ~PARENTS, typesToCheck);
                if (wc != null) {
                    return wc;
                }
            }
        }
        return null;
    }

    /**
     * Internal method only to be used during {@link #getAnimatingContainer(int, int)}.DO NOT CALL
     * FROM OUTSIDE.
     */
    protected boolean isSelfAnimating(int flags, int typesToCheck) {
        if (mSurfaceAnimator.isAnimating()
                && (mSurfaceAnimator.getAnimationType() & typesToCheck) > 0) {
            return true;
        }
        if ((flags & TRANSITION) != 0 && isWaitingForTransitionStart()) {
            return true;
        }
        return false;
    }

    /**
     * @deprecated Use {@link #getAnimatingContainer(int, int)} instead.
     */
    @Nullable
    @Deprecated
    final WindowContainer getAnimatingContainer() {
        return getAnimatingContainer(PARENTS, ANIMATION_TYPE_ALL);
    }

    /**
     * @see SurfaceAnimator#startDelayingAnimationStart
     */
    void startDelayingAnimationStart() {
        mSurfaceAnimator.startDelayingAnimationStart();
    }

    /**
     * @see SurfaceAnimator#endDelayingAnimationStart
     */
    void endDelayingAnimationStart() {
        mSurfaceAnimator.endDelayingAnimationStart();
    }

    @Override
    public int getSurfaceWidth() {
        return mSurfaceControl.getWidth();
    }

    @Override
    public int getSurfaceHeight() {
        return mSurfaceControl.getHeight();
    }

    @CallSuper
    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
        if (mSurfaceAnimator.isAnimating()) {
            pw.print(prefix); pw.println("ContainerAnimator:");
            mSurfaceAnimator.dump(pw, prefix + "  ");
        }
        if (mLastOrientationSource != null) {
            pw.println(prefix + "mLastOrientationSource=" + mLastOrientationSource);
        }
    }

    final void updateSurfacePosition() {
        updateSurfacePosition(getSyncTransaction());
    }

    void updateSurfacePosition(Transaction t) {
        // Avoid fighting with the organizer over Surface position.
        if (isOrganized()) return;

        if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) {
            return;
        }

        getRelativePosition(mTmpPos);
        if (mTmpPos.equals(mLastSurfacePosition)) {
            return;
        }

        t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
        mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);
    }

    @VisibleForTesting
    Point getLastSurfacePosition() {
        return mLastSurfacePosition;
    }

    /**
     * The {@code outFrame} retrieved by this method specifies where the animation will finish
     * the entrance animation, as the next frame will display the window at these coordinates. In
     * case of exit animation, this is where the animation will start, as the frame before the
     * animation is displaying the window at these bounds.
     *
     * @param outFrame The bounds where entrance animation finishes or exit animation starts.
     * @param outInsets Insets that are covered by system windows.
     * @param outStableInsets Insets that determine the area covered by the stable system windows.
     * @param outSurfaceInsets Positive insets between the drawing surface and window content.
     */
    void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
            Rect outSurfaceInsets) {
        final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
        outFrame.set(0, 0, displayInfo.appWidth, displayInfo.appHeight);
        outInsets.setEmpty();
        outStableInsets.setEmpty();
        outSurfaceInsets.setEmpty();
    }

    void getRelativePosition(Point outPos) {
        // In addition to updateSurfacePosition, we keep other code that sets
        // position from fighting with the organizer
        if (isOrganized()) {
            outPos.set(0, 0);
            return;
        }

        final Rect dispBounds = getBounds();
        outPos.set(dispBounds.left, dispBounds.top);
        final WindowContainer parent = getParent();
        if (parent != null) {
            final Rect parentBounds = parent.getBounds();
            outPos.offset(-parentBounds.left, -parentBounds.top);
        }
    }

    void waitForAllWindowsDrawn() {
        forAllWindows(w -> {
            w.requestDrawIfNeeded(mWaitingForDrawn);
        }, true /* traverseTopToBottom */);
    }

    Dimmer getDimmer() {
        if (mParent == null) {
            return null;
        }
        return mParent.getDimmer();
    }

    void setSurfaceControl(SurfaceControl sc) {
        mSurfaceControl = sc;
    }

    RemoteAnimationDefinition getRemoteAnimationDefinition() {
        return null;
    }

    /** Cheap way of doing cast and instanceof. */
    Task asTask() {
        return null;
    }

    /** Cheap way of doing cast and instanceof. */
    ActivityRecord asActivityRecord() {
        return null;
    }

    /**
     * @return {@code true} if window container is manage by a
     *          {@link android.window.WindowOrganizer}
     */
    boolean isOrganized() {
        return false;
    }

    static WindowContainer fromBinder(IBinder binder) {
        return RemoteToken.fromBinder(binder).getContainer();
    }

    static class RemoteToken extends IWindowContainerToken.Stub {

        final WeakReference<WindowContainer> mWeakRef;
        private WindowContainerToken mWindowContainerToken;

        RemoteToken(WindowContainer container) {
            mWeakRef = new WeakReference<>(container);
        }

        WindowContainer getContainer() {
            return mWeakRef.get();
        }

        static RemoteToken fromBinder(IBinder binder) {
            return (RemoteToken) binder;
        }

        WindowContainerToken toWindowContainerToken() {
            if (mWindowContainerToken == null) {
                mWindowContainerToken = new WindowContainerToken(this);
            }
            return mWindowContainerToken;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder(128);
            sb.append("RemoteToken{");
            sb.append(Integer.toHexString(System.identityHashCode(this)));
            sb.append(' ');
            sb.append(mWeakRef.get());
            sb.append('}');
            return sb.toString();
        }
    }

    @Override
    public void onTransactionReady(int mSyncId, Set<WindowContainer> windowContainersReady) {
        if (mWaitingListener == null) {
            return;
        }

        windowContainersReady.add(this);
        mWaitingListener.onTransactionReady(mWaitingSyncId, windowContainersReady);

        mWaitingListener = null;
        mWaitingSyncId = -1;
    }

    /**
     * Returns true if any of the children elected to participate in the Sync
     */
    boolean addChildrenToSyncSet(int localId) {
        boolean willSync = false;

        for (int i = 0; i < mChildren.size(); i++) {
            final WindowContainer child = mChildren.get(i);
            willSync |= mBLASTSyncEngine.addToSyncSet(localId, child);
        }
        return willSync;
    }

    boolean setPendingListener(BLASTSyncEngine.TransactionReadyListener waitingListener,
        int waitingId) {
        // If we are invisible, no need to sync, likewise if we are already engaged in a sync,
        // we can't support overlapping syncs on a single container yet.
        if (!isVisible() || mWaitingListener != null) {
            return false;
        }
        mUsingBLASTSyncTransaction = true;

        // Make sure to set these before we call setReady in case the sync was a no-op
        mWaitingSyncId = waitingId;
        mWaitingListener = waitingListener;
        return true;
    }

    boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener,
            int waitingId) {
        boolean willSync = setPendingListener(waitingListener, waitingId);
        if (!willSync) {
            return false;
        }

        int localId = mBLASTSyncEngine.startSyncSet(this);
        willSync |= addChildrenToSyncSet(localId);
        mBLASTSyncEngine.setReady(localId);

        return willSync;
    }

    boolean useBLASTSync() {
        return mUsingBLASTSyncTransaction;
    }

    void mergeBlastSyncTransaction(Transaction t) {
        t.merge(mBLASTSyncTransaction);
        mUsingBLASTSyncTransaction = false;
    }
}
