// 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/ash/api/tasks/tasks_client_impl.h"
#include <algorithm>
#include <iterator>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <vector>
#include "ash/api/tasks/tasks_client.h"
#include "ash/api/tasks/tasks_types.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/glanceables/glanceables_metrics.h"
#include "base/barrier_closure.h"
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "components/policy/content/policy_blocklist_service.h"
#include "components/policy/core/browser/url_blocklist_manager.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "google_apis/common/api_error_codes.h"
#include "google_apis/common/request_sender.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/tasks/tasks_api_request_types.h"
#include "google_apis/tasks/tasks_api_requests.h"
#include "google_apis/tasks/tasks_api_response_types.h"
#include "google_apis/tasks/tasks_api_task_status.h"
#include "ui/base/models/list_model.h"
namespace ash::api {
namespace {
using ::google_apis::ApiErrorCode;
using ::google_apis::tasks::InsertTaskRequest;
using ::google_apis::tasks::ListTaskListsRequest;
using ::google_apis::tasks::ListTasksRequest;
using ::google_apis::tasks::PatchTaskRequest;
using ::google_apis::tasks::TaskAssignmentInfo;
using ::google_apis::tasks::TaskRequestPayload;
using ::google_apis::tasks::TaskStatus;
constexpr char kTasksUrl[] = "https://tasks.google.com/";
Task::OriginSurfaceType GetTaskOriginSurfaceType(
const std::optional<TaskAssignmentInfo>& assignment_info) {
if (!assignment_info) {
return Task::OriginSurfaceType::kRegular;
}
switch (assignment_info->surface_type()) {
case TaskAssignmentInfo::SurfaceType::kDocument:
return Task::OriginSurfaceType::kDocument;
case TaskAssignmentInfo::SurfaceType::kSpace:
return Task::OriginSurfaceType::kSpace;
case TaskAssignmentInfo::SurfaceType::kUnknown:
return Task::OriginSurfaceType::kUnknown;
}
}
// Converts `raw_tasks` received from Google Tasks API to ash-friendly types.
std::vector<std::unique_ptr<Task>> ConvertTasks(
const std::vector<std::unique_ptr<google_apis::tasks::Task>>& raw_tasks) {
// Find root level tasks and collect task ids that have subtasks in one pass.
std::vector<const google_apis::tasks::Task*> root_tasks;
base::flat_set<std::string> tasks_with_subtasks;
for (const auto& item : raw_tasks) {
if (item->parent_id().empty()) {
root_tasks.push_back(item.get());
} else {
tasks_with_subtasks.insert(item->parent_id());
}
}
// Sort tasks by their position as they appear in the companion app with "My
// order" option selected.
// NOTE: ideally sorting should be performed on the UI/presentation layer, but
// there is a possibility that with further optimizations and plans to keep
// only top N visible tasks in memory, the sorting will need to be done at
// this layer.
std::sort(
root_tasks.begin(), root_tasks.end(),
[](const google_apis::tasks::Task* a, const google_apis::tasks::Task* b) {
return a->position().compare(b->position()) < 0;
});
// Convert `root_tasks` to ash-friendly types.
std::vector<std::unique_ptr<Task>> converted_tasks;
converted_tasks.reserve(root_tasks.size());
for (const auto* const root_task : root_tasks) {
const bool completed = root_task->status() == TaskStatus::kCompleted;
const bool has_subtasks = tasks_with_subtasks.contains(root_task->id());
const bool has_email_link =
std::find_if(root_task->links().begin(), root_task->links().end(),
[](const auto& link) {
return link->type() ==
google_apis::tasks::TaskLink::Type::kEmail;
}) != root_task->links().end();
const bool has_notes = !root_task->notes().empty();
converted_tasks.push_back(std::make_unique<Task>(
root_task->id(), root_task->title(), root_task->due(), completed,
has_subtasks, has_email_link, has_notes, root_task->updated(),
root_task->web_view_link(),
GetTaskOriginSurfaceType(root_task->assignment_info())));
}
return converted_tasks;
}
} // namespace
TasksClientImpl::TaskListsFetchState::TaskListsFetchState() = default;
TasksClientImpl::TaskListsFetchState::~TaskListsFetchState() = default;
TasksClientImpl::TasksFetchState::TasksFetchState() = default;
TasksClientImpl::TasksFetchState::~TasksFetchState() = default;
TasksClientImpl::TasksClientImpl(
Profile* profile,
const TasksClientImpl::CreateRequestSenderCallback&
create_request_sender_callback,
net::NetworkTrafficAnnotationTag traffic_annotation_tag)
: profile_(profile),
create_request_sender_callback_(create_request_sender_callback),
traffic_annotation_tag_(traffic_annotation_tag) {}
TasksClientImpl::~TasksClientImpl() = default;
bool TasksClientImpl::IsDisabledByAdmin() const {
// 1) Check the pref.
const auto* const pref_service = profile_->GetPrefs();
if (!pref_service ||
!base::Contains(pref_service->GetList(
prefs::kContextualGoogleIntegrationsConfiguration),
prefs::kGoogleTasksIntegrationName)) {
RecordContextualGoogleIntegrationStatus(
prefs::kGoogleTasksIntegrationName,
ContextualGoogleIntegrationStatus::kDisabledByPolicy);
return true;
}
// 2) Check if the Calendar app (home app for Tasks) is disabled by policy.
if (!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(
profile_)) {
return true;
}
auto calendar_app_readiness = apps::Readiness::kUnknown;
apps::AppServiceProxyFactory::GetForProfile(profile_)
->AppRegistryCache()
.ForOneApp(web_app::kGoogleCalendarAppId,
[&calendar_app_readiness](const apps::AppUpdate& update) {
calendar_app_readiness = update.Readiness();
});
if (calendar_app_readiness == apps::Readiness::kDisabledByPolicy) {
RecordContextualGoogleIntegrationStatus(
prefs::kGoogleTasksIntegrationName,
ContextualGoogleIntegrationStatus::kDisabledByAppBlock);
return true;
}
// 3) Check if the Tasks URL is blocked by policy.
const auto* const policy_blocklist_service =
PolicyBlocklistFactory::GetForBrowserContext(profile_);
if (!policy_blocklist_service ||
policy_blocklist_service->GetURLBlocklistState(GURL(kTasksUrl)) ==
policy::URLBlocklist::URLBlocklistState::URL_IN_BLOCKLIST) {
RecordContextualGoogleIntegrationStatus(
prefs::kGoogleTasksIntegrationName,
ContextualGoogleIntegrationStatus::kDisabledByUrlBlock);
return true;
}
RecordContextualGoogleIntegrationStatus(
prefs::kGoogleTasksIntegrationName,
ContextualGoogleIntegrationStatus::kEnabled);
return false;
}
const ui::ListModel<api::TaskList>* TasksClientImpl::GetCachedTaskLists() {
// Note that this doesn't consider `task_lists_fetch_state_`, which means that
// the returned `task_lists_` may contain data that was fetched long time ago.
if (task_lists_.item_count() == 0) {
return nullptr;
}
return &task_lists_;
}
void TasksClientImpl::GetTaskLists(bool force_fetch,
TasksClient::GetTaskListsCallback callback) {
if (task_lists_fetch_state_.status == FetchStatus::kFresh && !force_fetch) {
// The http error code should be null here since we use the cached fresh
// data instead of fetching from the Google Task API.
std::move(callback).Run(/*success=*/true, std::nullopt, &task_lists_);
return;
}
task_lists_fetch_state_.callbacks.push_back(std::move(callback));
if (task_lists_fetch_state_.status != FetchStatus::kRefreshing) {
task_lists_fetch_state_.status = FetchStatus::kRefreshing;
FetchTaskListsPage(/*page_token=*/"", /*page_number=*/1,
/*accumulated_raw_task_lists=*/{});
}
}
const ui::ListModel<api::Task>* TasksClientImpl::GetCachedTasksInTaskList(
const std::string& task_list_id) {
// Note that this doesn't consider `tasks_fetch_state_`, which means that the
// returned tasks may contain data that was fetched long time ago.
const auto iter = tasks_in_task_lists_.find(task_list_id);
if (iter == tasks_in_task_lists_.end()) {
return nullptr;
}
return &iter->second;
}
void TasksClientImpl::GetTasks(const std::string& task_list_id,
bool force_fetch,
TasksClient::GetTasksCallback callback) {
CHECK(!task_list_id.empty());
// TODO(b/337281579): Creating a list model in `tasks_in_task_lists_` before
// the tasks are actually returned will have `GetCachedTasksInTaskList()`
// return an empty list model, which is wrong as it is not the actual result.
// Move this to `RunGetTasksCallbacks()` will make more sense.
const auto [iter, inserted] = tasks_in_task_lists_.emplace(
std::piecewise_construct, std::forward_as_tuple(task_list_id),
std::forward_as_tuple());
const auto [status_it, state_inserted] =
tasks_fetch_state_.emplace(task_list_id, nullptr);
if (!status_it->second) {
status_it->second = std::make_unique<TasksFetchState>();
}
TasksFetchState& fetch_state = *status_it->second;
if (fetch_state.status == FetchStatus::kFresh && !force_fetch) {
// The http error code should be null here since we use the cached fresh
// data instead of fetching from the Google Task API.
std::move(callback).Run(/*success=*/true, std::nullopt, &iter->second);
return;
}
fetch_state.callbacks.push_back(std::move(callback));
if (fetch_state.status != FetchStatus::kRefreshing) {
fetch_state.status = FetchStatus::kRefreshing;
FetchTasksPage(task_list_id, /*page_token=*/"", /*page_number=*/1,
/*accumulated_raw_tasks=*/{});
}
}
void TasksClientImpl::MarkAsCompleted(const std::string& task_list_id,
const std::string& task_id,
bool completed) {
CHECK(!task_list_id.empty());
CHECK(!task_id.empty());
if (completed) {
pending_completed_tasks_[task_list_id].insert(task_id);
} else {
if (pending_completed_tasks_.contains(task_list_id)) {
pending_completed_tasks_[task_list_id].erase(task_id);
}
}
}
void TasksClientImpl::AddTask(const std::string& task_list_id,
const std::string& title,
TasksClient::OnTaskSavedCallback callback) {
CHECK(!task_list_id.empty());
CHECK(!title.empty());
CHECK(callback);
auto* const request_sender = GetRequestSender();
request_sender->StartRequestWithAuthRetry(std::make_unique<InsertTaskRequest>(
request_sender, task_list_id, /*previous_task_id=*/"",
TaskRequestPayload{.title = title, .status = TaskStatus::kNeedsAction},
base::BindOnce(&TasksClientImpl::OnTaskAdded, weak_factory_.GetWeakPtr(),
task_list_id, base::Time::Now(), std::move(callback))));
}
void TasksClientImpl::UpdateTask(const std::string& task_list_id,
const std::string& task_id,
const std::string& title,
bool completed,
TasksClient::OnTaskSavedCallback callback) {
CHECK(!task_list_id.empty());
CHECK(!task_id.empty());
CHECK(!title.empty() || completed);
CHECK(callback);
auto* const request_sender = GetRequestSender();
request_sender->StartRequestWithAuthRetry(std::make_unique<PatchTaskRequest>(
request_sender, task_list_id, task_id,
TaskRequestPayload{.title = title,
.status = completed ? TaskStatus::kCompleted
: TaskStatus::kNeedsAction},
base::BindOnce(&TasksClientImpl::OnTaskUpdated,
weak_factory_.GetWeakPtr(), task_list_id,
base::Time::Now(), std::move(callback))));
}
std::optional<base::Time> TasksClientImpl::GetTasksLastUpdateTime(
const std::string& task_list_id) const {
const auto iter = tasks_fetch_state_.find(task_list_id);
if (iter == tasks_fetch_state_.end()) {
return std::nullopt;
}
return iter->second->last_updated_time;
}
void TasksClientImpl::InvalidateCache() {
for (auto& task_list_state : tasks_fetch_state_) {
if (task_list_state.second->status == FetchStatus::kRefreshing) {
RunGetTasksCallbacks(task_list_state.first, FetchStatus::kNotFresh,
std::nullopt,
/*accumulated_raw_tasks=*/{});
} else {
task_list_state.second->status = FetchStatus::kNotFresh;
}
}
if (task_lists_fetch_state_.status == FetchStatus::kRefreshing) {
RunGetTaskListsCallbacks(FetchStatus::kNotFresh, std::nullopt,
/*accumulated_raw_task_lists=*/{});
} else {
task_lists_fetch_state_.status = FetchStatus::kNotFresh;
}
}
void TasksClientImpl::OnGlanceablesBubbleClosed(base::OnceClosure callback) {
// TODO(b/324462272): We need to watch this. This could cause one client to
// cancel the in-flight callbacks for requests sent by another client. This
// could become an issue when we have multiple clients for one
// `TasksClientImpl`. Remove this after adding a `kRefreshingInvalidated`
// state.
weak_factory_.InvalidateWeakPtrs();
int num_tasks_completed = 0;
for (const auto& [task_list_id, task_ids] : pending_completed_tasks_) {
num_tasks_completed += task_ids.size();
}
base::RepeatingClosure barrier_closure =
base::BarrierClosure(num_tasks_completed, std::move(callback));
// TODO(b/323975767): Generalize this histogram to the Tasks API.
base::UmaHistogramCounts100(
"Ash.Glanceables.Api.Tasks.SimultaneousMarkAsCompletedRequestsCount",
num_tasks_completed);
for (const auto& [task_list_id, task_ids] : pending_completed_tasks_) {
for (const auto& task_id : task_ids) {
auto* const request_sender = GetRequestSender();
request_sender->StartRequestWithAuthRetry(
std::make_unique<PatchTaskRequest>(
request_sender, task_list_id, task_id,
TaskRequestPayload{.status = TaskStatus::kCompleted},
base::BindOnce(&TasksClientImpl::OnMarkedAsCompleted,
weak_factory_.GetWeakPtr(), base::Time::Now(),
barrier_closure)));
}
}
pending_completed_tasks_.clear();
InvalidateCache();
}
void TasksClientImpl::FetchTaskListsPage(
const std::string& page_token,
int page_number,
std::vector<std::unique_ptr<google_apis::tasks::TaskList>>
accumulated_raw_task_lists) {
auto* const request_sender = GetRequestSender();
request_sender->StartRequestWithAuthRetry(
std::make_unique<ListTaskListsRequest>(
request_sender, page_token,
base::BindOnce(&TasksClientImpl::OnTaskListsPageFetched,
weak_factory_.GetWeakPtr(), base::Time::Now(),
page_number, std::move(accumulated_raw_task_lists))));
if (task_lists_request_callback_) {
task_lists_request_callback_.Run(page_token);
}
}
void TasksClientImpl::OnTaskListsPageFetched(
const base::Time& request_start_time,
int page_number,
std::vector<std::unique_ptr<google_apis::tasks::TaskList>>
accumulated_raw_task_lists,
base::expected<std::unique_ptr<google_apis::tasks::TaskLists>, ApiErrorCode>
result) {
// TODO(b/323975767): Generalize these histograms to the Tasks API.
base::UmaHistogramTimes("Ash.Glanceables.Api.Tasks.GetTaskLists.Latency",
base::Time::Now() - request_start_time);
base::UmaHistogramSparse("Ash.Glanceables.Api.Tasks.GetTaskLists.Status",
result.error_or(ApiErrorCode::HTTP_SUCCESS));
if (!result.has_value()) {
RunGetTaskListsCallbacks(FetchStatus::kNotFresh, result.error(),
/*accumulated_raw_task_lists=*/{});
return;
}
accumulated_raw_task_lists.insert(
accumulated_raw_task_lists.end(),
std::make_move_iterator(result.value()->mutable_items()->begin()),
std::make_move_iterator(result.value()->mutable_items()->end()));
if (result.value()->next_page_token().empty()) {
// TODO(b/323975767): Generalize these histograms to the Tasks API.
base::UmaHistogramCounts100(
"Ash.Glanceables.Api.Tasks.GetTaskLists.PagesCount", page_number);
base::UmaHistogramCounts100("Ash.Glanceables.Api.Tasks.TaskListsCount",
accumulated_raw_task_lists.size());
// Get the fresh data from the Google Task API request successfully.
RunGetTaskListsCallbacks(FetchStatus::kFresh, ApiErrorCode::HTTP_SUCCESS,
std::move(accumulated_raw_task_lists));
} else {
FetchTaskListsPage(result.value()->next_page_token(), page_number + 1,
std::move(accumulated_raw_task_lists));
}
}
void TasksClientImpl::FetchTasksPage(
const std::string& task_list_id,
const std::string& page_token,
int page_number,
std::vector<std::unique_ptr<google_apis::tasks::Task>>
accumulated_raw_tasks) {
auto* const request_sender = GetRequestSender();
request_sender->StartRequestWithAuthRetry(std::make_unique<ListTasksRequest>(
request_sender, task_list_id, page_token, /*include_assigned=*/
features::IsGlanceablesTimeManagementTasksViewAssignedTasksEnabled(),
base::BindOnce(&TasksClientImpl::OnTasksPageFetched,
weak_factory_.GetWeakPtr(), task_list_id,
std::move(accumulated_raw_tasks), base::Time::Now(),
page_number)));
if (tasks_request_callback_) {
tasks_request_callback_.Run(task_list_id, page_token);
}
}
void TasksClientImpl::OnTasksPageFetched(
const std::string& task_list_id,
std::vector<std::unique_ptr<google_apis::tasks::Task>>
accumulated_raw_tasks,
const base::Time& request_start_time,
int page_number,
base::expected<std::unique_ptr<google_apis::tasks::Tasks>, ApiErrorCode>
result) {
// TODO(b/323975767): Generalize these histograms to the Tasks API.
base::UmaHistogramTimes("Ash.Glanceables.Api.Tasks.GetTasks.Latency",
base::Time::Now() - request_start_time);
base::UmaHistogramSparse("Ash.Glanceables.Api.Tasks.GetTasks.Status",
result.error_or(ApiErrorCode::HTTP_SUCCESS));
if (!result.has_value()) {
RunGetTasksCallbacks(task_list_id, FetchStatus::kNotFresh, result.error(),
/*accumulated_raw_tasks=*/{});
return;
}
accumulated_raw_tasks.insert(
accumulated_raw_tasks.end(),
std::make_move_iterator(result.value()->mutable_items()->begin()),
std::make_move_iterator(result.value()->mutable_items()->end()));
if (result.value()->next_page_token().empty()) {
// TODO(b/323975767): Generalize these histograms to the Tasks API.
base::UmaHistogramCounts100("Ash.Glanceables.Api.Tasks.GetTasks.PagesCount",
page_number);
base::UmaHistogramCounts100("Ash.Glanceables.Api.Tasks.RawTasksCount",
accumulated_raw_tasks.size());
// Get the fresh data from the Google Task API request successfully.
RunGetTasksCallbacks(task_list_id, FetchStatus::kFresh,
ApiErrorCode::HTTP_SUCCESS,
std::move(accumulated_raw_tasks));
} else {
FetchTasksPage(task_list_id, result.value()->next_page_token(),
page_number + 1, std::move(accumulated_raw_tasks));
}
}
void TasksClientImpl::RunGetTaskListsCallbacks(
FetchStatus final_fetch_status,
std::optional<google_apis::ApiErrorCode> http_error,
std::vector<std::unique_ptr<google_apis::tasks::TaskList>>
accumulated_raw_task_lists) {
task_lists_fetch_state_.status = final_fetch_status;
if (final_fetch_status == FetchStatus::kFresh) {
task_lists_.DeleteAll();
// Gather existing cached task lists, and clear the ones that are no longer
// present in the task list.
std::set<std::string> abandoned_task_lists;
for (const auto& task_list : tasks_in_task_lists_) {
abandoned_task_lists.insert(task_list.first);
}
for (const auto& fetch_state : tasks_fetch_state_) {
abandoned_task_lists.insert(fetch_state.first);
}
for (const auto& raw_item : accumulated_raw_task_lists) {
abandoned_task_lists.erase(raw_item->id());
task_lists_.Add(std::make_unique<TaskList>(
raw_item->id(), raw_item->title(), raw_item->updated()));
}
for (const std::string& task_list_id : abandoned_task_lists) {
tasks_in_task_lists_.erase(task_list_id);
tasks_fetch_state_.erase(task_list_id);
}
}
std::vector<GetTaskListsCallback> callbacks;
task_lists_fetch_state_.callbacks.swap(callbacks);
for (auto& callback : callbacks) {
std::move(callback).Run(
/*success=*/final_fetch_status == FetchStatus::kFresh, http_error,
&task_lists_);
}
}
void TasksClientImpl::RunGetTasksCallbacks(
const std::string& task_list_id,
FetchStatus final_fetch_status,
std::optional<google_apis::ApiErrorCode> http_error,
std::vector<std::unique_ptr<google_apis::tasks::Task>>
accumulated_raw_tasks) {
auto fetch_state_it = tasks_fetch_state_.find(task_list_id);
if (fetch_state_it == tasks_fetch_state_.end()) {
return;
}
const auto iter = tasks_in_task_lists_.find(task_list_id);
TasksFetchState* fetch_state = fetch_state_it->second.get();
if (final_fetch_status == FetchStatus::kFresh &&
iter != tasks_in_task_lists_.end()) {
iter->second.DeleteAll();
for (auto& item : ConvertTasks(accumulated_raw_tasks)) {
iter->second.Add(std::move(item));
}
// TODO(b/323975767): Generalize this histogram to the Tasks API.
base::UmaHistogramCounts100("Ash.Glanceables.Api.Tasks.ProcessedTasksCount",
iter->second.item_count());
fetch_state->last_updated_time = base::Time::Now();
}
fetch_state->status = final_fetch_status;
std::vector<GetTasksCallback> callbacks;
fetch_state->callbacks.swap(callbacks);
const auto* task_list =
iter != tasks_in_task_lists_.end() ? &iter->second : &stub_task_list_;
for (auto& callback : callbacks) {
std::move(callback).Run(
/*success=*/final_fetch_status == FetchStatus::kFresh, http_error,
task_list);
}
}
void TasksClientImpl::OnMarkedAsCompleted(
const base::Time& request_start_time,
base::RepeatingClosure on_done,
base::expected<std::unique_ptr<google_apis::tasks::Task>, ApiErrorCode>
result) {
// TODO(b/323975767): Generalize these histograms to the Tasks API.
base::UmaHistogramTimes("Ash.Glanceables.Api.Tasks.PatchTask.Latency",
base::Time::Now() - request_start_time);
base::UmaHistogramSparse("Ash.Glanceables.Api.Tasks.PatchTask.Status",
result.error_or(ApiErrorCode::HTTP_SUCCESS));
on_done.Run();
}
void TasksClientImpl::OnTaskAdded(
const std::string& task_list_id,
const base::Time& request_start_time,
TasksClient::OnTaskSavedCallback callback,
base::expected<std::unique_ptr<google_apis::tasks::Task>, ApiErrorCode>
result) {
// TODO(b/323975767): Generalize these histograms to the Tasks API.
base::UmaHistogramTimes("Ash.Glanceables.Api.Tasks.InsertTask.Latency",
base::Time::Now() - request_start_time);
base::UmaHistogramSparse("Ash.Glanceables.Api.Tasks.InsertTask.Status",
result.error_or(ApiErrorCode::HTTP_SUCCESS));
if (!result.has_value()) {
std::move(callback).Run(/*http_error=*/result.error(), /*task=*/nullptr);
return;
}
const auto iter = tasks_in_task_lists_.find(task_list_id);
if (iter == tasks_in_task_lists_.end()) {
std::move(callback).Run(/*http_error=*/ApiErrorCode::HTTP_SUCCESS,
/*task=*/nullptr);
return;
}
const auto* const task = iter->second.AddAt(
/*index=*/0,
std::make_unique<Task>(result.value()->id(), result.value()->title(),
/*due=*/std::nullopt, /*completed=*/false,
/*has_subtasks=*/false,
/*has_email_link=*/false, /*has_notes=*/false,
result.value()->updated(),
result.value()->web_view_link(),
Task::OriginSurfaceType::kRegular));
std::move(callback).Run(/*http_error=*/ApiErrorCode::HTTP_SUCCESS,
/*task=*/task);
}
void TasksClientImpl::OnTaskUpdated(
const std::string& task_list_id,
const base::Time& request_start_time,
TasksClient::OnTaskSavedCallback callback,
base::expected<std::unique_ptr<google_apis::tasks::Task>, ApiErrorCode>
result) {
// TODO(b/323975767): Generalize these histograms to the Tasks API.
base::UmaHistogramTimes("Ash.Glanceables.Api.Tasks.PatchTask.Latency",
base::Time::Now() - request_start_time);
base::UmaHistogramSparse("Ash.Glanceables.Api.Tasks.PatchTask.Status",
result.error_or(ApiErrorCode::HTTP_SUCCESS));
if (!result.has_value()) {
std::move(callback).Run(/*http_error=*/result.error(), /*task=*/nullptr);
return;
}
const auto tasks_iter = tasks_in_task_lists_.find(task_list_id);
if (tasks_iter == tasks_in_task_lists_.end()) {
std::move(callback).Run(/*http_error=*/ApiErrorCode::HTTP_SUCCESS,
/*task=*/nullptr);
return;
}
const auto* const result_data = result->get();
const auto task_iter =
std::find_if(tasks_iter->second.begin(), tasks_iter->second.end(),
[&result_data](const auto& task) {
return task->id == result_data->id();
});
if (task_iter == tasks_iter->second.end()) {
std::move(callback).Run(/*http_error=*/ApiErrorCode::HTTP_SUCCESS,
/*task=*/nullptr);
return;
}
auto* const task = task_iter->get();
task->title = result_data->title();
task->completed = result_data->status() == TaskStatus::kCompleted;
task->updated = result_data->updated();
std::move(callback).Run(/*http_error=*/ApiErrorCode::HTTP_SUCCESS,
/*task=*/task);
}
google_apis::RequestSender* TasksClientImpl::GetRequestSender() {
if (!request_sender_) {
CHECK(create_request_sender_callback_);
request_sender_ = std::move(create_request_sender_callback_)
.Run({GaiaConstants::kTasksReadOnlyOAuth2Scope,
GaiaConstants::kTasksOAuth2Scope},
traffic_annotation_tag_);
CHECK(request_sender_);
}
return request_sender_.get();
}
} // namespace ash::api