1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171

ash / system / toast / anchored_nudge_manager_impl.h [blame]

// 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_SYSTEM_TOAST_ANCHORED_NUDGE_MANAGER_IMPL_H_
#define ASH_SYSTEM_TOAST_ANCHORED_NUDGE_MANAGER_IMPL_H_

#include <map>
#include <string>

#include "ash/ash_export.h"
#include "ash/capture_mode/capture_mode_education_controller.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/public/cpp/system/anchored_nudge_data.h"
#include "ash/public/cpp/system/anchored_nudge_manager.h"
#include "ash/system/toast/anchored_nudge.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"

namespace ui {
class ImplicitAnimationObserver;
}  // namespace ui

namespace views {
class LabelButton;
class View;
}  // namespace views

namespace ash {

struct AnchoredNudgeData;
class ScopedNudgePause;

// Class managing anchored nudge requests.
class ASH_EXPORT AnchoredNudgeManagerImpl : public AnchoredNudgeManager,
                                            public SessionObserver {
 public:
  AnchoredNudgeManagerImpl();
  AnchoredNudgeManagerImpl(const AnchoredNudgeManagerImpl&) = delete;
  AnchoredNudgeManagerImpl& operator=(const AnchoredNudgeManagerImpl&) = delete;
  ~AnchoredNudgeManagerImpl() override;

  // AnchoredNudgeManager:
  void Show(AnchoredNudgeData& nudge_data) override;
  void Cancel(const std::string& id) override;
  void MaybeRecordNudgeAction(NudgeCatalogName catalog_name) override;
  std::unique_ptr<ScopedNudgePause> CreateScopedPause() override;
  // TODO(b/296948349): Replace this with a new `GetNudge(id)` function as this
  // does not accurately reflect is a nudge is shown or not.
  bool IsNudgeShown(const std::string& id) override;

  // Removes all cached objects (e.g. observers, timers) related to a nudge when
  // its widget is destroying.
  void HandleNudgeWidgetDestroying(const std::string& id);

  // SessionObserver:
  void OnSessionStateChanged(session_manager::SessionState state) override;

  const std::u16string& GetNudgeBodyTextForTest(const std::string& id);
  views::View* GetNudgeAnchorViewForTest(const std::string& id);
  views::LabelButton* GetNudgePrimaryButtonForTest(const std::string& id);
  views::LabelButton* GetNudgeSecondaryButtonForTest(const std::string& id);
  AnchoredNudge* GetShownNudgeForTest(const std::string& id);
  NudgeCatalogName GetNudgeCatalogNameForTest(const std::string& id);

  // TODO(b/297619385): Move constants to a new constants file.
  // Nudges with a body text that has at least this number of characters will
  // update its default duration to medium length.
  static constexpr int kLongBodyTextLength = 60;

  // Default duration that is used for nudges that expire.
  static constexpr base::TimeDelta kNudgeDefaultDuration = base::Seconds(6);

  // Duration used for nudges with a button or a body text that has
  // `kLongBodyTextLength` or more characters.
  static constexpr base::TimeDelta kNudgeMediumDuration = base::Seconds(10);

  // Duration used for nudges that are meant to persist until the user interacts
  // with them.
  static constexpr base::TimeDelta kNudgeLongDuration = base::Minutes(30);

  // If `shown_nudges_` contains `nudge_id`, returns the associated nudge.
  // Otherwise, returns nullptr.
  AnchoredNudge* GetNudgeIfShown(const std::string& nudge_id) const;

  // Resets the registry map that records the time a nudge was last shown.
  void ResetNudgeRegistryForTesting();

 private:
  friend class AnchoredNudgeManagerImplTest;
  class AnchorViewObserver;
  class AnchorViewWidgetObserver;
  class NudgeWidgetObserver;
  class PausableTimer;

  // Returns the registry which keeps track of when a nudge was last shown.
  static std::vector<std::pair<NudgeCatalogName, base::TimeTicks>>&
  GetNudgeRegistry();

  // Records the nudge `ShownCount` metric, and stores the time the nudge was
  // shown in the nudge registry.
  void RecordNudgeShown(NudgeCatalogName catalog_name);

  // Records button pressed metrics.
  void RecordButtonPressed(NudgeCatalogName catalog_name,
                           bool is_primary_button);

  // Closes all `shown_nudges_` immediately. Used for shutdown, when a scoped
  // nudge pause is activated, or when the session state changes.
  void CloseAllNudges();

  // Pauses or resumes the dismiss timer corresponding to `nudge_id`.
  // Called when:
  // 1. A nudge's mouse hover state changes. OR
  // 2. A nudge's child focus state changes.
  void PauseOrResumeDismissTimer(const std::string& nudge_id, bool pause);

  // Chains the provided `callback` to a `Cancel()` call to dismiss a nudge with
  // `id`, and returns this chained callback. If the provided `callback` is
  // empty, only a `Cancel()` callback will be returned.
  base::RepeatingClosure ChainCancelCallback(base::RepeatingClosure callback,
                                             NudgeCatalogName catalog_name,
                                             const std::string& id,
                                             bool is_primary_button);

  // AnchoredNudgeManager:
  void Pause() override;
  void Resume() override;

  // Maps an `AnchoredNudge` `id` to pointer to the nudge with that id.
  // Used to cache and keep track of nudges that are currently displayed, so
  // they can be dismissed or their contents updated.
  std::map<std::string, raw_ptr<AnchoredNudge>> shown_nudges_;

  // Maps an `AnchoredNudge` `id` to an observation of that nudge's
  // `anchor_view`, which is used to close the nudge whenever its anchor view is
  // deleting or hiding.
  std::map<std::string, std::unique_ptr<AnchorViewObserver>>
      anchor_view_observers_;

  // Maps an `AnchoredNudge` `id` to an observation of that nudge's
  // `anchor_view` widget, which is used to close the nudge whenever its anchor
  // view widget is deleting or hiding.
  std::map<std::string, std::unique_ptr<AnchorViewWidgetObserver>>
      anchor_view_widget_observers_;

  // Maps an `AnchoredNudge` `id` to an observation of that nudge's widget,
  // which is used to clean up the cached objects related to that nudge when its
  // widget is destroying.
  std::map<std::string, std::unique_ptr<NudgeWidgetObserver>>
      nudge_widget_observers_;

  // Maps an `AnchoredNudge` `id` to an observation of the nudge's hide
  // animation. Used to destroy the nudge widget on animation completed.
  std::map<std::string, std::unique_ptr<ui::ImplicitAnimationObserver>>
      hide_animation_observers_;

  // Maps an `AnchoredNudge` `id` to a timer that's used to dismiss the nudge
  // after its duration has passed. Hovering over the nudge pauses the timer.
  std::map<std::string, PausableTimer> dismiss_timers_;

  // Keeps track of the number of `ScopedNudgePause`.
  int pause_counter_ = 0;

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

}  // namespace ash

#endif  // ASH_SYSTEM_TOAST_ANCHORED_NUDGE_MANAGER_IMPL_H_