chromium/chrome/browser/ui/android/favicon/java/src/org/chromium/chrome/browser/ui/favicon/FaviconHelper.java

// Copyright 2013 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.ui.favicon;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.content.res.AppCompatResources;

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

import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.url.GURL;

import java.util.List;

/**
 * This is a helper class to use favicon_service.cc's functionality.
 *
 * You can request a favicon image by web page URL. Note that an instance of
 * this class should be created & used & destroyed (by destroy()) in the same
 * thread due to the C++ base::CancelableTaskTracker class
 * requirement.
 */
public class FaviconHelper {
    private long mNativeFaviconHelper;

    /** Callback interface for getting the result from getLocalFaviconImageForURL method. */
    public interface FaviconImageCallback {
        /**
         * This method will be called when the result favicon is ready.
         *
         * @param image Favicon image.
         * @param iconUrl Favicon image's icon url.
         */
        @CalledByNative("FaviconImageCallback")
        public void onFaviconAvailable(Bitmap image, @JniType("GURL") GURL iconUrl);
    }

    /** Similar to {@link FaviconImageCallback} but with a list of urls used in the image. */
    public interface ComposedFaviconImageCallback {
        /**
         * @param image A composed image that contains some or all of the requested favicons.
         * @param iconUrls An ordered array of the icon urls that were used.
         */
        @CalledByNative("ComposedFaviconImageCallback")
        public void onComposedFaviconAvailable(
                Bitmap image, @JniType("std::vector<GURL>") GURL[] iconUrls);
    }

    /** Helper for generating default favicons and sharing the same icon between multiple views. */
    public static class DefaultFaviconHelper {
        private Bitmap mChromeDarkBitmap;
        private Bitmap mChromeLightBitmap;
        private Bitmap mDefaultDarkBitmap;
        private Bitmap mDefaultLightBitmap;

        private int getResourceId(GURL url) {
            return UrlUtilities.isInternalScheme(url)
                    ? R.drawable.chromelogo16
                    : R.drawable.ic_globe_24dp;
        }

        private Bitmap createBitmap(Context context, int resourceId, boolean useDarkIcon) {
            Resources resources = context.getResources();
            Drawable drawable = AppCompatResources.getDrawable(context, resourceId);
            int faviconSize = resources.getDimensionPixelSize(R.dimen.default_favicon_size);
            Bitmap tintedBitmap =
                    Bitmap.createBitmap(faviconSize, faviconSize, Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(tintedBitmap);
            drawable.setBounds(0, 0, faviconSize, faviconSize);
            final @ColorInt int tintColor =
                    context.getColor(
                            useDarkIcon
                                    ? R.color.default_icon_color_baseline
                                    : R.color.default_icon_color_light);
            drawable.setTint(tintColor);
            drawable.draw(c);
            return tintedBitmap;
        }

        /**
         * Generate a default favicon bitmap for the given URL.
         * @param context The {@link Context} to fetch the icons and tint.
         * @param url The URL of the page whose icon is being generated.
         * @param useDarkIcon Whether a dark icon should be used.
         * @return The favicon.
         */
        public Bitmap getDefaultFaviconBitmap(Context context, GURL url, boolean useDarkIcon) {
            boolean isInternal = UrlUtilities.isInternalScheme(url);
            Bitmap bitmap =
                    isInternal
                            ? (useDarkIcon ? mChromeDarkBitmap : mChromeLightBitmap)
                            : (useDarkIcon ? mDefaultDarkBitmap : mDefaultLightBitmap);
            if (bitmap != null) return bitmap;
            bitmap = createBitmap(context, getResourceId(url), useDarkIcon);
            if (isInternal && useDarkIcon) {
                mChromeDarkBitmap = bitmap;
            } else if (isInternal) {
                mChromeLightBitmap = bitmap;
            } else if (useDarkIcon) {
                mDefaultDarkBitmap = bitmap;
            } else {
                mDefaultLightBitmap = bitmap;
            }
            return bitmap;
        }

        /**
         * Generate a default favicon drawable for the given URL.
         * @param context The {@link Context} used to fetch the default icons and tint.
         * @param url The URL of the page whose icon is being generated.
         * @param useDarkIcon Whether a dark icon should be used.
         * @return The favicon.
         */
        public Drawable getDefaultFaviconDrawable(Context context, GURL url, boolean useDarkIcon) {
            return new BitmapDrawable(
                    context.getResources(), getDefaultFaviconBitmap(context, url, useDarkIcon));
        }

        /**
         * Gives the favicon for given resource id with current theme.
         * @param context The {@link Context} used to fetch the default icons and tint.
         * @param resourceId The integer that represents the id of the icon.
         * @param useDarkIcon Whether a dark icon should be used.
         * @return The favicon
         */
        public Bitmap getThemifiedBitmap(Context context, int resourceId, boolean useDarkIcon) {
            return createBitmap(context, resourceId, useDarkIcon);
        }

        /** Clears any of the cached default drawables. */
        public void clearCache() {
            mChromeDarkBitmap = null;
            mChromeLightBitmap = null;
            mDefaultDarkBitmap = null;
            mDefaultLightBitmap = null;
        }
    }

    /** Allocate and initialize the C++ side of this class. */
    public FaviconHelper() {
        mNativeFaviconHelper = FaviconHelperJni.get().init();
    }

    /**
     * Clean up the C++ side of this class. After the call, this class instance shouldn't be used.
     */
    public void destroy() {
        assert mNativeFaviconHelper != 0;
        FaviconHelperJni.get().destroy(mNativeFaviconHelper);
        mNativeFaviconHelper = 0;
    }

    /**
     * Get Favicon bitmap for the requested arguments. Retrieves favicons only for pages the user
     * has visited on the current device.
     * @param profile Profile used for the FaviconService construction.
     * @param pageUrl The target Page URL to get the favicon.
     * @param desiredSizeInPixel The size of the favicon in pixel we want to get.
     * @param faviconImageCallback A method to be called back when the result is available. Note
     *         that this callback is not called if this method returns false.
     * @return True if GetLocalFaviconImageForURL is successfully called.
     */
    public boolean getLocalFaviconImageForURL(
            Profile profile,
            GURL pageUrl,
            int desiredSizeInPixel,
            FaviconImageCallback faviconImageCallback) {
        assert mNativeFaviconHelper != 0;
        return FaviconHelperJni.get()
                .getLocalFaviconImageForURL(
                        mNativeFaviconHelper,
                        profile,
                        pageUrl,
                        desiredSizeInPixel,
                        faviconImageCallback);
    }

    /**
     * Get foreign Favicon bitmap for the requested arguments.
     * @param profile Profile used for the FaviconService construction.
     * @param pageUrl The target Page URL to get the favicon.
     * @param desiredSizeInPixel The size of the favicon in pixel we want to get.
     * @param faviconImageCallback A method to be called back when the result is available. Note
     *         that this callback is not called if this method returns false.
     * @return favicon Bitmap corresponding to the pageUrl.
     */
    public boolean getForeignFaviconImageForURL(
            Profile profile,
            GURL pageUrl,
            int desiredSizeInPixel,
            FaviconImageCallback faviconImageCallback) {
        assert mNativeFaviconHelper != 0;
        return FaviconHelperJni.get()
                .getForeignFaviconImageForURL(
                        mNativeFaviconHelper,
                        profile,
                        pageUrl,
                        desiredSizeInPixel,
                        faviconImageCallback);
    }

    /**
     * Get a composed, up to 4, Favicon bitmap for the requested arguments.
     * @param profile Profile used for the FaviconService construction.
     * @param urls The list of URLs whose favicon are requested to compose. Size should be 2 to 4.
     * @param desiredSizeInPixel The size of the favicon in pixel we want to get.
     * @param composedFaviconImageCallback A method to be called back when the result is available.
     *        Note that this callback is not called if this method returns false.
     * @return True if GetLocalFaviconImageForURL is successfully called.
     */
    public boolean getComposedFaviconImage(
            Profile profile,
            @NonNull List<GURL> urls,
            int desiredSizeInPixel,
            ComposedFaviconImageCallback composedFaviconImageCallback) {
        assert mNativeFaviconHelper != 0;

        if (urls.size() <= 1 || urls.size() > 4) {
            throw new IllegalStateException(
                    "Only able to compose 2 to 4 favicon, but requested " + urls.size());
        }

        return FaviconHelperJni.get()
                .getComposedFaviconImage(
                        mNativeFaviconHelper,
                        profile,
                        urls.toArray(new GURL[0]),
                        desiredSizeInPixel,
                        composedFaviconImageCallback);
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    @NativeMethods
    public interface Natives {
        long init();

        void destroy(long nativeFaviconHelper);

        boolean getComposedFaviconImage(
                long nativeFaviconHelper,
                @JniType("Profile*") Profile profile,
                @JniType("std::vector<GURL>") GURL[] urls,
                int desiredSizeInDip,
                ComposedFaviconImageCallback composedFaviconImageCallback);

        boolean getLocalFaviconImageForURL(
                long nativeFaviconHelper,
                @JniType("Profile*") Profile profile,
                @JniType("GURL") GURL pageUrl,
                int desiredSizeInDip,
                FaviconImageCallback faviconImageCallback);

        boolean getForeignFaviconImageForURL(
                long nativeFaviconHelper,
                @JniType("Profile*") Profile profile,
                @JniType("GURL") GURL pageUrl,
                int desiredSizeInDip,
                FaviconImageCallback faviconImageCallback);
    }
}