chromium/ash/system/mahi/mahi_ui_controller.h

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

#ifndef ASH_SYSTEM_MAHI_MAHI_UI_CONTROLLER_H_
#define ASH_SYSTEM_MAHI_MAHI_UI_CONTROLLER_H_

#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "ash/ash_export.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/system/mahi/mahi_ui_update.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "base/scoped_observation_traits.h"
#include "chromeos/components/mahi/public/cpp/mahi_manager.h"
#include "ui/views/widget/unique_widget_ptr.h"

namespace views {
class View;
}  // namespace views

class AccountId;

namespace ash {

// Communicates with `chromeos::MahiManager` and notifies delegates of updates.
class ASH_EXPORT MahiUiController : public SessionObserver {
 public:
  // Establishes the connection between `MahiUiController` and dependent views.
  class Delegate : public base::CheckedObserver {
   public:
    explicit Delegate(MahiUiController* ui_controller);
    Delegate(const Delegate&) = delete;
    Delegate& operator=(const Delegate&) = delete;
    ~Delegate() override;

    // Returns the view that associates with the delegate.
    virtual views::View* GetView() = 0;

    // Returns the visibility of the delegate's associated view for `state`.
    virtual bool GetViewVisibility(VisibilityState state) const = 0;

    // Notifies of a Mahi UI update.
    virtual void OnUpdated(const MahiUiUpdate& update) {}

   private:
    base::ScopedObservation<MahiUiController, Delegate> observation_{this};
  };

  // Lists question sources.
  // Note: this should be kept in sync with `MahiQuestionSource` enum in
  // tools/metrics/histograms/metadata/ash/enums.xml
  enum class QuestionSource {
    // From the Mahi menu view.
    kMenuView,

    // From the Mahi panel view.
    kPanel,

    // From the retry button.
    kRetry,

    kMaxValue = kRetry,
  };

  MahiUiController();
  MahiUiController(const MahiUiController&) = delete;
  MahiUiController& operator=(const MahiUiController&) = delete;
  ~MahiUiController() override;

  void AddDelegate(Delegate* delegate);
  void RemoveDelegate(Delegate* delegate);

  // Opens/closes the mahi panel on the display associated with `display_id`.
  // The panel is positioned on top of the provided `mahi_menu_bounds`.
  void OpenMahiPanel(int64_t display_id, gfx::Rect mahi_menu_bounds);
  void CloseMahiPanel();

  bool IsMahiPanelOpen();

  // Navigates to the Q&A view and notifies delegates.
  void NavigateToQuestionAnswerView();

  // Navigates to the summary & outlines section and notifies delegates.
  void NavigateToSummaryOutlinesSection();

  // Notifies delegates that there is a content refresh availability change.
  void NotifyRefreshAvailabilityChanged(bool available);

  // Updates the content icon and title, calls `UpdateSummaryAndOutlines` and
  // navigates to the summary view.
  void RefreshContents();

  // Retries the operation associated with `origin_state`.
  // If `origin_state` is `VisibilityState::kQuestionAndAnswer`, re-asks the
  // question.
  // If `origin_state` is `VisibilityState::kSummaryAndOutlines`, regenerates
  // the summary & outlines.
  // NOTE: `origin_state` should not be `VisibilityState::kError`.
  void Retry(VisibilityState origin_state);

  // Sends `question` to the backend. `current_panel_content` determines if the
  // `question` is regarding the current content displayed on the panel.
  // `source` indicates where `question` is posted.
  // If `update_summary_after_answer_question` is true, a request to update the
  // summary view will be made when the answer is loaded.
  void SendQuestion(const std::u16string& question,
                    bool current_panel_content,
                    QuestionSource source,
                    bool update_summary_after_answer_question = false);

  // Sends requests to the backend to update summary and outlines.
  // `delegates_` will be notified of the updated summary and outlines when
  // requests are fulfilled.
  void UpdateSummaryAndOutlines();

  // Records histogram that tracks the amount of times the panel was opened
  // during an active session.
  void RecordTimesPanelOpenedMetric();

  // SessionObserver:
  void OnSessionStateChanged(session_manager::SessionState state) override;
  void OnActiveUserSessionChanged(const AccountId& account_id) override;

  views::Widget* mahi_panel_widget() { return mahi_panel_widget_.get(); }

 private:
  void HandleError(const MahiUiError& error);

  // Notifies `delegates_` of `update`.
  void NotifyUiUpdate(const MahiUiUpdate& update);

  // Sets the visibility state and notifies `delegates_` of `update`.
  void SetVisibilityStateAndNotifyUiUpdate(VisibilityState state,
                                           const MahiUiUpdate& update);

  // Callbacks of `chromeos::MahiManager` APIs ---------------------------------

  void OnAnswerLoaded(std::optional<std::u16string> answer,
                      chromeos::MahiResponseStatus status);

  void OnOutlinesLoaded(std::vector<chromeos::MahiOutline> outlines,
                        chromeos::MahiResponseStatus status);

  void OnSummaryLoaded(std::u16string summary_text,
                       chromeos::MahiResponseStatus status);

  // Invalidates pending summary/outline/QA requests on new request to avoid
  // racing.
  void InvalidatePendingRequests();

  // The current state. Use `VisibilityState::kSummaryAndOutlines` by default.
  VisibilityState visibility_state_ = VisibilityState::kSummaryAndOutlines;

  base::ObserverList<Delegate> delegates_;

  views::UniqueWidgetPtr mahi_panel_widget_;

  // Used to record metrics. The count will be increased by one every time the
  // panel is opened, and reset to zero when the metric is recorded, which
  // happens when the session is no longer active or on shutdown.
  int times_panel_opened_per_session_ = 0;

  // Indicates the params of the most recent question.
  // Set when the controller receives a request to send a question.
  // Reset when the content is refreshed.
  std::optional<MahiQuestionParams> most_recent_question_params_;

  // Indicates that we need to update summary after answer is fully loaded.
  bool update_summary_after_answer_question_ = false;

  base::WeakPtrFactory<MahiUiController> weak_ptr_factory_{this};
};

}  // namespace ash

namespace base {

template <>
struct ScopedObservationTraits<ash::MahiUiController,
                               ash::MahiUiController::Delegate> {
  static void AddObserver(ash::MahiUiController* controller,
                          ash::MahiUiController::Delegate* delegate) {
    controller->AddDelegate(delegate);
  }
  static void RemoveObserver(ash::MahiUiController* controller,
                             ash::MahiUiController::Delegate* delegate) {
    controller->RemoveDelegate(delegate);
  }
};

}  // namespace base

#endif  // ASH_SYSTEM_MAHI_MAHI_UI_CONTROLLER_H_