launcher_nudge_controller.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. // Copyright 2021 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/shelf/launcher_nudge_controller.h"
  5. #include <memory>
  6. #include "ash/app_list/app_list_controller_impl.h"
  7. #include "ash/constants/ash_features.h"
  8. #include "ash/constants/ash_pref_names.h"
  9. #include "ash/constants/ash_switches.h"
  10. #include "ash/public/cpp/session/session_types.h"
  11. #include "ash/root_window_controller.h"
  12. #include "ash/session/session_controller_impl.h"
  13. #include "ash/shelf/home_button.h"
  14. #include "ash/shelf/home_button_controller.h"
  15. #include "ash/shelf/shelf.h"
  16. #include "ash/shelf/shelf_navigation_widget.h"
  17. #include "ash/shell.h"
  18. #include "base/command_line.h"
  19. #include "base/json/values_util.h"
  20. #include "base/time/time.h"
  21. #include "base/timer/wall_clock_timer.h"
  22. #include "components/prefs/pref_registry_simple.h"
  23. #include "components/prefs/pref_service.h"
  24. #include "components/prefs/scoped_user_pref_update.h"
  25. #include "ui/compositor/scoped_animation_duration_scale_mode.h"
  26. namespace ash {
  27. namespace {
  28. // Keys for user preferences.
  29. constexpr char kShownCount[] = "shown_count";
  30. constexpr char kLastShownTime[] = "last_shown_time";
  31. constexpr char kFirstLoginTime[] = "first_login_time";
  32. constexpr char kWasLauncherShown[] = "was_launcher_shown";
  33. // Constants for launcher nudge controller.
  34. constexpr base::TimeDelta kFirstTimeShowNudgeInterval = base::Days(1);
  35. constexpr base::TimeDelta kShowNudgeInterval = base::Days(1);
  36. constexpr base::TimeDelta kFirstTimeShowNudgeIntervalForTest = base::Minutes(3);
  37. constexpr base::TimeDelta kShowNudgeIntervalForTest = base::Minutes(3);
  38. // Returns the last active user pref service.
  39. PrefService* GetPrefs() {
  40. return Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  41. }
  42. // Gets the timestamp when the nudge was last shown.
  43. base::Time GetLastShownTime(PrefService* prefs) {
  44. const base::Value::Dict& dictionary =
  45. prefs->GetValueDict(prefs::kShelfLauncherNudge);
  46. absl::optional<base::Time> last_shown_time =
  47. base::ValueToTime(dictionary.Find(kLastShownTime));
  48. return last_shown_time.value_or(base::Time());
  49. }
  50. // Gets the timestamp when the user first logged in. The value will not be
  51. // set if the user has logged in before the launcher nudge feature was
  52. // enabled.
  53. base::Time GetFirstLoginTime(PrefService* prefs) {
  54. const base::Value::Dict& dictionary =
  55. prefs->GetValueDict(prefs::kShelfLauncherNudge);
  56. absl::optional<base::Time> first_login_time =
  57. base::ValueToTime(dictionary.Find(kFirstLoginTime));
  58. return first_login_time.value_or(base::Time());
  59. }
  60. // Returns true if the launcher has been shown before.
  61. bool WasLauncherShownPreviously(PrefService* prefs) {
  62. const base::Value::Dict& dictionary =
  63. prefs->GetValueDict(prefs::kShelfLauncherNudge);
  64. return dictionary.FindBool(kWasLauncherShown).value_or(false);
  65. }
  66. } // namespace
  67. // static
  68. constexpr base::TimeDelta
  69. LauncherNudgeController::kMinIntervalAfterHomeButtonAppears;
  70. LauncherNudgeController::LauncherNudgeController()
  71. : show_nudge_timer_(std::make_unique<base::WallClockTimer>()) {
  72. Shell::Get()->app_list_controller()->AddObserver(this);
  73. tablet_mode_observation_.Observe(Shell::Get()->tablet_mode_controller());
  74. }
  75. LauncherNudgeController::~LauncherNudgeController() {
  76. if (Shell::Get()->app_list_controller())
  77. Shell::Get()->app_list_controller()->RemoveObserver(this);
  78. tablet_mode_observation_.Reset();
  79. }
  80. // static
  81. void LauncherNudgeController::RegisterProfilePrefs(
  82. PrefRegistrySimple* registry) {
  83. registry->RegisterDictionaryPref(prefs::kShelfLauncherNudge);
  84. }
  85. // static
  86. HomeButton* LauncherNudgeController::GetHomeButtonForDisplay(
  87. int64_t display_id) {
  88. return Shell::Get()
  89. ->GetRootWindowControllerWithDisplayId(display_id)
  90. ->shelf()
  91. ->navigation_widget()
  92. ->GetHomeButton();
  93. }
  94. // static
  95. int LauncherNudgeController::GetShownCount(PrefService* prefs) {
  96. const base::Value::Dict& dictionary =
  97. prefs->GetValueDict(prefs::kShelfLauncherNudge);
  98. return dictionary.FindInt(kShownCount).value_or(0);
  99. }
  100. base::TimeDelta LauncherNudgeController::GetNudgeInterval(
  101. bool is_first_time) const {
  102. if (features::IsLauncherNudgeShortIntervalEnabled()) {
  103. return is_first_time ? kFirstTimeShowNudgeIntervalForTest
  104. : kShowNudgeIntervalForTest;
  105. }
  106. return is_first_time ? kFirstTimeShowNudgeInterval : kShowNudgeInterval;
  107. }
  108. void LauncherNudgeController::SetClockForTesting(
  109. const base::Clock* clock,
  110. const base::TickClock* timer_clock) {
  111. DCHECK(!show_nudge_timer_->IsRunning());
  112. show_nudge_timer_ =
  113. std::make_unique<base::WallClockTimer>(clock, timer_clock);
  114. clock_for_test_ = clock;
  115. }
  116. bool LauncherNudgeController::IsRecheckTimerRunningForTesting() {
  117. return show_nudge_timer_->IsRunning();
  118. }
  119. bool LauncherNudgeController::ShouldShowNudge(base::Time& recheck_time) const {
  120. PrefService* prefs = GetPrefs();
  121. if (!prefs)
  122. return false;
  123. // Do not show if the command line flag to hide nudges is set.
  124. if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshNoNudges))
  125. return false;
  126. if (GetFirstLoginTime(prefs).is_null()) {
  127. // Don't show the nudge to existing users. See
  128. // `OnActiveUserPrefServiceChanged()` for details.
  129. return false;
  130. }
  131. // Only show the launcher nudge in clamshell mode.
  132. if (Shell::Get()->IsInTabletMode())
  133. return false;
  134. // If the shown count meets the limit or the launcher has been opened before,
  135. // don't show the nudge.
  136. if (GetShownCount(prefs) >= kMaxShownCount ||
  137. WasLauncherShownPreviously(prefs)) {
  138. return false;
  139. }
  140. base::Time last_shown_time;
  141. base::TimeDelta interval;
  142. if (GetShownCount(prefs) == 0) {
  143. // Set the `last_shown_time` to the timestamp when the user first logs in
  144. // if the nudge hasn't been shown yet and the actual `last_shown_time` is
  145. // null. Calculate the expect nudge show time using that timestamp and its
  146. // corresponding interval.
  147. last_shown_time = GetFirstLoginTime(prefs);
  148. interval = GetNudgeInterval(/*is_first_time=*/true);
  149. } else {
  150. last_shown_time = GetLastShownTime(prefs);
  151. interval = GetNudgeInterval(/*is_first_time=*/false);
  152. }
  153. DCHECK(!last_shown_time.is_null());
  154. // The expect shown time of the nudge is set to the later one between the
  155. // calculated expect shown time since last shown and the
  156. // `earliest_available_time`, which is set to ensure the nudge is shown after
  157. // the home button has been shown enough of time.
  158. base::Time expect_shown_time =
  159. std::max(last_shown_time + interval, earliest_available_time_);
  160. if (GetNow() < expect_shown_time) {
  161. // Set the `recheck_time` to the expected time to show nudge.
  162. recheck_time = expect_shown_time;
  163. return false;
  164. }
  165. return true;
  166. }
  167. void LauncherNudgeController::HandleNudgeShown() {
  168. PrefService* prefs = GetPrefs();
  169. if (!prefs)
  170. return;
  171. const int shown_count = GetShownCount(prefs);
  172. DictionaryPrefUpdate update(prefs, prefs::kShelfLauncherNudge);
  173. update->SetIntKey(kShownCount, shown_count + 1);
  174. update->SetKey(kLastShownTime, base::TimeToValue(GetNow()));
  175. }
  176. void LauncherNudgeController::MaybeShowNudge() {
  177. if (!features::IsShelfLauncherNudgeEnabled())
  178. return;
  179. base::Time recheck_time;
  180. if (!ShouldShowNudge(recheck_time)) {
  181. // If `recheck_time` is set, start the timer to check again later for the
  182. // next time to show nudge.
  183. if (!recheck_time.is_null())
  184. ScheduleShowNudgeAttempt(recheck_time);
  185. return;
  186. }
  187. // Don't run the nudge animation if the duration multiplier is 0 to prevent
  188. // crashes that caused by showing the animation that immediately gets deleted.
  189. if (ui::ScopedAnimationDurationScaleMode::duration_multiplier() != 0) {
  190. // Only show the nudge on the home button which is on the same display with
  191. // the cursor.
  192. int64_t display_id_for_nudge =
  193. Shell::Get()->cursor_manager()->GetDisplay().id();
  194. HomeButton* home_button = GetHomeButtonForDisplay(display_id_for_nudge);
  195. home_button->StartNudgeAnimation();
  196. // Only update the prefs if the nudge animation is actually shown.
  197. HandleNudgeShown();
  198. }
  199. // Schedule the next attempt to show nudge if the shown count hasn't hit the
  200. // limit after showing a nudge.
  201. PrefService* prefs = GetPrefs();
  202. if (GetShownCount(prefs) < kMaxShownCount) {
  203. ScheduleShowNudgeAttempt(GetLastShownTime(prefs) +
  204. GetNudgeInterval(/*is_first_time=*/false));
  205. }
  206. }
  207. void LauncherNudgeController::ScheduleShowNudgeAttempt(
  208. base::Time recheck_time) {
  209. show_nudge_timer_->Start(
  210. FROM_HERE, recheck_time,
  211. base::BindOnce(&LauncherNudgeController::MaybeShowNudge,
  212. weak_ptr_factory_.GetWeakPtr()));
  213. }
  214. void LauncherNudgeController::OnActiveUserPrefServiceChanged(
  215. PrefService* prefs) {
  216. // If the current session is a guest session which is ephemeral and doesn't
  217. // save prefs, return early and don't show nudges for these session types.
  218. if (Shell::Get()
  219. ->session_controller()
  220. ->GetUserSession(0)
  221. ->user_info.is_ephemeral) {
  222. return;
  223. }
  224. if (Shell::Get()->session_controller()->IsUserFirstLogin()) {
  225. // If the current logged in user is a new one, record the first login time
  226. // to know when to show the nudge.
  227. DictionaryPrefUpdate update(prefs, prefs::kShelfLauncherNudge);
  228. update->SetKey(kFirstLoginTime, base::TimeToValue(GetNow()));
  229. } else if (GetFirstLoginTime(prefs).is_null()) {
  230. // For the users that has logged in before the nudge feature is landed, we
  231. // assume the user has opened the launcher before and thus don't show the
  232. // nudge to them.
  233. return;
  234. }
  235. // Set the `earliest_available_time_` according to the current login time and
  236. // check when the nudge could be shown.
  237. earliest_available_time_ = GetNow() + kMinIntervalAfterHomeButtonAppears;
  238. MaybeShowNudge();
  239. }
  240. void LauncherNudgeController::OnAppListVisibilityChanged(bool shown,
  241. int64_t display_id) {
  242. PrefService* prefs = GetPrefs();
  243. if (!prefs)
  244. return;
  245. // App list is shown by default in tablet mode, and does not necessary
  246. // require explicit user action. As a result, don't track app list visibility
  247. // changes in tablet mode as actions affecting nudge availability in clamshell
  248. // mode.
  249. if (Shell::Get()->IsInTabletMode())
  250. return;
  251. if (!WasLauncherShownPreviously(prefs) && shown) {
  252. DictionaryPrefUpdate update(prefs, prefs::kShelfLauncherNudge);
  253. update->SetBoolKey(kWasLauncherShown, true);
  254. }
  255. }
  256. void LauncherNudgeController::OnTabletModeEnded() {
  257. // If a nudge event became available while the device was in tablet mode, it
  258. // would have been ignored. Recheck whether the nudge can be shown again. Note
  259. // that the nudge is designed to be shown after
  260. // `kMinIntervalAfterHomeButtonAppears` amount of time since changing to
  261. // clamshell mode where home button exists.
  262. earliest_available_time_ = GetNow() + kMinIntervalAfterHomeButtonAppears;
  263. MaybeShowNudge();
  264. }
  265. void LauncherNudgeController::OnTabletControllerDestroyed() {
  266. tablet_mode_observation_.Reset();
  267. }
  268. base::Time LauncherNudgeController::GetNow() const {
  269. if (clock_for_test_)
  270. return clock_for_test_->Now();
  271. return base::Time::Now();
  272. }
  273. } // namespace ash