text: // Copyright 2015 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/android/bookmarks/partner_bookmarks_reader.h"
  
  #include "base/android/jni_android.h"
  #include "base/android/jni_string.h"
  #include "base/bind.h"
  #include "base/guid.h"
  #include "base/logging.h"
  #include "chrome/android/chrome_jni_headers/PartnerBookmarksReader_jni.h"
  #include "chrome/browser/android/bookmarks/partner_bookmarks_shim.h"
  #include "chrome/browser/browser_process.h"
  #include "chrome/browser/favicon/favicon_service_factory.h"
  #include "chrome/browser/favicon/large_icon_service_factory.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/profiles/profile_manager.h"
  #include "components/favicon/core/favicon_service.h"
  #include "components/favicon/core/large_icon_service_impl.h"
  #include "components/image_fetcher/core/image_fetcher.h"
  #include "content/public/browser/browser_task_traits.h"
  #include "content/public/browser/browser_thread.h"
  #include "third_party/skia/include/core/SkBitmap.h"
  #include "ui/gfx/codec/png_codec.h"
  #include "ui/gfx/favicon_size.h"
  
  using base::android::AttachCurrentThread;
  using base::android::CheckException;
  using base::android::ConvertJavaStringToUTF8;
  using base::android::ConvertJavaStringToUTF16;
  using base::android::ConvertUTF8ToJavaString;
  using base::android::JavaParamRef;
  using base::android::JavaRef;
  using base::android::ScopedJavaGlobalRef;
  using bookmarks::BookmarkNode;
  using content::BrowserThread;
  
  namespace {
  
  const int kPartnerBookmarksMinimumFaviconSizePx = 16;
  
  void SetFaviconTask(Profile* profile,
                      const GURL& page_url, const GURL& icon_url,
                      const std::vector<unsigned char>& image_data,
                      favicon_base::IconType icon_type) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    scoped_refptr<base::RefCountedMemory> bitmap_data(
        new base::RefCountedBytes(image_data));
    gfx::Size pixel_size(gfx::kFaviconSize, gfx::kFaviconSize);
    favicon::FaviconService* favicon_service =
        FaviconServiceFactory::GetForProfile(profile,
                                             ServiceAccessType::EXPLICIT_ACCESS);
    if (!favicon_service)
      return;
  
    favicon_service->MergeFavicon(
        page_url, page_url, icon_type, bitmap_data, pixel_size);
  }
  
  void SetFaviconCallback(Profile* profile,
                          const GURL& page_url, const GURL& icon_url,
                          const std::vector<unsigned char>& image_data,
                          favicon_base::IconType icon_type,
                          base::WaitableEvent* bookmark_added_event) {
    SetFaviconTask(profile, page_url, icon_url, image_data, icon_type);
    if (bookmark_added_event)
      bookmark_added_event->Signal();
  }
  
  void PrepareAndSetFavicon(jbyte* icon_bytes,
                            int icon_len,
                            BookmarkNode* node,
                            Profile* profile,
                            favicon_base::IconType icon_type) {
    SkBitmap icon_bitmap;
    if (!gfx::PNGCodec::Decode(
        reinterpret_cast<const unsigned char*>(icon_bytes),
        icon_len, &icon_bitmap))
      return;
    std::vector<unsigned char> image_data;
    if (!gfx::PNGCodec::EncodeBGRASkBitmap(icon_bitmap, false, &image_data))
      return;
    // TODO(aruslan): TODO(tedchoc): Follow up on how to avoid this through js.
    // Since the favicon URL is used as a key in the history's thumbnail DB,
    // this gives us a value which does not collide with others.
    GURL fake_icon_url = node->url();
  
    base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                              base::WaitableEvent::InitialState::NOT_SIGNALED);
    content::GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE, base::BindOnce(&SetFaviconCallback, profile, node->url(),
                                  fake_icon_url, image_data, icon_type, &event));
    // TODO(aruslan): http://b/6397072 If possible - avoid using favicon service
    event.Wait();
  }
  
  const BookmarkNode* GetNodeByID(const BookmarkNode* parent, int64_t id) {
    if (parent->id() == id)
      return parent;
    for (const auto& child : parent->children()) {
      const BookmarkNode* result = GetNodeByID(child.get(), id);
      if (result)
        return result;
    }
    return nullptr;
  }
  
  std::unique_ptr<BookmarkNode> CreatePartnerBookmarksRoot(int id) {
    return std::make_unique<BookmarkNode>(id, base::GUID::GenerateRandomV4(),
                                          GURL());
  }
  
  }  // namespace
  
  PartnerBookmarksReader::PartnerBookmarksReader(
      PartnerBookmarksShim* partner_bookmarks_shim,
      Profile* profile)
      : partner_bookmarks_shim_(partner_bookmarks_shim),
        profile_(profile),
        large_icon_service_(nullptr),
        wip_next_available_id_(0) {}
  
  PartnerBookmarksReader::~PartnerBookmarksReader() {}
  
  void PartnerBookmarksReader::PartnerBookmarksCreationComplete(
      JNIEnv*,
      const JavaParamRef<jobject>&) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    partner_bookmarks_shim_->SetPartnerBookmarksRoot(
        std::move(wip_partner_bookmarks_root_));
    wip_next_available_id_ = 0;
  }
  
  void PartnerBookmarksReader::Destroy(JNIEnv* env,
                                       const JavaParamRef<jobject>& obj) {
    delete this;
  }
  
  void PartnerBookmarksReader::Reset(JNIEnv* env,
                                     const JavaParamRef<jobject>& obj) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    wip_partner_bookmarks_root_.reset();
    wip_next_available_id_ = 0;
  }
  
  jlong PartnerBookmarksReader::AddPartnerBookmark(
      JNIEnv* env,
      const JavaParamRef<jobject>& obj,
      const JavaParamRef<jstring>& jurl,
      const JavaParamRef<jstring>& jtitle,
      jboolean is_folder,
      jlong parent_id,
      const JavaParamRef<jbyteArray>& favicon,
      const JavaParamRef<jbyteArray>& touchicon,
      jboolean fetch_uncached_favicons_from_server,
      jint desired_favicon_size_px,
      const JavaParamRef<jobject>& j_callback) {
    base::string16 url;
    base::string16 title;
    if (jurl) {
      DCHECK(!is_folder);
      url = ConvertJavaStringToUTF16(env, jurl);
    }
    if (jtitle)
      title = ConvertJavaStringToUTF16(env, jtitle);
  
    jlong node_id = 0;
    if (wip_partner_bookmarks_root_.get()) {
      std::unique_ptr<BookmarkNode> node = std::make_unique<BookmarkNode>(
          wip_next_available_id_++, base::GUID::GenerateRandomV4(), GURL(url));
      node->SetTitle(title);
  
      // Handle favicon and touchicon
      if (profile_ != nullptr) {
        if (favicon != nullptr || touchicon != nullptr) {
          jbyteArray icon = (touchicon != nullptr) ? touchicon : favicon;
          const favicon_base::IconType icon_type =
              touchicon ? favicon_base::IconType::kTouchIcon
                        : favicon_base::IconType::kFavicon;
          const int icon_len = env->GetArrayLength(icon);
          jbyte* icon_bytes = env->GetByteArrayElements(icon, nullptr);
          if (icon_bytes)
            PrepareAndSetFavicon(icon_bytes, icon_len, node.get(), profile_,
                                 icon_type);
          env->ReleaseByteArrayElements(icon, icon_bytes, JNI_ABORT);
        } else {
          // We should attempt to read the favicon from cache or retrieve it from
          // a server and cache it.
          Java_FetchFaviconCallback_onFaviconFetch(env, j_callback);
          GetFavicon(
              GURL(url), profile_, fetch_uncached_favicons_from_server,
              desired_favicon_size_px,
              base::BindOnce(&PartnerBookmarksReader::OnFaviconFetched,
                             base::Unretained(this),
                             ScopedJavaGlobalRef<jobject>(env, j_callback)));
        }
      }
  
      const BookmarkNode* parent =
          GetNodeByID(wip_partner_bookmarks_root_.get(), parent_id);
      if (!parent) {
        LOG(WARNING) << "partner_bookmarks_shim: invalid/unknown parent_id="
                     << parent_id << ": adding to the root";
        parent = wip_partner_bookmarks_root_.get();
      }
      node_id = node->id();
      const_cast<BookmarkNode*>(parent)->Add(std::move(node));
    } else {
      std::unique_ptr<BookmarkNode> node =
          CreatePartnerBookmarksRoot(wip_next_available_id_++);
      node_id = node->id();
      node->SetTitle(title);
      wip_partner_bookmarks_root_ = std::move(node);
    }
    return node_id;
  }
  
  // static
  std::unique_ptr<BookmarkNode>
  PartnerBookmarksReader::CreatePartnerBookmarksRootForTesting() {
    return CreatePartnerBookmarksRoot(/*id=*/0);
  }
  
  void PartnerBookmarksReader::GetFavicon(const GURL& page_url,
                                          Profile* profile,
                                          bool fallback_to_server,
                                          int desired_favicon_size_px,
                                          FaviconFetchedCallback callback) {
    content::GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE, base::BindOnce(&PartnerBookmarksReader::GetFaviconImpl,
                                  base::Unretained(this), page_url, profile,
                                  fallback_to_server, desired_favicon_size_px,
                                  std::move(callback)));
  }
  
  void PartnerBookmarksReader::GetFaviconImpl(const GURL& page_url,
                                              Profile* profile,
                                              bool fallback_to_server,
                                              int desired_favicon_size_px,
                                              FaviconFetchedCallback callback) {
    if (!GetLargeIconService()) {
      std::move(callback).Run(
          FaviconFetchResult::FAILURE_ICON_SERVICE_UNAVAILABLE);
      return;
    }
  
    GetFaviconFromCacheOrServer(page_url, fallback_to_server,
                                false /* from_server */, desired_favicon_size_px,
                                std::move(callback));
  }
  
  favicon::LargeIconService* PartnerBookmarksReader::GetLargeIconService() {
    if (!large_icon_service_) {
      large_icon_service_ =
          LargeIconServiceFactory::GetForBrowserContext(profile_);
    }
    return large_icon_service_;
  }
  
  void PartnerBookmarksReader::GetFaviconFromCacheOrServer(
      const GURL& page_url,
      bool fallback_to_server,
      bool from_server,
      int desired_favicon_size_px,
      FaviconFetchedCallback callback) {
    GetLargeIconService()->GetLargeIconRawBitmapOrFallbackStyleForPageUrl(
        page_url, kPartnerBookmarksMinimumFaviconSizePx, desired_favicon_size_px,
        base::BindOnce(&PartnerBookmarksReader::OnGetFaviconFromCacheFinished,
                       base::Unretained(this), page_url,
                       base::Passed(std::move(callback)), fallback_to_server,
                       from_server, desired_favicon_size_px),
        &favicon_task_tracker_);
  }
  
  void PartnerBookmarksReader::OnGetFaviconFromCacheFinished(
      const GURL& page_url,
      FaviconFetchedCallback callback,
      bool fallback_to_server,
      bool from_server,
      int desired_favicon_size_px,
      const favicon_base::LargeIconResult& result) {
    // |from_server| tells us if we fetched the image from the cache after we went
    // to server for it, so this successful cache retrieval should actually return
    // SUCCESS_FROM_SERVER.
    if (result.bitmap.is_valid()) {
      if (from_server) {
        std::move(callback).Run(FaviconFetchResult::SUCCESS_FROM_SERVER);
      } else {
        std::move(callback).Run(FaviconFetchResult::SUCCESS_FROM_CACHE);
      }
      return;
    }
  
    // We chose not to fetch from server if the cache failed, so return an empty
    // image.
    if (!fallback_to_server) {
      std::move(callback).Run(FaviconFetchResult::FAILURE_NOT_IN_CACHE);
      return;
    }
  
    // Try to fetch the favicon from a Google favicon server.
    net::NetworkTrafficAnnotationTag traffic_annotation =
        net::DefineNetworkTrafficAnnotation(
            "partner_bookmarks_reader_get_favicon", R"(
          semantics {
            sender: "Partner Bookmarks Reader"
            description:
              "Sends a request to a Google server to retrieve the favicon bitmap "
              "for a partner bookmark (URLs are public and provided by Google)."
            trigger:
              "A request can be sent if Chrome does not have a favicon for a "
              "particular partner bookmark."
            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."
          })");
    GetLargeIconService()
        ->GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
            page_url, false /* may_page_url_be_private */,
            false /* should_trim_page_url_path */, traffic_annotation,
            base::BindOnce(
                &PartnerBookmarksReader::OnGetFaviconFromServerFinished,
                base::Unretained(this), page_url, desired_favicon_size_px,
                base::Passed(std::move(callback))));
  }
  
  void PartnerBookmarksReader::OnGetFaviconFromServerFinished(
      const GURL& page_url,
      int desired_favicon_size_px,
      FaviconFetchedCallback callback,
      favicon_base::GoogleFaviconServerRequestStatus status) {
    if (status != favicon_base::GoogleFaviconServerRequestStatus::SUCCESS) {
      FaviconFetchResult result;
      if (status == favicon_base::GoogleFaviconServerRequestStatus::
                        FAILURE_CONNECTION_ERROR) {
        result = FaviconFetchResult::FAILURE_CONNECTION_ERROR;
      } else if (status == favicon_base::GoogleFaviconServerRequestStatus::
                               FAILURE_ON_WRITE) {
        result = FaviconFetchResult::FAILURE_WRITING_FAVICON_CACHE;
      } else {
        result = FaviconFetchResult::FAILURE_SERVER_ERROR;
      }
      std::move(callback).Run(result);
      return;
    }
  
    // The icon was successfully retrieved from the server, now we just have to
    // retrieve it from the cache where it was stored.
    GetFaviconFromCacheOrServer(page_url, false /* fallback_to_server */,
                                true /* from_server */, desired_favicon_size_px,
                                std::move(callback));
  }
  
  void PartnerBookmarksReader::OnFaviconFetched(
      const JavaRef<jobject>& j_callback,
      FaviconFetchResult result) {
    JNIEnv* env = AttachCurrentThread();
    Java_FetchFaviconCallback_onFaviconFetched(env, j_callback,
                                               static_cast<int>(result));
  }
  
  // ----------------------------------------------------------------
  
  static void JNI_PartnerBookmarksReader_DisablePartnerBookmarksEditing(
      JNIEnv* env) {
    PartnerBookmarksShim::DisablePartnerBookmarksEditing();
  }
  
  static jlong JNI_PartnerBookmarksReader_Init(JNIEnv* env,
                                               const JavaParamRef<jobject>& obj) {
    Profile* profile = ProfileManager::GetActiveUserProfile();
    PartnerBookmarksShim* partner_bookmarks_shim =
        PartnerBookmarksShim::BuildForBrowserContext(profile);
    PartnerBookmarksReader* reader = new PartnerBookmarksReader(
        partner_bookmarks_shim, profile);
    return reinterpret_cast<intptr_t>(reader);
  }
  
  static base::android::ScopedJavaLocalRef<jstring>
  JNI_PartnerBookmarksReader_GetNativeUrlString(
      JNIEnv* env,
      const JavaParamRef<jstring>& j_url) {
    GURL url(ConvertJavaStringToUTF8(j_url));
    return ConvertUTF8ToJavaString(env, url.spec());
  }
  