// Copyright 2016 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 "components/exo/notification_surface.h"

#include "components/exo/notification_surface_manager.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/surface.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/gfx/geometry/rect.h"

namespace exo {

NotificationSurface::NotificationSurface(NotificationSurfaceManager* manager,
                                         Surface* surface,
                                         const std::string& notification_key)
    : SurfaceTreeHost("ExoNotificationSurface"),
      manager_(manager),
      notification_key_(notification_key) {
  surface->AddSurfaceObserver(this);
  SetRootSurface(surface);
  SetArcAppType(host_window());
  host_window()->Show();
  host_window()->AddObserver(this);
}

NotificationSurface::~NotificationSurface() {
  if (host_window())
    host_window()->RemoveObserver(this);
  if (added_to_manager_)
    manager_->RemoveSurface(this);
  if (root_surface())
    root_surface()->RemoveSurfaceObserver(this);
}

const gfx::Size& NotificationSurface::GetContentSize() const {
  return root_surface()->content_size();
}

void NotificationSurface::SetApplicationId(const char* application_id) {
  SetShellApplicationId(host_window(), base::make_optional(application_id));
}

void NotificationSurface::OnSurfaceCommit() {
  SurfaceTreeHost::OnSurfaceCommit();

  // Defer AddSurface until there are contents to show.
  if (!added_to_manager_ && !host_window()->bounds().IsEmpty()) {
    added_to_manager_ = true;
    manager_->AddSurface(this);
  }

  // Only submit a compositor frame if the notification is being shown.
  // Submitting compositor frames while invisible causes Viz to hold on to
  // references to each notification buffer while it waits for an embedding (
  // or a timeout occurs). This can cause buffer starvation on the Android side,
  // leading to ANRs.
  if (is_embedded_)
    SubmitCompositorFrame();
}

void NotificationSurface::OnSurfaceDestroying(Surface* surface) {
  DCHECK_EQ(surface, root_surface());
  surface->RemoveSurfaceObserver(this);
  SetRootSurface(nullptr);
}

void NotificationSurface::OnWindowDestroying(aura::Window* window) {
  window->RemoveObserver(this);
}

void NotificationSurface::OnWindowAddedToRootWindow(aura::Window* window) {
  SubmitCompositorFrame();
  is_embedded_ = true;
}

void NotificationSurface::OnWindowRemovingFromRootWindow(
    aura::Window* window,
    aura::Window* new_root) {
  if (!new_root) {
    // Submit an empty compositor frame if the notification becomes invisible to
    // notify Viz that it can release its reference to the existing notification
    // buffer. We can't just evict the surface, because LayerTreeFrameSinkHolder
    // needs to hold on to resources that were used in the previous frame, if it
    // was associated with a different local surface id.
    SubmitEmptyCompositorFrame();

    // Evict the current surface. We can't only submit
    // an empty compositor frame, because then when re-opening the message
    // center, Viz may think it can use the empty compositor frame to display.
    // This will force viz to wait the given deadline for the next notification
    // compositor frame when showing the message center. This is to prevent
    // flashes when opening the message center.
    aura::Env::GetInstance()
        ->context_factory()
        ->GetHostFrameSinkManager()
        ->EvictSurfaces({host_window()->GetSurfaceId()});

    // Allocate a new local surface id for the next compositor frame.
    host_window()->AllocateLocalSurfaceId();

    is_embedded_ = false;

    // Set the deadline to default so Viz waits a while for the notification
    // compositor frame to arrive before showing the message center.
    host_window()->layer()->SetShowSurface(
        host_window()->GetSurfaceId(), host_window()->bounds().size(),
        SK_ColorWHITE, cc::DeadlinePolicy::UseDefaultDeadline(),
        /*stretch_content_to_fill_bounds=*/false);
  }
}

}  // namespace exo
