chromium/chrome/browser/android/metrics/android_session_durations_service.cc

// Copyright 2021 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/android/metrics/android_session_durations_service.h"

#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "chrome/browser/android/metrics/android_session_durations_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/signin/core/browser/signin_status_metrics_provider_helpers.h"
#include "components/sync/service/sync_session_durations_metrics_recorder.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/browser/android/metrics/jni_headers/AndroidSessionDurationsServiceState_jni.h"

namespace {
class IncognitoSessionDurationsMetricsRecorder {
 public:
  IncognitoSessionDurationsMetricsRecorder() = default;

  ~IncognitoSessionDurationsMetricsRecorder() { OnAppEnterBackground(); }

  void OnAppEnterForeground() {
    // When Chrome recovers from a crash while in Incognito, it creates the
    // Incognito profile first and then brings it to foreground. In this case we
    // should prevent double counting.
    if (is_foreground_)
      return;
    is_foreground_ = true;

    // |session_start_| is null prior to start of a new session. Therefore we
    // only need to record the current time.
    if (session_start_.is_null()) {
      session_start_ = base::Time::Now();
      return;
    }

    // Record the previously reported duration, so that subtracting this
    // histogram from 'Profile.Incognito.MovedToBackgroundAfterDuration' would
    // offset for the sessions that were recorded there, but were resumed
    // later.
    base::UmaHistogramCustomCounts(
        "Profile.Incognito.ResumedAfterReportedDuration",
        last_reported_duration_.InMinutes(), 1, base::Days(28).InMinutes(), 50);
  }

  void OnAppEnterBackground() {
    // This function may be called when Chrome is already in background and a
    // proper shutdown of the service takes place.
    if (!is_foreground_)
      return;
    is_foreground_ = false;

    last_reported_duration_ = base::Time::Now() - session_start_;
    base::UmaHistogramCustomCounts(
        "Profile.Incognito.MovedToBackgroundAfterDuration",
        last_reported_duration_.InMinutes(), 1, base::Days(28).InMinutes(), 50);
  }

  void SetSessionStartTimeForTesting(base::Time session_start) {
    session_start_ = session_start;
  }

  void GetStatusForSessionRestore(base::Time& session_start,
                                  base::TimeDelta& last_reported_duration) {
    session_start = session_start_;
    last_reported_duration = last_reported_duration_;
  }

  void RestoreSession(base::Time session_start,
                      base::TimeDelta last_reported_duration) {
    session_start_ = session_start;
    last_reported_duration_ = last_reported_duration;
    is_foreground_ = false;
    OnAppEnterForeground();
  }

 private:
  base::Time session_start_;
  base::TimeDelta last_reported_duration_;
  bool is_foreground_ = false;
};
}  // namespace

AndroidSessionDurationsService::AndroidSessionDurationsService() = default;

AndroidSessionDurationsService::~AndroidSessionDurationsService() = default;

void AndroidSessionDurationsService::InitializeForRegularProfile(
    PrefService* pref_service,
    syncer::SyncService* sync_service,
    signin::IdentityManager* identity_manager) {
  DCHECK(!incognito_session_metrics_recorder_);
  DCHECK(!sync_session_metrics_recorder_);
  CHECK(!password_session_duration_metrics_recorder_,
        base::NotFatalUntil::M130);
  DCHECK(!msbb_session_metrics_recorder_);

  sync_session_metrics_recorder_ =
      std::make_unique<syncer::SyncSessionDurationsMetricsRecorder>(
          sync_service, identity_manager);

  password_session_duration_metrics_recorder_ = std::make_unique<
      password_manager::PasswordSessionDurationsMetricsRecorder>(pref_service,
                                                                 sync_service);

  msbb_session_metrics_recorder_ =
      std::make_unique<unified_consent::MsbbSessionDurationsMetricsRecorder>(
          pref_service);

  // The AndroidSessionDurationsService object is created as soon as
  // the profile is initialized. On Android, the profile is initialized as part
  // of the native code initialization, which is done soon after the application
  // enters foreground and before any of the Chrome UI is shown. Let's start
  // tracking the session now.
  OnAppEnterForeground(base::TimeTicks::Now());
}

void AndroidSessionDurationsService::InitializeForIncognitoProfile() {
  DCHECK(!incognito_session_metrics_recorder_);
  DCHECK(!sync_session_metrics_recorder_);
  CHECK(!password_session_duration_metrics_recorder_,
        base::NotFatalUntil::M130);
  DCHECK(!msbb_session_metrics_recorder_);

  incognito_session_metrics_recorder_ =
      std::make_unique<IncognitoSessionDurationsMetricsRecorder>();
  OnAppEnterForeground(base::TimeTicks::Now());
}

signin_metrics::SingleProfileSigninStatus
AndroidSessionDurationsService::GetSigninStatus() const {
  switch (sync_session_metrics_recorder_->GetSigninStatus()) {
    case syncer::SyncSessionDurationsMetricsRecorder::SigninStatus::kSignedIn:
      return signin_metrics::SingleProfileSigninStatus::kSignedIn;
    case syncer::SyncSessionDurationsMetricsRecorder::SigninStatus::
        kSignedInWithError:
      return signin_metrics::SingleProfileSigninStatus::kSignedInWithError;
    case syncer::SyncSessionDurationsMetricsRecorder::SigninStatus::kSignedOut:
      return signin_metrics::SingleProfileSigninStatus::kSignedOut;
  }
}

bool AndroidSessionDurationsService::IsSyncing() const {
  return sync_session_metrics_recorder_->IsSyncing();
}

void AndroidSessionDurationsService::Shutdown() {
  sync_session_metrics_recorder_.reset();
  password_session_duration_metrics_recorder_.reset();
  msbb_session_metrics_recorder_.reset();
  incognito_session_metrics_recorder_.reset();
}

void AndroidSessionDurationsService::OnAppEnterForeground(
    base::TimeTicks session_start) {
  if (sync_session_metrics_recorder_) {
    // The non-incognito recorders are always created and destroyed together.
    CHECK(msbb_session_metrics_recorder_);

    sync_session_metrics_recorder_->OnSessionStarted(session_start);
    password_session_duration_metrics_recorder_->OnSessionStarted(
        session_start);
    msbb_session_metrics_recorder_->OnSessionStarted(session_start);
  } else {
    CHECK(!msbb_session_metrics_recorder_);
    incognito_session_metrics_recorder_->OnAppEnterForeground();
  }
}

void AndroidSessionDurationsService::OnAppEnterBackground(
    base::TimeDelta session_length) {
  if (sync_session_metrics_recorder_) {
    // The non-incognito recorders are always created and destroyed together.
    CHECK(msbb_session_metrics_recorder_);

    sync_session_metrics_recorder_->OnSessionEnded(session_length);
    password_session_duration_metrics_recorder_->OnSessionEnded(session_length);
    msbb_session_metrics_recorder_->OnSessionEnded(session_length);
  } else {
    CHECK(!msbb_session_metrics_recorder_);
    incognito_session_metrics_recorder_->OnAppEnterBackground();
  }
}

void AndroidSessionDurationsService::SetSessionStartTimeForTesting(
    base::Time session_start) {
  if (incognito_session_metrics_recorder_) {
    incognito_session_metrics_recorder_
        ->SetSessionStartTimeForTesting(  // IN-TEST
            session_start);
    return;
  }

  NOTIMPLEMENTED();
}

void AndroidSessionDurationsService::GetIncognitoSessionData(
    base::Time& session_start,
    base::TimeDelta& last_reported_duration) {
  DCHECK(incognito_session_metrics_recorder_);
  incognito_session_metrics_recorder_->GetStatusForSessionRestore(
      session_start, last_reported_duration);
}

void AndroidSessionDurationsService::RestoreIncognitoSession(
    base::Time session_start,
    base::TimeDelta last_reported_duration) {
  DCHECK(incognito_session_metrics_recorder_);
  incognito_session_metrics_recorder_->RestoreSession(session_start,
                                                      last_reported_duration);
}

// Returns a java object consisting of data required to restore the service.
// This function only covers Incognito profiles.
// static
base::android::ScopedJavaLocalRef<jobject>
JNI_AndroidSessionDurationsServiceState_GetAndroidSessionDurationsServiceState(
    JNIEnv* env,
    Profile* profile) {
  CHECK(profile->IsIncognitoProfile());

  AndroidSessionDurationsService* duration_service =
      AndroidSessionDurationsServiceFactory::GetForProfile(profile);
  base::Time session_start_time;
  base::TimeDelta last_reported_duration;
  duration_service->GetIncognitoSessionData(session_start_time,
                                            last_reported_duration);

  return Java_AndroidSessionDurationsServiceState_Constructor(
      env, session_start_time.InMillisecondsSinceUnixEpoch(),
      last_reported_duration.InMinutes());
}

// Restores the service from an archived android object.
// This function only covers Incognito profiles.
// static
void JNI_AndroidSessionDurationsServiceState_RestoreAndroidSessionDurationsServiceState(
    JNIEnv* env,
    Profile* profile,
    const base::android::JavaParamRef<jobject>& j_duration_service) {
  CHECK(profile->IsIncognitoProfile());

  AndroidSessionDurationsService* duration_service =
      AndroidSessionDurationsServiceFactory::GetForProfile(profile);

  base::Time session_start_time = base::Time::FromMillisecondsSinceUnixEpoch(
      Java_AndroidSessionDurationsServiceState_getSessionStartTime(
          env, j_duration_service));
  base::TimeDelta last_reported_duration = base::Minutes(
      Java_AndroidSessionDurationsServiceState_getLastReportedDuration(
          env, j_duration_service));

  duration_service->RestoreIncognitoSession(session_start_time,
                                            last_reported_duration);
}