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

Commit 8bd5503c authored by Arthur Hung's avatar Arthur Hung Committed by Automerger Merge Worker
Browse files

Introduce SurfaceFlinger Queued Transaction am: 58144273

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/native/+/13432370

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I6f7aa0035714146b1576560c7757d0805acc902b
parents 52d13175 58144273
Loading
Loading
Loading
Loading
+165 −171
Original line number Diff line number Diff line
@@ -1938,19 +1938,16 @@ void SurfaceFlinger::onMessageInvalidate(int64_t vsyncId, nsecs_t expectedVSyncT

bool SurfaceFlinger::handleMessageTransaction() {
    ATRACE_CALL();
    uint32_t transactionFlags = peekTransactionFlags();

    bool flushedATransaction = flushTransactionQueues();

    if (getTransactionFlags(eTransactionFlushNeeded)) {
        flushTransactionQueues();
    }
    uint32_t transactionFlags = peekTransactionFlags();
    bool runHandleTransaction =
            (transactionFlags && (transactionFlags != eTransactionFlushNeeded)) ||
            flushedATransaction ||
            mForceTraversal;
            ((transactionFlags & (~eTransactionFlushNeeded)) != 0) || mForceTraversal;

    if (runHandleTransaction) {
        handleTransaction(eTransactionMask);
    } else {
        getTransactionFlags(eTransactionFlushNeeded);
    }

    if (transactionFlushNeeded()) {
@@ -2859,7 +2856,6 @@ void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags) {
        });
    }

    commitInputWindowCommands();
    commitTransaction();
}

@@ -2900,11 +2896,6 @@ void SurfaceFlinger::updateInputWindowInfo() {
                                                                     : nullptr);
}

void SurfaceFlinger::commitInputWindowCommands() {
    mInputWindowCommands.merge(mPendingInputWindowCommands);
    mPendingInputWindowCommands.clear();
}

void SurfaceFlinger::updateCursorAsync() {
    compositionengine::CompositionRefreshArgs refreshArgs;
    for (const auto& [_, display] : ON_MAIN_THREAD(mDisplays)) {
@@ -3267,17 +3258,16 @@ void SurfaceFlinger::setTraversalNeeded() {
    mForceTraversal = true;
}

bool SurfaceFlinger::flushTransactionQueues() {
void SurfaceFlinger::flushTransactionQueues() {
    // to prevent onHandleDestroyed from being called while the lock is held,
    // we must keep a copy of the transactions (specifically the composer
    // states) around outside the scope of the lock
    std::vector<const TransactionState> transactions;
    bool flushedATransaction = false;
    {
        Mutex::Autolock _l(mStateLock);

        auto it = mTransactionQueues.begin();
        while (it != mTransactionQueues.end()) {
        Mutex::Autolock _l(mQueueLock);
        // Collect transactions from pending transaction queue.
        auto it = mPendingTransactionQueues.begin();
        while (it != mPendingTransactionQueues.end()) {
            auto& [applyToken, transactionQueue] = *it;

            while (!transactionQueue.empty()) {
@@ -3289,31 +3279,55 @@ bool SurfaceFlinger::flushTransactionQueues() {
                    break;
                }
                transactions.push_back(transaction);
                applyTransactionState(transaction.frameTimelineInfo, transaction.states,
                                      transaction.displays, transaction.flags,
                                      mPendingInputWindowCommands, transaction.desiredPresentTime,
                                      transaction.isAutoTimestamp, transaction.buffer,
                                      transaction.postTime, transaction.privileged,
                                      transaction.hasListenerCallbacks,
                                      transaction.listenerCallbacks, transaction.originPid,
                                      transaction.originUid, transaction.id, /*isMainThread*/ true);
                transactionQueue.pop();
                flushedATransaction = true;
            }

            if (transactionQueue.empty()) {
                it = mTransactionQueues.erase(it);
                mTransactionCV.broadcast();
                it = mPendingTransactionQueues.erase(it);
                mTransactionQueueCV.broadcast();
            } else {
                it = std::next(it, 1);
            }
        }

        // Collect transactions from current transaction queue or queue to pending transactions.
        // Case 1: push to pending when transactionIsReadyToBeApplied is false.
        // Case 2: push to pending when there exist a pending queue.
        // Case 3: others are ready to apply.
        while (!mTransactionQueue.empty()) {
            const auto& transaction = mTransactionQueue.front();
            bool pendingTransactions = mPendingTransactionQueues.find(transaction.applyToken) !=
                    mPendingTransactionQueues.end();
            // Call transactionIsReadyToBeApplied first in case we need to
            // incrementPendingBufferCount if the transaction contains a buffer.
            if (!transactionIsReadyToBeApplied(transaction.isAutoTimestamp,
                                               transaction.desiredPresentTime, transaction.states,
                                               true) ||
                pendingTransactions) {
                mPendingTransactionQueues[transaction.applyToken].push(transaction);
            } else {
                transactions.push_back(transaction);
            }
            mTransactionQueue.pop();
        }
    }

    // Now apply all transactions.
    Mutex::Autolock _l(mStateLock);
    for (const auto& transaction : transactions) {
        applyTransactionState(transaction.frameTimelineInfo, transaction.states,
                              transaction.displays, transaction.flags,
                              transaction.inputWindowCommands, transaction.desiredPresentTime,
                              transaction.isAutoTimestamp, transaction.buffer, transaction.postTime,
                              transaction.privileged, transaction.hasListenerCallbacks,
                              transaction.listenerCallbacks, transaction.originPid,
                              transaction.originUid, transaction.id);
    }
    return flushedATransaction;
}

bool SurfaceFlinger::transactionFlushNeeded() {
    return !mTransactionQueues.empty();
    Mutex::Autolock _l(mQueueLock);
    return !mPendingTransactionQueues.empty();
}

bool SurfaceFlinger::transactionIsReadyToBeApplied(bool isAutoTimestamp, int64_t desiredPresentTime,
@@ -3336,6 +3350,8 @@ bool SurfaceFlinger::transactionIsReadyToBeApplied(bool isAutoTimestamp, int64_t
        if (s.acquireFence && s.acquireFence->getStatus() == Fence::Status::Unsignaled) {
            ready = false;
        }

        Mutex::Autolock _l(mStateLock);
        sp<Layer> layer = nullptr;
        if (s.surface) {
            layer = fromHandleLocked(s.surface).promote();
@@ -3376,30 +3392,34 @@ status_t SurfaceFlinger::setTransactionState(
        const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
    ATRACE_CALL();

    const int64_t postTime = systemTime();
    {
        Mutex::Autolock _l(mQueueLock);

        const int64_t postTime = systemTime();
        bool privileged = callingThreadHasUnscopedSurfaceFlingerAccess();

    Mutex::Autolock _l(mStateLock);
        IPCThreadState* ipc = IPCThreadState::self();
        const int originPid = ipc->getCallingPid();
        const int originUid = ipc->getCallingUid();

        // If its TransactionQueue already has a pending TransactionState or if it is pending
    auto itr = mTransactionQueues.find(applyToken);
        auto itr = mPendingTransactionQueues.find(applyToken);
        // if this is an animation frame, wait until prior animation frame has
        // been applied by SF
        if (flags & eAnimation) {
        while (itr != mTransactionQueues.end()) {
            status_t err = mTransactionCV.waitRelative(mStateLock, s2ns(5));
            while (itr != mPendingTransactionQueues.end()) {
                status_t err = mTransactionQueueCV.waitRelative(mQueueLock, s2ns(5));
                if (CC_UNLIKELY(err != NO_ERROR)) {
                    ALOGW_IF(err == TIMED_OUT,
                             "setTransactionState timed out "
                             "waiting for animation frame to apply");
                    break;
                }
            itr = mTransactionQueues.find(applyToken);
                itr = mPendingTransactionQueues.find(applyToken);
            }
        }

    const bool pendingTransactions = itr != mTransactionQueues.end();
        const bool pendingTransactions = itr != mPendingTransactionQueues.end();
        // Expected present time is computed and cached on invalidate, so it may be stale.
        if (!pendingTransactions) {
            const auto now = systemTime();
@@ -3414,56 +3434,83 @@ status_t SurfaceFlinger::setTransactionState(
            }
        }

    IPCThreadState* ipc = IPCThreadState::self();
    const int originPid = ipc->getCallingPid();
    const int originUid = ipc->getCallingUid();
        mTransactionQueue.emplace(frameTimelineInfo, states, displays, flags, applyToken,
                                  inputWindowCommands, desiredPresentTime, isAutoTimestamp,
                                  uncacheBuffer, postTime, privileged, hasListenerCallbacks,
                                  listenerCallbacks, originPid, originUid, transactionId);

    // Call transactionIsReadyToBeApplied first in case we need to incrementPendingBufferCount
    // if the transaction contains a buffer.
    if (!transactionIsReadyToBeApplied(isAutoTimestamp, desiredPresentTime, states, true) ||
        pendingTransactions) {
        mTransactionQueues[applyToken].emplace(frameTimelineInfo, states, displays, flags,
                                               desiredPresentTime, isAutoTimestamp, uncacheBuffer,
                                               postTime, privileged, hasListenerCallbacks,
                                               listenerCallbacks, originPid, originUid,
                                               transactionId);
        if (pendingTransactions ||
            (!isAutoTimestamp && desiredPresentTime > mExpectedPresentTime.load())) {
            setTransactionFlags(eTransactionFlushNeeded);
            return NO_ERROR;
        }

    applyTransactionState(frameTimelineInfo, states, displays, flags, inputWindowCommands,
                          desiredPresentTime, isAutoTimestamp, uncacheBuffer, postTime, privileged,
                          hasListenerCallbacks, listenerCallbacks, originPid, originUid,
                          transactionId, /*isMainThread*/ false);
        // TODO(b/159125966): Remove eEarlyWakeup completely as no client should use this flag
        if (flags & eEarlyWakeup) {
            ALOGW("eEarlyWakeup is deprecated. Use eExplicitEarlyWakeup[Start|End]");
        }

        if (!privileged && (flags & (eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd))) {
            ALOGE("Only WindowManager is allowed to use eExplicitEarlyWakeup[Start|End] flags");
            flags &= ~(eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd);
        }

        const auto schedule = [](uint32_t flags) {
            if (flags & eEarlyWakeup) return TransactionSchedule::Early;
            if (flags & eExplicitEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;
            if (flags & eExplicitEarlyWakeupStart) return TransactionSchedule::EarlyStart;
            return TransactionSchedule::Late;
        }(flags);
        setTransactionFlags(eTransactionFlushNeeded, schedule);
    }

    // if this is a synchronous transaction, wait for it to take effect
    // before returning.
    const bool synchronous = flags & eSynchronous;
    const bool syncInput = inputWindowCommands.syncInputWindows;
    if (!synchronous && !syncInput) {
        return NO_ERROR;
    }

void SurfaceFlinger::applyTransactionState(
        const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,
        const Vector<DisplayState>& displays, uint32_t flags,
        const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime,
        bool isAutoTimestamp, const client_cache_t& uncacheBuffer, const int64_t postTime,
        bool privileged, bool hasListenerCallbacks,
        const std::vector<ListenerCallbacks>& listenerCallbacks, int originPid, int originUid,
        uint64_t transactionId, bool isMainThread) {
    uint32_t transactionFlags = 0;
    Mutex::Autolock _l(mStateLock);
    if (synchronous) {
        mTransactionPending = true;
    }
    if (syncInput) {
        mPendingSyncInputWindows = true;
    }

    if (flags & eAnimation) {
        // For window updates that are part of an animation we must wait for
        // previous animation "frames" to be handled.
        while (!isMainThread && mAnimTransactionPending) {
    // applyTransactionState can be called by either the main SF thread or by
    // another process through setTransactionState.  While a given process may wish
    // to wait on synchronous transactions, the main SF thread should never
    // be blocked.  Therefore, we only wait if isMainThread is false.
    while (mTransactionPending || mPendingSyncInputWindows) {
        status_t err = mTransactionCV.waitRelative(mStateLock, s2ns(5));
        if (CC_UNLIKELY(err != NO_ERROR)) {
            // just in case something goes wrong in SF, return to the
                // caller after a few seconds.
                ALOGW_IF(err == TIMED_OUT, "setTransactionState timed out "
                        "waiting for previous animation frame");
                mAnimTransactionPending = false;
            // called after a few seconds.
            ALOGW_IF(err == TIMED_OUT, "setTransactionState timed out!");
            mTransactionPending = false;
            mPendingSyncInputWindows = false;
            break;
        }
    }

    return NO_ERROR;
}

void SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelineInfo,
                                           const Vector<ComposerState>& states,
                                           const Vector<DisplayState>& displays, uint32_t flags,
                                           const InputWindowCommands& inputWindowCommands,
                                           const int64_t desiredPresentTime, bool isAutoTimestamp,
                                           const client_cache_t& uncacheBuffer,
                                           const int64_t postTime, bool privileged,
                                           bool hasListenerCallbacks,
                                           const std::vector<ListenerCallbacks>& listenerCallbacks,
                                           int originPid, int originUid, uint64_t transactionId) {
    uint32_t transactionFlags = 0;

    for (const DisplayState& display : displays) {
        transactionFlags |= setDisplayStateLocked(display);
    }
@@ -3521,80 +3568,25 @@ void SurfaceFlinger::applyTransactionState(
        transactionFlags = eTransactionNeeded;
    }

    // If we are on the main thread, we are about to preform a traversal. Clear the traversal bit
    // so we don't have to wake up again next frame to preform an uneeded traversal.
    if (isMainThread && (transactionFlags & eTraversalNeeded)) {
        transactionFlags = transactionFlags & (~eTraversalNeeded);
        mForceTraversal = true;
    }

    const auto schedule = [](uint32_t flags) {
        if (flags & eEarlyWakeup) return TransactionSchedule::Early;
        if (flags & eExplicitEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;
        if (flags & eExplicitEarlyWakeupStart) return TransactionSchedule::EarlyStart;
        return TransactionSchedule::Late;
    }(flags);

    if (transactionFlags) {
        if (mInterceptor->isEnabled()) {
            mInterceptor->saveTransaction(states, mCurrentState.displays, displays, flags,
                                          originPid, originUid, transactionId);
        }

        // TODO(b/159125966): Remove eEarlyWakeup completly as no client should use this flag
        if (flags & eEarlyWakeup) {
            ALOGW("eEarlyWakeup is deprecated. Use eExplicitEarlyWakeup[Start|End]");
        // We are on the main thread, we are about to preform a traversal. Clear the traversal bit
        // so we don't have to wake up again next frame to preform an unnecessary traversal.
        if (transactionFlags & eTraversalNeeded) {
            transactionFlags = transactionFlags & (~eTraversalNeeded);
            mForceTraversal = true;
        }

        if (!privileged && (flags & (eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd))) {
            ALOGE("Only WindowManager is allowed to use eExplicitEarlyWakeup[Start|End] flags");
            flags &= ~(eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd);
        if (transactionFlags) {
            setTransactionFlags(transactionFlags);
        }

        // this triggers the transaction
        setTransactionFlags(transactionFlags, schedule);

        if (flags & eAnimation) {
            mAnimTransactionPending = true;
        }

        // if this is a synchronous transaction, wait for it to take effect
        // before returning.
        const bool synchronous = flags & eSynchronous;
        const bool syncInput = inputWindowCommands.syncInputWindows;
        if (!synchronous && !syncInput) {
            return;
        }

        if (synchronous) {
            mTransactionPending = true;
        }
        if (syncInput) {
            mPendingSyncInputWindows = true;
        }


        // applyTransactionState can be called by either the main SF thread or by
        // another process through setTransactionState.  While a given process may wish
        // to wait on synchronous transactions, the main SF thread should never
        // be blocked.  Therefore, we only wait if isMainThread is false.
        while (!isMainThread && (mTransactionPending || mPendingSyncInputWindows)) {
            status_t err = mTransactionCV.waitRelative(mStateLock, s2ns(5));
            if (CC_UNLIKELY(err != NO_ERROR)) {
                // just in case something goes wrong in SF, return to the
                // called after a few seconds.
                ALOGW_IF(err == TIMED_OUT, "setTransactionState timed out!");
                mTransactionPending = false;
                mPendingSyncInputWindows = false;
                break;
            }
        }
    } else {
        // Update VsyncModulator state machine even if transaction is not needed.
        if (schedule == TransactionSchedule::EarlyStart ||
            schedule == TransactionSchedule::EarlyEnd) {
            modulateVsync(&VsyncModulator::setTransactionSchedule, schedule);
        }
    }
}

@@ -3988,7 +3980,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(
}

uint32_t SurfaceFlinger::addInputWindowCommands(const InputWindowCommands& inputWindowCommands) {
    bool hasChanges = mPendingInputWindowCommands.merge(inputWindowCommands);
    bool hasChanges = mInputWindowCommands.merge(inputWindowCommands);
    return hasChanges ? eTraversalNeeded : 0;
}

@@ -4253,8 +4245,10 @@ void SurfaceFlinger::onInitializeDisplays() {
    d.width = 0;
    d.height = 0;
    displays.add(d);
    setTransactionState(FrameTimelineInfo{}, state, displays, 0, nullptr,
                        mPendingInputWindowCommands, systemTime(), true, {}, false, {},

    // It should be on the main thread, apply it directly.
    applyTransactionState(FrameTimelineInfo{}, state, displays, 0, mInputWindowCommands,
                          systemTime(), true, {}, systemTime(), true, false, {}, getpid(), getuid(),
                          0 /* Undefined transactionId */);

    setPowerModeInternal(display, hal::PowerMode::ON);
+18 −11
Original line number Diff line number Diff line
@@ -438,15 +438,18 @@ private:
        TransactionState(const FrameTimelineInfo& frameTimelineInfo,
                         const Vector<ComposerState>& composerStates,
                         const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
                         int64_t desiredPresentTime, bool isAutoTimestamp,
                         const client_cache_t& uncacheBuffer, int64_t postTime, bool privileged,
                         bool hasListenerCallbacks,
                         const sp<IBinder>& applyToken,
                         const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
                         bool isAutoTimestamp, const client_cache_t& uncacheBuffer,
                         int64_t postTime, bool privileged, bool hasListenerCallbacks,
                         std::vector<ListenerCallbacks> listenerCallbacks, int originPid,
                         int originUid, uint64_t transactionId)
              : frameTimelineInfo(frameTimelineInfo),
                states(composerStates),
                displays(displayStates),
                flags(transactionFlags),
                applyToken(applyToken),
                inputWindowCommands(inputWindowCommands),
                desiredPresentTime(desiredPresentTime),
                isAutoTimestamp(isAutoTimestamp),
                buffer(uncacheBuffer),
@@ -462,6 +465,8 @@ private:
        Vector<ComposerState> states;
        Vector<DisplayState> displays;
        uint32_t flags;
        sp<IBinder> applyToken;
        InputWindowCommands inputWindowCommands;
        const int64_t desiredPresentTime;
        const bool isAutoTimestamp;
        client_cache_t buffer;
@@ -736,10 +741,10 @@ private:
                               const client_cache_t& uncacheBuffer, const int64_t postTime,
                               bool privileged, bool hasListenerCallbacks,
                               const std::vector<ListenerCallbacks>& listenerCallbacks,
                               int originPid, int originUid, uint64_t transactionId,
                               bool isMainThread = false) REQUIRES(mStateLock);
    // Returns true if at least one transaction was flushed
    bool flushTransactionQueues();
                               int originPid, int originUid, uint64_t transactionId)
            REQUIRES(mStateLock);
    // flush pending transaction that was presented after desiredPresentTime.
    void flushTransactionQueues();
    // Returns true if there is at least one transaction that needs to be flushed
    bool transactionFlushNeeded();
    uint32_t getTransactionFlags(uint32_t flags);
@@ -757,7 +762,7 @@ private:
    void commitOffscreenLayers();
    bool transactionIsReadyToBeApplied(bool isAutoTimestamp, int64_t desiredPresentTime,
                                       const Vector<ComposerState>& states,
                                       bool updateTransactionCounters = false) REQUIRES(mStateLock);
                                       bool updateTransactionCounters = false);
    uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
    uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands)
            REQUIRES(mStateLock);
@@ -1177,8 +1182,11 @@ private:
    uint32_t mTexturePoolSize = 0;
    std::vector<uint32_t> mTexturePool;

    std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash> mTransactionQueues;

    mutable Mutex mQueueLock;
    Condition mTransactionQueueCV;
    std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash>
            mPendingTransactionQueues GUARDED_BY(mQueueLock);
    std::queue<TransactionState> mTransactionQueue GUARDED_BY(mQueueLock);
    /*
     * Feature prototyping
     */
@@ -1256,7 +1264,6 @@ private:
    const float mEmulatedDisplayDensity;

    sp<os::IInputFlinger> mInputFlinger;
    InputWindowCommands mPendingInputWindowCommands GUARDED_BY(mStateLock);
    // Should only be accessed by the main thread.
    InputWindowCommands mInputWindowCommands;

+26 −0
Original line number Diff line number Diff line
@@ -57,6 +57,8 @@ public:
                                                      // Sample usage bits from screenrecord
                                                      GRALLOC_USAGE_HW_VIDEO_ENCODER |
                                                              GRALLOC_USAGE_SW_READ_OFTEN);
                sp<BufferListener> listener = new BufferListener(this);
                itemConsumer->setFrameAvailableListener(listener);

                vDisplay = SurfaceComposerClient::createDisplay(String8("VirtualDisplay"),
                                                                false /*secure*/);
@@ -68,6 +70,13 @@ public:
                                       Rect(displayState.layerStackSpaceRect), Rect(resolution));
                t.apply();
                SurfaceComposerClient::Transaction().apply(true);

                std::unique_lock lock(mMutex);
                mAvailable = false;
                // Wait for frame buffer ready.
                mCondition.wait_for(lock, std::chrono::seconds(2),
                                    [this]() NO_THREAD_SAFETY_ANALYSIS { return mAvailable; });

                BufferItem item;
                itemConsumer->acquireBuffer(&item, 0, true);
                auto sc = std::make_unique<ScreenCapture>(item.mGraphicBuffer);
@@ -80,6 +89,23 @@ public:
protected:
    LayerTransactionTest* mDelegate;
    RenderPath mRenderPath;
    std::mutex mMutex;
    std::condition_variable mCondition;
    bool mAvailable = false;

    void onFrameAvailable() {
        std::unique_lock lock(mMutex);
        mAvailable = true;
        mCondition.notify_all();
    }

    class BufferListener : public ConsumerBase::FrameAvailableListener {
    public:
        BufferListener(LayerRenderPathTestHarness* owner) : mOwner(owner) {}
        LayerRenderPathTestHarness* mOwner;

        void onFrameAvailable(const BufferItem& /*item*/) { mOwner->onFrameAvailable(); }
    };
};

class LayerTypeTransactionHarness : public LayerTransactionTest {
+2 −1
Original line number Diff line number Diff line
@@ -367,7 +367,8 @@ public:
        return mFlinger->SurfaceFlinger::getDisplayNativePrimaries(displayToken, primaries);
    }

    auto& getTransactionQueue() { return mFlinger->mTransactionQueues; }
    auto& getTransactionQueue() { return mFlinger->mTransactionQueue; }
    auto& getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; }

    auto setTransactionState(
            const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,
+9 −7

File changed.

Preview size limit exceeded, changes collapsed.

Loading