First part of fetching a favicon

This change adds a new class (favicon_caller) which wraps the calls to the API large_icon_service in order to request and fetch a favicon for a particular URL. The implementation is similar to content_suggestions_service.cc.

The website_parent_approval package now exposes new helper methods for Java, that utilise favicon_caller in order to fetch a favicon. These new JNI methods will be consumed by WebsiteParentApproval in a follow up change.

Bug: 1340912
Change-Id: I6457b560c51f31cd54ae8b715d98881f60b3e1c5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3810619
Reviewed-by: Nohemi Fernandez <[email protected]>
Commit-Queue: Anthi Orfanou <[email protected]>
Reviewed-by: Rainhard Findling <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1043425}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 9384821..96c298358 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3255,6 +3255,8 @@
       "ssl/chrome_security_state_model_delegate.h",
       "ssl/known_interception_disclosure_infobar.cc",
       "ssl/known_interception_disclosure_infobar.h",
+      "supervised_user/android/favicon_fetcher.cc",
+      "supervised_user/android/favicon_fetcher.h",
       "sync/android/sync_service_android_bridge.cc",
       "sync/android/sync_service_android_bridge.h",
       "sync/glue/synced_tab_delegate_android.cc",
diff --git a/chrome/browser/supervised_user/android/favicon_fetcher.cc b/chrome/browser/supervised_user/android/favicon_fetcher.cc
new file mode 100644
index 0000000..73e0890
--- /dev/null
+++ b/chrome/browser/supervised_user/android/favicon_fetcher.cc
@@ -0,0 +1,120 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/supervised_user/android/favicon_fetcher.h"
+
+#include <cstddef>
+#include <memory>
+
+#include "base/android/callback_android.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "chrome/browser/favicon/large_icon_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/favicon/core/large_icon_service.h"
+#include "components/favicon_base/favicon_callback.h"
+#include "components/favicon_base/favicon_types.h"
+#include "content/public/browser/browser_context.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "ui/gfx/android/java_bitmap.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_skia_rep.h"
+#include "url/gurl.h"
+
+FaviconFetcher::FaviconFetcher(Profile* profile)
+    : large_icon_service_(
+          LargeIconServiceFactory::GetForBrowserContext(profile)) {}
+
+void FaviconFetcher::OnFaviconDownloaded(
+    const GURL& url,
+    const base::android::ScopedJavaGlobalRef<jobject>& callback,
+    FaviconDimensions faviconDimensions,
+    favicon_base::GoogleFaviconServerRequestStatus status) {
+  if (status == favicon_base::GoogleFaviconServerRequestStatus::SUCCESS) {
+    FetchFavicon(url, false, faviconDimensions.min_source_size_in_pixel,
+                 faviconDimensions.desired_size_in_pixel, std::move(callback));
+  } else {
+    LOG(WARNING)
+        << "Unable to obtain a favicon image with the required specs for "
+        << url.host();
+    delete this;
+  }
+}
+
+void FaviconFetcher::OnGetFaviconFromCacheFinished(
+    const GURL& url,
+    bool continue_to_server,
+    const base::android::ScopedJavaGlobalRef<jobject>& callback,
+    FaviconDimensions faviconDimensions,
+    const favicon_base::LargeIconImageResult& image_result) {
+  if (!image_result.image.IsEmpty()) {
+    SkBitmap faviconBitmap =
+        image_result.image.AsImageSkia().GetRepresentation(1.0f).GetBitmap();
+    // Return the image to the caller by executing the callback and destroy this
+    // instance.
+    base::android::RunObjectCallbackAndroid(
+        callback, gfx::ConvertToJavaBitmap(faviconBitmap));
+    delete this;
+    return;
+  }
+
+  // Try to fetch the favicon from a Google favicon server.
+  if (continue_to_server) {
+    net::NetworkTrafficAnnotationTag traffic_annotation =
+        net::DefineNetworkTrafficAnnotation("favicon_fetcher_get_favicon", R"(
+          semantics {
+            sender: "Favicon"
+            description:
+              "Sends a request to a Google server to retrieve a favicon bitmap "
+              "for a URL that a supervised user has requested approval to "
+              "access from their parents."
+            trigger:
+              "A request can be sent if Chrome does not have a favicon for a "
+              "particular page that supervised users are requesting approval to "
+              "access from their parents."
+            data: "Page URL and desired icon size."
+            destination: GOOGLE_OWNED_SERVICE
+          }
+          policy {
+            cookies_allowed: NO
+            setting: "This feature cannot be disabled by settings."
+            policy_exception_justification: "Not implemented."
+          }
+          comments: "No policy is necessary as the request cannot be turned off via settings."
+            "A request for a favicon will always be sent for supervised users."
+          )");
+    large_icon_service_
+        ->GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
+            url,
+            /*may_page_url_be_private=*/true,
+            /*should_trim_page_url_path=*/false, traffic_annotation,
+            base::BindOnce(&FaviconFetcher::OnFaviconDownloaded,
+                           base::Unretained(this), url, std::move(callback),
+                           faviconDimensions));
+  } else {
+    delete this;
+  }
+}
+
+void FaviconFetcher::FetchFavicon(
+    const GURL& url,
+    bool continue_to_server,
+    int min_source_side_size_in_pixel,
+    int desired_side_size_in_pixel,
+    const base::android::ScopedJavaGlobalRef<jobject>& callback) {
+  FaviconDimensions faviconDimensions;
+  faviconDimensions.min_source_size_in_pixel = min_source_side_size_in_pixel;
+  faviconDimensions.desired_size_in_pixel = desired_side_size_in_pixel;
+
+  large_icon_service_->GetLargeIconImageOrFallbackStyleForPageUrl(
+      url, faviconDimensions.min_source_size_in_pixel,
+      faviconDimensions.desired_size_in_pixel,
+      base::BindOnce(&FaviconFetcher::OnGetFaviconFromCacheFinished,
+                     base::Unretained(this), url, continue_to_server,
+                     std::move(callback), faviconDimensions),
+      &task_tracker_);
+}
diff --git a/chrome/browser/supervised_user/android/favicon_fetcher.h b/chrome/browser/supervised_user/android/favicon_fetcher.h
new file mode 100644
index 0000000..66810c94
--- /dev/null
+++ b/chrome/browser/supervised_user/android/favicon_fetcher.h
@@ -0,0 +1,68 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SUPERVISED_USER_ANDROID_FAVICON_FETCHER_H_
+#define CHROME_BROWSER_SUPERVISED_USER_ANDROID_FAVICON_FETCHER_H_
+
+#include <string>
+#include "base/memory/raw_ptr.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/favicon/core/large_icon_service.h"
+#include "components/favicon_base/favicon_callback.h"
+#include "components/favicon_base/favicon_types.h"
+#include "ui/gfx/image/image.h"
+#include "url/gurl.h"
+
+class FaviconFetcher {
+ public:
+  explicit FaviconFetcher(Profile* profile);
+
+  FaviconFetcher() = delete;
+
+  FaviconFetcher(const FaviconFetcher&) = delete;
+
+  ~FaviconFetcher() = default;
+
+  // Initiates a request to fetch a favicon for a specific url.
+  // Wraps the calls to a service that obtains the favicon and returns
+  // through the provided callback. The FaviconFetcher will delete
+  // itself upon callback completion.
+  void FetchFavicon(
+      const GURL& url,
+      bool continue_to_server,
+      int min_source_size_in_pixel,
+      int desired_size_in_pixel,
+      const base::android::ScopedJavaGlobalRef<jobject>& callback);
+
+ private:
+  // Wrapper for favicon specs.
+  struct FaviconDimensions {
+    int min_source_size_in_pixel;
+    // Set to zero to return icon without rescaling.
+    int desired_size_in_pixel;
+  };
+
+  // Provides access to the status of a request for fetching a favicon.
+  void OnFaviconDownloaded(
+      const GURL& url,
+      const base::android::ScopedJavaGlobalRef<jobject>& callback,
+      FaviconDimensions faviconDimensions,
+      favicon_base::GoogleFaviconServerRequestStatus status);
+
+  // Returns the downloaded favicon through the provided callback.
+  // If the favicon is not present in cache, requests it from google servers.
+  void OnGetFaviconFromCacheFinished(
+      const GURL& url,
+      bool continue_to_server,
+      const base::android::ScopedJavaGlobalRef<jobject>& callback,
+      FaviconDimensions faviconDimensions,
+      const favicon_base::LargeIconImageResult& image_result);
+
+  // Required for execution of icon fetching.
+  raw_ptr<favicon::LargeIconService> large_icon_service_;
+  base::CancelableTaskTracker task_tracker_;
+};
+
+#endif  // CHROME_BROWSER_SUPERVISED_USER_ANDROID_FAVICON_CALLER_H_
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java
index a3b8f89..b3a31d4 100644
--- a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java
@@ -4,6 +4,9 @@
 
 package org.chromium.chrome.browser.supervised_user;
 
+import android.graphics.Bitmap;
+
+import org.chromium.base.Callback;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.browser.supervised_user.website_approval.WebsiteApprovalCoordinator;
@@ -14,6 +17,25 @@
  * Requests approval from a parent of a supervised user to unblock navigation to a given URL.
  */
 class WebsiteParentApproval {
+    // Favicon default specs
+    private static final int FAVICON_MIN_SOURCE_SIZE_PIXEL = 16;
+    private static final int FAVICON_DESIRED_SIZE_PIXEL = 32;
+
+    /**
+     * Wrapper class used to store a fetched favicon.
+     */
+    private static final class FaviconHelper {
+        Bitmap mFavicon;
+
+        public void setFavicon(Bitmap favicon) {
+            mFavicon = favicon;
+        }
+
+        public Bitmap getFavicon() {
+            return mFavicon;
+        }
+    }
+
     /**
      * Whether or not local (i.e. on-device) approval is supported.
      *
@@ -42,13 +64,17 @@
     private static void requestLocalApproval(WindowAndroid windowAndroid, GURL url) {
         // First ask the parent to authenticate.
         ParentAuthDelegate delegate = new ParentAuthDelegateImpl();
+        FaviconHelper faviconHelper = new FaviconHelper();
         delegate.requestLocalAuth(windowAndroid, url,
-                (success) -> { onParentAuthComplete(success, windowAndroid, url); });
+                (success) -> { onParentAuthComplete(success, windowAndroid, url, faviconHelper); });
+
+        WebsiteParentApprovalJni.get().fetchFavicon(url, FAVICON_MIN_SOURCE_SIZE_PIXEL,
+                FAVICON_DESIRED_SIZE_PIXEL, (Bitmap favicon) -> faviconHelper.setFavicon(favicon));
     }
 
     /** Displays the screen giving the parent the option to approve or deny the website.*/
     private static void onParentAuthComplete(
-            boolean success, WindowAndroid windowAndroid, GURL url) {
+            boolean success, WindowAndroid windowAndroid, GURL url, FaviconHelper faviconHelper) {
         if (!success) {
             WebsiteParentApprovalJni.get().onCompletion(false);
             return;
@@ -69,11 +95,15 @@
                         WebsiteParentApprovalJni.get().onCompletion(false);
                     }
                 });
+        // TODO(crbug.com/1340912): consume faviconHelper to set the favicon.
         websiteApprovalUi.show();
     }
 
     @NativeMethods
     interface Natives {
         void onCompletion(boolean success);
+
+        void fetchFavicon(GURL url, int minSourceSizePixel, int desiredSizePixel,
+                Callback<Bitmap> onFaviconFetched);
     }
 }
diff --git a/chrome/browser/supervised_user/android/website_parent_approval.cc b/chrome/browser/supervised_user/android/website_parent_approval.cc
index a36d954..7ff7dfc 100644
--- a/chrome/browser/supervised_user/android/website_parent_approval.cc
+++ b/chrome/browser/supervised_user/android/website_parent_approval.cc
@@ -7,10 +7,15 @@
 #include <jni.h>
 #include <memory>
 
+#include "base/android/callback_android.h"
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
+#include "base/callback.h"
 #include "base/no_destructor.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/supervised_user/android/favicon_fetcher.h"
 #include "chrome/browser/supervised_user/jni_headers/WebsiteParentApproval_jni.h"
 #include "chrome/browser/supervised_user/web_approvals_manager.h"
 #include "content/public/browser/web_contents.h"
@@ -35,6 +40,7 @@
       base::android::AttachCurrentThread());
 }
 
+// static
 void WebsiteParentApproval::RequestLocalApproval(
     content::WebContents* web_contents,
     const GURL& url,
@@ -57,3 +63,21 @@
   DCHECK(cb != nullptr);
   std::move(*cb).Run(jboolean);
 }
+
+// Triggers the asynchronous favicon request for a provided url.
+// Returns it via the provided callback.
+static void JNI_WebsiteParentApproval_FetchFavicon(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_url,
+    jint min_source_size_in_pixel,
+    jint desired_size_in_pixel,
+    const base::android::JavaParamRef<jobject>& on_favicon_fetched_callback) {
+  GURL url = *(url::GURLAndroid::ToNativeGURL(env, j_url));
+
+  FaviconFetcher* faviconFetcher =
+      new FaviconFetcher(ProfileManager::GetActiveUserProfile());
+
+  faviconFetcher->FetchFavicon(
+      url, true, min_source_size_in_pixel, desired_size_in_pixel,
+      base::android::ScopedJavaGlobalRef(on_favicon_fetched_callback));
+}
\ No newline at end of file
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 4a87c6e..2f3261d 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -388,4 +388,5 @@
  <item id="chrome_commerce_subscriptions_get" added_in_milestone="105" content_hash_code="02f49372" os_list="windows,android,linux,chromeos" file_path="components/commerce/core/subscriptions/subscriptions_server_proxy.cc" />
  <item id="cryptauth_get_my_devices" added_in_milestone="106" type="partial" second_id="oauth2_api_call_flow" content_hash_code="04b33199" os_list="chromeos" file_path="ash/services/device_sync/cryptauth_device_manager_impl.cc" />
  <item id="chrome_commerce_waa_fetcher" added_in_milestone="107" content_hash_code="016d45fc" os_list="linux,windows,android,chromeos" file_path="components/commerce/core/account_checker.cc" />
+ <item id="favicon_fetcher_get_favicon" added_in_milestone="106" content_hash_code="0640ce11" os_list="android" file_path="chrome/browser/supervised_user/android/favicon_fetcher.cc" />
 </annotations>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index 89e3706..8c15fa17 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -467,6 +467,7 @@
     <sender name="Favicon">
       <annotation id="content_suggestion_get_favicon"/>
       <annotation id="icon_catcher_get_large_icon"/>
+      <annotation id="favicon_fetcher_get_favicon"/>
     </sender>
     <sender name="Images">
       <annotation id="image_annotation"/>