blob: f942b390e10f4465219429df4e1a80c47e526179 [file] [log] [blame]
// Copyright 2018 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 "ash/assistant/assistant_controller.h"
#include "ash/assistant/assistant_controller_observer.h"
#include "ash/assistant/assistant_interaction_controller.h"
#include "ash/assistant/assistant_ui_controller.h"
#include "ash/assistant/util/deep_link_util.h"
#include "ash/new_window_controller.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/session/session_controller.h"
#include "ash/shell.h"
#include "ash/wm/mru_window_tracker.h"
#include "base/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/stl_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/unguessable_token.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/skbitmap_operations.h"
#include "ui/snapshot/snapshot.h"
#include "ui/snapshot/snapshot_aura.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
// When screenshot's width or height is smaller than this size, we will stop
// downsampling.
constexpr int kScreenshotMaxWidth = 1366;
constexpr int kScreenshotMaxHeight = 768;
std::vector<uint8_t> DownsampleAndEncodeImage(gfx::Image image) {
std::vector<uint8_t> res;
// We'll downsample the screenshot to avoid exceeding max allowed size on
// assistant server side if we are taking screenshot from high-res screen.
gfx::JPEGCodec::Encode(
SkBitmapOperations::DownsampleByTwoUntilSize(
image.AsBitmap(), kScreenshotMaxWidth, kScreenshotMaxHeight),
100 /* quality */, &res);
return res;
}
void EncodeScreenshotAndRunCallback(
AssistantController::RequestScreenshotCallback callback,
std::unique_ptr<ui::LayerTreeOwner> layer_owner,
gfx::Image image) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
base::TaskTraits{base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::BindOnce(&DownsampleAndEncodeImage, std::move(image)),
std::move(callback));
}
std::unique_ptr<ui::LayerTreeOwner> CreateLayerForAssistantSnapshot(
aura::Window* root_window) {
using LayerSet = base::flat_set<const ui::Layer*>;
LayerSet excluded_layers;
LayerSet blocked_layers;
aura::Window* overlay_container =
ash::Shell::GetContainer(root_window, kShellWindowId_OverlayContainer);
if (overlay_container)
excluded_layers.insert(overlay_container->layer());
MruWindowTracker::WindowList windows =
Shell::Get()->mru_window_tracker()->BuildMruWindowList();
for (aura::Window* window : windows) {
if (window->GetProperty(kBlockedForAssistantSnapshotKey))
blocked_layers.insert(window->layer());
}
return ::wm::RecreateLayersWithClosure(
root_window,
base::BindRepeating(
[](LayerSet blocked_layers, LayerSet excluded_layers,
ui::LayerOwner* owner) -> std::unique_ptr<ui::Layer> {
// Parent layer is excluded meaning that it's pointless to clone
// current child and all its descendants. This reduces the number
// of layers to create.
if (base::ContainsKey(blocked_layers, owner->layer()->parent()))
return nullptr;
if (base::ContainsKey(blocked_layers, owner->layer())) {
// Blocked layers are replaced with solid black layers so that
// they won't be included in the screenshot but still preserve
// the window stacking.
auto layer =
std::make_unique<ui::Layer>(ui::LayerType::LAYER_SOLID_COLOR);
layer->SetBounds(owner->layer()->bounds());
layer->SetColor(SK_ColorBLACK);
return layer;
}
if (excluded_layers.count(owner->layer()))
return nullptr;
return owner->RecreateLayer();
},
std::move(blocked_layers), std::move(excluded_layers)));
}
} // namespace
AssistantController::AssistantController()
: assistant_interaction_controller_(
std::make_unique<AssistantInteractionController>(this)),
assistant_ui_controller_(std::make_unique<AssistantUiController>(this)) {
// Note that the sub-controllers have a circular dependency.
// TODO(dmblack): Remove this circular dependency.
assistant_interaction_controller_->SetAssistantUiController(
assistant_ui_controller_.get());
assistant_ui_controller_->SetAssistantInteractionController(
assistant_interaction_controller_.get());
// Observe HighlighterController for region selections.
Shell::Get()->highlighter_controller()->AddObserver(this);
}
AssistantController::~AssistantController() {
Shell::Get()->highlighter_controller()->RemoveObserver(this);
// Explicitly clean up the circular dependency in the sub-controllers.
assistant_interaction_controller_->SetAssistantUiController(nullptr);
assistant_ui_controller_->SetAssistantInteractionController(nullptr);
}
void AssistantController::BindRequest(
mojom::AssistantControllerRequest request) {
assistant_controller_bindings_.AddBinding(this, std::move(request));
}
void AssistantController::AddObserver(AssistantControllerObserver* observer) {
observers_.AddObserver(observer);
}
void AssistantController::RemoveObserver(
AssistantControllerObserver* observer) {
observers_.RemoveObserver(observer);
}
void AssistantController::SetAssistant(
chromeos::assistant::mojom::AssistantPtr assistant) {
assistant_ = std::move(assistant);
// Provide reference to sub-controllers.
assistant_interaction_controller_->SetAssistant(assistant_.get());
assistant_ui_controller_->SetAssistant(assistant_.get());
}
void AssistantController::SetAssistantImageDownloader(
mojom::AssistantImageDownloaderPtr assistant_image_downloader) {
assistant_image_downloader_ = std::move(assistant_image_downloader);
}
void AssistantController::SetAssistantSetup(
mojom::AssistantSetupPtr assistant_setup) {
assistant_setup_ = std::move(assistant_setup);
// Provide reference to UI controller.
assistant_ui_controller_->SetAssistantSetup(assistant_setup_.get());
}
void AssistantController::SetWebContentsManager(
mojom::WebContentsManagerPtr web_contents_manager) {
web_contents_manager_ = std::move(web_contents_manager);
}
void AssistantController::RequestScreenshot(
const gfx::Rect& rect,
RequestScreenshotCallback callback) {
// TODO(muyuanli): handle multi-display when assistant's behavior is defined.
aura::Window* root_window = Shell::GetPrimaryRootWindow();
std::unique_ptr<ui::LayerTreeOwner> layer_owner =
CreateLayerForAssistantSnapshot(root_window);
ui::Layer* root_layer = layer_owner->root();
gfx::Rect source_rect =
rect.IsEmpty() ? gfx::Rect(root_window->bounds().size()) : rect;
// The root layer might have a scaling transform applied (if the user has
// changed the UI scale via Ctrl-Shift-Plus/Minus).
// Clear the transform so that the snapshot is taken at 1:1 scale relative
// to screen pixels.
root_layer->SetTransform(gfx::Transform());
root_window->layer()->Add(root_layer);
root_window->layer()->StackAtBottom(root_layer);
ui::GrabLayerSnapshotAsync(
root_layer, source_rect,
base::BindRepeating(&EncodeScreenshotAndRunCallback,
base::Passed(std::move(callback)),
base::Passed(std::move(layer_owner))));
}
void AssistantController::ManageWebContents(
const base::UnguessableToken& id_token,
mojom::ManagedWebContentsParamsPtr params,
mojom::WebContentsManager::ManageWebContentsCallback callback) {
DCHECK(web_contents_manager_);
const mojom::UserSession* user_session =
Shell::Get()->session_controller()->GetUserSession(0);
if (!user_session) {
LOG(WARNING) << "Unable to retrieve active user session.";
std::move(callback).Run(base::nullopt);
return;
}
// Supply account ID.
params->account_id = user_session->user_info->account_id;
// Specify that we will handle top level browser requests.
ash::mojom::ManagedWebContentsOpenUrlDelegatePtr ptr;
web_contents_open_url_delegate_bindings_.AddBinding(this,
mojo::MakeRequest(&ptr));
params->open_url_delegate_ptr_info = ptr.PassInterface();
web_contents_manager_->ManageWebContents(id_token, std::move(params),
std::move(callback));
}
void AssistantController::ReleaseWebContents(
const base::UnguessableToken& id_token) {
web_contents_manager_->ReleaseWebContents(id_token);
}
void AssistantController::ReleaseWebContents(
const std::vector<base::UnguessableToken>& id_tokens) {
web_contents_manager_->ReleaseAllWebContents(id_tokens);
}
void AssistantController::DownloadImage(
const GURL& url,
mojom::AssistantImageDownloader::DownloadCallback callback) {
DCHECK(assistant_image_downloader_);
const mojom::UserSession* user_session =
Shell::Get()->session_controller()->GetUserSession(0);
if (!user_session) {
LOG(WARNING) << "Unable to retrieve active user session.";
std::move(callback).Run(gfx::ImageSkia());
return;
}
AccountId account_id = user_session->user_info->account_id;
assistant_image_downloader_->Download(account_id, url, std::move(callback));
}
void AssistantController::OnHighlighterSelectionRecognized(
const gfx::Rect& rect) {
// TODO(muyuanli): Request screen context for |rect|.
NOTIMPLEMENTED();
}
void AssistantController::OnOpenUrlFromTab(const GURL& url) {
OpenUrl(url);
}
void AssistantController::OpenUrl(const GURL& url) {
if (assistant::util::IsDeepLinkUrl(url)) {
for (AssistantControllerObserver& observer : observers_)
observer.OnDeepLinkReceived(url);
return;
}
Shell::Get()->new_window_controller()->NewTabWithUrl(url);
for (AssistantControllerObserver& observer : observers_)
observer.OnUrlOpened(url);
}
std::unique_ptr<ui::LayerTreeOwner>
AssistantController::CreateLayerForAssistantSnapshotForTest() {
aura::Window* root_window = Shell::GetPrimaryRootWindow();
return CreateLayerForAssistantSnapshot(root_window);
}
} // namespace ash