// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/capture_mode/capture_mode_metrics.h"
#include "ash/capture_mode/capture_mode_behavior.h"
#include "ash/capture_mode/capture_mode_types.h"
#include "ash/shell.h"
#include "ash/system/toast/anchored_nudge_manager_impl.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
namespace ash {
namespace {
constexpr char kCaptureModeMetricCommonPrefix[] = "Ash.CaptureModeController.";
constexpr char kEndRecordingReasonHistogramRootWord[] = "EndRecordingReason";
constexpr char kBarButtonHistogramRootWord[] = "BarButtons";
constexpr char kCaptureAudioRecordingModeHistogramRootWord[] =
"AudioRecordingMode";
constexpr char kCaptureConfigurationHistogramRootWord[] =
"CaptureConfiguration";
constexpr char kCaptureRegionAdjustmentHistogramRootWord[] =
"CaptureRegionAdjusted";
constexpr char kDemoToolsEnabledOnRecordingStartRootWord[] =
"DemoToolsEnabledOnRecordingStart";
constexpr char kEntryPointHistogramRootWord[] = "EntryPoint";
constexpr char kRecordingDurationHistogramRootWord[] = "ScreenRecordingLength";
constexpr char kGifRecordingDurationHistogramRootWord[] = "GIFRecordingLength";
constexpr char kGifRecordingRegionToScreenRatioHistogramRootWord[] =
"GIFRecordingRegionToScreenRatio";
constexpr char kSaveToLocationHistogramRootWord[] = "SaveLocation";
constexpr char kSwitchToDefaultFolderReasonHistogramRootWord[] =
"SwitchToDefaultReason";
constexpr char kRecordingStartsWithCameraRootWord[] =
"RecordingStartsWithCamera";
constexpr char kCameraDisconnectionsDuringRecordingsRootWord[] =
"CameraDisconnectionsDuringRecordings";
constexpr char kCameraReconnectDurationRootWord[] = "CameraReconnectDuration";
constexpr char kRecordingCameraSizeOnStartRootWord[] =
"RecordingCameraSizeOnStart";
constexpr char kRecordingCameraPositionOnStartRootWord[] =
"RecordingCameraPositionOnStart";
constexpr char kGifRecordingFileSizeRootWord[] = "GIFRecordingFileSize";
constexpr char kScreenRecordingFileSizeRootWord[] = "ScreenRecordingFileSize";
constexpr char kNumberOfConnectedCamerasRootWord[] = "NumberOfConnectedCameras";
constexpr char kConsecutiveScreenshotRootWord[] = "ConsecutiveScreenshots";
constexpr char kQuickActionRootWord[] = "QuickAction";
constexpr char kScreenshotsPerDayRootWord[] = "ScreenshotsPerDay";
constexpr char kScreenshotsPerWeekRootWord[] = "ScreenshotsPerWeek";
constexpr char kSwitchesFromInitialModeRootWord[] =
"SwitchesFromInitialCaptureMode";
void RecordCaptureModeRecordingDurationInternal(
const std::string& histogram_name,
base::TimeDelta recording_duration) {
// Use the custom counts function instead of custom times so we can record in
// seconds instead of milliseconds. The max bucket is 3 hours.
base::UmaHistogramCustomCounts(histogram_name, recording_duration.InSeconds(),
/*min=*/1,
/*exclusive_max=*/base::Hours(3).InSeconds(),
/*buckets=*/50);
}
// Records capture mode education nudge actions, if the corresponding nudges
// were shown within a particular timeframe or the current session.
void MaybeRecordCaptureModeEducationNudgeActions() {
// Nudge action metrics are only recorded if the corresponding nudge was
// shown, so we can trigger all three arms here.
auto* nudge_manager = AnchoredNudgeManager::Get();
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kCaptureModeEducationShortcutNudge);
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kCaptureModeEducationShortcutTutorial);
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kCaptureModeEducationQuickSettingsNudge);
}
} // namespace
void RecordEndRecordingReason(EndRecordingReason reason) {
base::UmaHistogramEnumeration(
BuildHistogramName(kEndRecordingReasonHistogramRootWord,
/*behavior=*/nullptr,
/*append_ui_mode_suffix=*/true),
reason);
}
void RecordCaptureModeBarButtonType(CaptureModeBarButtonType button_type) {
base::UmaHistogramEnumeration(
BuildHistogramName(kBarButtonHistogramRootWord, /*behavior=*/nullptr,
/*append_ui_mode_suffix=*/true),
button_type);
}
void RecordCaptureModeConfiguration(CaptureModeType type,
CaptureModeSource source,
RecordingType recording_type,
AudioRecordingMode audio_mode,
const CaptureModeBehavior* behavior) {
std::string configuration_histogram_name =
BuildHistogramName(kCaptureConfigurationHistogramRootWord, behavior,
/*append_ui_mode_suffix=*/true);
base::UmaHistogramEnumeration(configuration_histogram_name,
GetConfiguration(type, source, recording_type));
if (type == CaptureModeType::kVideo &&
recording_type != RecordingType::kGif) {
base::UmaHistogramEnumeration(
BuildHistogramName(kCaptureAudioRecordingModeHistogramRootWord,
behavior,
/*append_ui_mode_suffix=*/true),
audio_mode);
}
}
void RecordGifRegionToScreenRatio(float ratio_percent) {
base::UmaHistogramPercentage(
BuildHistogramName(kGifRecordingRegionToScreenRatioHistogramRootWord,
/*behavior=*/nullptr, /*append_ui_mode_suffix=*/true),
ratio_percent);
}
void RecordCaptureModeEntryType(CaptureModeEntryType entry_type) {
base::UmaHistogramEnumeration(
BuildHistogramName(kEntryPointHistogramRootWord, /*behavior=*/nullptr,
/*append_ui_mode_suffix=*/true),
entry_type);
MaybeRecordCaptureModeEducationNudgeActions();
}
void RecordCaptureModeRecordingDuration(base::TimeDelta recording_duration,
const CaptureModeBehavior* behavior,
bool is_gif) {
RecordCaptureModeRecordingDurationInternal(
BuildHistogramName(!behavior->ShouldGifBeSupported() || !is_gif
? kRecordingDurationHistogramRootWord
: kGifRecordingDurationHistogramRootWord,
behavior,
/*append_ui_mode_suffix=*/true),
recording_duration);
}
void RecordVideoFileSizeKB(bool is_gif,
const CaptureModeBehavior* behavior,
int size_in_kb) {
if (!Shell::HasInstance()) {
// This function can be called asynchronously after the `Shell` instance had
// already been destroyed.
return;
}
if (size_in_kb < 0) {
LOG(ERROR) << "Failed to calculate the video file size. Is GIF: " << is_gif;
return;
}
base::UmaHistogramMemoryKB(
BuildHistogramName(is_gif ? kGifRecordingFileSizeRootWord
: kScreenRecordingFileSizeRootWord,
behavior, /*append_ui_mode_suffix=*/true),
size_in_kb);
}
void RecordCaptureModeSwitchesFromInitialMode(bool switched) {
base::UmaHistogramBoolean(
BuildHistogramName(kSwitchesFromInitialModeRootWord, /*behavior=*/nullptr,
/*append_ui_mode_suffix=*/false),
switched);
}
void RecordNumberOfCaptureRegionAdjustments(
int num_adjustments,
const CaptureModeBehavior* behavior) {
base::UmaHistogramCounts100(
BuildHistogramName(kCaptureRegionAdjustmentHistogramRootWord, behavior,
/*append_ui_mode_suffix=*/true),
num_adjustments);
}
void RecordNumberOfConsecutiveScreenshots(int num_consecutive_screenshots) {
if (num_consecutive_screenshots > 1) {
base::UmaHistogramCounts100(
BuildHistogramName(kConsecutiveScreenshotRootWord, /*behavior=*/nullptr,
/*append_ui_mode_suffix=*/false),
num_consecutive_screenshots);
}
}
void RecordNumberOfScreenshotsTakenInLastDay(
int num_screenshots_taken_in_last_day) {
base::UmaHistogramCounts100(
BuildHistogramName(kScreenshotsPerDayRootWord, /*behavior=*/nullptr,
/*append_ui_mode_suffix=*/false),
num_screenshots_taken_in_last_day);
}
void RecordNumberOfScreenshotsTakenInLastWeek(
int num_screenshots_taken_in_last_week) {
base::UmaHistogramCounts100(
BuildHistogramName(kScreenshotsPerWeekRootWord, /*behavior=*/nullptr,
/*append_ui_mode_suffix=*/false),
num_screenshots_taken_in_last_week);
}
void RecordScreenshotNotificationQuickAction(CaptureQuickAction action) {
base::UmaHistogramEnumeration(
BuildHistogramName(kQuickActionRootWord, /*behavior=*/nullptr,
/*append_ui_mode_suffix=*/false),
action);
}
void RecordSaveToLocation(CaptureModeSaveToLocation save_location,
const CaptureModeBehavior* behavior) {
// Save-to location metrics should not be recorded for the
// projector-inititated capture mode session.
const CaptureModeBehavior* modified_behavior =
behavior->behavior_type() == BehaviorType::kProjector ? nullptr
: behavior;
base::UmaHistogramEnumeration(
BuildHistogramName(kSaveToLocationHistogramRootWord, modified_behavior,
/*append_ui_mode_suffix=*/true),
save_location);
}
void RecordSwitchToDefaultFolderReason(
CaptureModeSwitchToDefaultReason reason) {
base::UmaHistogramEnumeration(
BuildHistogramName(kSwitchToDefaultFolderReasonHistogramRootWord,
/*behavior=*/nullptr,
/*append_ui_mode_suffix=*/true),
reason);
}
CaptureModeConfiguration GetConfiguration(CaptureModeType type,
CaptureModeSource source,
RecordingType recording_type) {
switch (source) {
case CaptureModeSource::kFullscreen:
return type == CaptureModeType::kImage
? CaptureModeConfiguration::kFullscreenScreenshot
: CaptureModeConfiguration::kFullscreenRecording;
case CaptureModeSource::kRegion:
if (type == CaptureModeType::kImage) {
return CaptureModeConfiguration::kRegionScreenshot;
}
return recording_type == RecordingType::kGif
? CaptureModeConfiguration::kRegionGifRecording
: CaptureModeConfiguration::kRegionRecording;
case CaptureModeSource::kWindow:
return type == CaptureModeType::kImage
? CaptureModeConfiguration::kWindowScreenshot
: CaptureModeConfiguration::kWindowRecording;
}
}
void RecordRecordingStartsWithCamera(bool starts_with_camera,
const CaptureModeBehavior* behavior) {
base::UmaHistogramBoolean(
BuildHistogramName(kRecordingStartsWithCameraRootWord, behavior,
/*append_ui_mode_suffix=*/true),
starts_with_camera);
}
void RecordCameraDisconnectionsDuringRecordings(int num_camera_disconnections) {
base::UmaHistogramCounts100(
BuildHistogramName(kCameraDisconnectionsDuringRecordingsRootWord,
/*behavior=*/nullptr,
/*append_ui_mode_suffix=*/true),
num_camera_disconnections);
}
void RecordNumberOfConnectedCameras(int num_camera_connected) {
base::UmaHistogramCounts100(
BuildHistogramName(kNumberOfConnectedCamerasRootWord,
/*behavior=*/nullptr,
/*append_ui_mode_suffix=*/false),
num_camera_connected);
}
void RecordCameraReconnectDuration(int length_in_seconds,
int grace_period_in_seconds) {
base::UmaHistogramCustomCounts(
BuildHistogramName(kCameraReconnectDurationRootWord, nullptr,
/*append_ui_mode_suffix=*/true),
length_in_seconds, 0, grace_period_in_seconds, grace_period_in_seconds);
}
void RecordCameraSizeOnStart(CaptureModeCameraSize camera_size) {
base::UmaHistogramEnumeration(
BuildHistogramName(kRecordingCameraSizeOnStartRootWord,
/*behavior=*/nullptr,
/*append_ui_mode_suffix=*/true),
camera_size);
}
void RecordCameraPositionOnStart(CameraPreviewSnapPosition camera_position) {
base::UmaHistogramEnumeration(
BuildHistogramName(kRecordingCameraPositionOnStartRootWord,
/*behavior=*/nullptr,
/*append_ui_mode_suffix=*/true),
camera_position);
}
void RecordRecordingStartsWithDemoTools(bool demo_tools_enabled,
const CaptureModeBehavior* behavior) {
base::UmaHistogramBoolean(
BuildHistogramName(kDemoToolsEnabledOnRecordingStartRootWord, behavior,
/*append_ui_mode_suffix=*/true),
demo_tools_enabled);
}
std::string BuildHistogramName(const char* const root_word,
const CaptureModeBehavior* behavior,
bool append_ui_mode_suffix) {
std::string histogram_name(kCaptureModeMetricCommonPrefix);
if (behavior) {
histogram_name.append(behavior->GetClientMetricComponent());
}
histogram_name.append(root_word);
if (append_ui_mode_suffix) {
histogram_name.append(Shell::Get()->IsInTabletMode() ? ".TabletMode"
: ".ClamshellMode");
}
return histogram_name;
}
} // namespace ash