[gtm] Implement showing cached tasks before fetching

This cl implements showing cached tasks that was fetched before in the
current session.

Original process to fetch data is to invalidate cache whenever the
glanceables is closed. The next time the glanceables is opened, the
task client considers that the data fetch is needed and does so, even
though the cached data exists.

In this change, we don't invalidate cache when the glanceables is
closed. In addition, we try getting the cached task lists and tasks
if they exist, and then force fetch the updated data at the same time.
Whenever the new data arrives, the views are updated.

Bug: b/321789067
Change-Id: I1b35d345456d75d298209b2d8d06dbec2b9815d3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5325116
Commit-Queue: Wen-Chien Wang <wcwang@chromium.org>
Reviewed-by: Artsiom Mitrokhin <amitrokhin@chromium.org>
Reviewed-by: Toni Barzic <tbarzic@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1269756}
diff --git a/ash/api/tasks/fake_tasks_client.cc b/ash/api/tasks/fake_tasks_client.cc
index 42b2072..5cbc52bc 100644
--- a/ash/api/tasks/fake_tasks_client.cc
+++ b/ash/api/tasks/fake_tasks_client.cc
@@ -36,37 +36,66 @@
 }  // namespace
 
 FakeTasksClient::FakeTasksClient()
-    : task_lists_(std::make_unique<ui::ListModel<TaskList>>()) {}
+    : task_lists_(std::make_unique<ui::ListModel<TaskList>>()),
+      cached_task_lists_(std::make_unique<ui::ListModel<TaskList>>()),
+      cached_tasks_(std::make_unique<ui::ListModel<Task>>()) {}
 
 FakeTasksClient::~FakeTasksClient() = default;
 
+const ui::ListModel<api::TaskList>* FakeTasksClient::GetCachedTaskLists() {
+  if (cached_task_lists_->item_count() == 0) {
+    return nullptr;
+  }
+
+  return cached_task_lists_.get();
+}
+
 void FakeTasksClient::GetTaskLists(bool force_fetch,
                                    GetTaskListsCallback callback) {
-  if (!paused_) {
-    std::move(callback).Run(/*succes=*/true, task_lists_.get());
-  } else {
+  const bool need_fetch = force_fetch || cached_task_lists_->item_count() == 0;
+  auto* task_lists_returned =
+      need_fetch ? task_lists_.get() : cached_task_lists_.get();
+
+  if (paused_ || (paused_on_fetch_ && need_fetch)) {
     pending_get_task_lists_callbacks_.push_back(base::BindOnce(
         [](ui::ListModel<TaskList>* task_lists, GetTaskListsCallback callback) {
           std::move(callback).Run(/*success=*/true, task_lists);
         },
-        task_lists_.get(), std::move(callback)));
+        task_lists_returned, std::move(callback)));
+  } else {
+    std::move(callback).Run(/*success=*/true, task_lists_returned);
   }
 }
 
+const ui::ListModel<api::Task>* FakeTasksClient::GetCachedTasksInTaskList(
+    const std::string& task_list_id) {
+  // TODO(b/321789067): Update `cached_tasks_` to a map of list id to tasks to
+  // adapt more complicated tests.
+  if (task_list_id != cached_task_list_id_) {
+    return nullptr;
+  }
+
+  return cached_tasks_.get();
+}
+
 void FakeTasksClient::GetTasks(const std::string& task_list_id,
                                bool force_fetch,
                                GetTasksCallback callback) {
   auto iter = tasks_in_task_lists_.find(task_list_id);
   CHECK(iter != tasks_in_task_lists_.end());
 
-  if (!paused_) {
-    std::move(callback).Run(/*success=*/true, iter->second.get());
-  } else {
+  const bool need_fetch = force_fetch || task_list_id != cached_task_list_id_;
+  auto* tasks_returned = need_fetch ? iter->second.get() : cached_tasks_.get();
+  cached_task_list_id_ = task_list_id;
+
+  if (paused_ || (paused_on_fetch_ && need_fetch)) {
     pending_get_tasks_callbacks_.push_back(base::BindOnce(
         [](ui::ListModel<Task>* tasks, GetTasksCallback callback) {
           std::move(callback).Run(/*success=*/true, tasks);
         },
-        iter->second.get(), std::move(callback)));
+        tasks_returned, std::move(callback)));
+  } else {
+    std::move(callback).Run(/*success=*/true, tasks_returned);
   }
 }
 
@@ -124,6 +153,8 @@
   RunPendingUpdateTaskCallbacks();
   completed_tasks_ += pending_completed_tasks_.size();
   pending_completed_tasks_.clear();
+  CacheTaskLists();
+  CacheTasks();
   std::move(callback).Run();
 }
 
@@ -144,6 +175,18 @@
   tasks->Add(std::move(task_data));
 }
 
+void FakeTasksClient::DeleteTaskList(const std::string& task_list_id) {
+  // Find the task list iterator with id `task_list_id`.
+  auto iter = std::find_if(
+      task_lists_->begin(), task_lists_->end(),
+      [task_list_id](const auto& list) { return list->id == task_list_id; });
+  if (iter == task_lists_->end()) {
+    return;
+  }
+
+  task_lists_->DeleteAt(iter - task_lists_->begin());
+}
+
 void FakeTasksClient::SetTasksLastUpdateTime(base::Time time) {
   last_updated_time_ = time;
 }
@@ -218,4 +261,24 @@
   std::move(callback).Run(task);
 }
 
+void FakeTasksClient::CacheTaskLists() {
+  cached_task_lists_->DeleteAll();
+  for (const auto& list : *task_lists_) {
+    cached_task_lists_->Add(
+        std::make_unique<TaskList>(list->id, list->title, list->updated));
+  }
+}
+
+void FakeTasksClient::CacheTasks() {
+  auto iter = tasks_in_task_lists_.find(cached_task_list_id_);
+  CHECK(iter != tasks_in_task_lists_.end());
+
+  cached_tasks_->DeleteAll();
+  for (const auto& task : *iter->second) {
+    cached_tasks_->Add(std::make_unique<Task>(
+        task->id, task->title, task->due, task->completed, task->has_subtasks,
+        task->has_email_link, task->has_notes, task->updated));
+  }
+}
+
 }  // namespace ash::api
diff --git a/ash/api/tasks/fake_tasks_client.h b/ash/api/tasks/fake_tasks_client.h
index 12d4b877..6ce5e784 100644
--- a/ash/api/tasks/fake_tasks_client.h
+++ b/ash/api/tasks/fake_tasks_client.h
@@ -34,7 +34,10 @@
   int completed_task_count() { return completed_tasks_; }
 
   // TasksClient:
+  const ui::ListModel<api::TaskList>* GetCachedTaskLists() override;
   void GetTaskLists(bool force_fetch, GetTaskListsCallback callback) override;
+  const ui::ListModel<api::Task>* GetCachedTasksInTaskList(
+      const std::string& task_list_id) override;
   void GetTasks(const std::string& task_list_id,
                 bool force_fetch,
                 GetTasksCallback callback) override;
@@ -62,6 +65,9 @@
   void AddTask(const std::string& task_list_id,
                std::unique_ptr<Task> task_data);
 
+  // Deletes the task list with `task_list_id` from `task_lists_`.
+  void DeleteTaskList(const std::string& task_list_id);
+
   void SetTasksLastUpdateTime(base::Time time);
 
   // Returns `bubble_closed_count_`, while also resetting the counter.
@@ -80,6 +86,7 @@
   size_t RunPendingUpdateTaskCallbacks();
 
   void set_paused(bool paused) { paused_ = paused; }
+  void set_paused_on_fetch(bool paused) { paused_on_fetch_ = paused; }
   void set_run_with_errors(bool run_with_errors) {
     run_with_errors_ = run_with_errors;
   }
@@ -96,9 +103,27 @@
                       bool completed,
                       TasksClient::OnTaskSavedCallback callback);
 
+  // Copies `task_lists_` to `cached_task_lists_` to save the task lists
+  // state when the glanceables are closed.
+  void CacheTaskLists();
+
+  // Copies the current showing tasks to `cached_tasks_` to save the tasks state
+  // when the glanceables are closed.
+  void CacheTasks();
+
   // All available task lists.
   std::unique_ptr<ui::ListModel<TaskList>> task_lists_;
 
+  // The cached task lists that is used before `task_lists_` is simulated to be
+  // fetched.
+  std::unique_ptr<ui::ListModel<TaskList>> cached_task_lists_;
+
+  // The cached tasks that was shown when the glanceables are closed.
+  std::unique_ptr<ui::ListModel<Task>> cached_tasks_;
+
+  // The id of the task list that was shown when the glanceables are closed.
+  std::string cached_task_list_id_;
+
   // Tracks completed tasks and the task list they belong to.
   std::vector<std::string> pending_completed_tasks_;
 
@@ -122,6 +147,11 @@
   // saved to the corresponding list and executed once
   // `RunPending**Callbacks()` is called.
   bool paused_ = false;
+
+  // Similar to `paused_`, but only moves callbacks to pending callbacks when
+  // data fetching is simulated, that is, callbacks are run immediately if the
+  // cached data is used.
+  bool paused_on_fetch_ = false;
   std::list<base::OnceClosure> pending_get_tasks_callbacks_;
   std::list<base::OnceClosure> pending_get_task_lists_callbacks_;
   std::list<base::OnceClosure> pending_add_task_callbacks_;
diff --git a/ash/api/tasks/tasks_client.h b/ash/api/tasks/tasks_client.h
index 66432ab..305723d 100644
--- a/ash/api/tasks/tasks_client.h
+++ b/ash/api/tasks/tasks_client.h
@@ -32,12 +32,23 @@
   using OnTaskSavedCallback = base::OnceCallback<void(const Task* task)>;
   using OnAllPendingCompletedTasksSavedCallback = base::OnceClosure;
 
+  // Returns the list model of the task list that was cached when the
+  // glanceables was previously opened. Returns a nullptr if there is no cached
+  // list.
+  virtual const ui::ListModel<api::TaskList>* GetCachedTaskLists() = 0;
+
   // Retrieves all the authenticated user's task lists and invokes `callback`
   // when done. If `force_fetch` is true, new data will be pulled from the
   // Google Tasks API.
   virtual void GetTaskLists(bool force_fetch,
                             GetTaskListsCallback callback) = 0;
 
+  // Returns the list model of the tasks in task list with `task_list_id` that
+  // was cached when the glanceables was previously opened. Returns a nullptr if
+  // there is no cached tasks or the list does not exist.
+  virtual const ui::ListModel<api::Task>* GetCachedTasksInTaskList(
+      const std::string& task_list_id) = 0;
+
   // Retrieves all tasks in the specified task list (`task_list_id` must not be
   // empty) and invokes `callback` when done. If `force_fetch` is true, new data
   // will be pulled from the Google Tasks API.
diff --git a/ash/glanceables/tasks/glanceables_tasks_view.cc b/ash/glanceables/tasks/glanceables_tasks_view.cc
index 43bebdb..66d9d83 100644
--- a/ash/glanceables/tasks/glanceables_tasks_view.cc
+++ b/ash/glanceables/tasks/glanceables_tasks_view.cc
@@ -70,6 +70,10 @@
 constexpr char kTasksManagementPage[] =
     "https://calendar.google.com/calendar/u/0/r/week?opentasks=1";
 
+api::TasksClient* GetTasksClient() {
+  return Shell::Get()->glanceables_controller()->GetTasksClient();
+}
+
 // Returns a displayable last modified time for kCantUpdateList.
 std::u16string GetLastUpdateTimeMessage(base::Time time) {
   const std::u16string time_of_day = base::TimeFormatTimeOfDay(time);
@@ -245,21 +249,7 @@
       base::to_underlying(GlanceablesViewId::kTasksBubbleHeaderIcon));
 
   tasks_combobox_model_ = std::make_unique<TasksComboboxModel>(task_lists);
-  task_list_combo_box_view_ = tasks_header_view_->AddChildView(
-      std::make_unique<Combobox>(tasks_combobox_model_.get()));
-  task_list_combo_box_view_->SetID(
-      base::to_underlying(GlanceablesViewId::kTasksBubbleComboBox));
-  task_list_combo_box_view_->SetProperty(
-      views::kFlexBehaviorKey,
-      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
-                               views::MaximumFlexSizeRule::kPreferred));
-  combobox_view_observation_.Observe(task_list_combo_box_view_);
-
-  task_list_combo_box_view_->SetTooltipText(l10n_util::GetStringUTF16(
-      IDS_GLANCEABLES_TASKS_DROPDOWN_ACCESSIBLE_NAME));
-  task_list_combo_box_view_->SetAccessibleDescription(u"");
-  task_list_combo_box_view_->SetSelectionChangedCallback(base::BindRepeating(
-      &GlanceablesTasksView::SelectedTasksListChanged, base::Unretained(this)));
+  CreateComboBoxView();
 
   list_footer_view_ =
       list_view->AddChildView(std::make_unique<GlanceablesListFooterView>(
@@ -273,7 +263,16 @@
   list_footer_view_->SetBorder(views::CreateEmptyBorder(kFooterBorderInsets));
   list_footer_view_->SetVisible(false);
 
-  ScheduleUpdateTasksList(/*initial_update=*/true);
+  const auto* active_task_list = GetActiveTaskList();
+  auto* tasks =
+      GetTasksClient()->GetCachedTasksInTaskList(active_task_list->id);
+  if (tasks) {
+    UpdateTasksInTaskList(active_task_list->id, active_task_list->title,
+                          ListShownContext::kCachedList, /*fetch_success=*/true,
+                          tasks);
+  } else {
+    ScheduleUpdateTasks(ListShownContext::kInitialList);
+  }
 }
 
 GlanceablesTasksView::~GlanceablesTasksView() {
@@ -298,6 +297,27 @@
   AnnounceListStateOnComboBoxAccessibility();
 }
 
+void GlanceablesTasksView::UpdateTaskLists(
+    const ui::ListModel<api::TaskList>* task_lists) {
+  tasks_combobox_model_->UpdateTaskLists(task_lists);
+  SetIsLoading(true);
+
+  CHECK(tasks_combobox_model_->GetDefaultIndex().has_value());
+  auto* active_task_list = tasks_combobox_model_->GetTaskListAt(
+      tasks_combobox_model_->GetDefaultIndex().value());
+
+  recreate_combobox_callback_ =
+      base::BindOnce(&GlanceablesTasksView::CreateComboBoxView,
+                     weak_ptr_factory_.GetWeakPtr());
+
+  // Force fetch the updated tasks with the new active task list.
+  GetTasksClient()->GetTasks(
+      active_task_list->id, /*force_fetch=*/true,
+      base::BindOnce(&GlanceablesTasksView::UpdateTasksInTaskList,
+                     weak_ptr_factory_.GetWeakPtr(), active_task_list->id,
+                     active_task_list->title, ListShownContext::kInitialList));
+}
+
 void GlanceablesTasksView::AddNewTaskButtonPressed() {
   // TODO(b/301253574): make sure there is only one view is in `kEdit` state.
   auto* const pending_new_task = task_items_container_view_->AddChildViewAt(
@@ -331,38 +351,49 @@
   weak_ptr_factory_.InvalidateWeakPtrs();
   tasks_requested_time_ = base::TimeTicks::Now();
   tasks_list_change_count_++;
-  ScheduleUpdateTasksList(/*initial_update=*/false);
+  ScheduleUpdateTasks(ListShownContext::kUserSelectedList);
 }
 
-void GlanceablesTasksView::ScheduleUpdateTasksList(bool initial_update) {
+void GlanceablesTasksView::ScheduleUpdateTasks(ListShownContext context) {
   if (!task_list_combo_box_view_->GetSelectedIndex().has_value()) {
     return;
   }
 
-  progress_bar_->UpdateProgressBarVisibility(/*visible=*/true);
+  SetIsLoading(true);
   task_list_combo_box_view_->SetAccessibleDescription(u"");
 
   const auto* const active_task_list = GetActiveTaskList();
   tasks_combobox_model_->SaveLastSelectedTaskList(active_task_list->id);
-  Shell::Get()->glanceables_controller()->GetTasksClient()->GetTasks(
-      active_task_list->id, /*force_fetch=*/false,
-      base::BindOnce(&GlanceablesTasksView::UpdateTasksList,
+  GetTasksClient()->GetTasks(
+      active_task_list->id, /*force_fetch=*/true,
+      base::BindOnce(&GlanceablesTasksView::UpdateTasksInTaskList,
                      weak_ptr_factory_.GetWeakPtr(), active_task_list->id,
-                     active_task_list->title, initial_update));
+                     active_task_list->title, context));
 }
 
-void GlanceablesTasksView::UpdateTasksList(
+void GlanceablesTasksView::UpdateTasksInTaskList(
     const std::string& task_list_id,
     const std::string& task_list_title,
-    bool initial_update,
+    ListShownContext context,
     bool fetch_success,
     const ui::ListModel<api::Task>* tasks) {
   const gfx::Size old_preferred_size = GetPreferredSize();
-  progress_bar_->UpdateProgressBarVisibility(/*visible=*/false);
+  SetIsLoading(false);
+
+  if (!recreate_combobox_callback_.is_null()) {
+    std::move(recreate_combobox_callback_).Run();
+  }
+
+  // Discard the fetched tasks that is not shown now.
+  if (task_list_id != GetActiveTaskList()->id) {
+    return;
+  }
 
   if (!fetch_success) {
-    if (initial_update) {
-      // TODO(b/323959143): Show "Couldn't load item" view.
+    if (!GetTasksClient()->GetCachedTasksInTaskList(task_list_id) &&
+        context == ListShownContext::kInitialList) {
+      // TODO(b/323959143): Show "Couldn't load item" view if there is no cached
+      // view shown.
       return;
     } else {
       // TODO(b/323959143): The error message should only be shown after we
@@ -373,17 +404,22 @@
     }
   }
 
-  if (initial_update) {
-    add_new_task_button_->SetVisible(true);
-    base::UmaHistogramCounts100(
-        "Ash.Glanceables.TimeManagement.TasksCountInDefaultTaskList",
-        tasks->item_count());
-  } else {
-    RecordNumberOfAddedTasks(added_tasks_, task_list_initially_empty_,
-                             user_with_no_tasks_);
-    added_tasks_ = 0;
+  switch (context) {
+    case ListShownContext::kCachedList:
+      break;
+    case ListShownContext::kInitialList:
+      base::UmaHistogramCounts100(
+          "Ash.Glanceables.TimeManagement.TasksCountInDefaultTaskList",
+          tasks->item_count());
+      break;
+    case ListShownContext::kUserSelectedList:
+      RecordNumberOfAddedTasks(added_tasks_, task_list_initially_empty_,
+                               user_with_no_tasks_);
+      added_tasks_ = 0;
+      break;
   }
 
+  add_new_task_button_->SetVisible(true);
   task_items_container_view_->RemoveAllChildViews();
 
   size_t num_tasks_shown = 0;
@@ -417,24 +453,29 @@
 
   if (old_preferred_size != GetPreferredSize()) {
     PreferredSizeChanged();
-    if (!initial_update) {
+    if (context == ListShownContext::kUserSelectedList) {
       GetWidget()->LayoutRootViewIfNecessary();
       ScrollViewToVisible();
     }
   }
 
-  auto* controller = Shell::Get()->glanceables_controller();
-
-  if (initial_update) {
-    RecordTasksInitialLoadTime(
-        /* first_occurrence=*/controller->bubble_shown_count() == 1,
-        base::TimeTicks::Now() - controller->last_bubble_show_time());
-  } else {
-    RecordActiveTaskListChanged();
-    RecordTasksChangeLoadTime(base::TimeTicks::Now() - tasks_requested_time_);
+  switch (context) {
+    case ListShownContext::kCachedList:
+      break;
+    case ListShownContext::kInitialList: {
+      auto* controller = Shell::Get()->glanceables_controller();
+      RecordTasksInitialLoadTime(
+          /*first_occurrence=*/controller->bubble_shown_count() == 1,
+          base::TimeTicks::Now() - controller->last_bubble_show_time());
+      first_task_list_shown_ = true;
+      break;
+    }
+    case ListShownContext::kUserSelectedList:
+      RecordActiveTaskListChanged();
+      RecordTasksChangeLoadTime(base::TimeTicks::Now() - tasks_requested_time_);
+      first_task_list_shown_ = true;
+      break;
   }
-
-  first_task_list_shown_ = true;
 }
 
 void GlanceablesTasksView::AnnounceListStateOnComboBoxAccessibility() {
@@ -447,8 +488,7 @@
 void GlanceablesTasksView::MarkTaskAsCompleted(const std::string& task_list_id,
                                                const std::string& task_id,
                                                bool completed) {
-  Shell::Get()->glanceables_controller()->GetTasksClient()->MarkAsCompleted(
-      task_list_id, task_id, completed);
+  GetTasksClient()->MarkAsCompleted(task_list_id, task_id, completed);
 }
 
 void GlanceablesTasksView::ActionButtonPressed(TasksLaunchSource source) {
@@ -489,9 +529,9 @@
     RecordTaskAdditionResult(TaskModificationResult::kCommitted);
   }
 
-  progress_bar_->UpdateProgressBarVisibility(/*visible=*/true);
+  SetIsLoading(true);
 
-  auto* const client = Shell::Get()->glanceables_controller()->GetTasksClient();
+  auto* const client = GetTasksClient();
   auto on_task_saved = base::BindOnce(
       &GlanceablesTasksView::OnTaskSaved, weak_ptr_factory_.GetWeakPtr(),
       std::move(view), task_id, std::move(callback));
@@ -519,7 +559,7 @@
   } else if (task->title.empty()) {
     task_items_container_view_->RemoveChildViewT(view.get());
   }
-  progress_bar_->UpdateProgressBarVisibility(/*visible=*/false);
+  SetIsLoading(false);
   std::move(callback).Run(task);
   list_footer_view_->SetVisible(task_items_container_view_->children().size() >=
                                 kMaximumTasks);
@@ -540,10 +580,7 @@
   switch (error_type) {
     case GlanceablesTasksErrorType::kCantUpdateList: {
       auto last_modified_time =
-          Shell::Get()
-              ->glanceables_controller()
-              ->GetTasksClient()
-              ->GetTasksLastUpdateTime(GetActiveTaskList()->id);
+          GetTasksClient()->GetTasksLastUpdateTime(GetActiveTaskList()->id);
       CHECK(last_modified_time.has_value());
       return GetLastUpdateTimeMessage(last_modified_time.value());
     }
@@ -573,6 +610,37 @@
   PreferredSizeChanged();
 }
 
+void GlanceablesTasksView::CreateComboBoxView() {
+  if (task_list_combo_box_view_) {
+    combobox_view_observation_.Reset();
+    tasks_header_view_->RemoveChildViewT(
+        std::exchange(task_list_combo_box_view_, nullptr));
+  }
+
+  task_list_combo_box_view_ = tasks_header_view_->AddChildView(
+      std::make_unique<Combobox>(tasks_combobox_model_.get()));
+  task_list_combo_box_view_->SetID(
+      base::to_underlying(GlanceablesViewId::kTasksBubbleComboBox));
+  task_list_combo_box_view_->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
+                               views::MaximumFlexSizeRule::kPreferred));
+  combobox_view_observation_.Observe(task_list_combo_box_view_);
+
+  task_list_combo_box_view_->SetTooltipText(l10n_util::GetStringUTF16(
+      IDS_GLANCEABLES_TASKS_DROPDOWN_ACCESSIBLE_NAME));
+  task_list_combo_box_view_->SetAccessibleDescription(u"");
+  task_list_combo_box_view_->SetSelectionChangedCallback(base::BindRepeating(
+      &GlanceablesTasksView::SelectedTasksListChanged, base::Unretained(this)));
+}
+
+void GlanceablesTasksView::SetIsLoading(bool is_loading) {
+  progress_bar_->UpdateProgressBarVisibility(is_loading);
+
+  // Disable all events in the subtree if the data fetch is ongoing.
+  SetCanProcessEventsWithinSubtree(!is_loading);
+}
+
 BEGIN_METADATA(GlanceablesTasksView)
 END_METADATA
 
diff --git a/ash/glanceables/tasks/glanceables_tasks_view.h b/ash/glanceables/tasks/glanceables_tasks_view.h
index 3a4ae6d..65a56d6 100644
--- a/ash/glanceables/tasks/glanceables_tasks_view.h
+++ b/ash/glanceables/tasks/glanceables_tasks_view.h
@@ -70,7 +70,23 @@
   // views::ViewObserver:
   void OnViewFocused(views::View* view) override;
 
+  // Updates the cached task lists to `task_lists` and the tasks that are
+  // supposed to show.
+  void UpdateTaskLists(const ui::ListModel<api::TaskList>* task_lists);
+
  private:
+  // The context of why the current task list is shown.
+  enum class ListShownContext {
+    // The list is a cached one that will be updated later after the lists data
+    // are fetched.
+    kCachedList,
+    // The list that is loaded by default when users open glanceables.
+    kInitialList,
+    // The list is manually selected by the users through
+    // `task_list_combo_box_view_`.
+    kUserSelectedList
+  };
+
   // Handles press behavior for `add_new_task_button_`.
   void AddNewTaskButtonPressed();
 
@@ -81,12 +97,12 @@
 
   // Handles switching between tasks lists.
   void SelectedTasksListChanged();
-  void ScheduleUpdateTasksList(bool initial_update);
-  void UpdateTasksList(const std::string& task_list_id,
-                       const std::string& task_list_title,
-                       bool initial_update,
-                       bool fetch_success,
-                       const ui::ListModel<api::Task>* tasks);
+  void ScheduleUpdateTasks(ListShownContext context);
+  void UpdateTasksInTaskList(const std::string& task_list_id,
+                             const std::string& task_list_title,
+                             ListShownContext context,
+                             bool fetch_success,
+                             const ui::ListModel<api::Task>* tasks);
 
   // Announces text describing the task list state through a screen
   // reader, using `task_list_combo_box_view_` view accessibility helper.
@@ -133,6 +149,14 @@
   // Removes `task_view` from the tasks container.
   void RemoveTaskView(base::WeakPtr<GlanceablesTaskViewV2> task_view);
 
+  // Creates and initializes `task_list_combo_box_view_`.
+  void CreateComboBoxView();
+
+  // This function should be called with `is_loading` = true if `this` is
+  // waiting for fetched data to be returned. After the data arrives, resets the
+  // states by calling with `is_loading` = false.
+  void SetIsLoading(bool is_loading);
+
   // Model for the combobox used to change the active task list.
   std::unique_ptr<TasksComboboxModel> tasks_combobox_model_;
 
@@ -169,6 +193,9 @@
   // list was selected.
   bool user_with_no_tasks_ = false;
 
+  // Callback that recreates `task_list_combo_box_view_`.
+  base::OnceClosure recreate_combobox_callback_;
+
   base::ScopedObservation<views::View, views::ViewObserver>
       combobox_view_observation_{this};
 
diff --git a/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc b/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc
index 39c8916..6c8d3f4 100644
--- a/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc
+++ b/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc
@@ -84,6 +84,9 @@
           base::DoNothing());
     }
 
+    // Simulate closing the glanceables bubble to cache the tasks.
+    fake_glanceables_tasks_client_->OnGlanceablesBubbleClosed();
+
     // Recreate the tasks view to update the task views.
     view_ = widget_->SetContentsView(std::make_unique<GlanceablesTasksView>(
         fake_glanceables_tasks_client_->task_lists()));
diff --git a/ash/system/unified/glanceable_tray_bubble_view.cc b/ash/system/unified/glanceable_tray_bubble_view.cc
index 724fff7..15408f6 100644
--- a/ash/system/unified/glanceable_tray_bubble_view.cc
+++ b/ash/system/unified/glanceable_tray_bubble_view.cc
@@ -294,10 +294,19 @@
       Shell::Get()->glanceables_controller()->GetTasksClient();
   if (should_show_non_calendar_glanceables && tasks_client) {
     CHECK(!tasks_bubble_view_);
-    tasks_client->GetTaskLists(
-        /*force_fetch=*/false,
-        base::BindOnce(&GlanceableTrayBubbleView::AddTaskBubbleViewIfNeeded,
-                       weak_ptr_factory_.GetWeakPtr()));
+    auto* cached_list = tasks_client->GetCachedTaskLists();
+    if (!cached_list) {
+      tasks_client->GetTaskLists(
+          /*force_fetch=*/true,
+          base::BindOnce(&GlanceableTrayBubbleView::AddTaskBubbleViewIfNeeded,
+                         weak_ptr_factory_.GetWeakPtr()));
+    } else {
+      AddTaskBubbleViewIfNeeded(/*fetch_success=*/true, cached_list);
+      tasks_client->GetTaskLists(
+          /*force_fetch=*/true,
+          base::BindOnce(&GlanceableTrayBubbleView::UpdateTaskLists,
+                         weak_ptr_factory_.GetWeakPtr()));
+    }
   }
 
   const int max_height = CalculateMaxTrayBubbleHeight(shelf_->GetWindow());
@@ -402,6 +411,16 @@
   AdjustChildrenFocusOrder();
 }
 
+void GlanceableTrayBubbleView::UpdateTaskLists(
+    bool fetch_success,
+    const ui::ListModel<api::TaskList>* task_lists) {
+  if (fetch_success &&
+      features::IsGlanceablesTimeManagementTasksViewEnabled()) {
+    views::AsViewClass<GlanceablesTasksView>(tasks_bubble_view_)
+        ->UpdateTaskLists(task_lists);
+  }
+}
+
 void GlanceableTrayBubbleView::OnGlanceablesContainerPreferredSizeChanged() {
   if (!initialized_) {
     return;
diff --git a/ash/system/unified/glanceable_tray_bubble_view.h b/ash/system/unified/glanceable_tray_bubble_view.h
index 4d8bcd7..d3d29f55 100644
--- a/ash/system/unified/glanceable_tray_bubble_view.h
+++ b/ash/system/unified/glanceable_tray_bubble_view.h
@@ -68,6 +68,10 @@
       bool fetch_success,
       const ui::ListModel<api::TaskList>* task_lists);
 
+  // Updates the cached task lists to `task_lists`.
+  void UpdateTaskLists(bool fetch_success,
+                       const ui::ListModel<api::TaskList>* task_lists);
+
   void OnGlanceablesContainerPreferredSizeChanged();
   void OnGlanceablesContainerHeightChanged(int height_delta);
 
diff --git a/ash/system/unified/tasks_combobox_model.cc b/ash/system/unified/tasks_combobox_model.cc
index 5e98e51..fef7d5ee 100644
--- a/ash/system/unified/tasks_combobox_model.cc
+++ b/ash/system/unified/tasks_combobox_model.cc
@@ -46,6 +46,11 @@
   pref_service->ClearPref(kLastSelectedTaskListTimePref);
 }
 
+void TasksComboboxModel::UpdateTaskLists(
+    const ui::ListModel<api::TaskList>* task_lists) {
+  task_lists_ = task_lists;
+}
+
 size_t TasksComboboxModel::GetItemCount() const {
   return task_lists_->item_count();
 }
diff --git a/ash/system/unified/tasks_combobox_model.h b/ash/system/unified/tasks_combobox_model.h
index 1773d1c1..78cdd64 100644
--- a/ash/system/unified/tasks_combobox_model.h
+++ b/ash/system/unified/tasks_combobox_model.h
@@ -39,6 +39,9 @@
   // Clears tasks glanceables state saved in user prefs.
   static void ClearUserStatePrefs(PrefService* pref_service);
 
+  // Updates `task_lists_` with `task_list`.
+  void UpdateTaskLists(const ui::ListModel<api::TaskList>* task_lists);
+
   // ui::ComboboxModel:
   size_t GetItemCount() const override;
   std::u16string GetItemAt(size_t index) const override;
@@ -51,7 +54,7 @@
 
  private:
   // Owned by `GlanceableTasksClientImpl`.
-  const raw_ptr<const ui::ListModel<api::TaskList>> task_lists_;
+  raw_ptr<const ui::ListModel<api::TaskList>> task_lists_;
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ui/ash/api/tasks/tasks_client_impl.cc b/chrome/browser/ui/ash/api/tasks/tasks_client_impl.cc
index 8fa044b..1b8098f 100644
--- a/chrome/browser/ui/ash/api/tasks/tasks_client_impl.cc
+++ b/chrome/browser/ui/ash/api/tasks/tasks_client_impl.cc
@@ -108,6 +108,16 @@
 
 TasksClientImpl::~TasksClientImpl() = default;
 
+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) {
@@ -124,6 +134,18 @@
   }
 }
 
+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) {
diff --git a/chrome/browser/ui/ash/api/tasks/tasks_client_impl.h b/chrome/browser/ui/ash/api/tasks/tasks_client_impl.h
index 8f9427f..9cf0211 100644
--- a/chrome/browser/ui/ash/api/tasks/tasks_client_impl.h
+++ b/chrome/browser/ui/ash/api/tasks/tasks_client_impl.h
@@ -53,8 +53,11 @@
   ~TasksClientImpl() override;
 
   // TasksClient:
+  const ui::ListModel<api::TaskList>* GetCachedTaskLists() override;
   void GetTaskLists(bool force_fetch,
                     TasksClient::GetTaskListsCallback callback) override;
+  const ui::ListModel<api::Task>* GetCachedTasksInTaskList(
+      const std::string& task_list_id) override;
   void GetTasks(const std::string& task_list_id,
                 bool force_fetch,
                 TasksClient::GetTasksCallback callback) override;
diff --git a/chrome/browser/ui/ash/api/tasks/tasks_client_impl_unittest.cc b/chrome/browser/ui/ash/api/tasks/tasks_client_impl_unittest.cc
index 2ab96f13..1dfd557 100644
--- a/chrome/browser/ui/ash/api/tasks/tasks_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/api/tasks/tasks_client_impl_unittest.cc
@@ -251,6 +251,22 @@
   EXPECT_EQ(std::get<1>(future.Take()), task_lists);
 }
 
+TEST_F(TasksClientImplTest, GetCachedTaskLists) {
+  EXPECT_CALL(request_handler(), HandleRequest(_))
+      .WillOnce(Return(ByMove(TestRequestHandler::CreateSuccessfulResponse(
+          kDefaultTaskListsResponseContent))));
+
+  EXPECT_EQ(client()->GetCachedTaskLists(), nullptr);
+
+  TaskListsFuture future;
+  client()->GetTaskLists(/*force_fetch=*/false, future.GetRepeatingCallback());
+  ASSERT_TRUE(future.Wait());
+
+  const auto [status, task_lists] = future.Take();
+  EXPECT_TRUE(status);
+  EXPECT_EQ(client()->GetCachedTaskLists(), task_lists);
+}
+
 TEST_F(TasksClientImplTest, ConcurrentGetTaskListsCalls) {
   EXPECT_CALL(request_handler(), HandleRequest(_))
       .WillOnce(Return(ByMove(TestRequestHandler::CreateSuccessfulResponse(
@@ -391,7 +407,7 @@
   TaskListsFuture future;
   client()->GetTaskLists(/*force_fetch=*/false, future.GetRepeatingCallback());
 
-  // Simulate bubble closure before first request response arives.
+  // Simulate bubble closure before first request response arrives.
   client()->OnGlanceablesBubbleClosed();
 
   ASSERT_TRUE(future.Wait());
@@ -498,7 +514,7 @@
   client()->OnGlanceablesBubbleClosed();
 
   TaskListsFuture failure_future;
-  client()->GetTaskLists(/*force_fetch=*/false, failure_future.GetCallback());
+  client()->GetTaskLists(/*force_fetch=*/true, failure_future.GetCallback());
   ASSERT_TRUE(failure_future.Wait());
 
   const auto [failure_status, failed_task_lists] = failure_future.Take();
@@ -591,7 +607,7 @@
   client()->OnGlanceablesBubbleClosed();
 
   TaskListsFuture failure_future;
-  client()->GetTaskLists(/*force_fetch=*/false, failure_future.GetCallback());
+  client()->GetTaskLists(/*force_fetch=*/true, failure_future.GetCallback());
   ASSERT_TRUE(failure_future.Wait());
 
   const auto [failed_status, failed_task_lists] = failure_future.Take();
@@ -838,7 +854,7 @@
   EXPECT_EQ(task_lists->GetItemAt(1)->id, "asdfgh");
 
   TasksFuture abandoned_tasks_future;
-  client()->GetTasks("asdfgh", /*force_fetch=*/false,
+  client()->GetTasks("asdfgh", /*force_fetch=*/true,
                      abandoned_tasks_future.GetCallback());
   ASSERT_TRUE(abandoned_tasks_future.Wait());
 
@@ -850,7 +866,7 @@
   EXPECT_EQ(abandoned_tasks->GetItemAt(0)->id, "fgh");
 
   TasksFuture tasks_future;
-  client()->GetTasks("qwerty", /*force_fetch=*/false,
+  client()->GetTasks("qwerty", /*force_fetch=*/true,
                      tasks_future.GetCallback());
   ASSERT_TRUE(tasks_future.Wait());
 
@@ -864,7 +880,7 @@
   client()->OnGlanceablesBubbleClosed();
 
   TaskListsFuture refreshed_task_list_future;
-  client()->GetTaskLists(/*force_fetch=*/false,
+  client()->GetTaskLists(/*force_fetch=*/true,
                          refreshed_task_list_future.GetCallback());
   ASSERT_TRUE(refreshed_task_list_future.Wait());
 
@@ -876,7 +892,7 @@
   EXPECT_EQ(refreshed_task_lists->GetItemAt(0)->id, "qwerty");
 
   TasksFuture refreshed_abandoned_tasks_future;
-  client()->GetTasks("asdfgh", /*force_fetch=*/false,
+  client()->GetTasks("asdfgh", /*force_fetch=*/true,
                      refreshed_abandoned_tasks_future.GetCallback());
   ASSERT_TRUE(refreshed_abandoned_tasks_future.Wait());
 
@@ -887,7 +903,7 @@
   EXPECT_EQ(refreshed_abandoned_tasks->item_count(), 0u);
 
   TasksFuture refreshed_tasks_future;
-  client()->GetTasks("qwerty", /*force_fetch=*/false,
+  client()->GetTasks("qwerty", /*force_fetch=*/true,
                      refreshed_tasks_future.GetCallback());
   ASSERT_TRUE(refreshed_tasks_future.Wait());
 
@@ -1073,6 +1089,24 @@
       /*expected_bucket_count=*/2);
 }
 
+TEST_F(TasksClientImplTest, GetCachedTasks) {
+  EXPECT_CALL(request_handler(), HandleRequest(_))
+      .WillOnce(Return(ByMove(TestRequestHandler::CreateSuccessfulResponse(
+          kDefaultTasksResponseContent))));
+
+  EXPECT_EQ(client()->GetCachedTasksInTaskList("test-task-list-id"), nullptr);
+
+  TasksFuture future;
+  client()->GetTasks("test-task-list-id", /*force_fetch=*/false,
+                     future.GetRepeatingCallback());
+  ASSERT_TRUE(future.Wait());
+
+  const auto [success, root_tasks] = future.Take();
+  EXPECT_TRUE(success);
+  EXPECT_EQ(client()->GetCachedTasksInTaskList("test-task-list-id"),
+            root_tasks);
+}
+
 TEST_F(TasksClientImplTest, GetTasksOnSubsequentCalls) {
   EXPECT_CALL(request_handler(), HandleRequest(_))
       .WillOnce(Return(ByMove(TestRequestHandler::CreateSuccessfulResponse(
@@ -1349,7 +1383,7 @@
   client()->OnGlanceablesBubbleClosed();
 
   TasksFuture failed_future;
-  client()->GetTasks("test-task-list-id", /*force_fetch=*/false,
+  client()->GetTasks("test-task-list-id", /*force_fetch=*/true,
                      failed_future.GetCallback());
   ASSERT_TRUE(failed_future.Wait());
 
@@ -1370,7 +1404,7 @@
       /*expected_bucket_count=*/1);
 
   TasksFuture retry_future;
-  client()->GetTasks("test-task-list-id", /*force_fetch=*/false,
+  client()->GetTasks("test-task-list-id", /*force_fetch=*/true,
                      retry_future.GetCallback());
   ASSERT_TRUE(retry_future.Wait());
 
@@ -1444,7 +1478,7 @@
   client()->OnGlanceablesBubbleClosed();
 
   TasksFuture failure_future;
-  client()->GetTasks("task-list-1", /*force_fetch=*/false,
+  client()->GetTasks("task-list-1", /*force_fetch=*/true,
                      failure_future.GetCallback());
   ASSERT_TRUE(failure_future.Wait());
 
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_browsertest.cc b/chrome/browser/ui/ash/glanceables/glanceables_browsertest.cc
index d1ee381..b520f36 100644
--- a/chrome/browser/ui/ash/glanceables/glanceables_browsertest.cc
+++ b/chrome/browser/ui/ash/glanceables/glanceables_browsertest.cc
@@ -132,6 +132,12 @@
     return event_generator_.get();
   }
 
+  void ToggleDateTray() {
+    GetEventGenerator()->MoveMouseTo(
+        GetDateTray()->GetBoundsInScreen().CenterPoint());
+    GetEventGenerator()->ClickLeftButton();
+  }
+
   GlanceableTrayBubble* GetGlanceableTrayBubble() const {
     return date_tray_->bubble_.get();
   }
@@ -254,9 +260,7 @@
   ASSERT_TRUE(glanceables_controller()->GetClassroomClient());
 
   // Click the date tray to show the glanceable bubbles.
-  GetEventGenerator()->MoveMouseTo(
-      GetDateTray()->GetBoundsInScreen().CenterPoint());
-  GetEventGenerator()->ClickLeftButton();
+  ToggleDateTray();
 
   EXPECT_TRUE(GetGlanceableTrayBubble());
   EXPECT_TRUE(GetStudentView());
@@ -285,9 +289,7 @@
   ASSERT_TRUE(glanceables_controller()->GetClassroomClient());
 
   // Click the date tray to show the glanceable bubbles.
-  GetEventGenerator()->MoveMouseTo(
-      GetDateTray()->GetBoundsInScreen().CenterPoint());
-  GetEventGenerator()->ClickLeftButton();
+  ToggleDateTray();
 
   EXPECT_TRUE(GetGlanceableTrayBubble());
   EXPECT_TRUE(GetStudentView());
@@ -317,9 +319,7 @@
   ASSERT_TRUE(glanceables_controller()->GetClassroomClient());
 
   // Click the date tray to show the glanceable bubbles.
-  GetEventGenerator()->MoveMouseTo(
-      GetDateTray()->GetBoundsInScreen().CenterPoint());
-  GetEventGenerator()->ClickLeftButton();
+  ToggleDateTray();
 
   EXPECT_TRUE(GetGlanceableTrayBubble());
   EXPECT_TRUE(GetStudentView());
@@ -371,9 +371,7 @@
   ASSERT_TRUE(glanceables_controller()->GetTasksClient());
   EXPECT_FALSE(GetGlanceableTrayBubble());
 
-  GetEventGenerator()->MoveMouseTo(
-      GetDateTray()->GetBoundsInScreen().CenterPoint());
-  GetEventGenerator()->ClickLeftButton();
+  ToggleDateTray();
 
   EXPECT_TRUE(GetGlanceableTrayBubble());
   EXPECT_TRUE(GetTasksView());
@@ -415,9 +413,7 @@
   EXPECT_FALSE(GetGlanceableTrayBubble());
 
   // Click the date tray to show the glanceable bubbles.
-  GetEventGenerator()->MoveMouseTo(
-      GetDateTray()->GetBoundsInScreen().CenterPoint());
-  GetEventGenerator()->ClickLeftButton();
+  ToggleDateTray();
 
   EXPECT_TRUE(GetGlanceableTrayBubble());
   EXPECT_TRUE(GetTasksView());
@@ -448,9 +444,7 @@
   EXPECT_FALSE(GetGlanceableTrayBubble());
 
   // Click the date tray to show the glanceable bubbles.
-  GetEventGenerator()->MoveMouseTo(
-      GetDateTray()->GetBoundsInScreen().CenterPoint());
-  GetEventGenerator()->ClickLeftButton();
+  ToggleDateTray();
 
   EXPECT_TRUE(GetGlanceableTrayBubble());
   EXPECT_TRUE(GetTasksView());
@@ -508,9 +502,7 @@
   EXPECT_FALSE(GetGlanceableTrayBubble());
 
   // Click the date tray to show the glanceable bubbles.
-  GetEventGenerator()->MoveMouseTo(
-      GetDateTray()->GetBoundsInScreen().CenterPoint());
-  GetEventGenerator()->ClickLeftButton();
+  ToggleDateTray();
 
   ASSERT_TRUE(GetGlanceableTrayBubble());
   ASSERT_TRUE(GetTasksView());
@@ -587,9 +579,7 @@
   EXPECT_FALSE(GetGlanceableTrayBubble());
 
   // Click the date tray to show the glanceable bubbles.
-  GetEventGenerator()->MoveMouseTo(
-      GetDateTray()->GetBoundsInScreen().CenterPoint());
-  GetEventGenerator()->ClickLeftButton();
+  ToggleDateTray();
 
   EXPECT_TRUE(GetGlanceableTrayBubble());
   EXPECT_TRUE(GetTasksView());
@@ -663,9 +653,7 @@
 
 IN_PROC_BROWSER_TEST_F(GlanceablesWithAddEditBrowserTest, TasksViewLayout) {
   // Click the date tray to show the glanceable bubbles.
-  GetEventGenerator()->MoveMouseTo(
-      GetDateTray()->GetBoundsInScreen().CenterPoint());
-  GetEventGenerator()->ClickLeftButton();
+  ToggleDateTray();
 
   ASSERT_TRUE(GetGlanceableTrayBubble());
   ASSERT_TRUE(GetTasksView());
@@ -718,4 +706,138 @@
   EXPECT_FALSE(scroll_bar->GetVisible());
 }
 
+IN_PROC_BROWSER_TEST_F(GlanceablesWithAddEditBrowserTest,
+                       ShowsCachedDataBasic) {
+  auto* const client = fake_glanceables_tasks_client();
+  client->set_paused_on_fetch(true);
+
+  // Click the date tray to show the glanceable bubbles. For the first time the
+  // glanceables are shown, the tasks need to be fetched and the view should not
+  // be shown before the data returns.
+  ToggleDateTray();
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(GetGlanceableTrayBubble());
+  ASSERT_FALSE(GetTasksView());
+
+  client->RunPendingGetTaskListsCallbacks();
+  client->RunPendingGetTasksCallbacks();
+  ASSERT_TRUE(GetTasksView());
+
+  // Close the glanceables.
+  ToggleDateTray();
+  ASSERT_FALSE(GetGlanceableTrayBubble());
+
+  // The second and following times when the tasks are shown, the cached
+  // tasks should be shown while waiting the new change to be fetched.
+  ToggleDateTray();
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(GetGlanceableTrayBubble());
+  ASSERT_TRUE(GetTasksView());
+}
+
+IN_PROC_BROWSER_TEST_F(GlanceablesWithAddEditBrowserTest,
+                       CachedTaskListAreUpdatedAfterFetch) {
+  // Click the date tray to show the glanceable bubbles.
+  ToggleDateTray();
+
+  EXPECT_TRUE(GetGlanceableTrayBubble());
+  EXPECT_TRUE(GetTasksView());
+
+  // Check that task list items from the first list are shown.
+  EXPECT_EQ(GetCurrentTaskListItemTitles(),
+            std::vector<std::string>(
+                {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"}));
+
+  // Close the glanceables.
+  ToggleDateTray();
+  base::RunLoop().RunUntilIdle();
+
+  // Turn on the pause_on_fetch to verify the cached tasks and the updated
+  // tasks.
+  auto* const client = fake_glanceables_tasks_client();
+  client->set_paused_on_fetch(true);
+
+  // Add a task in Task List 1 directly via the client as an updated task.
+  client->AddTask(
+      /*task_list_id=*/"TaskListID1",
+      std::make_unique<api::Task>(
+          /*id=*/"TaskListItem5", /*title=*/"Task List 1 Item 3 Title",
+          /*due=*/base::Time::Now(), /*completed=*/false,
+          /*has_subtasks=*/false, /*has_email_link=*/false,
+          /*has_notes=*/false, /*updated=*/base::Time::Now()));
+
+  // Open the glanceables again.
+  ToggleDateTray();
+  base::RunLoop().RunUntilIdle();
+
+  // Check that only the cached task list items from the first list are shown.
+  EXPECT_EQ(GetCurrentTaskListItemTitles(),
+            std::vector<std::string>(
+                {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"}));
+
+  client->RunPendingGetTaskListsCallbacks();
+  EXPECT_FALSE(GetTasksView()->GetCanProcessEventsWithinSubtree());
+  client->RunPendingGetTasksCallbacks();
+
+  // After running the get callbacks, the newly added task is shown.
+  EXPECT_EQ(GetCurrentTaskListItemTitles(),
+            std::vector<std::string>({"Task List 1 Item 1 Title",
+                                      "Task List 1 Item 2 Title",
+                                      "Task List 1 Item 3 Title"}));
+  EXPECT_TRUE(GetTasksView()->GetCanProcessEventsWithinSubtree());
+}
+
+IN_PROC_BROWSER_TEST_F(GlanceablesWithAddEditBrowserTest,
+                       UpdateShownListIfCachedTaskListDeleted) {
+  // Click the date tray to show the glanceable bubbles.
+  ToggleDateTray();
+
+  EXPECT_TRUE(GetGlanceableTrayBubble());
+  EXPECT_TRUE(GetTasksView());
+
+  // Check that task list items from the first list are shown.
+  const auto* combobox = GetTasksComboBoxView();
+  EXPECT_EQ(combobox->GetTextForRow(combobox->GetSelectedIndex().value()),
+            u"Task List 1 Title");
+  EXPECT_EQ(GetCurrentTaskListItemTitles(),
+            std::vector<std::string>(
+                {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"}));
+
+  // Close the glanceables.
+  ToggleDateTray();
+  base::RunLoop().RunUntilIdle();
+
+  // Turn on the pause_on_fetch to verify the cached tasks and the updated
+  // tasks.
+  auto* const client = fake_glanceables_tasks_client();
+  client->set_paused_on_fetch(true);
+
+  // Delete the task list that was shown.
+  client->DeleteTaskList("TaskListID1");
+
+  // Open the glanceables again.
+  ToggleDateTray();
+  base::RunLoop().RunUntilIdle();
+
+  // Check that deleted list is still showing as it is cached.
+  combobox = GetTasksComboBoxView();
+  EXPECT_EQ(combobox->GetTextForRow(combobox->GetSelectedIndex().value()),
+            u"Task List 1 Title");
+  EXPECT_EQ(GetCurrentTaskListItemTitles(),
+            std::vector<std::string>(
+                {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"}));
+
+  client->RunPendingGetTaskListsCallbacks();
+  client->RunPendingGetTasksCallbacks();
+
+  // After running the get callbacks, the task list shown is updated.
+  combobox = GetTasksComboBoxView();
+  EXPECT_EQ(combobox->GetTextForRow(combobox->GetSelectedIndex().value()),
+            u"Task List 2 Title");
+  EXPECT_EQ(GetCurrentTaskListItemTitles(),
+            std::vector<std::string>({"Task List 2 Item 1 Title",
+                                      "Task List 2 Item 2 Title",
+                                      "Task List 2 Item 3 Title"}));
+}
+
 }  // namespace ash