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

// Copyright 2023 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.VisibleForTesting;

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

import org.chromium.base.Callback;
import org.chromium.chrome.browser.feed.v2.FeedUserActionType;
import org.chromium.url.GURL;

/** Java bridge to feed::SurfaceRenderer, provides shallow JNI bindings. */
@JNINamespace("feed::android")
public class FeedSurfaceRendererBridge {
    private int mSurfaceId;
    private long mNativeSurfaceRenderer;
    private Renderer mRenderer;

    /**
     * Calls from native to Java to implement rendering for the feed. See `feed::SurfaceRenderer`
     * for documentation.
     */
    public interface Renderer {
        void replaceDataStoreEntry(String key, byte[] data);

        void removeDataStoreEntry(String key);

        void onStreamUpdated(byte[] data);
    }

    /** Factory for FeedSurfaceRendererBridge. */
    public interface Factory {
        default FeedSurfaceRendererBridge create(
                Renderer renderer,
                FeedReliabilityLoggingBridge reliabilityLoggingBridge,
                @StreamKind int streamKind,
                SingleWebFeedParameters webFeedParameters) {
            return new FeedSurfaceRendererBridge(
                    renderer, reliabilityLoggingBridge, streamKind, webFeedParameters);
        }
    }

    public FeedSurfaceRendererBridge(
            Renderer renderer,
            FeedReliabilityLoggingBridge reliabilityLoggingBridge,
            @StreamKind int streamKind,
            SingleWebFeedParameters webFeedParameters) {
        mRenderer = renderer;
        if (streamKind == StreamKind.SINGLE_WEB_FEED) {
            mNativeSurfaceRenderer =
                    FeedSurfaceRendererBridgeJni.get()
                            .initWebFeed(
                                    this,
                                    webFeedParameters.getWebFeedId(),
                                    reliabilityLoggingBridge.getNativePtr(),
                                    webFeedParameters.getEntryPoint());
        } else {
            mNativeSurfaceRenderer =
                    FeedSurfaceRendererBridgeJni.get()
                            .init(this, streamKind, reliabilityLoggingBridge.getNativePtr());
        }
        mSurfaceId = FeedSurfaceRendererBridgeJni.get().getSurfaceId(mNativeSurfaceRenderer);
    }

    public int surfaceId() {
        return mSurfaceId;
    }

    public void destroy() {
        assert mRenderer != null;
        FeedSurfaceRendererBridgeJni.get().destroy(mNativeSurfaceRenderer);
        mNativeSurfaceRenderer = 0;
        mRenderer = null;
    }

    @CalledByNative
    private void replaceDataStoreEntry(String key, byte[] data) {
        if (mRenderer == null) {
            return;
        }
        mRenderer.replaceDataStoreEntry(key, data);
    }

    @CalledByNative
    private void removeDataStoreEntry(String key) {
        if (mRenderer == null) {
            return;
        }
        mRenderer.removeDataStoreEntry(key);
    }

    /** Called when the stream update content is available. The content will get passed to UI */
    @CalledByNative
    private void onStreamUpdated(byte[] data) {
        if (mRenderer == null) {
            return;
        }
        mRenderer.onStreamUpdated(data);
    }

    //
    // Methods bound to the surface instance, which do nothing after destroy().
    //

    void loadMore(Callback<Boolean> callback) {
        // Cancel if destroyed.
        if (mRenderer == null) {
            return;
        }
        FeedSurfaceRendererBridgeJni.get().loadMore(mNativeSurfaceRenderer, callback);
    }

    void manualRefresh(Callback<Boolean> callback) {
        // Cancel if destroyed.
        if (mRenderer == null) {
            return;
        }
        FeedSurfaceRendererBridgeJni.get().manualRefresh(mNativeSurfaceRenderer, callback);
    }

    void surfaceOpened() {
        // Cancel if destroyed.
        if (mRenderer == null) {
            return;
        }
        FeedSurfaceRendererBridgeJni.get().surfaceOpened(mNativeSurfaceRenderer);
    }

    void surfaceClosed() {
        // Cancel if destroyed.
        if (mRenderer == null) {
            return;
        }
        FeedSurfaceRendererBridgeJni.get().surfaceClosed(mNativeSurfaceRenderer);
    }

    //
    // Methods which may be called after destroy().
    //

    void reportFeedViewed() {
        FeedSurfaceRendererBridgeJni.get().reportFeedViewed(mSurfaceId);
    }

    void reportSliceViewed(String sliceId) {
        FeedSurfaceRendererBridgeJni.get().reportSliceViewed(mSurfaceId, sliceId);
    }

    void reportPageLoaded(boolean inNewTab) {
        FeedSurfaceRendererBridgeJni.get().reportPageLoaded(mSurfaceId, inNewTab);
    }

    void reportOpenAction(GURL url, String sliceId, @OpenActionType int openActionType) {
        FeedSurfaceRendererBridgeJni.get()
                .reportOpenAction(mSurfaceId, url, sliceId, openActionType);
    }

    void reportOpenVisitComplete(long visitTimeMs) {
        FeedSurfaceRendererBridgeJni.get().reportOpenVisitComplete(mSurfaceId, visitTimeMs);
    }

    void reportOtherUserAction(@FeedUserActionType int userAction) {
        FeedSurfaceRendererBridgeJni.get().reportOtherUserAction(mSurfaceId, userAction);
    }

    void reportStreamScrolled(int distanceDp) {
        FeedSurfaceRendererBridgeJni.get().reportStreamScrolled(mSurfaceId, distanceDp);
    }

    void reportStreamScrollStart() {
        FeedSurfaceRendererBridgeJni.get().reportStreamScrollStart(mSurfaceId);
    }

    void updateUserProfileOnLinkClick(GURL url, long[] mids) {
        FeedSurfaceRendererBridgeJni.get().updateUserProfileOnLinkClick(url, mids);
    }

    void processThereAndBackAgain(byte[] data, byte[] loggingParameters) {
        FeedSurfaceRendererBridgeJni.get().processThereAndBackAgain(data, loggingParameters);
    }

    int executeEphemeralChange(byte[] data) {
        return FeedSurfaceRendererBridgeJni.get().executeEphemeralChange(mSurfaceId, data);
    }

    void commitEphemeralChange(int changeId) {
        FeedSurfaceRendererBridgeJni.get().commitEphemeralChange(mSurfaceId, changeId);
    }

    void discardEphemeralChange(int changeId) {
        FeedSurfaceRendererBridgeJni.get().discardEphemeralChange(mSurfaceId, changeId);
    }

    long getLastFetchTimeMs() {
        return FeedSurfaceRendererBridgeJni.get().getLastFetchTimeMs(mSurfaceId);
    }

    void reportInfoCardTrackViewStarted(int type) {
        FeedSurfaceRendererBridgeJni.get().reportInfoCardTrackViewStarted(mSurfaceId, type);
    }

    void reportInfoCardViewed(int type, int minimumViewIntervalSeconds) {
        FeedSurfaceRendererBridgeJni.get()
                .reportInfoCardViewed(mSurfaceId, type, minimumViewIntervalSeconds);
    }

    void reportInfoCardClicked(int type) {
        FeedSurfaceRendererBridgeJni.get().reportInfoCardClicked(mSurfaceId, type);
    }

    void reportInfoCardDismissedExplicitly(int type) {
        FeedSurfaceRendererBridgeJni.get().reportInfoCardDismissedExplicitly(mSurfaceId, type);
    }

    void resetInfoCardStates(int type) {
        FeedSurfaceRendererBridgeJni.get().resetInfoCardStates(mSurfaceId, type);
    }

    void invalidateContentCacheFor(@StreamKind int feedToInvalidate) {
        FeedSurfaceRendererBridgeJni.get().invalidateContentCacheFor(feedToInvalidate);
    }

    void reportContentSliceVisibleTimeForGoodVisits(long elapsedMs) {
        FeedSurfaceRendererBridgeJni.get()
                .reportContentSliceVisibleTimeForGoodVisits(mSurfaceId, elapsedMs);
    }

    void contentViewed(long docid) {
        FeedSurfaceRendererBridgeJni.get().contentViewed(mSurfaceId, docid);
    }

    @NativeMethods
    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public interface Natives {
        // Constructors.
        long init(
                FeedSurfaceRendererBridge caller,
                @StreamKind int streamKind,
                long nativeFeedReliabilityLoggingBridge);

        long initWebFeed(
                FeedSurfaceRendererBridge caller,
                byte[] webFeedId,
                long nativeFeedReliabilityLoggingBridge,
                int entryPoint);

        // Member functions, must not be called after destroy().
        void destroy(long nativeFeedSurfaceRendererBridge);

        void loadMore(long nativeFeedSurfaceRendererBridge, Callback<Boolean> callback);

        void manualRefresh(long nativeFeedSurfaceRendererBridge, Callback<Boolean> callback);

        int getSurfaceId(long nativeFeedSurfaceRendererBridge);

        void surfaceOpened(long nativeFeedSurfaceRendererBridge);

        void surfaceClosed(long nativeFeedSurfaceRendererBridge);

        // Static functions, can be called after creation, and destroy().
        void reportFeedViewed(int surfaceId);

        void reportSliceViewed(int surfaceId, String sliceId);

        void reportPageLoaded(int surfaceId, boolean inNewTab);

        void reportOpenAction(
                int surfaceId, GURL url, String sliceId, @OpenActionType int openActionType);

        void reportOpenVisitComplete(int surfaceId, long visitTimeMs);

        void reportOtherUserAction(int surfaceId, @FeedUserActionType int userAction);

        void reportStreamScrolled(int surfaceId, int distanceDp);

        void reportStreamScrollStart(int surfaceId);

        void updateUserProfileOnLinkClick(GURL url, long[] mids);

        void processThereAndBackAgain(byte[] data, byte[] loggingParameters);

        int executeEphemeralChange(int surfaceId, byte[] data);

        void commitEphemeralChange(int surfaceId, int changeId);

        void discardEphemeralChange(int surfaceId, int changeId);

        long getLastFetchTimeMs(int surfaceId);

        void reportInfoCardTrackViewStarted(int surfaceId, int type);

        void reportInfoCardViewed(int surfaceId, int type, int minimumViewIntervalSeconds);

        void reportInfoCardClicked(int surfaceId, int type);

        void reportInfoCardDismissedExplicitly(int surfaceId, int type);

        void resetInfoCardStates(int surfaceId, int type);

        void invalidateContentCacheFor(@StreamKind int feedToInvalidate);

        void reportContentSliceVisibleTimeForGoodVisits(int surfaceId, long elapsedMs);

        void contentViewed(int surfaceId, long docid);
    }
}