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 
5 #include "android_webview/browser/metrics/visibility_metrics_logger.h"
6 
7 #include "base/metrics/histogram_macros.h"
8 #include "base/no_destructor.h"
9 #include "base/rand_util.h"
10 #include "base/time/time.h"
11 #include "content/public/browser/browser_task_traits.h"
12 #include "content/public/browser/browser_thread.h"
13 
14 using base::NoDestructor;
15 using content::BrowserThread;
16 
17 namespace android_webview {
18 
19 // Have bypassed the usual macros here because they do not support a
20 // means to increment counters by more than 1 per call.
21 base::HistogramBase*
CreateHistogramForDurationTracking(const char * name,int max_value)22 VisibilityMetricsLogger::CreateHistogramForDurationTracking(const char* name,
23                                                             int max_value) {
24   return base::Histogram::FactoryGet(
25       name, 1, max_value + 1, max_value + 2,
26       base::HistogramBase::kUmaTargetedHistogramFlag);
27 }
28 
GetGlobalVisibilityHistogram()29 base::HistogramBase* VisibilityMetricsLogger::GetGlobalVisibilityHistogram() {
30   static NoDestructor<base::HistogramBase*> histogram(
31       CreateHistogramForDurationTracking(
32           "Android.WebView.Visibility.Global",
33           static_cast<int>(VisibilityMetricsLogger::Visibility::kMaxValue)));
34   return *histogram;
35 }
36 
37 base::HistogramBase*
GetPerWebViewVisibilityHistogram()38 VisibilityMetricsLogger::GetPerWebViewVisibilityHistogram() {
39   static NoDestructor<base::HistogramBase*> histogram(
40       CreateHistogramForDurationTracking(
41           "Android.WebView.Visibility.PerWebView",
42           static_cast<int>(VisibilityMetricsLogger::Visibility::kMaxValue)));
43   return *histogram;
44 }
45 
46 base::HistogramBase*
GetGlobalOpenWebVisibilityHistogram()47 VisibilityMetricsLogger::GetGlobalOpenWebVisibilityHistogram() {
48   static NoDestructor<base::HistogramBase*> histogram(
49       CreateHistogramForDurationTracking(
50           "Android.WebView.WebViewOpenWebVisible.Global",
51           static_cast<int>(
52               VisibilityMetricsLogger::WebViewOpenWebVisibility::kMaxValue)));
53   return *histogram;
54 }
55 
56 base::HistogramBase*
GetPerWebViewOpenWebVisibilityHistogram()57 VisibilityMetricsLogger::GetPerWebViewOpenWebVisibilityHistogram() {
58   static NoDestructor<base::HistogramBase*> histogram(
59       CreateHistogramForDurationTracking(
60           "Android.WebView.WebViewOpenWebVisible.PerWebView",
61           static_cast<int>(
62               VisibilityMetricsLogger::WebViewOpenWebVisibility::kMaxValue)));
63   return *histogram;
64 }
65 
66 base::HistogramBase*
GetOpenWebVisibileScreenPortionHistogram()67 VisibilityMetricsLogger::GetOpenWebVisibileScreenPortionHistogram() {
68   static NoDestructor<base::HistogramBase*> histogram(
69       CreateHistogramForDurationTracking(
70           "Android.WebView.WebViewOpenWebVisible.ScreenPortion",
71           static_cast<int>(VisibilityMetricsLogger::
72                                WebViewOpenWebScreenPortion::kMaxValue)));
73   return *histogram;
74 }
75 
VisibilityMetricsLogger()76 VisibilityMetricsLogger::VisibilityMetricsLogger() {
77   DCHECK_CURRENTLY_ON(BrowserThread::UI);
78   last_update_time_ = base::TimeTicks::Now();
79 }
80 
81 VisibilityMetricsLogger::~VisibilityMetricsLogger() = default;
82 
AddClient(Client * client)83 void VisibilityMetricsLogger::AddClient(Client* client) {
84   DCHECK_CURRENTLY_ON(BrowserThread::UI);
85   DCHECK(client_visibility_.find(client) == client_visibility_.end());
86 
87   UpdateDurations(base::TimeTicks::Now());
88 
89   client_visibility_[client] = VisibilityInfo();
90   ProcessClientUpdate(client, client->GetVisibilityInfo());
91 }
92 
RemoveClient(Client * client)93 void VisibilityMetricsLogger::RemoveClient(Client* client) {
94   DCHECK_CURRENTLY_ON(BrowserThread::UI);
95   DCHECK(client_visibility_.find(client) != client_visibility_.end());
96 
97   UpdateDurations(base::TimeTicks::Now());
98 
99   ProcessClientUpdate(client, VisibilityInfo());
100   client_visibility_.erase(client);
101 }
102 
ClientVisibilityChanged(Client * client)103 void VisibilityMetricsLogger::ClientVisibilityChanged(Client* client) {
104   DCHECK_CURRENTLY_ON(BrowserThread::UI);
105   DCHECK(client_visibility_.find(client) != client_visibility_.end());
106 
107   UpdateDurations(base::TimeTicks::Now());
108 
109   ProcessClientUpdate(client, client->GetVisibilityInfo());
110 }
111 
UpdateOpenWebScreenArea(int pixels,int percentage)112 void VisibilityMetricsLogger::UpdateOpenWebScreenArea(int pixels,
113                                                       int percentage) {
114   DCHECK_CURRENTLY_ON(BrowserThread::UI);
115 
116   UpdateDurations(base::TimeTicks::Now());
117 
118   open_web_screen_area_pixels_ = pixels;
119   open_web_screen_area_percentage_ = percentage;
120 
121   DCHECK(percentage >= 0);
122   DCHECK(percentage <= 100);
123   current_open_web_screen_portion_ =
124       static_cast<VisibilityMetricsLogger::WebViewOpenWebScreenPortion>(
125           percentage / 10);
126 }
127 
UpdateDurations(base::TimeTicks update_time)128 void VisibilityMetricsLogger::UpdateDurations(base::TimeTicks update_time) {
129   base::TimeDelta delta = update_time - last_update_time_;
130   if (visible_client_count_ > 0) {
131     visible_duration_tracker_.any_webview_tracked_duration_ += delta;
132   } else {
133     visible_duration_tracker_.no_webview_tracked_duration_ += delta;
134   }
135 
136   if (visible_webcontent_client_count_ > 0) {
137     webcontent_visible_tracker_.any_webview_tracked_duration_ += delta;
138   } else {
139     webcontent_visible_tracker_.no_webview_tracked_duration_ += delta;
140   }
141 
142   visible_duration_tracker_.per_webview_duration_ +=
143       delta * visible_client_count_;
144   visible_duration_tracker_.per_webview_untracked_duration_ +=
145       delta * (client_visibility_.size() - visible_client_count_);
146 
147   webcontent_visible_tracker_.per_webview_duration_ +=
148       delta * visible_webcontent_client_count_;
149   webcontent_visible_tracker_.per_webview_untracked_duration_ +=
150       delta * (client_visibility_.size() - visible_webcontent_client_count_);
151 
152   open_web_screen_portion_tracked_duration_[static_cast<int>(
153       current_open_web_screen_portion_)] += delta;
154 
155   last_update_time_ = update_time;
156 }
157 
IsVisible() const158 bool VisibilityMetricsLogger::VisibilityInfo::IsVisible() const {
159   return view_attached && view_visible && window_visible;
160 }
161 
ContainsOpenWebContent() const162 bool VisibilityMetricsLogger::VisibilityInfo::ContainsOpenWebContent() const {
163   return scheme_http_or_https;
164 }
165 
IsDisplayingOpenWebContent() const166 bool VisibilityMetricsLogger::VisibilityInfo::IsDisplayingOpenWebContent()
167     const {
168   return IsVisible() && ContainsOpenWebContent();
169 }
170 
ProcessClientUpdate(Client * client,const VisibilityInfo & info)171 void VisibilityMetricsLogger::ProcessClientUpdate(Client* client,
172                                                   const VisibilityInfo& info) {
173   VisibilityInfo curr_info = client_visibility_[client];
174   bool was_visible = curr_info.IsVisible();
175   bool is_visible = info.IsVisible();
176   bool was_visible_web = curr_info.IsDisplayingOpenWebContent();
177   bool is_visible_web = info.IsDisplayingOpenWebContent();
178   client_visibility_[client] = info;
179   DCHECK(!was_visible || visible_client_count_ > 0);
180 
181   bool any_client_was_visible = visible_client_count_ > 0;
182 
183   if (!was_visible && is_visible) {
184     ++visible_client_count_;
185   } else if (was_visible && !is_visible) {
186     --visible_client_count_;
187   }
188 
189   if (!was_visible_web && is_visible_web) {
190     ++visible_webcontent_client_count_;
191   } else if (was_visible_web && !is_visible_web) {
192     --visible_webcontent_client_count_;
193   }
194 
195   bool any_client_is_visible = visible_client_count_ > 0;
196   if (on_visibility_changed_callback_ &&
197       any_client_was_visible != any_client_is_visible) {
198     on_visibility_changed_callback_.Run(any_client_is_visible);
199   }
200 }
201 
SetOnVisibilityChangedCallback(OnVisibilityChangedCallback callback)202 void VisibilityMetricsLogger::SetOnVisibilityChangedCallback(
203     OnVisibilityChangedCallback callback) {
204   DCHECK_CURRENTLY_ON(BrowserThread::UI);
205   on_visibility_changed_callback_ = std::move(callback);
206 }
207 
RecordMetrics()208 void VisibilityMetricsLogger::RecordMetrics() {
209   DCHECK_CURRENTLY_ON(BrowserThread::UI);
210   UpdateDurations(base::TimeTicks::Now());
211   RecordVisibilityMetrics();
212   RecordOpenWebDisplayMetrics();
213   RecordScreenPortionMetrics();
214 }
215 
RecordVisibilityMetrics()216 void VisibilityMetricsLogger::RecordVisibilityMetrics() {
217   int32_t any_webview_visible_seconds;
218   int32_t no_webview_visible_seconds;
219   int32_t total_webview_visible_seconds;
220   int32_t total_no_webview_visible_seconds;
221 
222   any_webview_visible_seconds =
223       visible_duration_tracker_.any_webview_tracked_duration_.InSeconds();
224   visible_duration_tracker_.any_webview_tracked_duration_ -=
225       base::TimeDelta::FromSeconds(any_webview_visible_seconds);
226   no_webview_visible_seconds =
227       visible_duration_tracker_.no_webview_tracked_duration_.InSeconds();
228   visible_duration_tracker_.no_webview_tracked_duration_ -=
229       base::TimeDelta::FromSeconds(no_webview_visible_seconds);
230 
231   total_webview_visible_seconds =
232       visible_duration_tracker_.per_webview_duration_.InSeconds();
233   visible_duration_tracker_.per_webview_duration_ -=
234       base::TimeDelta::FromSeconds(total_webview_visible_seconds);
235   total_no_webview_visible_seconds =
236       visible_duration_tracker_.per_webview_untracked_duration_.InSeconds();
237   visible_duration_tracker_.per_webview_untracked_duration_ -=
238       base::TimeDelta::FromSeconds(total_no_webview_visible_seconds);
239 
240   if (any_webview_visible_seconds) {
241     GetGlobalVisibilityHistogram()->AddCount(
242         static_cast<int>(Visibility::kVisible), any_webview_visible_seconds);
243   }
244   if (no_webview_visible_seconds) {
245     GetGlobalVisibilityHistogram()->AddCount(
246         static_cast<int>(Visibility::kNotVisible), no_webview_visible_seconds);
247   }
248 
249   if (total_webview_visible_seconds) {
250     GetPerWebViewVisibilityHistogram()->AddCount(
251         static_cast<int>(Visibility::kVisible), total_webview_visible_seconds);
252   }
253   if (total_no_webview_visible_seconds) {
254     GetPerWebViewVisibilityHistogram()->AddCount(
255         static_cast<int>(Visibility::kNotVisible),
256         total_no_webview_visible_seconds);
257   }
258 }
259 
RecordOpenWebDisplayMetrics()260 void VisibilityMetricsLogger::RecordOpenWebDisplayMetrics() {
261   int32_t any_webcontent_visible_seconds;
262   int32_t no_webcontent_visible_seconds;
263   int32_t total_webcontent_isible_seconds;
264   int32_t total_not_webcontent_or_not_visible_seconds;
265 
266   any_webcontent_visible_seconds =
267       webcontent_visible_tracker_.any_webview_tracked_duration_.InSeconds();
268   webcontent_visible_tracker_.any_webview_tracked_duration_ -=
269       base::TimeDelta::FromSeconds(any_webcontent_visible_seconds);
270   no_webcontent_visible_seconds =
271       webcontent_visible_tracker_.no_webview_tracked_duration_.InSeconds();
272   webcontent_visible_tracker_.no_webview_tracked_duration_ -=
273       base::TimeDelta::FromSeconds(no_webcontent_visible_seconds);
274 
275   total_webcontent_isible_seconds =
276       webcontent_visible_tracker_.per_webview_duration_.InSeconds();
277   webcontent_visible_tracker_.per_webview_duration_ -=
278       base::TimeDelta::FromSeconds(total_webcontent_isible_seconds);
279   total_not_webcontent_or_not_visible_seconds =
280       webcontent_visible_tracker_.per_webview_untracked_duration_.InSeconds();
281   webcontent_visible_tracker_.per_webview_untracked_duration_ -=
282       base::TimeDelta::FromSeconds(total_not_webcontent_or_not_visible_seconds);
283 
284   if (any_webcontent_visible_seconds) {
285     GetGlobalOpenWebVisibilityHistogram()->AddCount(
286         static_cast<int>(WebViewOpenWebVisibility::kDisplayOpenWebContent),
287         any_webcontent_visible_seconds);
288   }
289   if (no_webcontent_visible_seconds) {
290     GetGlobalOpenWebVisibilityHistogram()->AddCount(
291         static_cast<int>(WebViewOpenWebVisibility::kNotDisplayOpenWebContent),
292         no_webcontent_visible_seconds);
293   }
294 
295   if (total_webcontent_isible_seconds) {
296     GetPerWebViewOpenWebVisibilityHistogram()->AddCount(
297         static_cast<int>(WebViewOpenWebVisibility::kDisplayOpenWebContent),
298         total_webcontent_isible_seconds);
299   }
300   if (total_not_webcontent_or_not_visible_seconds) {
301     GetPerWebViewOpenWebVisibilityHistogram()->AddCount(
302         static_cast<int>(WebViewOpenWebVisibility::kNotDisplayOpenWebContent),
303         total_not_webcontent_or_not_visible_seconds);
304   }
305 }
306 
RecordScreenPortionMetrics()307 void VisibilityMetricsLogger::RecordScreenPortionMetrics() {
308   for (int i = 0; i < static_cast<int>(WebViewOpenWebScreenPortion::kMaxValue);
309        i++) {
310     int32_t elapsed_seconds =
311         open_web_screen_portion_tracked_duration_[i].InSeconds();
312     if (elapsed_seconds == 0)
313       continue;
314 
315     open_web_screen_portion_tracked_duration_[i] -=
316         base::TimeDelta::FromSeconds(elapsed_seconds);
317     GetOpenWebVisibileScreenPortionHistogram()->AddCount(i, elapsed_seconds);
318   }
319 }
320 
321 }  // namespace android_webview