123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- // Copyright 2020 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "ash/clipboard/clipboard_nudge_controller.h"
- #include "ash/clipboard/clipboard_history_item.h"
- #include "ash/clipboard/clipboard_history_util.h"
- #include "ash/clipboard/clipboard_nudge.h"
- #include "ash/constants/ash_features.h"
- #include "ash/constants/ash_pref_names.h"
- #include "ash/session/session_controller_impl.h"
- #include "ash/shell.h"
- #include "base/bind.h"
- #include "base/json/values_util.h"
- #include "base/logging.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/metrics/histogram_macros.h"
- #include "components/prefs/pref_registry_simple.h"
- #include "components/prefs/pref_service.h"
- #include "components/prefs/scoped_user_pref_update.h"
- #include "ui/base/clipboard/clipboard_monitor.h"
- #include "ui/compositor/layer.h"
- #include "ui/compositor/scoped_layer_animation_settings.h"
- namespace ash {
- namespace {
- // Keys for tooltip sub-preferences for shown count and last time shown.
- constexpr char kShownCount[] = "shown_count";
- constexpr char kLastTimeShown[] = "last_time_shown";
- constexpr char kNewFeatureBadgeCount[] = "new_feature_shown_count";
- // The maximum number of 1 second buckets used to record the time between
- // showing the nudge and recording the feature being opened/used.
- constexpr int kMaxSeconds = 61;
- // Clock that can be overridden for testing.
- base::Clock* g_clock_override = nullptr;
- base::Time GetTime() {
- if (g_clock_override)
- return g_clock_override->Now();
- return base::Time::Now();
- }
- bool LogFeatureOpenTime(
- const ClipboardNudgeController::TimeMetricHelper& metric_show_time,
- const std::string& open_histogram) {
- if (!metric_show_time.ShouldLogFeatureOpenTime())
- return false;
- base::TimeDelta time_since_shown =
- metric_show_time.GetTimeSinceShown(GetTime());
- // Tracks the amount of time between showing the user a nudge and
- // the user opening the ClipboardHistory menu.
- base::UmaHistogramExactLinear(open_histogram, time_since_shown.InSeconds(),
- kMaxSeconds);
- return true;
- }
- bool LogFeatureUsedTime(
- const ClipboardNudgeController::TimeMetricHelper& metric_show_time,
- const std::string& paste_histogram) {
- if (!metric_show_time.ShouldLogFeatureUsedTime())
- return false;
- base::TimeDelta time_since_shown =
- metric_show_time.GetTimeSinceShown(GetTime());
- // Tracks the amount of time between showing the user a nudge and
- // the user opening the ClipboardHistory menu.
- base::UmaHistogramExactLinear(paste_histogram, time_since_shown.InSeconds(),
- kMaxSeconds);
- return true;
- }
- } // namespace
- ClipboardNudgeController::ClipboardNudgeController(
- ClipboardHistory* clipboard_history,
- ClipboardHistoryControllerImpl* clipboard_history_controller)
- : clipboard_history_(clipboard_history),
- clipboard_history_controller_(clipboard_history_controller) {
- clipboard_history_->AddObserver(this);
- clipboard_history_controller_->AddObserver(this);
- ui::ClipboardMonitor::GetInstance()->AddObserver(this);
- if (chromeos::features::IsClipboardHistoryNudgeSessionResetEnabled())
- Shell::Get()->session_controller()->AddObserver(this);
- }
- ClipboardNudgeController::~ClipboardNudgeController() {
- clipboard_history_->RemoveObserver(this);
- clipboard_history_controller_->RemoveObserver(this);
- ui::ClipboardMonitor::GetInstance()->RemoveObserver(this);
- if (chromeos::features::IsClipboardHistoryNudgeSessionResetEnabled())
- Shell::Get()->session_controller()->RemoveObserver(this);
- }
- // static
- void ClipboardNudgeController::RegisterProfilePrefs(
- PrefRegistrySimple* registry) {
- registry->RegisterDictionaryPref(prefs::kMultipasteNudges);
- }
- void ClipboardNudgeController::OnClipboardHistoryItemAdded(
- const ClipboardHistoryItem& item,
- bool is_duplicate) {
- PrefService* prefs =
- Shell::Get()->session_controller()->GetLastActiveUserPrefService();
- if (!ShouldShowNudge(prefs))
- return;
- switch (clipboard_state_) {
- case ClipboardState::kInit:
- clipboard_state_ = ClipboardState::kFirstCopy;
- return;
- case ClipboardState::kFirstPaste:
- clipboard_state_ = ClipboardState::kSecondCopy;
- return;
- case ClipboardState::kFirstCopy:
- case ClipboardState::kSecondCopy:
- case ClipboardState::kShouldShowNudge:
- return;
- }
- }
- void ClipboardNudgeController::MarkNewFeatureBadgeShown() {
- PrefService* prefs =
- Shell::Get()->session_controller()->GetLastActiveUserPrefService();
- if (!prefs)
- return;
- const int shown_count = GetNewFeatureBadgeShownCount(prefs);
- DictionaryPrefUpdate update(prefs, prefs::kMultipasteNudges);
- update->SetIntPath(kNewFeatureBadgeCount, shown_count + 1);
- base::UmaHistogramBoolean(kNewBadge_ShowCount, true);
- if (new_feature_last_shown_time_.ShouldLogFeatureOpenTime()) {
- base::UmaHistogramExactLinear(kNewBadge_OpenTime, kMaxSeconds, kMaxSeconds);
- }
- if (new_feature_last_shown_time_.ShouldLogFeatureUsedTime()) {
- base::UmaHistogramExactLinear(kNewBadge_PasteTime, kMaxSeconds,
- kMaxSeconds);
- }
- new_feature_last_shown_time_.ResetTime();
- }
- void ClipboardNudgeController::MarkScreenshotNotificationShown() {
- base::UmaHistogramBoolean(kScreenshotNotification_ShowCount, true);
- if (screenshot_notification_last_shown_time_.ShouldLogFeatureOpenTime()) {
- base::UmaHistogramExactLinear(kScreenshotNotification_OpenTime, kMaxSeconds,
- kMaxSeconds);
- }
- if (screenshot_notification_last_shown_time_.ShouldLogFeatureUsedTime()) {
- base::UmaHistogramExactLinear(kScreenshotNotification_PasteTime,
- kMaxSeconds, kMaxSeconds);
- }
- screenshot_notification_last_shown_time_.ResetTime();
- }
- bool ClipboardNudgeController::ShouldShowNewFeatureBadge() {
- PrefService* prefs =
- Shell::Get()->session_controller()->GetLastActiveUserPrefService();
- if (!prefs)
- return false;
- int badge_shown_count = GetNewFeatureBadgeShownCount(prefs);
- // We should not show more nudges after hitting the limit.
- return badge_shown_count < kContextMenuBadgeShowLimit;
- }
- void ClipboardNudgeController::OnClipboardDataRead() {
- PrefService* prefs =
- Shell::Get()->session_controller()->GetLastActiveUserPrefService();
- if (!ClipboardHistoryUtil::IsEnabledInCurrentMode() || !prefs ||
- !ShouldShowNudge(prefs)) {
- return;
- }
- switch (clipboard_state_) {
- case ClipboardState::kFirstCopy:
- clipboard_state_ = ClipboardState::kFirstPaste;
- last_paste_timestamp_ = GetTime();
- return;
- case ClipboardState::kFirstPaste:
- // Subsequent pastes should reset the timestamp.
- last_paste_timestamp_ = GetTime();
- return;
- case ClipboardState::kSecondCopy:
- if (GetTime() - last_paste_timestamp_ < kMaxTimeBetweenPaste) {
- ShowNudge(ClipboardNudgeType::kOnboardingNudge);
- HandleNudgeShown();
- } else {
- // ClipboardState should be reset to kFirstPaste when timed out.
- clipboard_state_ = ClipboardState::kFirstPaste;
- last_paste_timestamp_ = GetTime();
- }
- return;
- case ClipboardState::kInit:
- case ClipboardState::kShouldShowNudge:
- return;
- }
- }
- void ClipboardNudgeController::OnActiveUserPrefServiceChanged(
- PrefService* prefs) {
- // Reset the nudge prefs so that the nudge can be shown again.
- DictionaryPrefUpdate update(prefs, prefs::kMultipasteNudges);
- update->SetIntPath(kShownCount, 0);
- update->SetPath(kLastTimeShown, base::TimeToValue(base::Time()));
- update->SetIntPath(kNewFeatureBadgeCount, 0);
- }
- void ClipboardNudgeController::ShowNudge(ClipboardNudgeType nudge_type) {
- DCHECK_NE(nudge_type, ClipboardNudgeType::kNewFeatureBadge);
- current_nudge_type_ = nudge_type;
- SystemNudgeController::ShowNudge();
- // Tracks the number of times the ClipboardHistory nudge is shown.
- // This allows us to understand the conversion rate of showing a nudge to
- // a user opening and then using the clipboard history feature.
- switch (nudge_type) {
- case ClipboardNudgeType::kOnboardingNudge:
- if (last_shown_time_.ShouldLogFeatureOpenTime()) {
- base::UmaHistogramExactLinear(kOnboardingNudge_OpenTime, kMaxSeconds,
- kMaxSeconds);
- }
- if (last_shown_time_.ShouldLogFeatureUsedTime()) {
- base::UmaHistogramExactLinear(kOnboardingNudge_PasteTime, kMaxSeconds,
- kMaxSeconds);
- }
- last_shown_time_.ResetTime();
- base::UmaHistogramBoolean(kOnboardingNudge_ShowCount, true);
- break;
- case ClipboardNudgeType::kZeroStateNudge:
- if (zero_state_last_shown_time_.ShouldLogFeatureOpenTime()) {
- base::UmaHistogramExactLinear(kZeroStateNudge_OpenTime, kMaxSeconds,
- kMaxSeconds);
- }
- if (zero_state_last_shown_time_.ShouldLogFeatureUsedTime()) {
- base::UmaHistogramExactLinear(kZeroStateNudge_PasteTime, kMaxSeconds,
- kMaxSeconds);
- }
- zero_state_last_shown_time_.ResetTime();
- base::UmaHistogramBoolean(kZeroStateNudge_ShowCount, true);
- break;
- default:
- NOTREACHED();
- }
- }
- void ClipboardNudgeController::HandleNudgeShown() {
- clipboard_state_ = ClipboardState::kInit;
- PrefService* prefs =
- Shell::Get()->session_controller()->GetLastActiveUserPrefService();
- if (!prefs)
- return;
- const int shown_count = GetShownCount(prefs);
- DictionaryPrefUpdate update(prefs, prefs::kMultipasteNudges);
- update->SetIntPath(kShownCount, shown_count + 1);
- update->SetPath(kLastTimeShown, base::TimeToValue(GetTime()));
- }
- void ClipboardNudgeController::OnClipboardHistoryMenuShown(
- crosapi::mojom::ClipboardHistoryControllerShowSource show_source) {
- if (LogFeatureOpenTime(last_shown_time_, kOnboardingNudge_OpenTime))
- last_shown_time_.set_was_logged_as_opened();
- switch (show_source) {
- case crosapi::mojom::ClipboardHistoryControllerShowSource::kAccelerator:
- case crosapi::mojom::ClipboardHistoryControllerShowSource::kVirtualKeyboard:
- case crosapi::mojom::ClipboardHistoryControllerShowSource::kUnknown:
- break;
- case crosapi::mojom::ClipboardHistoryControllerShowSource::
- kRenderViewContextMenu:
- case crosapi::mojom::ClipboardHistoryControllerShowSource::
- kTextfieldContextMenu:
- if (LogFeatureOpenTime(new_feature_last_shown_time_, kNewBadge_OpenTime))
- new_feature_last_shown_time_.set_was_logged_as_opened();
- }
- if (LogFeatureOpenTime(zero_state_last_shown_time_, kZeroStateNudge_OpenTime))
- zero_state_last_shown_time_.set_was_logged_as_opened();
- if (LogFeatureOpenTime(screenshot_notification_last_shown_time_,
- kScreenshotNotification_OpenTime)) {
- screenshot_notification_last_shown_time_.set_was_logged_as_opened();
- }
- }
- void ClipboardNudgeController::OnClipboardHistoryPasted() {
- if (LogFeatureUsedTime(last_shown_time_, kOnboardingNudge_PasteTime))
- last_shown_time_.set_was_logged_as_used();
- if (LogFeatureUsedTime(new_feature_last_shown_time_, kNewBadge_PasteTime))
- new_feature_last_shown_time_.set_was_logged_as_used();
- if (LogFeatureUsedTime(zero_state_last_shown_time_,
- kZeroStateNudge_PasteTime)) {
- zero_state_last_shown_time_.set_was_logged_as_used();
- }
- if (LogFeatureUsedTime(screenshot_notification_last_shown_time_,
- kScreenshotNotification_PasteTime)) {
- screenshot_notification_last_shown_time_.set_was_logged_as_used();
- }
- }
- void ClipboardNudgeController::OverrideClockForTesting(
- base::Clock* test_clock) {
- DCHECK(!g_clock_override);
- g_clock_override = test_clock;
- }
- void ClipboardNudgeController::ClearClockOverrideForTesting() {
- DCHECK(g_clock_override);
- g_clock_override = nullptr;
- }
- const ClipboardState& ClipboardNudgeController::GetClipboardStateForTesting() {
- return clipboard_state_;
- }
- std::unique_ptr<SystemNudge> ClipboardNudgeController::CreateSystemNudge() {
- return std::make_unique<ClipboardNudge>(current_nudge_type_);
- }
- int ClipboardNudgeController::GetShownCount(PrefService* prefs) {
- const base::Value::Dict& dictionary =
- prefs->GetValueDict(prefs::kMultipasteNudges);
- return dictionary.FindInt(kShownCount).value_or(0);
- }
- int ClipboardNudgeController::GetNewFeatureBadgeShownCount(PrefService* prefs) {
- const base::Value::Dict& dictionary =
- prefs->GetValueDict(prefs::kMultipasteNudges);
- return dictionary.FindInt(kNewFeatureBadgeCount).value_or(0);
- }
- base::Time ClipboardNudgeController::GetLastShownTime(PrefService* prefs) {
- const base::Value::Dict& dictionary =
- prefs->GetValueDict(prefs::kMultipasteNudges);
- absl::optional<base::Time> last_shown_time =
- base::ValueToTime(dictionary.Find(kLastTimeShown));
- return last_shown_time.value_or(base::Time());
- }
- bool ClipboardNudgeController::ShouldShowNudge(PrefService* prefs) {
- if (!prefs)
- return false;
- int nudge_shown_count = GetShownCount(prefs);
- base::Time last_shown_time = GetLastShownTime(prefs);
- // We should not show more nudges after hitting the limit.
- if (nudge_shown_count >= kNotificationLimit)
- return false;
- // If the nudge has yet to be shown, we should return true.
- if (last_shown_time.is_null())
- return true;
- // We should show the nudge if enough time has passed since the nudge was last
- // shown.
- return base::Time::Now() - last_shown_time > kMinInterval;
- }
- base::Time ClipboardNudgeController::GetTime() {
- if (g_clock_override)
- return g_clock_override->Now();
- return base::Time::Now();
- }
- void ClipboardNudgeController::TimeMetricHelper::ResetTime() {
- last_shown_time_ =
- g_clock_override ? g_clock_override->Now() : base::Time::Now();
- was_logged_as_opened_ = false;
- was_logged_as_used_ = false;
- }
- bool ClipboardNudgeController::TimeMetricHelper::ShouldLogFeatureUsedTime()
- const {
- return !last_shown_time_.is_null() && !was_logged_as_used_;
- }
- bool ClipboardNudgeController::TimeMetricHelper::ShouldLogFeatureOpenTime()
- const {
- return !last_shown_time_.is_null() && !was_logged_as_opened_;
- }
- base::TimeDelta ClipboardNudgeController::TimeMetricHelper::GetTimeSinceShown(
- base::Time current_time) const {
- return current_time - last_shown_time_;
- }
- } // namespace ash
|