| Index: chrome/browser/metrics/desktop_engagement_service.cc
|
| diff --git a/chrome/browser/metrics/desktop_engagement_service.cc b/chrome/browser/metrics/desktop_engagement_service.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2ba67b57297a9e1b1d4c198de3144c772355917d
|
| --- /dev/null
|
| +++ b/chrome/browser/metrics/desktop_engagement_service.cc
|
| @@ -0,0 +1,160 @@
|
| +// Copyright 2016 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 "chrome/browser/metrics/desktop_engagement_service.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/metrics/histogram_macros.h"
|
| +#include "chrome/browser/metrics/audible_contents_tracker.h"
|
| +#include "chrome/browser/metrics/chrome_visibility_observer.h"
|
| +
|
| +namespace {
|
| +
|
| +DesktopEngagementService* g_instance = nullptr;
|
| +
|
| +const int kActivityIntervalMinutes = 1;
|
| +
|
| +// Small adapter for observing AudibleContentsTracker notifications and
|
| +// dispatching them to the DesktopEngagementService.
|
| +class AudibleContentsObserver
|
| + : public metrics::AudibleContentsTracker::Observer {
|
| + public:
|
| + AudibleContentsObserver() {}
|
| +
|
| + // A one way initialization function. This causes a tracker/observer pair to
|
| + // be created, which live for the lifetime of the browser. They are
|
| + // deliberately leaked at shutdown.
|
| + static void Initialize() {
|
| + DCHECK(!tracker_);
|
| + DCHECK(!observer_);
|
| + observer_ = new AudibleContentsObserver();
|
| + tracker_ = new metrics::AudibleContentsTracker(observer_);
|
| + }
|
| +
|
| + // AudibleContentsTracker::Observer:
|
| + void OnAudioStart() override { g_instance->OnAudioStart(); }
|
| + void OnAudioEnd() override { g_instance->OnAudioEnd(); }
|
| +
|
| + private:
|
| + // Singletons for performing the tracking and observing state changes. These
|
| + // are deliberately leaked at shutdown.
|
| + static metrics::AudibleContentsTracker* tracker_;
|
| + static AudibleContentsObserver* observer_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(AudibleContentsObserver);
|
| +};
|
| +
|
| +metrics::AudibleContentsTracker* AudibleContentsObserver::tracker_ = nullptr;
|
| +AudibleContentsObserver* AudibleContentsObserver::observer_ = nullptr;
|
| +
|
| +} // namespace
|
| +
|
| +// static
|
| +void DesktopEngagementService::Initialize() {
|
| + g_instance = new DesktopEngagementService();
|
| + AudibleContentsObserver::Initialize();
|
| + metrics::ChromeVisibilityObserver::Initialize();
|
| +}
|
| +
|
| +// static
|
| +bool DesktopEngagementService::IsInitialized() {
|
| + return g_instance != nullptr;
|
| +}
|
| +
|
| +// static
|
| +DesktopEngagementService* DesktopEngagementService::Get() {
|
| + DCHECK(g_instance);
|
| + return g_instance;
|
| +}
|
| +
|
| +void DesktopEngagementService::StartTimer(base::TimeDelta duration) {
|
| + timer_.Start(FROM_HERE, duration,
|
| + base::Bind(&DesktopEngagementService::OnTimerFired,
|
| + weak_factory_.GetWeakPtr()));
|
| +}
|
| +
|
| +void DesktopEngagementService::OnVisibilityChanged(bool visible) {
|
| + is_visible_ = visible;
|
| + if (is_visible_) {
|
| + OnUserEvent();
|
| + } else if (in_session_ && !is_audio_playing_) {
|
| + DLOG(ERROR) << "Ending session due to visibility change";
|
| + EndSession();
|
| + }
|
| +}
|
| +
|
| +void DesktopEngagementService::OnUserEvent() {
|
| + if (!is_visible_)
|
| + return;
|
| +
|
| + last_user_event_ = base::TimeTicks::Now();
|
| + // This may start session.
|
| + if (!in_session_) {
|
| + DLOG(ERROR) << "Starting session due to user event";
|
| + StartSession();
|
| + }
|
| +}
|
| +
|
| +void DesktopEngagementService::OnAudioStart() {
|
| + // This may start session.
|
| + is_audio_playing_ = true;
|
| + if (!in_session_) {
|
| + DLOG(ERROR) << "Starting session due to audio start";
|
| + StartSession();
|
| + }
|
| +}
|
| +
|
| +void DesktopEngagementService::OnAudioEnd() {
|
| + is_audio_playing_ = false;
|
| +
|
| + // If the timer is not running, this means that no user events happened in the
|
| + // last 5 minutes so the session can be terminated.
|
| + if (!timer_.IsRunning()) {
|
| + DLOG(ERROR) << "Ending session due to audio ending";
|
| + EndSession();
|
| + }
|
| +}
|
| +
|
| +DesktopEngagementService::DesktopEngagementService()
|
| + : session_start_(base::TimeTicks::Now()),
|
| + last_user_event_(session_start_),
|
| + weak_factory_(this) {
|
| + StartSession();
|
| + // TODO: Add user events observers here, or add a static Get() method to
|
| + // DesktopEngagementService.
|
| +}
|
| +
|
| +DesktopEngagementService::~DesktopEngagementService() {}
|
| +
|
| +void DesktopEngagementService::OnTimerFired() {
|
| + base::TimeDelta remaining = base::TimeTicks::Now() - last_user_event_;
|
| + if (remaining < base::TimeDelta::FromMinutes(kActivityIntervalMinutes)) {
|
| + StartTimer(remaining);
|
| + return;
|
| + }
|
| +
|
| + // No user events happened in the last 5 min. Terminate the session now.
|
| + if (!is_audio_playing_) {
|
| + DLOG(ERROR) << "Ending session after delay";
|
| + EndSession();
|
| + }
|
| +}
|
| +
|
| +void DesktopEngagementService::StartSession() {
|
| + in_session_ = true;
|
| + session_start_ = base::TimeTicks::Now();
|
| + StartTimer(base::TimeDelta::FromMinutes(kActivityIntervalMinutes));
|
| +}
|
| +
|
| +void DesktopEngagementService::EndSession() {
|
| + in_session_ = false;
|
| + base::TimeDelta delta = base::TimeTicks::Now() - session_start_;
|
| +
|
| + constexpr unsigned kNumTimeSlices = 60 / 5 * 24;
|
| + DLOG(ERROR) << "Logging session length of " << delta.InSeconds()
|
| + << " seconds.";
|
| + UMA_HISTOGRAM_CUSTOM_TIMES("Session.TotalDuration", delta,
|
| + base::TimeDelta::FromMilliseconds(1),
|
| + base::TimeDelta::FromHours(24), kNumTimeSlices);
|
| +}
|
|
|