chromium/android_webview/browser/metrics/aw_metrics_service_client.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "android_webview/browser/metrics/aw_metrics_service_client.h"

#include <jni.h>
#include <cstdint>

#include "android_webview/browser/metrics/android_metrics_provider.h"
#include "android_webview/common/aw_features.h"
#include "base/android/callback_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/base_paths_android.h"
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/path_service.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/metrics_service.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/android/channel_getter.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "android_webview/browser_jni_headers/AwMetricsServiceClient_jni.h"

namespace android_webview {

namespace {

// IMPORTANT: DO NOT CHANGE sample rates without first ensuring the Chrome
// Metrics team has the appropriate backend bandwidth and storage.

// Sample at 2%, based on storage concerns. We sample at a different rate than
// Chrome because we have more metrics "clients" (each app on the device counts
// as a separate client).
const int kStableSampledInRatePerMille = 20;

// Sample non-stable channels at 99%, to boost volume for pre-stable
// experiments. We choose 99% instead of 100% for consistency with Chrome and to
// exercise the out-of-sample code path.
const int kBetaDevCanarySampledInRatePerMille = 990;

// The fraction of UMA clients for whom package name data is uploaded. This
// threshold and the corresponding privacy requirements are described in more
// detail at http://shortn/_CzfDUxTxm2 (internal document). We also have public
// documentation for metrics collection in WebView more generally (see
// https://developer.android.com/guide/webapps/webview-privacy).
//
// Do not change this constant without seeking privacy approval with the teams
// outlined in the internal document above.
const int kPackageNameLimitRatePerMille = 100;  // (10% of UMA clients)

AwMetricsServiceClient* g_aw_metrics_service_client = nullptr;

int GetBaseSampleRatePerMille() {
  // Down-sample unknown channel as a precaution in case it ends up being
  // shipped to Stable users.
  version_info::Channel channel = version_info::android::GetChannel();
  if (channel == version_info::Channel::STABLE ||
      channel == version_info::Channel::UNKNOWN) {
    return kStableSampledInRatePerMille;
  }
  return kBetaDevCanarySampledInRatePerMille;
}

}  // namespace

const base::TimeDelta kRecordAppDataDirectorySizeDelay = base::Seconds(10);

AwMetricsServiceClient::Delegate::Delegate() = default;
AwMetricsServiceClient::Delegate::~Delegate() = default;

// static
AwMetricsServiceClient* AwMetricsServiceClient::GetInstance() {
  DCHECK(g_aw_metrics_service_client);
  g_aw_metrics_service_client->EnsureOnValidSequence();
  return g_aw_metrics_service_client;
}

// static
void AwMetricsServiceClient::SetInstance(
    std::unique_ptr<AwMetricsServiceClient> aw_metrics_service_client) {
  DCHECK(!g_aw_metrics_service_client);
  DCHECK(aw_metrics_service_client);
  g_aw_metrics_service_client = aw_metrics_service_client.release();
  g_aw_metrics_service_client->EnsureOnValidSequence();
}

AwMetricsServiceClient::AwMetricsServiceClient(
    std::unique_ptr<Delegate> delegate)
    : time_created_(base::Time::Now()), delegate_(std::move(delegate)) {}

AwMetricsServiceClient::~AwMetricsServiceClient() = default;

int32_t AwMetricsServiceClient::GetProduct() {
  return metrics::ChromeUserMetricsExtension::ANDROID_WEBVIEW;
}

int AwMetricsServiceClient::GetSampleRatePerMille() const {
  return 1000;
}

bool AwMetricsServiceClient::ShouldApplyMetricsFiltering() const {
  bool used_to_sample_in = GetSampleBucketValue() < GetBaseSampleRatePerMille();
  return !used_to_sample_in;
}

std::string AwMetricsServiceClient::GetAppPackageNameIfLoggable() {
  AndroidMetricsServiceClient::InstallerPackageType installer_type =
      GetInstallerPackageType();
  // Always record the app package name of system apps and apps
  // from the play store
  if (installer_type == InstallerPackageType::SYSTEM_APP ||
      installer_type == InstallerPackageType::GOOGLE_PLAY_STORE) {
    return GetAppPackageName();
  }
  return std::string();
}

bool AwMetricsServiceClient::ShouldRecordPackageName() {
  return true;
}

// Used below in AwMetricsServiceClient::OnMetricsStart.
void RecordAppDataDirectorySize() {
  TRACE_EVENT_BEGIN0("android_webview", "RecordAppDataDirectorySize");
  base::TimeTicks start_time = base::TimeTicks::Now();

  base::FilePath app_data_dir;
  base::PathService::Get(base::DIR_ANDROID_APP_DATA, &app_data_dir);
  int64_t bytes = base::ComputeDirectorySize(app_data_dir);
  // Record size up to 100MB
  base::UmaHistogramCounts100000("Android.WebView.AppDataDirectory.Size",
                                 bytes / 1024);

  base::UmaHistogramMediumTimes(
      "Android.WebView.AppDataDirectory.TimeToComputeSize",
      base::TimeTicks::Now() - start_time);
  TRACE_EVENT_END0("android_webview", "RecordAppDataDirectorySize");
}

void AwMetricsServiceClient::OnMetricsStart() {
  delegate_->AddWebViewAppStateObserver(this);
  if (base::FeatureList::IsEnabled(
          android_webview::features::kWebViewRecordAppDataDirectorySize) &&
      IsReportingEnabled()) {
    // Calculating directory size can be fairly expensive, so only do this when
    // we are certain that the UMA histogram will be logged to the server.
    base::ThreadPool::PostDelayedTask(
        FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
        base::BindOnce(&RecordAppDataDirectorySize),
        kRecordAppDataDirectorySizeDelay);
  }
}

void AwMetricsServiceClient::OnMetricsNotStarted() {}

int AwMetricsServiceClient::GetPackageNameLimitRatePerMille() {
  return kPackageNameLimitRatePerMille;
}

void AwMetricsServiceClient::OnAppStateChanged(
    WebViewAppStateObserver::State state) {
  // To match MetricsService's expectation,
  // - does nothing if no WebView has ever been created.
  // - starts notifying MetricsService once a WebView is created and the app
  //   is foreground.
  // - consolidates the other states other than kForeground into background.
  // - avoids the duplicated notification.
  if (state == WebViewAppStateObserver::State::kDestroyed &&
      !delegate_->HasAwContentsEverCreated()) {
    return;
  }

  bool foreground = state == WebViewAppStateObserver::State::kForeground;

  if (foreground == app_in_foreground_)
    return;

  app_in_foreground_ = foreground;
  if (app_in_foreground_) {
    GetMetricsService()->OnAppEnterForeground();
  } else {
    // TODO(crbug.com/40118864): Turn on the background recording.
    // Not recording in background, this matches Chrome's behavior.
    GetMetricsService()->OnAppEnterBackground(
        /* keep_recording_in_background = false */);
  }
}

void AwMetricsServiceClient::RegisterAdditionalMetricsProviders(
    metrics::MetricsService* service) {
  delegate_->RegisterAdditionalMetricsProviders(service);
}

// static
void AwMetricsServiceClient::RegisterMetricsPrefs(
    PrefRegistrySimple* registry) {
  RegisterPrefs(registry);
  AndroidMetricsProvider::RegisterPrefs(registry);
}

// static
void JNI_AwMetricsServiceClient_SetHaveMetricsConsent(JNIEnv* env,
                                                      jboolean user_consent,
                                                      jboolean app_consent) {
  AwMetricsServiceClient::GetInstance()->SetHaveMetricsConsent(user_consent,
                                                               app_consent);
}

// static
void JNI_AwMetricsServiceClient_SetFastStartupForTesting(
    JNIEnv* env,
    jboolean fast_startup_for_testing) {
  AwMetricsServiceClient::GetInstance()->SetFastStartupForTesting(
      fast_startup_for_testing);
}

// static
void JNI_AwMetricsServiceClient_SetUploadIntervalForTesting(
    JNIEnv* env,
    jlong upload_interval_ms) {
  AwMetricsServiceClient::GetInstance()->SetUploadIntervalForTesting(
      base::Milliseconds(upload_interval_ms));
}

// static
void JNI_AwMetricsServiceClient_SetOnFinalMetricsCollectedListenerForTesting(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& listener) {
  AwMetricsServiceClient::GetInstance()
      ->SetOnFinalMetricsCollectedListenerForTesting(base::BindRepeating(
          base::android::RunRunnableAndroid,
          base::android::ScopedJavaGlobalRef<jobject>(listener)));
}

}  // namespace android_webview