Skip to content

Commit

Permalink
input-overlay: add educational UI
Browse files Browse the repository at this point in the history
On first run per app/game, the feature must show a introductory UI to
the user in order to briefly describe its functionality. This CL adds
said UI and the corresponding string/icon resources, and the UI will be
visible every time the user opens a game tied to gaming input overlay.

A follow-up CL must control this UI so that its only shown once, on the
first run, by tying the feature to the user's profile.

Bug: b:188452744
Test: manual, install/open archero, this is the first UI seen.
Change-Id: I18249d3280c79ca556049c72effe730d59b1e690
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3564108
Commit-Queue: David Jacobo <djacobo@chromium.org>
Reviewed-by: Cici Ruan <cuicuiruan@google.com>
Reviewed-by: Kyle Horimoto <khorimoto@chromium.org>
Auto-Submit: David Jacobo <djacobo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1001335}
  • Loading branch information
David Jacobo authored and Chromium LUCI CQ committed May 10, 2022
1 parent 944f860 commit 72f43ac
Show file tree
Hide file tree
Showing 18 changed files with 359 additions and 12 deletions.
12 changes: 12 additions & 0 deletions chrome/app/generated_resources.grd
Original file line number Diff line number Diff line change
Expand Up @@ -13001,6 +13001,18 @@ Please help our engineers fix this problem. Tell us what happened right before y
<message name="IDS_INPUT_OVERLAY_EDIT_MODE_CANCEL" desc="Discard all the newly assigned key-bindings in this session.">
Cancel
</message>
<message name="IDS_INPUT_OVERLAY_EDUCATIONAL_TITLE" desc="Educational dialog's title, this is a friendly name for the feature.">
Game Control
</message>
<message name="IDS_INPUT_OVERLAY_EDUCATIONAL_DESCRIPTION" desc="Text on the educational dialog describing the funcionality introduced by this feature.">
You’ve been invited to try out keyboard control for this game.
</message>
<message name="IDS_INPUT_OVERLAY_EDUCATIONAL_ACCEPT_BUTTON" desc="Educational dialog's accept button text.">
Got it
</message>
<message name="IDS_INPUT_OVERLAY_EDUCATIONAL_BETA" desc="Label marking the feature as 'Beta', this has to be removed after the feature is fully approved.">
Beta
</message>
</if>

<!-- Privacy Sandbox Dialog strings -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
896c1efed16c6ad99748168aaa7008a955dfb8c3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
896c1efed16c6ad99748168aaa7008a955dfb8c3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
896c1efed16c6ad99748168aaa7008a955dfb8c3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
896c1efed16c6ad99748168aaa7008a955dfb8c3
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ class ArcInputOverlayManagerTest : public exo::test::ExoTestBase {
arc_test_input_overlay_manager_->OnWindowFocused(gain_focus, lost_focus);
}

// TODO(djacobo): Maybe move all tests inside input_overlay namespace.
void DismissEducationalDialog(input_overlay::TouchInjector* injector) {
injector->GetControllerForTesting()->DismissEducationalViewForTesting();
}

protected:
std::unique_ptr<ArcInputOverlayManager> arc_test_input_overlay_manager_;

Expand Down Expand Up @@ -188,7 +193,11 @@ TEST_F(ArcInputOverlayManagerTest, TestKeyEventSourceRewriterForMultiDisplay) {
// I/O takes time here.
task_environment()->FastForwardBy(kIORead);
// arc_window->SetBounds(display1, gfx::Rect(1010, 910, 100, 100));
// Make sure to dismiss the educational dialog in beforehand.
auto* injector = GetTouchInjector(arc_window->GetWindow());
EXPECT_TRUE(injector);
WindowFocus(arc_window->GetWindow(), nullptr);
DismissEducationalDialog(injector);
EXPECT_TRUE(GetKeyEventSourceRewriter());
// Simulate the fact that key events are only sent to primary root window
// when there is no text input focus. Make sure the input overlay window can
Expand All @@ -197,12 +206,12 @@ TEST_F(ArcInputOverlayManagerTest, TestKeyEventSourceRewriterForMultiDisplay) {
std::make_unique<ui::test::EventGenerator>(root_windows[0]);
input_overlay::test::EventCapturer event_capturer;
root_windows[1]->AddPostTargetHandler(&event_capturer);
event_generator->PressKey(ui::VKEY_A, ui::EF_NONE, 1 /* keyboard id */);
event_generator->PressKey(ui::VKEY_A, ui::EF_NONE, /*source_device_id=*/1);
EXPECT_TRUE(event_capturer.key_events().empty());
EXPECT_TRUE(event_capturer.touch_events().size() == 1);
EXPECT_EQ(1u, event_capturer.touch_events().size());
event_generator->ReleaseKey(ui::VKEY_A, ui::EF_NONE, 1);
EXPECT_TRUE(event_capturer.key_events().empty());
EXPECT_TRUE(event_capturer.touch_events().size() == 2);
EXPECT_EQ(2u, event_capturer.touch_events().size());
event_capturer.Clear();
root_windows[1]->RemovePostTargetHandler(&event_capturer);
// Move to the primary display.
Expand Down
70 changes: 63 additions & 7 deletions chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "ash/shell.h"
#include "base/bind.h"
#include "chrome/browser/ash/arc/input_overlay/ui/edit_mode_exit_view.h"
#include "chrome/browser/ash/arc/input_overlay/ui/educational_view.h"
#include "chrome/browser/ash/arc/input_overlay/ui/error_view.h"
#include "chrome/browser/ash/arc/input_overlay/ui/input_menu_view.h"
#include "chrome/grit/generated_resources.h"
Expand Down Expand Up @@ -42,10 +43,7 @@ DisplayOverlayController::DisplayOverlayController(
: touch_injector_(touch_injector) {
AddOverlay();
touch_injector_->set_display_overlay_controller(this);
// TODO(cuicuiruan): Initially it should be in |kEducation| mode when
// launching and showing the educational dialog. Redo the logic here when the
// educational dialog is ready.
SetDisplayMode(DisplayMode::kView);
SetDisplayMode(DisplayMode::kEducation);
ash::Shell::Get()->AddPreTargetHandler(this);
}

Expand All @@ -55,8 +53,14 @@ DisplayOverlayController::~DisplayOverlayController() {
}

void DisplayOverlayController::OnWindowBoundsChanged() {
auto mode = display_mode_;
SetDisplayMode(DisplayMode::kNone);
SetDisplayMode(DisplayMode::kView);
// Transition to |kView| mode except while on |kEducation| mode since
// displaying this UI needs to be ensured as the user shouldn't be able to
// manually access said view.
if (mode != DisplayMode::kEducation)
mode = DisplayMode::kView;
SetDisplayMode(mode);
}

// For test:
Expand All @@ -78,7 +82,7 @@ void DisplayOverlayController::AddOverlay() {
params.focusable = true;
shell_surface_base->AddOverlay(std::move(params));

SetDisplayMode(DisplayMode::kView);
SetDisplayMode(DisplayMode::kEducation);
}

void DisplayOverlayController::RemoveOverlayIfAny() {
Expand Down Expand Up @@ -185,6 +189,12 @@ void DisplayOverlayController::RemoveEditModeExitView() {
edit_mode_view_ = nullptr;
}

void DisplayOverlayController::OnEducationalViewDismissed() {
// TODO(djacobo|cuicuiruan): Save this pref on user's profile so the
// educational dialog is not seen ever again for this app.
SetDisplayMode(DisplayMode::kView);
}

views::Widget* DisplayOverlayController::GetOverlayWidget() {
auto* shell_surface_base =
exo::GetShellSurfaceBaseForWindow(touch_injector_->target_window());
Expand Down Expand Up @@ -221,6 +231,13 @@ gfx::Point DisplayOverlayController::CalculateEditModeExitPosition() {
std::max(0, view->height() / 2 - kEditModeExitHeight / 2));
}

views::View* DisplayOverlayController::GetParentView() {
auto* overlay_widget = GetOverlayWidget();
if (!overlay_widget)
return nullptr;
return overlay_widget->GetContentsView();
}

void DisplayOverlayController::SetDisplayMode(DisplayMode mode) {
if (display_mode_ == mode)
return;
Expand All @@ -237,13 +254,18 @@ void DisplayOverlayController::SetDisplayMode(DisplayMode mode) {
RemoveEditModeExitView();
break;
case DisplayMode::kEducation:
// TODO(cuicuiruan): Add educational dialog.
// If the dialog doesn't needs to be shown just abort the rest of the
// function and call again on |kView| mode.
RemoveEducationalView();
if (!MaybeShowEducationalView())
return;
overlay_widget->GetNativeWindow()->SetEventTargetingPolicy(
aura::EventTargetingPolicy::kTargetAndDescendants);
break;
case DisplayMode::kView:
RemoveInputMenuView();
RemoveEditModeExitView();
RemoveEducationalView();
AddInputMappingView(overlay_widget);
AddMenuEntryView(overlay_widget);
overlay_widget->GetNativeWindow()->SetEventTargetingPolicy(
Expand All @@ -252,6 +274,7 @@ void DisplayOverlayController::SetDisplayMode(DisplayMode mode) {
case DisplayMode::kEdit:
RemoveInputMenuView();
RemoveMenuEntryView();
RemoveEducationalView();
AddEditModeExitView(overlay_widget);
overlay_widget->GetNativeWindow()->SetEventTargetingPolicy(
aura::EventTargetingPolicy::kTargetAndDescendants);
Expand Down Expand Up @@ -413,5 +436,38 @@ void DisplayOverlayController::ProcessPressedEvent(
}
}

bool DisplayOverlayController::MaybeShowEducationalView() {
bool first_run = FirstRun();
if (first_run) {
auto* overlay_widget = GetOverlayWidget();
DCHECK(overlay_widget);
auto* parent_view = overlay_widget->GetContentsView();
DCHECK(parent_view);

educational_view_ = parent_view->AddChildView(
EducationalView::BuildMenu(this, GetParentView()));
} else {
SetDisplayMode(DisplayMode::kView);
}

return first_run;
}

void DisplayOverlayController::RemoveEducationalView() {
if (!educational_view_)
return;
educational_view_->parent()->RemoveChildViewT(educational_view_);
educational_view_ = nullptr;
}

bool DisplayOverlayController::FirstRun() const {
// TODO(djacobo|cuicuiruan): Add logic to retrieve user's pref.
return false;
}

void DisplayOverlayController::DismissEducationalViewForTesting() {
OnEducationalViewDismissed();
}

} // namespace input_overlay
} // namespace arc
17 changes: 17 additions & 0 deletions chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ class Widget;
} // namespace views

namespace arc {
class ArcInputOverlayManagerTest;
namespace input_overlay {
class TouchInjector;
class InputMappingView;
class InputMenuView;
class ActionEditMenu;
class EditModeExitView;
class ErrorView;
class EducationalView;

// DisplayOverlayController manages the input mapping view, view and edit mode,
// menu, and educational dialog. It also handles the visibility of the
Expand Down Expand Up @@ -69,7 +71,9 @@ class DisplayOverlayController : public ui::EventHandler {
void OnTouchEvent(ui::TouchEvent* event) override;

private:
friend class ::arc::ArcInputOverlayManagerTest;
friend class DisplayOverlayControllerTest;
friend class EducationalView;
friend class InputMenuView;
friend class InputMappingView;

Expand All @@ -86,9 +90,12 @@ class DisplayOverlayController : public ui::EventHandler {
void RemoveMenuEntryView();
void RemoveEditModeExitView();

void OnEducationalViewDismissed();

views::Widget* GetOverlayWidget();
gfx::Point CalculateMenuEntryPosition();
gfx::Point CalculateEditModeExitPosition();
views::View* GetParentView();
bool HasMenuView() const;
void SetInputMappingVisible(bool visible);
bool GetInputMappingViewVisible() const;
Expand All @@ -100,8 +107,17 @@ class DisplayOverlayController : public ui::EventHandler {
// their view bounds.
void ProcessPressedEvent(const ui::LocatedEvent& event);

// Decide whether or not to show ed. dialog based on user profile's data.
bool MaybeShowEducationalView();
// Remove |EducationalView| and its references.
void RemoveEducationalView();
// Checks user's profile data to see if the related game/app has been run
// before.
bool FirstRun() const;

// For test:
gfx::Rect GetInputMappingViewBoundsForTesting();
void DismissEducationalViewForTesting();

TouchInjector* touch_injector() { return touch_injector_; }

Expand All @@ -114,6 +130,7 @@ class DisplayOverlayController : public ui::EventHandler {
raw_ptr<ActionEditMenu> action_edit_menu_ = nullptr;
raw_ptr<EditModeExitView> edit_mode_view_ = nullptr;
raw_ptr<ErrorView> error_ = nullptr;
raw_ptr<EducationalView> educational_view_ = nullptr;

DisplayMode display_mode_ = DisplayMode::kNone;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class DisplayOverlayControllerTest : public exo::test::ExoTestBase {
return controller_->GetInputMappingViewBoundsForTesting();
}

void DismissEducationalDialog() {
controller_->DismissEducationalViewForTesting();
}

protected:
std::unique_ptr<input_overlay::test::ArcTestWindow> arc_test_window_;
std::unique_ptr<DisplayOverlayController> controller_;
Expand Down Expand Up @@ -78,6 +82,8 @@ class DisplayOverlayControllerTest : public exo::test::ExoTestBase {
};

TEST_F(DisplayOverlayControllerTest, TestWindowBoundsChange) {
// Make sure educational dialog is bypassed.
DismissEducationalDialog();
auto original_bounds = GetInputMappingViewBounds();
auto new_bounds = gfx::Rect(original_bounds);
new_bounds.set_width(new_bounds.size().width() + 50);
Expand Down
8 changes: 6 additions & 2 deletions chrome/browser/ash/arc/input_overlay/touch_injector.cc
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,6 @@ void TouchInjector::OnBindingChange(Action* target_action,
void TouchInjector::OnBindingSave() {
for (auto& action : actions_)
action->BindPending();
if (display_overlay_controller_)
display_overlay_controller_->SetDisplayMode(DisplayMode::kView);
OnSaveProtoFile();
}

Expand All @@ -223,6 +221,8 @@ const std::string* TouchInjector::GetPackageName() const {
}

void TouchInjector::OnProtoDataAvailable(std::unique_ptr<AppDataProto> proto) {
if (proto->actions().empty())
return;
for (const ActionProto& action_proto : proto->actions()) {
auto* action = GetActionById(action_proto.id());
DCHECK(action);
Expand Down Expand Up @@ -571,5 +571,9 @@ int TouchInjector::GetRewrittenTouchInfoSizeForTesting() {
return rewritten_touch_infos_.size();
}

DisplayOverlayController* TouchInjector::GetControllerForTesting() {
return display_overlay_controller_;
}

} // namespace input_overlay
} // namespace arc
3 changes: 3 additions & 0 deletions chrome/browser/ash/arc/input_overlay/touch_injector.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Window;
} // namespace aura

namespace arc {
class ArcInputOverlayManagerTest;
namespace input_overlay {
class DisplayOverlayController;
class Action;
Expand Down Expand Up @@ -108,6 +109,7 @@ class TouchInjector : public ui::EventRewriter {
const Continuation continuation) override;

private:
friend class ::arc::ArcInputOverlayManagerTest;
friend class TouchInjectorTest;

struct TouchPointInfo {
Expand Down Expand Up @@ -161,6 +163,7 @@ class TouchInjector : public ui::EventRewriter {
int GetRewrittenTouchIdForTesting(ui::PointerId original_id);
gfx::PointF GetRewrittenRootLocationForTesting(ui::PointerId original_id);
int GetRewrittenTouchInfoSizeForTesting();
DisplayOverlayController* GetControllerForTesting();

raw_ptr<aura::Window> target_window_;
base::WeakPtr<ui::EventRewriterContinuation> continuation_;
Expand Down
1 change: 1 addition & 0 deletions chrome/browser/ash/arc/input_overlay/ui/action_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ ActionView::ActionView(Action* action,
ActionView::~ActionView() = default;

void ActionView::SetDisplayMode(DisplayMode mode) {
DCHECK(mode != DisplayMode::kEducation);
if ((!editable_ && mode == DisplayMode::kEdit) || mode == DisplayMode::kMenu)
return;
if (mode == DisplayMode::kView) {
Expand Down

0 comments on commit 72f43ac

Please sign in to comment.