// Copyright 2013 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 "content/browser/media/capture/aura_window_capture_machine.h"

#include <algorithm>
#include <utility>

#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "cc/output/copy_output_request.h"
#include "cc/output/copy_output_result.h"
#include "components/viz/common/gl_helper.h"
#include "content/browser/compositor/image_transport_factory.h"
#include "content/browser/media/capture/desktop_capture_device_uma_types.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/service_manager_connection.h"
#include "media/base/video_util.h"
#include "media/capture/content/thread_safe_capture_oracle.h"
#include "media/capture/content/video_capture_oracle.h"
#include "media/capture/video_capture_types.h"
#include "services/device/public/interfaces/constants.mojom.h"
#include "services/device/public/interfaces/wake_lock_provider.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/cursor/cursors_aura.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/dip_util.h"
#include "ui/compositor/layer.h"

namespace content {

AuraWindowCaptureMachine::AuraWindowCaptureMachine()
    : desktop_window_(NULL),
      screen_capture_(false),
      frame_capture_active_(true),
      weak_factory_(this) {}

AuraWindowCaptureMachine::~AuraWindowCaptureMachine() {}

void AuraWindowCaptureMachine::Start(
  const scoped_refptr<media::ThreadSafeCaptureOracle>& oracle_proxy,
  const media::VideoCaptureParams& params,
  const base::Callback<void(bool)> callback) {
  // Starts the capture machine asynchronously.
  BrowserThread::PostTaskAndReplyWithResult(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(&AuraWindowCaptureMachine::InternalStart,
                 base::Unretained(this),
                 oracle_proxy,
                 params),
      callback);
}

bool AuraWindowCaptureMachine::InternalStart(
    const scoped_refptr<media::ThreadSafeCaptureOracle>& oracle_proxy,
    const media::VideoCaptureParams& params) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // The window might be destroyed between SetWindow() and Start().
  if (!desktop_window_)
    return false;

  // If the associated layer is already destroyed then return failure.
  ui::Layer* layer = desktop_window_->layer();
  if (!layer)
    return false;

  DCHECK(oracle_proxy);
  oracle_proxy_ = oracle_proxy;
  capture_params_ = params;

  // Update capture size.
  UpdateCaptureSize();

  // Start observing for GL context losses.
  ImageTransportFactory::GetInstance()->GetContextFactory()->AddObserver(this);

  // Start observing compositor updates.
  aura::WindowTreeHost* const host = desktop_window_->GetHost();
  ui::Compositor* const compositor = host ? host->compositor() : nullptr;
  if (!compositor)
    return false;
  compositor->AddAnimationObserver(this);

  DCHECK(!wake_lock_);
  // Request Wake Lock. In some testing contexts, the service manager
  // connection isn't initialized.
  if (ServiceManagerConnection::GetForProcess()) {
    service_manager::Connector* connector =
        ServiceManagerConnection::GetForProcess()->GetConnector();
    DCHECK(connector);
    device::mojom::WakeLockProviderPtr wake_lock_provider;
    connector->BindInterface(device::mojom::kServiceName,
                             mojo::MakeRequest(&wake_lock_provider));
    wake_lock_provider->GetWakeLockWithoutContext(
        device::mojom::WakeLockType::PreventDisplaySleep,
        device::mojom::WakeLockReason::ReasonOther,
        "Desktop capturer is running", mojo::MakeRequest(&wake_lock_));

    wake_lock_->RequestWakeLock();
  }

  return true;
}

void AuraWindowCaptureMachine::Suspend() {
  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                          base::Bind(&AuraWindowCaptureMachine::InternalSuspend,
                                     base::Unretained(this)));
}

void AuraWindowCaptureMachine::InternalSuspend() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DVLOG(1) << "Suspending frame capture and delivery.";
  frame_capture_active_ = false;
}

void AuraWindowCaptureMachine::Resume() {
  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                          base::Bind(&AuraWindowCaptureMachine::InternalResume,
                                     base::Unretained(this)));
}

void AuraWindowCaptureMachine::InternalResume() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DVLOG(1) << "Resuming frame capture and delivery.";
  frame_capture_active_ = true;
  // Whenever capture resumes, capture a refresh frame immediately to make sure
  // no content updates are missing from the video stream.
  MaybeCaptureForRefresh();
}

void AuraWindowCaptureMachine::Stop(const base::Closure& callback) {
  // Stops the capture machine asynchronously.
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE, base::Bind(
          &AuraWindowCaptureMachine::InternalStop,
          base::Unretained(this),
          callback));
}

void AuraWindowCaptureMachine::InternalStop(const base::Closure& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Cancel any and all outstanding callbacks owned by external modules.
  weak_factory_.InvalidateWeakPtrs();

  if (wake_lock_)
    wake_lock_->CancelWakeLock();
  // Stop observing compositor and window events.
  if (desktop_window_) {
    if (aura::WindowTreeHost* host = desktop_window_->GetHost()) {
      if (ui::Compositor* compositor = host->compositor())
        compositor->RemoveAnimationObserver(this);
    }
    desktop_window_->RemoveObserver(this);
    desktop_window_ = NULL;
    cursor_renderer_.reset();
  }

  // Stop observing for GL context losses.
  ImageTransportFactory::GetInstance()->GetContextFactory()->RemoveObserver(
      this);
  OnLostResources();

  callback.Run();
}

void AuraWindowCaptureMachine::MaybeCaptureForRefresh() {
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&AuraWindowCaptureMachine::Capture,
                 // Use of Unretained() is safe here since this task must run
                 // before InternalStop().
                 base::Unretained(this),
                 base::TimeTicks()));
}

void AuraWindowCaptureMachine::SetWindow(aura::Window* window) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  DCHECK(!desktop_window_);
  desktop_window_ = window;
  cursor_renderer_.reset(new CursorRendererAura(window, kCursorAlwaysEnabled));

  // Start observing window events.
  desktop_window_->AddObserver(this);

  // We must store this for the UMA reporting in DidCopyOutput() as
  // desktop_window_ might be destroyed at that point.
  screen_capture_ = window->IsRootWindow();
  IncrementDesktopCaptureCounter(screen_capture_ ? SCREEN_CAPTURER_CREATED
                                                 : WINDOW_CAPTURER_CREATED);
}

void AuraWindowCaptureMachine::UpdateCaptureSize() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (oracle_proxy_ && desktop_window_) {
     ui::Layer* layer = desktop_window_->layer();
     oracle_proxy_->UpdateCaptureSize(ui::ConvertSizeToPixel(
         layer, layer->bounds().size()));
  }
  cursor_renderer_->Clear();
}

void AuraWindowCaptureMachine::Capture(base::TimeTicks event_time) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Do not capture if the desktop window is already destroyed.
  if (!desktop_window_)
    return;

  scoped_refptr<media::VideoFrame> frame;
  media::ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb;

  // TODO(miu): Need to fix this so the compositor is providing the presentation
  // timestamps and damage regions, to leverage the frame timestamp rewriting
  // logic.  http://crbug.com/492839
  const base::TimeTicks start_time = base::TimeTicks::Now();
  media::VideoCaptureOracle::Event event;
  if (event_time.is_null()) {
    event = media::VideoCaptureOracle::kActiveRefreshRequest;
    event_time = start_time;
  } else {
    event = media::VideoCaptureOracle::kCompositorUpdate;
  }
  if (oracle_proxy_->ObserveEventAndDecideCapture(
          event, gfx::Rect(), event_time, &frame, &capture_frame_cb)) {
    std::unique_ptr<cc::CopyOutputRequest> request =
        cc::CopyOutputRequest::CreateRequest(
            base::BindOnce(&AuraWindowCaptureMachine::DidCopyOutput,
                           weak_factory_.GetWeakPtr(), std::move(frame),
                           event_time, start_time, capture_frame_cb));
    gfx::Rect window_rect = gfx::Rect(desktop_window_->bounds().width(),
                                      desktop_window_->bounds().height());
    request->set_area(window_rect);
    desktop_window_->layer()->RequestCopyOfOutput(std::move(request));
  }
}

void AuraWindowCaptureMachine::DidCopyOutput(
    scoped_refptr<media::VideoFrame> video_frame,
    base::TimeTicks event_time,
    base::TimeTicks start_time,
    const CaptureFrameCallback& capture_frame_cb,
    std::unique_ptr<cc::CopyOutputResult> result) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  static bool first_call = true;

  const bool succeeded = ProcessCopyOutputResponse(
      video_frame, event_time, capture_frame_cb, std::move(result));

  const base::TimeDelta capture_time = base::TimeTicks::Now() - start_time;

  // The two UMA_ blocks must be put in its own scope since it creates a static
  // variable which expected constant histogram name.
  if (screen_capture_) {
    UMA_HISTOGRAM_TIMES(kUmaScreenCaptureTime, capture_time);
  } else {
    UMA_HISTOGRAM_TIMES(kUmaWindowCaptureTime, capture_time);
  }

  if (first_call) {
    first_call = false;
    if (screen_capture_) {
      IncrementDesktopCaptureCounter(succeeded ? FIRST_SCREEN_CAPTURE_SUCCEEDED
                                               : FIRST_SCREEN_CAPTURE_FAILED);
    } else {
      IncrementDesktopCaptureCounter(succeeded
                                         ? FIRST_WINDOW_CAPTURE_SUCCEEDED
                                         : FIRST_WINDOW_CAPTURE_FAILED);
    }
  }

  // If ProcessCopyOutputResponse() failed, it will not run |capture_frame_cb|,
  // so do that now.
  if (!succeeded)
    capture_frame_cb.Run(std::move(video_frame), event_time, false);
}

bool AuraWindowCaptureMachine::ProcessCopyOutputResponse(
    scoped_refptr<media::VideoFrame> video_frame,
    base::TimeTicks event_time,
    const CaptureFrameCallback& capture_frame_cb,
    std::unique_ptr<cc::CopyOutputResult> result) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!desktop_window_) {
    VLOG(1) << "Ignoring CopyOutputResult: Capture target has gone away.";
    return false;
  }
  if (result->IsEmpty()) {
    VLOG(1) << "CopyOutputRequest failed: No texture or bitmap in result.";
    return false;
  }
  if (result->size().IsEmpty()) {
    VLOG(1) << "CopyOutputRequest failed: Zero-area texture/bitmap result.";
    return false;
  }
  DCHECK(video_frame);

  // Compute the dest size we want after the letterboxing resize. Make the
  // coordinates and sizes even because we letterbox in YUV space
  // (see CopyRGBToVideoFrame). They need to be even for the UV samples to
  // line up correctly.
  // The video frame's visible_rect() and the result's size() are both physical
  // pixels.
  gfx::Rect region_in_frame = media::ComputeLetterboxRegion(
      video_frame->visible_rect(), result->size());
  region_in_frame = gfx::Rect(region_in_frame.x() & ~1,
                              region_in_frame.y() & ~1,
                              region_in_frame.width() & ~1,
                              region_in_frame.height() & ~1);
  if (region_in_frame.IsEmpty()) {
    VLOG(1) << "Aborting capture: Computed empty letterboxed content region.";
    return false;
  }

  ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
  viz::GLHelper* gl_helper = factory->GetGLHelper();
  if (!gl_helper) {
    VLOG(1) << "Aborting capture: No GLHelper available for YUV readback.";
    return false;
  }

  viz::TextureMailbox texture_mailbox;
  std::unique_ptr<cc::SingleReleaseCallback> release_callback;
  result->TakeTexture(&texture_mailbox, &release_callback);
  DCHECK(texture_mailbox.IsTexture());
  if (!texture_mailbox.IsTexture()) {
    VLOG(1) << "Aborting capture: Failed to take texture from mailbox.";
    return false;
  }

  gfx::Rect result_rect(result->size());
  if (!yuv_readback_pipeline_ ||
      yuv_readback_pipeline_->scaler()->SrcSize() != result_rect.size() ||
      yuv_readback_pipeline_->scaler()->SrcSubrect() != result_rect ||
      yuv_readback_pipeline_->scaler()->DstSize() != region_in_frame.size()) {
    yuv_readback_pipeline_.reset(gl_helper->CreateReadbackPipelineYUV(
        viz::GLHelper::SCALER_QUALITY_FAST, result_rect.size(), result_rect,
        region_in_frame.size(), true, true));
  }

  cursor_renderer_->SnapshotCursorState(region_in_frame);
  yuv_readback_pipeline_->ReadbackYUV(
      texture_mailbox.mailbox(), texture_mailbox.sync_token(),
      video_frame->visible_rect(),
      video_frame->stride(media::VideoFrame::kYPlane),
      video_frame->data(media::VideoFrame::kYPlane),
      video_frame->stride(media::VideoFrame::kUPlane),
      video_frame->data(media::VideoFrame::kUPlane),
      video_frame->stride(media::VideoFrame::kVPlane),
      video_frame->data(media::VideoFrame::kVPlane), region_in_frame.origin(),
      base::Bind(&CopyOutputFinishedForVideo, weak_factory_.GetWeakPtr(),
                 event_time, capture_frame_cb, video_frame,
                 base::Passed(&release_callback)));
  media::LetterboxYUV(video_frame.get(), region_in_frame);
  return true;
}

using CaptureFrameCallback =
    media::ThreadSafeCaptureOracle::CaptureFrameCallback;

void AuraWindowCaptureMachine::CopyOutputFinishedForVideo(
    base::WeakPtr<AuraWindowCaptureMachine> machine,
    base::TimeTicks event_time,
    const CaptureFrameCallback& capture_frame_cb,
    scoped_refptr<media::VideoFrame> target,
    std::unique_ptr<cc::SingleReleaseCallback> release_callback,
    bool result) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  release_callback->Run(gpu::SyncToken(), false);

  // Render the cursor and deliver the captured frame if the
  // AuraWindowCaptureMachine has not been stopped (i.e., the WeakPtr is
  // still valid).
  if (machine) {
    if (machine->cursor_renderer_ && result)
      machine->cursor_renderer_->RenderOnVideoFrame(target.get());
  } else {
    VLOG(1) << "Aborting capture: AuraWindowCaptureMachine has gone away.";
    result = false;
  }

  capture_frame_cb.Run(std::move(target), event_time, result);
}

void AuraWindowCaptureMachine::OnWindowBoundsChanged(
    aura::Window* window,
    const gfx::Rect& old_bounds,
    const gfx::Rect& new_bounds) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(desktop_window_ && window == desktop_window_);

  // Post a task to update capture size after first returning to the event loop.
  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
      &AuraWindowCaptureMachine::UpdateCaptureSize,
      weak_factory_.GetWeakPtr()));
}

void AuraWindowCaptureMachine::OnWindowDestroying(aura::Window* window) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  InternalStop(base::Bind(&base::DoNothing));

  oracle_proxy_->ReportError(FROM_HERE, "OnWindowDestroying()");
}

void AuraWindowCaptureMachine::OnWindowAddedToRootWindow(
    aura::Window* window) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(window == desktop_window_);

  if (aura::WindowTreeHost* host = window->GetHost()) {
    if (ui::Compositor* compositor = host->compositor())
      compositor->AddAnimationObserver(this);
  }
}

void AuraWindowCaptureMachine::OnWindowRemovingFromRootWindow(
    aura::Window* window,
    aura::Window* new_root) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(window == desktop_window_);

  if (aura::WindowTreeHost* host = window->GetHost()) {
    if (ui::Compositor* compositor = host->compositor())
      compositor->RemoveAnimationObserver(this);
  }
}

void AuraWindowCaptureMachine::OnAnimationStep(base::TimeTicks timestamp) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!timestamp.is_null());

  // HACK: The compositor invokes this observer method to step layer animation
  // forward. Scheduling frame capture was not the intention, and so invoking
  // this method does not actually indicate the content has changed. However,
  // this is the only reliable way to ensure all screen changes are captured, as
  // of this writing.
  // http://crbug.com/600031
  //
  // TODO(miu): Need a better observer callback interface from the compositor
  // for this use case. The solution here will always capture frames at the
  // maximum framerate, which means CPU/GPU is being wasted on redundant
  // captures and quality/smoothness of animating content will suffer
  // significantly.
  // http://crbug.com/492839
  if (frame_capture_active_)
    Capture(timestamp);
}

void AuraWindowCaptureMachine::OnCompositingShuttingDown(
    ui::Compositor* compositor) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  compositor->RemoveAnimationObserver(this);
}

void AuraWindowCaptureMachine::OnLostResources() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  yuv_readback_pipeline_.reset();
}

}  // namespace content
