chromium/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedReliabilityLoggingBridge.java

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.feed;

import androidx.annotation.Nullable;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;

import org.chromium.chrome.browser.xsurface.feed.FeedLaunchReliabilityLogger;
import org.chromium.chrome.browser.xsurface.feed.FeedUserInteractionReliabilityLogger;
import org.chromium.chrome.browser.xsurface.feed.FeedUserInteractionReliabilityLogger.PaginationResult;
import org.chromium.components.feed.proto.wire.ReliabilityLoggingEnums.DiscoverAboveTheFoldRenderResult;
import org.chromium.components.feed.proto.wire.ReliabilityLoggingEnums.DiscoverLaunchResult;

/** JNI bridge making reliability logging methods available to native code. */
@JNINamespace("feed::android")
public class FeedReliabilityLoggingBridge {
    private final long mNativePtr;
    private FeedLaunchReliabilityLogger mLaunchLogger;
    private @Nullable FeedUserInteractionReliabilityLogger mUserInteractionLogger;
    private DiscoverAboveTheFoldRenderResult mRenderResult;
    private boolean mRenderingStarted;
    private DiscoverLaunchResult mLaunchResult;

    public static org.jni_zero.JniStaticTestMocker<FeedReliabilityLoggingBridge.Natives>
            getTestHooksForTesting() {
        return FeedReliabilityLoggingBridgeJni.TEST_HOOKS;
    }

    public FeedReliabilityLoggingBridge() {
        // mLaunchLogger should be null until FeedStream.bind() calls setLogger(). We don't expect
        // mLaunchLogger to be used until then.
        mNativePtr = FeedReliabilityLoggingBridgeJni.get().init(this);
    }

    public void setLogger(FeedReliabilityLogger logger) {
        if (logger != null) {
            mLaunchLogger = logger.getLaunchLogger();
            mUserInteractionLogger = logger.getUserInteractionLogger();
        }
        // The launch logger may not be provided if FeedReliabilityLogger is mocked in testing.
        // In this case, use the default no-op instance.
        if (mLaunchLogger == null) {
            mLaunchLogger = new FeedLaunchReliabilityLogger() {};
        }
    }

    public long getNativePtr() {
        return mNativePtr;
    }

    public void destroy() {
        FeedReliabilityLoggingBridgeJni.get().destroy(mNativePtr);
    }

    @CalledByNative
    public void logOtherLaunchStart(long timestamp) {
        mLaunchLogger.logFeedLaunchOtherStart(timestamp);
    }

    @CalledByNative
    public void logCacheReadStart(long timestamp) {
        mLaunchLogger.logCacheReadStart(timestamp);
    }

    @CalledByNative
    public void logCacheReadEnd(long timestamp, int cacheReadResult) {
        mLaunchLogger.logCacheReadEnd(timestamp, cacheReadResult);
    }

    @CalledByNative
    public void logFeedRequestStart(int requestId, long timestamp) {
        mLaunchLogger
                .getNetworkRequestReliabilityLogger2(requestId)
                .logFeedQueryRequestStart(timestamp);
    }

    @CalledByNative
    public void logWebFeedRequestStart(int requestId, long timestamp) {
        mLaunchLogger
                .getNetworkRequestReliabilityLogger2(requestId)
                .logWebFeedRequestStart(timestamp);
    }

    @CalledByNative
    public void logSingleWebFeedRequestStart(int requestId, long timestamp) {
        mLaunchLogger
                .getNetworkRequestReliabilityLogger2(requestId)
                .logSingleWebFeedRequestStart(timestamp);
    }

    @CalledByNative
    public void logActionsUploadRequestStart(int requestId, long timestamp) {
        mLaunchLogger
                .getNetworkRequestReliabilityLogger2(requestId)
                .logActionsUploadRequestStart(timestamp);
    }

    @CalledByNative
    public void logRequestSent(int requestId, long timestamp) {
        mLaunchLogger.getNetworkRequestReliabilityLogger2(requestId).logRequestSent(timestamp);
    }

    @CalledByNative
    public void logResponseReceived(
            int requestId,
            long serverRecvTimestamp,
            long serverSendTimestamp,
            long clientRecvTimestamp) {
        mLaunchLogger
                .getNetworkRequestReliabilityLogger2(requestId)
                .logResponseReceived(serverRecvTimestamp, serverSendTimestamp, clientRecvTimestamp);
    }

    @CalledByNative
    public void logRequestFinished(int requestId, long timestamp, int canonicalStatus) {
        mLaunchLogger
                .getNetworkRequestReliabilityLogger2(requestId)
                .logRequestFinished(timestamp, canonicalStatus);
    }

    @CalledByNative
    public void logAboveTheFoldRender(long timestamp, int aboveTheFoldRenderResult) {
        // It's possible to be called multiple times per launch, so we only log "render start" from
        // the first call.
        if (!mRenderingStarted) {
            mLaunchLogger.logAtfRenderStart(timestamp);
            mRenderingStarted = true;
        }

        // Record mRenderResult to be logged later by onStreamUpdateFinished().
        mRenderResult = DiscoverAboveTheFoldRenderResult.forNumber(aboveTheFoldRenderResult);
        if (mRenderResult == null) {
            mRenderResult = DiscoverAboveTheFoldRenderResult.INTERNAL_ERROR;
        }
    }

    @CalledByNative
    public void logLoadingIndicatorShown(long timestamp) {
        mLaunchLogger.logLoadingIndicatorShown(timestamp);
    }

    @CalledByNative
    public void logLaunchFinishedAfterStreamUpdate(int discoverLaunchResult) {
        if (mLaunchResult != null) return;

        mLaunchResult = DiscoverLaunchResult.forNumber(discoverLaunchResult);
        if (mLaunchResult == null) {
            mLaunchResult = DiscoverLaunchResult.ABORTED_DUE_TO_INVALID_STATE;
        }
    }

    @CalledByNative
    public void logLoadMoreStarted() {
        if (mUserInteractionLogger != null) {
            mUserInteractionLogger.onPaginationStarted();
        }
    }

    @CalledByNative
    public void logLoadMoreActionUploadRequestStarted() {
        if (mUserInteractionLogger != null) {
            mUserInteractionLogger.onPaginationActionUploadRequestStarted();
        }
    }

    @CalledByNative
    public void logLoadMoreRequestSent() {
        if (mUserInteractionLogger != null) {
            mUserInteractionLogger.onPaginationRequestSent();
        }
    }

    @CalledByNative
    public void logLoadMoreResponseReceived(long serverRecvTimestamp, long serverSendTimestamp) {
        if (mUserInteractionLogger != null) {
            mUserInteractionLogger.onPaginationResponseReceived(
                    serverRecvTimestamp, serverSendTimestamp);
        }
    }

    @CalledByNative
    public void logLoadMoreRequestFinished(int canonicalStatus) {
        if (mUserInteractionLogger != null) {
            mUserInteractionLogger.onPaginationRequestFinished(canonicalStatus);
        }
    }

    @CalledByNative
    public void logLoadMoreEnded(boolean success) {
        if (mUserInteractionLogger != null) {
            mUserInteractionLogger.onPaginationEnded(
                    success ? PaginationResult.SUCCESS_WITH_MORE_FEED : PaginationResult.FAILURE);
        }
    }

    @CalledByNative
    public void reportExperiments(@JniType("std::vector<int32_t>") int[] experimentIds) {
        mLaunchLogger.reportExperiments(experimentIds);
        if (mUserInteractionLogger != null) {
            mUserInteractionLogger.reportExperiments(experimentIds);
        }
    }

    public void onStreamUpdateFinished() {
        if (!mLaunchLogger.isLaunchInProgress()) return;

        if (mRenderResult != null) {
            mLaunchLogger.logAtfRenderEnd(System.nanoTime(), mRenderResult.getNumber());
            mRenderResult = null;
        }

        if (mLaunchResult != null) {
            mLaunchLogger.logLaunchFinished(System.nanoTime(), mLaunchResult.getNumber());
            mLaunchResult = null;
        }

        mRenderingStarted = false;
    }

    public void onStreamUpdateError() {
        if (!mLaunchLogger.isLaunchInProgress()) return;
        mLaunchLogger.logAtfRenderEnd(
                System.nanoTime(), DiscoverAboveTheFoldRenderResult.INTERNAL_ERROR.getNumber());
        mLaunchLogger.logLaunchFinished(
                System.nanoTime(), DiscoverLaunchResult.FAILED_TO_RENDER.getNumber());
        mRenderingStarted = false;
        mRenderResult = null;
        mLaunchResult = null;
    }

    @NativeMethods
    public interface Natives {
        long init(FeedReliabilityLoggingBridge thisRef);

        void destroy(long nativeFeedReliabilityLoggingBridge);
    }
}