blob: fe65f6fd71e12b4cf89b691d00a61146646b8174 [file] [log] [blame]
// 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 "chrome/browser/chromeos/arc/voice_interaction/arc_voice_interaction_framework_service.h"
#include <memory>
#include <string>
#include <utility>
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/bind.h"
#include "base/files/scoped_temp_dir.h"
#include "chrome/browser/chromeos/arc/arc_session_manager.h"
#include "chrome/browser/chromeos/arc/voice_interaction/fake_voice_interaction_controller.h"
#include "chrome/browser/chromeos/arc/voice_interaction/highlighter_controller_client.h"
#include "chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/test_browser_window.h"
#include "chrome/test/base/test_browser_window_aura.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_cras_audio_client.h"
#include "components/arc/arc_bridge_service.h"
#include "components/arc/arc_util.h"
#include "components/arc/test/connection_holder_util.h"
#include "components/arc/test/fake_arc_session.h"
#include "components/arc/test/fake_voice_interaction_framework_instance.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/core/session_manager.h"
#include "services/service_manager/public/cpp/service.h"
#include "services/service_manager/public/cpp/test/test_connector_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_tree_owner.h"
namespace arc {
namespace {
class TestHighlighterController : public ash::mojom::HighlighterController,
public service_manager::Service {
public:
TestHighlighterController() : binding_(this) {}
~TestHighlighterController() override = default;
void CallHandleSelection(const gfx::Rect& rect) {
client_->HandleSelection(rect);
}
void CallHandleEnabledStateChange(bool enabled) {
is_enabled_ = enabled;
client_->HandleEnabledStateChange(enabled);
}
bool client_attached() const { return static_cast<bool>(client_); }
// ash::mojom::HighlighterController:
void SetClient(ash::mojom::HighlighterControllerClientPtr client) override {
DCHECK(!client_);
client_ = std::move(client);
// Okay to use base::Unretained(this), as |client_| will be destroyed before
// |this|.
client_.set_connection_error_handler(
base::BindOnce(&TestHighlighterController::OnClientConnectionLost,
base::Unretained(this)));
}
void ExitHighlighterMode() override {
// simulate exiting current session.
CallHandleEnabledStateChange(false);
}
void FlushMojo() { client_.FlushForTesting(); }
bool is_enabled() { return is_enabled_; }
// service_manager::Service:
void OnBindInterface(const service_manager::BindSourceInfo& source_info,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) override {
DCHECK(interface_name == ash::mojom::HighlighterController::Name_);
binding_.Bind(
ash::mojom::HighlighterControllerRequest(std::move(interface_pipe)));
}
private:
void OnClientConnectionLost() {
client_.reset();
binding_.Close();
}
mojo::Binding<ash::mojom::HighlighterController> binding_;
ash::mojom::HighlighterControllerClientPtr client_;
bool is_enabled_ = false;
DISALLOW_COPY_AND_ASSIGN(TestHighlighterController);
};
std::unique_ptr<TestBrowserWindow> CreateTestBrowserWindow(
aura::Window* parent) {
auto window =
std::make_unique<aura::Window>(nullptr, aura::client::WINDOW_TYPE_NORMAL);
window->Init(ui::LAYER_TEXTURED);
window->SetBounds(gfx::Rect(0, 0, 200, 200));
parent->AddChild(window.get());
return std::make_unique<TestBrowserWindowAura>(std::move(window));
}
ui::Layer* FindLayer(ui::Layer* root, ui::Layer* target) {
if (root == target)
return target;
for (auto* child : root->children()) {
auto* result = FindLayer(child, target);
if (result)
return result;
}
return nullptr;
}
} // namespace
class ArcVoiceInteractionFrameworkServiceTest : public ash::AshTestBase {
public:
ArcVoiceInteractionFrameworkServiceTest() = default;
void SetUp() override {
AshTestBase::SetUp();
// Setup test profile.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
TestingProfile::Builder profile_builder;
profile_builder.SetProfileName("user@gmail.com");
profile_builder.SetPath(temp_dir_.GetPath().AppendASCII("TestArcProfile"));
profile_ = profile_builder.Build();
// Setup dependencies for voice interaction framework service.
session_manager_ = std::make_unique<session_manager::SessionManager>();
arc_session_manager_ = std::make_unique<ArcSessionManager>(
std::make_unique<ArcSessionRunner>(base::Bind(FakeArcSession::Create)));
arc_bridge_service_ = std::make_unique<ArcBridgeService>();
auto highlighter_controller_ptr =
std::make_unique<TestHighlighterController>();
highlighter_controller_ = highlighter_controller_ptr.get();
voice_interaction_controller_ =
std::make_unique<FakeVoiceInteractionController>();
voice_interaction_controller_client_ =
std::make_unique<VoiceInteractionControllerClient>();
connector_factory_ =
service_manager::TestConnectorFactory::CreateForUniqueService(
std::move(highlighter_controller_ptr));
connector_ = connector_factory_->CreateConnector();
framework_service_ = std::make_unique<ArcVoiceInteractionFrameworkService>(
profile_.get(), arc_bridge_service_.get());
framework_service_->GetHighlighterClientForTesting()
->SetConnectorForTesting(connector_.get());
voice_interaction_controller_client()->SetControllerForTesting(
voice_interaction_controller_->CreateInterfacePtrAndBind());
framework_instance_ =
std::make_unique<FakeVoiceInteractionFrameworkInstance>();
arc_bridge_service_->voice_interaction_framework()->SetInstance(
framework_instance_.get());
WaitForInstanceReady(arc_bridge_service_->voice_interaction_framework());
// Flushing is required for the AttachClient call to get through to the
// highligther controller.
FlushHighlighterControllerMojo();
framework_service()->SetVoiceInteractionSetupCompleted();
// Flushing is required for the notify mojo call to get through to the voice
// interaction controller.
FlushVoiceInteractionControllerMojo();
}
void TearDown() override {
arc_bridge_service_->voice_interaction_framework()->CloseInstance(
framework_instance_.get());
voice_interaction_controller_.reset();
voice_interaction_controller_client_.reset();
framework_instance_.reset();
framework_service_.reset();
arc_bridge_service_.reset();
arc_session_manager_.reset();
session_manager_.reset();
profile_.reset();
AshTestBase::TearDown();
}
protected:
ArcBridgeService* arc_bridge_service() const {
return arc_bridge_service_.get();
}
ArcVoiceInteractionFrameworkService* framework_service() const {
return framework_service_.get();
}
FakeVoiceInteractionFrameworkInstance* framework_instance() const {
return framework_instance_.get();
}
TestHighlighterController* highlighter_controller() const {
return highlighter_controller_;
}
FakeVoiceInteractionController* voice_interaction_controller() {
return voice_interaction_controller_.get();
}
VoiceInteractionControllerClient* voice_interaction_controller_client() {
return voice_interaction_controller_client_.get();
}
void FlushHighlighterControllerMojo() {
framework_service_->GetHighlighterClientForTesting()->FlushMojoForTesting();
}
void FlushVoiceInteractionControllerMojo() {
voice_interaction_controller_client()->FlushMojoForTesting();
}
TestingProfile* profile() const { return profile_.get(); }
private:
std::unique_ptr<TestingProfile> profile_;
base::ScopedTempDir temp_dir_;
std::unique_ptr<session_manager::SessionManager> session_manager_;
std::unique_ptr<ArcBridgeService> arc_bridge_service_;
std::unique_ptr<ArcSessionManager> arc_session_manager_;
std::unique_ptr<service_manager::TestConnectorFactory> connector_factory_;
std::unique_ptr<service_manager::Connector> connector_;
// |highlighter_controller_| is valid until |connector_factory_| is deleted.
TestHighlighterController* highlighter_controller_;
std::unique_ptr<FakeVoiceInteractionController> voice_interaction_controller_;
std::unique_ptr<ArcVoiceInteractionFrameworkService> framework_service_;
std::unique_ptr<FakeVoiceInteractionFrameworkInstance> framework_instance_;
std::unique_ptr<VoiceInteractionControllerClient>
voice_interaction_controller_client_;
DISALLOW_COPY_AND_ASSIGN(ArcVoiceInteractionFrameworkServiceTest);
};
TEST_F(ArcVoiceInteractionFrameworkServiceTest, StartSetupWizard) {
framework_service()->StartVoiceInteractionSetupWizard();
// The signal to start setup wizard should be sent.
EXPECT_EQ(1u, framework_instance()->setup_wizard_count());
}
TEST_F(ArcVoiceInteractionFrameworkServiceTest, ShowSettings) {
framework_service()->ShowVoiceInteractionSettings();
// The signal to show voice interaction settings should be sent.
EXPECT_EQ(1u, framework_instance()->show_settings_count());
}
TEST_F(ArcVoiceInteractionFrameworkServiceTest, StartSession) {
framework_service()->StartSessionFromUserInteraction(gfx::Rect());
// A notification should be sent if the container is not ready yet.
FlushVoiceInteractionControllerMojo();
EXPECT_EQ(ash::mojom::VoiceInteractionState::NOT_READY,
voice_interaction_controller()->voice_interaction_state());
// The signal to start voice interaction session should be sent.
EXPECT_EQ(1u, framework_instance()->start_session_count());
}
TEST_F(ArcVoiceInteractionFrameworkServiceTest, StartSessionWithoutFlag) {
// Remove the voice interaction enabled flag.
framework_service()->SetVoiceInteractionEnabled(false,
base::BindOnce([](bool) {}));
framework_service()->StartSessionFromUserInteraction(gfx::Rect());
// The signal should not be sent when voice interaction disabled.
EXPECT_EQ(0u, framework_instance()->start_session_count());
}
TEST_F(ArcVoiceInteractionFrameworkServiceTest, StartSessionWithoutInstance) {
// Reset the framework instance.
arc_bridge_service()->voice_interaction_framework()->CloseInstance(
framework_instance());
framework_service()->StartSessionFromUserInteraction(gfx::Rect());
// A notification should be sent if the container is not ready yet.
FlushVoiceInteractionControllerMojo();
EXPECT_EQ(ash::mojom::VoiceInteractionState::NOT_READY,
voice_interaction_controller()->voice_interaction_state());
// The signal should not be sent when framework instance not ready.
EXPECT_EQ(0u, framework_instance()->start_session_count());
}
TEST_F(ArcVoiceInteractionFrameworkServiceTest, ToggleSession) {
framework_service()->ToggleSessionFromUserInteraction();
// A notification should be sent if the container is not ready yet.
FlushVoiceInteractionControllerMojo();
EXPECT_EQ(ash::mojom::VoiceInteractionState::NOT_READY,
voice_interaction_controller()->voice_interaction_state());
// The signal to toggle voice interaction session should be sent.
EXPECT_EQ(1u, framework_instance()->toggle_session_count());
}
TEST_F(ArcVoiceInteractionFrameworkServiceTest, HotwordTriggered) {
auto* audio_client = static_cast<chromeos::FakeCrasAudioClient*>(
chromeos::DBusThreadManager::Get()->GetCrasAudioClient());
audio_client->NotifyHotwordTriggeredForTesting(0, 0);
EXPECT_TRUE(framework_service()->ValidateTimeSinceUserInteraction());
}
TEST_F(ArcVoiceInteractionFrameworkServiceTest, HighlighterControllerClient) {
EXPECT_TRUE(highlighter_controller()->client_attached());
// Enabled state should propagate to the framework instance.
highlighter_controller()->CallHandleEnabledStateChange(true);
highlighter_controller()->FlushMojo();
EXPECT_EQ(1u, framework_instance()->set_metalayer_visibility_count());
EXPECT_TRUE(framework_instance()->metalayer_visible());
// Disabled state should propagate to the framework instance.
framework_instance()->ResetCounters();
highlighter_controller()->CallHandleEnabledStateChange(false);
highlighter_controller()->FlushMojo();
EXPECT_EQ(1u, framework_instance()->set_metalayer_visibility_count());
EXPECT_FALSE(framework_instance()->metalayer_visible());
// Enable the state again.
framework_instance()->ResetCounters();
highlighter_controller()->CallHandleEnabledStateChange(true);
highlighter_controller()->FlushMojo();
EXPECT_EQ(1u, framework_instance()->set_metalayer_visibility_count());
EXPECT_TRUE(framework_instance()->metalayer_visible());
// Simulate a valid selection.
framework_instance()->ResetCounters();
const gfx::Rect selection(100, 200, 300, 400);
highlighter_controller()->CallHandleSelection(selection);
highlighter_controller()->CallHandleEnabledStateChange(false);
highlighter_controller()->FlushMojo();
// Neither the selected region nor the state update should reach the
// framework instance yet.
EXPECT_EQ(0u, framework_instance()->start_session_for_region_count());
EXPECT_EQ(0u, framework_instance()->set_metalayer_visibility_count());
EXPECT_TRUE(framework_instance()->metalayer_visible());
framework_service()
->GetHighlighterClientForTesting()
->SimulateSelectionTimeoutForTesting();
// After a timeout, the selected region should reach the framework instance.
EXPECT_EQ(1u, framework_instance()->start_session_for_region_count());
EXPECT_EQ(selection.ToString(),
framework_instance()->selected_region().ToString());
// However, the state update should not be explicitly sent to the framework
// instance, since the state change is implied with a valid selection.
EXPECT_EQ(0u, framework_instance()->set_metalayer_visibility_count());
// Clear the framework instance to simulate the container crash.
// The client should become detached.
arc_bridge_service()->voice_interaction_framework()->CloseInstance(
framework_instance());
FlushHighlighterControllerMojo();
EXPECT_FALSE(highlighter_controller()->client_attached());
// Set the framework instance again to simulate the container restart.
// The client should become attached again.
arc_bridge_service()->voice_interaction_framework()->SetInstance(
framework_instance());
WaitForInstanceReady(arc_bridge_service()->voice_interaction_framework());
FlushHighlighterControllerMojo();
EXPECT_TRUE(highlighter_controller()->client_attached());
// State update should reach the client normally.
framework_instance()->ResetCounters();
highlighter_controller()->CallHandleEnabledStateChange(true);
highlighter_controller()->FlushMojo();
EXPECT_EQ(1u, framework_instance()->set_metalayer_visibility_count());
EXPECT_TRUE(framework_instance()->metalayer_visible());
}
TEST_F(ArcVoiceInteractionFrameworkServiceTest,
ExitVoiceInteractionAlsoExitHighlighter) {
highlighter_controller()->CallHandleEnabledStateChange(true);
framework_service()->ToggleSessionFromUserInteraction();
framework_instance()->FlushMojoForTesting();
FlushHighlighterControllerMojo();
EXPECT_EQ(ash::mojom::VoiceInteractionState::RUNNING,
framework_service()->GetStateForTesting());
framework_service()->ToggleSessionFromUserInteraction();
framework_instance()->FlushMojoForTesting();
FlushHighlighterControllerMojo();
EXPECT_EQ(ash::mojom::VoiceInteractionState::STOPPED,
framework_service()->GetStateForTesting());
EXPECT_FALSE(highlighter_controller()->is_enabled());
}
TEST_F(ArcVoiceInteractionFrameworkServiceTest,
VoiceInteractionControllerClient) {
FakeVoiceInteractionController* controller = voice_interaction_controller();
VoiceInteractionControllerClient* controller_client =
voice_interaction_controller_client();
// The voice interaction flags should be set after the initial setup.
EXPECT_EQ(controller->voice_interaction_state(),
ash::mojom::VoiceInteractionState::STOPPED);
// Send the signal to set the voice interaction state.
controller_client->NotifyStatusChanged(
ash::mojom::VoiceInteractionState::RUNNING);
FlushVoiceInteractionControllerMojo();
EXPECT_EQ(controller->voice_interaction_state(),
ash::mojom::VoiceInteractionState::RUNNING);
}
TEST_F(ArcVoiceInteractionFrameworkServiceTest,
CapturingScreenshotBlocksIncognitoWindows) {
auto browser_window =
CreateTestBrowserWindow(ash::Shell::GetPrimaryRootWindow());
Browser::CreateParams params(profile(), true);
params.type = Browser::TYPE_TABBED;
params.window = browser_window.get();
Browser browser(params);
browser_window->GetNativeWindow()->Show();
profile()->ForceIncognito(true);
// Layer::RecreateLayer() will replace the window's layer with the newly
// created layer. Thus, we need to save the |old_layer| for comparison.
auto* old_layer = browser_window->GetNativeWindow()->layer();
auto layer_owner = framework_service()->CreateLayerTreeForSnapshotForTesting(
ash::Shell::GetPrimaryRootWindow());
EXPECT_FALSE(FindLayer(layer_owner->root(), old_layer));
profile()->ForceIncognito(false);
old_layer = browser_window->GetNativeWindow()->layer();
layer_owner = framework_service()->CreateLayerTreeForSnapshotForTesting(
ash::Shell::GetPrimaryRootWindow());
EXPECT_TRUE(FindLayer(layer_owner->root(), old_layer));
ash::Shell::GetPrimaryRootWindow()->RemoveChild(
browser_window->GetNativeWindow());
}
} // namespace arc