/*
 * Copyright 2022 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.
 */

// #define LOG_NDEBUG 0
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#undef LOG_TAG
#define LOG_TAG "SurfaceFlinger"

#include <numeric>
#include <optional>

#include <common/FlagManager.h>
#include <ftl/small_map.h>
#include <gui/TraceUtils.h>
#include <ui/DisplayMap.h>
#include <ui/FloatRect.h>

#include "DisplayHardware/HWC2.h"
#include "DisplayHardware/Hal.h"
#include "Layer.h" // eFrameRateSelectionPriority constants
#include "LayerLog.h"
#include "LayerSnapshotBuilder.h"
#include "TimeStats/TimeStats.h"
#include "Tracing/TransactionTracing.h"

namespace android::surfaceflinger::frontend {

using namespace ftl::flag_operators;

namespace {

FloatRect getMaxDisplayBounds(const DisplayInfos& displays) {
    const ui::Size maxSize = [&displays] {
        if (displays.empty()) return ui::Size{5000, 5000};

        return std::accumulate(displays.begin(), displays.end(), ui::kEmptySize,
                               [](ui::Size size, const auto& pair) -> ui::Size {
                                   const auto& display = pair.second;
                                   return {std::max(size.getWidth(), display.info.logicalWidth),
                                           std::max(size.getHeight(), display.info.logicalHeight)};
                               });
    }();

    // Ignore display bounds for now since they will be computed later. Use a large Rect bound
    // to ensure it's bigger than an actual display will be.
    const float xMax = static_cast<float>(maxSize.getWidth()) * 10.f;
    const float yMax = static_cast<float>(maxSize.getHeight()) * 10.f;

    return {-xMax, -yMax, xMax, yMax};
}

// Applies the given transform to the region, while protecting against overflows caused by any
// offsets. If applying the offset in the transform to any of the Rects in the region would result
// in an overflow, they are not added to the output Region.
Region transformTouchableRegionSafely(const ui::Transform& t, const Region& r,
                                      const std::string& debugWindowName) {
    // Round the translation using the same rounding strategy used by ui::Transform.
    const auto tx = static_cast<int32_t>(t.tx() + 0.5);
    const auto ty = static_cast<int32_t>(t.ty() + 0.5);

    ui::Transform transformWithoutOffset = t;
    transformWithoutOffset.set(0.f, 0.f);

    const Region transformed = transformWithoutOffset.transform(r);

    // Apply the translation to each of the Rects in the region while discarding any that overflow.
    Region ret;
    for (const auto& rect : transformed) {
        Rect newRect;
        if (__builtin_add_overflow(rect.left, tx, &newRect.left) ||
            __builtin_add_overflow(rect.top, ty, &newRect.top) ||
            __builtin_add_overflow(rect.right, tx, &newRect.right) ||
            __builtin_add_overflow(rect.bottom, ty, &newRect.bottom)) {
            ALOGE("Applying transform to touchable region of window '%s' resulted in an overflow.",
                  debugWindowName.c_str());
            continue;
        }
        ret.orSelf(newRect);
    }
    return ret;
}

/*
 * We don't want to send the layer's transform to input, but rather the
 * parent's transform. This is because Layer's transform is
 * information about how the buffer is placed on screen. The parent's
 * transform makes more sense to send since it's information about how the
 * layer is placed on screen. This transform is used by input to determine
 * how to go from screen space back to window space.
 */
ui::Transform getInputTransform(const LayerSnapshot& snapshot) {
    if (!snapshot.hasBufferOrSidebandStream()) {
        return snapshot.geomLayerTransform;
    }
    return snapshot.parentTransform;
}

/**
 * Returns the bounds used to fill the input frame and the touchable region.
 *
 * Similar to getInputTransform, we need to update the bounds to include the transform.
 * This is because bounds don't include the buffer transform, where the input assumes
 * that's already included.
 */
std::pair<FloatRect, bool> getInputBounds(const LayerSnapshot& snapshot, bool fillParentBounds) {
    FloatRect inputBounds = snapshot.croppedBufferSize.toFloatRect();
    if (snapshot.hasBufferOrSidebandStream() && snapshot.croppedBufferSize.isValid() &&
        snapshot.localTransform.getType() != ui::Transform::IDENTITY) {
        inputBounds = snapshot.localTransform.transform(inputBounds);
    }

    bool inputBoundsValid = snapshot.croppedBufferSize.isValid();
    if (!inputBoundsValid) {
        /**
         * Input bounds are based on the layer crop or buffer size. But if we are using
         * the layer bounds as the input bounds (replaceTouchableRegionWithCrop flag) then
         * we can use the parent bounds as the input bounds if the layer does not have buffer
         * or a crop. We want to unify this logic but because of compat reasons we cannot always
         * use the parent bounds. A layer without a buffer can get input. So when a window is
         * initially added, its touchable region can fill its parent layer bounds and that can
         * have negative consequences.
         */
        inputBounds = fillParentBounds ? snapshot.geomLayerBounds : FloatRect{};
    }

    // Clamp surface inset to the input bounds.
    const float inset = static_cast<float>(snapshot.inputInfo.surfaceInset);
    const float xSurfaceInset = std::clamp(inset, 0.f, inputBounds.getWidth() / 2.f);
    const float ySurfaceInset = std::clamp(inset, 0.f, inputBounds.getHeight() / 2.f);

    // Apply the insets to the input bounds.
    inputBounds.left += xSurfaceInset;
    inputBounds.top += ySurfaceInset;
    inputBounds.right -= xSurfaceInset;
    inputBounds.bottom -= ySurfaceInset;
    return {inputBounds, inputBoundsValid};
}

Rect getInputBoundsInDisplaySpace(const LayerSnapshot& snapshot, const FloatRect& insetBounds,
                                  const ui::Transform& screenToDisplay) {
    // InputDispatcher works in the display device's coordinate space. Here, we calculate the
    // frame and transform used for the layer, which determines the bounds and the coordinate space
    // within which the layer will receive input.

    // Coordinate space definitions:
    //   - display: The display device's coordinate space. Correlates to pixels on the display.
    //   - screen: The post-rotation coordinate space for the display, a.k.a. logical display space.
    //   - layer: The coordinate space of this layer.
    //   - input: The coordinate space in which this layer will receive input events. This could be
    //            different than layer space if a surfaceInset is used, which changes the origin
    //            of the input space.

    // Crop the input bounds to ensure it is within the parent's bounds.
    const FloatRect croppedInsetBoundsInLayer = snapshot.geomLayerBounds.intersect(insetBounds);

    const ui::Transform layerToScreen = getInputTransform(snapshot);
    const ui::Transform layerToDisplay = screenToDisplay * layerToScreen;

    return Rect{layerToDisplay.transform(croppedInsetBoundsInLayer)};
}

void fillInputFrameInfo(gui::WindowInfo& info, const ui::Transform& screenToDisplay,
                        const LayerSnapshot& snapshot) {
    auto [inputBounds, inputBoundsValid] = getInputBounds(snapshot, /*fillParentBounds=*/false);
    if (!inputBoundsValid) {
        info.touchableRegion.clear();
    }

    info.frame = getInputBoundsInDisplaySpace(snapshot, inputBounds, screenToDisplay);

    ui::Transform inputToLayer;
    inputToLayer.set(inputBounds.left, inputBounds.top);
    const ui::Transform layerToScreen = getInputTransform(snapshot);
    const ui::Transform inputToDisplay = screenToDisplay * layerToScreen * inputToLayer;

    // InputDispatcher expects a display-to-input transform.
    info.transform = inputToDisplay.inverse();

    // The touchable region is specified in the input coordinate space. Change it to display space.
    info.touchableRegion =
            transformTouchableRegionSafely(inputToDisplay, info.touchableRegion, snapshot.name);
}

void handleDropInputMode(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot) {
    if (snapshot.inputInfo.inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
        return;
    }

    // Check if we need to drop input unconditionally
    const gui::DropInputMode dropInputMode = snapshot.dropInputMode;
    if (dropInputMode == gui::DropInputMode::ALL) {
        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
        ALOGV("Dropping input for %s as requested by policy.", snapshot.name.c_str());
        return;
    }

    // Check if we need to check if the window is obscured by parent
    if (dropInputMode != gui::DropInputMode::OBSCURED) {
        return;
    }

    // Check if the parent has set an alpha on the layer
    if (parentSnapshot.color.a != 1.0_hf) {
        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
        ALOGV("Dropping input for %s as requested by policy because alpha=%f",
              snapshot.name.c_str(), static_cast<float>(parentSnapshot.color.a));
    }

    // Check if the parent has cropped the buffer
    Rect bufferSize = snapshot.croppedBufferSize;
    if (!bufferSize.isValid()) {
        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
        return;
    }

    // Screenbounds are the layer bounds cropped by parents, transformed to screenspace.
    // To check if the layer has been cropped, we take the buffer bounds, apply the local
    // layer crop and apply the same set of transforms to move to screenspace. If the bounds
    // match then the layer has not been cropped by its parents.
    Rect bufferInScreenSpace(snapshot.geomLayerTransform.transform(bufferSize));
    bool croppedByParent = bufferInScreenSpace != Rect{snapshot.transformedBounds};

    if (croppedByParent) {
        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
        ALOGV("Dropping input for %s as requested by policy because buffer is cropped by parent",
              snapshot.name.c_str());
    } else {
        // If the layer is not obscured by its parents (by setting an alpha or crop), then only drop
        // input if the window is obscured. This check should be done in surfaceflinger but the
        // logic currently resides in inputflinger. So pass the if_obscured check to input to only
        // drop input events if the window is obscured.
        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
    }
}

auto getBlendMode(const LayerSnapshot& snapshot, const RequestedLayerState& requested) {
    auto blendMode = Hwc2::IComposerClient::BlendMode::NONE;
    if (snapshot.alpha != 1.0f || !snapshot.isContentOpaque()) {
        blendMode = requested.premultipliedAlpha ? Hwc2::IComposerClient::BlendMode::PREMULTIPLIED
                                                 : Hwc2::IComposerClient::BlendMode::COVERAGE;
    }
    return blendMode;
}

void updateVisibility(LayerSnapshot& snapshot, bool visible) {
    if (snapshot.isVisible != visible) {
        snapshot.changes |= RequestedLayerState::Changes::Visibility;
    }
    snapshot.isVisible = visible;

    // TODO(b/238781169) we are ignoring this compat for now, since we will have
    // to remove any optimization based on visibility.

    // For compatibility reasons we let layers which can receive input
    // receive input before they have actually submitted a buffer. Because
    // of this we use canReceiveInput instead of isVisible to check the
    // policy-visibility, ignoring the buffer state. However for layers with
    // hasInputInfo()==false we can use the real visibility state.
    // We are just using these layers for occlusion detection in
    // InputDispatcher, and obviously if they aren't visible they can't occlude
    // anything.
    const bool visibleForInput =
            snapshot.hasInputInfo() ? snapshot.canReceiveInput() : snapshot.isVisible;
    snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visibleForInput);
    LLOGV(snapshot.sequence, "updating visibility %s %s", visible ? "true" : "false",
          snapshot.getDebugString().c_str());
}

bool needsInputInfo(const LayerSnapshot& snapshot, const RequestedLayerState& requested) {
    if (requested.potentialCursor) {
        return false;
    }

    if (snapshot.inputInfo.token != nullptr) {
        return true;
    }

    if (snapshot.hasBufferOrSidebandStream()) {
        return true;
    }

    return requested.windowInfoHandle &&
            requested.windowInfoHandle->getInfo()->inputConfig.test(
                    gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL);
}

void updateMetadata(LayerSnapshot& snapshot, const RequestedLayerState& requested,
                    const LayerSnapshotBuilder::Args& args) {
    snapshot.metadata.clear();
    for (const auto& [key, mandatory] : args.supportedLayerGenericMetadata) {
        auto compatIter = args.genericLayerMetadataKeyMap.find(key);
        if (compatIter == std::end(args.genericLayerMetadataKeyMap)) {
            continue;
        }
        const uint32_t id = compatIter->second;
        auto it = requested.metadata.mMap.find(id);
        if (it == std::end(requested.metadata.mMap)) {
            continue;
        }

        snapshot.metadata.emplace(key,
                                  compositionengine::GenericLayerMetadataEntry{mandatory,
                                                                               it->second});
    }
}

void clearChanges(LayerSnapshot& snapshot) {
    snapshot.changes.clear();
    snapshot.clientChanges = 0;
    snapshot.contentDirty = false;
    snapshot.hasReadyFrame = false;
    snapshot.sidebandStreamHasFrame = false;
    snapshot.surfaceDamage.clear();
}

// TODO (b/259407931): Remove.
uint32_t getPrimaryDisplayRotationFlags(
        const ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays) {
    for (auto& [_, display] : displays) {
        if (display.isPrimary) {
            return display.rotationFlags;
        }
    }
    return 0;
}

} // namespace

LayerSnapshot LayerSnapshotBuilder::getRootSnapshot() {
    LayerSnapshot snapshot;
    snapshot.path = LayerHierarchy::TraversalPath::ROOT;
    snapshot.changes = ftl::Flags<RequestedLayerState::Changes>();
    snapshot.clientChanges = 0;
    snapshot.isHiddenByPolicyFromParent = false;
    snapshot.isHiddenByPolicyFromRelativeParent = false;
    snapshot.parentTransform.reset();
    snapshot.geomLayerTransform.reset();
    snapshot.geomInverseLayerTransform.reset();
    snapshot.geomLayerBounds = getMaxDisplayBounds({});
    snapshot.roundedCorner = RoundedCornerState();
    snapshot.stretchEffect = {};
    snapshot.outputFilter.layerStack = ui::DEFAULT_LAYER_STACK;
    snapshot.outputFilter.toInternalDisplay = false;
    snapshot.isSecure = false;
    snapshot.color.a = 1.0_hf;
    snapshot.colorTransformIsIdentity = true;
    snapshot.shadowSettings.length = 0.f;
    snapshot.layerMetadata.mMap.clear();
    snapshot.relativeLayerMetadata.mMap.clear();
    snapshot.inputInfo.touchOcclusionMode = gui::TouchOcclusionMode::BLOCK_UNTRUSTED;
    snapshot.dropInputMode = gui::DropInputMode::NONE;
    snapshot.trustedOverlay = gui::TrustedOverlay::UNSET;
    snapshot.gameMode = gui::GameMode::Unsupported;
    snapshot.frameRate = {};
    snapshot.fixedTransformHint = ui::Transform::ROT_INVALID;
    snapshot.ignoreLocalTransform = false;
    return snapshot;
}

LayerSnapshotBuilder::LayerSnapshotBuilder() {}

LayerSnapshotBuilder::LayerSnapshotBuilder(Args args) : LayerSnapshotBuilder() {
    args.forceUpdate = ForceUpdateFlags::ALL;
    updateSnapshots(args);
}

bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) {
    const bool forceUpdate = args.forceUpdate != ForceUpdateFlags::NONE;

    if (args.layerLifecycleManager.getGlobalChanges().get() == 0 && !forceUpdate &&
        !args.displayChanges) {
        return true;
    }

    // There are only content changes which do not require any child layer snapshots to be updated.
    ALOGV("%s", __func__);
    ATRACE_NAME("FastPath");

    uint32_t primaryDisplayRotationFlags = getPrimaryDisplayRotationFlags(args.displays);
    if (forceUpdate || args.displayChanges) {
        for (auto& snapshot : mSnapshots) {
            const RequestedLayerState* requested =
                    args.layerLifecycleManager.getLayerFromId(snapshot->path.id);
            if (!requested) continue;
            snapshot->merge(*requested, forceUpdate, args.displayChanges, args.forceFullDamage,
                            primaryDisplayRotationFlags);
        }
        return false;
    }

    // Walk through all the updated requested layer states and update the corresponding snapshots.
    for (const RequestedLayerState* requested : args.layerLifecycleManager.getChangedLayers()) {
        auto range = mIdToSnapshots.equal_range(requested->id);
        for (auto it = range.first; it != range.second; it++) {
            it->second->merge(*requested, forceUpdate, args.displayChanges, args.forceFullDamage,
                              primaryDisplayRotationFlags);
        }
    }

    if ((args.layerLifecycleManager.getGlobalChanges().get() &
         ~(RequestedLayerState::Changes::Content | RequestedLayerState::Changes::Buffer).get()) !=
        0) {
        // We have changes that require us to walk the hierarchy and update child layers.
        // No fast path for you.
        return false;
    }
    return true;
}

void LayerSnapshotBuilder::updateSnapshots(const Args& args) {
    ATRACE_NAME("UpdateSnapshots");
    LayerSnapshot rootSnapshot = args.rootSnapshot;
    if (args.parentCrop) {
        rootSnapshot.geomLayerBounds = *args.parentCrop;
    } else if (args.forceUpdate == ForceUpdateFlags::ALL || args.displayChanges) {
        rootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays);
    }
    if (args.displayChanges) {
        rootSnapshot.changes = RequestedLayerState::Changes::AffectsChildren |
                RequestedLayerState::Changes::Geometry;
    }
    if (args.forceUpdate == ForceUpdateFlags::HIERARCHY) {
        rootSnapshot.changes |=
                RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Visibility;
        rootSnapshot.clientChanges |= layer_state_t::eReparent;
    }

    for (auto& snapshot : mSnapshots) {
        if (snapshot->reachablilty == LayerSnapshot::Reachablilty::Reachable) {
            snapshot->reachablilty = LayerSnapshot::Reachablilty::Unreachable;
        }
    }

    LayerHierarchy::TraversalPath root = LayerHierarchy::TraversalPath::ROOT;
    if (args.root.getLayer()) {
        // The hierarchy can have a root layer when used for screenshots otherwise, it will have
        // multiple children.
        LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root, args.root.getLayer()->id,
                                                                LayerHierarchy::Variant::Attached);
        updateSnapshotsInHierarchy(args, args.root, root, rootSnapshot, /*depth=*/0);
    } else {
        for (auto& [childHierarchy, variant] : args.root.mChildren) {
            LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root,
                                                                    childHierarchy->getLayer()->id,
                                                                    variant);
            updateSnapshotsInHierarchy(args, *childHierarchy, root, rootSnapshot, /*depth=*/0);
        }
    }

    // Update touchable region crops outside the main update pass. This is because a layer could be
    // cropped by any other layer and it requires both snapshots to be updated.
    updateTouchableRegionCrop(args);

    const bool hasUnreachableSnapshots = sortSnapshotsByZ(args);

    // Destroy unreachable snapshots for clone layers. And destroy snapshots for non-clone
    // layers if the layer have been destroyed.
    // TODO(b/238781169) consider making clone layer ids stable as well
    if (!hasUnreachableSnapshots && args.layerLifecycleManager.getDestroyedLayers().empty()) {
        return;
    }

    std::unordered_set<uint32_t> destroyedLayerIds;
    for (auto& destroyedLayer : args.layerLifecycleManager.getDestroyedLayers()) {
        destroyedLayerIds.insert(destroyedLayer->id);
    }

    auto it = mSnapshots.begin();
    while (it < mSnapshots.end()) {
        auto& traversalPath = it->get()->path;
        const bool unreachable =
                it->get()->reachablilty == LayerSnapshot::Reachablilty::Unreachable;
        const bool isClone = traversalPath.isClone();
        const bool layerIsDestroyed =
                destroyedLayerIds.find(traversalPath.id) != destroyedLayerIds.end();
        const bool destroySnapshot = (unreachable && isClone) || layerIsDestroyed;

        if (!destroySnapshot) {
            it++;
            continue;
        }

        mPathToSnapshot.erase(traversalPath);

        auto range = mIdToSnapshots.equal_range(traversalPath.id);
        auto matchingSnapshot =
                std::find_if(range.first, range.second, [&traversalPath](auto& snapshotWithId) {
                    return snapshotWithId.second->path == traversalPath;
                });
        mIdToSnapshots.erase(matchingSnapshot);
        mNeedsTouchableRegionCrop.erase(traversalPath);
        mSnapshots.back()->globalZ = it->get()->globalZ;
        std::iter_swap(it, mSnapshots.end() - 1);
        mSnapshots.erase(mSnapshots.end() - 1);
    }
}

void LayerSnapshotBuilder::update(const Args& args) {
    for (auto& snapshot : mSnapshots) {
        clearChanges(*snapshot);
    }

    if (tryFastUpdate(args)) {
        return;
    }
    updateSnapshots(args);
}

const LayerSnapshot& LayerSnapshotBuilder::updateSnapshotsInHierarchy(
        const Args& args, const LayerHierarchy& hierarchy,
        LayerHierarchy::TraversalPath& traversalPath, const LayerSnapshot& parentSnapshot,
        int depth) {
    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(depth > 50,
                                    "Cycle detected in LayerSnapshotBuilder. See "
                                    "builder_stack_overflow_transactions.winscope");

    const RequestedLayerState* layer = hierarchy.getLayer();
    LayerSnapshot* snapshot = getSnapshot(traversalPath);
    const bool newSnapshot = snapshot == nullptr;
    uint32_t primaryDisplayRotationFlags = getPrimaryDisplayRotationFlags(args.displays);
    if (newSnapshot) {
        snapshot = createSnapshot(traversalPath, *layer, parentSnapshot);
        snapshot->merge(*layer, /*forceUpdate=*/true, /*displayChanges=*/true, args.forceFullDamage,
                        primaryDisplayRotationFlags);
        snapshot->changes |= RequestedLayerState::Changes::Created;
    }

    if (traversalPath.isRelative()) {
        bool parentIsRelative = traversalPath.variant == LayerHierarchy::Variant::Relative;
        updateRelativeState(*snapshot, parentSnapshot, parentIsRelative, args);
    } else {
        if (traversalPath.isAttached()) {
            resetRelativeState(*snapshot);
        }
        updateSnapshot(*snapshot, args, *layer, parentSnapshot, traversalPath);
    }

    for (auto& [childHierarchy, variant] : hierarchy.mChildren) {
        LayerHierarchy::ScopedAddToTraversalPath addChildToPath(traversalPath,
                                                                childHierarchy->getLayer()->id,
                                                                variant);
        const LayerSnapshot& childSnapshot =
                updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot,
                                           depth + 1);
        updateFrameRateFromChildSnapshot(*snapshot, childSnapshot, args);
    }

    return *snapshot;
}

LayerSnapshot* LayerSnapshotBuilder::getSnapshot(uint32_t layerId) const {
    if (layerId == UNASSIGNED_LAYER_ID) {
        return nullptr;
    }
    LayerHierarchy::TraversalPath path{.id = layerId};
    return getSnapshot(path);
}

LayerSnapshot* LayerSnapshotBuilder::getSnapshot(const LayerHierarchy::TraversalPath& id) const {
    auto it = mPathToSnapshot.find(id);
    return it == mPathToSnapshot.end() ? nullptr : it->second;
}

LayerSnapshot* LayerSnapshotBuilder::createSnapshot(const LayerHierarchy::TraversalPath& path,
                                                    const RequestedLayerState& layer,
                                                    const LayerSnapshot& parentSnapshot) {
    mSnapshots.emplace_back(std::make_unique<LayerSnapshot>(layer, path));
    LayerSnapshot* snapshot = mSnapshots.back().get();
    snapshot->globalZ = static_cast<size_t>(mSnapshots.size()) - 1;
    if (path.isClone() && !LayerHierarchy::isMirror(path.variant)) {
        snapshot->mirrorRootPath = parentSnapshot.mirrorRootPath;
    }
    snapshot->ignoreLocalTransform =
            path.isClone() && path.variant == LayerHierarchy::Variant::Detached_Mirror;
    mPathToSnapshot[path] = snapshot;

    mIdToSnapshots.emplace(path.id, snapshot);
    return snapshot;
}

bool LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) {
    if (!mResortSnapshots && args.forceUpdate == ForceUpdateFlags::NONE &&
        !args.layerLifecycleManager.getGlobalChanges().any(
                RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Visibility |
                RequestedLayerState::Changes::Input)) {
        // We are not force updating and there are no hierarchy or visibility changes. Avoid sorting
        // the snapshots.
        return false;
    }
    mResortSnapshots = false;

    size_t globalZ = 0;
    args.root.traverseInZOrder(
            [this, &globalZ](const LayerHierarchy&,
                             const LayerHierarchy::TraversalPath& traversalPath) -> bool {
                LayerSnapshot* snapshot = getSnapshot(traversalPath);
                if (!snapshot) {
                    return true;
                }

                if (snapshot->getIsVisible() || snapshot->hasInputInfo()) {
                    updateVisibility(*snapshot, snapshot->getIsVisible());
                    size_t oldZ = snapshot->globalZ;
                    size_t newZ = globalZ++;
                    snapshot->globalZ = newZ;
                    if (oldZ == newZ) {
                        return true;
                    }
                    mSnapshots[newZ]->globalZ = oldZ;
                    LLOGV(snapshot->sequence, "Made visible z=%zu -> %zu %s", oldZ, newZ,
                          snapshot->getDebugString().c_str());
                    std::iter_swap(mSnapshots.begin() + static_cast<ssize_t>(oldZ),
                                   mSnapshots.begin() + static_cast<ssize_t>(newZ));
                }
                return true;
            });
    mNumInterestingSnapshots = (int)globalZ;
    bool hasUnreachableSnapshots = false;
    while (globalZ < mSnapshots.size()) {
        mSnapshots[globalZ]->globalZ = globalZ;
        /* mark unreachable snapshots as explicitly invisible */
        updateVisibility(*mSnapshots[globalZ], false);
        if (mSnapshots[globalZ]->reachablilty == LayerSnapshot::Reachablilty::Unreachable) {
            hasUnreachableSnapshots = true;
        }
        globalZ++;
    }
    return hasUnreachableSnapshots;
}

void LayerSnapshotBuilder::updateRelativeState(LayerSnapshot& snapshot,
                                               const LayerSnapshot& parentSnapshot,
                                               bool parentIsRelative, const Args& args) {
    if (parentIsRelative) {
        snapshot.isHiddenByPolicyFromRelativeParent =
                parentSnapshot.isHiddenByPolicyFromParent || parentSnapshot.invalidTransform;
        if (args.includeMetadata) {
            snapshot.relativeLayerMetadata = parentSnapshot.layerMetadata;
        }
    } else {
        snapshot.isHiddenByPolicyFromRelativeParent =
                parentSnapshot.isHiddenByPolicyFromRelativeParent;
        if (args.includeMetadata) {
            snapshot.relativeLayerMetadata = parentSnapshot.relativeLayerMetadata;
        }
    }
    if (snapshot.reachablilty == LayerSnapshot::Reachablilty::Unreachable) {
        snapshot.reachablilty = LayerSnapshot::Reachablilty::ReachableByRelativeParent;
    }
}

void LayerSnapshotBuilder::updateFrameRateFromChildSnapshot(LayerSnapshot& snapshot,
                                                            const LayerSnapshot& childSnapshot,
                                                            const Args& args) {
    if (args.forceUpdate == ForceUpdateFlags::NONE &&
        !args.layerLifecycleManager.getGlobalChanges().any(
                RequestedLayerState::Changes::Hierarchy) &&
        !childSnapshot.changes.any(RequestedLayerState::Changes::FrameRate) &&
        !snapshot.changes.any(RequestedLayerState::Changes::FrameRate)) {
        return;
    }

    using FrameRateCompatibility = scheduler::FrameRateCompatibility;
    if (snapshot.frameRate.isValid()) {
        // we already have a valid framerate.
        return;
    }

    // We return whether this layer or its children has a vote. We ignore ExactOrMultiple votes
    // for the same reason we are allowing touch boost for those layers. See
    // RefreshRateSelector::rankFrameRates for details.
    const auto layerVotedWithDefaultCompatibility = childSnapshot.frameRate.vote.rate.isValid() &&
            childSnapshot.frameRate.vote.type == FrameRateCompatibility::Default;
    const auto layerVotedWithNoVote =
            childSnapshot.frameRate.vote.type == FrameRateCompatibility::NoVote;
    const auto layerVotedWithCategory =
            childSnapshot.frameRate.category != FrameRateCategory::Default;
    const auto layerVotedWithExactCompatibility = childSnapshot.frameRate.vote.rate.isValid() &&
            childSnapshot.frameRate.vote.type == FrameRateCompatibility::Exact;

    bool childHasValidFrameRate = layerVotedWithDefaultCompatibility || layerVotedWithNoVote ||
            layerVotedWithCategory || layerVotedWithExactCompatibility;

    // If we don't have a valid frame rate, but the children do, we set this
    // layer as NoVote to allow the children to control the refresh rate
    if (childHasValidFrameRate) {
        snapshot.frameRate = scheduler::LayerInfo::FrameRate(Fps(), FrameRateCompatibility::NoVote);
        snapshot.changes |= RequestedLayerState::Changes::FrameRate;
    }
}

void LayerSnapshotBuilder::resetRelativeState(LayerSnapshot& snapshot) {
    snapshot.isHiddenByPolicyFromRelativeParent = false;
    snapshot.relativeLayerMetadata.mMap.clear();
}

void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& args,
                                          const RequestedLayerState& requested,
                                          const LayerSnapshot& parentSnapshot,
                                          const LayerHierarchy::TraversalPath& path) {
    // Always update flags and visibility
    ftl::Flags<RequestedLayerState::Changes> parentChanges = parentSnapshot.changes &
            (RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry |
             RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Metadata |
             RequestedLayerState::Changes::AffectsChildren | RequestedLayerState::Changes::Input |
             RequestedLayerState::Changes::FrameRate | RequestedLayerState::Changes::GameMode);
    snapshot.changes |= parentChanges;
    if (args.displayChanges) snapshot.changes |= RequestedLayerState::Changes::Geometry;
    snapshot.reachablilty = LayerSnapshot::Reachablilty::Reachable;
    snapshot.clientChanges |= (parentSnapshot.clientChanges & layer_state_t::AFFECTS_CHILDREN);
    snapshot.isHiddenByPolicyFromParent = parentSnapshot.isHiddenByPolicyFromParent ||
            parentSnapshot.invalidTransform || requested.isHiddenByPolicy() ||
            (args.excludeLayerIds.find(path.id) != args.excludeLayerIds.end());

    const bool forceUpdate = args.forceUpdate == ForceUpdateFlags::ALL ||
            snapshot.clientChanges & layer_state_t::eReparent ||
            snapshot.changes.any(RequestedLayerState::Changes::Visibility |
                                 RequestedLayerState::Changes::Created);

    if (forceUpdate || snapshot.clientChanges & layer_state_t::eLayerStackChanged) {
        // If root layer, use the layer stack otherwise get the parent's layer stack.
        snapshot.outputFilter.layerStack =
                parentSnapshot.path == LayerHierarchy::TraversalPath::ROOT
                ? requested.layerStack
                : parentSnapshot.outputFilter.layerStack;
    }

    if (forceUpdate || snapshot.clientChanges & layer_state_t::eTrustedOverlayChanged) {
        switch (requested.trustedOverlay) {
            case gui::TrustedOverlay::UNSET:
                snapshot.trustedOverlay = parentSnapshot.trustedOverlay;
                break;
            case gui::TrustedOverlay::DISABLED:
                snapshot.trustedOverlay = FlagManager::getInstance().override_trusted_overlay()
                        ? requested.trustedOverlay
                        : parentSnapshot.trustedOverlay;
                break;
            case gui::TrustedOverlay::ENABLED:
                snapshot.trustedOverlay = requested.trustedOverlay;
                break;
        }
    }

    if (snapshot.isHiddenByPolicyFromParent &&
        !snapshot.changes.test(RequestedLayerState::Changes::Created)) {
        if (forceUpdate ||
            snapshot.changes.any(RequestedLayerState::Changes::Geometry |
                                 RequestedLayerState::Changes::BufferSize |
                                 RequestedLayerState::Changes::Input)) {
            updateInput(snapshot, requested, parentSnapshot, path, args);
        }
        return;
    }

    if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Mirror)) {
        // Display mirrors are always placed in a VirtualDisplay so we never want to capture layers
        // marked as skip capture
        snapshot.handleSkipScreenshotFlag = parentSnapshot.handleSkipScreenshotFlag ||
                (requested.layerStackToMirror != ui::INVALID_LAYER_STACK);
    }

    if (forceUpdate || snapshot.clientChanges & layer_state_t::eAlphaChanged) {
        snapshot.color.a = parentSnapshot.color.a * requested.color.a;
        snapshot.alpha = snapshot.color.a;
        snapshot.inputInfo.alpha = snapshot.color.a;
    }

    if (forceUpdate || snapshot.clientChanges & layer_state_t::eFlagsChanged) {
        snapshot.isSecure =
                parentSnapshot.isSecure || (requested.flags & layer_state_t::eLayerSecure);
        snapshot.outputFilter.toInternalDisplay = parentSnapshot.outputFilter.toInternalDisplay ||
                (requested.flags & layer_state_t::eLayerSkipScreenshot);
    }

    if (forceUpdate || snapshot.clientChanges & layer_state_t::eStretchChanged) {
        snapshot.stretchEffect = (requested.stretchEffect.hasEffect())
                ? requested.stretchEffect
                : parentSnapshot.stretchEffect;
    }

    if (forceUpdate || snapshot.clientChanges & layer_state_t::eColorTransformChanged) {
        if (!parentSnapshot.colorTransformIsIdentity) {
            snapshot.colorTransform = parentSnapshot.colorTransform * requested.colorTransform;
            snapshot.colorTransformIsIdentity = false;
        } else {
            snapshot.colorTransform = requested.colorTransform;
            snapshot.colorTransformIsIdentity = !requested.hasColorTransform;
        }
    }

    if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::GameMode)) {
        snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE)
                ? requested.gameMode
                : parentSnapshot.gameMode;
        updateMetadata(snapshot, requested, args);
        if (args.includeMetadata) {
            snapshot.layerMetadata = parentSnapshot.layerMetadata;
            snapshot.layerMetadata.merge(requested.metadata);
        }
    }

    if (forceUpdate || snapshot.clientChanges & layer_state_t::eFixedTransformHintChanged ||
        args.displayChanges) {
        snapshot.fixedTransformHint = requested.fixedTransformHint != ui::Transform::ROT_INVALID
                ? requested.fixedTransformHint
                : parentSnapshot.fixedTransformHint;

        if (snapshot.fixedTransformHint != ui::Transform::ROT_INVALID) {
            snapshot.transformHint = snapshot.fixedTransformHint;
        } else {
            const auto display = args.displays.get(snapshot.outputFilter.layerStack);
            snapshot.transformHint = display.has_value()
                    ? std::make_optional<>(display->get().transformHint)
                    : std::nullopt;
        }
    }

    if (forceUpdate ||
        args.layerLifecycleManager.getGlobalChanges().any(
                RequestedLayerState::Changes::Hierarchy) ||
        snapshot.changes.any(RequestedLayerState::Changes::FrameRate |
                             RequestedLayerState::Changes::Hierarchy)) {
        const bool shouldOverrideChildren = parentSnapshot.frameRateSelectionStrategy ==
                scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
        const bool propagationAllowed = parentSnapshot.frameRateSelectionStrategy !=
                scheduler::LayerInfo::FrameRateSelectionStrategy::Self;
        if ((!requested.requestedFrameRate.isValid() && propagationAllowed) ||
            shouldOverrideChildren) {
            snapshot.inheritedFrameRate = parentSnapshot.inheritedFrameRate;
        } else {
            snapshot.inheritedFrameRate = requested.requestedFrameRate;
        }
        // Set the framerate as the inherited frame rate and allow children to override it if
        // needed.
        snapshot.frameRate = snapshot.inheritedFrameRate;
        snapshot.changes |= RequestedLayerState::Changes::FrameRate;
    }

    if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionStrategyChanged) {
        if (parentSnapshot.frameRateSelectionStrategy ==
            scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren) {
            snapshot.frameRateSelectionStrategy =
                    scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
        } else {
            const auto strategy = scheduler::LayerInfo::convertFrameRateSelectionStrategy(
                    requested.frameRateSelectionStrategy);
            snapshot.frameRateSelectionStrategy = strategy;
        }
    }

    if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionPriority) {
        snapshot.frameRateSelectionPriority =
                (requested.frameRateSelectionPriority == Layer::PRIORITY_UNSET)
                ? parentSnapshot.frameRateSelectionPriority
                : requested.frameRateSelectionPriority;
    }

    if (forceUpdate ||
        snapshot.clientChanges &
                (layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged |
                 layer_state_t::eAlphaChanged)) {
        snapshot.backgroundBlurRadius = args.supportsBlur
                ? static_cast<int>(parentSnapshot.color.a * (float)requested.backgroundBlurRadius)
                : 0;
        snapshot.blurRegions = requested.blurRegions;
        for (auto& region : snapshot.blurRegions) {
            region.alpha = region.alpha * snapshot.color.a;
        }
    }

    if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Geometry)) {
        uint32_t primaryDisplayRotationFlags = getPrimaryDisplayRotationFlags(args.displays);
        updateLayerBounds(snapshot, requested, parentSnapshot, primaryDisplayRotationFlags);
    }

    if (forceUpdate || snapshot.clientChanges & layer_state_t::eCornerRadiusChanged ||
        snapshot.changes.any(RequestedLayerState::Changes::Geometry |
                             RequestedLayerState::Changes::BufferUsageFlags)) {
        updateRoundedCorner(snapshot, requested, parentSnapshot, args);
    }

    if (forceUpdate || snapshot.clientChanges & layer_state_t::eShadowRadiusChanged ||
        snapshot.changes.any(RequestedLayerState::Changes::Geometry)) {
        updateShadows(snapshot, requested, args.globalShadowSettings);
    }

    if (forceUpdate ||
        snapshot.changes.any(RequestedLayerState::Changes::Geometry |
                             RequestedLayerState::Changes::Input)) {
        updateInput(snapshot, requested, parentSnapshot, path, args);
    }

    // computed snapshot properties
    snapshot.forceClientComposition =
            snapshot.shadowSettings.length > 0 || snapshot.stretchEffect.hasEffect();
    snapshot.contentOpaque = snapshot.isContentOpaque();
    snapshot.isOpaque = snapshot.contentOpaque && !snapshot.roundedCorner.hasRoundedCorners() &&
            snapshot.color.a == 1.f;
    snapshot.blendMode = getBlendMode(snapshot, requested);
    LLOGV(snapshot.sequence,
          "%supdated %s changes:%s parent:%s requested:%s requested:%s from parent %s",
          args.forceUpdate == ForceUpdateFlags::ALL ? "Force " : "",
          snapshot.getDebugString().c_str(), snapshot.changes.string().c_str(),
          parentSnapshot.changes.string().c_str(), requested.changes.string().c_str(),
          std::to_string(requested.what).c_str(), parentSnapshot.getDebugString().c_str());
}

void LayerSnapshotBuilder::updateRoundedCorner(LayerSnapshot& snapshot,
                                               const RequestedLayerState& requested,
                                               const LayerSnapshot& parentSnapshot,
                                               const Args& args) {
    if (args.skipRoundCornersWhenProtected && requested.isProtected()) {
        snapshot.roundedCorner = RoundedCornerState();
        return;
    }
    snapshot.roundedCorner = RoundedCornerState();
    RoundedCornerState parentRoundedCorner;
    if (parentSnapshot.roundedCorner.hasRoundedCorners()) {
        parentRoundedCorner = parentSnapshot.roundedCorner;
        ui::Transform t = snapshot.localTransform.inverse();
        parentRoundedCorner.cropRect = t.transform(parentRoundedCorner.cropRect);
        parentRoundedCorner.radius.x *= t.getScaleX();
        parentRoundedCorner.radius.y *= t.getScaleY();
    }

    FloatRect layerCropRect = snapshot.croppedBufferSize.toFloatRect();
    const vec2 radius(requested.cornerRadius, requested.cornerRadius);
    RoundedCornerState layerSettings(layerCropRect, radius);
    const bool layerSettingsValid = layerSettings.hasRoundedCorners() && !layerCropRect.isEmpty();
    const bool parentRoundedCornerValid = parentRoundedCorner.hasRoundedCorners();
    if (layerSettingsValid && parentRoundedCornerValid) {
        // If the parent and the layer have rounded corner settings, use the parent settings if
        // the parent crop is entirely inside the layer crop. This has limitations and cause
        // rendering artifacts. See b/200300845 for correct fix.
        if (parentRoundedCorner.cropRect.left > layerCropRect.left &&
            parentRoundedCorner.cropRect.top > layerCropRect.top &&
            parentRoundedCorner.cropRect.right < layerCropRect.right &&
            parentRoundedCorner.cropRect.bottom < layerCropRect.bottom) {
            snapshot.roundedCorner = parentRoundedCorner;
        } else {
            snapshot.roundedCorner = layerSettings;
        }
    } else if (layerSettingsValid) {
        snapshot.roundedCorner = layerSettings;
    } else if (parentRoundedCornerValid) {
        snapshot.roundedCorner = parentRoundedCorner;
    }
}

void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot,
                                             const RequestedLayerState& requested,
                                             const LayerSnapshot& parentSnapshot,
                                             uint32_t primaryDisplayRotationFlags) {
    snapshot.geomLayerTransform = parentSnapshot.geomLayerTransform * snapshot.localTransform;
    const bool transformWasInvalid = snapshot.invalidTransform;
    snapshot.invalidTransform = !LayerSnapshot::isTransformValid(snapshot.geomLayerTransform);
    if (snapshot.invalidTransform) {
        auto& t = snapshot.geomLayerTransform;
        auto& requestedT = requested.requestedTransform;
        std::string transformDebug =
                base::StringPrintf(" transform={%f,%f,%f,%f}  requestedTransform={%f,%f,%f,%f}",
                                   t.dsdx(), t.dsdy(), t.dtdx(), t.dtdy(), requestedT.dsdx(),
                                   requestedT.dsdy(), requestedT.dtdx(), requestedT.dtdy());
        std::string bufferDebug;
        if (requested.externalTexture) {
            auto unRotBuffer = requested.getUnrotatedBufferSize(primaryDisplayRotationFlags);
            auto& destFrame = requested.destinationFrame;
            bufferDebug = base::StringPrintf(" buffer={%d,%d}  displayRot=%d"
                                             " destFrame={%d,%d,%d,%d} unRotBuffer={%d,%d}",
                                             requested.externalTexture->getWidth(),
                                             requested.externalTexture->getHeight(),
                                             primaryDisplayRotationFlags, destFrame.left,
                                             destFrame.top, destFrame.right, destFrame.bottom,
                                             unRotBuffer.getHeight(), unRotBuffer.getWidth());
        }
        ALOGW("Resetting transform for %s because it is invalid.%s%s",
              snapshot.getDebugString().c_str(), transformDebug.c_str(), bufferDebug.c_str());
        snapshot.geomLayerTransform.reset();
    }
    if (transformWasInvalid != snapshot.invalidTransform) {
        // If transform is invalid, the layer will be hidden.
        mResortSnapshots = true;
    }
    snapshot.geomInverseLayerTransform = snapshot.geomLayerTransform.inverse();

    FloatRect parentBounds = parentSnapshot.geomLayerBounds;
    parentBounds = snapshot.localTransform.inverse().transform(parentBounds);
    snapshot.geomLayerBounds =
            (requested.externalTexture) ? snapshot.bufferSize.toFloatRect() : parentBounds;
    if (!requested.crop.isEmpty()) {
        snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(requested.crop.toFloatRect());
    }
    snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(parentBounds);
    snapshot.transformedBounds = snapshot.geomLayerTransform.transform(snapshot.geomLayerBounds);
    const Rect geomLayerBoundsWithoutTransparentRegion =
            RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds),
                                        requested.transparentRegion);
    snapshot.transformedBoundsWithoutTransparentRegion =
            snapshot.geomLayerTransform.transform(geomLayerBoundsWithoutTransparentRegion);
    snapshot.parentTransform = parentSnapshot.geomLayerTransform;

    // Subtract the transparent region and snap to the bounds
    const Rect bounds =
            RequestedLayerState::reduce(snapshot.croppedBufferSize, requested.transparentRegion);
    if (requested.potentialCursor) {
        snapshot.cursorFrame = snapshot.geomLayerTransform.transform(bounds);
    }
}

void LayerSnapshotBuilder::updateShadows(LayerSnapshot& snapshot, const RequestedLayerState&,
                                         const ShadowSettings& globalShadowSettings) {
    if (snapshot.shadowSettings.length > 0.f) {
        snapshot.shadowSettings.ambientColor = globalShadowSettings.ambientColor;
        snapshot.shadowSettings.spotColor = globalShadowSettings.spotColor;
        snapshot.shadowSettings.lightPos = globalShadowSettings.lightPos;
        snapshot.shadowSettings.lightRadius = globalShadowSettings.lightRadius;

        // Note: this preserves existing behavior of shadowing the entire layer and not cropping
        // it if transparent regions are present. This may not be necessary since shadows are
        // typically cast by layers without transparent regions.
        snapshot.shadowSettings.boundaries = snapshot.geomLayerBounds;

        // If the casting layer is translucent, we need to fill in the shadow underneath the
        // layer. Otherwise the generated shadow will only be shown around the casting layer.
        snapshot.shadowSettings.casterIsTranslucent =
                !snapshot.isContentOpaque() || (snapshot.alpha < 1.0f);
        snapshot.shadowSettings.ambientColor *= snapshot.alpha;
        snapshot.shadowSettings.spotColor *= snapshot.alpha;
    }
}

void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot,
                                       const RequestedLayerState& requested,
                                       const LayerSnapshot& parentSnapshot,
                                       const LayerHierarchy::TraversalPath& path,
                                       const Args& args) {
    using InputConfig = gui::WindowInfo::InputConfig;

    if (requested.windowInfoHandle) {
        snapshot.inputInfo = *requested.windowInfoHandle->getInfo();
    } else {
        snapshot.inputInfo = {};
        // b/271132344 revisit this and see if we can always use the layers uid/pid
        snapshot.inputInfo.name = requested.name;
        snapshot.inputInfo.ownerUid = gui::Uid{requested.ownerUid};
        snapshot.inputInfo.ownerPid = gui::Pid{requested.ownerPid};
    }
    snapshot.touchCropId = requested.touchCropId;

    snapshot.inputInfo.id = static_cast<int32_t>(snapshot.uniqueSequence);
    snapshot.inputInfo.displayId =
            ui::LogicalDisplayId{static_cast<int32_t>(snapshot.outputFilter.layerStack.id)};
    snapshot.inputInfo.touchOcclusionMode = requested.hasInputInfo()
            ? requested.windowInfoHandle->getInfo()->touchOcclusionMode
            : parentSnapshot.inputInfo.touchOcclusionMode;
    snapshot.inputInfo.canOccludePresentation = parentSnapshot.inputInfo.canOccludePresentation ||
            (requested.flags & layer_state_t::eCanOccludePresentation);
    if (requested.dropInputMode == gui::DropInputMode::ALL ||
        parentSnapshot.dropInputMode == gui::DropInputMode::ALL) {
        snapshot.dropInputMode = gui::DropInputMode::ALL;
    } else if (requested.dropInputMode == gui::DropInputMode::OBSCURED ||
               parentSnapshot.dropInputMode == gui::DropInputMode::OBSCURED) {
        snapshot.dropInputMode = gui::DropInputMode::OBSCURED;
    } else {
        snapshot.dropInputMode = gui::DropInputMode::NONE;
    }

    if (snapshot.isSecure ||
        parentSnapshot.inputInfo.inputConfig.test(InputConfig::SENSITIVE_FOR_PRIVACY)) {
        snapshot.inputInfo.inputConfig |= InputConfig::SENSITIVE_FOR_PRIVACY;
    }

    updateVisibility(snapshot, snapshot.isVisible);
    if (!needsInputInfo(snapshot, requested)) {
        return;
    }

    static frontend::DisplayInfo sDefaultInfo = {.isSecure = false};
    const std::optional<frontend::DisplayInfo> displayInfoOpt =
            args.displays.get(snapshot.outputFilter.layerStack);
    bool noValidDisplay = !displayInfoOpt.has_value();
    auto displayInfo = displayInfoOpt.value_or(sDefaultInfo);

    if (!requested.windowInfoHandle) {
        snapshot.inputInfo.inputConfig = InputConfig::NO_INPUT_CHANNEL;
    }
    fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot);

    if (noValidDisplay) {
        // Do not let the window receive touches if it is not associated with a valid display
        // transform. We still allow the window to receive keys and prevent ANRs.
        snapshot.inputInfo.inputConfig |= InputConfig::NOT_TOUCHABLE;
    }

    snapshot.inputInfo.alpha = snapshot.color.a;

    handleDropInputMode(snapshot, parentSnapshot);

    // If the window will be blacked out on a display because the display does not have the secure
    // flag and the layer has the secure flag set, then drop input.
    if (!displayInfo.isSecure && snapshot.isSecure) {
        snapshot.inputInfo.inputConfig |= InputConfig::DROP_INPUT;
    }

    if (requested.touchCropId != UNASSIGNED_LAYER_ID || path.isClone()) {
        mNeedsTouchableRegionCrop.insert(path);
    }
    auto cropLayerSnapshot = getSnapshot(requested.touchCropId);
    if (!cropLayerSnapshot && snapshot.inputInfo.replaceTouchableRegionWithCrop) {
        FloatRect inputBounds = getInputBounds(snapshot, /*fillParentBounds=*/true).first;
        Rect inputBoundsInDisplaySpace =
                getInputBoundsInDisplaySpace(snapshot, inputBounds, displayInfo.transform);
        snapshot.inputInfo.touchableRegion = Region(inputBoundsInDisplaySpace);
    }

    // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state
    // if it was set by WM for a known system overlay
    if (snapshot.trustedOverlay == gui::TrustedOverlay::ENABLED) {
        snapshot.inputInfo.inputConfig |= InputConfig::TRUSTED_OVERLAY;
    }

    snapshot.inputInfo.contentSize = snapshot.croppedBufferSize.getSize();

    // If the layer is a clone, we need to crop the input region to cloned root to prevent
    // touches from going outside the cloned area.
    if (path.isClone()) {
        snapshot.inputInfo.inputConfig |= InputConfig::CLONE;
        // Cloned layers shouldn't handle watch outside since their z order is not determined by
        // WM or the client.
        snapshot.inputInfo.inputConfig.clear(InputConfig::WATCH_OUTSIDE_TOUCH);
    }
}

std::vector<std::unique_ptr<LayerSnapshot>>& LayerSnapshotBuilder::getSnapshots() {
    return mSnapshots;
}

void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor) const {
    for (int i = 0; i < mNumInterestingSnapshots; i++) {
        LayerSnapshot& snapshot = *mSnapshots[(size_t)i];
        if (!snapshot.isVisible) continue;
        visitor(snapshot);
    }
}

// Visit each visible snapshot in z-order
void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor,
                                                  const LayerHierarchy& root) const {
    root.traverseInZOrder(
            [this, visitor](const LayerHierarchy&,
                            const LayerHierarchy::TraversalPath& traversalPath) -> bool {
                LayerSnapshot* snapshot = getSnapshot(traversalPath);
                if (snapshot && snapshot->isVisible) {
                    visitor(*snapshot);
                }
                return true;
            });
}

void LayerSnapshotBuilder::forEachVisibleSnapshot(const Visitor& visitor) {
    for (int i = 0; i < mNumInterestingSnapshots; i++) {
        std::unique_ptr<LayerSnapshot>& snapshot = mSnapshots.at((size_t)i);
        if (!snapshot->isVisible) continue;
        visitor(snapshot);
    }
}

void LayerSnapshotBuilder::forEachInputSnapshot(const ConstVisitor& visitor) const {
    for (int i = mNumInterestingSnapshots - 1; i >= 0; i--) {
        LayerSnapshot& snapshot = *mSnapshots[(size_t)i];
        if (!snapshot.hasInputInfo()) continue;
        visitor(snapshot);
    }
}

void LayerSnapshotBuilder::updateTouchableRegionCrop(const Args& args) {
    if (mNeedsTouchableRegionCrop.empty()) {
        return;
    }

    static constexpr ftl::Flags<RequestedLayerState::Changes> AFFECTS_INPUT =
            RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Created |
            RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry |
            RequestedLayerState::Changes::Input;

    if (args.forceUpdate != ForceUpdateFlags::ALL &&
        !args.layerLifecycleManager.getGlobalChanges().any(AFFECTS_INPUT) && !args.displayChanges) {
        return;
    }

    for (auto& path : mNeedsTouchableRegionCrop) {
        frontend::LayerSnapshot* snapshot = getSnapshot(path);
        if (!snapshot) {
            continue;
        }
        LLOGV(snapshot->sequence, "updateTouchableRegionCrop=%s",
              snapshot->getDebugString().c_str());
        const std::optional<frontend::DisplayInfo> displayInfoOpt =
                args.displays.get(snapshot->outputFilter.layerStack);
        static frontend::DisplayInfo sDefaultInfo = {.isSecure = false};
        auto displayInfo = displayInfoOpt.value_or(sDefaultInfo);

        bool needsUpdate =
                args.forceUpdate == ForceUpdateFlags::ALL || snapshot->changes.any(AFFECTS_INPUT);
        auto cropLayerSnapshot = getSnapshot(snapshot->touchCropId);
        needsUpdate =
                needsUpdate || (cropLayerSnapshot && cropLayerSnapshot->changes.any(AFFECTS_INPUT));
        auto clonedRootSnapshot = path.isClone() ? getSnapshot(snapshot->mirrorRootPath) : nullptr;
        needsUpdate = needsUpdate ||
                (clonedRootSnapshot && clonedRootSnapshot->changes.any(AFFECTS_INPUT));

        if (!needsUpdate) {
            continue;
        }

        if (snapshot->inputInfo.replaceTouchableRegionWithCrop) {
            Rect inputBoundsInDisplaySpace;
            if (!cropLayerSnapshot) {
                FloatRect inputBounds = getInputBounds(*snapshot, /*fillParentBounds=*/true).first;
                inputBoundsInDisplaySpace =
                        getInputBoundsInDisplaySpace(*snapshot, inputBounds, displayInfo.transform);
            } else {
                FloatRect inputBounds =
                        getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first;
                inputBoundsInDisplaySpace =
                        getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds,
                                                     displayInfo.transform);
            }
            snapshot->inputInfo.touchableRegion = Region(inputBoundsInDisplaySpace);
        } else if (cropLayerSnapshot) {
            FloatRect inputBounds =
                    getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first;
            Rect inputBoundsInDisplaySpace =
                    getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds,
                                                 displayInfo.transform);
            snapshot->inputInfo.touchableRegion =
                    snapshot->inputInfo.touchableRegion.intersect(inputBoundsInDisplaySpace);
        }

        // If the layer is a clone, we need to crop the input region to cloned root to prevent
        // touches from going outside the cloned area.
        if (clonedRootSnapshot) {
            const Rect rect =
                    displayInfo.transform.transform(Rect{clonedRootSnapshot->transformedBounds});
            snapshot->inputInfo.touchableRegion =
                    snapshot->inputInfo.touchableRegion.intersect(rect);
        }
    }
}

} // namespace android::surfaceflinger::frontend
