contextual_tooltip.cc 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  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/controls/contextual_tooltip.h"
  5. #include "ash/constants/ash_features.h"
  6. #include "ash/constants/ash_pref_names.h"
  7. #include "ash/constants/ash_switches.h"
  8. #include "ash/public/cpp/shelf_config.h"
  9. #include "ash/session/session_controller_impl.h"
  10. #include "ash/shell.h"
  11. #include "base/json/values_util.h"
  12. #include "base/no_destructor.h"
  13. #include "base/strings/strcat.h"
  14. #include "base/strings/string_util.h"
  15. #include "base/time/time.h"
  16. #include "components/prefs/scoped_user_pref_update.h"
  17. namespace ash {
  18. namespace contextual_tooltip {
  19. namespace {
  20. // Keys for tooltip sub-preferences for shown count and last time shown.
  21. constexpr char kShownCount[] = "shown_count";
  22. constexpr char kLastTimeShown[] = "last_time_shown";
  23. // Keys for tooltip sub-preferences of how many times a gesture has been
  24. // successfully performed by the user.
  25. constexpr char kSuccessCount[] = "success_count";
  26. // Whether the drag handle nudge cannot be shown because the shelf is currently
  27. // hidden - used to unblock showing back gesture when shelf is hidden (the back
  28. // gesture will normally show only if the drag handle nudge has already been
  29. // shown within the last nudge show interval).
  30. bool g_drag_handle_nudge_disabled_for_hidden_shelf = false;
  31. // Whether the back gesture nudge is currently being shown.
  32. bool g_back_gesture_nudge_showing = false;
  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. std::string TooltipTypeToString(TooltipType type) {
  40. switch (type) {
  41. case TooltipType::kBackGesture:
  42. return "back_gesture";
  43. case TooltipType::kHomeToOverview:
  44. return "home_to_overview";
  45. case TooltipType::kInAppToHome:
  46. return "in_app_to_home";
  47. case TooltipType::kKeyboardBacklightColor:
  48. return "keyboard_backlight_color";
  49. case TooltipType::kKeyboardBacklightWallpaperColor:
  50. return "keyboard_backlight_wallpaper_color";
  51. }
  52. return "invalid";
  53. }
  54. // Creates the path to the dictionary value from the contextual tooltip type and
  55. // the sub-preference.
  56. std::string GetPath(TooltipType type, const std::string& sub_pref) {
  57. return base::JoinString({TooltipTypeToString(type), sub_pref}, ".");
  58. }
  59. base::Time GetLastShownTime(PrefService* prefs, TooltipType type) {
  60. const base::Value* last_shown_time =
  61. prefs->GetValueDict(prefs::kContextualTooltips)
  62. .FindByDottedPath(GetPath(type, kLastTimeShown));
  63. if (!last_shown_time)
  64. return base::Time();
  65. return *base::ValueToTime(last_shown_time);
  66. }
  67. int GetSuccessCount(PrefService* prefs, TooltipType type) {
  68. absl::optional<int> success_count =
  69. prefs->GetValueDict(prefs::kContextualTooltips)
  70. .FindIntByDottedPath(GetPath(type, kSuccessCount));
  71. return success_count.value_or(0);
  72. }
  73. const absl::optional<base::TimeDelta>& GetMinIntervalOverride() {
  74. // Overridden minimum time between showing contextual nudges to the user.
  75. static absl::optional<base::TimeDelta> min_interval_override;
  76. if (!min_interval_override) {
  77. min_interval_override = switches::ContextualNudgesInterval();
  78. }
  79. return min_interval_override;
  80. }
  81. } // namespace
  82. void RegisterProfilePrefs(PrefRegistrySimple* registry) {
  83. if (features::AreContextualNudgesEnabled())
  84. registry->RegisterDictionaryPref(prefs::kContextualTooltips);
  85. }
  86. bool ShouldShowNudge(PrefService* prefs,
  87. TooltipType type,
  88. base::TimeDelta* recheck_delay) {
  89. auto set_recheck_delay = [&recheck_delay](base::TimeDelta delay) {
  90. if (recheck_delay)
  91. *recheck_delay = delay;
  92. };
  93. if (!features::AreContextualNudgesEnabled()) {
  94. set_recheck_delay(base::TimeDelta());
  95. return false;
  96. }
  97. if (type == TooltipType::kInAppToHome &&
  98. g_drag_handle_nudge_disabled_for_hidden_shelf) {
  99. set_recheck_delay(base::TimeDelta());
  100. return false;
  101. }
  102. const int success_count = GetSuccessCount(prefs, type);
  103. if ((type == TooltipType::kHomeToOverview &&
  104. success_count >= kSuccessLimitHomeToOverview) ||
  105. (type == TooltipType::kBackGesture &&
  106. success_count >= kSuccessLimitBackGesture) ||
  107. (type == TooltipType::kInAppToHome &&
  108. success_count >= kSuccessLimitInAppToHome) ||
  109. (type == TooltipType::kKeyboardBacklightColor &&
  110. success_count >= kSuccessLimitKeyboardBacklightColor)) {
  111. set_recheck_delay(base::TimeDelta());
  112. return false;
  113. }
  114. const int shown_count = GetShownCount(prefs, type);
  115. if (shown_count >= kNotificationLimit) {
  116. set_recheck_delay(base::TimeDelta());
  117. return false;
  118. }
  119. // Before showing back gesture nudge, do not show it if in-app to shelf nudge
  120. // should be shown (to prevent two nudges from showing up at the same time).
  121. // Verify that the in-app to home nudge was shown within the last show
  122. // interval.
  123. if (type == TooltipType::kBackGesture) {
  124. if (!g_drag_handle_nudge_disabled_for_hidden_shelf &&
  125. ShouldShowNudge(prefs, TooltipType::kInAppToHome, nullptr)) {
  126. set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge);
  127. return false;
  128. }
  129. // Verify that drag handle nudge has been shown at least a minute ago.
  130. const base::Time drag_handle_nudge_last_shown_time =
  131. GetLastShownTime(prefs, TooltipType::kInAppToHome);
  132. if (!drag_handle_nudge_last_shown_time.is_null()) {
  133. const base::TimeDelta time_since_drag_handle_nudge =
  134. GetTime() - drag_handle_nudge_last_shown_time;
  135. if (time_since_drag_handle_nudge <
  136. kMinIntervalBetweenBackAndDragHandleNudge) {
  137. set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge -
  138. time_since_drag_handle_nudge);
  139. return false;
  140. }
  141. }
  142. }
  143. // Make sure that drag handle nudge is not shown within a minute of back
  144. // gesture nudge.
  145. if (type == TooltipType::kInAppToHome) {
  146. if (g_back_gesture_nudge_showing) {
  147. set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge);
  148. return false;
  149. }
  150. const base::Time back_nudge_last_shown_time =
  151. GetLastShownTime(prefs, TooltipType::kBackGesture);
  152. if (!back_nudge_last_shown_time.is_null()) {
  153. const base::TimeDelta time_since_back_nudge =
  154. GetTime() - back_nudge_last_shown_time;
  155. if (time_since_back_nudge < kMinIntervalBetweenBackAndDragHandleNudge) {
  156. set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge -
  157. time_since_back_nudge);
  158. return false;
  159. }
  160. }
  161. }
  162. if (shown_count == 0)
  163. return true;
  164. const base::Time last_shown_time = GetLastShownTime(prefs, type);
  165. const base::TimeDelta min_interval =
  166. GetMinIntervalOverride().value_or(kMinInterval);
  167. const base::TimeDelta time_since_last_nudge = GetTime() - last_shown_time;
  168. if (time_since_last_nudge < min_interval) {
  169. set_recheck_delay(min_interval - time_since_last_nudge);
  170. return false;
  171. }
  172. return true;
  173. }
  174. base::TimeDelta GetNudgeTimeout(PrefService* prefs, TooltipType type) {
  175. const int shown_count = GetShownCount(prefs, type);
  176. if (shown_count == 0)
  177. return base::TimeDelta();
  178. return kNudgeShowDuration;
  179. }
  180. int GetShownCount(PrefService* prefs, TooltipType type) {
  181. absl::optional<int> shown_count =
  182. prefs->GetValueDict(prefs::kContextualTooltips)
  183. .FindIntByDottedPath(GetPath(type, kShownCount));
  184. return shown_count.value_or(0);
  185. }
  186. void HandleNudgeShown(PrefService* prefs, TooltipType type) {
  187. const int shown_count = GetShownCount(prefs, type);
  188. DictionaryPrefUpdate update(prefs, prefs::kContextualTooltips);
  189. update->SetIntPath(GetPath(type, kShownCount), shown_count + 1);
  190. update->SetPath(GetPath(type, kLastTimeShown), base::TimeToValue(GetTime()));
  191. }
  192. void HandleGesturePerformed(PrefService* prefs, TooltipType type) {
  193. const int success_count = GetSuccessCount(prefs, type);
  194. DictionaryPrefUpdate update(prefs, prefs::kContextualTooltips);
  195. update->SetIntPath(GetPath(type, kSuccessCount), success_count + 1);
  196. }
  197. void SetDragHandleNudgeDisabledForHiddenShelf(bool nudge_disabled) {
  198. g_drag_handle_nudge_disabled_for_hidden_shelf = nudge_disabled;
  199. }
  200. void SetBackGestureNudgeShowing(bool showing) {
  201. g_back_gesture_nudge_showing = showing;
  202. }
  203. void ClearPrefs() {
  204. DCHECK(Shell::Get()->session_controller()->GetLastActiveUserPrefService());
  205. DictionaryPrefUpdate update(
  206. Shell::Get()->session_controller()->GetLastActiveUserPrefService(),
  207. prefs::kContextualTooltips);
  208. base::Value* nudges_dict = update.Get();
  209. if (nudges_dict && !nudges_dict->DictEmpty())
  210. nudges_dict->DictClear();
  211. }
  212. void OverrideClockForTesting(base::Clock* test_clock) {
  213. DCHECK(!g_clock_override);
  214. g_clock_override = test_clock;
  215. }
  216. void ClearClockOverrideForTesting() {
  217. DCHECK(g_clock_override);
  218. g_clock_override = nullptr;
  219. }
  220. } // namespace contextual_tooltip
  221. } // namespace ash