chromium/chrome/browser/android/ntp/most_visited_sites_bridge.cc

// 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.

#include "chrome/browser/android/ntp/most_visited_sites_bridge.h"

#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/ntp_tiles/chrome_most_visited_sites_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/favicon_base/favicon_types.h"
#include "components/history/core/browser/history_service.h"
#include "components/ntp_tiles/metrics.h"
#include "components/ntp_tiles/most_visited_sites.h"
#include "components/ntp_tiles/section_type.h"
#include "ui/gfx/android/java_bitmap.h"
#include "url/android/gurl_android.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/android/chrome_jni_headers/MostVisitedSitesBridge_jni.h"
#include "chrome/android/chrome_jni_headers/MostVisitedSites_jni.h"

using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF8;
using base::android::JavaParamRef;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
using ntp_tiles::MostVisitedSites;
using ntp_tiles::NTPTilesVector;
using ntp_tiles::SectionType;
using ntp_tiles::TileTitleSource;
using ntp_tiles::TileSource;
using ntp_tiles::TileVisualType;

namespace {

class JavaHomepageClient : public MostVisitedSites::HomepageClient {
 public:
  JavaHomepageClient(JNIEnv* env,
                     const JavaParamRef<jobject>& obj,
                     Profile* profile);

  JavaHomepageClient(const JavaHomepageClient&) = delete;
  JavaHomepageClient& operator=(const JavaHomepageClient&) = delete;

  bool IsHomepageTileEnabled() const override;
  GURL GetHomepageUrl() const override;
  void QueryHomepageTitle(TitleCallback title_callback) override;

 private:
  void OnTitleEntryFound(TitleCallback title_callback,
                         history::QueryURLResult result);

  ScopedJavaGlobalRef<jobject> client_;
  raw_ptr<Profile> profile_;

  // Used in loading titles.
  base::CancelableTaskTracker task_tracker_;
};

JavaHomepageClient::JavaHomepageClient(JNIEnv* env,
                                       const JavaParamRef<jobject>& obj,
                                       Profile* profile)
    : client_(env, obj), profile_(profile) {
  DCHECK(profile);
}

void JavaHomepageClient::QueryHomepageTitle(TitleCallback title_callback) {
  DCHECK(!title_callback.is_null());
  GURL url = GetHomepageUrl();
  if (url.is_empty()) {
    std::move(title_callback).Run(std::nullopt);
    return;
  }
  history::HistoryService* const history_service =
      HistoryServiceFactory::GetForProfileIfExists(
          profile_, ServiceAccessType::EXPLICIT_ACCESS);
  if (!history_service) {
    std::move(title_callback).Run(std::nullopt);
    return;
  }
  // If the client is destroyed, the tracker will cancel this task automatically
  // and the callback will not be called. Therefore, base::Unretained works.
  history_service->QueryURL(
      url,
      /*want_visits=*/false,
      base::BindOnce(&JavaHomepageClient::OnTitleEntryFound,
                     base::Unretained(this), std::move(title_callback)),
      &task_tracker_);
}

void JavaHomepageClient::OnTitleEntryFound(TitleCallback title_callback,
                                           history::QueryURLResult result) {
  if (!result.success) {
    std::move(title_callback).Run(std::nullopt);
    return;
  }
  std::move(title_callback).Run(result.row.title());
}

bool JavaHomepageClient::IsHomepageTileEnabled() const {
  return Java_HomepageClient_isHomepageTileEnabled(AttachCurrentThread(),
                                                   client_);
}

GURL JavaHomepageClient::GetHomepageUrl() const {
  base::android::ScopedJavaLocalRef<jstring> url =
      Java_HomepageClient_getHomepageUrl(AttachCurrentThread(), client_);
  if (url.is_null()) {
    return GURL();
  }
  return GURL(ConvertJavaStringToUTF8(url));
}

}  // namespace

class MostVisitedSitesBridge::JavaObserver : public MostVisitedSites::Observer {
 public:
  JavaObserver(JNIEnv* env, const JavaParamRef<jobject>& obj);

  JavaObserver(const JavaObserver&) = delete;
  JavaObserver& operator=(const JavaObserver&) = delete;

  void OnURLsAvailable(
      const std::map<SectionType, NTPTilesVector>& sections) override;

  void OnIconMadeAvailable(const GURL& site_url) override;

 private:
  ScopedJavaGlobalRef<jobject> observer_;
};

MostVisitedSitesBridge::JavaObserver::JavaObserver(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj)
    : observer_(env, obj) {}

void MostVisitedSitesBridge::JavaObserver::OnURLsAvailable(
    const std::map<SectionType, NTPTilesVector>& sections) {
  JNIEnv* env = AttachCurrentThread();
  std::vector<jni_zero::ScopedJavaLocalRef<jobject>> suggestions;
  for (const auto& section : sections) {
    int32_t section_type = static_cast<int32_t>(section.first);
    const NTPTilesVector& tiles = section.second;
    for (const auto& tile : tiles) {
      suggestions.push_back(Java_MostVisitedSitesBridge_makeSiteSuggestion(
          env, tile.title, tile.url, static_cast<int32_t>(tile.title_source),
          static_cast<int32_t>(tile.source), section_type));
    }
  }
  Java_MostVisitedSitesBridge_onURLsAvailable(env, observer_, suggestions);
}

void MostVisitedSitesBridge::JavaObserver::OnIconMadeAvailable(
    const GURL& site_url) {
  JNIEnv* env = AttachCurrentThread();
  Java_MostVisitedSitesBridge_onIconMadeAvailable(env, observer_, site_url);
}

MostVisitedSitesBridge::MostVisitedSitesBridge(Profile* profile)
    : most_visited_(ChromeMostVisitedSitesFactory::NewForProfile(profile)),
      profile_(profile) {
  DCHECK(!profile->IsOffTheRecord());
}

MostVisitedSitesBridge::~MostVisitedSitesBridge() {}

void MostVisitedSitesBridge::Destroy(JNIEnv* env,
                                     const JavaParamRef<jobject>& obj) {
  delete this;
}

void MostVisitedSitesBridge::OnHomepageStateChanged(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj) {
  most_visited_->RefreshTiles();
}

void MostVisitedSitesBridge::SetHomepageClient(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& obj,
    const base::android::JavaParamRef<jobject>& j_client) {
  most_visited_->SetHomepageClient(
      std::make_unique<JavaHomepageClient>(env, j_client, profile_));
}

void MostVisitedSitesBridge::SetObserver(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jobject>& j_observer,
    jint num_sites) {
  java_observer_ = std::make_unique<JavaObserver>(env, j_observer);
  most_visited_->AddMostVisitedURLsObserver(java_observer_.get(), num_sites);
}

void MostVisitedSitesBridge::AddOrRemoveBlockedUrl(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jobject>& j_url,
    jboolean add_url) {
  GURL url = url::GURLAndroid::ToNativeGURL(env, j_url);
  most_visited_->AddOrRemoveBlockedUrl(url, add_url);
}

void MostVisitedSitesBridge::RecordPageImpression(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jint jtiles_count) {
  ntp_tiles::metrics::RecordPageImpression(jtiles_count);
}

void MostVisitedSitesBridge::RecordTileImpression(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jint jindex,
    jint jvisual_type,
    jint jicon_type,
    jint jtitle_source,
    jint jsource,
    const JavaParamRef<jobject>& jurl) {
  GURL url = url::GURLAndroid::ToNativeGURL(env, jurl);
  TileTitleSource title_source = static_cast<TileTitleSource>(jtitle_source);
  TileSource source = static_cast<TileSource>(jsource);
  TileVisualType visual_type = static_cast<TileVisualType>(jvisual_type);
  favicon_base::IconType icon_type =
      static_cast<favicon_base::IconType>(jicon_type);

  ntp_tiles::metrics::RecordTileImpression(ntp_tiles::NTPTileImpression(
      jindex, source, title_source, visual_type, icon_type, url));
}

void MostVisitedSitesBridge::RecordOpenedMostVisitedItem(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jint index,
    jint tile_type,
    jint title_source,
    jint source) {
  ntp_tiles::metrics::RecordTileClick(ntp_tiles::NTPTileImpression(
      index, static_cast<TileSource>(source),
      static_cast<TileTitleSource>(title_source),
      static_cast<TileVisualType>(tile_type), favicon_base::IconType::kInvalid,
      /*url_for_rappor=*/GURL()));
}

static jlong JNI_MostVisitedSitesBridge_Init(JNIEnv* env,
                                             const JavaParamRef<jobject>& obj,
                                             Profile* profile) {
  MostVisitedSitesBridge* most_visited_sites =
      new MostVisitedSitesBridge(profile);
  return reinterpret_cast<intptr_t>(most_visited_sites);
}