blob: 94171d7cc2b1c98edeb0f4a4e462e925b0c30691 [file] [log] [blame]
// Copyright 2025 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/contextual_tasks/internal/contextual_tasks_service_impl.h"
#include <optional>
#include <utility>
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/uuid.h"
#include "components/contextual_search/contextual_search_service.h"
#include "components/contextual_tasks/internal/composite_context_decorator.h"
#include "components/contextual_tasks/internal/conversions.h"
#include "components/contextual_tasks/public/account_utils.h"
#include "components/contextual_tasks/public/contextual_task.h"
#include "components/contextual_tasks/public/contextual_task_context.h"
#include "components/contextual_tasks/public/contextual_tasks_service.h"
#include "components/contextual_tasks/public/features.h"
#include "components/omnibox/browser/aim_eligibility_service.h"
#include "components/prefs/pref_service.h"
#include "components/sessions/core/session_id.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/report_unrecoverable_error.h"
#include "components/sync/model/client_tag_based_data_type_processor.h"
#include "url/gurl.h"
namespace contextual_tasks {
namespace {
struct MergeUrlResourcesResult {
std::vector<UrlResource> final_resources;
std::vector<UrlResource> added_or_updated_resources;
std::vector<base::Uuid> removed_resource_ids;
bool has_changes = false;
};
// Helper to find the index of a matching resource in existing_resources.
// Returns -1 if no match is found.
int FindMatchingResourceIndex(
const UrlResource& incoming_res,
const std::vector<UrlResource>& existing_resources,
const std::vector<bool>& existing_matched) {
for (size_t i = 0; i < existing_resources.size(); ++i) {
if (existing_matched[i]) {
continue;
}
const auto& existing_res = existing_resources[i];
// 1. Match by url_id.
if (incoming_res.url_id.is_valid() &&
existing_res.url_id == incoming_res.url_id) {
return static_cast<int>(i);
}
// 2. Match by context_id.
if (incoming_res.context_id.has_value() &&
existing_res.context_id == incoming_res.context_id) {
return static_cast<int>(i);
}
// 3. Match by url.
if (incoming_res.url.is_valid() && existing_res.url == incoming_res.url) {
return static_cast<int>(i);
}
}
return -1;
}
// Merges incoming resources with existing ones.
// Returns a result containing the final list of resources, as well as lists of
// added/updated and removed resources to be used for sync notifications.
MergeUrlResourcesResult MergeUrlResources(
const std::vector<UrlResource>& existing_resources,
std::vector<UrlResource> incoming_resources) {
MergeUrlResourcesResult result;
std::vector<bool> existing_matched(existing_resources.size(), false);
result.has_changes = false;
for (auto& incoming_res : incoming_resources) {
int matched_index = FindMatchingResourceIndex(
incoming_res, existing_resources, existing_matched);
if (matched_index != -1) {
existing_matched[matched_index] = true;
const auto& existing_res = existing_resources[matched_index];
// Copy over fields if missing.
if (!incoming_res.url_id.is_valid()) {
incoming_res.url_id = existing_res.url_id;
}
if (!incoming_res.url.is_valid()) {
incoming_res.url = existing_res.url;
}
if (!incoming_res.tab_id.has_value()) {
incoming_res.tab_id = existing_res.tab_id;
}
if (!incoming_res.title.has_value()) {
incoming_res.title = existing_res.title;
}
if (!incoming_res.context_id.has_value()) {
incoming_res.context_id = existing_res.context_id;
}
// Check if anything changed.
if (incoming_res.url_id != existing_res.url_id ||
incoming_res.url != existing_res.url ||
incoming_res.tab_id != existing_res.tab_id ||
incoming_res.title != existing_res.title ||
incoming_res.context_id != existing_res.context_id) {
result.has_changes = true;
result.added_or_updated_resources.push_back(incoming_res);
}
} else {
// New resource.
result.has_changes = true;
if (!incoming_res.url_id.is_valid()) {
incoming_res.url_id = base::Uuid::GenerateRandomV4();
}
result.added_or_updated_resources.push_back(incoming_res);
}
}
// Check for removed resources.
for (size_t i = 0; i < existing_resources.size(); ++i) {
if (!existing_matched[i]) {
result.has_changes = true;
result.removed_resource_ids.push_back(existing_resources[i].url_id);
}
}
// If no content changes, adds or removes were detected, check if the order
// has changed.
if (!result.has_changes &&
existing_resources.size() == incoming_resources.size()) {
for (size_t i = 0; i < existing_resources.size(); ++i) {
if (existing_resources[i].url_id != incoming_resources[i].url_id) {
result.has_changes = true;
break;
}
}
}
result.final_resources = std::move(incoming_resources);
return result;
}
void RecordNumberOfActiveTasks(int count) {
base::UmaHistogramCounts100("ContextualTasks.ActiveTasksCount", count);
}
} // namespace
ContextualTasksServiceImpl::ContextualTasksServiceImpl(
version_info::Channel channel,
syncer::RepeatingDataTypeStoreFactory data_type_store_factory,
std::unique_ptr<CompositeContextDecorator> composite_context_decorator,
AimEligibilityService* aim_eligibility_service,
signin::IdentityManager* identity_manager,
PrefService* pref_service,
bool supports_ephemeral_only,
base::RepeatingCallback<size_t()> get_active_task_count_callback)
: composite_context_decorator_(std::move(composite_context_decorator)),
get_active_task_count_callback_(
std::move(get_active_task_count_callback)),
aim_eligibility_service_(aim_eligibility_service),
identity_manager_(identity_manager),
pref_service_(pref_service),
supports_ephemeral_only_(supports_ephemeral_only) {
const base::RepeatingClosure& dump_stack =
base::BindRepeating(&syncer::ReportUnrecoverableError, channel);
auto ai_thread_processor =
std::make_unique<syncer::ClientTagBasedDataTypeProcessor>(
syncer::AI_THREAD, dump_stack);
ai_thread_sync_bridge_ = std::make_unique<AiThreadSyncBridge>(
std::move(ai_thread_processor), data_type_store_factory);
auto contextual_task_processor =
std::make_unique<syncer::ClientTagBasedDataTypeProcessor>(
syncer::CONTEXTUAL_TASK, dump_stack);
contextual_task_sync_bridge_ = std::make_unique<ContextualTaskSyncBridge>(
std::move(contextual_task_processor), data_type_store_factory);
// Wait for both AiThreadSyncBridge and ContextualTaskSyncBridge to finish
// loading their data store.
on_data_loaded_barrier_ = base::BarrierClosure(
2, base::BindOnce(&ContextualTasksServiceImpl::OnDataStoresLoaded,
weak_ptr_factory_.GetWeakPtr()));
}
ContextualTasksServiceImpl::~ContextualTasksServiceImpl() {
for (auto& observer : observers_) {
observer.OnWillBeDestroyed();
}
}
FeatureEligibility ContextualTasksServiceImpl::GetFeatureEligibility() {
return {base::FeatureList::IsEnabled(contextual_tasks::kContextualTasks),
aim_eligibility_service_->IsAimEligible(),
contextual_search::ContextualSearchService::IsContextSharingEnabled(
pref_service_)};
}
bool ContextualTasksServiceImpl::IsInitialized() {
return is_initialized_;
}
ContextualTask ContextualTasksServiceImpl::CreateTask() {
// Note that we are adding 1 to the number of active tasks because the
// histogram is recorded before the task is created, but it's not
// immediately reflected in the active tasks count.
RecordNumberOfActiveTasks(get_active_task_count_callback_.Run() + 1);
base::Uuid task_id = base::Uuid::GenerateRandomV4();
ContextualTask task(task_id, supports_ephemeral_only_);
return AddTaskAndNotify(std::move(task));
}
ContextualTask ContextualTasksServiceImpl::CreateTaskFromUrl(const GURL& url) {
// Note that we are adding 1 to the number of active tasks because the
// histogram is recorded before the task is created, but it's not
// immediately reflected in the active tasks count.
RecordNumberOfActiveTasks(get_active_task_count_callback_.Run() + 1);
base::Uuid task_id = base::Uuid::GenerateRandomV4();
bool is_ephemeral = supports_ephemeral_only_ ||
!IsUrlForPrimaryAccount(identity_manager_, url);
ContextualTask task(task_id, is_ephemeral);
return AddTaskAndNotify(std::move(task));
}
void ContextualTasksServiceImpl::GetTaskById(
const base::Uuid& task_id,
base::OnceCallback<void(std::optional<ContextualTask>)> callback) const {
auto it = tasks_.find(task_id);
std::optional<ContextualTask> result;
if (it != tasks_.end()) {
result = it->second;
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(result)));
}
void ContextualTasksServiceImpl::GetTasks(
base::OnceCallback<void(std::vector<ContextualTask>)> callback) const {
std::vector<ContextualTask> tasks;
for (const auto& pair : tasks_) {
ContextualTask task = pair.second;
if (task.IsEphemeral() || supports_ephemeral_only_) {
continue;
}
tasks.push_back(task);
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(tasks)));
}
void ContextualTasksServiceImpl::DeleteTask(const base::Uuid& task_id) {
contextual_task_sync_bridge_->OnTaskRemovedLocally(task_id);
RemoveTaskInternal(task_id, TriggerSource::kLocal);
}
void ContextualTasksServiceImpl::UpdateThreadForTask(
const base::Uuid& task_id,
ThreadType thread_type,
const std::string& server_id,
std::optional<std::string> conversation_turn_id,
std::optional<std::string> title) {
auto [it, is_new_task] = FindOrCreateTask(task_id, thread_type, server_id);
// If a thread already exists and its server ID does not match the new server
// ID, it indicates a mismatch or an attempt to update a different thread, so
// we return.
std::optional<Thread> thread = it->second.GetThread();
// If the task doesn't exist doesn't have the right thread, return early.
if (thread.has_value() &&
(thread->server_id != server_id || thread->type != thread_type)) {
return;
}
// Determine the new title and conversation turn ID. If provided, use them;
// otherwise, retain the existing values if a thread already exists.
const std::string& new_title =
title.value_or(thread.has_value() ? thread->title : "");
const std::string& new_conversation_turn_id = conversation_turn_id.value_or(
thread.has_value() ? thread->conversation_turn_id : "");
// Add or update the thread information within the task.
it->second.AddThread(
Thread(thread_type, server_id, new_title, new_conversation_turn_id));
if (is_new_task) {
contextual_task_sync_bridge_->OnTaskAddedLocally(it->second);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&ContextualTasksServiceImpl::NotifyTaskAdded,
weak_ptr_factory_.GetWeakPtr(), it->second,
TriggerSource::kLocal));
} else {
contextual_task_sync_bridge_->OnTaskUpdatedLocally(it->second);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ContextualTasksServiceImpl::NotifyTaskUpdated,
weak_ptr_factory_.GetWeakPtr(), it->second,
TriggerSource::kLocal));
}
}
void ContextualTasksServiceImpl::RemoveThreadFromTask(
const base::Uuid& task_id,
ThreadType type,
const std::string& server_id) {
auto it = tasks_.find(task_id);
if (it != tasks_.end()) {
it->second.RemoveThread(type, server_id);
// If the task no longer has any thread, remove it.
if (!it->second.GetThread()) {
DeleteTask(task_id);
}
}
}
std::optional<ContextualTask> ContextualTasksServiceImpl::GetTaskFromServerId(
ThreadType thread_type,
const std::string& server_id) {
for (const auto& pair : tasks_) {
std::optional<Thread> thread = pair.second.GetThread();
if (thread.has_value() && thread->type == thread_type &&
thread->server_id == server_id) {
return pair.second;
}
}
return std::nullopt;
}
void ContextualTasksServiceImpl::AttachUrlToTask(const base::Uuid& task_id,
const GURL& url) {
auto it = tasks_.find(task_id);
if (it != tasks_.end()) {
UrlResource url_resource(base::Uuid::GenerateRandomV4(), url);
if (it->second.AddUrlResource(url_resource)) {
contextual_task_sync_bridge_->OnUrlAddedToTaskLocally(task_id,
url_resource);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ContextualTasksServiceImpl::NotifyTaskUpdated,
weak_ptr_factory_.GetWeakPtr(), it->second,
TriggerSource::kLocal));
}
}
}
void ContextualTasksServiceImpl::DetachUrlFromTask(const base::Uuid& task_id,
const GURL& url) {
auto it = tasks_.find(task_id);
if (it != tasks_.end()) {
std::optional<base::Uuid> url_id = it->second.RemoveUrl(url);
if (url_id) {
contextual_task_sync_bridge_->OnUrlRemovedFromTaskLocally(url_id.value());
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ContextualTasksServiceImpl::NotifyTaskUpdated,
weak_ptr_factory_.GetWeakPtr(), it->second,
TriggerSource::kLocal));
}
}
}
void ContextualTasksServiceImpl::SetUrlResourcesFromServer(
const base::Uuid& task_id,
std::vector<UrlResource> url_resources) {
auto it = tasks_.find(task_id);
if (it == tasks_.end()) {
return;
}
// Merge incoming resources with existing ones and calculate the diff.
ContextualTask& task = it->second;
MergeUrlResourcesResult result =
MergeUrlResources(task.GetUrlResources(), std::move(url_resources));
if (!result.has_changes) {
return;
}
// Notify sync bridge about changed resources.
for (const auto& res : result.added_or_updated_resources) {
contextual_task_sync_bridge_->OnUrlAddedToTaskLocally(task_id, res);
}
for (const auto& id : result.removed_resource_ids) {
contextual_task_sync_bridge_->OnUrlRemovedFromTaskLocally(id);
}
// Update the local in-memory task state.
task.SetUrlResourcesFromServer(std::move(result.final_resources));
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&ContextualTasksServiceImpl::NotifyTaskUpdated,
weak_ptr_factory_.GetWeakPtr(), task,
TriggerSource::kLocal));
}
void ContextualTasksServiceImpl::AssociateTabWithTask(const base::Uuid& task_id,
SessionID tab_id) {
auto it = tasks_.find(task_id);
if (it == tasks_.end()) {
return;
}
std::optional<ContextualTask> current_task = GetContextualTaskForTab(tab_id);
if (current_task) {
if (current_task->GetTaskId() == task_id) {
// The tab is already associated with this exact task.
// Return early to prevent unnecessary disassociation (which could delete
// the task).
return;
}
DisassociateTabFromTask(current_task->GetTaskId(), tab_id);
}
tab_to_task_[tab_id] = task_id;
it->second.AddTabId(tab_id);
base::UmaHistogramCounts100("ContextualTasks.TabAffiliationCount",
GetTabsAssociatedWithTask(task_id).size());
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ContextualTasksServiceImpl::NotifyTaskAssociatedToTab,
weak_ptr_factory_.GetWeakPtr(), task_id, tab_id));
}
void ContextualTasksServiceImpl::DisassociateTabFromTask(
const base::Uuid& task_id,
SessionID tab_id) {
tab_to_task_.erase(tab_id);
auto it = tasks_.find(task_id);
if (it != tasks_.end()) {
it->second.RemoveTabId(tab_id);
}
// If the task doesn't have a thread and tabs associated with it,
// it can be safely removed here.
if (!it->second.GetThread() && it->second.GetTabIds().empty()) {
RemoveTaskInternal(task_id, TriggerSource::kLocal);
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&ContextualTasksServiceImpl::NotifyTaskDisassociatedFromTab,
weak_ptr_factory_.GetWeakPtr(), task_id, tab_id));
}
void ContextualTasksServiceImpl::DisassociateAllTabsFromTask(
const base::Uuid& task_id) {
for (const SessionID& tab_id : GetTabsAssociatedWithTask(task_id)) {
DisassociateTabFromTask(task_id, tab_id);
}
}
std::optional<ContextualTask>
ContextualTasksServiceImpl::GetContextualTaskForTab(SessionID tab_id) const {
auto it = tab_to_task_.find(tab_id);
if (it != tab_to_task_.end()) {
auto task_it = tasks_.find(it->second);
if (task_it != tasks_.end()) {
return task_it->second;
}
}
return std::nullopt;
}
std::vector<SessionID> ContextualTasksServiceImpl::GetTabsAssociatedWithTask(
const base::Uuid& task_id) const {
std::vector<SessionID> associated_tabs;
for (const auto& pair : tab_to_task_) {
if (pair.second == task_id) {
associated_tabs.push_back(pair.first);
}
}
return associated_tabs;
}
void ContextualTasksServiceImpl::ClearAllTabAssociationsForTask(
const base::Uuid& task_id) {
auto task_it = tasks_.find(task_id);
if (task_it == tasks_.end()) {
return;
}
// Get a copy of the tab IDs before clearing them from the task.
const std::vector<SessionID> tab_ids_to_remove = task_it->second.GetTabIds();
// Clear the tab IDs from the task object itself.
task_it->second.ClearTabIds();
// Remove each of the tab IDs from the main lookup map.
for (const auto& tab_id : tab_ids_to_remove) {
tab_to_task_.erase(tab_id);
}
}
void ContextualTasksServiceImpl::GetContextForTask(
const base::Uuid& task_id,
const std::set<ContextualTaskContextSource>& sources,
std::unique_ptr<ContextDecorationParams> params,
base::OnceCallback<void(std::unique_ptr<ContextualTaskContext>)>
context_callback) {
auto it = tasks_.find(task_id);
if (it == tasks_.end()) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(context_callback),
std::unique_ptr<ContextualTaskContext>()));
return;
}
composite_context_decorator_->DecorateContext(
std::make_unique<ContextualTaskContext>(it->second), sources,
std::move(params), std::move(context_callback));
}
void ContextualTasksServiceImpl::AddObserver(
ContextualTasksService::Observer* observer) {
observers_.AddObserver(observer);
}
void ContextualTasksServiceImpl::RemoveObserver(
ContextualTasksService::Observer* observer) {
observers_.RemoveObserver(observer);
}
base::WeakPtr<syncer::DataTypeControllerDelegate>
ContextualTasksServiceImpl::GetAiThreadControllerDelegate() {
return ai_thread_sync_bridge_->change_processor()->GetControllerDelegate();
}
void ContextualTasksServiceImpl::SetAiThreadSyncBridgeForTesting(
std::unique_ptr<AiThreadSyncBridge> bridge) {
ai_thread_sync_bridge_ = std::move(bridge);
}
void ContextualTasksServiceImpl::SetContextualTaskSyncBridgeForTesting(
std::unique_ptr<ContextualTaskSyncBridge> bridge) {
contextual_task_sync_bridge_ = std::move(bridge);
}
void ContextualTasksServiceImpl::OnThreadDataStoreLoaded() {
on_data_loaded_barrier_.Run();
}
void ContextualTasksServiceImpl::OnThreadAddedOrUpdatedRemotely(
const std::vector<proto::AiThreadEntity>& threads) {
std::map<std::string, const proto::AiThreadEntity&> thread_map;
for (const auto& thread : threads) {
thread_map.emplace(thread.specifics().server_id(), thread);
}
for (auto& task_entry : tasks_) {
ContextualTask& task = task_entry.second;
if (!task.GetThread()) {
continue;
}
auto it = thread_map.find(task.GetThread()->server_id);
if (it == thread_map.end() ||
ToThreadType(it->second.specifics().type()) != task.GetThread()->type) {
continue;
}
// Check if the thread has changed for the task.
const proto::AiThreadEntity& new_thread_entity = it->second;
const std::optional<Thread>& old_thread = task.GetThread();
if (old_thread->conversation_turn_id !=
new_thread_entity.specifics().conversation_turn_id() ||
old_thread->title != new_thread_entity.specifics().title()) {
task.AddThread(
Thread(ThreadType::kAiMode, new_thread_entity.specifics().server_id(),
new_thread_entity.specifics().title(),
new_thread_entity.specifics().conversation_turn_id()));
NotifyTaskUpdated(task, TriggerSource::kRemote);
}
}
}
void ContextualTasksServiceImpl::OnThreadRemovedRemotely(
const std::vector<base::Uuid>& thread_ids) {
std::set<std::string> removed_thread_server_ids;
for (const auto& id : thread_ids) {
removed_thread_server_ids.insert(id.AsLowercaseString());
}
std::vector<base::Uuid> tasks_to_delete;
for (const auto& task_entry : tasks_) {
const ContextualTask& task = task_entry.second;
if (task.GetThread()) {
if (removed_thread_server_ids.count(task.GetThread()->server_id)) {
tasks_to_delete.push_back(task.GetTaskId());
}
}
}
for (const auto& task_id : tasks_to_delete) {
RemoveTaskInternal(task_id, TriggerSource::kRemote);
}
}
std::pair<std::map<base::Uuid, ContextualTask>::iterator, bool>
ContextualTasksServiceImpl::FindOrCreateTask(const base::Uuid& task_id,
ThreadType thread_type,
const std::string& server_id) {
auto it = tasks_.find(task_id);
if (it != tasks_.end()) {
return {it, /*is_new_task=*/false};
}
// Task not found, but we have a task ID. Create the task on the fly unless
// we already have a task for this server ID.
std::optional<ContextualTask> existing_task =
GetTaskFromServerId(thread_type, server_id);
if (existing_task.has_value()) {
// TODO(nyquist): This is a temporary solution to avoid creating
// duplicate tasks. We should remove this once we have a better solution
// for handling out-of-sync tasks.
it = tasks_.find(existing_task->GetTaskId());
return {it, /*is_new_task=*/false};
}
it =
tasks_.emplace(task_id, ContextualTask(task_id, supports_ephemeral_only_))
.first;
return {it, /*is_new_task=*/true};
}
void ContextualTasksServiceImpl::RemoveTaskInternal(const base::Uuid& task_id,
TriggerSource source) {
auto task_it = tasks_.find(task_id);
if (task_it == tasks_.end()) {
return;
}
const auto& task = task_it->second;
for (const auto& tab_id : task.GetTabIds()) {
tab_to_task_.erase(tab_id);
}
tasks_.erase(task_it);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ContextualTasksServiceImpl::NotifyTaskRemoved,
weak_ptr_factory_.GetWeakPtr(), task_id, source));
}
size_t ContextualTasksServiceImpl::GetTabIdMapSizeForTesting() const {
return tab_to_task_.size();
}
void ContextualTasksServiceImpl::OnContextualTaskDataStoreLoaded() {
on_data_loaded_barrier_.Run();
// TODO(shaktisahu): CHECK that no data read from store if
// supports_ephemeral_only_.
}
void ContextualTasksServiceImpl::OnTaskAddedOrUpdatedRemotely(
const std::vector<ContextualTask>& contextual_tasks) {
CHECK(!supports_ephemeral_only_);
for (const auto& task : contextual_tasks) {
if (tasks_.find(task.GetTaskId()) == tasks_.end()) {
tasks_.insert_or_assign(task.GetTaskId(), task);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ContextualTasksServiceImpl::NotifyTaskAdded,
weak_ptr_factory_.GetWeakPtr(), task,
TriggerSource::kRemote));
} else {
tasks_.insert_or_assign(task.GetTaskId(), task);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ContextualTasksServiceImpl::NotifyTaskUpdated,
weak_ptr_factory_.GetWeakPtr(), task,
TriggerSource::kRemote));
}
}
}
void ContextualTasksServiceImpl::OnTaskRemovedRemotely(
const std::vector<base::Uuid>& task_ids) {
CHECK(!supports_ephemeral_only_);
for (const auto& task_id : task_ids) {
RemoveTaskInternal(task_id, TriggerSource::kRemote);
}
}
void ContextualTasksServiceImpl::NotifyTaskAdded(const ContextualTask& task,
TriggerSource source) {
for (auto& observer : observers_) {
observer.OnTaskAdded(task, source);
}
}
void ContextualTasksServiceImpl::NotifyTaskUpdated(const ContextualTask& task,
TriggerSource source) {
for (auto& observer : observers_) {
observer.OnTaskUpdated(task, source);
}
}
void ContextualTasksServiceImpl::NotifyTaskRemoved(const base::Uuid& task_id,
TriggerSource source) {
for (auto& observer : observers_) {
observer.OnTaskRemoved(task_id, source);
}
}
void ContextualTasksServiceImpl::NotifyTaskAssociatedToTab(
const base::Uuid& task_id,
SessionID tab_id) {
observers_.Notify(&ContextualTasksService::Observer::OnTaskAssociatedToTab,
task_id, tab_id);
}
void ContextualTasksServiceImpl::NotifyTaskDisassociatedFromTab(
const base::Uuid& task_id,
SessionID tab_id) {
observers_.Notify(
&ContextualTasksService::Observer::OnTaskDisassociatedFromTab, task_id,
tab_id);
}
ContextualTask ContextualTasksServiceImpl::AddTaskAndNotify(
ContextualTask task) {
auto it = tasks_.emplace(task.GetTaskId(), task).first;
contextual_task_sync_bridge_->OnTaskAddedLocally(task);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&ContextualTasksServiceImpl::NotifyTaskAdded,
weak_ptr_factory_.GetWeakPtr(), it->second,
TriggerSource::kLocal));
return it->second;
}
void ContextualTasksServiceImpl::OnDataStoresLoaded() {
is_initialized_ = true;
std::vector<ContextualTask> tasks = BuildTasks();
for (const auto& task : tasks) {
tasks_.emplace(task.GetTaskId(), task);
}
for (auto& observer : observers_) {
observer.OnInitialized();
}
}
std::vector<ContextualTask> ContextualTasksServiceImpl::BuildTasks() const {
std::vector<ContextualTask> tasks = contextual_task_sync_bridge_->GetTasks();
auto it = tasks.begin();
while (it != tasks.end()) {
// If the task doesn't have a thread, filter it out here as there is no
// proper title to display it. It is also hard to differentiate between
// tasks without threads. The caller should use GetTaskById() to retrieve
// it.
if (!it->GetThread()) {
++it;
continue;
}
std::string thread_id = it->GetThread()->server_id;
std::optional<Thread> thread = ai_thread_sync_bridge_->GetThread(thread_id);
// Thread could be empty if the threads bridge is not fully synced, or if
// the thread is deleted. In both cases we should not returning the task.
// and should either wait for the sync update or delete the task.
if (!thread) {
it = tasks.erase(it);
} else {
it->AddThread(thread.value());
++it;
}
}
return tasks;
}
} // namespace contextual_tasks