forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
contextual_tooltip.cc
291 lines (244 loc) · 9.81 KB
/
contextual_tooltip.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// 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/shelf/contextual_tooltip.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/contextual_nudge_status_tracker.h"
#include "ash/shell.h"
#include "base/json/values_util.h"
#include "base/no_destructor.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "components/prefs/scoped_user_pref_update.h"
namespace ash {
namespace contextual_tooltip {
namespace {
// Keys for tooltip sub-preferences for shown count and last time shown.
constexpr char kShownCount[] = "shown_count";
constexpr char kLastTimeShown[] = "last_time_shown";
// Keys for tooltip sub-preferences of how many times a gesture has been
// successfully performed by the user.
constexpr char kSuccessCount[] = "success_count";
// Whether the drag handle nudge cannot be shown because the shelf is currently
// hidden - used to unblock showing back gesture when shelf is hidden (the back
// gesture will normally show only if the drag handle nudge has already been
// shown within the last nudge show interval).
bool g_drag_handle_nudge_disabled_for_hidden_shelf = false;
// Whether the back gesture nudge is currently being shown.
bool g_back_gesture_nudge_showing = false;
base::Clock* g_clock_override = nullptr;
base::Time GetTime() {
if (g_clock_override)
return g_clock_override->Now();
return base::Time::Now();
}
std::string TooltipTypeToString(TooltipType type) {
switch (type) {
case TooltipType::kBackGesture:
return "back_gesture";
case TooltipType::kHomeToOverview:
return "home_to_overview";
case TooltipType::kInAppToHome:
return "in_app_to_home";
}
return "invalid";
}
// Creates the path to the dictionary value from the contextual tooltip type and
// the sub-preference.
std::string GetPath(TooltipType type, const std::string& sub_pref) {
return base::JoinString({TooltipTypeToString(type), sub_pref}, ".");
}
base::Time GetLastShownTime(PrefService* prefs, TooltipType type) {
const base::Value* last_shown_time =
prefs->GetDictionary(prefs::kContextualTooltips)
->FindPath(GetPath(type, kLastTimeShown));
if (!last_shown_time)
return base::Time();
return *base::ValueToTime(last_shown_time);
}
int GetSuccessCount(PrefService* prefs, TooltipType type) {
absl::optional<int> success_count =
prefs->GetDictionary(prefs::kContextualTooltips)
->FindIntPath(GetPath(type, kSuccessCount));
return success_count.value_or(0);
}
const absl::optional<base::TimeDelta>& GetMinIntervalOverride() {
// Overridden minimum time between showing contextual nudges to the user.
static absl::optional<base::TimeDelta> min_interval_override;
if (!min_interval_override) {
min_interval_override = switches::ContextualNudgesInterval();
}
return min_interval_override;
}
using TrackerTable =
std::map<TooltipType, std::unique_ptr<ContextualNudgeStatusTracker>>;
TrackerTable& GetStatusTrackerTable() {
// Dictionary mapping each nudge to its status tracker.
static base::NoDestructor<TrackerTable> status_tracker_table;
return *status_tracker_table;
}
ContextualNudgeStatusTracker* GetStatusTracker(TooltipType type) {
if (GetStatusTrackerTable().find(type) == GetStatusTrackerTable().end()) {
GetStatusTrackerTable().insert(TrackerTable::value_type(
type, std::make_unique<ContextualNudgeStatusTracker>(type)));
}
return GetStatusTrackerTable().find(type)->second.get();
}
} // namespace
void RegisterProfilePrefs(PrefRegistrySimple* registry) {
if (features::AreContextualNudgesEnabled())
registry->RegisterDictionaryPref(prefs::kContextualTooltips);
}
bool ShouldShowNudge(PrefService* prefs,
TooltipType type,
base::TimeDelta* recheck_delay) {
auto set_recheck_delay = [&recheck_delay](base::TimeDelta delay) {
if (recheck_delay)
*recheck_delay = delay;
};
if (!features::AreContextualNudgesEnabled()) {
set_recheck_delay(base::TimeDelta());
return false;
}
if (type == TooltipType::kInAppToHome &&
g_drag_handle_nudge_disabled_for_hidden_shelf) {
set_recheck_delay(base::TimeDelta());
return false;
}
const int success_count = GetSuccessCount(prefs, type);
if ((type == TooltipType::kHomeToOverview &&
success_count >= kSuccessLimitHomeToOverview) ||
(type == TooltipType::kBackGesture &&
success_count >= kSuccessLimitBackGesture) ||
(type == TooltipType::kInAppToHome &&
success_count >= kSuccessLimitInAppToHome)) {
set_recheck_delay(base::TimeDelta());
return false;
}
const int shown_count = GetShownCount(prefs, type);
if (shown_count >= kNotificationLimit) {
set_recheck_delay(base::TimeDelta());
return false;
}
// Before showing back gesture nudge, do not show it if in-app to shelf nudge
// should be shown (to prevent two nudges from showing up at the same time).
// Verify that the in-app to home nudge was shown within the last show
// interval.
if (type == TooltipType::kBackGesture) {
if (!g_drag_handle_nudge_disabled_for_hidden_shelf &&
ShouldShowNudge(prefs, TooltipType::kInAppToHome, nullptr)) {
set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge);
return false;
}
// Verify that drag handle nudge has been shown at least a minute ago.
const base::Time drag_handle_nudge_last_shown_time =
GetLastShownTime(prefs, TooltipType::kInAppToHome);
if (!drag_handle_nudge_last_shown_time.is_null()) {
const base::TimeDelta time_since_drag_handle_nudge =
GetTime() - drag_handle_nudge_last_shown_time;
if (time_since_drag_handle_nudge <
kMinIntervalBetweenBackAndDragHandleNudge) {
set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge -
time_since_drag_handle_nudge);
return false;
}
}
}
// Make sure that drag handle nudge is not shown within a minute of back
// gesture nudge.
if (type == TooltipType::kInAppToHome) {
if (g_back_gesture_nudge_showing) {
set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge);
return false;
}
const base::Time back_nudge_last_shown_time =
GetLastShownTime(prefs, TooltipType::kBackGesture);
if (!back_nudge_last_shown_time.is_null()) {
const base::TimeDelta time_since_back_nudge =
GetTime() - back_nudge_last_shown_time;
if (time_since_back_nudge < kMinIntervalBetweenBackAndDragHandleNudge) {
set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge -
time_since_back_nudge);
return false;
}
}
}
if (shown_count == 0)
return true;
const base::Time last_shown_time = GetLastShownTime(prefs, type);
const base::TimeDelta min_interval =
GetMinIntervalOverride().value_or(kMinInterval);
const base::TimeDelta time_since_last_nudge = GetTime() - last_shown_time;
if (time_since_last_nudge < min_interval) {
set_recheck_delay(min_interval - time_since_last_nudge);
return false;
}
return true;
}
base::TimeDelta GetNudgeTimeout(PrefService* prefs, TooltipType type) {
const int shown_count = GetShownCount(prefs, type);
if (shown_count == 0)
return base::TimeDelta();
return kNudgeShowDuration;
}
int GetShownCount(PrefService* prefs, TooltipType type) {
absl::optional<int> shown_count =
prefs->GetDictionary(prefs::kContextualTooltips)
->FindIntPath(GetPath(type, kShownCount));
return shown_count.value_or(0);
}
void HandleNudgeShown(PrefService* prefs, TooltipType type) {
const int shown_count = GetShownCount(prefs, type);
DictionaryPrefUpdate update(prefs, prefs::kContextualTooltips);
update->SetIntPath(GetPath(type, kShownCount), shown_count + 1);
update->SetPath(GetPath(type, kLastTimeShown), base::TimeToValue(GetTime()));
GetStatusTracker(type)->HandleNudgeShown(base::TimeTicks::Now());
}
void HandleGesturePerformed(PrefService* prefs, TooltipType type) {
const int success_count = GetSuccessCount(prefs, type);
DictionaryPrefUpdate update(prefs, prefs::kContextualTooltips);
update->SetIntPath(GetPath(type, kSuccessCount), success_count + 1);
GetStatusTracker(type)->HandleGesturePerformed(base::TimeTicks::Now());
}
void MaybeLogNudgeDismissedMetrics(TooltipType type,
DismissNudgeReason reason) {
GetStatusTracker(type)->MaybeLogNudgeDismissedMetrics(reason);
}
void SetDragHandleNudgeDisabledForHiddenShelf(bool nudge_disabled) {
g_drag_handle_nudge_disabled_for_hidden_shelf = nudge_disabled;
}
void SetBackGestureNudgeShowing(bool showing) {
g_back_gesture_nudge_showing = showing;
}
void ClearPrefs() {
DCHECK(Shell::Get()->session_controller()->GetLastActiveUserPrefService());
DictionaryPrefUpdate update(
Shell::Get()->session_controller()->GetLastActiveUserPrefService(),
prefs::kContextualTooltips);
base::DictionaryValue* nudges_dict = update.Get();
if (nudges_dict && !nudges_dict->DictEmpty())
nudges_dict->DictClear();
}
void OverrideClockForTesting(base::Clock* test_clock) {
DCHECK(!g_clock_override);
g_clock_override = test_clock;
}
void ClearClockOverrideForTesting() {
DCHECK(g_clock_override);
g_clock_override = nullptr;
}
void ClearStatusTrackerTableForTesting() {
GetStatusTrackerTable().clear();
}
ASH_EXPORT bool CanRecordGesturePerformedMetricForTesting(TooltipType type) {
return !GetStatusTracker(type)->gesture_time_recorded();
}
ASH_EXPORT bool CanRecordNudgeHiddenMetricForTesting(TooltipType type) {
return GetStatusTracker(type)->can_record_dismiss_metrics();
}
} // namespace contextual_tooltip
} // namespace ash