clipboard_nudge_controller.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/clipboard/clipboard_nudge_controller.h"
  5. #include "ash/clipboard/clipboard_history_item.h"
  6. #include "ash/clipboard/clipboard_history_util.h"
  7. #include "ash/clipboard/clipboard_nudge.h"
  8. #include "ash/constants/ash_features.h"
  9. #include "ash/constants/ash_pref_names.h"
  10. #include "ash/session/session_controller_impl.h"
  11. #include "ash/shell.h"
  12. #include "base/bind.h"
  13. #include "base/json/values_util.h"
  14. #include "base/logging.h"
  15. #include "base/metrics/histogram_functions.h"
  16. #include "base/metrics/histogram_macros.h"
  17. #include "components/prefs/pref_registry_simple.h"
  18. #include "components/prefs/pref_service.h"
  19. #include "components/prefs/scoped_user_pref_update.h"
  20. #include "ui/base/clipboard/clipboard_monitor.h"
  21. #include "ui/compositor/layer.h"
  22. #include "ui/compositor/scoped_layer_animation_settings.h"
  23. namespace ash {
  24. namespace {
  25. // Keys for tooltip sub-preferences for shown count and last time shown.
  26. constexpr char kShownCount[] = "shown_count";
  27. constexpr char kLastTimeShown[] = "last_time_shown";
  28. constexpr char kNewFeatureBadgeCount[] = "new_feature_shown_count";
  29. // The maximum number of 1 second buckets used to record the time between
  30. // showing the nudge and recording the feature being opened/used.
  31. constexpr int kMaxSeconds = 61;
  32. // Clock that can be overridden for testing.
  33. base::Clock* g_clock_override = nullptr;
  34. base::Time GetTime() {
  35. if (g_clock_override)
  36. return g_clock_override->Now();
  37. return base::Time::Now();
  38. }
  39. bool LogFeatureOpenTime(
  40. const ClipboardNudgeController::TimeMetricHelper& metric_show_time,
  41. const std::string& open_histogram) {
  42. if (!metric_show_time.ShouldLogFeatureOpenTime())
  43. return false;
  44. base::TimeDelta time_since_shown =
  45. metric_show_time.GetTimeSinceShown(GetTime());
  46. // Tracks the amount of time between showing the user a nudge and
  47. // the user opening the ClipboardHistory menu.
  48. base::UmaHistogramExactLinear(open_histogram, time_since_shown.InSeconds(),
  49. kMaxSeconds);
  50. return true;
  51. }
  52. bool LogFeatureUsedTime(
  53. const ClipboardNudgeController::TimeMetricHelper& metric_show_time,
  54. const std::string& paste_histogram) {
  55. if (!metric_show_time.ShouldLogFeatureUsedTime())
  56. return false;
  57. base::TimeDelta time_since_shown =
  58. metric_show_time.GetTimeSinceShown(GetTime());
  59. // Tracks the amount of time between showing the user a nudge and
  60. // the user opening the ClipboardHistory menu.
  61. base::UmaHistogramExactLinear(paste_histogram, time_since_shown.InSeconds(),
  62. kMaxSeconds);
  63. return true;
  64. }
  65. } // namespace
  66. ClipboardNudgeController::ClipboardNudgeController(
  67. ClipboardHistory* clipboard_history,
  68. ClipboardHistoryControllerImpl* clipboard_history_controller)
  69. : clipboard_history_(clipboard_history),
  70. clipboard_history_controller_(clipboard_history_controller) {
  71. clipboard_history_->AddObserver(this);
  72. clipboard_history_controller_->AddObserver(this);
  73. ui::ClipboardMonitor::GetInstance()->AddObserver(this);
  74. if (chromeos::features::IsClipboardHistoryNudgeSessionResetEnabled())
  75. Shell::Get()->session_controller()->AddObserver(this);
  76. }
  77. ClipboardNudgeController::~ClipboardNudgeController() {
  78. clipboard_history_->RemoveObserver(this);
  79. clipboard_history_controller_->RemoveObserver(this);
  80. ui::ClipboardMonitor::GetInstance()->RemoveObserver(this);
  81. if (chromeos::features::IsClipboardHistoryNudgeSessionResetEnabled())
  82. Shell::Get()->session_controller()->RemoveObserver(this);
  83. }
  84. // static
  85. void ClipboardNudgeController::RegisterProfilePrefs(
  86. PrefRegistrySimple* registry) {
  87. registry->RegisterDictionaryPref(prefs::kMultipasteNudges);
  88. }
  89. void ClipboardNudgeController::OnClipboardHistoryItemAdded(
  90. const ClipboardHistoryItem& item,
  91. bool is_duplicate) {
  92. PrefService* prefs =
  93. Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  94. if (!ShouldShowNudge(prefs))
  95. return;
  96. switch (clipboard_state_) {
  97. case ClipboardState::kInit:
  98. clipboard_state_ = ClipboardState::kFirstCopy;
  99. return;
  100. case ClipboardState::kFirstPaste:
  101. clipboard_state_ = ClipboardState::kSecondCopy;
  102. return;
  103. case ClipboardState::kFirstCopy:
  104. case ClipboardState::kSecondCopy:
  105. case ClipboardState::kShouldShowNudge:
  106. return;
  107. }
  108. }
  109. void ClipboardNudgeController::MarkNewFeatureBadgeShown() {
  110. PrefService* prefs =
  111. Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  112. if (!prefs)
  113. return;
  114. const int shown_count = GetNewFeatureBadgeShownCount(prefs);
  115. DictionaryPrefUpdate update(prefs, prefs::kMultipasteNudges);
  116. update->SetIntPath(kNewFeatureBadgeCount, shown_count + 1);
  117. base::UmaHistogramBoolean(kNewBadge_ShowCount, true);
  118. if (new_feature_last_shown_time_.ShouldLogFeatureOpenTime()) {
  119. base::UmaHistogramExactLinear(kNewBadge_OpenTime, kMaxSeconds, kMaxSeconds);
  120. }
  121. if (new_feature_last_shown_time_.ShouldLogFeatureUsedTime()) {
  122. base::UmaHistogramExactLinear(kNewBadge_PasteTime, kMaxSeconds,
  123. kMaxSeconds);
  124. }
  125. new_feature_last_shown_time_.ResetTime();
  126. }
  127. void ClipboardNudgeController::MarkScreenshotNotificationShown() {
  128. base::UmaHistogramBoolean(kScreenshotNotification_ShowCount, true);
  129. if (screenshot_notification_last_shown_time_.ShouldLogFeatureOpenTime()) {
  130. base::UmaHistogramExactLinear(kScreenshotNotification_OpenTime, kMaxSeconds,
  131. kMaxSeconds);
  132. }
  133. if (screenshot_notification_last_shown_time_.ShouldLogFeatureUsedTime()) {
  134. base::UmaHistogramExactLinear(kScreenshotNotification_PasteTime,
  135. kMaxSeconds, kMaxSeconds);
  136. }
  137. screenshot_notification_last_shown_time_.ResetTime();
  138. }
  139. bool ClipboardNudgeController::ShouldShowNewFeatureBadge() {
  140. PrefService* prefs =
  141. Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  142. if (!prefs)
  143. return false;
  144. int badge_shown_count = GetNewFeatureBadgeShownCount(prefs);
  145. // We should not show more nudges after hitting the limit.
  146. return badge_shown_count < kContextMenuBadgeShowLimit;
  147. }
  148. void ClipboardNudgeController::OnClipboardDataRead() {
  149. PrefService* prefs =
  150. Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  151. if (!ClipboardHistoryUtil::IsEnabledInCurrentMode() || !prefs ||
  152. !ShouldShowNudge(prefs)) {
  153. return;
  154. }
  155. switch (clipboard_state_) {
  156. case ClipboardState::kFirstCopy:
  157. clipboard_state_ = ClipboardState::kFirstPaste;
  158. last_paste_timestamp_ = GetTime();
  159. return;
  160. case ClipboardState::kFirstPaste:
  161. // Subsequent pastes should reset the timestamp.
  162. last_paste_timestamp_ = GetTime();
  163. return;
  164. case ClipboardState::kSecondCopy:
  165. if (GetTime() - last_paste_timestamp_ < kMaxTimeBetweenPaste) {
  166. ShowNudge(ClipboardNudgeType::kOnboardingNudge);
  167. HandleNudgeShown();
  168. } else {
  169. // ClipboardState should be reset to kFirstPaste when timed out.
  170. clipboard_state_ = ClipboardState::kFirstPaste;
  171. last_paste_timestamp_ = GetTime();
  172. }
  173. return;
  174. case ClipboardState::kInit:
  175. case ClipboardState::kShouldShowNudge:
  176. return;
  177. }
  178. }
  179. void ClipboardNudgeController::OnActiveUserPrefServiceChanged(
  180. PrefService* prefs) {
  181. // Reset the nudge prefs so that the nudge can be shown again.
  182. DictionaryPrefUpdate update(prefs, prefs::kMultipasteNudges);
  183. update->SetIntPath(kShownCount, 0);
  184. update->SetPath(kLastTimeShown, base::TimeToValue(base::Time()));
  185. update->SetIntPath(kNewFeatureBadgeCount, 0);
  186. }
  187. void ClipboardNudgeController::ShowNudge(ClipboardNudgeType nudge_type) {
  188. DCHECK_NE(nudge_type, ClipboardNudgeType::kNewFeatureBadge);
  189. current_nudge_type_ = nudge_type;
  190. SystemNudgeController::ShowNudge();
  191. // Tracks the number of times the ClipboardHistory nudge is shown.
  192. // This allows us to understand the conversion rate of showing a nudge to
  193. // a user opening and then using the clipboard history feature.
  194. switch (nudge_type) {
  195. case ClipboardNudgeType::kOnboardingNudge:
  196. if (last_shown_time_.ShouldLogFeatureOpenTime()) {
  197. base::UmaHistogramExactLinear(kOnboardingNudge_OpenTime, kMaxSeconds,
  198. kMaxSeconds);
  199. }
  200. if (last_shown_time_.ShouldLogFeatureUsedTime()) {
  201. base::UmaHistogramExactLinear(kOnboardingNudge_PasteTime, kMaxSeconds,
  202. kMaxSeconds);
  203. }
  204. last_shown_time_.ResetTime();
  205. base::UmaHistogramBoolean(kOnboardingNudge_ShowCount, true);
  206. break;
  207. case ClipboardNudgeType::kZeroStateNudge:
  208. if (zero_state_last_shown_time_.ShouldLogFeatureOpenTime()) {
  209. base::UmaHistogramExactLinear(kZeroStateNudge_OpenTime, kMaxSeconds,
  210. kMaxSeconds);
  211. }
  212. if (zero_state_last_shown_time_.ShouldLogFeatureUsedTime()) {
  213. base::UmaHistogramExactLinear(kZeroStateNudge_PasteTime, kMaxSeconds,
  214. kMaxSeconds);
  215. }
  216. zero_state_last_shown_time_.ResetTime();
  217. base::UmaHistogramBoolean(kZeroStateNudge_ShowCount, true);
  218. break;
  219. default:
  220. NOTREACHED();
  221. }
  222. }
  223. void ClipboardNudgeController::HandleNudgeShown() {
  224. clipboard_state_ = ClipboardState::kInit;
  225. PrefService* prefs =
  226. Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  227. if (!prefs)
  228. return;
  229. const int shown_count = GetShownCount(prefs);
  230. DictionaryPrefUpdate update(prefs, prefs::kMultipasteNudges);
  231. update->SetIntPath(kShownCount, shown_count + 1);
  232. update->SetPath(kLastTimeShown, base::TimeToValue(GetTime()));
  233. }
  234. void ClipboardNudgeController::OnClipboardHistoryMenuShown(
  235. crosapi::mojom::ClipboardHistoryControllerShowSource show_source) {
  236. if (LogFeatureOpenTime(last_shown_time_, kOnboardingNudge_OpenTime))
  237. last_shown_time_.set_was_logged_as_opened();
  238. switch (show_source) {
  239. case crosapi::mojom::ClipboardHistoryControllerShowSource::kAccelerator:
  240. case crosapi::mojom::ClipboardHistoryControllerShowSource::kVirtualKeyboard:
  241. case crosapi::mojom::ClipboardHistoryControllerShowSource::kUnknown:
  242. break;
  243. case crosapi::mojom::ClipboardHistoryControllerShowSource::
  244. kRenderViewContextMenu:
  245. case crosapi::mojom::ClipboardHistoryControllerShowSource::
  246. kTextfieldContextMenu:
  247. if (LogFeatureOpenTime(new_feature_last_shown_time_, kNewBadge_OpenTime))
  248. new_feature_last_shown_time_.set_was_logged_as_opened();
  249. }
  250. if (LogFeatureOpenTime(zero_state_last_shown_time_, kZeroStateNudge_OpenTime))
  251. zero_state_last_shown_time_.set_was_logged_as_opened();
  252. if (LogFeatureOpenTime(screenshot_notification_last_shown_time_,
  253. kScreenshotNotification_OpenTime)) {
  254. screenshot_notification_last_shown_time_.set_was_logged_as_opened();
  255. }
  256. }
  257. void ClipboardNudgeController::OnClipboardHistoryPasted() {
  258. if (LogFeatureUsedTime(last_shown_time_, kOnboardingNudge_PasteTime))
  259. last_shown_time_.set_was_logged_as_used();
  260. if (LogFeatureUsedTime(new_feature_last_shown_time_, kNewBadge_PasteTime))
  261. new_feature_last_shown_time_.set_was_logged_as_used();
  262. if (LogFeatureUsedTime(zero_state_last_shown_time_,
  263. kZeroStateNudge_PasteTime)) {
  264. zero_state_last_shown_time_.set_was_logged_as_used();
  265. }
  266. if (LogFeatureUsedTime(screenshot_notification_last_shown_time_,
  267. kScreenshotNotification_PasteTime)) {
  268. screenshot_notification_last_shown_time_.set_was_logged_as_used();
  269. }
  270. }
  271. void ClipboardNudgeController::OverrideClockForTesting(
  272. base::Clock* test_clock) {
  273. DCHECK(!g_clock_override);
  274. g_clock_override = test_clock;
  275. }
  276. void ClipboardNudgeController::ClearClockOverrideForTesting() {
  277. DCHECK(g_clock_override);
  278. g_clock_override = nullptr;
  279. }
  280. const ClipboardState& ClipboardNudgeController::GetClipboardStateForTesting() {
  281. return clipboard_state_;
  282. }
  283. std::unique_ptr<SystemNudge> ClipboardNudgeController::CreateSystemNudge() {
  284. return std::make_unique<ClipboardNudge>(current_nudge_type_);
  285. }
  286. int ClipboardNudgeController::GetShownCount(PrefService* prefs) {
  287. const base::Value::Dict& dictionary =
  288. prefs->GetValueDict(prefs::kMultipasteNudges);
  289. return dictionary.FindInt(kShownCount).value_or(0);
  290. }
  291. int ClipboardNudgeController::GetNewFeatureBadgeShownCount(PrefService* prefs) {
  292. const base::Value::Dict& dictionary =
  293. prefs->GetValueDict(prefs::kMultipasteNudges);
  294. return dictionary.FindInt(kNewFeatureBadgeCount).value_or(0);
  295. }
  296. base::Time ClipboardNudgeController::GetLastShownTime(PrefService* prefs) {
  297. const base::Value::Dict& dictionary =
  298. prefs->GetValueDict(prefs::kMultipasteNudges);
  299. absl::optional<base::Time> last_shown_time =
  300. base::ValueToTime(dictionary.Find(kLastTimeShown));
  301. return last_shown_time.value_or(base::Time());
  302. }
  303. bool ClipboardNudgeController::ShouldShowNudge(PrefService* prefs) {
  304. if (!prefs)
  305. return false;
  306. int nudge_shown_count = GetShownCount(prefs);
  307. base::Time last_shown_time = GetLastShownTime(prefs);
  308. // We should not show more nudges after hitting the limit.
  309. if (nudge_shown_count >= kNotificationLimit)
  310. return false;
  311. // If the nudge has yet to be shown, we should return true.
  312. if (last_shown_time.is_null())
  313. return true;
  314. // We should show the nudge if enough time has passed since the nudge was last
  315. // shown.
  316. return base::Time::Now() - last_shown_time > kMinInterval;
  317. }
  318. base::Time ClipboardNudgeController::GetTime() {
  319. if (g_clock_override)
  320. return g_clock_override->Now();
  321. return base::Time::Now();
  322. }
  323. void ClipboardNudgeController::TimeMetricHelper::ResetTime() {
  324. last_shown_time_ =
  325. g_clock_override ? g_clock_override->Now() : base::Time::Now();
  326. was_logged_as_opened_ = false;
  327. was_logged_as_used_ = false;
  328. }
  329. bool ClipboardNudgeController::TimeMetricHelper::ShouldLogFeatureUsedTime()
  330. const {
  331. return !last_shown_time_.is_null() && !was_logged_as_used_;
  332. }
  333. bool ClipboardNudgeController::TimeMetricHelper::ShouldLogFeatureOpenTime()
  334. const {
  335. return !last_shown_time_.is_null() && !was_logged_as_opened_;
  336. }
  337. base::TimeDelta ClipboardNudgeController::TimeMetricHelper::GetTimeSinceShown(
  338. base::Time current_time) const {
  339. return current_time - last_shown_time_;
  340. }
  341. } // namespace ash