blob: 9638387104064bc30633e42ef8400fa245a1c67d [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/glic/glic_metrics.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/time/time.h"
#include "chrome/browser/background/glic/glic_launcher_configuration.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/glic/fre/glic_fre_controller.h"
#include "chrome/browser/glic/glic_pref_names.h"
#include "chrome/browser/glic/public/context/glic_sharing_manager.h"
#include "chrome/browser/glic/public/glic_enabling.h"
#include "chrome/browser/glic/widget/browser_conditions.h"
#include "chrome/browser/glic/widget/glic_window_controller.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/browser_window/public/desktop_browser_window_capabilities.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/common/chrome_features.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/base_window.h"
#include "ui/views/widget/widget.h"
namespace glic {
namespace {
bool CheckFreStatus(Profile* profile, prefs::FreStatus status) {
return profile->GetPrefs()->GetInteger(prefs::kGlicCompletedFre) ==
static_cast<int>(status);
}
class DelegateImpl : public GlicMetrics::Delegate {
public:
explicit DelegateImpl(GlicWindowController* window_controller,
GlicSharingManager* sharing_manager,
PrefService* pref_service)
: window_controller_(window_controller),
sharing_manager_(sharing_manager),
pref_service_(pref_service) {}
gfx::Size GetWindowSize() const override {
return window_controller_->GetSize();
}
bool IsWindowShowing() const override {
return window_controller_->IsShowing();
}
bool IsWindowAttached() const override {
return window_controller_->IsAttached();
}
content::WebContents* GetContents() override {
FocusedTabData ftd = sharing_manager_->GetFocusedTabData();
return ftd.is_focus() ? ftd.focus()->GetContents() : nullptr;
}
ActiveTabSharingState GetActiveTabSharingState() override {
if (!pref_service_->GetBoolean(prefs::kGlicTabContextEnabled)) {
return ActiveTabSharingState::kTabContextPermissionNotGranted;
}
FocusedTabData ftd = sharing_manager_->GetFocusedTabData();
if (ftd.is_focus()) {
return ActiveTabSharingState::kActiveTabIsShared;
} else if (ftd.unfocused_tab()) {
return ActiveTabSharingState::kCannotShareActiveTab;
}
return ActiveTabSharingState::kNoTabCanBeShared;
}
int32_t GetNumPinnedTabs() const override {
return sharing_manager_->GetNumPinnedTabs();
}
private:
raw_ptr<GlicWindowController> window_controller_;
raw_ptr<GlicSharingManager> sharing_manager_;
raw_ptr<PrefService> pref_service_;
};
constexpr char kHistogramGlicPanelPresentationTime[] =
"Glic.PanelPresentationTime2";
constexpr static base::TimeDelta kLogSizeMetricsDelay = base::Minutes(3);
enum class ModeOffset : int {
kTextAttached = 1,
kAudioAttached = 2,
kTextDetached = 3,
kAudioDetached = 4,
kMaxValue = kAudioDetached,
};
ResponseSegmentation GetResponseSegmentation(bool attached,
mojom::WebClientMode mode,
mojom::InvocationSource source) {
if (mode == mojom::WebClientMode::kUnknown) {
return ResponseSegmentation::kUnknown;
}
ModeOffset modeOffset;
if (mode == mojom::WebClientMode::kText && attached) {
modeOffset = ModeOffset::kTextAttached;
} else if (mode == mojom::WebClientMode::kAudio && attached) {
modeOffset = ModeOffset::kAudioAttached;
} else if (mode == mojom::WebClientMode::kText && !attached) {
modeOffset = ModeOffset::kTextDetached;
} else {
modeOffset = ModeOffset::kAudioDetached;
}
int baseIndex =
static_cast<int>(source) * (static_cast<int>(ModeOffset::kMaxValue));
int offset = static_cast<int>(modeOffset);
return static_cast<ResponseSegmentation>(baseIndex + offset);
}
} // namespace
namespace internal {
// LINT.IfChange(BrowserActiveState)
// This must match enums.xml.
enum class BrowserActiveState {
// A browser window is currently active, or was active less than one second
// ago. This 1 second allowance helps ignore differences in window activation
// timing for different platforms.
kBrowserActive = 0,
// A browser window is not active, but was active within the last N seconds,
// and is still visible.
kBrowserRecentlyActive1to5s = 1,
kBrowserRecentlyActive5to10s = 2,
kBrowserRecentlyActive10to30s = 3,
// No browser windows are active or have been active within the last 10
// seconds, but a browser window is still visible.
kBrowserInactive = 4,
// No browser windows are visible.
kBrowserHidden = 5,
kMaxValue = kBrowserHidden,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicBrowserActiveState)
// Computes BrowserActiveState.
class BrowserActivityObserver : public BrowserListObserver {
public:
BrowserActivityObserver() { BrowserList::AddObserver(this); }
~BrowserActivityObserver() override { BrowserList::RemoveObserver(this); }
BrowserActiveState GetBrowserActiveState() const {
if (active_browser_) {
return BrowserActiveState::kBrowserActive;
}
bool browser_hidden = true;
for (Browser* browser : *BrowserList::GetInstance()) {
if (!browser->GetWindow()->IsMinimized() &&
browser->capabilities()->IsVisibleOnScreen() &&
browser->GetWindow()->IsVisible()) {
browser_hidden = false;
break;
}
}
if (browser_hidden) {
return BrowserActiveState::kBrowserHidden;
}
if (last_browser_active_time_) {
auto time_since_active =
base::TimeTicks::Now() - *last_browser_active_time_;
if (time_since_active < base::Seconds(1)) {
return BrowserActiveState::kBrowserActive;
} else if (time_since_active < base::Seconds(5)) {
return BrowserActiveState::kBrowserRecentlyActive1to5s;
} else if (time_since_active < base::Seconds(10)) {
return BrowserActiveState::kBrowserRecentlyActive5to10s;
} else if (time_since_active < base::Seconds(30)) {
return BrowserActiveState::kBrowserRecentlyActive10to30s;
}
}
return BrowserActiveState::kBrowserInactive;
}
// BrowserListObserver impl.
void OnBrowserRemoved(Browser* browser) override {
if (active_browser_ == browser) {
active_browser_ = nullptr;
}
}
void OnBrowserSetLastActive(Browser* browser) override {
active_browser_ = browser;
last_browser_active_time_ = std::nullopt;
}
void OnBrowserNoLongerActive(Browser* browser) override {
if (active_browser_ == browser) {
active_browser_ = nullptr;
}
if (!active_browser_) {
last_browser_active_time_ = base::TimeTicks::Now();
}
}
private:
// The active browser, or null if none is active.
raw_ptr<Browser> active_browser_ = nullptr;
// If the browser is not active, the time at which it was last active.
std::optional<base::TimeTicks> last_browser_active_time_;
};
} // namespace internal
GlicMetrics::GlicMetrics(Profile* profile, GlicEnabling* enabling)
: profile_(profile),
enabling_(enabling),
browser_activity_observer_(
std::make_unique<internal::BrowserActivityObserver>()) {
impression_timer_.Start(
FROM_HERE, base::Minutes(15),
base::BindRepeating(&GlicMetrics::OnImpressionTimerFired,
base::Unretained(this)));
subscriptions_.push_back(
enabling_->RegisterAllowedChanged(base::BindRepeating(
&GlicMetrics::OnMaybeEnabledAndConsentForProfileChanged,
base::Unretained(this))));
is_enabled_ = enabling_->IsEnabledAndConsentForProfile(profile_);
is_pinned_ = profile_->GetPrefs()->GetBoolean(prefs::kGlicPinnedToTabstrip);
pref_registrar_.Init(profile_->GetPrefs());
pref_registrar_.Add(
prefs::kGlicCompletedFre,
base::BindRepeating(
&GlicMetrics::OnMaybeEnabledAndConsentForProfileChanged,
base::Unretained(this)));
pref_registrar_.Add(prefs::kGlicPinnedToTabstrip,
base::BindRepeating(&GlicMetrics::OnPinningPrefChanged,
base::Unretained(this)));
pref_registrar_.Add(
prefs::kGlicTabContextEnabled,
base::BindRepeating(&GlicMetrics::OnTabContextEnabledPrefChanged,
base::Unretained(this)));
}
GlicMetrics::~GlicMetrics() = default;
void GlicMetrics::OnFreAccepted() {
// Store the current time in a instance variable.
fre_accepted_time_ = base::TimeTicks::Now();
}
void GlicMetrics::OnUserInputSubmitted(mojom::WebClientMode mode) {
if (!fre_accepted_time_.is_null()) {
base::TimeDelta delta = base::TimeTicks::Now() - fre_accepted_time_;
base::UmaHistogramLongTimes("Glic.FreToFirstQueryTime", delta);
base::UmaHistogramCustomTimes("Glic.FreToFirstQueryTimeMax24H", delta,
base::Milliseconds(1), base::Hours(24), 50);
fre_accepted_time_ = base::TimeTicks();
}
base::UmaHistogramEnumeration(
"Glic.Session.InputSubmit.BrowserActiveState",
browser_activity_observer_->GetBrowserActiveState());
base::RecordAction(base::UserMetricsAction("GlicResponseInputSubmit"));
base::UmaHistogramEnumeration(
"Glic.Sharing.ActiveTabSharingState.OnUserInputSubmitted",
delegate_->GetActiveTabSharingState());
input_submitted_time_ = base::TimeTicks::Now();
input_mode_ = mode;
inputs_modes_used_.insert(mode);
last_input_mode_ = mode;
}
void GlicMetrics::OnResponseStarted() {
response_started_ = true;
base::UmaHistogramEnumeration(
"Glic.Session.ResponseStart.BrowserActiveState",
browser_activity_observer_->GetBrowserActiveState());
base::RecordAction(base::UserMetricsAction("GlicResponseStart"));
// It doesn't make sense to record response start without input submission.
if (input_submitted_time_.is_null()) {
base::UmaHistogramEnumeration("Glic.Metrics.Error",
Error::kResponseStartWithoutInput);
return;
}
if (!delegate_->IsWindowShowing()) {
base::UmaHistogramEnumeration("Glic.Metrics.Error",
Error::kResponseStartWhileHidingOrHidden);
return;
}
base::TimeDelta start_time = base::TimeTicks::Now() - input_submitted_time_;
base::UmaHistogramMediumTimes("Glic.Response.StartTime", start_time);
switch (input_mode_) {
case mojom::WebClientMode::kUnknown:
base::UmaHistogramMediumTimes("Glic.Response.StartTime.InputMode.Unknown",
start_time);
break;
case mojom::WebClientMode::kText:
base::UmaHistogramMediumTimes("Glic.Response.StartTime.InputMode.Text",
start_time);
break;
case mojom::WebClientMode::kAudio:
base::UmaHistogramMediumTimes("Glic.Response.StartTime.InputMode.Audio",
start_time);
break;
}
if (did_request_context_) {
base::UmaHistogramMediumTimes("Glic.Response.StartTime.WithContext",
start_time);
} else {
base::UmaHistogramMediumTimes("Glic.Response.StartTime.WithoutContext",
start_time);
}
base::RecordAction(base::UserMetricsAction("GlicResponse"));
++session_responses_;
// More detailed metrics.
bool attached = delegate_->IsWindowAttached();
base::UmaHistogramBoolean("Glic.Response.Attached", attached);
base::UmaHistogramEnumeration("Glic.Response.InvocationSource",
invocation_source_);
base::UmaHistogramEnumeration("Glic.Response.InputMode", input_mode_);
base::UmaHistogramEnumeration(
"Glic.Response.Segmentation",
GetResponseSegmentation(attached, input_mode_, invocation_source_));
base::UmaHistogramCounts100("Glic.Response.TabsPinnedForSharingCount",
delegate_->GetNumPinnedTabs());
ukm::builders::Glic_Response(source_id_)
.SetAttached(attached)
.SetInvocationSource(static_cast<int64_t>(invocation_source_))
.SetWebClientMode(static_cast<int64_t>(input_mode_))
.Record(ukm::UkmRecorder::Get());
}
void GlicMetrics::OnResponseStopped(mojom::ResponseStopCause cause) {
// The client may call "stopped" without "started" for very short responses.
// We synthetically call it ourselves in this case.
if (!input_submitted_time_.is_null() && !response_started_) {
OnResponseStarted();
}
base::RecordAction(base::UserMetricsAction("GlicResponseStop"));
std::string_view cause_suffix;
switch (cause) {
case mojom::ResponseStopCause::kUser:
cause_suffix = ".ByUser";
base::RecordAction(base::UserMetricsAction("GlicResponseStopByUser"));
break;
case mojom::ResponseStopCause::kOther:
cause_suffix = ".Other";
base::RecordAction(base::UserMetricsAction("GlicResponseStopOther"));
break;
case mojom::ResponseStopCause::kUnknown:
cause_suffix = ".UnknownCause";
base::RecordAction(
base::UserMetricsAction("GlicResponseStopUnknownCause"));
break;
}
if (input_submitted_time_.is_null()) {
base::UmaHistogramEnumeration("Glic.Metrics.Error",
Error::kResponseStopWithoutInput);
base::UmaHistogramEnumeration(
base::StrCat({"Glic.Metrics.Error", cause_suffix}),
Error::kResponseStopWithoutInput);
} else {
base::TimeTicks now = base::TimeTicks::Now();
base::UmaHistogramMediumTimes("Glic.Response.StopTime",
now - input_submitted_time_);
base::UmaHistogramMediumTimes(
base::StrCat({"Glic.Response.StopTime", cause_suffix}),
now - input_submitted_time_);
}
// Reset all times.
input_submitted_time_ = base::TimeTicks();
did_request_context_ = false;
source_id_ = no_url_source_id_;
response_started_ = false;
}
void GlicMetrics::OnSessionTerminated() {
base::RecordAction(base::UserMetricsAction("GlicWebClientSessionEnd"));
}
void GlicMetrics::OnResponseRated(bool positive) {
base::UmaHistogramBoolean("Glic.Response.Rated", positive);
}
void GlicMetrics::OnTurnCompleted(mojom::WebClientModel model,
base::TimeDelta duration) {
base::UmaHistogramMediumTimes(model == mojom::WebClientModel::kActor
? "Glic.Response.TurnDuration.Actor"
: "Glic.Response.TurnDuration.Default",
duration);
}
void GlicMetrics::OnGlicWindowOpen(bool attached,
mojom::InvocationSource source) {
base::UmaHistogramEnumeration(
"Glic.Session.Open.BrowserActiveState",
browser_activity_observer_->GetBrowserActiveState());
base::RecordAction(base::UserMetricsAction("GlicSessionBegin"));
session_start_time_ = base::TimeTicks::Now();
invocation_source_ = source;
base::UmaHistogramBoolean("Glic.Session.Open.Attached", attached);
base::UmaHistogramEnumeration("Glic.Session.Open.InvocationSource", source);
ukm::builders::Glic_WindowOpen(source_id_)
.SetAttached(attached)
.SetInvocationSource(static_cast<int64_t>(source))
.Record(ukm::UkmRecorder::Get());
const base::Time last_dismissed_time =
profile_->GetPrefs()->GetTime(prefs::kGlicWindowLastDismissedTime);
if (!last_dismissed_time.is_null()) {
base::TimeDelta elapsed_time_from_last_session =
base::Time::Now() - last_dismissed_time;
base::UmaHistogramCounts10M(
"Glic.PanelWebUi.ElapsedTimeBetweenSessions",
base::saturated_cast<int>(elapsed_time_from_last_session.InSeconds()));
}
// Update the last dismissed timestamp. The pref might not get updated on
// ungraceful shutdowns. As such, by updating the pref on opening the Glic
// window, the dismissal timestamp will get approximated by the opening
// timestamp, instead of the previously dismissal timestamp.
profile_->GetPrefs()->SetTime(prefs::kGlicWindowLastDismissedTime,
base::Time::Now());
}
void GlicMetrics::OnGlicWindowOpenAndReady() {
if (show_start_time_.is_null()) {
return;
}
base::UmaHistogramEnumeration(
"Glic.Sharing.ActiveTabSharingState.OnPanelOpenAndReady",
delegate_->GetActiveTabSharingState());
// Record the presentation time of showing the glic panel in an UMA histogram.
std::string input_mode;
if (starting_mode_ == mojom::WebClientMode::kText) {
input_mode = ".Text";
} else if (starting_mode_ == mojom::WebClientMode::kAudio) {
input_mode = ".Audio";
}
base::TimeDelta presentation_time = base::TimeTicks::Now() - show_start_time_;
base::UmaHistogramCustomTimes(
base::StrCat({kHistogramGlicPanelPresentationTime, ".All"}),
presentation_time, base::Milliseconds(1), base::Seconds(60), 50);
if (starting_mode_ != mojom::WebClientMode::kUnknown) {
base::UmaHistogramCustomTimes(
base::StrCat({kHistogramGlicPanelPresentationTime, input_mode}),
presentation_time, base::Milliseconds(1), base::Seconds(60), 50);
}
ResetGlicWindowPresentationTimingState();
}
void GlicMetrics::OnGlicWindowShown(
Browser* browser,
std::optional<display::Display> glic_display,
const gfx::Rect& glic_bounds) {
GlicMetrics::OnGlicWindowSizeTimerFired();
glic_window_size_timer_.Start(
FROM_HERE, kLogSizeMetricsDelay,
base::BindRepeating(&GlicMetrics::OnGlicWindowSizeTimerFired,
base::Unretained(this)));
base::UmaHistogramEnumeration(
"Glic.PositionOnDisplay.OnOpen",
GetDisplayPositionOfPoint(glic_display, glic_bounds.CenterPoint()));
base::UmaHistogramEnumeration(
"Glic.PositionOnChrome.OnOpen",
GetChromeRelativePositionOfPoint(browser, glic_bounds.CenterPoint()));
base::UmaHistogramEnumeration(
"Glic.PercentOverlapWithBrowser.OnOpen",
GetPercentOverlapWithBrowser(browser, glic_bounds));
}
void GlicMetrics::OnGlicWindowResize() {
base::RecordAction(base::UserMetricsAction("GlicPanelResized"));
}
void GlicMetrics::OnWidgetUserResizeStarted() {
base::RecordAction(base::UserMetricsAction("GlicPanelUserResizeStarted"));
gfx::Size size_on_user_resize_started = delegate_->GetWindowSize();
base::UmaHistogramCounts10000("Glic.PanelWebUi.UserResizeStarted.Width",
size_on_user_resize_started.width());
base::UmaHistogramCounts10000("Glic.PanelWebUi.UserResizeStarted.Height",
size_on_user_resize_started.height());
}
void GlicMetrics::OnWidgetUserResizeEnded() {
base::RecordAction(base::UserMetricsAction("GlicPanelUserResizeEnded"));
gfx::Size size_on_user_resize_ended = delegate_->GetWindowSize();
base::UmaHistogramCounts10000("Glic.PanelWebUi.UserResizeEnded.Width",
size_on_user_resize_ended.width());
base::UmaHistogramCounts10000("Glic.PanelWebUi.UserResizeEnded.Height",
size_on_user_resize_ended.height());
}
void GlicMetrics::OnGlicWindowClose(Browser* last_active_browser,
std::optional<display::Display> display,
const gfx::Rect& glic_bounds) {
base::RecordAction(base::UserMetricsAction("GlicSessionEnd"));
base::UmaHistogramEnumeration(
"Glic.PositionOnDisplay.OnClose",
GetDisplayPositionOfPoint(display, glic_bounds.CenterPoint()));
base::UmaHistogramEnumeration(
"Glic.PositionOnChrome.OnClose",
GetChromeRelativePositionOfPoint(last_active_browser,
glic_bounds.CenterPoint()));
base::UmaHistogramEnumeration(
"Glic.PercentOverlapWithBrowser.OnClose",
GetPercentOverlapWithBrowser(last_active_browser, glic_bounds));
base::UmaHistogramCounts1000("Glic.Session.ResponseCount",
session_responses_);
if (session_start_time_.is_null()) {
base::UmaHistogramEnumeration("Glic.Metrics.Error",
Error::kWindowCloseWithoutWindowOpen);
} else {
base::TimeDelta open_time = base::TimeTicks::Now() - session_start_time_;
base::UmaHistogramCustomTimes("Glic.Session.Duration", open_time,
/*min=*/base::Seconds(1),
/*max=*/base::Days(10), /*buckets=*/50);
}
session_responses_ = 0;
session_start_time_ = base::TimeTicks();
InputModesUsed modes_used = InputModesUsed::kNone;
if (!inputs_modes_used_.empty()) {
if (inputs_modes_used_.size() == 2) {
modes_used = InputModesUsed::kTextAndAudio;
} else {
modes_used = inputs_modes_used_.contains(mojom::WebClientMode::kAudio)
? InputModesUsed::kOnlyAudio
: InputModesUsed::kOnlyText;
}
}
inputs_modes_used_.clear();
base::UmaHistogramEnumeration("Glic.Session.InputModesUsed", modes_used);
base::UmaHistogramCounts100("Glic.Session.AttachStateChanges",
attach_change_count_);
attach_change_count_ = 0;
if (base::FeatureList::IsEnabled(features::kGlicScrollTo)) {
base::UmaHistogramCounts100("Glic.ScrollTo.SessionCount",
scroll_attempt_count_);
scroll_attempt_count_ = 0;
}
glic_window_size_timer_.Stop();
profile_->GetPrefs()->SetTime(prefs::kGlicWindowLastDismissedTime,
base::Time::Now());
}
void GlicMetrics::OnGlicScrollAttempt() {
CHECK(base::FeatureList::IsEnabled(features::kGlicScrollTo));
++scroll_attempt_count_;
if (!input_submitted_time_.is_null()) {
scroll_input_submitted_time_ = input_submitted_time_;
scroll_input_mode_ = input_mode_;
}
}
void GlicMetrics::OnGlicScrollComplete(bool success) {
CHECK(base::FeatureList::IsEnabled(features::kGlicScrollTo));
if (success && !scroll_input_submitted_time_.is_null()) {
base::TimeDelta time_to_scroll =
base::TimeTicks::Now() - scroll_input_submitted_time_;
switch (scroll_input_mode_) {
case mojom::WebClientMode::kAudio:
base::UmaHistogramMediumTimes(
"Glic.ScrollTo.UserPromptToScrollTime.Audio", time_to_scroll);
break;
case mojom::WebClientMode::kText:
base::UmaHistogramMediumTimes(
"Glic.ScrollTo.UserPromptToScrollTime.Text", time_to_scroll);
break;
case mojom::WebClientMode::kUnknown:
break;
}
}
scroll_input_submitted_time_ = base::TimeTicks();
scroll_input_mode_ = mojom::WebClientMode::kUnknown;
}
void GlicMetrics::LogClosedCaptionsShown() {
CHECK(base::FeatureList::IsEnabled(features::kGlicClosedCaptioning));
bool pref_enabled =
profile_->GetPrefs()->GetBoolean(prefs::kGlicClosedCaptioningEnabled);
base::UmaHistogramBoolean("Glic.Response.ClosedCaptionsShown", pref_enabled);
}
void GlicMetrics::LogGetContextFromFocusedTabError(
GlicGetContextFromFocusedTabError error) {
std::string mode_string;
switch (last_input_mode_) {
case mojom::WebClientMode::kText:
mode_string = "Text";
break;
case mojom::WebClientMode::kAudio:
mode_string = "Audio";
break;
case mojom::WebClientMode::kUnknown:
mode_string = "Unknown";
break;
}
base::UmaHistogramEnumeration(
base::StrCat({"Glic.Api.GetContextFromFocusedTab.Error.", mode_string}),
error);
}
void GlicMetrics::SetControllers(GlicWindowController* window_controller,
GlicSharingManager* sharing_manager) {
delegate_ = std::make_unique<DelegateImpl>(window_controller, sharing_manager,
profile_->GetPrefs());
}
void GlicMetrics::SetDelegateForTesting(std::unique_ptr<Delegate> delegate) {
delegate_ = std::move(delegate);
}
void GlicMetrics::DidRequestContextFromFocusedTab() {
did_request_context_ = true;
content::WebContents* web_contents = delegate_->GetContents();
if (web_contents) {
source_id_ = web_contents->GetPrimaryMainFrame()->GetPageUkmSourceId();
} else {
source_id_ = no_url_source_id_;
}
}
void GlicMetrics::OnImpressionTimerFired() {
if (!enabling_->IsAllowed()) {
EntryPointStatus impression;
if (CheckFreStatus(profile_, prefs::FreStatus::kNotStarted)) {
// Profile not eligible, and not started FRE
impression = EntryPointStatus::kBeforeFreNotEligible;
} else if (CheckFreStatus(profile_, prefs::FreStatus::kIncomplete)) {
// Profile not eligible, started but not completed FRE
impression = EntryPointStatus::kIncompleteFreNotEligible;
} else {
// Profile not eligible, completed FRE
impression = EntryPointStatus::kAfterFreNotEligible;
}
base::UmaHistogramEnumeration("Glic.EntryPoint.Status", impression);
return;
}
// Profile eligible, has not started FRE
if (CheckFreStatus(profile_, prefs::FreStatus::kNotStarted)) {
base::UmaHistogramEnumeration("Glic.EntryPoint.Status",
EntryPointStatus::kBeforeFreAndEligible);
return;
}
// Profile eligible, started but not completed FRE
if (CheckFreStatus(profile_, prefs::FreStatus::kIncomplete)) {
base::UmaHistogramEnumeration("Glic.EntryPoint.Status",
EntryPointStatus::kIncompleteFreAndEligible);
return;
}
// Profile eligible and completed FRE
EntryPointStatus impression;
bool is_os_entrypoint_enabled =
g_browser_process->local_state()->GetBoolean(prefs::kGlicLauncherEnabled);
if (is_pinned_ && is_os_entrypoint_enabled) {
impression = EntryPointStatus::kAfterFreBrowserAndOs;
} else if (is_pinned_) {
impression = EntryPointStatus::kAfterFreBrowserOnly;
} else if (is_os_entrypoint_enabled) {
impression = EntryPointStatus::kAfterFreOsOnly;
} else {
impression = EntryPointStatus::kAfterFreThreeDotOnly;
}
base::UmaHistogramEnumeration("Glic.EntryPoint.Status", impression);
ui::Accelerator saved_hotkey =
glic::GlicLauncherConfiguration::GetGlobalHotkey();
base::UmaHistogramBoolean("Glic.OsEntrypoint.Settings.ShortcutStatus",
saved_hotkey != ui::Accelerator());
}
void GlicMetrics::OnGlicWindowSizeTimerFired() {
// A 4K screen is 3840 or 4096 pixels wide and 2160 tall. Doubling this and
// rounding up to 10000 should give a reasonable upper bound on DIPs for
// both directions.
gfx::Size currentSize = delegate_->GetWindowSize();
base::UmaHistogramCounts10000("Glic.PanelWebUi.Size.Width",
currentSize.width());
base::UmaHistogramCounts10000("Glic.PanelWebUi.Size.Height",
currentSize.height());
base::UmaHistogramCounts10M("Glic.PanelWebUi.Size.Area",
currentSize.GetArea());
}
void GlicMetrics::OnMaybeEnabledAndConsentForProfileChanged() {
bool is_enabled = enabling_->IsEnabledAndConsentForProfile(profile_);
if (is_enabled == is_enabled_) {
// No change, early exit.
return;
}
is_enabled_ = is_enabled;
if (is_enabled_) {
base::RecordAction(base::UserMetricsAction("Glic.Enabled"));
} else {
base::RecordAction(base::UserMetricsAction("Glic.Disabled"));
}
}
void GlicMetrics::OnPinningPrefChanged() {
bool is_pinned =
profile_->GetPrefs()->GetBoolean(prefs::kGlicPinnedToTabstrip);
if (is_pinned == is_pinned_) {
// No change, early exit.
return;
}
is_pinned_ = is_pinned;
if (is_pinned_) {
base::RecordAction(base::UserMetricsAction("Glic.Pinned"));
} else {
base::RecordAction(base::UserMetricsAction("Glic.Unpinned"));
}
}
void GlicMetrics::OnTabPinnedForSharing(GlicTabPinnedForSharingResult result) {
base::UmaHistogramEnumeration("Glic.Sharing.TabPinnedForSharing", result);
}
void GlicMetrics::OnTabContextEnabledPrefChanged() {
bool is_panel_open = !session_start_time_.is_null();
bool is_enabled =
profile_->GetPrefs()->GetBoolean(prefs::kGlicTabContextEnabled);
if (is_panel_open && is_enabled) {
base::UmaHistogramEnumeration(
"Glic.Sharing.ActiveTabSharingState."
"OnTabContextPermissionGranted",
delegate_->GetActiveTabSharingState());
}
}
void GlicMetrics::ResetGlicWindowPresentationTimingState() {
show_start_time_ = base::TimeTicks();
starting_mode_ = mojom::WebClientMode::kUnknown;
}
DisplayPosition GlicMetrics::GetDisplayPositionOfPoint(
std::optional<display::Display> display,
const gfx::Point& glic_center_point) {
if (!display) {
return DisplayPosition::kUnknown;
}
gfx::Rect work_area_bounds = display->work_area();
if (!work_area_bounds.Contains(glic_center_point) ||
work_area_bounds.IsEmpty()) {
return DisplayPosition::kUnknown;
}
// Adjust glic center point to the origin of the display's work area.
gfx::Point glic_work_area_center_point =
glic_center_point - work_area_bounds.OffsetFromOrigin();
int x_index = std::floor(3 * glic_work_area_center_point.x() /
work_area_bounds.width());
int y_index = std::floor(3 * glic_work_area_center_point.y() /
work_area_bounds.height());
// This is unexpected to happen but just in case.
if (x_index < 0 || x_index > 2 || y_index < 0 || y_index > 2) {
return DisplayPosition::kUnknown;
}
const std::array<std::array<DisplayPosition, 3>, 3> position_map = {{
{DisplayPosition::kTopLeft, DisplayPosition::kCenterLeft,
DisplayPosition::kBottomLeft},
{DisplayPosition::kTopCenter, DisplayPosition::kCenterCenter,
DisplayPosition::kBottomCenter},
{DisplayPosition::kTopRight, DisplayPosition::kCenterRight,
DisplayPosition::kBottomRight},
}};
return position_map[x_index][y_index];
}
ChromeRelativePosition GlicMetrics::GetChromeRelativePositionOfPoint(
Browser* browser,
const gfx::Point& glic_center_point) {
if (!IsBrowserVisible(browser)) {
return ChromeRelativePosition::kNoVisibleChromeBrowser;
}
// Check if the center point is on a different display
std::optional<display::Display> browser_display =
browser->GetBrowserView().GetWidget()->GetNearestDisplay();
if (browser_display &&
!browser_display->work_area().Contains(glic_center_point)) {
return ChromeRelativePosition::kChromeOnOtherDisplay;
}
gfx::Rect browser_bounds =
browser->GetBrowserView().GetWidget()->GetWindowBoundsInScreen();
int x_index;
if (glic_center_point.x() < browser_bounds.x()) {
x_index = 0;
} else if (glic_center_point.x() < browser_bounds.right()) {
x_index = 1;
} else {
x_index = 2;
}
int y_index;
if (glic_center_point.y() < browser_bounds.y()) {
y_index = 0;
} else if (glic_center_point.y() < browser_bounds.bottom()) {
y_index = 1;
} else {
y_index = 2;
}
const std::array<std::array<ChromeRelativePosition, 3>, 3> position_map = {{
{ChromeRelativePosition::kAboveLeft, ChromeRelativePosition::kCenterLeft,
ChromeRelativePosition::kBelowLeft},
{ChromeRelativePosition::kAboveCenter, ChromeRelativePosition::kOverlap,
ChromeRelativePosition::kBelowCenter},
{ChromeRelativePosition::kAboveRight,
ChromeRelativePosition::kCenterRight,
ChromeRelativePosition::kBelowRight},
}};
return position_map[x_index][y_index];
}
PercentOverlap GlicMetrics::GetPercentOverlapWithBrowser(
Browser* browser,
const gfx::Rect& glic_bounds) {
if (!IsBrowserVisible(browser)) {
return PercentOverlap::kNoVisibleChromeBrowser;
}
int glic_area = glic_bounds.width() * glic_bounds.height();
if (glic_area == 0) {
return PercentOverlap::k0;
}
gfx::Rect browser_glic_intersect_bounds =
browser->GetBrowserView().GetWidget()->GetWindowBoundsInScreen();
browser_glic_intersect_bounds.Intersect(glic_bounds);
int browser_glic_intersect_area = browser_glic_intersect_bounds.width() *
browser_glic_intersect_bounds.height();
// Calculate overlap percentage and round to the nearest 10.
int percentOverlap = round(10 * browser_glic_intersect_area / glic_area) * 10;
switch (percentOverlap) {
case 100:
return PercentOverlap::k100;
case 90:
return PercentOverlap::k90;
case 80:
return PercentOverlap::k80;
case 70:
return PercentOverlap::k70;
case 60:
return PercentOverlap::k60;
case 50:
return PercentOverlap::k50;
case 40:
return PercentOverlap::k40;
case 30:
return PercentOverlap::k30;
case 20:
return PercentOverlap::k20;
case 10:
return PercentOverlap::k10;
case 0:
default:
return PercentOverlap::k0;
}
}
void GlicMetrics::OnAttachedToBrowser(AttachChangeReason reason) {
base::UmaHistogramEnumeration("Glic.AttachedToBrowser", reason);
if (reason != AttachChangeReason::kInit) {
attach_change_count_++;
}
}
void GlicMetrics::OnDetachedFromBrowser(AttachChangeReason reason) {
base::UmaHistogramEnumeration("Glic.DetachedFromBrowser", reason);
if (reason != AttachChangeReason::kInit) {
attach_change_count_++;
}
}
} // namespace glic