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 172 173 174 175 176 177 178 179 180 181 182
|
// 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 COMPONENTS_USER_EDUCATION_COMMON_FEATURE_PROMO_FEATURE_PROMO_LIFECYCLE_H_
#define COMPONENTS_USER_EDUCATION_COMMON_FEATURE_PROMO_FEATURE_PROMO_LIFECYCLE_H_
#include <memory>
#include <optional>
#include <string_view>
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/user_education/common/feature_promo/feature_promo_result.h"
#include "components/user_education/common/feature_promo/feature_promo_specification.h"
#include "components/user_education/common/help_bubble/help_bubble.h"
#include "components/user_education/common/user_education_storage_service.h"
namespace base {
struct Feature;
}
namespace user_education {
// Implements business logic around the lifecycle of an IPH feature promo,
// depending on promo type and subtype. Tracks information about the IPH in
// question through `UserEducationStorageService`.
class FeaturePromoLifecycle {
public:
using PromoSubtype = FeaturePromoSpecification::PromoSubtype;
using PromoType = FeaturePromoSpecification::PromoType;
FeaturePromoLifecycle(UserEducationStorageService* storage_service,
std::string_view promo_key,
const base::Feature* iph_feature,
PromoType promo_type,
PromoSubtype promo_subtype,
int num_rotating_entries);
~FeaturePromoLifecycle();
FeaturePromoLifecycle(const FeaturePromoLifecycle&) = delete;
void operator=(const FeaturePromoLifecycle&) = delete;
// Sets reshow policy, if there is one.
FeaturePromoLifecycle& SetReshowPolicy(base::TimeDelta reshow_time,
std::optional<int> max_show_count);
// Returns whether the policy and previous usage of this IPH would allow it to
// be shown again; for example, a snoozeable IPH cannot show if it is
// currently in the snooze period.
//
// For certain types of high-priority promos, `reshow_time` and
// `max_reshow_count` may be used to allow re-showing previously-dismissed
// promos.
FeaturePromoResult CanShow() const;
// Returns whether the policy and previous usage of this IPH would allow it to
// be snoozed if it were shown; meaningless if `CanShow()` is false.
bool CanSnooze() const;
// For rotating promos only; retrieves the current promo index. The index will
// be incremented when the promo data is written. The value returned will be
// clamped to the number of rotating promo entries, exclusive, such that if
// the current value exceeds the number of entries it will wrap around to 0.
int GetPromoIndex() const;
// Allows the current promo index to be set (rotating promos only). This is
// useful if a previously-available promo has been removed, leaving a gap in
// the rotation at the current index.
//
// For example, say the promos are {P1, null, P2, P3}. Then if GetPromoIndex()
// returns 1, the calling code may see the null value and increment the index
// to 2. Setting it back on the lifecycle ensures that the correct next index
// is stored on completion, so that P3 is shown next time rather than showing
// P2 again.
void SetPromoIndex(int new_index);
// Notifies that the promo was shown. `tracker` will be used to release the
// feature when the promo ends.
void OnPromoShown(std::unique_ptr<HelpBubble> help_bubble,
feature_engagement::Tracker* tracker);
// Notifies that the promo is being shown for a demo. No data will be stored.
void OnPromoShownForDemo(std::unique_ptr<HelpBubble> help_bubble);
// Notifies that the promo bubble closed. If none of the other dismissed,
// snoozed, continued, etc. callbacks have been called, assumes that the
// bubble was closed programmatically without user input.
//
// Returns whether the promo was aborted as a result.
bool OnPromoBubbleClosed(HelpBubble::CloseReason close_reason);
// Notifies that the promo was ended for the specified `close_reason`.
// May result in pref data and/or histogram logging. If `continue_promo` is
// true, the bubble should be closed but the promo is not ended -
// `OnContinuedPromoEnded()` should be called when the continuation finishes.
void OnPromoEnded(FeaturePromoClosedReason close_reason,
bool continue_promo = false);
// For custom action, Tutorial, etc. indicates that the
void OnContinuedPromoEnded(bool completed_successfully);
HelpBubble* help_bubble() { return help_bubble_.get(); }
const HelpBubble* help_bubble() const { return help_bubble_.get(); }
const base::Feature* iph_feature() const { return iph_feature_; }
bool was_started() const { return state_ != State::kNotStarted; }
bool is_promo_active() const {
return state_ == State::kRunning || state_ == State::kContinued;
}
bool is_bubble_visible() const { return state_ == State::kRunning; }
bool is_demo() const { return was_started() && !tracker_; }
PromoType promo_type() const { return promo_type_; }
PromoSubtype promo_subtype() const { return promo_subtype_; }
int num_rotating_entries() const { return num_rotating_entries_; }
private:
enum class State { kNotStarted, kRunning, kContinued, kClosed };
// Records `PromoData` about the promo closing, unless in demo mode.
void MaybeWriteClosedPromoData(FeaturePromoClosedReason close_reason);
// Records that an IPH was shown, including type and identity.
void RecordShown();
// Records user actions and histograms that discern what action was taken to
// close a promotion. Does not record in demo mode.
void MaybeRecordClosedReason(FeaturePromoClosedReason close_reason);
// If the promo is running, ends it, possibly dismissing the Tracker.
bool MaybeEndPromo();
// Returns whether `promo_data` satisfies the requirements for being shown as
// a snooze promo.
FeaturePromoResult CanShowSnoozePromo(
const FeaturePromoData& promo_data) const;
// Gets the current time, which is based on the reference clock provided by
// `storage_service` and can be overridden by tests.
base::Time GetCurrentTime() const;
// If `promo_index_` isn't yet known, reads it from `data`; if `data` is null,
// reads it from the storage service instead.
void MaybeCachePromoIndex(const FeaturePromoData* data) const;
// Returns the result of trying to reshow a promo.
FeaturePromoResult GetReshowResult(base::Time last_show_time,
int show_count) const;
// The service that stores non-transient data about the IPH.
const raw_ptr<UserEducationStorageService> storage_service_;
// The current promo key, or empty for none.
const std::string promo_key_;
// Data about the current IPH.
const raw_ptr<const base::Feature> iph_feature_;
const PromoType promo_type_;
const PromoSubtype promo_subtype_;
const int num_rotating_entries_;
// Optional data about the current IPH.
std::optional<base::TimeDelta> reshow_delay_;
std::optional<int> max_show_count_;
// These are cached values for rotating promos that are applied later when the
// data for the promo is updated.
mutable std::optional<int> promo_index_;
// The current state of the promo.
State state_ = State::kNotStarted;
bool wrote_close_data_ = false;
bool tracker_dismissed_ = false;
// The tracker, in the event the bubble needs to be dismissed.
raw_ptr<feature_engagement::Tracker> tracker_ = nullptr;
std::unique_ptr<HelpBubble> help_bubble_;
};
} // namespace user_education
#endif // COMPONENTS_USER_EDUCATION_COMMON_FEATURE_PROMO_FEATURE_PROMO_LIFECYCLE_H_
|