chromium/chrome/browser/page_image_service/android/java/src/org/chromium/chrome/browser/page_image_service/ImageServiceBridge.java

// Copyright 2024 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.page_image_service;

import android.graphics.Bitmap;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;

import org.chromium.base.Callback;
import org.chromium.base.CallbackController;
import org.chromium.chrome.browser.page_image_service.ImageServiceMetrics.SalientImageUrlFetchResult;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.image_fetcher.ImageFetcher;
import org.chromium.page_image_service.mojom.ClientId;
import org.chromium.url.GURL;

import java.util.HashMap;
import java.util.Map;

/** Allows java access to the native ImageService. */
public class ImageServiceBridge {
    private final @ClientId.EnumType int mClientId;
    private final String mImageFetcherClientName;
    // Cache the results for repeated queries to avoid extra calls through the JNI/network.
    private final Map<GURL, GURL> mSalientImageUrlCache = new HashMap<>();
    private final CallbackController mCallbackController = new CallbackController();
    private final ImageFetcher mImageFetcher;

    private long mNativeImageServiceBridge;

    /**
     * @param clientId The ImageService client the salient image url is being fetched for.
     * @param imageFetcherClientName The string name of the ImageFetcher client.
     * @param profile Used to acquire dependencies in native.
     * @param imageFetcher The fetcher to fetch the image.
     */
    public ImageServiceBridge(
            @ClientId.EnumType int clientId,
            @NonNull String imageFetcherClientName,
            @NonNull Profile profile,
            @NonNull ImageFetcher imageFetcher) {
        mClientId = clientId;
        mImageFetcherClientName = imageFetcherClientName;
        mNativeImageServiceBridge = ImageServiceBridgeJni.get().init(profile);
        mImageFetcher = imageFetcher;
    }

    public void destroy() {
        ImageServiceBridgeJni.get().destroy(mNativeImageServiceBridge);
        mSalientImageUrlCache.clear();
        mCallbackController.destroy();
        mImageFetcher.destroy();
    }

    /** Cleanup any cached bitmap or URLs in memory. */
    public void clear() {
        mImageFetcher.clear();
    }

    /**
     * Fetch the salient image {@link GURL} for the given page {@link GURL}.
     *
     * @param isAccountData Whether the underlying datatype being fetched for is account-bound.
     * @param pageUrl The url of the page the salient image is being fetched for.
     * @param imageSize The size of the salient image.
     * @param callback The callback to receive the salient image url.
     */
    @Deprecated
    public void fetchImageFor(
            boolean isAccountData,
            @NonNull GURL pageUrl,
            int imageSize,
            Callback<Bitmap> callback) {
        Callback<GURL> imageUrlCallback =
                mCallbackController.makeCancelable(
                        (imageUrl) -> {
                            if (imageUrl == null) {
                                callback.onResult(null);
                                return;
                            }

                            mImageFetcher.fetchImage(
                                    ImageFetcher.Params.create(
                                            imageUrl,
                                            mImageFetcherClientName,
                                            imageSize,
                                            imageSize),
                                    callback);
                        });
        fetchImageUrlFor(isAccountData, pageUrl, imageUrlCallback);
    }

    /**
     * Fetches the URL of the salient image and pass to the callback. The URL of the salient image
     * will be cached.
     */
    @VisibleForTesting
    void fetchImageUrlFor(
            boolean isAccountData, @NonNull GURL pageUrl, @NonNull Callback<GURL> callback) {
        if (mSalientImageUrlCache.containsKey(pageUrl)) {
            GURL cacheResult = mSalientImageUrlCache.get(pageUrl);
            callback.onResult(cacheResult);
            ImageServiceMetrics.recordFetchImageUrlResult(
                    mClientId,
                    cacheResult == null
                            ? SalientImageUrlFetchResult.FAILED_FROM_CACHE
                            : SalientImageUrlFetchResult.SUCCEED_FROM_CACHE);
            return;
        }

        ImageServiceBridgeJni.get()
                .fetchImageUrlFor(
                        mNativeImageServiceBridge,
                        isAccountData,
                        mClientId,
                        pageUrl,
                        mCallbackController.makeCancelable(
                                (salientImageUrl) -> {
                                    mSalientImageUrlCache.put(pageUrl, salientImageUrl);
                                    callback.onResult(salientImageUrl);
                                    ImageServiceMetrics.recordFetchImageUrlResult(
                                            mClientId,
                                            salientImageUrl == null
                                                    ? SalientImageUrlFetchResult.FAILED_FROM_NETWORK
                                                    : SalientImageUrlFetchResult
                                                            .SUCCEED_FROM_NETWORK);
                                }));
    }

    static String clientIdToString(@ClientId.EnumType int clientId) {
        return ImageServiceBridgeJni.get().clientIdToString(clientId);
    }

    boolean isUrlCachedForTesting(GURL pageUrl, GURL imageUrl) {
        return mSalientImageUrlCache.containsKey(pageUrl)
                && mSalientImageUrlCache.containsValue(imageUrl);
    }

    boolean isUrlCacheEmptyForTesting() {
        return mSalientImageUrlCache.isEmpty();
    }

    @NativeMethods
    public interface Natives {
        // Static methods.
        long init(@JniType("Profile*") Profile profile);

        @JniType("std::string")
        String clientIdToString(@ClientId.EnumType int clientId);

        // Instance methods.
        void destroy(long nativeImageServiceBridge);

        void fetchImageUrlFor(
                long nativeImageServiceBridge,
                boolean isAccountData,
                @ClientId.EnumType int clientId,
                @JniType("GURL") GURL pageUrl,
                Callback<GURL> callback);
    }
}