chromium/chrome/browser/android/auxiliary_search/auxiliary_search_provider.cc

// Copyright 2023 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/auxiliary_search/auxiliary_search_provider.h"

#include "base/android/callback_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/singleton.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/android/auxiliary_search/proto/auxiliary_search_group.pb.h"
#include "chrome/browser/android/persisted_tab_data/sensitivity_persisted_tab_data_android.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/flags/android/chrome_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_keyed_service_factory.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "url/url_constants.h"

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

using base::android::ToJavaByteArray;
using bookmarks::BookmarkModel;
using bookmarks::BookmarkNode;

namespace {

using BackToJavaCallback = base::OnceCallback<void(
    std::unique_ptr<std::vector<base::WeakPtr<TabAndroid>>>)>;

class AuxiliarySearchProviderFactory : public ProfileKeyedServiceFactory {
 public:
  static AuxiliarySearchProvider* GetForProfile(Profile* profile) {
    return static_cast<AuxiliarySearchProvider*>(
        GetInstance()->GetServiceForBrowserContext(profile, true));
  }

  static AuxiliarySearchProviderFactory* GetInstance() {
    return base::Singleton<AuxiliarySearchProviderFactory>::get();
  }

  AuxiliarySearchProviderFactory()
      : ProfileKeyedServiceFactory(
            "AuxiliarySearchProvider",
            ProfileSelections::Builder()
                .WithRegular(ProfileSelection::kRedirectedToOriginal)
                .WithGuest(ProfileSelection::kNone)
                .Build()) {}

 private:
  // ProfileKeyedServiceFactory overrides
  KeyedService* BuildServiceInstanceFor(
      content::BrowserContext* context) const override {
    Profile* profile = Profile::FromBrowserContext(context);
    DCHECK(!profile->IsOffTheRecord());

    return new AuxiliarySearchProvider(profile);
  }
};

void callJavaCallbackWithTabList(
    JNIEnv* env,
    const base::android::ScopedJavaGlobalRef<jobject>& j_callback_obj,
    std::unique_ptr<std::vector<base::WeakPtr<TabAndroid>>>
        non_sensitive_tabs) {
  std::vector<base::android::ScopedJavaLocalRef<jobject>> j_tabs_list;
  for (base::WeakPtr<TabAndroid> tab_android : *non_sensitive_tabs.get()) {
    if (tab_android) {
      j_tabs_list.push_back(tab_android->GetJavaObject());
    }
  }
  base::android::RunObjectCallbackAndroid(
      j_callback_obj, base::android::ToJavaArrayOfObjects(env, j_tabs_list));
}

bool IsSchemeAllowed(const GURL& url) {
  return url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme);
}

void FilterNonSensitiveSearchableTabs(
    std::unique_ptr<std::vector<base::WeakPtr<TabAndroid>>> all_tabs,
    int current_tab_index,
    size_t max_tab_count,
    std::unique_ptr<std::vector<base::WeakPtr<TabAndroid>>> non_sensitive_tabs,
    BackToJavaCallback callback,
    PersistedTabDataAndroid* persisted_tab_data) {
  SensitivityPersistedTabDataAndroid* sensitivity_persisted_tab_data_android =
      static_cast<SensitivityPersistedTabDataAndroid*>(persisted_tab_data);

  if (current_tab_index >= 0 &&
      !sensitivity_persisted_tab_data_android->is_sensitive()) {
    non_sensitive_tabs->push_back(all_tabs->at(current_tab_index));
  }
  int next_tab_index = current_tab_index - 1;

  // Make sure the next tab index is valid, and the next tab's week pointer is
  // still available.
  while (next_tab_index >= 0 && max_tab_count > non_sensitive_tabs->size() &&
         !all_tabs->at(next_tab_index)) {
    --next_tab_index;
  }

  if (next_tab_index < 0 || max_tab_count <= non_sensitive_tabs->size()) {
    std::move(callback).Run(std::move(non_sensitive_tabs));
    return;
  }

  base::WeakPtr<TabAndroid> next_tab = all_tabs->at(next_tab_index);
  SensitivityPersistedTabDataAndroid::From(
      next_tab.get(),
      base::BindOnce(&FilterNonSensitiveSearchableTabs, std::move(all_tabs),
                     next_tab_index, max_tab_count,
                     std::move(non_sensitive_tabs), std::move(callback)));
}

}  // namespace

AuxiliarySearchProvider::AuxiliarySearchProvider(Profile* profile)
    : profile_(profile) {
  max_bookmark_donation_count_ =
      chrome::android::kAuxiliarySearchMaxBookmarksCountParam.Get();
  max_tab_donation_count_ =
      chrome::android::kAuxiliarySearchMaxTabsCountParam.Get();
}

AuxiliarySearchProvider::~AuxiliarySearchProvider() = default;

base::android::ScopedJavaLocalRef<jbyteArray>
AuxiliarySearchProvider::GetBookmarksSearchableData(JNIEnv* env) const {
  auxiliary_search::AuxiliarySearchBookmarkGroup group =
      GetBookmarks(BookmarkModelFactory::GetForBrowserContext(profile_.get()));

  std::string serialized_group;
  if (!group.SerializeToString(&serialized_group)) {
    serialized_group.clear();
  }

  return ToJavaByteArray(env, serialized_group);
}

void AuxiliarySearchProvider::GetNonSensitiveTabs(
    JNIEnv* env,
    const base::android::JavaParamRef<jobjectArray>& j_tabs_android,
    const base::android::JavaParamRef<jobject>& j_callback_obj) const {
  std::vector<raw_ptr<TabAndroid, VectorExperimental>> all_tabs =
      TabAndroid::GetAllNativeTabs(
          env, base::android::ScopedJavaLocalRef<jobjectArray>(j_tabs_android));

  GetNonSensitiveTabsInternal(
      all_tabs, base::BindOnce(&callJavaCallbackWithTabList, env,
                               base::android::ScopedJavaGlobalRef<jobject>(
                                   j_callback_obj)));
}

auxiliary_search::AuxiliarySearchBookmarkGroup
AuxiliarySearchProvider::GetBookmarks(bookmarks::BookmarkModel* model) const {
  auxiliary_search::AuxiliarySearchBookmarkGroup group;
  std::vector<const BookmarkNode*> nodes;
  bookmarks::GetMostRecentlyUsedEntries(model, max_bookmark_donation_count_,
                                        &nodes);
  for (const BookmarkNode* node : nodes) {
    GURL url = node->url();
    if (!IsSchemeAllowed(url)) {
      continue;
    }

    auxiliary_search::AuxiliarySearchEntry* bookmark = group.add_bookmark();
    bookmark->set_title(base::UTF16ToUTF8(node->GetTitle()));
    bookmark->set_url(url.spec());
    if (!node->date_added().is_null()) {
      bookmark->set_creation_timestamp(
          node->date_added().InMillisecondsSinceUnixEpoch());
    }
    if (!node->date_last_used().is_null()) {
      bookmark->set_last_access_timestamp(
          node->date_last_used().InMillisecondsSinceUnixEpoch());
    }
  }

  return group;
}

// static
std::vector<base::WeakPtr<TabAndroid>>
AuxiliarySearchProvider::FilterTabsByScheme(
    const std::vector<raw_ptr<TabAndroid, VectorExperimental>>& tabs) {
  std::vector<base::WeakPtr<TabAndroid>> filtered_tabs;
  for (TabAndroid* tab : tabs) {
    if (IsSchemeAllowed(tab->GetURL())) {
      filtered_tabs.push_back(tab->GetWeakPtr());
    }
  }
  return filtered_tabs;
}

void AuxiliarySearchProvider::GetNonSensitiveTabsInternal(
    const std::vector<raw_ptr<TabAndroid, VectorExperimental>>& all_tabs,
    NonSensitiveTabsCallback callback) const {
  std::unique_ptr<std::vector<base::WeakPtr<TabAndroid>>> non_sensitive_tabs =
      std::make_unique<std::vector<base::WeakPtr<TabAndroid>>>();
  std::vector<base::WeakPtr<TabAndroid>> filtered_tabs =
      FilterTabsByScheme(all_tabs);
  if (filtered_tabs.size() == 0) {
    std::move(callback).Run(std::move(non_sensitive_tabs));
    return;
  }

  base::WeakPtr<TabAndroid> next_tab =
      filtered_tabs.at(filtered_tabs.size() - 1);
  SensitivityPersistedTabDataAndroid::From(
      next_tab.get(),
      base::BindOnce(&FilterNonSensitiveSearchableTabs,
                     std::make_unique<std::vector<base::WeakPtr<TabAndroid>>>(
                         filtered_tabs),
                     filtered_tabs.size() - 1, max_tab_donation_count_,
                     std::move(non_sensitive_tabs), std::move(callback)));
}

// static
jlong JNI_AuxiliarySearchBridge_GetForProfile(JNIEnv* env, Profile* profile) {
  DCHECK(profile);

  return reinterpret_cast<intptr_t>(
      AuxiliarySearchProviderFactory::GetForProfile(profile));
}

// static
void AuxiliarySearchProvider::EnsureFactoryBuilt() {
  AuxiliarySearchProviderFactory::GetInstance();
}