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

#include "base/android/application_status_listener.h"
#include "base/android/build_info.h"
#include "base/base_switches.h"
#include "base/test/scoped_feature_list.h"
#include "components/viz/common/features.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "content/browser/renderer_host/compositor_impl_android.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/gpu_stream_constants.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/gpu_browsertest_helpers.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "gpu/config/gpu_finch_features.h"
#include "gpu/ipc/client/gpu_channel_host.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "ui/android/window_android.h"
#include "url/gurl.h"

namespace content {

namespace {

enum class CompositorImplMode {
  kNormal,
  kViz,
  kVizSkDDL,
};

class CompositorImplBrowserTest
    : public testing::WithParamInterface<CompositorImplMode>,
      public ContentBrowserTest {
 public:
  CompositorImplBrowserTest() {}

  void SetUp() override {
    switch (GetParam()) {
      case CompositorImplMode::kNormal:
        break;
      case CompositorImplMode::kViz:
        scoped_feature_list_.InitAndEnableFeature(
            features::kVizDisplayCompositor);
        break;
      case CompositorImplMode::kVizSkDDL:
        scoped_feature_list_.InitWithFeatures(
            {features::kVizDisplayCompositor, features::kUseSkiaRenderer,
             features::kDefaultEnableOopRasterization},
            {});
        break;
    }

    ContentBrowserTest::SetUp();
  }

 protected:
  void SetUpOnMainThread() override {
    ASSERT_TRUE(embedded_test_server()->Start());
    net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
    https_server.ServeFilesFromSourceDirectory("content/test/data");
    ASSERT_TRUE(https_server.Start());
    GURL http_url(embedded_test_server()->GetURL("/title1.html"));
    ASSERT_TRUE(NavigateToURL(shell(), http_url));
  }

  ui::WindowAndroid* window() const {
    return web_contents()->GetTopLevelNativeWindow();
  }

  CompositorImpl* compositor_impl() const {
    return static_cast<CompositorImpl*>(window()->GetCompositor());
  }

  WebContentsImpl* web_contents() const {
    return static_cast<WebContentsImpl*>(shell()->web_contents());
  }

  RenderWidgetHostViewAndroid* render_widget_host_view_android() const {
    return static_cast<RenderWidgetHostViewAndroid*>(
        web_contents()->GetRenderWidgetHostView());
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;

  DISALLOW_COPY_AND_ASSIGN(CompositorImplBrowserTest);
};

INSTANTIATE_TEST_CASE_P(P,
                        CompositorImplBrowserTest,
                        ::testing::Values(CompositorImplMode::kNormal,
                                          CompositorImplMode::kViz,
                                          CompositorImplMode::kVizSkDDL));

class CompositorImplLowEndBrowserTest : public CompositorImplBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kEnableLowEndDeviceMode);
    command_line->AppendSwitch(switches::kInProcessGPU);
    content::ContentBrowserTest::SetUpCommandLine(command_line);
  }
};

// Viz on android is not yet compatible with in-process GPU. Only run in
// kNormal mode.
// TODO(ericrk): Make this work everywhere. https://crbug.com/851643
INSTANTIATE_TEST_CASE_P(P,
                        CompositorImplLowEndBrowserTest,
                        ::testing::Values(CompositorImplMode::kNormal));

// RunLoop implementation that calls glFlush() every second until it observes
// OnContextLost().
class ContextLostRunLoop : public viz::ContextLostObserver {
 public:
  ContextLostRunLoop(viz::ContextProvider* context_provider)
      : context_provider_(context_provider) {
    context_provider_->AddObserver(this);
  }
  ~ContextLostRunLoop() override { context_provider_->RemoveObserver(this); }

  void RunUntilContextLost() {
    CheckForContextLoss();
    run_loop_.Run();
  }

  void CheckForContextLoss() {
    if (did_lose_context_) {
      run_loop_.Quit();
      return;
    }
    context_provider_->ContextGL()->Flush();
    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&ContextLostRunLoop::CheckForContextLoss,
                       base::Unretained(this)),
        base::TimeDelta::FromSeconds(1));
  }

 private:
  // viz::LostContextProvider:
  void OnContextLost() override { did_lose_context_ = true; }

  viz::ContextProvider* const context_provider_;
  bool did_lose_context_ = false;
  base::RunLoop run_loop_;

  DISALLOW_COPY_AND_ASSIGN(ContextLostRunLoop);
};

// RunLoop implementation that runs until it observes a compositor frame.
class CompositorFrameRunLoop : public ui::WindowAndroidObserver {
 public:
  CompositorFrameRunLoop(ui::WindowAndroid* window) : window_(window) {
    window_->AddObserver(this);
  }
  ~CompositorFrameRunLoop() override { window_->RemoveObserver(this); }

  void RunUntilFrame() { run_loop_.Run(); }

 private:
  // ui::WindowAndroidObserver:
  void OnCompositingDidCommit() override { run_loop_.Quit(); }
  void OnRootWindowVisibilityChanged(bool visible) override {}
  void OnAttachCompositor() override {}
  void OnDetachCompositor() override {}
  void OnActivityStopped() override {}
  void OnActivityStarted() override {}

  ui::WindowAndroid* const window_;
  base::RunLoop run_loop_;

  DISALLOW_COPY_AND_ASSIGN(CompositorFrameRunLoop);
};

IN_PROC_BROWSER_TEST_P(CompositorImplLowEndBrowserTest,
                       CompositorImplDropsResourcesOnBackground) {
  // This test makes invalid assumptions when surface synchronization is
  // enabled. The compositor lock is obsolete, and inspecting frames
  // from the CompositorImpl does not guarantee renderer CompositorFrames
  // are ready.
  if (features::IsSurfaceSynchronizationEnabled())
    return;

  auto* rwhva = render_widget_host_view_android();
  auto* compositor = compositor_impl();
  auto context = GpuBrowsertestCreateContext(
      GpuBrowsertestEstablishGpuChannelSyncRunLoop());
  context->BindToCurrentThread();

  CompositorFrameRunLoop(window()).RunUntilFrame();
  EXPECT_TRUE(rwhva->HasValidFrame());

  ContextLostRunLoop run_loop(context.get());
  compositor->SetVisibleForTesting(false);
  base::android::ApplicationStatusListener::NotifyApplicationStateChange(
      base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES);
  rwhva->OnRootWindowVisibilityChanged(false);
  rwhva->Hide();

  // Ensure that context is eventually dropped and at that point we do not have
  // a valid frame.
  run_loop.RunUntilContextLost();
  EXPECT_FALSE(rwhva->HasValidFrame());

  // Become visible again:
  compositor->SetVisibleForTesting(true);
  base::android::ApplicationStatusListener::NotifyApplicationStateChange(
      base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
  rwhva->Show();
  rwhva->OnRootWindowVisibilityChanged(true);

  // We should have taken the compositor lock on resume.
  EXPECT_TRUE(compositor->IsLockedForTesting());
  EXPECT_FALSE(rwhva->HasValidFrame());

  // The compositor should eventually be unlocked and produce a frame.
  CompositorFrameRunLoop(window()).RunUntilFrame();
  EXPECT_FALSE(compositor->IsLockedForTesting());
  EXPECT_TRUE(rwhva->HasValidFrame());
}

// RunLoop implementation that runs until it observes a swap with size.
class CompositorSwapRunLoop {
 public:
  CompositorSwapRunLoop(CompositorImpl* compositor) : compositor_(compositor) {
    compositor_->SetSwapCompletedWithSizeCallbackForTesting(base::BindRepeating(
        &CompositorSwapRunLoop::DidSwap, base::Unretained(this)));
  }
  ~CompositorSwapRunLoop() {
    compositor_->SetSwapCompletedWithSizeCallbackForTesting(base::DoNothing());
  }

  void RunUntilSwap() { run_loop_.Run(); }

 private:
  void DidSwap(const gfx::Size& pixel_size) {
    EXPECT_FALSE(pixel_size.IsEmpty());
    run_loop_.Quit();
  }

  CompositorImpl* compositor_;
  base::RunLoop run_loop_;

  DISALLOW_COPY_AND_ASSIGN(CompositorSwapRunLoop);
};

IN_PROC_BROWSER_TEST_P(CompositorImplBrowserTest,
                       CompositorImplReceivesSwapCallbacks) {
  // OOP-R is required for this test to succeed with SkDDL, but is disabled on
  // Android L and lower.
  if (GetParam() == CompositorImplMode::kVizSkDDL &&
      base::android::BuildInfo::GetInstance()->sdk_int() <
          base::android::SDK_VERSION_MARSHMALLOW) {
    return;
  }
  CompositorSwapRunLoop(compositor_impl()).RunUntilSwap();
}

}  // namespace
}  // namespace content
