// 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 "media/mojo/services/watch_time_recorder.h"

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/hash/hash.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/test_message_loop.h"
#include "base/time/time.h"
#include "components/ukm/test_ukm_recorder.h"
#include "media/base/video_codecs.h"
#include "media/base/watch_time_keys.h"
#include "media/mojo/services/media_metrics_provider.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using UkmEntry = ukm::builders::Media_BasicPlayback;

namespace media {

constexpr char kTestOrigin[] = "https://test.google.com/";

class WatchTimeRecorderTest : public testing::Test {
 public:
  WatchTimeRecorderTest()
      : computation_keys_(
            {WatchTimeKey::kAudioSrc, WatchTimeKey::kAudioMse,
             WatchTimeKey::kAudioEme, WatchTimeKey::kAudioHls,
             WatchTimeKey::kAudioVideoSrc, WatchTimeKey::kAudioVideoMse,
             WatchTimeKey::kAudioVideoEme, WatchTimeKey::kAudioVideoHls}),
        mtbr_keys_({kMeanTimeBetweenRebuffersAudioSrc,
                    kMeanTimeBetweenRebuffersAudioMse,
                    kMeanTimeBetweenRebuffersAudioEme,
                    kMeanTimeBetweenRebuffersAudioHls,
                    kMeanTimeBetweenRebuffersAudioVideoSrc,
                    kMeanTimeBetweenRebuffersAudioVideoMse,
                    kMeanTimeBetweenRebuffersAudioVideoEme,
                    kMeanTimeBetweenRebuffersAudioVideoHls}),
        smooth_keys_(
            {kRebuffersCountAudioSrc, kRebuffersCountAudioMse,
             kRebuffersCountAudioEme, kRebuffersCountAudioHls,
             kRebuffersCountAudioVideoSrc, kRebuffersCountAudioVideoMse,
             kRebuffersCountAudioVideoEme, kRebuffersCountAudioVideoHls}),
        discard_keys_({kDiscardedWatchTimeAudioSrc, kDiscardedWatchTimeAudioMse,
                       kDiscardedWatchTimeAudioEme, kDiscardedWatchTimeAudioHls,
                       kDiscardedWatchTimeAudioVideoSrc,
                       kDiscardedWatchTimeAudioVideoMse,
                       kDiscardedWatchTimeAudioVideoEme,
                       kDiscardedWatchTimeAudioVideoHls}) {
    source_id_ = test_recorder_->GetNewSourceID();
    ResetMetricRecorders();
    MediaMetricsProvider::Create(
        MediaMetricsProvider::BrowsingMode::kIncognito,
        MediaMetricsProvider::FrameStatus::kTopFrame, GetSourceId(),
        VideoDecodePerfHistory::SaveCallback(),
        base::BindRepeating(&WatchTimeRecorderTest::IsShuttingDown,
                            base::Unretained(this)),
        base::BindRepeating(&WatchTimeRecorderTest::AutoPipReason,
                            base::Unretained(this)),
        provider_.BindNewPipeAndPassReceiver());
  }

  WatchTimeRecorderTest(const WatchTimeRecorderTest&) = delete;
  WatchTimeRecorderTest& operator=(const WatchTimeRecorderTest&) = delete;

  ~WatchTimeRecorderTest() override { base::RunLoop().RunUntilIdle(); }

  void Initialize(mojom::PlaybackPropertiesPtr properties) {
    provider_->Initialize(IsMsePlayback(properties),
                          IsMsePlayback(properties)
                              ? mojom::MediaURLScheme::kUnknown
                              : mojom::MediaURLScheme::kHttp,
                          properties->media_stream_type);
    provider_->AcquireWatchTimeRecorder(std::move(properties),
                                        wtr_.BindNewPipeAndPassReceiver());
  }

  void Initialize(
      bool has_audio,
      bool has_video,
      bool is_encrypted,
      DemuxerType demuxer_type,
      mojom::MediaStreamType media_stream_type = mojom::MediaStreamType::kNone,
      RendererType renderer_type = RendererType::kRendererImpl) {
    Initialize(mojom::PlaybackProperties::New(
        has_audio, has_video, false, false, is_encrypted, false,
        media_stream_type, renderer_type, demuxer_type));
  }

  void ExpectWatchTime(const std::vector<std::string_view>& keys,
                       base::TimeDelta value,
                       const base::Location& location = FROM_HERE) {
    for (int i = 0; i <= static_cast<int>(WatchTimeKey::kWatchTimeKeyMax);
         ++i) {
      const std::string_view test_key =
          ConvertWatchTimeKeyToStringForUma(static_cast<WatchTimeKey>(i));
      if (test_key.empty())
        continue;
      if (std::ranges::contains(keys, test_key)) {
        histogram_tester_->ExpectUniqueSample(test_key, value.InMilliseconds(),
                                              1);
      } else {
        histogram_tester_->ExpectTotalCount(test_key, 0, location);
      }
    }
  }

  void ExpectHelper(const std::vector<std::string_view>& full_key_list,
                    const std::vector<std::string_view>& keys,
                    int64_t value) {
    for (auto key : full_key_list) {
      if (std::ranges::contains(keys, key))
        histogram_tester_->ExpectUniqueSample(key, value, 1);
      else
        histogram_tester_->ExpectTotalCount(key, 0);
    }
  }

  void ExpectMtbrTime(const std::vector<std::string_view>& keys,
                      base::TimeDelta value) {
    ExpectHelper(mtbr_keys_, keys, value.InMilliseconds());
  }

  void ExpectZeroRebuffers(const std::vector<std::string_view>& keys) {
    ExpectHelper(smooth_keys_, keys, 0);
  }

  void ExpectRebuffers(const std::vector<std::string_view>& keys, int count) {
    ExpectHelper(smooth_keys_, keys, count);
  }

  void ExpectAacAudioCodecProfileHistogram(AudioCodecProfile profile) {
    constexpr char kAudioCodecProfileHistogram[] =
        "Media.AudioCodecProfile.AAC";
    histogram_tester_->ExpectUniqueSample(kAudioCodecProfileHistogram,
                                          static_cast<int64_t>(profile), 1);
  }

  bool IsMsePlayback(const mojom::PlaybackPropertiesPtr& properties) {
    return properties->demuxer_type == DemuxerType::kChunkDemuxer;
  }

  void ExpectNoUkmWatchTime() {
    // We always add a source in testing.
    ASSERT_EQ(1u, test_recorder_->sources_count());
    ASSERT_EQ(0u, test_recorder_->entries_count());
  }

  void ExpectUkmWatchTime(const std::vector<std::string_view>& keys,
                          base::TimeDelta value) {
    const auto& entries =
        test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
    EXPECT_EQ(1u, entries.size());
    for (const ukm::mojom::UkmEntry* entry : entries) {
      test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
      for (auto key : keys) {
        test_recorder_->ExpectEntryMetric(entry, key.data(),
                                          value.InMilliseconds());
      }
    }
  }

  void ResetMetricRecorders() {
    histogram_tester_ = std::make_unique<base::HistogramTester>();
    // Ensure cleared global before attempting to create a new TestUkmReporter.
    test_recorder_.reset();
    test_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
    test_recorder_->UpdateSourceURL(source_id_, GURL(kTestOrigin));
  }

  mojom::SecondaryPlaybackPropertiesPtr CreateSecondaryProperties() {
    return mojom::SecondaryPlaybackProperties::New(
        AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kUnknown,
        H264PROFILE_MAIN, AudioDecoderType::kUnknown,
        VideoDecoderType::kUnknown, EncryptionScheme::kUnencrypted,
        EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
  }

  ukm::SourceId GetSourceId() { return source_id_; }

  MOCK_METHOD(bool, IsShuttingDown, ());
  MOCK_METHOD0(GetCurrentMediaTime, base::TimeDelta());
  MOCK_METHOD(PictureInPictureEventsInfo::AutoPipReason, AutoPipReason, ());

 protected:
  base::test::SingleThreadTaskEnvironment task_environment_;
  mojo::Remote<mojom::MediaMetricsProvider> provider_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;
  std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_recorder_;
  ukm::SourceId source_id_;
  mojo::Remote<mojom::WatchTimeRecorder> wtr_;
  const std::vector<WatchTimeKey> computation_keys_;
  const std::vector<std::string_view> mtbr_keys_;
  const std::vector<std::string_view> smooth_keys_;
  const std::vector<std::string_view> discard_keys_;
};

TEST_F(WatchTimeRecorderTest, TestBasicReporting) {
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(25);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(50);

  for (int i = 0; i <= static_cast<int>(WatchTimeKey::kWatchTimeKeyMax); ++i) {
    const WatchTimeKey key = static_cast<WatchTimeKey>(i);

    auto key_str = ConvertWatchTimeKeyToStringForUma(key);
    SCOPED_TRACE(key_str.empty() ? base::NumberToString(i)
                                 : std::string(key_str));

    // Values for |is_background| and |is_muted| don't matter in this test since
    // they don't prevent the muted or background keys from being recorded.
    Initialize(true, false, true, DemuxerType::kChunkDemuxer);
    wtr_->UpdateSecondaryProperties(CreateSecondaryProperties());

    wtr_->RecordWatchTime(WatchTimeKey::kWatchTimeKeyMax, kWatchTime1);
    wtr_->RecordWatchTime(key, kWatchTime1);
    wtr_->RecordWatchTime(key, kWatchTime2);
    base::RunLoop().RunUntilIdle();

    // Nothing should be recorded yet since we haven't finalized.
    ExpectWatchTime({}, base::TimeDelta());

    // Only the requested key should be finalized.
    wtr_->FinalizeWatchTime({key});
    base::RunLoop().RunUntilIdle();

    if (!key_str.empty())
      ExpectWatchTime({key_str}, kWatchTime2);

    // These keys are only reported for a full finalize.
    ExpectMtbrTime({}, base::TimeDelta());
    ExpectZeroRebuffers({});
    ExpectNoUkmWatchTime();

    // Verify nothing else is recorded except for what we finalized above.
    ResetMetricRecorders();
    wtr_.reset();
    base::RunLoop().RunUntilIdle();
    ExpectWatchTime({}, base::TimeDelta());
    ExpectMtbrTime({}, base::TimeDelta());
    ExpectZeroRebuffers({});

    switch (key) {
      case WatchTimeKey::kAudioAll:
      case WatchTimeKey::kAudioBackgroundAll:
      case WatchTimeKey::kAudioVideoAll:
      case WatchTimeKey::kAudioVideoBackgroundAll:
      case WatchTimeKey::kAudioVideoMutedAll:
      case WatchTimeKey::kVideoAll:
      case WatchTimeKey::kVideoBackgroundAll:
        ExpectUkmWatchTime({UkmEntry::kWatchTimeName}, kWatchTime2);
        break;

      // These keys are not reported, instead we boolean flags for each type.
      case WatchTimeKey::kAudioDisplayFullscreen:
      case WatchTimeKey::kAudioDisplayInline:
      case WatchTimeKey::kAudioDisplayPictureInPicture:
      case WatchTimeKey::kAudioMse:
      case WatchTimeKey::kAudioEme:
      case WatchTimeKey::kAudioSrc:
      case WatchTimeKey::kAudioHls:
      case WatchTimeKey::kAudioEmbeddedExperience:
      case WatchTimeKey::kAudioBackgroundMse:
      case WatchTimeKey::kAudioBackgroundEme:
      case WatchTimeKey::kAudioBackgroundSrc:
      case WatchTimeKey::kAudioBackgroundHls:
      case WatchTimeKey::kAudioBackgroundEmbeddedExperience:
      case WatchTimeKey::kAudioVideoMse:
      case WatchTimeKey::kAudioVideoEme:
      case WatchTimeKey::kAudioVideoSrc:
      case WatchTimeKey::kAudioVideoHls:
      case WatchTimeKey::kAudioVideoEmbeddedExperience:
      case WatchTimeKey::kAudioVideoMutedMse:
      case WatchTimeKey::kAudioVideoMutedEme:
      case WatchTimeKey::kAudioVideoMutedSrc:
      case WatchTimeKey::kAudioVideoMutedHls:
      case WatchTimeKey::kAudioVideoMutedEmbeddedExperience:
      case WatchTimeKey::kAudioVideoBackgroundMse:
      case WatchTimeKey::kAudioVideoBackgroundEme:
      case WatchTimeKey::kAudioVideoBackgroundSrc:
      case WatchTimeKey::kAudioVideoBackgroundHls:
      case WatchTimeKey::kAudioVideoBackgroundEmbeddedExperience:
      case WatchTimeKey::kVideoMse:
      case WatchTimeKey::kVideoEme:
      case WatchTimeKey::kVideoSrc:
      case WatchTimeKey::kVideoHls:
      case WatchTimeKey::kVideoEmbeddedExperience:
      case WatchTimeKey::kVideoBackgroundMse:
      case WatchTimeKey::kVideoBackgroundEme:
      case WatchTimeKey::kVideoBackgroundSrc:
      case WatchTimeKey::kVideoBackgroundHls:
      case WatchTimeKey::kVideoBackgroundEmbeddedExperience:
      case WatchTimeKey::kAudioVideoMediaFoundationAll:
      case WatchTimeKey::kAudioVideoMediaFoundationEme:
      case WatchTimeKey::kAudioVideoHdrAll:
      case WatchTimeKey::kAudioVideoHdrEme:
      case WatchTimeKey::kAudioVideoSdrAll:
      case WatchTimeKey::kAudioVideoSdrEme:
      case WatchTimeKey::kAudioVideoMediaFoundationHdrAll:
      case WatchTimeKey::kAudioVideoMediaFoundationHdrEme:
      case WatchTimeKey::kAudioVideoMediaFoundationSdrAll:
      case WatchTimeKey::kAudioVideoMediaFoundationSdrEme:
      case WatchTimeKey::kAudioVideoDominantVisibleContent:
      case WatchTimeKey::kAudioVideoAuxiliaryVisibleContent:
      case WatchTimeKey::kAudioVideoMutedDominantVisibleContent:
      case WatchTimeKey::kAudioVideoMutedAuxiliaryVisibleContent:
      case WatchTimeKey::kVideoDominantVisibleContent:
      case WatchTimeKey::kVideoAuxiliaryVisibleContent:
        ExpectUkmWatchTime({}, base::TimeDelta());
        break;

      // These keys roll up into the battery watch time field.
      case WatchTimeKey::kAudioBattery:
      case WatchTimeKey::kAudioBackgroundBattery:
      case WatchTimeKey::kAudioVideoBattery:
      case WatchTimeKey::kAudioVideoMutedBattery:
      case WatchTimeKey::kAudioVideoBackgroundBattery:
      case WatchTimeKey::kVideoBattery:
      case WatchTimeKey::kVideoBackgroundBattery:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_BatteryName}, kWatchTime2);
        break;

      // These keys roll up into the AC watch time field.
      case WatchTimeKey::kAudioAc:
      case WatchTimeKey::kAudioBackgroundAc:
      case WatchTimeKey::kAudioVideoAc:
      case WatchTimeKey::kAudioVideoBackgroundAc:
      case WatchTimeKey::kAudioVideoMutedAc:
      case WatchTimeKey::kVideoAc:
      case WatchTimeKey::kVideoBackgroundAc:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_ACName}, kWatchTime2);
        break;

      case WatchTimeKey::kAudioVideoDisplayFullscreen:
      case WatchTimeKey::kAudioVideoMutedDisplayFullscreen:
      case WatchTimeKey::kVideoDisplayFullscreen:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_DisplayFullscreenName},
                           kWatchTime2);
        break;

      case WatchTimeKey::kAudioVideoDisplayInline:
      case WatchTimeKey::kAudioVideoMutedDisplayInline:
      case WatchTimeKey::kVideoDisplayInline:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_DisplayInlineName},
                           kWatchTime2);
        break;

      case WatchTimeKey::kAudioVideoDisplayPictureInPicture:
      case WatchTimeKey::kAudioVideoMutedDisplayPictureInPicture:
      case WatchTimeKey::kVideoDisplayPictureInPicture:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_DisplayPictureInPictureName},
                           kWatchTime2);
        break;

      case WatchTimeKey::kAudioVideoAutoPipMediaPlayback:
      case WatchTimeKey::kAudioAutoPipMediaPlayback:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_AutoPipName}, kWatchTime2);
        break;

      case WatchTimeKey::kAudioNativeControlsOn:
      case WatchTimeKey::kAudioVideoNativeControlsOn:
      case WatchTimeKey::kAudioVideoMutedNativeControlsOn:
      case WatchTimeKey::kVideoNativeControlsOn:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_NativeControlsOnName},
                           kWatchTime2);
        break;

      case WatchTimeKey::kAudioNativeControlsOff:
      case WatchTimeKey::kAudioVideoNativeControlsOff:
      case WatchTimeKey::kAudioVideoMutedNativeControlsOff:
      case WatchTimeKey::kVideoNativeControlsOff:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_NativeControlsOffName},
                           kWatchTime2);
        break;
    }

    ResetMetricRecorders();
  }
}

TEST_F(WatchTimeRecorderTest, TestBasicReportingMediaStream) {
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(25);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(50);

  for (int i = 0; i <= static_cast<int>(WatchTimeKey::kWatchTimeKeyMax); ++i) {
    const WatchTimeKey key = static_cast<WatchTimeKey>(i);

    auto key_str = ConvertWatchTimeKeyToStringForUma(key);
    SCOPED_TRACE(key_str.empty() ? base::NumberToString(i)
                                 : std::string(key_str));

    // Values for |is_background| and |is_muted| don't matter in this test since
    // they don't prevent the muted or background keys from being recorded.
    Initialize(true, false, true, DemuxerType::kChunkDemuxer,
               mojom::MediaStreamType::kLocalDeviceCapture);
    wtr_->UpdateSecondaryProperties(CreateSecondaryProperties());

    wtr_->RecordWatchTime(WatchTimeKey::kWatchTimeKeyMax, kWatchTime1);
    wtr_->RecordWatchTime(key, kWatchTime1);
    wtr_->RecordWatchTime(key, kWatchTime2);
    base::RunLoop().RunUntilIdle();

    // Nothing should be recorded yet since we haven't finalized.
    ExpectWatchTime({}, base::TimeDelta());

    // Only the requested key should be finalized.
    wtr_->FinalizeWatchTime({key});
    base::RunLoop().RunUntilIdle();

    if (!key_str.empty())
      ExpectWatchTime({}, base::TimeDelta());

    ExpectMtbrTime({}, base::TimeDelta());
    ExpectZeroRebuffers({});
    ExpectNoUkmWatchTime();

    ResetMetricRecorders();
    wtr_.reset();
    base::RunLoop().RunUntilIdle();
    ExpectWatchTime({}, base::TimeDelta());
    ExpectMtbrTime({}, base::TimeDelta());
    ExpectZeroRebuffers({});

    // UKM watch time should be recorded even with no UMA.
    switch (key) {
      case WatchTimeKey::kAudioAll:
      case WatchTimeKey::kAudioBackgroundAll:
      case WatchTimeKey::kAudioVideoAll:
      case WatchTimeKey::kAudioVideoBackgroundAll:
      case WatchTimeKey::kAudioVideoMutedAll:
      case WatchTimeKey::kVideoAll:
      case WatchTimeKey::kVideoBackgroundAll:
        ExpectUkmWatchTime({UkmEntry::kWatchTimeName}, kWatchTime2);
        break;

      // These keys are not reported, instead we boolean flags for each type.
      case WatchTimeKey::kAudioDisplayFullscreen:
      case WatchTimeKey::kAudioDisplayInline:
      case WatchTimeKey::kAudioDisplayPictureInPicture:
      case WatchTimeKey::kAudioMse:
      case WatchTimeKey::kAudioEme:
      case WatchTimeKey::kAudioSrc:
      case WatchTimeKey::kAudioHls:
      case WatchTimeKey::kAudioEmbeddedExperience:
      case WatchTimeKey::kAudioBackgroundMse:
      case WatchTimeKey::kAudioBackgroundEme:
      case WatchTimeKey::kAudioBackgroundSrc:
      case WatchTimeKey::kAudioBackgroundHls:
      case WatchTimeKey::kAudioBackgroundEmbeddedExperience:
      case WatchTimeKey::kAudioVideoMse:
      case WatchTimeKey::kAudioVideoEme:
      case WatchTimeKey::kAudioVideoSrc:
      case WatchTimeKey::kAudioVideoHls:
      case WatchTimeKey::kAudioVideoEmbeddedExperience:
      case WatchTimeKey::kAudioVideoMutedMse:
      case WatchTimeKey::kAudioVideoMutedEme:
      case WatchTimeKey::kAudioVideoMutedSrc:
      case WatchTimeKey::kAudioVideoMutedHls:
      case WatchTimeKey::kAudioVideoMutedEmbeddedExperience:
      case WatchTimeKey::kAudioVideoBackgroundMse:
      case WatchTimeKey::kAudioVideoBackgroundEme:
      case WatchTimeKey::kAudioVideoBackgroundSrc:
      case WatchTimeKey::kAudioVideoBackgroundHls:
      case WatchTimeKey::kAudioVideoBackgroundEmbeddedExperience:
      case WatchTimeKey::kVideoMse:
      case WatchTimeKey::kVideoEme:
      case WatchTimeKey::kVideoSrc:
      case WatchTimeKey::kVideoHls:
      case WatchTimeKey::kVideoEmbeddedExperience:
      case WatchTimeKey::kVideoBackgroundMse:
      case WatchTimeKey::kVideoBackgroundEme:
      case WatchTimeKey::kVideoBackgroundSrc:
      case WatchTimeKey::kVideoBackgroundHls:
      case WatchTimeKey::kVideoBackgroundEmbeddedExperience:
      case WatchTimeKey::kAudioVideoMediaFoundationAll:
      case WatchTimeKey::kAudioVideoMediaFoundationEme:
      case WatchTimeKey::kAudioVideoHdrAll:
      case WatchTimeKey::kAudioVideoHdrEme:
      case WatchTimeKey::kAudioVideoSdrAll:
      case WatchTimeKey::kAudioVideoSdrEme:
      case WatchTimeKey::kAudioVideoMediaFoundationHdrAll:
      case WatchTimeKey::kAudioVideoMediaFoundationHdrEme:
      case WatchTimeKey::kAudioVideoMediaFoundationSdrAll:
      case WatchTimeKey::kAudioVideoMediaFoundationSdrEme:
      case WatchTimeKey::kAudioVideoDominantVisibleContent:
      case WatchTimeKey::kAudioVideoAuxiliaryVisibleContent:
      case WatchTimeKey::kAudioVideoMutedDominantVisibleContent:
      case WatchTimeKey::kAudioVideoMutedAuxiliaryVisibleContent:
      case WatchTimeKey::kVideoDominantVisibleContent:
      case WatchTimeKey::kVideoAuxiliaryVisibleContent:
        ExpectUkmWatchTime({}, base::TimeDelta());
        break;

      // These keys roll up into the battery watch time field.
      case WatchTimeKey::kAudioBattery:
      case WatchTimeKey::kAudioBackgroundBattery:
      case WatchTimeKey::kAudioVideoBattery:
      case WatchTimeKey::kAudioVideoMutedBattery:
      case WatchTimeKey::kAudioVideoBackgroundBattery:
      case WatchTimeKey::kVideoBattery:
      case WatchTimeKey::kVideoBackgroundBattery:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_BatteryName}, kWatchTime2);
        break;

      // These keys roll up into the AC watch time field.
      case WatchTimeKey::kAudioAc:
      case WatchTimeKey::kAudioBackgroundAc:
      case WatchTimeKey::kAudioVideoAc:
      case WatchTimeKey::kAudioVideoBackgroundAc:
      case WatchTimeKey::kAudioVideoMutedAc:
      case WatchTimeKey::kVideoAc:
      case WatchTimeKey::kVideoBackgroundAc:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_ACName}, kWatchTime2);
        break;

      case WatchTimeKey::kAudioVideoDisplayFullscreen:
      case WatchTimeKey::kAudioVideoMutedDisplayFullscreen:
      case WatchTimeKey::kVideoDisplayFullscreen:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_DisplayFullscreenName},
                           kWatchTime2);
        break;

      case WatchTimeKey::kAudioVideoDisplayInline:
      case WatchTimeKey::kAudioVideoMutedDisplayInline:
      case WatchTimeKey::kVideoDisplayInline:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_DisplayInlineName},
                           kWatchTime2);
        break;

      case WatchTimeKey::kAudioVideoAutoPipMediaPlayback:
      case WatchTimeKey::kAudioAutoPipMediaPlayback:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_AutoPipName}, kWatchTime2);
        break;

      case WatchTimeKey::kAudioVideoDisplayPictureInPicture:
      case WatchTimeKey::kAudioVideoMutedDisplayPictureInPicture:
      case WatchTimeKey::kVideoDisplayPictureInPicture:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_DisplayPictureInPictureName},
                           kWatchTime2);
        break;

      case WatchTimeKey::kAudioNativeControlsOn:
      case WatchTimeKey::kAudioVideoNativeControlsOn:
      case WatchTimeKey::kAudioVideoMutedNativeControlsOn:
      case WatchTimeKey::kVideoNativeControlsOn:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_NativeControlsOnName},
                           kWatchTime2);
        break;

      case WatchTimeKey::kAudioNativeControlsOff:
      case WatchTimeKey::kAudioVideoNativeControlsOff:
      case WatchTimeKey::kAudioVideoMutedNativeControlsOff:
      case WatchTimeKey::kVideoNativeControlsOff:
        ExpectUkmWatchTime({UkmEntry::kWatchTime_NativeControlsOffName},
                           kWatchTime2);
        break;
    }

    ResetMetricRecorders();
  }
}

TEST_F(WatchTimeRecorderTest, TestRebufferingMetrics) {
  Initialize(true, false, true, DemuxerType::kChunkDemuxer);

  constexpr base::TimeDelta kWatchTime = base::Seconds(50);
  for (auto key : computation_keys_)
    wtr_->RecordWatchTime(key, kWatchTime);
  wtr_->UpdateUnderflowCount(1);
  wtr_->UpdateUnderflowCount(2);

  // Trigger finalization of everything.
  wtr_->FinalizeWatchTime({});
  base::RunLoop().RunUntilIdle();

  ExpectMtbrTime(mtbr_keys_, kWatchTime / 2);
  ExpectRebuffers(smooth_keys_, 2);

  // Now rerun the test without any rebuffering.
  ResetMetricRecorders();
  for (auto key : computation_keys_)
    wtr_->RecordWatchTime(key, kWatchTime);
  wtr_->FinalizeWatchTime({});
  base::RunLoop().RunUntilIdle();

  ExpectMtbrTime({}, base::TimeDelta());
  ExpectZeroRebuffers(smooth_keys_);

  // Now rerun the test with a small amount of watch time and ensure rebuffering
  // isn't recorded because we haven't met the watch time requirements.
  ResetMetricRecorders();
  constexpr base::TimeDelta kWatchTimeShort = base::Seconds(5);
  for (auto key : computation_keys_)
    wtr_->RecordWatchTime(key, kWatchTimeShort);
  wtr_->UpdateUnderflowCount(1);
  wtr_->UpdateUnderflowCount(2);
  wtr_->FinalizeWatchTime({});
  base::RunLoop().RunUntilIdle();

  // Nothing should be logged since this doesn't meet requirements.
  ExpectMtbrTime({}, base::TimeDelta());
  for (auto key : smooth_keys_)
    histogram_tester_->ExpectTotalCount(key, 0);
}

TEST_F(WatchTimeRecorderTest, TestRebufferingMetricsMediaStream) {
  Initialize(true, false, true, DemuxerType::kChunkDemuxer,
             mojom::MediaStreamType::kLocalDeviceCapture);

  constexpr base::TimeDelta kWatchTime = base::Seconds(50);
  for (auto key : computation_keys_)
    wtr_->RecordWatchTime(key, kWatchTime);
  wtr_->UpdateUnderflowCount(1);
  wtr_->UpdateUnderflowCount(2);

  // Trigger finalization of everything.
  wtr_->FinalizeWatchTime({});
  base::RunLoop().RunUntilIdle();

  ExpectMtbrTime({}, base::TimeDelta());
  ExpectRebuffers({}, 0);

  // Now rerun the test without any rebuffering.
  ResetMetricRecorders();
  for (auto key : computation_keys_)
    wtr_->RecordWatchTime(key, kWatchTime);
  wtr_->FinalizeWatchTime({});
  base::RunLoop().RunUntilIdle();

  ExpectMtbrTime({}, base::TimeDelta());
  ExpectRebuffers({}, 0);
}

TEST_F(WatchTimeRecorderTest, TestDiscardMetrics) {
  Initialize(true, false, true, DemuxerType::kChunkDemuxer);
  wtr_->UpdateSecondaryProperties(CreateSecondaryProperties());

  constexpr base::TimeDelta kWatchTime = base::Seconds(5);
  for (auto key : computation_keys_)
    wtr_->RecordWatchTime(key, kWatchTime);

  // Trigger finalization of everything.
  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // No standard watch time should be recorded because it falls below the
  // reporting threshold.
  ExpectWatchTime({}, base::TimeDelta());

  // Verify the time was instead logged to the discard keys.
  for (auto key : discard_keys_) {
    histogram_tester_->ExpectUniqueSample(key, kWatchTime.InMilliseconds(), 1);
  }

  // UKM watch time won't be logged because we aren't sending "All" keys.
  ExpectUkmWatchTime({}, base::TimeDelta());
}

TEST_F(WatchTimeRecorderTest, TestDiscardMetricsMediaStream) {
  Initialize(true, false, true, DemuxerType::kChunkDemuxer,
             mojom::MediaStreamType::kLocalDeviceCapture);
  wtr_->UpdateSecondaryProperties(CreateSecondaryProperties());

  constexpr base::TimeDelta kWatchTime = base::Seconds(5);
  for (auto key : computation_keys_)
    wtr_->RecordWatchTime(key, kWatchTime);

  // Trigger finalization of everything.
  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // No watch time and no discard metrics should be logged.
  ExpectWatchTime({}, base::TimeDelta());
  for (auto key : discard_keys_) {
    histogram_tester_->ExpectTotalCount(key, 0);
  }

  // UKM watch time won't be logged because we aren't sending "All" keys.
  ExpectUkmWatchTime({}, base::TimeDelta());
}

#define EXPECT_UKM(name, value) \
  test_recorder_->ExpectEntryMetric(entry, name, value)
#define EXPECT_NO_UKM(name) \
  EXPECT_FALSE(test_recorder_->EntryHasMetric(entry, name))
#define EXPECT_HAS_UKM(name) \
  EXPECT_TRUE(test_recorder_->EntryHasMetric(entry, name));

TEST_F(WatchTimeRecorderTest, TestFinalizeNoDuplication) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  // Verify that UKM is reported along with the watch time.
  constexpr base::TimeDelta kWatchTime = base::Seconds(4);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);

  // Finalize everything. UKM is only recorded at destruction, so this should do
  // nothing.
  wtr_->FinalizeWatchTime({});
  base::RunLoop().RunUntilIdle();

  // No watch time should have been recorded since this is below the UMA report
  // threshold.
  ExpectWatchTime({}, base::TimeDelta());
  ExpectMtbrTime({}, base::TimeDelta());
  ExpectZeroRebuffers({});
  ExpectNoUkmWatchTime();

  const auto& empty_entries =
      test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(0u, empty_entries.size());

  // Verify UKM is logged at destruction time.
  ResetMetricRecorders();
  wtr_.reset();
  base::RunLoop().RunUntilIdle();
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));

    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kAudioCodecName,
               static_cast<int>(secondary_properties->audio_codec));
    EXPECT_UKM(UkmEntry::kVideoCodecName,
               static_cast<int>(secondary_properties->video_codec));
    EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
               static_cast<int64_t>(secondary_properties->audio_codec_profile));
    EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
               secondary_properties->video_codec_profile);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(
        UkmEntry::kAudioEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
    EXPECT_UKM(
        UkmEntry::kVideoEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->video_encryption_scheme));
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kRendererTypeName,
               static_cast<int64_t>(properties->renderer_type));
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
    EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
    EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
               secondary_properties->natural_size.width());
    EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
               secondary_properties->natural_size.height());
    EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime.InMilliseconds());
    EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);

    EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_AutoPipName);
  }
}

TEST_F(WatchTimeRecorderTest, FinalizeWithoutWatchTime) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  // Finalize everything. UKM is only recorded at destruction, so this should do
  // nothing.
  wtr_->FinalizeWatchTime({});
  base::RunLoop().RunUntilIdle();

  // No watch time should have been recorded even though a finalize event will
  // be sent, however a UKM entry with the playback properties will still be
  // generated.
  ExpectWatchTime({}, base::TimeDelta());
  ExpectMtbrTime({}, base::TimeDelta());
  ExpectZeroRebuffers({});
  ExpectNoUkmWatchTime();

  const auto& empty_entries =
      test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(0u, empty_entries.size());

  // Destructing the recorder should generate a UKM report though.
  ResetMetricRecorders();
  wtr_.reset();
  base::RunLoop().RunUntilIdle();
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));

    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kAudioCodecName,
               static_cast<int>(secondary_properties->audio_codec));
    EXPECT_UKM(UkmEntry::kVideoCodecName,
               static_cast<int>(secondary_properties->video_codec));
    EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
               static_cast<int64_t>(secondary_properties->audio_codec_profile));
    EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
               secondary_properties->video_codec_profile);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(
        UkmEntry::kAudioEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
    EXPECT_UKM(
        UkmEntry::kVideoEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->video_encryption_scheme));
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
    EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
    EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
               secondary_properties->natural_size.width());
    EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
               secondary_properties->natural_size.height());
    EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);

    EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
    EXPECT_NO_UKM(UkmEntry::kWatchTimeName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_AutoPipName);
  }
}

TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideo) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kXHE_AAC,
          H264PROFILE_MAIN, AudioDecoderType::kUnknown,
          VideoDecoderType::kUnknown, EncryptionScheme::kCenc,
          EncryptionScheme::kCbcs, gfx::Size(800, 600));
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  constexpr base::TimeDelta kWatchTime = base::Seconds(4);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);
  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  ExpectAacAudioCodecProfileHistogram(
      secondary_properties->audio_codec_profile);

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));

    EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime.InMilliseconds());
    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kAudioCodecName,
               static_cast<int>(secondary_properties->audio_codec));
    EXPECT_UKM(UkmEntry::kVideoCodecName,
               static_cast<int>(secondary_properties->video_codec));
    EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
               static_cast<int64_t>(secondary_properties->audio_codec_profile));
    EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
               secondary_properties->video_codec_profile);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(
        UkmEntry::kAudioEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
    EXPECT_UKM(
        UkmEntry::kVideoEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->video_encryption_scheme));
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
    EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
    EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
               secondary_properties->natural_size.width());
    EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
               secondary_properties->natural_size.height());
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
    EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);

    EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_AutoPipName);
  }
}

TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoWithExtras) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, true, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kChunkDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kOpus, VideoCodec::kVP9, AudioCodecProfile::kUnknown,
          VP9PROFILE_PROFILE0, AudioDecoderType::kUnknown,
          VideoDecoderType::kUnknown, EncryptionScheme::kUnencrypted,
          EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  EXPECT_CALL(*this, AutoPipReason)
      .WillRepeatedly(testing::Return(
          PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback));

  constexpr base::TimeDelta kWatchTime = base::Seconds(54);
  const base::TimeDelta kWatchTime2 = kWatchTime * 2;
  const base::TimeDelta kWatchTime3 = kWatchTime / 3;
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime2);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAc, kWatchTime);

  // Ensure partial finalize does not affect final report.
  wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoAc});
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoBattery, kWatchTime);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoNativeControlsOn, kWatchTime);
  wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoNativeControlsOn});
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoNativeControlsOff, kWatchTime);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayFullscreen,
                        kWatchTime3);
  wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoDisplayFullscreen});
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayInline, kWatchTime3);
  wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoDisplayInline});
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayPictureInPicture,
                        kWatchTime3);
  wtr_->UpdateUnderflowCount(3);
  constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(500);
  wtr_->UpdateUnderflowDuration(2, kUnderflowDuration);
  wtr_->UpdateVideoDecodeStats(10, 2);
  wtr_->OnError(PIPELINE_ERROR_DECODE);

  secondary_properties->audio_decoder = AudioDecoderType::kMojo;
  secondary_properties->video_decoder = VideoDecoderType::kMojo;
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  wtr_->SetAutoplayInitiated(true);

  wtr_->OnDurationChanged(base::Seconds(9500));

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime2.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_ACName, kWatchTime.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_BatteryName, kWatchTime.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_NativeControlsOnName,
               kWatchTime.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_NativeControlsOffName,
               kWatchTime.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_DisplayFullscreenName,
               kWatchTime3.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_DisplayInlineName,
               kWatchTime3.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName,
               kWatchTime3.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_AutoPipName, kWatchTime3.InMilliseconds());
    EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
               kWatchTime2.InMilliseconds() / 3);
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);

    // Values taken from .cc private enumeration (and should never change).
    EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 2);
    EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 5);

    // Duration should be rounded up.
    EXPECT_UKM(UkmEntry::kDurationName, 10000000);

    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kAudioCodecName,
               static_cast<int>(secondary_properties->audio_codec));
    EXPECT_UKM(UkmEntry::kVideoCodecName,
               static_cast<int>(secondary_properties->video_codec));
    EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
               static_cast<int64_t>(secondary_properties->audio_codec_profile));
    EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
               secondary_properties->video_codec_profile);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(
        UkmEntry::kAudioEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
    EXPECT_UKM(
        UkmEntry::kVideoEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->video_encryption_scheme));
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_ERROR_DECODE);
    EXPECT_UKM(UkmEntry::kRebuffersCountName, 3);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 2);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
               kUnderflowDuration.InMilliseconds());
    EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, 10);
    EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, 2);
    EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
               secondary_properties->natural_size.width());
    EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
               secondary_properties->natural_size.height());
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, true);
  }
}

TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoBackgroundMuted) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, true, true, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  constexpr base::TimeDelta kWatchTime = base::Seconds(54);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoBackgroundAll, kWatchTime);
  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  if (secondary_properties->audio_codec == AudioCodec::kAAC) {
    ExpectAacAudioCodecProfileHistogram(
        secondary_properties->audio_codec_profile);
  }

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));

    EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime.InMilliseconds());
    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kAudioCodecName,
               static_cast<int>(secondary_properties->audio_codec));
    EXPECT_UKM(UkmEntry::kVideoCodecName,
               static_cast<int>(secondary_properties->video_codec));
    EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
               static_cast<int64_t>(secondary_properties->audio_codec_profile));
    EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
               secondary_properties->video_codec_profile);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(
        UkmEntry::kAudioEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
    EXPECT_UKM(
        UkmEntry::kVideoEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->video_encryption_scheme));
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
    EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
    EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
               secondary_properties->natural_size.width());
    EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
               secondary_properties->natural_size.height());
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
    EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);

    EXPECT_NO_UKM(UkmEntry::kDurationName);
    EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_AutoPipName);
  }
}

TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoDuration) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  wtr_->OnDurationChanged(base::Seconds(12345));
  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  if (secondary_properties->audio_codec == AudioCodec::kAAC) {
    ExpectAacAudioCodecProfileHistogram(
        secondary_properties->audio_codec_profile);
  }

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));

    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kAudioCodecName,
               static_cast<int>(secondary_properties->audio_codec));
    EXPECT_UKM(UkmEntry::kVideoCodecName,
               static_cast<int>(secondary_properties->video_codec));
    EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
               static_cast<int64_t>(secondary_properties->audio_codec_profile));
    EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
               secondary_properties->video_codec_profile);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(
        UkmEntry::kAudioEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
    EXPECT_UKM(
        UkmEntry::kVideoEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->video_encryption_scheme));
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
    EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
    EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
               secondary_properties->natural_size.width());
    EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
               secondary_properties->natural_size.height());
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
    EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);

    // Duration should be rounded to the most significant digit.
    EXPECT_UKM(UkmEntry::kDurationName, 10000000);

    EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_AutoPipName);
  }
}

TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoDurationInfinite) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  wtr_->OnDurationChanged(kInfiniteDuration);
  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  if (secondary_properties->audio_codec == AudioCodec::kAAC) {
    ExpectAacAudioCodecProfileHistogram(
        secondary_properties->audio_codec_profile);
  }

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));

    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kAudioCodecName,
               static_cast<int>(secondary_properties->audio_codec));
    EXPECT_UKM(UkmEntry::kVideoCodecName,
               static_cast<int>(secondary_properties->video_codec));
    EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
               static_cast<int64_t>(secondary_properties->audio_codec_profile));
    EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
               secondary_properties->video_codec_profile);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(
        UkmEntry::kAudioEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->audio_encryption_scheme));
    EXPECT_UKM(
        UkmEntry::kVideoEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties->video_encryption_scheme));
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
    EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
    EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
               secondary_properties->natural_size.width());
    EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
               secondary_properties->natural_size.height());
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
    EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 0);
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);

    // Duration should be unrecorded when infinite.
    EXPECT_NO_UKM(UkmEntry::kDurationName);
    EXPECT_NO_UKM(UkmEntry::kWatchTimeName);
    EXPECT_NO_UKM(UkmEntry::kMeanTimeBetweenRebuffersName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_ACName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_BatteryName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOnName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_NativeControlsOffName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayFullscreenName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayInlineName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
    EXPECT_NO_UKM(UkmEntry::kWatchTime_AutoPipName);
  }
}

TEST_F(WatchTimeRecorderTest, BasicUkmMediaStreamType) {
  std::vector<mojom::MediaStreamType> media_stream_types{
      mojom::MediaStreamType::kLocalElementCapture,
      mojom::MediaStreamType::kLocalDeviceCapture,
      mojom::MediaStreamType::kLocalTabCapture,
      mojom::MediaStreamType::kLocalDesktopCapture,
      mojom::MediaStreamType::kLocalDisplayCapture,
      mojom::MediaStreamType::kRemote,
      mojom::MediaStreamType::kNone,
  };

  for (const auto& media_stream_type : media_stream_types) {
    mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
        true, true, false, false, false, false, media_stream_type,
        RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
    Initialize(properties.Clone());
    wtr_->UpdateSecondaryProperties(CreateSecondaryProperties());

    constexpr base::TimeDelta kWatchTime = base::Seconds(1);
    wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);
    wtr_.reset();
    base::RunLoop().RunUntilIdle();

    const auto& entries =
        test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
    ASSERT_EQ(1u, entries.size());

    // Check that the media stream type is set correctly.
    for (const ukm::mojom::UkmEntry* entry : entries) {
      EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
                 static_cast<int64_t>(media_stream_type));
    }
    ResetMetricRecorders();
  }
}

// Might happen due to timing issues, so ensure no crashes.
TEST_F(WatchTimeRecorderTest, NoSecondaryProperties) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, true, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kChunkDemuxer);
  Initialize(properties.Clone());

  constexpr base::TimeDelta kWatchTime = base::Seconds(54);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);
  wtr_.reset();
  base::RunLoop().RunUntilIdle();
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(0u, entries.size());
}

TEST_F(WatchTimeRecorderTest, SingleSecondaryPropertiesUnknownToKnown) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, true, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kChunkDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kUnknown, VideoCodec::kUnknown,
          AudioCodecProfile::kUnknown, VIDEO_CODEC_PROFILE_UNKNOWN,
          AudioDecoderType::kUnknown, VideoDecoderType::kUnknown,
          EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted,
          gfx::Size(800, 600));
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());

  constexpr base::TimeDelta kWatchTime = base::Seconds(54);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime);

  mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kXHE_AAC,
          H264PROFILE_MAIN, AudioDecoderType::kFFmpeg,
          VideoDecoderType::kFFmpeg, EncryptionScheme::kUnencrypted,
          EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
  wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // Since we only transitioned unknown values to known values, there should be
  // only a single UKM entry.
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
    EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime.InMilliseconds());
    EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 1);
    EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 2);
    EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
    EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
    EXPECT_UKM(UkmEntry::kAudioCodecName,
               static_cast<int>(secondary_properties2->audio_codec));
    EXPECT_UKM(UkmEntry::kVideoCodecName,
               static_cast<int>(secondary_properties2->video_codec));
    EXPECT_UKM(
        UkmEntry::kAudioCodecProfileName,
        static_cast<int64_t>(secondary_properties2->audio_codec_profile));
    EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
               secondary_properties2->video_codec_profile);
    EXPECT_UKM(
        UkmEntry::kAudioEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties2->audio_encryption_scheme));
    EXPECT_UKM(
        UkmEntry::kVideoEncryptionSchemeName,
        static_cast<int64_t>(secondary_properties2->video_encryption_scheme));
    EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
               secondary_properties2->natural_size.width());
    EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
               secondary_properties2->natural_size.height());
    EXPECT_NO_UKM(UkmEntry::kDurationName);
  }
}

TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalize) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, true, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kChunkDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kOpus, VideoCodec::kVP9, AudioCodecProfile::kUnknown,
          VP9PROFILE_PROFILE0, AudioDecoderType::kMojo, VideoDecoderType::kMojo,
          EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted,
          gfx::Size(400, 300));
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());

  constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(54);
  const int kUnderflowCount1 = 2;
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime1);
  wtr_->UpdateUnderflowCount(kUnderflowCount1);
  wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration);

  constexpr int kDecodedFrameCount1 = 10;
  constexpr int kDroppedFrameCount1 = 2;
  wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1, kDroppedFrameCount1);

  mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kUnknown,
          H264PROFILE_MAIN, AudioDecoderType::kFFmpeg,
          VideoDecoderType::kFFmpeg, EncryptionScheme::kCenc,
          EncryptionScheme::kCenc, gfx::Size(800, 600));
  wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());

  constexpr base::TimeDelta kWatchTime2 = base::Seconds(25);
  const int kUnderflowCount2 = 3;

  // Watch time and underflow counts continue to accumulate during property
  // changes, so we report the sum here instead of just kWatchTime2.
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll,
                        kWatchTime1 + kWatchTime2);
  wtr_->UpdateUnderflowCount(kUnderflowCount1 + kUnderflowCount2);
  wtr_->OnError(PIPELINE_ERROR_DECODE);
  wtr_->OnDurationChanged(base::Seconds(5125));

  constexpr int kDecodedFrameCount2 = 20;
  constexpr int kDroppedFrameCount2 = 10;
  wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1 + kDecodedFrameCount2,
                               kDroppedFrameCount1 + kDroppedFrameCount2);

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // All records should have the following:
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(2u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
    EXPECT_UKM(UkmEntry::kDurationName, 5000000);
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);

    // All records inherit the final pipeline status code.
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_ERROR_DECODE);
  }

  // The first record should have...
  auto* entry = entries[0].get();
  EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime1.InMilliseconds());
  EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
             kWatchTime1.InMilliseconds() / kUnderflowCount1);
  EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 2);
  EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 5);
  EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount1);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, kUnderflowCount1);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
             kUnderflowDuration.InMilliseconds());
  EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount1);
  EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount1);
  EXPECT_UKM(UkmEntry::kAudioCodecName,
             static_cast<int>(secondary_properties1->audio_codec));
  EXPECT_UKM(UkmEntry::kVideoCodecName,
             static_cast<int>(secondary_properties1->video_codec));
  EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
             static_cast<int64_t>(secondary_properties1->audio_codec_profile));
  EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
             secondary_properties1->video_codec_profile);
  EXPECT_UKM(
      UkmEntry::kAudioEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties1->audio_encryption_scheme));
  EXPECT_UKM(
      UkmEntry::kVideoEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties1->video_encryption_scheme));
  EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
             secondary_properties1->natural_size.width());
  EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
             secondary_properties1->natural_size.height());

  // The second record should have...
  entry = entries[1];
  EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime2.InMilliseconds());
  EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
             kWatchTime2.InMilliseconds() / kUnderflowCount2);
  EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 1);
  EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 2);
  EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount2);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
  EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount2);
  EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount2);

  EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
  EXPECT_UKM(UkmEntry::kAudioCodecName,
             static_cast<int>(secondary_properties2->audio_codec));
  EXPECT_UKM(UkmEntry::kVideoCodecName,
             static_cast<int>(secondary_properties2->video_codec));
  EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
             static_cast<int64_t>(secondary_properties2->audio_codec_profile));
  EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
             secondary_properties2->video_codec_profile);
  EXPECT_UKM(
      UkmEntry::kAudioEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties2->audio_encryption_scheme));
  EXPECT_UKM(
      UkmEntry::kVideoEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties2->video_encryption_scheme));
  EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
             secondary_properties2->natural_size.width());
  EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
             secondary_properties2->natural_size.height());
}

TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalizeNo2ndWT) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, true, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kChunkDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kOpus, VideoCodec::kVP9, AudioCodecProfile::kUnknown,
          VP9PROFILE_PROFILE0, AudioDecoderType::kMojo, VideoDecoderType::kMojo,
          EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted,
          gfx::Size(400, 300));
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());

  constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(54);
  const int kUnderflowCount1 = 2;
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime1);
  wtr_->UpdateUnderflowCount(kUnderflowCount1);
  wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration);

  constexpr int kDecodedFrameCount1 = 10;
  constexpr int kDroppedFrameCount1 = 2;
  wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1, kDroppedFrameCount1);

  mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kXHE_AAC,
          H264PROFILE_MAIN, AudioDecoderType::kFFmpeg,
          VideoDecoderType::kFFmpeg, EncryptionScheme::kUnencrypted,
          EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
  wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());

  // Don't record any watch time to the new record, it should report zero watch
  // time upon destruction. This ensures there's always a Finalize to prevent
  // UKM was receiving negative values from the previous unfinalized record.
  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // All records should have the following:
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(2u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_OK);
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
    EXPECT_NO_UKM(UkmEntry::kDurationName);
  }

  // The first record should have...
  auto* entry = entries[0].get();
  EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime1.InMilliseconds());
  EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
             kWatchTime1.InMilliseconds() / kUnderflowCount1);
  EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 2);
  EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 5);
  EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount1);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, kUnderflowCount1);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
             kUnderflowDuration.InMilliseconds());
  EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount1);
  EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount1);
  EXPECT_UKM(UkmEntry::kAudioCodecName,
             static_cast<int>(secondary_properties1->audio_codec));
  EXPECT_UKM(UkmEntry::kVideoCodecName,
             static_cast<int>(secondary_properties1->video_codec));
  EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
             static_cast<int64_t>(secondary_properties1->audio_codec_profile));
  EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
             secondary_properties1->video_codec_profile);
  EXPECT_UKM(
      UkmEntry::kAudioEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties1->audio_encryption_scheme));
  EXPECT_UKM(
      UkmEntry::kVideoEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties1->video_encryption_scheme));
  EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
             secondary_properties1->natural_size.width());
  EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
             secondary_properties1->natural_size.height());

  // The second record should have...
  entry = entries[1];
  EXPECT_UKM(UkmEntry::kWatchTimeName, 0);
  EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 1);
  EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 2);
  EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
  EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, 0);
  EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, 0);
  EXPECT_UKM(UkmEntry::kAudioCodecName,
             static_cast<int>(secondary_properties2->audio_codec));
  EXPECT_UKM(UkmEntry::kVideoCodecName,
             static_cast<int>(secondary_properties2->video_codec));
  EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
             static_cast<int64_t>(secondary_properties2->audio_codec_profile));
  EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
             secondary_properties2->video_codec_profile);
  EXPECT_UKM(
      UkmEntry::kAudioEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties2->audio_encryption_scheme));
  EXPECT_UKM(
      UkmEntry::kVideoEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties2->video_encryption_scheme));
  EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
             secondary_properties2->natural_size.width());
  EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
             secondary_properties2->natural_size.height());
}

TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesWithFinalize) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, true, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kChunkDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kOpus, VideoCodec::kVP9, AudioCodecProfile::kUnknown,
          VP9PROFILE_PROFILE0, AudioDecoderType::kMojo, VideoDecoderType::kMojo,
          EncryptionScheme::kCbcs, EncryptionScheme::kCbcs,
          gfx::Size(400, 300));
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());

  constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(54);
  const int kUnderflowCount1 = 2;
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime1);
  wtr_->UpdateUnderflowCount(kUnderflowCount1);
  wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration);

  constexpr int kDecodedFrameCount1 = 10;
  constexpr int kDroppedFrameCount1 = 2;
  wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1, kDroppedFrameCount1);

  // Force a finalize here so that the there is no unfinalized watch time at the
  // time of the secondary property update.
  wtr_->FinalizeWatchTime({});

  mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kXHE_AAC,
          H264PROFILE_MAIN, AudioDecoderType::kFFmpeg,
          VideoDecoderType::kFFmpeg, EncryptionScheme::kUnencrypted,
          EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
  wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());

  constexpr base::TimeDelta kWatchTime2 = base::Seconds(25);
  const int kUnderflowCount2 = 3;

  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime2);
  wtr_->UpdateUnderflowCount(kUnderflowCount2);
  wtr_->OnError(PIPELINE_ERROR_DECODE);

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // All records should have the following:
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(2u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
    EXPECT_NO_UKM(UkmEntry::kDurationName);

    // All records inherit the final pipeline status code.
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_ERROR_DECODE);
  }

  // The first record should have...
  auto* entry = entries[0].get();
  EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime1.InMilliseconds());
  EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
             kWatchTime1.InMilliseconds() / kUnderflowCount1);
  EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 2);
  EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 5);
  EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount1);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, kUnderflowCount1);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
             kUnderflowDuration.InMilliseconds());
  EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount1);
  EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount1);
  EXPECT_UKM(UkmEntry::kAudioCodecName,
             static_cast<int>(secondary_properties1->audio_codec));
  EXPECT_UKM(UkmEntry::kVideoCodecName,
             static_cast<int>(secondary_properties1->video_codec));
  EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
             static_cast<int64_t>(secondary_properties1->audio_codec_profile));
  EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
             secondary_properties1->video_codec_profile);
  EXPECT_UKM(
      UkmEntry::kAudioEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties1->audio_encryption_scheme));
  EXPECT_UKM(
      UkmEntry::kVideoEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties1->video_encryption_scheme));
  EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
             secondary_properties1->natural_size.width());
  EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
             secondary_properties1->natural_size.height());

  // The second record should have...
  entry = entries[1];
  EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime2.InMilliseconds());
  EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
             kWatchTime2.InMilliseconds() / kUnderflowCount2);
  EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 1);
  EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 2);
  EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount2);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
  EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, 0);
  EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, 0);
  EXPECT_UKM(UkmEntry::kAudioCodecName,
             static_cast<int>(secondary_properties2->audio_codec));
  EXPECT_UKM(UkmEntry::kVideoCodecName,
             static_cast<int>(secondary_properties2->video_codec));
  EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
             static_cast<int64_t>(secondary_properties2->audio_codec_profile));
  EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
             secondary_properties2->video_codec_profile);
  EXPECT_UKM(
      UkmEntry::kAudioEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties2->audio_encryption_scheme));
  EXPECT_UKM(
      UkmEntry::kVideoEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties2->video_encryption_scheme));
  EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
             secondary_properties2->natural_size.width());
  EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
             secondary_properties2->natural_size.height());
}

TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesRebufferCarryover) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, true, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kChunkDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kOpus, VideoCodec::kVP9, AudioCodecProfile::kUnknown,
          VP9PROFILE_PROFILE0, AudioDecoderType::kMojo, VideoDecoderType::kMojo,
          EncryptionScheme::kCbcs, EncryptionScheme::kCbcs,
          gfx::Size(400, 300));
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());

  constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(54);
  const int kUnderflowCount1 = 2;
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll, kWatchTime1);
  wtr_->UpdateUnderflowCount(kUnderflowCount1);

  // Complete all but one of the rebuffers in this update.
  wtr_->UpdateUnderflowDuration(kUnderflowCount1 - 1, kUnderflowDuration);

  mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
      mojom::SecondaryPlaybackProperties::New(
          AudioCodec::kAAC, VideoCodec::kH264, AudioCodecProfile::kXHE_AAC,
          H264PROFILE_MAIN, AudioDecoderType::kFFmpeg,
          VideoDecoderType::kFFmpeg, EncryptionScheme::kUnencrypted,
          EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
  wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());

  constexpr base::TimeDelta kWatchTime2 = base::Seconds(25);
  const int kUnderflowCount2 = 3;

  // Watch time and underflow counts continue to accumulate during property
  // changes, so we report the sum here instead of just kWatchTime2.
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoAll,
                        kWatchTime1 + kWatchTime2);
  wtr_->UpdateUnderflowCount(kUnderflowCount1 + kUnderflowCount2);

  // Complete the last underflow in the new property set. Unfortunately this
  // means it will now be associated with this block of watch time. Use a non
  // integer multiplier to avoid incorrect carry over being hidden.
  wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration * 1.5);

  wtr_->OnError(PIPELINE_ERROR_DECODE);
  wtr_->OnDurationChanged(base::Seconds(5125));

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // All records should have the following:
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(2u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kIsBackgroundName, properties->is_background);
    EXPECT_UKM(UkmEntry::kIsMutedName, properties->is_muted);
    EXPECT_UKM(UkmEntry::kHasAudioName, properties->has_audio);
    EXPECT_UKM(UkmEntry::kHasVideoName, properties->has_video);
    EXPECT_UKM(UkmEntry::kIsEMEName, properties->is_eme);
    EXPECT_UKM(UkmEntry::kIsMSEName, IsMsePlayback(properties));
    EXPECT_UKM(UkmEntry::kMediaStreamTypeName,
               static_cast<int64_t>(properties->media_stream_type));
    EXPECT_UKM(UkmEntry::kAutoplayInitiatedName, false);
    EXPECT_UKM(UkmEntry::kDurationName, 5000000);
    EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);

    // All records inherit the final pipeline status code.
    EXPECT_UKM(UkmEntry::kLastPipelineStatusName, PIPELINE_ERROR_DECODE);
  }

  // The first record should have...
  auto* entry = entries[0].get();
  EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime1.InMilliseconds());
  EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
             kWatchTime1.InMilliseconds() / kUnderflowCount1);
  EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 2);
  EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 5);
  EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount1);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, kUnderflowCount1 - 1);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
             kUnderflowDuration.InMilliseconds());
  EXPECT_UKM(UkmEntry::kAudioCodecName,
             static_cast<int>(secondary_properties1->audio_codec));
  EXPECT_UKM(UkmEntry::kVideoCodecName,
             static_cast<int>(secondary_properties1->video_codec));
  EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
             static_cast<int64_t>(secondary_properties1->audio_codec_profile));
  EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
             secondary_properties1->video_codec_profile);
  EXPECT_UKM(
      UkmEntry::kAudioEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties1->audio_encryption_scheme));
  EXPECT_UKM(
      UkmEntry::kVideoEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties1->video_encryption_scheme));
  EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
             secondary_properties1->natural_size.width());
  EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
             secondary_properties1->natural_size.height());

  // The second record should have...
  entry = entries[1];
  EXPECT_UKM(UkmEntry::kWatchTimeName, kWatchTime2.InMilliseconds());
  EXPECT_UKM(UkmEntry::kMeanTimeBetweenRebuffersName,
             kWatchTime2.InMilliseconds() / kUnderflowCount2);
  EXPECT_UKM(UkmEntry::kAudioDecoderNameName, 1);
  EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 2);
  EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount2);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 1);
  EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
             (kUnderflowDuration * 1.5 - kUnderflowDuration).InMilliseconds());
  EXPECT_UKM(UkmEntry::kAudioCodecName,
             static_cast<int>(secondary_properties2->audio_codec));
  EXPECT_UKM(UkmEntry::kVideoCodecName,
             static_cast<int>(secondary_properties2->video_codec));
  EXPECT_UKM(UkmEntry::kAudioCodecProfileName,
             static_cast<int64_t>(secondary_properties2->audio_codec_profile));
  EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
             secondary_properties2->video_codec_profile);
  EXPECT_UKM(
      UkmEntry::kAudioEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties2->audio_encryption_scheme));
  EXPECT_UKM(
      UkmEntry::kVideoEncryptionSchemeName,
      static_cast<int64_t>(secondary_properties2->video_encryption_scheme));
  EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
             secondary_properties2->natural_size.width());
  EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
             secondary_properties2->natural_size.height());
}

TEST_F(WatchTimeRecorderTest, AutoPipReasonMediaPlaybackAudioAndVideo) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  EXPECT_CALL(*this, AutoPipReason)
      .WillRepeatedly(testing::Return(
          PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback));

  constexpr base::TimeDelta kWatchTime = base::Seconds(54);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayPictureInPicture,
                        kWatchTime);
  wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoDisplayPictureInPicture});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());

  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName,
               kWatchTime.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_AutoPipName, kWatchTime.InMilliseconds());
  }

  auto auto_pip_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioVideoAutoPipMediaPlayback);
  auto display_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioVideoDisplayPictureInPicture);
  ASSERT_TRUE(!auto_pip_key_str.empty());
  ASSERT_TRUE(!display_key_str.empty());
  ExpectWatchTime({auto_pip_key_str, display_key_str}, kWatchTime);
}

TEST_F(WatchTimeRecorderTest, AutoPipReasonBrowserInitiatedkAudioAndVideo) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  EXPECT_CALL(*this, AutoPipReason)
      .WillRepeatedly(testing::Return(
          PictureInPictureEventsInfo::AutoPipReason::kBrowserInitiated));

  constexpr base::TimeDelta kWatchTime = base::Seconds(54);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayPictureInPicture,
                        kWatchTime);
  wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoDisplayPictureInPicture});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());

  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName,
               kWatchTime.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_AutoPipName, kWatchTime.InMilliseconds());
  }

  auto auto_pip_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioVideoAutoPipMediaPlayback);
  auto display_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioVideoDisplayPictureInPicture);
  ASSERT_TRUE(!auto_pip_key_str.empty());
  ASSERT_TRUE(!display_key_str.empty());
  ExpectWatchTime({auto_pip_key_str, display_key_str}, kWatchTime);
}

TEST_F(WatchTimeRecorderTest, AutoPipReasonMediaPlaybackAudio) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, false, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  EXPECT_CALL(*this, AutoPipReason)
      .WillRepeatedly(testing::Return(
          PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback));

  constexpr base::TimeDelta kWatchTime = base::Seconds(54);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioDisplayPictureInPicture,
                        kWatchTime);
  wtr_->FinalizeWatchTime({WatchTimeKey::kAudioDisplayPictureInPicture});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());

  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
    EXPECT_UKM(UkmEntry::kWatchTime_AutoPipName, kWatchTime.InMilliseconds());
  }

  auto auto_pip_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioAutoPipMediaPlayback);
  ASSERT_TRUE(!auto_pip_key_str.empty());
  ExpectWatchTime({auto_pip_key_str}, kWatchTime);
}

TEST_F(WatchTimeRecorderTest, AutoPipReasonBrowserInitiatedAudio) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, false, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  EXPECT_CALL(*this, AutoPipReason)
      .WillRepeatedly(testing::Return(
          PictureInPictureEventsInfo::AutoPipReason::kBrowserInitiated));

  constexpr base::TimeDelta kWatchTime = base::Seconds(54);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioDisplayPictureInPicture,
                        kWatchTime);
  wtr_->FinalizeWatchTime({WatchTimeKey::kAudioDisplayPictureInPicture});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());

  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_NO_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName);
    EXPECT_UKM(UkmEntry::kWatchTime_AutoPipName, kWatchTime.InMilliseconds());
  }

  auto auto_pip_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioAutoPipMediaPlayback);
  ASSERT_TRUE(!auto_pip_key_str.empty());
  ExpectWatchTime({auto_pip_key_str}, kWatchTime);
}

TEST_F(WatchTimeRecorderTest, AutoPipReasonUnknownAudioAndVideo) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  EXPECT_CALL(*this, AutoPipReason)
      .WillRepeatedly(
          testing::Return(PictureInPictureEventsInfo::AutoPipReason::kUnknown));

  constexpr base::TimeDelta kWatchTime = base::Seconds(54);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayPictureInPicture,
                        kWatchTime);
  wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoDisplayPictureInPicture});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());

  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName,
               kWatchTime.InMilliseconds());
    EXPECT_NO_UKM(UkmEntry::kWatchTime_AutoPipName);
  }

  auto display_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioVideoDisplayPictureInPicture);
  ASSERT_TRUE(!display_key_str.empty());
  ExpectWatchTime({display_key_str}, kWatchTime);
}

TEST_F(WatchTimeRecorderTest, AutoPipReasonVideoConferencingAudioAndVideo) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, false, false,
      mojom::MediaStreamType::kLocalDeviceCapture, RendererType::kRendererImpl,
      DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  EXPECT_CALL(*this, AutoPipReason)
      .WillRepeatedly(testing::Return(
          PictureInPictureEventsInfo::AutoPipReason::kVideoConferencing));

  constexpr base::TimeDelta kWatchTime = base::Seconds(54);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayPictureInPicture,
                        kWatchTime);
  wtr_->FinalizeWatchTime({WatchTimeKey::kAudioVideoDisplayPictureInPicture});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());

  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName,
               kWatchTime.InMilliseconds());
    EXPECT_NO_UKM(UkmEntry::kWatchTime_AutoPipName);
  }

  ExpectWatchTime({}, base::TimeDelta());
}

TEST_F(WatchTimeRecorderTest,
       AutoPipReasonMediaPlaybackAudioAndVideoSingleFinalize) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  EXPECT_CALL(*this, AutoPipReason)
      .WillRepeatedly(testing::Return(
          PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback));

  constexpr base::TimeDelta kWatchTime1 = base::Seconds(25);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(50);
  constexpr base::TimeDelta kWatchTime3 = base::Seconds(100);

  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayPictureInPicture,
                        kWatchTime1);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayInline, kWatchTime2);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayPictureInPicture,
                        kWatchTime3);
  wtr_->FinalizeWatchTime({});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());

  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName,
               kWatchTime3.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_DisplayInlineName,
               kWatchTime2.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_AutoPipName, kWatchTime3.InMilliseconds());
  }

  auto auto_pip_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioVideoAutoPipMediaPlayback);
  ASSERT_TRUE(!auto_pip_key_str.empty());
  histogram_tester_->ExpectUniqueSample(auto_pip_key_str,
                                        kWatchTime3.InMilliseconds(), 1);

  auto display_inline_key_str =
      ConvertWatchTimeKeyToStringForUma(WatchTimeKey::kAudioVideoDisplayInline);
  ASSERT_TRUE(!display_inline_key_str.empty());
  histogram_tester_->ExpectUniqueSample(display_inline_key_str,
                                        kWatchTime2.InMilliseconds(), 1);

  auto display_pip_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioVideoDisplayPictureInPicture);
  ASSERT_TRUE(!display_pip_key_str.empty());
  histogram_tester_->ExpectUniqueSample(display_pip_key_str,
                                        kWatchTime3.InMilliseconds(), 1);
}

TEST_F(WatchTimeRecorderTest,
       AutoPipReasonMediaPlaybackAudioAndVideoMultipleFinalize) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, false, false, mojom::MediaStreamType::kNone,
      RendererType::kRendererImpl, DemuxerType::kFFmpegDemuxer);
  mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
      CreateSecondaryProperties();
  Initialize(properties.Clone());
  wtr_->UpdateSecondaryProperties(secondary_properties.Clone());

  EXPECT_CALL(*this, AutoPipReason)
      .WillRepeatedly(testing::Return(
          PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback));

  constexpr base::TimeDelta kWatchTime1 = base::Seconds(25);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(50);
  constexpr base::TimeDelta kWatchTime3 = base::Seconds(100);

  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayPictureInPicture,
                        kWatchTime1);
  wtr_->FinalizeWatchTime({});
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayInline, kWatchTime2);
  wtr_->FinalizeWatchTime({});
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoDisplayPictureInPicture,
                        kWatchTime3);
  wtr_->FinalizeWatchTime({});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());

  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestOrigin));
    EXPECT_UKM(UkmEntry::kWatchTime_DisplayPictureInPictureName,
               kWatchTime1.InMilliseconds() + kWatchTime3.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_DisplayInlineName,
               kWatchTime2.InMilliseconds());
    EXPECT_UKM(UkmEntry::kWatchTime_AutoPipName,
               kWatchTime1.InMilliseconds() + kWatchTime3.InMilliseconds());
  }

  auto auto_pip_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioVideoAutoPipMediaPlayback);
  ASSERT_TRUE(!auto_pip_key_str.empty());
  histogram_tester_->ExpectTotalCount(auto_pip_key_str, 2);
  histogram_tester_->ExpectBucketCount(auto_pip_key_str,
                                       kWatchTime1.InMilliseconds(), 1);
  histogram_tester_->ExpectBucketCount(auto_pip_key_str,
                                       kWatchTime3.InMilliseconds(), 1);

  auto display_inline_key_str =
      ConvertWatchTimeKeyToStringForUma(WatchTimeKey::kAudioVideoDisplayInline);
  ASSERT_TRUE(!display_inline_key_str.empty());
  histogram_tester_->ExpectTotalCount(display_inline_key_str, 1);
  histogram_tester_->ExpectBucketCount(display_inline_key_str,
                                       kWatchTime2.InMilliseconds(), 1);

  auto display_pip_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioVideoDisplayPictureInPicture);
  ASSERT_TRUE(!display_pip_key_str.empty());
  histogram_tester_->ExpectTotalCount(display_pip_key_str, 2);
  histogram_tester_->ExpectBucketCount(display_pip_key_str,
                                       kWatchTime1.InMilliseconds(), 1);
  histogram_tester_->ExpectBucketCount(display_pip_key_str,
                                       kWatchTime3.InMilliseconds(), 1);
}

TEST_F(WatchTimeRecorderTest, HdrWatchTime_AllOnlyRecorded) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, /*is_eme=*/false, false,
      mojom::MediaStreamType::kNone, RendererType::kRendererImpl,
      DemuxerType::kChunkDemuxer);
  Initialize(properties.Clone());

  constexpr base::TimeDelta kWatchTime = base::Seconds(25);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoHdrAll, kWatchTime);
  wtr_->FinalizeWatchTime({});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // We don't record UKMs for HDR watch times.
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(0u, entries.size());

  // Only HDR all watch time UMA is expected.
  ExpectWatchTime({kWatchTimeAudioVideoHdrAll}, kWatchTime);
  histogram_tester_->ExpectTotalCount(kWatchTimeAudioVideoHdrEme, 0);
}

TEST_F(WatchTimeRecorderTest, HdrWatchTime_AllAndEmeRecorded) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, /*is_eme=*/true, false,
      mojom::MediaStreamType::kNone, RendererType::kRendererImpl,
      DemuxerType::kChunkDemuxer);
  Initialize(properties.Clone());

  constexpr base::TimeDelta kWatchTime = base::Seconds(25);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoHdrAll, kWatchTime);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoHdrEme, kWatchTime);
  wtr_->FinalizeWatchTime({});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // We don't record UKMs for HDR watch times.
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(0u, entries.size());

  // Both HDR all & eme watch time UMAs are expected.
  ExpectWatchTime({kWatchTimeAudioVideoHdrAll, kWatchTimeAudioVideoHdrEme},
                  kWatchTime);
}

TEST_F(WatchTimeRecorderTest, HdrWatchTime_SdrNeverRecorded) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, /*is_eme=*/true, false,
      mojom::MediaStreamType::kNone, RendererType::kRendererImpl,
      DemuxerType::kChunkDemuxer);
  Initialize(properties.Clone());

  constexpr base::TimeDelta kWatchTime = base::Seconds(25);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoSdrAll, kWatchTime);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoSdrEme, kWatchTime);
  wtr_->FinalizeWatchTime({});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // We don't record UKMs for SDR/HDR watch times.
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(0u, entries.size());

  // We are not interested in SDR watch times thus no UMA key for SDR exists.
  auto sdr_all_key_str =
      ConvertWatchTimeKeyToStringForUma(WatchTimeKey::kAudioVideoSdrAll);
  auto sdr_eme_key_str =
      ConvertWatchTimeKeyToStringForUma(WatchTimeKey::kAudioVideoSdrEme);
  ASSERT_TRUE(sdr_all_key_str.empty());
  ASSERT_TRUE(sdr_eme_key_str.empty());

  // No HDR watch time UMAs are expected.
  histogram_tester_->ExpectTotalCount(kWatchTimeAudioVideoHdrAll, 0);
  histogram_tester_->ExpectTotalCount(kWatchTimeAudioVideoHdrEme, 0);
}

TEST_F(WatchTimeRecorderTest, HdrWatchTime_MediaFoundationAllOnlyRecorded) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, /*is_eme=*/false, false,
      mojom::MediaStreamType::kNone, RendererType::kMediaFoundation,
      DemuxerType::kChunkDemuxer);
  Initialize(properties.Clone());

  constexpr base::TimeDelta kWatchTime = base::Seconds(25);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoMediaFoundationHdrAll,
                        kWatchTime);
  wtr_->FinalizeWatchTime({});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // We don't record UKMs for HDR watch times.
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(0u, entries.size());

  // Only MediaFoundation HDR all watch time UMA is expected.
  ExpectWatchTime({kWatchTimeAudioVideoMediaFoundationHdrAll}, kWatchTime);
  histogram_tester_->ExpectTotalCount(kWatchTimeAudioVideoMediaFoundationHdrEme,
                                      0);
}

TEST_F(WatchTimeRecorderTest, HdrWatchTime_MediaFoundationAllAndEmeRecorded) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, /*is_eme=*/true, false,
      mojom::MediaStreamType::kNone, RendererType::kMediaFoundation,
      DemuxerType::kChunkDemuxer);
  Initialize(properties.Clone());

  constexpr base::TimeDelta kWatchTime = base::Seconds(25);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoMediaFoundationHdrAll,
                        kWatchTime);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoMediaFoundationHdrEme,
                        kWatchTime);
  wtr_->FinalizeWatchTime({});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // We don't record UKMs for HDR watch times.
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(0u, entries.size());

  // Both MediaFoundation HDR all & eme watch time UMAs are expected.
  ExpectWatchTime({kWatchTimeAudioVideoMediaFoundationHdrAll,
                   kWatchTimeAudioVideoMediaFoundationHdrEme},
                  kWatchTime);
}

TEST_F(WatchTimeRecorderTest, HdrWatchTime_MediaFoundationSdrNeverRecorded) {
  mojom::PlaybackPropertiesPtr properties = mojom::PlaybackProperties::New(
      true, true, false, false, /*is_eme=*/true, false,
      mojom::MediaStreamType::kNone, RendererType::kMediaFoundation,
      DemuxerType::kChunkDemuxer);
  Initialize(properties.Clone());

  constexpr base::TimeDelta kWatchTime = base::Seconds(25);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoMediaFoundationSdrAll,
                        kWatchTime);
  wtr_->RecordWatchTime(WatchTimeKey::kAudioVideoMediaFoundationSdrEme,
                        kWatchTime);
  wtr_->FinalizeWatchTime({});

  wtr_.reset();
  base::RunLoop().RunUntilIdle();

  // We don't record UKMs for MediaFoundation SDR/HDR watch times.
  const auto& entries = test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(0u, entries.size());

  // We are not interested in MediaFoundationSDR watch times thus no UMA key for
  // SDR exists.
  auto sdr_all_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioVideoMediaFoundationSdrAll);
  auto sdr_eme_key_str = ConvertWatchTimeKeyToStringForUma(
      WatchTimeKey::kAudioVideoMediaFoundationSdrEme);
  ASSERT_TRUE(sdr_all_key_str.empty());
  ASSERT_TRUE(sdr_eme_key_str.empty());

  // No MediaFoundation HDR watch time UMAs are expected.
  histogram_tester_->ExpectTotalCount(kWatchTimeAudioVideoMediaFoundationHdrAll,
                                      0);
  histogram_tester_->ExpectTotalCount(kWatchTimeAudioVideoMediaFoundationHdrEme,
                                      0);
}

#undef EXPECT_UKM
#undef EXPECT_NO_UKM
#undef EXPECT_HAS_UKM

}  // namespace media
