app_list_nudge_controller.cc 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. // Copyright 2022 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/app_list/views/app_list_nudge_controller.h"
  5. #include <memory>
  6. #include <string>
  7. #include "ash/constants/ash_pref_names.h"
  8. #include "ash/constants/ash_switches.h"
  9. #include "ash/public/cpp/app_list/app_list_types.h"
  10. #include "ash/resources/vector_icons/vector_icons.h"
  11. #include "ash/session/session_controller_impl.h"
  12. #include "ash/shell.h"
  13. #include "base/json/values_util.h"
  14. #include "base/time/time.h"
  15. #include "components/prefs/pref_registry_simple.h"
  16. #include "components/prefs/pref_service.h"
  17. #include "components/prefs/scoped_user_pref_update.h"
  18. namespace ash {
  19. namespace {
  20. // Flags to enable/disable the app list nudges for test.
  21. bool g_reorder_nudge_disabled_for_test = false;
  22. bool g_privacy_notice_accepted_for_test = false;
  23. // Reorder nudge dictionary pref keys.
  24. constexpr char kReorderNudgeShownCount[] = "shown_count";
  25. constexpr char kReorderNudgeConfirmed[] = "confirmed";
  26. // Privacy notice dictionary pref keys.
  27. const char kPrivacyNoticeAcceptedKey[] = "accepted";
  28. const char kPrivacyNoticeShownKey[] = "shown";
  29. // Maximum number of times that the nudge is showing to users.
  30. constexpr int kMaxShowCount = 3;
  31. // Returns the last active user pref service.
  32. PrefService* GetPrefs() {
  33. if (!Shell::HasInstance())
  34. return nullptr;
  35. return Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  36. }
  37. // Returns the preference path string that corresponds to nudge type `type`.
  38. std::string GetPrefPath(AppListNudgeController::NudgeType type) {
  39. switch (type) {
  40. case AppListNudgeController::NudgeType::kReorderNudge:
  41. return prefs::kAppListReorderNudge;
  42. default:
  43. NOTREACHED();
  44. return "";
  45. }
  46. }
  47. // Returns true if the app list has been reordered before.
  48. bool WasAppListReorderedPreviously(PrefService* prefs) {
  49. const base::Value::Dict& dictionary =
  50. prefs->GetValueDict(prefs::kAppListReorderNudge);
  51. return dictionary.FindBool(kReorderNudgeConfirmed).value_or(false);
  52. }
  53. } // namespace
  54. AppListNudgeController::AppListNudgeController() = default;
  55. // static
  56. void AppListNudgeController::RegisterProfilePrefs(
  57. PrefRegistrySimple* registry) {
  58. registry->RegisterDictionaryPref(prefs::kAppListReorderNudge);
  59. registry->RegisterDictionaryPref(prefs::kLauncherFilesPrivacyNotice);
  60. }
  61. // static
  62. void AppListNudgeController::ResetPrefsForNewUserSession(PrefService* prefs) {
  63. prefs->ClearPref(prefs::kAppListReorderNudge);
  64. prefs->ClearPref(prefs::kLauncherFilesPrivacyNotice);
  65. }
  66. // static
  67. int AppListNudgeController::GetShownCount(PrefService* prefs, NudgeType type) {
  68. const base::Value::Dict& dictionary = prefs->GetValueDict(GetPrefPath(type));
  69. return dictionary.FindIntByDottedPath(kReorderNudgeShownCount).value_or(0);
  70. }
  71. // static
  72. void AppListNudgeController::SetReorderNudgeDisabledForTest(bool is_disabled) {
  73. g_reorder_nudge_disabled_for_test = is_disabled;
  74. }
  75. // static
  76. void AppListNudgeController::SetPrivacyNoticeAcceptedForTest(bool is_disabled) {
  77. g_privacy_notice_accepted_for_test = is_disabled;
  78. }
  79. bool AppListNudgeController::ShouldShowReorderNudge() const {
  80. if (g_reorder_nudge_disabled_for_test)
  81. return false;
  82. PrefService* prefs = GetPrefs();
  83. if (!prefs)
  84. return false;
  85. // Don't show the reorder nudge if the privacy notice is showing.
  86. if (current_nudge_ == NudgeType::kPrivacyNotice)
  87. return false;
  88. if (GetShownCount(prefs, NudgeType::kReorderNudge) < kMaxShowCount &&
  89. !WasAppListReorderedPreviously(prefs)) {
  90. return true;
  91. }
  92. return false;
  93. }
  94. void AppListNudgeController::OnTemporarySortOrderChanged(
  95. const absl::optional<AppListSortOrder>& new_order) {
  96. PrefService* prefs = GetPrefs();
  97. if (!prefs)
  98. return;
  99. // Record the reorder action so that the nudge view won't be showing anymore.
  100. DictionaryPrefUpdate update(prefs, prefs::kAppListReorderNudge);
  101. update->SetBoolPath(kReorderNudgeConfirmed, true);
  102. }
  103. void AppListNudgeController::SetPrivacyNoticeAcceptedPref(bool accepted) {
  104. PrefService* prefs = GetPrefs();
  105. if (!prefs)
  106. return;
  107. {
  108. DictionaryPrefUpdate privacy_pref_update(
  109. prefs, prefs::kLauncherFilesPrivacyNotice);
  110. privacy_pref_update->SetBoolKey(kPrivacyNoticeAcceptedKey, accepted);
  111. }
  112. }
  113. void AppListNudgeController::SetPrivacyNoticeShownPref(bool shown) {
  114. PrefService* prefs = GetPrefs();
  115. if (!prefs)
  116. return;
  117. DictionaryPrefUpdate privacy_pref_update(prefs,
  118. prefs::kLauncherFilesPrivacyNotice);
  119. privacy_pref_update->SetBoolKey(kPrivacyNoticeShownKey, shown);
  120. }
  121. bool AppListNudgeController::IsPrivacyNoticeAccepted() const {
  122. if (g_privacy_notice_accepted_for_test)
  123. return true;
  124. const PrefService* prefs = GetPrefs();
  125. if (!prefs)
  126. return false;
  127. return prefs->GetValueDict(prefs::kLauncherFilesPrivacyNotice)
  128. .FindBool(kPrivacyNoticeAcceptedKey)
  129. .value_or(false);
  130. }
  131. bool AppListNudgeController::WasPrivacyNoticeShown() const {
  132. const PrefService* prefs = GetPrefs();
  133. if (!prefs)
  134. return false;
  135. return prefs->GetValueDict(prefs::kLauncherFilesPrivacyNotice)
  136. .FindBool(kPrivacyNoticeShownKey)
  137. .value_or(false);
  138. }
  139. void AppListNudgeController::SetPrivacyNoticeShown(bool shown) {
  140. DCHECK(current_nudge_ != NudgeType::kReorderNudge);
  141. current_nudge_ = shown ? NudgeType::kPrivacyNotice : NudgeType::kNone;
  142. }
  143. void AppListNudgeController::SetNudgeVisible(bool is_nudge_visible,
  144. NudgeType type) {
  145. // Do not update the state and prefs if it didn't change.
  146. if (is_visible_ == is_nudge_visible && is_active_ == is_nudge_visible &&
  147. current_nudge_ == type) {
  148. return;
  149. }
  150. // All NudgeType transition must start from or end to kNone to make sure the
  151. // prefs is correctly recorded.
  152. DCHECK(current_nudge_ == NudgeType::kNone || type == NudgeType::kNone ||
  153. current_nudge_ == type);
  154. const bool is_visible_updated = is_visible_ != is_nudge_visible;
  155. const bool is_active_updated = is_active_ != is_nudge_visible;
  156. is_visible_ = is_nudge_visible;
  157. // `is_active_` should be updated along with `is_visible_` if visibility
  158. // updates.
  159. is_active_ = is_visible_;
  160. current_nudge_ = type;
  161. UpdateCurrentNudgeStateInPrefs(is_visible_updated, is_active_updated);
  162. }
  163. void AppListNudgeController::SetNudgeActive(bool is_nudge_active,
  164. NudgeType type) {
  165. // Do not update the state and prefs if it didn't change.
  166. if (is_active_ == is_nudge_active && current_nudge_ == type)
  167. return;
  168. // All NudgeType transition must start from or end to kNone to make sure the
  169. // prefs is correctly recorded.
  170. DCHECK(current_nudge_ == NudgeType::kNone || type == NudgeType::kNone ||
  171. current_nudge_ == type);
  172. // The nudge must be visible to change its active state.
  173. DCHECK(is_visible_);
  174. const bool is_active_updated = is_active_ != is_nudge_active;
  175. current_nudge_ = type;
  176. is_active_ = is_nudge_active;
  177. UpdateCurrentNudgeStateInPrefs(false, is_active_updated);
  178. }
  179. void AppListNudgeController::OnReorderNudgeConfirmed() {
  180. PrefService* prefs = GetPrefs();
  181. if (!prefs)
  182. return;
  183. // Record the nudge as confirmed so that it will not show up again.
  184. DictionaryPrefUpdate update(prefs, prefs::kAppListReorderNudge);
  185. update->SetBoolPath(kReorderNudgeConfirmed, true);
  186. }
  187. void AppListNudgeController::UpdateCurrentNudgeStateInPrefs(
  188. bool is_visible_updated,
  189. bool is_active_updated) {
  190. PrefService* prefs = GetPrefs();
  191. if (!prefs)
  192. return;
  193. // Handle the case where the nudge is active to the users.
  194. if (is_active_) {
  195. switch (current_nudge_) {
  196. case NudgeType::kReorderNudge: {
  197. // Only reset the timer if the `is_active_` state is updated.
  198. if (is_active_updated)
  199. current_nudge_show_timestamp_ = base::Time::Now();
  200. break;
  201. }
  202. case NudgeType::kPrivacyNotice:
  203. case NudgeType::kNone:
  204. break;
  205. }
  206. return;
  207. }
  208. // Handle the case where the nudge is not active to the users.
  209. switch (current_nudge_) {
  210. case NudgeType::kReorderNudge: {
  211. DictionaryPrefUpdate update(prefs, prefs::kAppListReorderNudge);
  212. base::TimeDelta shown_duration =
  213. base::Time::Now() - current_nudge_show_timestamp_;
  214. // Caches that the nudge is considered as shown if:
  215. // 1. the time threshold is skipped; or
  216. // 2. the time delta of showing the nudge is long enough.
  217. if (ash::switches::IsSkipRecorderNudgeShowThresholdDurationEnabled() ||
  218. shown_duration >= base::Seconds(1)) {
  219. is_nudge_considered_as_shown_ = true;
  220. }
  221. // Update the number of times that the reorder nudge was
  222. // shown to users if the visibility updates.
  223. if (is_visible_updated) {
  224. MaybeIncrementShownCountInPrefs(update, shown_duration);
  225. is_nudge_considered_as_shown_ = false;
  226. }
  227. } break;
  228. case NudgeType::kPrivacyNotice:
  229. case NudgeType::kNone:
  230. break;
  231. }
  232. }
  233. void AppListNudgeController::MaybeIncrementShownCountInPrefs(
  234. DictionaryPrefUpdate& update,
  235. base::TimeDelta duration) {
  236. // Only increment the shown count if the nudge changed to invisible state and
  237. // the nudge was shown long enough to the user before the nudge became
  238. // invisible. Note that if the nudge is inactive but visible, it doesn't count
  239. // as showing once to the user.
  240. if (!is_visible_ && is_nudge_considered_as_shown_) {
  241. update->SetIntPath(kReorderNudgeShownCount,
  242. GetShownCount(GetPrefs(), NudgeType::kReorderNudge) + 1);
  243. }
  244. }
  245. } // namespace ash