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

#include "third_party/blink/renderer/platform/media/watch_time_reporter.h"

#include <memory>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "media/base/mock_media_log.h"
#include "media/base/picture_in_picture_events_info.h"
#include "media/base/pipeline_status.h"
#include "media/base/test_helpers.h"
#include "media/base/watch_time_keys.h"
#include "media/mojo/mojom/media_metrics_provider.mojom-blink.h"
#include "media/mojo/mojom/watch_time_recorder.mojom-blink.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/public/platform/web_media_player.h"
#include "third_party/blink/renderer/platform/media/watch_time_component.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"

namespace blink {

using ::media::WatchTimeKey;
using ::testing::_;

constexpr gfx::Size kSizeTooSmall = gfx::Size(101, 101);
constexpr gfx::Size kSizeJustRight = gfx::Size(201, 201);

#define EXPECT_WATCH_TIME(key, value)                                          \
  do {                                                                         \
    EXPECT_CALL(                                                               \
        *this, OnWatchTimeUpdate((has_video_ && has_audio_)                    \
                                     ? WatchTimeKey::kAudioVideo##key          \
                                     : has_audio_ ? WatchTimeKey::kAudio##key  \
                                                  : WatchTimeKey::kVideo##key, \
                                 value))                                       \
        .RetiresOnSaturation();                                                \
  } while (0)

#define EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(key, value)                     \
  do {                                                                         \
    if (!has_video_ || !has_audio_)                                            \
      break;                                                                   \
    EXPECT_CALL(*this,                                                         \
                OnWatchTimeUpdate(WatchTimeKey::kAudioVideoMuted##key, value)) \
        .RetiresOnSaturation();                                                \
  } while (0)

#define EXPECT_BACKGROUND_WATCH_TIME(key, value)                            \
  do {                                                                      \
    EXPECT_CALL(*this,                                                      \
                OnWatchTimeUpdate(                                          \
                    (has_video_ && has_audio_)                              \
                        ? WatchTimeKey::kAudioVideoBackground##key          \
                        : has_audio_ ? WatchTimeKey::kAudioBackground##key  \
                                     : WatchTimeKey::kVideoBackground##key, \
                    value))                                                 \
        .RetiresOnSaturation();                                             \
  } while (0)

#define EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_MEDIAFOUNDATION(key, value)       \
  do {                                                                     \
    if (!has_video_ || !has_audio_)                                        \
      break;                                                               \
    EXPECT_CALL(*this,                                                     \
                OnWatchTimeUpdate(                                         \
                    WatchTimeKey::kAudioVideoMediaFoundation##key, value)) \
        .RetiresOnSaturation();                                            \
  } while (0)

#define EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(key, value)                  \
  do {                                                                    \
    if (!has_video_ || !has_audio_)                                       \
      break;                                                              \
    EXPECT_CALL(*this,                                                    \
                OnWatchTimeUpdate(WatchTimeKey::kAudioVideo##key, value)) \
        .RetiresOnSaturation();                                           \
  } while (0)

#define EXPECT_WATCH_TIME_IF_VIDEO(key, value)                                \
  do {                                                                        \
    if (!has_video_)                                                          \
      break;                                                                  \
    if (has_audio_) {                                                         \
      EXPECT_CALL(*this,                                                      \
                  OnWatchTimeUpdate(WatchTimeKey::kAudioVideo##key, value))   \
          .RetiresOnSaturation();                                             \
    } else {                                                                  \
      EXPECT_CALL(*this, OnWatchTimeUpdate(WatchTimeKey::kVideo##key, value)) \
          .RetiresOnSaturation();                                             \
    }                                                                         \
  } while (0)

#define EXPECT_WATCH_TIME_FINALIZED() \
  EXPECT_CALL(*this, OnWatchTimeFinalized()).RetiresOnSaturation();

// The following macros have .Times() values equal to the number of keys that a
// finalize event is expected to finalize.
#define EXPECT_POWER_WATCH_TIME_FINALIZED()       \
  EXPECT_CALL(*this, OnPowerWatchTimeFinalized()) \
      .Times(2)                                   \
      .RetiresOnSaturation();

#define EXPECT_CONTROLS_WATCH_TIME_FINALIZED()       \
  EXPECT_CALL(*this, OnControlsWatchTimeFinalized()) \
      .Times(2)                                      \
      .RetiresOnSaturation();

#define EXPECT_DISPLAY_WATCH_TIME_FINALIZED()         \
  if (has_audio_) {                                   \
    EXPECT_CALL(*this, OnDisplayWatchTimeFinalized()) \
        .Times(4)                                     \
        .RetiresOnSaturation();                       \
  } else {                                            \
    EXPECT_CALL(*this, OnDisplayWatchTimeFinalized()) \
        .Times(3)                                     \
        .RetiresOnSaturation();                       \
  }

using WatchTimeReporterTestData = std::tuple<bool, bool>;
class WatchTimeReporterTest
    : public testing::TestWithParam<WatchTimeReporterTestData> {
 public:
  class WatchTimeInterceptor : public media::mojom::blink::WatchTimeRecorder {
   public:
    WatchTimeInterceptor(
        media::PictureInPictureEventsInfo::AutoPipReasonCallback
            auto_pip_reason_cb,
        WatchTimeReporterTest* parent)
        : auto_pip_reason_cb_(std::move(auto_pip_reason_cb)), parent_(parent) {}
    WatchTimeInterceptor(const WatchTimeInterceptor&) = delete;
    WatchTimeInterceptor& operator=(const WatchTimeInterceptor&) = delete;
    ~WatchTimeInterceptor() override = default;

    // mojom::blink::WatchTimeRecorder implementation:
    void RecordWatchTime(WatchTimeKey key, base::TimeDelta value) override {
      parent_->OnWatchTimeUpdate(key, value);
    }

    void FinalizeWatchTime(
        const Vector<WatchTimeKey>& watch_time_keys) override {
      if (watch_time_keys.empty()) {
        parent_->OnWatchTimeFinalized();
      } else {
        for (auto key : watch_time_keys) {
          switch (key) {
            case WatchTimeKey::kAudioBattery:
            case WatchTimeKey::kAudioAc:
            case WatchTimeKey::kAudioBackgroundBattery:
            case WatchTimeKey::kAudioBackgroundAc:
            case WatchTimeKey::kAudioVideoBattery:
            case WatchTimeKey::kAudioVideoAc:
            case WatchTimeKey::kAudioVideoMutedBattery:
            case WatchTimeKey::kAudioVideoMutedAc:
            case WatchTimeKey::kAudioVideoBackgroundBattery:
            case WatchTimeKey::kAudioVideoBackgroundAc:
            case WatchTimeKey::kVideoBattery:
            case WatchTimeKey::kVideoAc:
            case WatchTimeKey::kVideoBackgroundBattery:
            case WatchTimeKey::kVideoBackgroundAc:
              parent_->OnPowerWatchTimeFinalized();
              break;

            case WatchTimeKey::kAudioNativeControlsOn:
            case WatchTimeKey::kAudioNativeControlsOff:
            case WatchTimeKey::kAudioVideoNativeControlsOn:
            case WatchTimeKey::kAudioVideoNativeControlsOff:
            case WatchTimeKey::kAudioVideoMutedNativeControlsOn:
            case WatchTimeKey::kAudioVideoMutedNativeControlsOff:
            case WatchTimeKey::kVideoNativeControlsOn:
            case WatchTimeKey::kVideoNativeControlsOff:
              parent_->OnControlsWatchTimeFinalized();
              break;

            case WatchTimeKey::kAudioDisplayFullscreen:
            case WatchTimeKey::kAudioDisplayInline:
            case WatchTimeKey::kAudioDisplayPictureInPicture:
            case WatchTimeKey::kAudioVideoAutoPipMediaPlayback:
            case WatchTimeKey::kAudioAutoPipMediaPlayback:
            case WatchTimeKey::kAudioVideoDisplayFullscreen:
            case WatchTimeKey::kAudioVideoDisplayInline:
            case WatchTimeKey::kAudioVideoDisplayPictureInPicture:
            case WatchTimeKey::kAudioVideoMutedDisplayFullscreen:
            case WatchTimeKey::kAudioVideoMutedDisplayInline:
            case WatchTimeKey::kAudioVideoMutedDisplayPictureInPicture:
            case WatchTimeKey::kVideoDisplayFullscreen:
            case WatchTimeKey::kVideoDisplayInline:
            case WatchTimeKey::kVideoDisplayPictureInPicture:
              parent_->OnDisplayWatchTimeFinalized();
              break;
            case WatchTimeKey::kAudioVideoDominantVisibleContent:
            case WatchTimeKey::kAudioVideoAuxiliaryVisibleContent:
            case WatchTimeKey::kAudioVideoMutedDominantVisibleContent:
            case WatchTimeKey::kAudioVideoMutedAuxiliaryVisibleContent:
            case WatchTimeKey::kVideoDominantVisibleContent:
            case WatchTimeKey::kVideoAuxiliaryVisibleContent:
              parent_->OnDominantWatchTimeFinalized();
              break;

            case WatchTimeKey::kAudioAll:
            case WatchTimeKey::kAudioMse:
            case WatchTimeKey::kAudioEme:
            case WatchTimeKey::kAudioSrc:
            case WatchTimeKey::kAudioHls:
            case WatchTimeKey::kAudioEmbeddedExperience:
            case WatchTimeKey::kAudioBackgroundAll:
            case WatchTimeKey::kAudioBackgroundMse:
            case WatchTimeKey::kAudioBackgroundEme:
            case WatchTimeKey::kAudioBackgroundSrc:
            case WatchTimeKey::kAudioBackgroundHls:
            case WatchTimeKey::kAudioBackgroundEmbeddedExperience:
            case WatchTimeKey::kAudioVideoAll:
            case WatchTimeKey::kAudioVideoMse:
            case WatchTimeKey::kAudioVideoEme:
            case WatchTimeKey::kAudioVideoSrc:
            case WatchTimeKey::kAudioVideoHls:
            case WatchTimeKey::kAudioVideoEmbeddedExperience:
            case WatchTimeKey::kAudioVideoMutedAll:
            case WatchTimeKey::kAudioVideoMutedMse:
            case WatchTimeKey::kAudioVideoMutedEme:
            case WatchTimeKey::kAudioVideoMutedSrc:
            case WatchTimeKey::kAudioVideoMutedHls:
            case WatchTimeKey::kAudioVideoMutedEmbeddedExperience:
            case WatchTimeKey::kAudioVideoBackgroundAll:
            case WatchTimeKey::kAudioVideoBackgroundMse:
            case WatchTimeKey::kAudioVideoBackgroundEme:
            case WatchTimeKey::kAudioVideoBackgroundSrc:
            case WatchTimeKey::kAudioVideoBackgroundHls:
            case WatchTimeKey::kAudioVideoBackgroundEmbeddedExperience:
            case WatchTimeKey::kVideoAll:
            case WatchTimeKey::kVideoMse:
            case WatchTimeKey::kVideoEme:
            case WatchTimeKey::kVideoSrc:
            case WatchTimeKey::kVideoHls:
            case WatchTimeKey::kVideoEmbeddedExperience:
            case WatchTimeKey::kVideoBackgroundAll:
            case WatchTimeKey::kVideoBackgroundMse:
            case WatchTimeKey::kVideoBackgroundEme:
            case WatchTimeKey::kVideoBackgroundSrc:
            case WatchTimeKey::kVideoBackgroundHls:
            case WatchTimeKey::kVideoBackgroundEmbeddedExperience:
            case WatchTimeKey::kAudioVideoMediaFoundationAll:
            case WatchTimeKey::kAudioVideoMediaFoundationEme:
            case WatchTimeKey::kAudioVideoMediaFoundationHdrAll:
            case WatchTimeKey::kAudioVideoMediaFoundationHdrEme:
            case WatchTimeKey::kAudioVideoMediaFoundationSdrAll:
            case WatchTimeKey::kAudioVideoMediaFoundationSdrEme:
            case WatchTimeKey::kAudioVideoHdrAll:
            case WatchTimeKey::kAudioVideoHdrEme:
            case WatchTimeKey::kAudioVideoSdrAll:
            case WatchTimeKey::kAudioVideoSdrEme:
              // These keys do not support partial finalization.
              FAIL();
          };
        }
      }
    }

    void OnError(const media::PipelineStatus& status) override {
      parent_->OnError(status);
    }

    void UpdateSecondaryProperties(
        media::mojom::blink::SecondaryPlaybackPropertiesPtr
            secondary_properties) override {
      parent_->OnUpdateSecondaryProperties(std::move(secondary_properties));
    }

    void UpdateUnderflowCount(int32_t count) override {
      parent_->OnUnderflowUpdate(count);
    }

    void UpdateUnderflowDuration(int32_t total_completed_count,
                                 base::TimeDelta total_duration) override {
      parent_->OnUnderflowDurationUpdate(total_completed_count, total_duration);
    }

    void SetAutoplayInitiated(bool value) override {
      parent_->OnSetAutoplayInitiated(value);
    }

    void OnDurationChanged(base::TimeDelta duration) override {
      parent_->OnDurationChanged(duration);
    }

    void UpdateVideoDecodeStats(uint32_t video_frames_decoded,
                                uint32_t video_frames_dropped) override {
      parent_->OnUpdateVideoDecodeStats(video_frames_decoded,
                                        video_frames_dropped);
    }

   private:
    media::PictureInPictureEventsInfo::AutoPipReasonCallback
        auto_pip_reason_cb_;
    raw_ptr<WatchTimeReporterTest> parent_;
  };

  class FakeMediaMetricsProvider
      : public media::mojom::blink::MediaMetricsProvider {
   public:
    explicit FakeMediaMetricsProvider(WatchTimeReporterTest* parent)
        : parent_(parent) {}
    ~FakeMediaMetricsProvider() override {}

    // mojom::blink::WatchTimeRecorderProvider implementation:
    void AcquireWatchTimeRecorder(
        media::mojom::blink::PlaybackPropertiesPtr properties,
        mojo::PendingReceiver<media::mojom::blink::WatchTimeRecorder> receiver)
        override {
      mojo::MakeSelfOwnedReceiver(
          std::make_unique<WatchTimeInterceptor>(
              base::BindRepeating(&FakeMediaMetricsProvider::AutoPipReason,
                                  base::Unretained(this)),
              parent_),
          std::move(receiver));
    }
    void AcquireVideoDecodeStatsRecorder(
        mojo::PendingReceiver<media::mojom::blink::VideoDecodeStatsRecorder>
            receiver) override {
      FAIL();
    }
    void AcquirePlaybackEventsRecorder(
        mojo::PendingReceiver<media::mojom::blink::PlaybackEventsRecorder>
            receiver) override {}
    void Initialize(
        bool is_mse,
        media::mojom::blink::MediaURLScheme url_scheme,
        media::mojom::blink::MediaStreamType media_stream_type) override {}
    void OnStarted(const media::PipelineStatus& status) override {}
    void OnError(const media::PipelineStatus& status) override {}
    void OnFallback(const media::PipelineStatus& status) override {}
    void SetIsEME() override {}
    void SetHasTrackChange() override {}
    void SetTimeToMetadata(base::TimeDelta elapsed) override {}
    void SetTimeToFirstFrame(base::TimeDelta elapsed) override {}
    void SetTimeToPlayReady(base::TimeDelta elapsed) override {}
    void SetVisibilityRatioAtPlaybackStart(double ratio) override {}
    void SetContainerName(
        media::container_names::MediaContainerName container_name) override {}
    void SetRendererType(media::RendererType renderer_type) override {}
    void SetDemuxerType(media::DemuxerType demuxer_type) override {}
    void SetKeySystem(const String& key_system) override {}
    void SetHasWaitingForKey() override {}
    void SetIsHardwareSecure() override {}
    void SetHasPlayed() override {}
    void SetHaveEnough() override {}
    void SetHasAudio(media::AudioCodec audio_codec) override {}
    void SetHasVideo(media::VideoCodec video_codec) override {}
    void SetVideoPipelineInfo(const media::VideoPipelineInfo& info) override {}
    void SetAudioPipelineInfo(const media::AudioPipelineInfo& info) override {}

   private:
    media::PictureInPictureEventsInfo::AutoPipReason AutoPipReason() {
      return media::PictureInPictureEventsInfo::AutoPipReason::kUnknown;
    }

    raw_ptr<WatchTimeReporterTest> parent_;
  };

  WatchTimeReporterTest()
      : has_video_(std::get<0>(GetParam())),
        has_audio_(std::get<1>(GetParam())),
        fake_metrics_provider_(this) {}

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

  ~WatchTimeReporterTest() override {
    CycleReportingTimer();
  }

  void TearDown() override {
    // Reset the reporter to ensure orderly cleanup.
    wtr_.reset();
  }

 protected:
  void Initialize(
      bool is_encrypted,
      const gfx::Size& initial_video_size,
      media::DemuxerType demuxer_type = media::DemuxerType::kFFmpegDemuxer,
      media::RendererType renderer_type = media::RendererType::kRendererImpl) {
    if (wtr_ && IsMonitoring())
      EXPECT_WATCH_TIME_FINALIZED();

    wtr_ = std::make_unique<WatchTimeReporter>(
        media::mojom::blink::PlaybackProperties::New(
            has_audio_, has_video_, false, false, is_encrypted, false,
            media::mojom::blink::MediaStreamType::kNone, renderer_type,
            demuxer_type),
        initial_video_size,
        blink::BindRepeating(&WatchTimeReporterTest::GetCurrentMediaTime,
                             Unretained(this)),
        BindRepeating(&WatchTimeReporterTest::GetPipelineStatistics,
                      Unretained(this)),
        &fake_metrics_provider_, scheduler::GetSequencedTaskRunnerForTesting(),
        task_environment_.GetMockTickClock());
    reporting_interval_ = wtr_->reporting_interval_;

    // Most tests don't care about this.
    EXPECT_CALL(*this, GetPipelineStatistics())
        .WillRepeatedly(testing::Return(media::PipelineStatistics()));
    EXPECT_CALL(*this, OnUpdateVideoDecodeStats(_, _))
        .Times(testing::AnyNumber());
  }

  void CycleReportingTimer() {
    task_environment_.FastForwardBy(reporting_interval_);
  }

  bool IsMonitoring() const { return wtr_->reporting_timer_.IsRunning(); }

  bool IsBackgroundMonitoring() const {
    return wtr_->background_reporter_->reporting_timer_.IsRunning();
  }

  bool IsMutedMonitoring() const {
    return wtr_->muted_reporter_ &&
           wtr_->muted_reporter_->reporting_timer_.IsRunning();
  }

  void DisableMutedReporting() { wtr_->muted_reporter_.reset(); }

  // We call directly into the reporter for this instead of using an actual
  // PowerMonitorTestSource since that results in a posted tasks which interfere
  // with our ability to test the timer.
  void SetOnBatteryPower(bool on_battery_power) {
    wtr_->power_component_->SetCurrentValue(on_battery_power);
  }

  bool IsOnBatteryPower() const {
    return wtr_->power_component_->current_value_for_testing();
  }

  void OnPowerStateChange(bool on_battery_power) {
    base::PowerStateObserver::BatteryPowerStatus battery_power_status_ =
        on_battery_power
            ? base::PowerStateObserver::BatteryPowerStatus::kBatteryPower
            : base::PowerStateObserver::BatteryPowerStatus::kExternalPower;
    wtr_->OnBatteryPowerStatusChange(battery_power_status_);
    if (wtr_->background_reporter_) {
      wtr_->background_reporter_->OnBatteryPowerStatusChange(
          battery_power_status_);
    }
    if (wtr_->muted_reporter_)
      wtr_->muted_reporter_->OnBatteryPowerStatusChange(battery_power_status_);
  }

  void OnNativeControlsEnabled(bool enabled) {
    enabled ? wtr_->OnNativeControlsEnabled()
            : wtr_->OnNativeControlsDisabled();
  }

  void OnDisplayTypeChanged(WebMediaPlayer::DisplayType display_type) {
    wtr_->OnDisplayTypeChanged(display_type);
  }

  enum {
    // After |test_callback_func| is executed, should watch time continue to
    // accumulate?
    kAccumulationContinuesAfterTest = 1,

    // |test_callback_func| for hysteresis tests enters and exits finalize mode
    // for watch time, not all exits require a new current time update.
    kFinalizeExitDoesNotRequireCurrentTime = 2,

    // During finalize the watch time should not continue on the starting power
    // metric. By default this means the AC metric will be finalized, but if
    // used with |kStartOnBattery| it will be the battery metric.
    kFinalizePowerWatchTime = 4,

    // During finalize the power watch time should continue on the metric
    // opposite the starting metric (by default it's AC, it's battery if
    // |kStartOnBattery| is specified.
    kTransitionPowerWatchTime = 8,

    // Indicates that power watch time should be reported to the battery metric.
    kStartOnBattery = 16,

    // Indicates an extra start event may be generated during test execution.
    kFinalizeInterleavedStartEvent = 32,

    // During finalize the watch time should not continue on the starting
    // controls metric. By default this means the NativeControsOff metric will
    // be finalized, but if used with |kStartWithNativeControls| it will be the
    // NativeControlsOn metric.
    kFinalizeControlsWatchTime = 64,

    // During finalize the controls watch time should continue on the metric
    // opposite the starting metric (by default it's non-native controls, it's
    // native controls if |kStartWithNativeControls| is specified.
    kTransitionControlsWatchTime = 128,

    // Indicates that controls watch time should be reported to the native
    // controls metric.
    kStartWithNativeControls = 256,

    // During finalize the watch time should not continue on the starting
    // display metric. By default this means the DisplayInline metric will be
    // finalized, but if used with |kStartWithDisplayFullscreen| it will be the
    // DisplayFullscreen metric.
    kFinalizeDisplayWatchTime = 1024,

    // During finalize the display watch time should continue on the metric
    // opposite the starting metric (by default it's inline, it's fullscreen if
    // |kStartWithDisplayFullscreen| is specified.
    kTransitionDisplayWatchTime = 2048,

    // Indicates that the watch time should be reporter to the fullscreen
    // display metric.
    kStartWithDisplayFullscreen = 4096,
  };

  template <int TestFlags = 0, typename HysteresisTestCallback>
  void RunHysteresisTest(HysteresisTestCallback test_callback_func) {
    Initialize(false, kSizeJustRight);

    // Disable nested reporters for the hysteresis tests.
    wtr_->background_reporter_.reset();
    wtr_->muted_reporter_.reset();

    if (TestFlags & kStartWithNativeControls)
      OnNativeControlsEnabled(true);
    if (TestFlags & kStartWithDisplayFullscreen)
      OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kFullscreen);

    // Setup all current time expectations first since they need to use the
    // InSequence macro for ease of use, but we don't want the watch time
    // expectations to be in sequence (or expectations would depend on sorted
    // order of histogram names).
    constexpr base::TimeDelta kWatchTime1 = base::Seconds(10);
    constexpr base::TimeDelta kWatchTime2 = base::Seconds(12);
    constexpr base::TimeDelta kWatchTime3 = base::Seconds(15);
    constexpr base::TimeDelta kWatchTime4 = base::Seconds(30);
    {
      testing::InSequence s;

      EXPECT_CALL(*this, GetCurrentMediaTime())
          .WillOnce(testing::Return(base::TimeDelta()))
          .WillOnce(testing::Return(kWatchTime1));

      // Setup conditions depending on if the test will not resume watch time
      // accumulation or not; i.e. the finalize criteria will not be undone
      // within the hysteresis time.
      if (TestFlags & kAccumulationContinuesAfterTest) {
        EXPECT_CALL(*this, GetCurrentMediaTime())
            .Times(TestFlags & (kFinalizeExitDoesNotRequireCurrentTime |
                                kFinalizePowerWatchTime |
                                kFinalizeControlsWatchTime |
                                kFinalizeDisplayWatchTime)
                       ? 1
                       : 2)
            .WillRepeatedly(testing::Return(kWatchTime2));
        EXPECT_CALL(*this, GetCurrentMediaTime())
            .WillOnce(testing::Return(kWatchTime3));
      } else {
        // Current time should be requested when entering the finalize state.
        EXPECT_CALL(*this, GetCurrentMediaTime())
            .Times(TestFlags & kFinalizeInterleavedStartEvent ? 2 : 1)
            .WillRepeatedly(testing::Return(kWatchTime2));
      }

      if (TestFlags & kTransitionPowerWatchTime) {
        EXPECT_CALL(*this, GetCurrentMediaTime())
            .WillOnce(testing::Return(kWatchTime4));
      }

      if (TestFlags & kTransitionControlsWatchTime) {
        EXPECT_CALL(*this, GetCurrentMediaTime())
            .WillOnce(testing::Return(kWatchTime4));
      }

      if (TestFlags & kTransitionDisplayWatchTime) {
        EXPECT_CALL(*this, GetCurrentMediaTime())
            .WillOnce(testing::Return(kWatchTime4));
      }
    }

    wtr_->OnPlaying();
    EXPECT_TRUE(IsMonitoring());
    if (TestFlags & kStartOnBattery)
      SetOnBatteryPower(true);
    else
      ASSERT_FALSE(IsOnBatteryPower());

    EXPECT_WATCH_TIME(All, kWatchTime1);
    EXPECT_WATCH_TIME(Src, kWatchTime1);
    if (TestFlags & kStartOnBattery)
      EXPECT_WATCH_TIME(Battery, kWatchTime1);
    else
      EXPECT_WATCH_TIME(Ac, kWatchTime1);
    if (TestFlags & kStartWithNativeControls)
      EXPECT_WATCH_TIME(NativeControlsOn, kWatchTime1);
    else
      EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime1);
    if (TestFlags & kStartWithDisplayFullscreen)
      EXPECT_WATCH_TIME(DisplayFullscreen, kWatchTime1);
    else
      EXPECT_WATCH_TIME(DisplayInline, kWatchTime1);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime1);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime1);

    CycleReportingTimer();

    // Invoke the test.
    test_callback_func();

    const base::TimeDelta kExpectedWatchTime =
        TestFlags & kAccumulationContinuesAfterTest ? kWatchTime3 : kWatchTime2;

    EXPECT_WATCH_TIME(All, kExpectedWatchTime);
    EXPECT_WATCH_TIME(Src, kExpectedWatchTime);
    const base::TimeDelta kExpectedPowerWatchTime =
        TestFlags & kFinalizePowerWatchTime ? kWatchTime2 : kExpectedWatchTime;
    const base::TimeDelta kExpectedContolsWatchTime =
        TestFlags & kFinalizeControlsWatchTime ? kWatchTime2
                                               : kExpectedWatchTime;
    const base::TimeDelta kExpectedDisplayWatchTime =
        TestFlags & kFinalizeDisplayWatchTime ? kWatchTime2
                                              : kExpectedWatchTime;

    if (TestFlags & kStartOnBattery)
      EXPECT_WATCH_TIME(Battery, kExpectedPowerWatchTime);
    else
      EXPECT_WATCH_TIME(Ac, kExpectedPowerWatchTime);

    if (TestFlags & kStartWithNativeControls)
      EXPECT_WATCH_TIME(NativeControlsOn, kExpectedContolsWatchTime);
    else
      EXPECT_WATCH_TIME(NativeControlsOff, kExpectedContolsWatchTime);

    if (TestFlags & kStartWithDisplayFullscreen)
      EXPECT_WATCH_TIME(DisplayFullscreen, kExpectedDisplayWatchTime);
    else
      EXPECT_WATCH_TIME(DisplayInline, kExpectedDisplayWatchTime);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kExpectedWatchTime);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kExpectedWatchTime);

    // Special case when testing battery watch time.
    if (TestFlags & kTransitionPowerWatchTime) {
      ASSERT_TRUE(TestFlags & kAccumulationContinuesAfterTest)
          << "kTransitionPowerWatchTime tests must be done with "
             "kAccumulationContinuesAfterTest";

      EXPECT_POWER_WATCH_TIME_FINALIZED();
      CycleReportingTimer();

      // Run one last cycle that is long enough to trigger a new watch time
      // entry on the opposite of the current power watch time graph; i.e. if we
      // started on battery we'll now record one for ac and vice versa.
      EXPECT_WATCH_TIME(All, kWatchTime4);
      EXPECT_WATCH_TIME(Src, kWatchTime4);
      EXPECT_WATCH_TIME(DisplayInline, kWatchTime4);
      EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime4);
      if (TestFlags & kStartOnBattery)
        EXPECT_WATCH_TIME(Ac, kWatchTime4 - kWatchTime2);
      else
        EXPECT_WATCH_TIME(Battery, kWatchTime4 - kWatchTime2);
      EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime4);
      EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime4);
    } else if (TestFlags & kTransitionControlsWatchTime) {
      ASSERT_TRUE(TestFlags & kAccumulationContinuesAfterTest)
          << "kTransitionControlsWatchTime tests must be done with "
             "kAccumulationContinuesAfterTest";

      EXPECT_CONTROLS_WATCH_TIME_FINALIZED();
      CycleReportingTimer();

      // Run one last cycle that is long enough to trigger a new watch time
      // entry on the opposite of the current power watch time graph; i.e. if we
      // started on battery we'll now record one for ac and vice versa.
      EXPECT_WATCH_TIME(All, kWatchTime4);
      EXPECT_WATCH_TIME(Src, kWatchTime4);
      EXPECT_WATCH_TIME(Ac, kWatchTime4);
      EXPECT_WATCH_TIME(DisplayInline, kWatchTime4);
      if (TestFlags & kStartWithNativeControls)
        EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime4 - kWatchTime2);
      else
        EXPECT_WATCH_TIME(NativeControlsOn, kWatchTime4 - kWatchTime2);
      EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime4);
      EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime4);
    } else if (TestFlags & kTransitionDisplayWatchTime) {
      ASSERT_TRUE(TestFlags & kAccumulationContinuesAfterTest)
          << "kTransitionDisplayWatchTime tests must be done with "
             "kAccumulationContinuesAfterTest";

      EXPECT_DISPLAY_WATCH_TIME_FINALIZED();
      CycleReportingTimer();

      // Run one last cycle that is long enough to trigger a new watch time
      // entry on the opposite of the current power watch time graph; i.e. if we
      // started on battery we'll now record one for ac and vice versa.
      EXPECT_WATCH_TIME(All, kWatchTime4);
      EXPECT_WATCH_TIME(Src, kWatchTime4);
      EXPECT_WATCH_TIME(Ac, kWatchTime4);
      EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime4);
      if (TestFlags & kStartWithDisplayFullscreen) {
        EXPECT_WATCH_TIME(DisplayInline, kWatchTime4 - kWatchTime2);
      } else {
        EXPECT_WATCH_TIME(DisplayFullscreen, kWatchTime4 - kWatchTime2);
      }
      EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime4);
      EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime4);
    }

    EXPECT_WATCH_TIME_FINALIZED();
    wtr_.reset();
  }

  MOCK_METHOD0(GetCurrentMediaTime, base::TimeDelta());
  MOCK_METHOD0(GetPipelineStatistics, media::PipelineStatistics());

  MOCK_METHOD0(OnWatchTimeFinalized, void(void));
  MOCK_METHOD0(OnPowerWatchTimeFinalized, void(void));
  MOCK_METHOD0(OnControlsWatchTimeFinalized, void(void));
  MOCK_METHOD0(OnDisplayWatchTimeFinalized, void(void));
  MOCK_METHOD0(OnDominantWatchTimeFinalized, void(void));
  MOCK_METHOD2(OnWatchTimeUpdate, void(WatchTimeKey, base::TimeDelta));
  MOCK_METHOD1(OnUnderflowUpdate, void(int));
  MOCK_METHOD2(OnUnderflowDurationUpdate, void(int, base::TimeDelta));
  MOCK_METHOD1(OnError, void(media::PipelineStatus));
  MOCK_METHOD1(OnUpdateSecondaryProperties,
               void(media::mojom::blink::SecondaryPlaybackPropertiesPtr));
  MOCK_METHOD1(OnSetAutoplayInitiated, void(bool));
  MOCK_METHOD1(OnDurationChanged, void(base::TimeDelta));
  MOCK_METHOD2(OnUpdateVideoDecodeStats, void(uint32_t, uint32_t));

  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  const bool has_video_;
  const bool has_audio_;

  FakeMediaMetricsProvider fake_metrics_provider_;
  std::unique_ptr<WatchTimeReporter> wtr_;
  base::TimeDelta reporting_interval_;
};

class DisplayTypeWatchTimeReporterTest : public WatchTimeReporterTest {};

// Tests that watch time reporting is appropriately enabled or disabled.
TEST_P(WatchTimeReporterTest, WatchTimeReporter) {
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillRepeatedly(testing::Return(base::TimeDelta()));

  Initialize(true, gfx::Size(), media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_EQ(!has_video_, IsMonitoring());

  Initialize(true, gfx::Size(), media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_EQ(!has_video_, IsMonitoring());

  Initialize(true, kSizeTooSmall, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_EQ(!has_video_, IsMonitoring());

  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  Initialize(false, kSizeJustRight);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  Initialize(false, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  EXPECT_CALL(*this,
              OnError(media::HasStatusCode(media::PIPELINE_ERROR_DECODE)))
      .Times((has_audio_ && has_video_) ? 3 : 2);
  wtr_->OnError(media::PIPELINE_ERROR_DECODE);

  Initialize(true, gfx::Size(), media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_EQ(!has_video_, IsMonitoring());

  Initialize(false, gfx::Size());
  wtr_->OnPlaying();
  EXPECT_EQ(!has_video_, IsMonitoring());

  Initialize(false, gfx::Size(), media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_EQ(!has_video_, IsMonitoring());

  if (!has_video_)
    EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterInfiniteStartTime) {
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillRepeatedly(testing::Return(media::kInfiniteDuration));
  Initialize(false, kSizeJustRight);
  wtr_->OnPlaying();
  EXPECT_FALSE(IsMonitoring());
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterBasic) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);
  constexpr base::TimeDelta kWatchTimeLate = base::Seconds(10);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillRepeatedly(testing::Return(kWatchTimeLate));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);

  media::PipelineStatistics stats;
  stats.video_frames_decoded = 10;
  stats.video_frames_dropped = 2;
  if (has_video_) {
    EXPECT_CALL(*this, GetPipelineStatistics())
        .WillOnce(testing::Return(media::PipelineStatistics()))
        .WillRepeatedly(testing::Return(stats));
    EXPECT_CALL(*this, OnUpdateVideoDecodeStats(stats.video_frames_decoded,
                                                stats.video_frames_dropped));
  }

  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
  EXPECT_WATCH_TIME(All, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeEarly);
  CycleReportingTimer();

  wtr_->OnUnderflow();
  constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
  wtr_->OnUnderflowComplete(kUnderflowDuration);
  wtr_->OnUnderflow();
  EXPECT_WATCH_TIME(Ac, kWatchTimeLate);
  EXPECT_WATCH_TIME(All, kWatchTimeLate);
  EXPECT_WATCH_TIME(Eme, kWatchTimeLate);
  EXPECT_WATCH_TIME(Mse, kWatchTimeLate);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeLate);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeLate);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeLate);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeLate);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeLate);
  EXPECT_CALL(*this, OnUnderflowUpdate(2));
  EXPECT_CALL(*this, OnUnderflowDurationUpdate(1, kUnderflowDuration));
  CycleReportingTimer();

  EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterStatsOffsetCorrectly) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);
  constexpr base::TimeDelta kWatchTimeLate = base::Seconds(10);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillRepeatedly(testing::Return(kWatchTimeLate));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);

  media::PipelineStatistics initial_stats;
  initial_stats.video_frames_decoded = 10;
  initial_stats.video_frames_dropped = 2;

  media::PipelineStatistics stats;
  stats.video_frames_decoded = 17;
  stats.video_frames_dropped = 7;
  if (has_video_) {
    EXPECT_CALL(*this, GetPipelineStatistics())
        .WillOnce(testing::Return(initial_stats))
        .WillRepeatedly(testing::Return(stats));
    EXPECT_CALL(
        *this,
        OnUpdateVideoDecodeStats(
            stats.video_frames_decoded - initial_stats.video_frames_decoded,
            stats.video_frames_dropped - initial_stats.video_frames_dropped));
  }

  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
  EXPECT_WATCH_TIME(All, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeEarly);
  CycleReportingTimer();

  wtr_->OnUnderflow();
  constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
  wtr_->OnUnderflowComplete(kUnderflowDuration);
  wtr_->OnUnderflow();
  EXPECT_WATCH_TIME(Ac, kWatchTimeLate);
  EXPECT_WATCH_TIME(All, kWatchTimeLate);
  EXPECT_WATCH_TIME(Eme, kWatchTimeLate);
  EXPECT_WATCH_TIME(Mse, kWatchTimeLate);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeLate);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeLate);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeLate);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeLate);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeLate);
  EXPECT_CALL(*this, OnUnderflowUpdate(2));
  EXPECT_CALL(*this, OnUnderflowDurationUpdate(1, kUnderflowDuration));
  CycleReportingTimer();

  EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterDuration) {
  constexpr base::TimeDelta kDuration1 = base::Seconds(5);
  constexpr base::TimeDelta kDuration2 = base::Seconds(10);
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);

  EXPECT_CALL(*this, OnDurationChanged(kDuration1))
      .Times((has_audio_ && has_video_) ? 3 : 2);
  wtr_->OnDurationChanged(kDuration1);
  CycleReportingTimer();

  EXPECT_CALL(*this, OnDurationChanged(kDuration2))
      .Times((has_audio_ && has_video_) ? 3 : 2);
  wtr_->OnDurationChanged(kDuration2);
  CycleReportingTimer();
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterUnderflow) {
  constexpr base::TimeDelta kWatchTimeFirst = base::Seconds(5);
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(10);
  constexpr base::TimeDelta kWatchTimeLate = base::Seconds(15);
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTimeFirst))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillOnce(testing::Return(kWatchTimeEarly))  // Extra 2 for muted.
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillRepeatedly(testing::Return(kWatchTimeLate));
  } else {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTimeFirst))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillRepeatedly(testing::Return(kWatchTimeLate));
  }
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  EXPECT_WATCH_TIME(Ac, kWatchTimeFirst);
  EXPECT_WATCH_TIME(All, kWatchTimeFirst);
  EXPECT_WATCH_TIME(Eme, kWatchTimeFirst);
  EXPECT_WATCH_TIME(Mse, kWatchTimeFirst);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeFirst);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeFirst);
  CycleReportingTimer();

  wtr_->OnUnderflow();
  wtr_->OnVolumeChange(0);

  constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
  wtr_->OnUnderflowComplete(kUnderflowDuration);

  // This underflow call should be ignored since it happens after the finalize.
  // Note: We use a muted call above to trigger finalize instead of say a pause
  // since media time will be the same in the event of a pause and no underflow
  // should trigger after a pause in any case.
  wtr_->OnUnderflow();

  EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
  EXPECT_WATCH_TIME(All, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeEarly);
  EXPECT_WATCH_TIME_FINALIZED();

  // Since we're using a mute event above, we'll have some muted watch time.
  const base::TimeDelta kWatchTime = kWatchTimeLate - kWatchTimeEarly;
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime);

  EXPECT_CALL(*this, OnUnderflowUpdate(1))
      .Times((has_audio_ && has_video_) ? 2 : 1);
  EXPECT_CALL(*this, OnUnderflowDurationUpdate(1, kUnderflowDuration));
  CycleReportingTimer();

  // Muted watch time shouldn't finalize until destruction.
  if (has_audio_ && has_video_)
    EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterUnderflowSpansFinalize) {
  constexpr base::TimeDelta kWatchTimeFirst = base::Seconds(5);
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(10);
  constexpr base::TimeDelta kWatchTimeLate = base::Seconds(15);
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTimeFirst))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillOnce(testing::Return(kWatchTimeEarly))  // Extra 2 for muted.
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillRepeatedly(testing::Return(kWatchTimeLate));
  } else {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTimeFirst))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillRepeatedly(testing::Return(kWatchTimeLate));
  }
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  EXPECT_WATCH_TIME(Ac, kWatchTimeFirst);
  EXPECT_WATCH_TIME(All, kWatchTimeFirst);
  EXPECT_WATCH_TIME(Eme, kWatchTimeFirst);
  EXPECT_WATCH_TIME(Mse, kWatchTimeFirst);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeFirst);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeFirst);
  CycleReportingTimer();

  wtr_->OnUnderflow();
  wtr_->OnVolumeChange(0);

  EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
  EXPECT_WATCH_TIME(All, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeEarly);
  EXPECT_WATCH_TIME_FINALIZED();

  // Since we're using a mute event above, we'll have some muted watch time.
  const base::TimeDelta kWatchTime = kWatchTimeLate - kWatchTimeEarly;
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_CALL(*this, OnUnderflowUpdate(1));
  CycleReportingTimer();

  // Muted watch time shouldn't finalize until destruction.
  if (has_audio_ && has_video_)
    EXPECT_WATCH_TIME_FINALIZED();

  // This underflow completion should be dropped since we've lost the original
  // underflow it corresponded to in the finalize.
  constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
  wtr_->OnUnderflowComplete(kUnderflowDuration);
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterUnderflowTooLong) {
  constexpr base::TimeDelta kWatchTimeFirst = base::Seconds(5);
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(10);
  constexpr base::TimeDelta kWatchTimeLate = base::Seconds(15);
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTimeFirst))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillOnce(testing::Return(kWatchTimeEarly))  // Extra 2 for muted.
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillRepeatedly(testing::Return(kWatchTimeLate));
  } else {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTimeFirst))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillRepeatedly(testing::Return(kWatchTimeLate));
  }
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  EXPECT_WATCH_TIME(Ac, kWatchTimeFirst);
  EXPECT_WATCH_TIME(All, kWatchTimeFirst);
  EXPECT_WATCH_TIME(Eme, kWatchTimeFirst);
  EXPECT_WATCH_TIME(Mse, kWatchTimeFirst);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeFirst);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeFirst);
  CycleReportingTimer();

  wtr_->OnUnderflow();
  wtr_->OnVolumeChange(0);

  // This underflow took too long to complete so is dropped.
  constexpr base::TimeDelta kUnderflowDuration = base::Minutes(2);
  wtr_->OnUnderflowComplete(kUnderflowDuration);

  EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
  EXPECT_WATCH_TIME(All, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeEarly);
  EXPECT_WATCH_TIME_FINALIZED();

  // Since we're using a mute event above, we'll have some muted watch time.
  const base::TimeDelta kWatchTime = kWatchTimeLate - kWatchTimeEarly;
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_CALL(*this, OnUnderflowUpdate(1));
  CycleReportingTimer();

  // Muted watch time shouldn't finalize until destruction.
  if (has_audio_ && has_video_)
    EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterNoUnderflowDoubleReport) {
  constexpr base::TimeDelta kWatchTimeFirst = base::Seconds(5);
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(10);
  constexpr base::TimeDelta kWatchTimeLate = base::Seconds(15);
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTimeFirst))
        .WillOnce(testing::Return(kWatchTimeFirst))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillRepeatedly(testing::Return(kWatchTimeLate));
  } else {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTimeFirst))
        .WillOnce(testing::Return(kWatchTimeFirst))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillOnce(testing::Return(kWatchTimeEarly))
        .WillRepeatedly(testing::Return(kWatchTimeLate));
  }
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  EXPECT_WATCH_TIME(Ac, kWatchTimeFirst);
  EXPECT_WATCH_TIME(All, kWatchTimeFirst);
  EXPECT_WATCH_TIME(Eme, kWatchTimeFirst);
  EXPECT_WATCH_TIME(Mse, kWatchTimeFirst);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeFirst);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeFirst);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeFirst);
  EXPECT_CALL(*this, OnUnderflowUpdate(1));
  wtr_->OnUnderflow();
  CycleReportingTimer();

  EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
  EXPECT_WATCH_TIME(All, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
  EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeEarly);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeEarly);

  // This cycle should not report another underflow.
  CycleReportingTimer();

  constexpr base::TimeDelta kUnderflowDuration = base::Milliseconds(250);
  wtr_->OnUnderflowComplete(kUnderflowDuration);
  EXPECT_CALL(*this, OnUnderflowDurationUpdate(1, kUnderflowDuration));

  EXPECT_WATCH_TIME_FINALIZED();
}

// Verify secondary properties pass through correctly.
TEST_P(WatchTimeReporterTest, WatchTimeReporterSecondaryProperties) {
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);

  auto properties = media::mojom::blink::SecondaryPlaybackProperties::New(
      has_audio_ ? media::AudioCodec::kAAC : media::AudioCodec::kUnknown,
      has_video_ ? media::VideoCodec::kH264 : media::VideoCodec::kUnknown,
      has_audio_ ? media::AudioCodecProfile::kXHE_AAC
                 : media::AudioCodecProfile::kUnknown,
      has_video_ ? media::H264PROFILE_MAIN : media::VIDEO_CODEC_PROFILE_UNKNOWN,
      has_audio_ ? media::AudioDecoderType::kMojo
                 : media::AudioDecoderType::kUnknown,
      has_video_ ? media::VideoDecoderType::kMojo
                 : media::VideoDecoderType::kUnknown,
      has_audio_ ? media::EncryptionScheme::kCenc
                 : media::EncryptionScheme::kUnencrypted,
      has_video_ ? media::EncryptionScheme::kCbcs
                 : media::EncryptionScheme::kUnencrypted,
      has_video_ ? gfx::Size(800, 600) : gfx::Size());

  // Get a pointer to our original properties since we're not allowed to use
  // lambda capture for movable types in Chromium C++ yet.
  auto* properies_ptr = properties.get();

  // Muted watch time is only reported for audio+video.
  EXPECT_CALL(*this, OnUpdateSecondaryProperties(_))
      .Times((has_audio_ && has_video_) ? 3 : 2)
      .WillRepeatedly([properies_ptr](auto secondary_properties) {
        ASSERT_TRUE(properies_ptr->Equals(*secondary_properties));
      });
  wtr_->UpdateSecondaryProperties(properties.Clone());
  CycleReportingTimer();

  // Ensure expectations are met before |properies| goes out of scope.
  testing::Mock::VerifyAndClearExpectations(this);
}

TEST_P(WatchTimeReporterTest, SecondaryProperties_SizeIncreased) {
  if (!has_video_)
    return;

  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillRepeatedly(testing::Return(base::TimeDelta()));
  Initialize(false, kSizeTooSmall);
  wtr_->OnPlaying();
  EXPECT_FALSE(IsMonitoring());

  EXPECT_CALL(*this, OnUpdateSecondaryProperties(_))
      .Times((has_audio_ && has_video_) ? 3 : 2);
  wtr_->UpdateSecondaryProperties(
      media::mojom::blink::SecondaryPlaybackProperties::New(
          media::AudioCodec::kUnknown, media::VideoCodec::kUnknown,
          media::AudioCodecProfile::kUnknown,
          media::VIDEO_CODEC_PROFILE_UNKNOWN, media::AudioDecoderType::kUnknown,
          media::VideoDecoderType::kUnknown,
          media::EncryptionScheme::kUnencrypted,
          media::EncryptionScheme::kUnencrypted, kSizeJustRight));
  EXPECT_TRUE(IsMonitoring());

  EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(WatchTimeReporterTest, SecondaryProperties_SizeDecreased) {
  if (!has_video_)
    return;

  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillRepeatedly(testing::Return(base::TimeDelta()));
  Initialize(false, kSizeJustRight);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  EXPECT_CALL(*this, OnUpdateSecondaryProperties(_))
      .Times((has_audio_ && has_video_) ? 3 : 2);
  wtr_->UpdateSecondaryProperties(
      media::mojom::blink::SecondaryPlaybackProperties::New(
          media::AudioCodec::kUnknown, media::VideoCodec::kUnknown,
          media::AudioCodecProfile::kUnknown,
          media::VIDEO_CODEC_PROFILE_UNKNOWN, media::AudioDecoderType::kUnknown,
          media::VideoDecoderType::kUnknown,
          media::EncryptionScheme::kUnencrypted,
          media::EncryptionScheme::kUnencrypted, kSizeTooSmall));
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  EXPECT_FALSE(IsMonitoring());
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterAutoplayInitiated) {
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);

  EXPECT_CALL(*this, OnSetAutoplayInitiated(true))
      .Times((has_audio_ && has_video_) ? 3 : 2);
  wtr_->SetAutoplayInitiated(true);
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterShownHidden) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(8);
  constexpr base::TimeDelta kWatchTimeLate = base::Seconds(25);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillRepeatedly(testing::Return(kWatchTimeLate));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  wtr_->OnHidden();
  const base::TimeDelta kExpectedWatchTime = kWatchTimeLate - kWatchTimeEarly;
  EXPECT_BACKGROUND_WATCH_TIME(Ac, kExpectedWatchTime);
  EXPECT_BACKGROUND_WATCH_TIME(All, kExpectedWatchTime);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kExpectedWatchTime);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kExpectedWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();

  // One call for the background, one for the foreground, and one for the muted
  // reporter if we have audio+video.
  EXPECT_CALL(*this,
              OnError(media::HasStatusCode(media::PIPELINE_ERROR_DECODE)))
      .Times((has_audio_ && has_video_) ? 3 : 2);
  wtr_->OnError(media::PIPELINE_ERROR_DECODE);

  const base::TimeDelta kExpectedForegroundWatchTime = kWatchTimeEarly;
  EXPECT_WATCH_TIME(Ac, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME(All, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME(Eme, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME(Mse, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent,
                             kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterBackgroundHysteresis) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(8);
  constexpr base::TimeDelta kWatchTimeLate = base::Seconds(10);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))  // 2x for playing
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTimeEarly))  // 2x for shown
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillOnce(testing::Return(kWatchTimeEarly))  // 2x for hidden
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillOnce(testing::Return(kWatchTimeEarly))  // 1x for timer cycle.
      .WillRepeatedly(testing::Return(kWatchTimeLate));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  DisableMutedReporting();  // Just complicates this test.

  wtr_->OnHidden();
  wtr_->OnPlaying();
  EXPECT_TRUE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());

  wtr_->OnShown();
  wtr_->OnHidden();
  EXPECT_BACKGROUND_WATCH_TIME(Ac, kWatchTimeEarly);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTimeEarly);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTimeEarly);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kWatchTimeEarly);
  EXPECT_TRUE(IsBackgroundMonitoring());
  EXPECT_TRUE(IsMonitoring());
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  EXPECT_TRUE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());

  EXPECT_BACKGROUND_WATCH_TIME(Ac, kWatchTimeLate);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTimeLate);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTimeLate);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kWatchTimeLate);
  EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterShownHiddenBackground) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(8);
  constexpr base::TimeDelta kWatchTimeLate = base::Seconds(10);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillRepeatedly(testing::Return(kWatchTimeLate));

  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  DisableMutedReporting();  // Just complicates this test.

  wtr_->OnHidden();
  wtr_->OnPlaying();
  EXPECT_TRUE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());

  wtr_->OnShown();
  EXPECT_BACKGROUND_WATCH_TIME(Ac, kWatchTimeEarly);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTimeEarly);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTimeEarly);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kWatchTimeEarly);
  EXPECT_WATCH_TIME_FINALIZED();

  const base::TimeDelta kExpectedForegroundWatchTime =
      kWatchTimeLate - kWatchTimeEarly;
  EXPECT_WATCH_TIME(Ac, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME(All, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME(Eme, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME(Mse, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent,
                             kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kExpectedForegroundWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kExpectedForegroundWatchTime);
  CycleReportingTimer();

  EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterHiddenPausedBackground) {
  constexpr base::TimeDelta kWatchTime = base::Seconds(8);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillRepeatedly(testing::Return(kWatchTime));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnHidden();
  wtr_->OnPlaying();
  EXPECT_TRUE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());

  wtr_->OnPaused();
  EXPECT_BACKGROUND_WATCH_TIME(Ac, kWatchTime);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTime);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTime);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  EXPECT_FALSE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterHiddenSeekedBackground) {
  constexpr base::TimeDelta kWatchTime = base::Seconds(8);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillRepeatedly(testing::Return(kWatchTime));
  Initialize(true, kSizeJustRight);
  wtr_->OnHidden();
  wtr_->OnPlaying();
  EXPECT_TRUE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());

  EXPECT_BACKGROUND_WATCH_TIME(Ac, kWatchTime);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTime);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTime);
  EXPECT_BACKGROUND_WATCH_TIME(Src, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_->OnSeeking();

  EXPECT_FALSE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterHiddenPowerBackground) {
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(8);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(16);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime1))
      .WillOnce(testing::Return(kWatchTime1))
      .WillRepeatedly(testing::Return(kWatchTime2));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnHidden();
  wtr_->OnPlaying();
  EXPECT_TRUE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());

  OnPowerStateChange(true);
  EXPECT_BACKGROUND_WATCH_TIME(Ac, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kWatchTime1);
  EXPECT_POWER_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  wtr_->OnPaused();
  EXPECT_BACKGROUND_WATCH_TIME(Battery, kWatchTime2 - kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTime2);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTime2);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kWatchTime2);
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  EXPECT_FALSE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterHiddenControlsBackground) {
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(8);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(16);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime1))
      .WillOnce(testing::Return(kWatchTime2));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnHidden();
  wtr_->OnPlaying();
  EXPECT_TRUE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());

  OnNativeControlsEnabled(true);

  EXPECT_BACKGROUND_WATCH_TIME(Ac, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kWatchTime1);
  CycleReportingTimer();

  wtr_->OnPaused();
  EXPECT_BACKGROUND_WATCH_TIME(Ac, kWatchTime2);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTime2);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTime2);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kWatchTime2);
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  EXPECT_FALSE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());
}

TEST_P(DisplayTypeWatchTimeReporterTest,
       WatchTimeReporterHiddenDisplayTypeBackground) {
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(8);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(16);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime1))
      .WillOnce(testing::Return(kWatchTime2));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnHidden();
  wtr_->OnPlaying();
  EXPECT_TRUE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());

  OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kFullscreen);

  EXPECT_BACKGROUND_WATCH_TIME(Ac, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kWatchTime1);
  CycleReportingTimer();

  wtr_->OnPaused();
  EXPECT_BACKGROUND_WATCH_TIME(Ac, kWatchTime2);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTime2);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTime2);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kWatchTime2);
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  EXPECT_FALSE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterHiddenMuted) {
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(8);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(25);

  // Expectations for when muted watch time is recorded and when it isn't.
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))  // 2x playing.
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTime1))  // 2x muted.
        .WillOnce(testing::Return(kWatchTime1))
        .WillOnce(testing::Return(kWatchTime1))  // 2x shown.
        .WillOnce(testing::Return(kWatchTime1))
        .WillRepeatedly(testing::Return(kWatchTime2));
  } else {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))  // 2x playing.
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTime1))  // 1x muted.
        .WillOnce(testing::Return(kWatchTime1))  // 1x shown.
        .WillRepeatedly(testing::Return(kWatchTime2));
  }

  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnHidden();
  wtr_->OnPlaying();
  EXPECT_TRUE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());

  wtr_->OnVolumeChange(0);
  EXPECT_TRUE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMutedMonitoring());

  EXPECT_BACKGROUND_WATCH_TIME(Ac, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(All, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(Eme, kWatchTime1);
  EXPECT_BACKGROUND_WATCH_TIME(Mse, kWatchTime1);
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  wtr_->OnShown();
  EXPECT_FALSE(IsBackgroundMonitoring());
  EXPECT_FALSE(IsMonitoring());
  EXPECT_EQ(has_audio_ && has_video_, IsMutedMonitoring());

  const base::TimeDelta kWatchTime = kWatchTime2 - kWatchTime1;
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTime);
  if (has_audio_ && has_video_)
    EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(WatchTimeReporterTest, WatchTimeReporterMultiplePartialFinalize) {
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(8);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(16);

  // Transition controls and battery.
  {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTime1))
        .WillOnce(testing::Return(kWatchTime1))
        .WillOnce(testing::Return(kWatchTime1))
        .WillOnce(testing::Return(kWatchTime2));
    Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
    wtr_->OnPlaying();
    EXPECT_TRUE(IsMonitoring());

    OnNativeControlsEnabled(true);
    OnPowerStateChange(true);

    EXPECT_WATCH_TIME(Ac, kWatchTime1);
    EXPECT_WATCH_TIME(All, kWatchTime1);
    EXPECT_WATCH_TIME(Eme, kWatchTime1);
    EXPECT_WATCH_TIME(Mse, kWatchTime1);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime1);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTime1);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime1);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime1);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime1);
    EXPECT_CONTROLS_WATCH_TIME_FINALIZED();
    EXPECT_POWER_WATCH_TIME_FINALIZED();
    CycleReportingTimer();

    wtr_->OnPaused();
    EXPECT_WATCH_TIME(All, kWatchTime2);
    EXPECT_WATCH_TIME(Eme, kWatchTime2);
    EXPECT_WATCH_TIME(Mse, kWatchTime2);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTime2);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime2);
    EXPECT_WATCH_TIME(NativeControlsOn, kWatchTime2 - kWatchTime1);
    EXPECT_WATCH_TIME(Battery, kWatchTime2 - kWatchTime1);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime2);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime2);
    EXPECT_WATCH_TIME_FINALIZED();
    CycleReportingTimer();

    EXPECT_FALSE(IsMonitoring());
  }

  // Transition display type and battery. Test only works with video.
  if (has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTime1))
        .WillOnce(testing::Return(kWatchTime1))
        .WillOnce(testing::Return(kWatchTime1))
        .WillOnce(testing::Return(kWatchTime2));
    Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
    wtr_->OnPlaying();
    EXPECT_TRUE(IsMonitoring());

    OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kFullscreen);
    OnPowerStateChange(true);

    EXPECT_WATCH_TIME(Ac, kWatchTime1);
    EXPECT_WATCH_TIME(All, kWatchTime1);
    EXPECT_WATCH_TIME(Eme, kWatchTime1);
    EXPECT_WATCH_TIME(Mse, kWatchTime1);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime1);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTime1);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime1);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime1);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime1);
    EXPECT_DISPLAY_WATCH_TIME_FINALIZED();
    EXPECT_POWER_WATCH_TIME_FINALIZED();
    CycleReportingTimer();

    wtr_->OnPaused();
    EXPECT_WATCH_TIME(All, kWatchTime2);
    EXPECT_WATCH_TIME(Eme, kWatchTime2);
    EXPECT_WATCH_TIME(Mse, kWatchTime2);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime2);
    EXPECT_WATCH_TIME(DisplayFullscreen, kWatchTime2 - kWatchTime1);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime2);
    EXPECT_WATCH_TIME(Battery, kWatchTime2 - kWatchTime1);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime2);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime2);
    EXPECT_WATCH_TIME_FINALIZED();
    CycleReportingTimer();

    EXPECT_FALSE(IsMonitoring());
  }

  // Transition controls, battery and display type. Test only works with video.
  if (has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillOnce(testing::Return(kWatchTime1))
        .WillOnce(testing::Return(kWatchTime1))
        .WillOnce(testing::Return(kWatchTime1))
        .WillOnce(testing::Return(kWatchTime1))
        .WillOnce(testing::Return(kWatchTime2));
    Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
    wtr_->OnPlaying();
    EXPECT_TRUE(IsMonitoring());

    OnNativeControlsEnabled(true);
    OnPowerStateChange(true);
    OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kVideoPictureInPicture);

    EXPECT_WATCH_TIME(Ac, kWatchTime1);
    EXPECT_WATCH_TIME(All, kWatchTime1);
    EXPECT_WATCH_TIME(Eme, kWatchTime1);
    EXPECT_WATCH_TIME(Mse, kWatchTime1);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime1);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTime1);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime1);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime1);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime1);
    EXPECT_CONTROLS_WATCH_TIME_FINALIZED();
    EXPECT_POWER_WATCH_TIME_FINALIZED();
    EXPECT_DISPLAY_WATCH_TIME_FINALIZED();
    CycleReportingTimer();

    wtr_->OnPaused();
    EXPECT_WATCH_TIME(All, kWatchTime2);
    EXPECT_WATCH_TIME(Eme, kWatchTime2);
    EXPECT_WATCH_TIME(Mse, kWatchTime2);
    EXPECT_WATCH_TIME(DisplayPictureInPicture, kWatchTime2 - kWatchTime1);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime2);
    EXPECT_WATCH_TIME(NativeControlsOn, kWatchTime2 - kWatchTime1);
    EXPECT_WATCH_TIME(Battery, kWatchTime2 - kWatchTime1);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime2);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime2);
    EXPECT_WATCH_TIME_FINALIZED();
    CycleReportingTimer();

    EXPECT_FALSE(IsMonitoring());
  }
}

// Tests that starting from a non-zero base works.
TEST_P(WatchTimeReporterTest, WatchTimeReporterNonZeroStart) {
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(5);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(15);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(kWatchTime1))
      .WillRepeatedly(testing::Return(kWatchTime2));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  const base::TimeDelta kWatchTime = kWatchTime2 - kWatchTime1;
  EXPECT_WATCH_TIME(Ac, kWatchTime);
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Eme, kWatchTime);
  EXPECT_WATCH_TIME(Mse, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime);
  CycleReportingTimer();

  EXPECT_WATCH_TIME_FINALIZED();
}

// Tests that seeking causes an immediate finalization.
TEST_P(WatchTimeReporterTest, SeekFinalizes) {
  constexpr base::TimeDelta kWatchTime = base::Seconds(10);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  EXPECT_WATCH_TIME(Ac, kWatchTime);
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Eme, kWatchTime);
  EXPECT_WATCH_TIME(Mse, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_->OnSeeking();
}

// Tests that seeking can't be undone by anything other than OnPlaying().
TEST_P(WatchTimeReporterTest, SeekOnlyClearedByPlaying) {
  constexpr base::TimeDelta kWatchTime = base::Seconds(10);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillRepeatedly(testing::Return(kWatchTime));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  EXPECT_WATCH_TIME(Ac, kWatchTime);
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Eme, kWatchTime);
  EXPECT_WATCH_TIME(Mse, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_->OnSeeking();
  EXPECT_FALSE(IsMonitoring());

  wtr_->OnHidden();
  wtr_->OnShown();
  wtr_->OnVolumeChange(0);
  wtr_->OnVolumeChange(1);
  EXPECT_FALSE(IsMonitoring());

  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  // Because the above calls may tickle the background and muted reporters,
  // we'll receive 2-3 finalize calls upon destruction if they exist.
  if (has_audio_ && has_video_)
    EXPECT_WATCH_TIME_FINALIZED();
  EXPECT_WATCH_TIME_FINALIZED();
  EXPECT_WATCH_TIME_FINALIZED();
}

// Tests that seeking causes an immediate finalization, but does not trample a
// previously set finalize time.
TEST_P(WatchTimeReporterTest, SeekFinalizeDoesNotTramplePreviousFinalize) {
  constexpr base::TimeDelta kWatchTime = base::Seconds(10);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  EXPECT_WATCH_TIME(Ac, kWatchTime);
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Eme, kWatchTime);
  EXPECT_WATCH_TIME(Mse, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_->OnPaused();
  wtr_->OnSeeking();
}

// Tests that watch time is finalized upon destruction.
TEST_P(WatchTimeReporterTest, WatchTimeReporterFinalizeOnDestruction) {
  constexpr base::TimeDelta kWatchTime = base::Seconds(10);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());

  // Finalize the histogram before any cycles of the timer have run.
  EXPECT_WATCH_TIME(Ac, kWatchTime);
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Eme, kWatchTime);
  EXPECT_WATCH_TIME(Mse, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
}

// Tests that watch time categories are mapped correctly.
TEST_P(WatchTimeReporterTest, WatchTimeCategoryMapping) {
  constexpr base::TimeDelta kWatchTime = base::Seconds(10);

  // Verify ac, all, src, non-native controls
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime));
  Initialize(false, kSizeJustRight);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());
  EXPECT_WATCH_TIME(Ac, kWatchTime);
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Src, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_.reset();

  // Verify ac, all, mse, non-native controls
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime));
  Initialize(false, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());
  EXPECT_WATCH_TIME(Ac, kWatchTime);
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Mse, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_.reset();

  // Verify ac, all, eme, src, non-native controls
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime));
  Initialize(true, kSizeJustRight);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());
  EXPECT_WATCH_TIME(Ac, kWatchTime);
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Eme, kWatchTime);
  EXPECT_WATCH_TIME(Src, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_.reset();

  // Verify all, battery, src, non-native controls
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime));
  Initialize(false, kSizeJustRight);
  wtr_->OnPlaying();
  SetOnBatteryPower(true);
  EXPECT_TRUE(IsMonitoring());
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Battery, kWatchTime);
  EXPECT_WATCH_TIME(Src, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_.reset();

  // Verify ac, all, src, native controls
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime));
  Initialize(false, kSizeJustRight);
  OnNativeControlsEnabled(true);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());
  EXPECT_WATCH_TIME(Ac, kWatchTime);
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Src, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOn, kWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_.reset();

  // Verify all, battery, src, non-native controls, display fullscreen
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime));
  Initialize(false, kSizeJustRight);
  OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kFullscreen);
  wtr_->OnPlaying();
  SetOnBatteryPower(true);
  EXPECT_TRUE(IsMonitoring());
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Battery, kWatchTime);
  EXPECT_WATCH_TIME(Src, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME(DisplayFullscreen, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_.reset();

  // Verify ac, all, src, native controls, display picture-in-picture
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime));
  Initialize(false, kSizeJustRight);
  OnNativeControlsEnabled(true);
  OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kVideoPictureInPicture);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());
  EXPECT_WATCH_TIME(Ac, kWatchTime);
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Src, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOn, kWatchTime);
  EXPECT_WATCH_TIME(DisplayPictureInPicture, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_.reset();

  // Verify ac, all, src, native controls, display picture-in-picture (with
  // display type changed using `kDocumentPictureInPicture`).
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime));
  Initialize(false, kSizeJustRight);
  OnNativeControlsEnabled(true);
  OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kDocumentPictureInPicture);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMonitoring());
  EXPECT_WATCH_TIME(Ac, kWatchTime);
  EXPECT_WATCH_TIME(All, kWatchTime);
  EXPECT_WATCH_TIME(Src, kWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOn, kWatchTime);
  EXPECT_WATCH_TIME(DisplayPictureInPicture, kWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_.reset();
}

TEST_P(WatchTimeReporterTest, PlayPauseHysteresisContinuation) {
  RunHysteresisTest<kAccumulationContinuesAfterTest>([this]() {
    wtr_->OnPaused();
    wtr_->OnPlaying();
  });
}

TEST_P(WatchTimeReporterTest, PlayPauseHysteresisFinalized) {
  RunHysteresisTest([this]() { wtr_->OnPaused(); });
}

TEST_P(WatchTimeReporterTest, OnVolumeChangeHysteresisContinuation) {
  RunHysteresisTest<kAccumulationContinuesAfterTest>([this]() {
    wtr_->OnVolumeChange(0);
    wtr_->OnVolumeChange(1);
  });
}

TEST_P(WatchTimeReporterTest, OnVolumeChangeHysteresisFinalized) {
  RunHysteresisTest([this]() { wtr_->OnVolumeChange(0); });
}

TEST_P(WatchTimeReporterTest, OnShownHiddenHysteresisContinuation) {
  RunHysteresisTest<kAccumulationContinuesAfterTest>([this]() {
    wtr_->OnHidden();
    wtr_->OnShown();
  });
}

TEST_P(WatchTimeReporterTest, OnShownHiddenHysteresisFinalized) {
  RunHysteresisTest([this]() { wtr_->OnHidden(); });
}

TEST_P(WatchTimeReporterTest, OnPowerStateChangeHysteresisBatteryContinuation) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeExitDoesNotRequireCurrentTime | kStartOnBattery>(
      [this]() {
        OnPowerStateChange(false);
        OnPowerStateChange(true);
      });
}

TEST_P(WatchTimeReporterTest, OnPowerStateChangeHysteresisBatteryFinalized) {
  RunHysteresisTest<kAccumulationContinuesAfterTest | kFinalizePowerWatchTime |
                    kStartOnBattery>([this]() { OnPowerStateChange(false); });
}

TEST_P(WatchTimeReporterTest, OnPowerStateChangeHysteresisAcContinuation) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeExitDoesNotRequireCurrentTime>([this]() {
    OnPowerStateChange(true);
    OnPowerStateChange(false);
  });
}

TEST_P(WatchTimeReporterTest, OnPowerStateChangeHysteresisAcFinalized) {
  RunHysteresisTest<kAccumulationContinuesAfterTest | kFinalizePowerWatchTime>(
      [this]() { OnPowerStateChange(true); });
}

TEST_P(WatchTimeReporterTest, OnPowerStateChangeBatteryTransitions) {
  RunHysteresisTest<kAccumulationContinuesAfterTest | kFinalizePowerWatchTime |
                    kStartOnBattery | kTransitionPowerWatchTime>(
      [this]() { OnPowerStateChange(false); });
}

TEST_P(WatchTimeReporterTest, OnPowerStateChangeAcTransitions) {
  RunHysteresisTest<kAccumulationContinuesAfterTest | kFinalizePowerWatchTime |
                    kTransitionPowerWatchTime>(
      [this]() { OnPowerStateChange(true); });
}

TEST_P(WatchTimeReporterTest, OnControlsChangeHysteresisNativeContinuation) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeExitDoesNotRequireCurrentTime |
                    kStartWithNativeControls>([this]() {
    OnNativeControlsEnabled(false);
    OnNativeControlsEnabled(true);
  });
}

TEST_P(WatchTimeReporterTest, OnControlsChangeHysteresisNativeFinalized) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeControlsWatchTime | kStartWithNativeControls>(
      [this]() { OnNativeControlsEnabled(false); });
}

TEST_P(WatchTimeReporterTest, OnControlsChangeHysteresisNativeOffContinuation) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeExitDoesNotRequireCurrentTime>([this]() {
    OnNativeControlsEnabled(true);
    OnNativeControlsEnabled(false);
  });
}

TEST_P(WatchTimeReporterTest, OnControlsChangeHysteresisNativeOffFinalized) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeControlsWatchTime>(
      [this]() { OnNativeControlsEnabled(true); });
}

TEST_P(WatchTimeReporterTest, OnControlsChangeToNativeOff) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeControlsWatchTime | kStartWithNativeControls |
                    kTransitionControlsWatchTime>(
      [this]() { OnNativeControlsEnabled(false); });
}

TEST_P(WatchTimeReporterTest, OnControlsChangeToNative) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeControlsWatchTime | kTransitionControlsWatchTime>(
      [this]() { OnNativeControlsEnabled(true); });
}

TEST_P(DisplayTypeWatchTimeReporterTest,
       OnDisplayTypeChangeHysteresisFullscreenContinuation) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeExitDoesNotRequireCurrentTime |
                    kStartWithDisplayFullscreen>([this]() {
    OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kInline);
    OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kFullscreen);
  });
}

TEST_P(DisplayTypeWatchTimeReporterTest,
       OnDisplayTypeChangeHysteresisNativeFinalized) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeDisplayWatchTime | kStartWithDisplayFullscreen>(
      [this]() { OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kInline); });
}

TEST_P(DisplayTypeWatchTimeReporterTest,
       OnDisplayTypeChangeHysteresisInlineContinuation) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeExitDoesNotRequireCurrentTime>([this]() {
    OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kFullscreen);
    OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kInline);
  });
}

TEST_P(DisplayTypeWatchTimeReporterTest,
       OnDisplayTypeChangeHysteresisNativeOffFinalized) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeDisplayWatchTime>([this]() {
    OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kFullscreen);
  });
}

TEST_P(DisplayTypeWatchTimeReporterTest,
       OnDisplayTypeChangeInlineToFullscreen) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeDisplayWatchTime | kStartWithDisplayFullscreen |
                    kTransitionDisplayWatchTime>(
      [this]() { OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kInline); });
}

TEST_P(DisplayTypeWatchTimeReporterTest,
       OnDisplayTypeChangeFullscreenToInline) {
  RunHysteresisTest<kAccumulationContinuesAfterTest |
                    kFinalizeDisplayWatchTime | kTransitionDisplayWatchTime>(
      [this]() {
        OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kFullscreen);
      });
}

// Tests that the first finalize is the only one that matters.
TEST_P(WatchTimeReporterTest, HysteresisFinalizedWithEarliest) {
  RunHysteresisTest([this]() {
    wtr_->OnPaused();

    // These subsequent "stop events" should do nothing since a finalize time
    // has already been selected.
    wtr_->OnHidden();
    wtr_->OnVolumeChange(0);
  });
}

// Tests that if a stop, stop, start sequence occurs, the middle stop is not
// undone and thus finalize still occurs.
TEST_P(WatchTimeReporterTest, HysteresisPartialExitStillFinalizes) {
  auto stop_event = [this](size_t i) {
    if (i == 0) {
      wtr_->OnPaused();
    } else if (i == 1) {
      wtr_->OnVolumeChange(0);
    } else {
      ASSERT_TRUE(has_video_);
      wtr_->OnHidden();
    }
  };

  auto start_event = [this](size_t i) {
    if (i == 0) {
      wtr_->OnPlaying();
    } else if (i == 1) {
      wtr_->OnVolumeChange(1);
    } else {
      ASSERT_TRUE(has_video_);
      wtr_->OnShown();
    }
  };

  const size_t kTestSize = has_video_ ? 3 : 2;
  for (size_t i = 0; i < kTestSize; ++i) {
    for (size_t j = 0; j < kTestSize; ++j) {
      if (i == j)
        continue;

      RunHysteresisTest<kFinalizeInterleavedStartEvent>(
          [i, j, start_event, stop_event]() {
            stop_event(i);
            stop_event(j);
            start_event(i);
          });
    }
  }
}

// Tests Media Foundation related Keys being used and given to recorder.
TEST_P(WatchTimeReporterTest, WatchTimeReporterMediaFoundation) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer,
               media::RendererType::kMediaFoundation);
    wtr_->OnPlaying();

    // Check the following keys are used.
    EXPECT_WATCH_TIME(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_MEDIAFOUNDATION(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_MEDIAFOUNDATION(Eme, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(MediaFoundationSdrAll,
                                         kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(MediaFoundationSdrEme,
                                         kWatchTimeEarly);

    EXPECT_TRUE(IsMonitoring());

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests Media Foundation related keys given no EME.
TEST_P(WatchTimeReporterTest, WatchTimeReporterMediaFoundationNoEme) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(false, kSizeJustRight, media::DemuxerType::kChunkDemuxer,
               media::RendererType::kMediaFoundation);
    wtr_->OnPlaying();

    // Check the following keys are used.
    EXPECT_WATCH_TIME(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_MEDIAFOUNDATION(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(MediaFoundationSdrAll,
                                         kWatchTimeEarly);

    EXPECT_TRUE(IsMonitoring());

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests HDR related Keys given no EME.
TEST_P(WatchTimeReporterTest, WatchTimeReporterHdrAll) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  // Will include only audio and only video testing when the related keys are
  // added.
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(false, kSizeJustRight, media::DemuxerType::kChunkDemuxer);

    wtr_->OnHdrChanged(true);
    wtr_->OnPlaying();

    // Check the following keys are used.
    EXPECT_WATCH_TIME(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(HdrAll, kWatchTimeEarly);

    EXPECT_TRUE(IsMonitoring());

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests HDR related Keys being used and given to recorder.
TEST_P(WatchTimeReporterTest, WatchTimeReporterHdrEme) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  // Will include only audio and only video testing when the related keys are
  // added.
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);

    wtr_->OnHdrChanged(true);
    wtr_->OnPlaying();

    // Check the following keys are used.
    EXPECT_WATCH_TIME(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(HdrAll, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(HdrEme, kWatchTimeEarly);

    EXPECT_TRUE(IsMonitoring());

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests MediaFoundation HDR related Keys given no EME.
TEST_P(WatchTimeReporterTest, WatchTimeReporterMediaFoundationHdrAll) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  // Will include only audio and only video testing when the related keys are
  // added.
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(false, kSizeJustRight, media::DemuxerType::kChunkDemuxer,
               media::RendererType::kMediaFoundation);

    wtr_->OnHdrChanged(true);
    wtr_->OnPlaying();

    // Check the following keys are used.
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(MediaFoundationAll, kWatchTimeEarly);
    EXPECT_WATCH_TIME(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(MediaFoundationHdrAll,
                                         kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(HdrAll, kWatchTimeEarly);

    EXPECT_TRUE(IsMonitoring());

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests MediaFoundation HDR related Keys being used and given to recorder.
TEST_P(WatchTimeReporterTest, WatchTimeReporterMediaFoundationHdrEme) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  // Will include only audio and only video testing when the related keys are
  // added.
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer,
               media::RendererType::kMediaFoundation);

    wtr_->OnHdrChanged(true);
    wtr_->OnPlaying();

    // Check the following keys are used.
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(MediaFoundationAll, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(MediaFoundationEme, kWatchTimeEarly);
    EXPECT_WATCH_TIME(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(MediaFoundationHdrAll,
                                         kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(MediaFoundationHdrEme,
                                         kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(HdrAll, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(HdrEme, kWatchTimeEarly);

    EXPECT_TRUE(IsMonitoring());

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests Audio + video HLS SDR support.
TEST_P(WatchTimeReporterTest, WatchTimeReporterSdrHls) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  // Will include only audio and only video testing when the related keys are
  // added.
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(true, kSizeJustRight, media::DemuxerType::kManifestDemuxer);

    wtr_->OnPlaying();

    // Check the following keys are used.
    EXPECT_WATCH_TIME(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Hls, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeEarly);

    EXPECT_TRUE(IsMonitoring());

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests Audio + video HLS, SDR, muted support.
TEST_P(WatchTimeReporterTest, WatchTimeReporterSdrMutedHls) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  // Will include only audio and only video testing when the related keys are
  // added.
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(true, kSizeJustRight, media::DemuxerType::kManifestDemuxer);

    wtr_->OnVolumeChange(0);
    wtr_->OnPlaying();
    EXPECT_TRUE(IsMutedMonitoring());
    EXPECT_FALSE(IsMonitoring());

    // Check the following keys are used.
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Hls, kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent,
                                           kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTimeEarly);

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests Audio + video HLS HDR support.
TEST_P(WatchTimeReporterTest, WatchTimeReporterHdrHls) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  // Will include only audio and only video testing when the related keys are
  // added.
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(true, kSizeJustRight, media::DemuxerType::kManifestDemuxer);

    wtr_->OnHdrChanged(true);
    wtr_->OnPlaying();

    // Check the following keys are used.
    EXPECT_WATCH_TIME(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Hls, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(HdrAll, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(HdrEme, kWatchTimeEarly);

    EXPECT_TRUE(IsMonitoring());

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests Audio + video HLS, SDR, mute/unmute support.
TEST_P(WatchTimeReporterTest, WatchTimeReporterSdrMuteUnmuteHls) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  // Will include only audio and only video testing when the related keys are
  // added.
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(true, kSizeJustRight, media::DemuxerType::kManifestDemuxer);

    wtr_->OnVolumeChange(0);
    wtr_->OnPlaying();
    EXPECT_TRUE(IsMutedMonitoring());
    EXPECT_FALSE(IsMonitoring());

    wtr_->OnVolumeChange(1);
    EXPECT_WATCH_TIME_FINALIZED();

    EXPECT_TRUE(IsMonitoring());
    // Check the following keys are used.
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Hls, kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent,
                                           kWatchTimeEarly);
    EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTimeEarly);

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests Audio + HLS support.
TEST_P(WatchTimeReporterTest, WatchTimeReporterAudioHls) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  // Will include only audio and only video testing when the related keys are
  // added.
  if (has_audio_ && !has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(true, kSizeJustRight, media::DemuxerType::kManifestDemuxer);

    wtr_->OnPlaying();

    // Check the following keys are used.
    EXPECT_WATCH_TIME(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Hls, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);

    EXPECT_TRUE(IsMonitoring());

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests dominant visible content.
TEST_P(WatchTimeReporterTest, WatchTimeReporterDominantVisibleContent) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  // Will include only audio and only video testing when the related keys are
  // added.
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(true, kSizeJustRight);

    wtr_->OnDominantVisibleContentChanged(true);
    wtr_->OnPlaying();

    // Check the following keys are used.
    EXPECT_WATCH_TIME(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Src, kWatchTimeEarly);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_VIDEO(DominantVisibleContent, kWatchTimeEarly);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeEarly);

    EXPECT_TRUE(IsMonitoring());

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

// Tests auxiliary visible content.
TEST_P(WatchTimeReporterTest, WatchTimeReporterAuxiliaryVisibleContent) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(5);

  // Will include only audio and only video testing when the related keys are
  // added.
  if (has_audio_ && has_video_) {
    EXPECT_CALL(*this, GetCurrentMediaTime())
        .WillOnce(testing::Return(base::TimeDelta()))
        .WillRepeatedly(testing::Return(kWatchTimeEarly));
    Initialize(true, kSizeJustRight);

    // Set to dominant then instantly switch back.
    wtr_->OnDominantVisibleContentChanged(true);
    wtr_->OnDominantVisibleContentChanged(false);
    wtr_->OnPlaying();

    // Check the following keys are used.
    EXPECT_WATCH_TIME(All, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
    EXPECT_WATCH_TIME(Src, kWatchTimeEarly);
    EXPECT_WATCH_TIME(DisplayInline, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent, kWatchTimeEarly);
    EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kWatchTimeEarly);
    EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kWatchTimeEarly);

    EXPECT_TRUE(IsMonitoring());

    EXPECT_WATCH_TIME_FINALIZED();
  }
}

class MutedWatchTimeReporterTest : public WatchTimeReporterTest {};

TEST_P(MutedWatchTimeReporterTest, MutedHysteresis) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(8);
  constexpr base::TimeDelta kWatchTimeLate = base::Seconds(10);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))  // 2x for playing
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTimeEarly))  // 3x for unmute.
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillOnce(testing::Return(kWatchTimeEarly))  // 2x for mute
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillOnce(testing::Return(kWatchTimeEarly))  // 1x for timer cycle.
      .WillRepeatedly(testing::Return(kWatchTimeLate));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);

  wtr_->OnVolumeChange(0);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());

  wtr_->OnVolumeChange(1);
  wtr_->OnVolumeChange(0);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent,
                                         kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTimeEarly);

  EXPECT_TRUE(IsMutedMonitoring());
  EXPECT_TRUE(IsMonitoring());
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  EXPECT_TRUE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());

  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTimeLate);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTimeLate);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTimeLate);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTimeLate);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTimeLate);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent,
                                         kWatchTimeLate);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTimeLate);
  EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(MutedWatchTimeReporterTest, MuteUnmute) {
  constexpr base::TimeDelta kWatchTimeEarly = base::Seconds(8);
  constexpr base::TimeDelta kWatchTimeLate = base::Seconds(10);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillOnce(testing::Return(kWatchTimeEarly))
      .WillRepeatedly(testing::Return(kWatchTimeLate));

  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnVolumeChange(0);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());

  wtr_->OnVolumeChange(1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent,
                                         kWatchTimeEarly);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTimeEarly);
  EXPECT_WATCH_TIME_FINALIZED();

  const base::TimeDelta kExpectedUnmutedWatchTime =
      kWatchTimeLate - kWatchTimeEarly;
  EXPECT_WATCH_TIME(Ac, kExpectedUnmutedWatchTime);
  EXPECT_WATCH_TIME(All, kExpectedUnmutedWatchTime);
  EXPECT_WATCH_TIME(Eme, kExpectedUnmutedWatchTime);
  EXPECT_WATCH_TIME(Mse, kExpectedUnmutedWatchTime);
  EXPECT_WATCH_TIME(NativeControlsOff, kExpectedUnmutedWatchTime);
  EXPECT_WATCH_TIME(DisplayInline, kExpectedUnmutedWatchTime);
  EXPECT_WATCH_TIME_IF_VIDEO(AuxiliaryVisibleContent,
                             kExpectedUnmutedWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrAll, kExpectedUnmutedWatchTime);
  EXPECT_WATCH_TIME_IF_AUDIO_VIDEO_HDR(SdrEme, kExpectedUnmutedWatchTime);
  CycleReportingTimer();

  EXPECT_WATCH_TIME_FINALIZED();
}

TEST_P(MutedWatchTimeReporterTest, MutedPaused) {
  constexpr base::TimeDelta kWatchTime = base::Seconds(8);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillRepeatedly(testing::Return(kWatchTime));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnVolumeChange(0);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());

  wtr_->OnPaused();
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  EXPECT_FALSE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());
}

TEST_P(MutedWatchTimeReporterTest, MutedSeeked) {
  constexpr base::TimeDelta kWatchTime = base::Seconds(8);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillRepeatedly(testing::Return(kWatchTime));
  Initialize(true, kSizeJustRight);
  wtr_->OnVolumeChange(0);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());

  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Src, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTime);
  EXPECT_WATCH_TIME_FINALIZED();
  wtr_->OnSeeking();

  EXPECT_FALSE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());
}

TEST_P(MutedWatchTimeReporterTest, MutedPower) {
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(8);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(16);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime1))
      .WillOnce(testing::Return(kWatchTime1))
      .WillRepeatedly(testing::Return(kWatchTime2));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnVolumeChange(0);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());

  OnPowerStateChange(true);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTime1);
  EXPECT_POWER_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  wtr_->OnPaused();
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Battery, kWatchTime2 - kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTime2);
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  EXPECT_FALSE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());
}

TEST_P(MutedWatchTimeReporterTest, MutedControls) {
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(8);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(16);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime1))
      .WillOnce(testing::Return(kWatchTime1))
      .WillRepeatedly(testing::Return(kWatchTime2));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnVolumeChange(0);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());

  OnNativeControlsEnabled(true);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTime1);
  EXPECT_CONTROLS_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  wtr_->OnPaused();
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOn,
                                         kWatchTime2 - kWatchTime1);
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  EXPECT_FALSE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());
}

TEST_P(MutedWatchTimeReporterTest, MutedDisplayType) {
  constexpr base::TimeDelta kWatchTime1 = base::Seconds(8);
  constexpr base::TimeDelta kWatchTime2 = base::Seconds(16);
  EXPECT_CALL(*this, GetCurrentMediaTime())
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(base::TimeDelta()))
      .WillOnce(testing::Return(kWatchTime1))
      .WillOnce(testing::Return(kWatchTime1))
      .WillRepeatedly(testing::Return(kWatchTime2));
  Initialize(true, kSizeJustRight, media::DemuxerType::kChunkDemuxer);
  wtr_->OnVolumeChange(0);
  wtr_->OnPlaying();
  EXPECT_TRUE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());

  OnDisplayTypeChanged(WebMediaPlayer::DisplayType::kFullscreen);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayInline, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTime1);
  EXPECT_DISPLAY_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  wtr_->OnPaused();
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Ac, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(All, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Eme, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(Mse, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(DisplayFullscreen,
                                         kWatchTime2 - kWatchTime1);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(AuxiliaryVisibleContent, kWatchTime2);
  EXPECT_MUTED_WATCH_TIME_IF_AUDIO_VIDEO(NativeControlsOff, kWatchTime2);
  EXPECT_WATCH_TIME_FINALIZED();
  CycleReportingTimer();

  EXPECT_FALSE(IsMutedMonitoring());
  EXPECT_FALSE(IsMonitoring());
}

INSTANTIATE_TEST_SUITE_P(WatchTimeReporterTest,
                         WatchTimeReporterTest,
                         testing::ValuesIn({// has_video, has_audio
                                            std::make_tuple(true, true),
                                            // has_video
                                            std::make_tuple(true, false),
                                            // has_audio
                                            std::make_tuple(false, true)}));

// Separate test set since display tests only work with video.
INSTANTIATE_TEST_SUITE_P(DisplayTypeWatchTimeReporterTest,
                         DisplayTypeWatchTimeReporterTest,
                         testing::ValuesIn({// has_video, has_audio
                                            std::make_tuple(true, true),
                                            // has_video
                                            std::make_tuple(true, false)}));

// Separate test set since muted tests only work with audio+video.
INSTANTIATE_TEST_SUITE_P(MutedWatchTimeReporterTest,
                         MutedWatchTimeReporterTest,
                         testing::ValuesIn({
                             // has_video, has_audio
                             std::make_tuple(true, true),
                         }));

}  // namespace blink
