chromium/components/saved_tab_groups/android/tab_group_sync_service_android.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/saved_tab_groups/android/tab_group_sync_service_android.h"

#include <memory>

#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/memory/scoped_refptr.h"
#include "components/saved_tab_groups/android/tab_group_sync_conversions_bridge.h"
#include "components/saved_tab_groups/android/tab_group_sync_conversions_utils.h"
#include "components/saved_tab_groups/tab_group_sync_service.h"
#include "components/saved_tab_groups/types.h"
#include "url/android/gurl_android.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/saved_tab_groups/jni_headers/TabGroupSyncServiceImpl_jni.h"

using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF16;
using base::android::ConvertJavaStringToUTF8;
using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef;

namespace tab_groups {
namespace {

const char kTabGroupSyncServiceBridgeKey[] = "tab_group_sync_service_bridge";
const int kInvalidTabId = -1;

}  // namespace

// This function is declared in tab_group_sync_service.h and
// should be linked in to any binary using TabGroupSyncService::GetJavaObject.
// static
ScopedJavaLocalRef<jobject> TabGroupSyncService::GetJavaObject(
    TabGroupSyncService* service) {
  if (!service->GetUserData(kTabGroupSyncServiceBridgeKey)) {
    service->SetUserData(kTabGroupSyncServiceBridgeKey,
                         std::make_unique<TabGroupSyncServiceAndroid>(service));
  }

  TabGroupSyncServiceAndroid* bridge = static_cast<TabGroupSyncServiceAndroid*>(
      service->GetUserData(kTabGroupSyncServiceBridgeKey));

  return bridge->GetJavaObject();
}

TabGroupSyncServiceAndroid::TabGroupSyncServiceAndroid(
    TabGroupSyncService* tab_group_sync_service)
    : tab_group_sync_service_(tab_group_sync_service) {
  DCHECK(tab_group_sync_service_);
  JNIEnv* env = base::android::AttachCurrentThread();
  java_obj_.Reset(env, Java_TabGroupSyncServiceImpl_create(
                           env, reinterpret_cast<int64_t>(this))
                           .obj());
  tab_group_sync_service_->AddObserver(this);
}

TabGroupSyncServiceAndroid::~TabGroupSyncServiceAndroid() {
  JNIEnv* env = base::android::AttachCurrentThread();
  Java_TabGroupSyncServiceImpl_clearNativePtr(env, java_obj_);
  tab_group_sync_service_->RemoveObserver(this);
}

ScopedJavaLocalRef<jobject> TabGroupSyncServiceAndroid::GetJavaObject() {
  return ScopedJavaLocalRef<jobject>(java_obj_);
}

void TabGroupSyncServiceAndroid::OnInitialized() {
  JNIEnv* env = base::android::AttachCurrentThread();
  Java_TabGroupSyncServiceImpl_onInitialized(env, java_obj_);
}

void TabGroupSyncServiceAndroid::OnTabGroupAdded(const SavedTabGroup& group,
                                                 TriggerSource source) {
  JNIEnv* env = base::android::AttachCurrentThread();
  auto j_group = TabGroupSyncConversionsBridge::CreateGroup(env, group);
  Java_TabGroupSyncServiceImpl_onTabGroupAdded(env, java_obj_, j_group,
                                               static_cast<jint>(source));
}

void TabGroupSyncServiceAndroid::OnTabGroupUpdated(const SavedTabGroup& group,
                                                   TriggerSource source) {
  JNIEnv* env = base::android::AttachCurrentThread();
  auto j_group = TabGroupSyncConversionsBridge::CreateGroup(env, group);
  Java_TabGroupSyncServiceImpl_onTabGroupUpdated(env, java_obj_, j_group,
                                                 static_cast<jint>(source));
}

void TabGroupSyncServiceAndroid::OnTabGroupRemoved(
    const LocalTabGroupID& local_id,
    TriggerSource source) {
  JNIEnv* env = base::android::AttachCurrentThread();
  auto j_group_id =
      TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, local_id);
  Java_TabGroupSyncServiceImpl_onTabGroupRemovedWithLocalId(
      env, java_obj_, j_group_id, static_cast<jint>(source));
}

void TabGroupSyncServiceAndroid::OnTabGroupRemoved(const base::Uuid& sync_id,
                                                   TriggerSource source) {
  JNIEnv* env = base::android::AttachCurrentThread();
  Java_TabGroupSyncServiceImpl_onTabGroupRemovedWithSyncId(
      env, java_obj_, UuidToJavaString(env, sync_id),
      static_cast<jint>(source));
}

void TabGroupSyncServiceAndroid::OnTabGroupLocalIdChanged(
    const base::Uuid& sync_id,
    const std::optional<LocalTabGroupID>& local_id) {
  JNIEnv* env = base::android::AttachCurrentThread();
  auto j_local_id =
      TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, local_id);
  Java_TabGroupSyncServiceImpl_onTabGroupLocalIdChanged(
      env, java_obj_, UuidToJavaString(env, sync_id), j_local_id);
}

ScopedJavaLocalRef<jstring> TabGroupSyncServiceAndroid::CreateGroup(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jobject>& j_group_id) {
  LocalTabGroupID group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_group_id);

  SavedTabGroup group(std::u16string(), tab_groups::TabGroupColorId::kGrey,
                      std::vector<SavedTabGroupTab>(), std::nullopt,
                      std::nullopt, group_id);

  // Copy group GUID before moving the group.
  const base::Uuid group_guid = group.saved_guid();
  tab_group_sync_service_->AddGroup(std::move(group));

  return UuidToJavaString(env, group_guid);
}

void TabGroupSyncServiceAndroid::RemoveGroupByLocalId(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jobject>& j_local_group_id) {
  auto group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_local_group_id);
  tab_group_sync_service_->RemoveGroup(group_id);
}

void TabGroupSyncServiceAndroid::RemoveGroupBySyncId(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jstring>& j_sync_group_id) {
  auto sync_group_id = JavaStringToUuid(env, j_sync_group_id);
  tab_group_sync_service_->RemoveGroup(sync_group_id);
}

void TabGroupSyncServiceAndroid::UpdateVisualData(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jobject>& j_group_id,
    const JavaParamRef<jstring>& j_title,
    jint j_color) {
  auto group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_group_id);
  auto title = ConvertJavaStringToUTF16(env, j_title);
  auto color = static_cast<tab_groups::TabGroupColorId>(j_color);
  TabGroupVisualData visual_data(title, color, /*is_collapsed=*/false);
  tab_group_sync_service_->UpdateVisualData(group_id, &visual_data);
}

void TabGroupSyncServiceAndroid::MakeTabGroupShared(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jobject>& j_group_id,
    const JavaParamRef<jstring>& j_collaboration_id) {
  LocalTabGroupID tab_group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_group_id);
  std::string collaboration_id =
      ConvertJavaStringToUTF8(env, j_collaboration_id);
  tab_group_sync_service_->MakeTabGroupShared(tab_group_id, collaboration_id);
}

void TabGroupSyncServiceAndroid::AddTab(JNIEnv* env,
                                        const JavaParamRef<jobject>& j_caller,
                                        const JavaParamRef<jobject>& j_group_id,
                                        jint j_tab_id,
                                        const JavaParamRef<jstring>& j_title,
                                        const JavaParamRef<jobject>& j_url,
                                        jint j_position) {
  auto group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_group_id);
  auto tab_id = FromJavaTabId(j_tab_id);
  auto title = ConvertJavaStringToUTF16(env, j_title);
  GURL url = url::GURLAndroid::ToNativeGURL(env, j_url);
  std::optional<size_t> position =
      j_position < 0 ? std::nullopt : std::make_optional<size_t>(j_position);
  tab_group_sync_service_->AddTab(group_id, tab_id, title, url, position);
}

void TabGroupSyncServiceAndroid::UpdateTab(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jobject>& j_group_id,
    jint j_tab_id,
    const JavaParamRef<jstring>& j_title,
    const JavaParamRef<jobject>& j_url,
    jint j_position) {
  auto group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_group_id);
  auto tab_id = FromJavaTabId(j_tab_id);
  auto title = ConvertJavaStringToUTF16(env, j_title);
  GURL url = url::GURLAndroid::ToNativeGURL(env, j_url);

  SavedTabGroupTabBuilder tab_builder;
  tab_builder.SetURL(url);
  tab_builder.SetTitle(title);
  if (j_position >= 0) {
    tab_builder.SetPosition(j_position);
  }
  tab_group_sync_service_->UpdateTab(group_id, tab_id, std::move(tab_builder));
}

void TabGroupSyncServiceAndroid::RemoveTab(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jobject>& j_group_id,
    jint j_tab_id) {
  auto group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_group_id);
  auto tab_id = FromJavaTabId(j_tab_id);
  tab_group_sync_service_->RemoveTab(group_id, tab_id);
}

void TabGroupSyncServiceAndroid::MoveTab(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jobject>& j_group_id,
    jint j_tab_id,
    int j_new_index_in_group) {
  auto group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_group_id);
  auto tab_id = FromJavaTabId(j_tab_id);
  tab_group_sync_service_->MoveTab(group_id, tab_id, j_new_index_in_group);
}

void TabGroupSyncServiceAndroid::OnTabSelected(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jobject>& j_group_id,
    jint j_tab_id) {
  auto group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_group_id);
  auto tab_id = FromJavaTabId(j_tab_id);
  tab_group_sync_service_->OnTabSelected(group_id, tab_id);
}

ScopedJavaLocalRef<jobjectArray> TabGroupSyncServiceAndroid::GetAllGroupIds(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller) {
  std::vector<std::string> sync_ids;
  const auto& groups = tab_group_sync_service_->GetAllGroups();
  for (const auto& group : groups) {
    sync_ids.emplace_back(group.saved_guid().AsLowercaseString());
  }

  return base::android::ToJavaArrayOfStrings(env, sync_ids);
}

ScopedJavaLocalRef<jobject> TabGroupSyncServiceAndroid::GetGroupBySyncGroupId(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jstring>& j_sync_group_id) {
  auto sync_group_id = JavaStringToUuid(env, j_sync_group_id);

  std::optional<SavedTabGroup> group =
      tab_group_sync_service_->GetGroup(sync_group_id);
  if (!group.has_value()) {
    return ScopedJavaLocalRef<jobject>();
  }

  return TabGroupSyncConversionsBridge::CreateGroup(env, group.value());
}

ScopedJavaLocalRef<jobject> TabGroupSyncServiceAndroid::GetGroupByLocalGroupId(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jobject>& j_local_group_id) {
  auto local_group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_local_group_id);
  std::optional<SavedTabGroup> group =
      tab_group_sync_service_->GetGroup(local_group_id);
  if (!group.has_value()) {
    return ScopedJavaLocalRef<jobject>();
  }
  return TabGroupSyncConversionsBridge::CreateGroup(env, group.value());
}

ScopedJavaLocalRef<jobjectArray> TabGroupSyncServiceAndroid::GetDeletedGroupIds(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller) {
  auto group_ids = tab_group_sync_service_->GetDeletedGroupIds();
  std::vector<ScopedJavaLocalRef<jobject>> j_group_ids;
  for (const auto& group_id : group_ids) {
    auto j_group_id =
        TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, group_id);
    j_group_ids.emplace_back(j_group_id);
  }
  return base::android::ToJavaArrayOfObjects(env, j_group_ids);
}

void TabGroupSyncServiceAndroid::UpdateLocalTabGroupMapping(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jstring>& j_sync_id,
    const JavaParamRef<jobject>& j_local_id) {
  auto sync_id = JavaStringToUuid(env, j_sync_id);
  auto local_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_local_id);
  tab_group_sync_service_->UpdateLocalTabGroupMapping(sync_id, local_id);
}

void TabGroupSyncServiceAndroid::RemoveLocalTabGroupMapping(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jobject>& j_local_id) {
  auto local_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_local_id);
  tab_group_sync_service_->RemoveLocalTabGroupMapping(local_id);
}

void TabGroupSyncServiceAndroid::UpdateLocalTabId(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jobject>& j_group_id,
    const JavaParamRef<jstring>& j_sync_tab_id,
    jint j_local_tab_id) {
  auto local_group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_group_id);
  auto sync_tab_id = JavaStringToUuid(env, j_sync_tab_id);
  auto local_tab_id = FromJavaTabId(j_local_tab_id);
  tab_group_sync_service_->UpdateLocalTabId(local_group_id, sync_tab_id,
                                            local_tab_id);
}

bool TabGroupSyncServiceAndroid::IsRemoteDevice(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    const JavaParamRef<jstring>& j_sync_cache_guid) {
  auto sync_cache_guid = ConvertJavaStringToUTF8(env, j_sync_cache_guid);
  return tab_group_sync_service_->IsRemoteDevice(sync_cache_guid);
}

void TabGroupSyncServiceAndroid::RecordTabGroupEvent(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_caller,
    jint j_event_type,
    const JavaParamRef<jobject>& j_local_group_id,
    jint j_local_tab_id,
    jint j_opening_source,
    jint j_closing_source) {
  EventDetails event_details(static_cast<TabGroupEvent>(j_event_type));
  event_details.local_tab_group_id =
      TabGroupSyncConversionsBridge::FromJavaTabGroupId(env, j_local_group_id);
  if (j_local_tab_id != kInvalidTabId) {
    event_details.local_tab_id = FromJavaTabId(j_local_tab_id);
  }

  auto opening_source = static_cast<OpeningSource>(j_opening_source);
  if (opening_source != OpeningSource::kUnknown) {
    event_details.opening_source = opening_source;
  }

  auto closing_source = static_cast<ClosingSource>(j_closing_source);
  if (closing_source != ClosingSource::kUnknown) {
    event_details.closing_source = closing_source;
  }

  tab_group_sync_service_->RecordTabGroupEvent(event_details);
}

}  // namespace tab_groups