void TransactionCallbackInvoker::sendCallbacks() {
    std::lock_guard lock(mMutex);
    // For each listener
    auto completedTransactionsItr = mCompletedTransactions.begin();
    while (completedTransactionsItr != mCompletedTransactions.end()) {
        auto& [listener, transactionStatsDeque] = *completedTransactionsItr;
        ListenerStats listenerStats;
        listenerStats.listener = listener;

        // For each transaction
        auto transactionStatsItr = transactionStatsDeque.begin();
        while (transactionStatsItr != transactionStatsDeque.end()) {
            auto& transactionStats = *transactionStatsItr;
            // If this transaction is still registering, it is not safe to send a callback
            // because there could be surface controls that haven't been added to
            // transaction stats or mPendingTransactions.
            if (isRegisteringTransaction(listener, transactionStats.callbackIds)) {
                break;
            }

            // If we are still waiting on the callback handles for this transaction, stop
            // here because all transaction callbacks for the same listener must come in order
            auto pendingTransactions = mPendingTransactions.find(listener);
            if (pendingTransactions != mPendingTransactions.end() &&
                pendingTransactions->second.count(transactionStats.callbackIds) != 0) {
                break;
            }
            // If the transaction has been latched
            if (transactionStats.latchTime >= 0 &&
                !containsOnCommitCallbacks(transactionStats.callbackIds)) {
                if (!mPresentFence) {
                    break;
                }
                transactionStats.presentFence = mPresentFence;
            }

            // Remove the transaction from completed to the callback
            listenerStats.transactionStats.push_back(std::move(transactionStats));
            transactionStatsItr = transactionStatsDeque.erase(transactionStatsItr);
        }
        // If the listener has completed transactions
        if (!listenerStats.transactionStats.empty()) {
            // If the listener is still alive
            if (listener->isBinderAlive()) {
                // Send callback.  The listener stored in listenerStats
                // comes from the cross-process setTransactionState call to
                // SF.  This MUST be an ITransactionCompletedListener.  We
                // keep it as an IBinder due to consistency reasons: if we
                // interface_cast at the IPC boundary when reading a Parcel,
                // we get pointers that compare unequal in the SF process.
                interface_cast<ITransactionCompletedListener>(listenerStats.listener)
                        ->onTransactionCompleted(listenerStats);
                if (transactionStatsDeque.empty()) {
                    listener->unlinkToDeath(mDeathRecipient);
                    completedTransactionsItr =
                            mCompletedTransactions.erase(completedTransactionsItr);
                } else {
                    completedTransactionsItr++;
                }
            } else {
                completedTransactionsItr =
                        mCompletedTransactions.erase(completedTransactionsItr);
            }
        } else {
            completedTransactionsItr++;
        }
    }

    if (mPresentFence) {
        mPresentFence.clear();
    }
}