chromium/ash/picker/picker_controller.h

// 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.

#ifndef ASH_PICKER_PICKER_CONTROLLER_H_
#define ASH_PICKER_PICKER_CONTROLLER_H_

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

#include "ash/ash_export.h"
#include "ash/picker/metrics/picker_feature_usage_metrics.h"
#include "ash/picker/metrics/picker_session_metrics.h"
#include "ash/picker/model/picker_emoji_history_model.h"
#include "ash/picker/model/picker_emoji_suggester.h"
#include "ash/picker/model/picker_model.h"
#include "ash/picker/picker_asset_fetcher_impl_delegate.h"
#include "ash/picker/picker_caps_lock_bubble_controller.h"
#include "ash/picker/picker_insert_media_request.h"
#include "ash/picker/views/picker_feature_tour.h"
#include "ash/picker/views/picker_view_delegate.h"
#include "ash/public/cpp/picker/picker_search_result.h"
#include "ash/public/cpp/picker/picker_web_paste_target.h"
#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/base/emoji/emoji_panel_helper.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/input_device_event_observer.h"
#include "ui/views/view_observer.h"
#include "ui/views/widget/unique_widget_ptr.h"

class PrefService;

namespace input_method {
class ImeKeyboard;
}

namespace ui {
class TextInputClient;
}

namespace ash {

class PickerAssetFetcher;
class PickerClient;
class PickerModel;
class PickerPasteRequest;
class PickerSearchController;
class PickerSuggestionsController;

// Controls a Picker widget.
class ASH_EXPORT PickerController : public PickerViewDelegate,
                                    public views::ViewObserver,
                                    public PickerAssetFetcherImplDelegate {
 public:
  PickerController();
  PickerController(const PickerController&) = delete;
  PickerController& operator=(const PickerController&) = delete;
  ~PickerController() override;

  // Maximum time to wait for focus to be regained after completing the feature
  // tour. If this timeout is reached, we stop waiting for focus and show the
  // Picker widget regardless of the focus state.
  static constexpr base::TimeDelta kShowWidgetPostFeatureTourTimeout =
      base::Seconds(2);

  // Time from when the insert is issued and when we give up inserting.
  static constexpr base::TimeDelta kInsertMediaTimeout = base::Seconds(2);

  // Time from when a search starts to when the first set of results are
  // published.
  static constexpr base::TimeDelta kBurnInPeriod = base::Milliseconds(200);

  // Disables the feature key checking.
  static void DisableFeatureKeyCheck();

  // Whether the feature is currently enabled or not based on the secret key and
  // other factors.
  bool IsFeatureEnabled();

  // Sets the `client` used by this class and the widget to communicate with the
  // browser. `client` may be set to null, which will close the Widget if it's
  // open, and may call "stop search" methods on the PREVIOUS client.
  // If `client` is not null, then it must remain valid for the lifetime of this
  // class, or until AFTER `SetClient` is called with a different client.
  // Caution: If `client` outlives this class, the client should avoid calling
  // this method on a destructed class instance to avoid a use after free.
  void SetClient(PickerClient* client);

  // This should be run when the Profile from the client is ready.
  void OnClientProfileSet();

  // Toggles the visibility of the Picker widget.
  // This must only be called after `SetClient` is called with a valid client.
  // `trigger_event_timestamp` is the timestamp of the event that triggered the
  // Widget to be toggled. For example, if the feature was triggered by a mouse
  // click, then it should be the timestamp of the click. By default, the
  // timestamp is the time this function is called.
  void ToggleWidget(
      base::TimeTicks trigger_event_timestamp = base::TimeTicks::Now());

  // Returns the Picker widget for tests.
  views::Widget* widget_for_testing() { return widget_.get(); }
  PickerFeatureTour& feature_tour_for_testing() { return feature_tour_; }
  PickerCapsLockBubbleController& caps_lock_bubble_controller_for_testing() {
    return caps_lock_bubble_controller_;
  }

  // PickerViewDelegate:
  std::vector<PickerCategory> GetAvailableCategories() override;
  void GetZeroStateSuggestedResults(SuggestedResultsCallback callback) override;
  void GetResultsForCategory(PickerCategory category,
                             SearchResultsCallback callback) override;
  void StartSearch(std::u16string_view query,
                   std::optional<PickerCategory> category,
                   SearchResultsCallback callback) override;
  void StopSearch() override;
  void StartEmojiSearch(std::u16string_view,
                        EmojiSearchResultsCallback callback) override;
  void CloseWidgetThenInsertResultOnNextFocus(
      const PickerSearchResult& result) override;
  void OpenResult(const PickerSearchResult& result) override;
  void ShowEmojiPicker(ui::EmojiPickerCategory category,
                       std::u16string_view query) override;
  void ShowEditor(std::optional<std::string> preset_query_id,
                  std::optional<std::string> freeform_text) override;
  PickerAssetFetcher* GetAssetFetcher() override;
  PickerSessionMetrics& GetSessionMetrics() override;
  PickerActionType GetActionForResult(
      const PickerSearchResult& result) override;
  std::vector<PickerEmojiResult> GetSuggestedEmoji() override;
  bool IsGifsEnabled() override;
  PickerModeType GetMode() override;
  PickerCapsLockPosition GetCapsLockPosition() override;

  // views:ViewObserver:
  void OnViewIsDeleting(views::View* view) override;

  // PickerAssetFetcherImplDelegate:
  void FetchFileThumbnail(const base::FilePath& path,
                          const gfx::Size& size,
                          FetchFileThumbnailCallback callback) override;

  // Disables the feature tour. Only works in tests.
  static void DisableFeatureTourForTesting();

 private:
  // Trigger source for showing the Picker widget. This is used to determine
  // how the widget should be shown on the screen.
  enum class WidgetTriggerSource {
    // The user triggered Picker as part of their usual user flow, e.g. toggled
    // Picker with a key press.
    kDefault,
    // The user triggered Picker by completing the feature tour.
    kFeatureTour,
  };

  // Active Picker session tied to the lifetime of the PickerWidget.
  struct Session {
    PickerModel model;
    PickerEmojiHistoryModel emoji_history_model;
    PickerEmojiSuggester emoji_suggester;
    PickerSessionMetrics session_metrics;
    // Periodically records usage metrics based on the Standard Feature Usage
    // Logging (SFUL) framework.
    PickerFeatureUsageMetrics feature_usage_metrics;

    Session(PrefService* prefs,
            ui::TextInputClient* focused_client,
            input_method::ImeKeyboard* ime_keyboard,
            PickerModel::EditorStatus editor_status,
            PickerEmojiSuggester::GetNameCallback get_name);
    ~Session();
  };

  void ShowWidget(base::TimeTicks trigger_event_timestamp,
                  WidgetTriggerSource trigger_source);
  void CloseWidget();
  void ShowWidgetPostFeatureTour();
  void InsertResultOnNextFocus(const PickerSearchResult& result);
  void OnInsertCompleted(const PickerRichMedia& media,
                         PickerInsertMediaRequest::Result result);
  PrefService* GetPrefs();

  std::optional<PickerWebPasteTarget> GetWebPasteTarget();

  PickerFeatureTour feature_tour_;
  PickerCapsLockBubbleController caps_lock_bubble_controller_;
  std::unique_ptr<Session> session_;
  views::UniqueWidgetPtr widget_;
  std::unique_ptr<PickerAssetFetcher> asset_fetcher_;
  std::unique_ptr<PickerInsertMediaRequest> insert_media_request_;
  std::unique_ptr<PickerPasteRequest> paste_request_;
  std::unique_ptr<PickerSuggestionsController> suggestions_controller_;
  std::unique_ptr<PickerSearchController> search_controller_;

  raw_ptr<PickerClient> client_ = nullptr;

  base::OnceCallback<void(std::optional<std::string> preset_query_id,
                          std::optional<std::string> freeform_text)>
      show_editor_callback_;

  // Timer used to delay closing the Widget for accessibility.
  base::OneShotTimer close_widget_delay_timer_;

  base::ScopedObservation<views::View, views::ViewObserver> view_observation_{
      this};

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

}  // namespace ash

#endif  // ASH_PICKER_PICKER_CONTROLLER_H_