1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ash/assistant/ui/main_stage/assistant_card_element_view.h"
6
7 #include <memory>
8
9 #include "ash/assistant/model/ui/assistant_card_element.h"
10 #include "ash/assistant/ui/assistant_ui_constants.h"
11 #include "ash/assistant/ui/assistant_view_delegate.h"
12 #include "ash/assistant/ui/main_stage/assistant_ui_element_view_animator.h"
13 #include "ash/assistant/util/deep_link_util.h"
14 #include "ash/public/cpp/assistant/controller/assistant_controller.h"
15 #include "ui/aura/window.h"
16 #include "ui/aura/window_tree_host.h"
17 #include "ui/events/event.h"
18 #include "ui/events/event_sink.h"
19 #include "ui/events/event_utils.h"
20 #include "ui/views/accessibility/view_accessibility.h"
21 #include "ui/views/layout/fill_layout.h"
22 #include "ui/views/view.h"
23 #include "ui/views/widget/widget.h"
24
25 namespace ash {
26
27 namespace {
28
29 using assistant::util::DeepLinkParam;
30 using assistant::util::DeepLinkType;
31 using assistant::util::ProactiveSuggestionsAction;
32
33 // Helpers ---------------------------------------------------------------------
34
CreateAndSendMouseClick(aura::WindowTreeHost * host,const gfx::Point & location_in_pixels)35 void CreateAndSendMouseClick(aura::WindowTreeHost* host,
36 const gfx::Point& location_in_pixels) {
37 ui::MouseEvent press_event(ui::ET_MOUSE_PRESSED, location_in_pixels,
38 location_in_pixels, ui::EventTimeForNow(),
39 ui::EF_LEFT_MOUSE_BUTTON,
40 ui::EF_LEFT_MOUSE_BUTTON);
41
42 // Send an ET_MOUSE_PRESSED event.
43 ui::EventDispatchDetails details =
44 host->event_sink()->OnEventFromSource(&press_event);
45
46 if (details.dispatcher_destroyed)
47 return;
48
49 ui::MouseEvent release_event(ui::ET_MOUSE_RELEASED, location_in_pixels,
50 location_in_pixels, ui::EventTimeForNow(),
51 ui::EF_LEFT_MOUSE_BUTTON,
52 ui::EF_LEFT_MOUSE_BUTTON);
53
54 // Send an ET_MOUSE_RELEASED event.
55 ignore_result(host->event_sink()->OnEventFromSource(&release_event));
56 }
57
58 } // namespace
59
AssistantCardElementView(AssistantViewDelegate * delegate,const AssistantCardElement * card_element)60 AssistantCardElementView::AssistantCardElementView(
61 AssistantViewDelegate* delegate,
62 const AssistantCardElement* card_element)
63 : delegate_(delegate), card_element_(card_element) {
64 InitLayout();
65
66 // We observe contents_view() to receive events pertaining to the underlying
67 // WebContents including focus change and suppressed navigation events.
68 contents_view_->AddObserver(this);
69 }
70
~AssistantCardElementView()71 AssistantCardElementView::~AssistantCardElementView() {
72 contents_view_->RemoveObserver(this);
73 }
74
GetClassName() const75 const char* AssistantCardElementView::GetClassName() const {
76 return "AssistantCardElementView";
77 }
78
GetLayerForAnimating()79 ui::Layer* AssistantCardElementView::GetLayerForAnimating() {
80 return native_view()->layer();
81 }
82
ToStringForTesting() const83 std::string AssistantCardElementView::ToStringForTesting() const {
84 return card_element_->html();
85 }
86
AddedToWidget()87 void AssistantCardElementView::AddedToWidget() {
88 aura::Window* const top_level_window = native_view()->GetToplevelWindow();
89
90 // Find the window for the Assistant card.
91 aura::Window* window = native_view();
92 while (window->parent() != top_level_window)
93 window = window->parent();
94
95 // The Assistant card window will consume all events that enter it. This
96 // prevents us from being able to scroll the native view hierarchy
97 // vertically. As such, we need to prevent the Assistant card window from
98 // receiving events it doesn't need. It needs mouse click events for
99 // handling links.
100 window->SetProperty(assistant::ui::kOnlyAllowMouseClickEvents, true);
101 }
102
ChildPreferredSizeChanged(views::View * child)103 void AssistantCardElementView::ChildPreferredSizeChanged(views::View* child) {
104 PreferredSizeChanged();
105 }
106
OnGestureEvent(ui::GestureEvent * event)107 void AssistantCardElementView::OnGestureEvent(ui::GestureEvent* event) {
108 // We need to route GESTURE_TAP events to our Assistant card because links
109 // should be tappable. The Assistant card window will not receive gesture
110 // events so we convert the gesture into analogous mouse events.
111 if (event->type() != ui::ET_GESTURE_TAP) {
112 views::View::OnGestureEvent(event);
113 return;
114 }
115
116 // Consume the original event.
117 event->StopPropagation();
118 event->SetHandled();
119
120 aura::Window* root_window = GetWidget()->GetNativeWindow()->GetRootWindow();
121
122 // Get the appropriate event location in pixels.
123 gfx::Point location_in_pixels = event->location();
124 ConvertPointToScreen(this, &location_in_pixels);
125 aura::WindowTreeHost* host = root_window->GetHost();
126 host->ConvertDIPToPixels(&location_in_pixels);
127
128 wm::CursorManager* cursor_manager = delegate_->GetCursorManager();
129
130 // We want to prevent the cursor from changing its visibility during our
131 // mouse events because we are actually handling a gesture. To accomplish
132 // this, we cache the cursor's visibility and lock it in its current state.
133 const bool visible = cursor_manager->IsCursorVisible();
134 cursor_manager->LockCursor();
135
136 CreateAndSendMouseClick(host, location_in_pixels);
137
138 // Restore the original cursor visibility that may have changed during our
139 // sequence of mouse events. This change would not have been perceivable to
140 // the user since it occurred within our lock.
141 if (visible)
142 cursor_manager->ShowCursor();
143 else
144 cursor_manager->HideCursor();
145
146 // Release our cursor lock.
147 cursor_manager->UnlockCursor();
148 }
149
ScrollRectToVisible(const gfx::Rect & rect)150 void AssistantCardElementView::ScrollRectToVisible(const gfx::Rect& rect) {
151 // We expect this method is called outside this class to show its contents
152 // bounds. Inside this class, should call views::View::ScrollRectToVisible()
153 // to show the focused node in the web contents.
154 DCHECK(rect == GetContentsBounds());
155
156 // When this view is focused, View::Focus() calls ScrollViewToVisible(), which
157 // calls ScrollRectToVisible(). But we don't want that call to do anything,
158 // since the true focused item is not this view but a node in the contained
159 // web contents. That will be scrolled into view by FocusedNodeChanged()
160 // below, so just no-op here.
161 if (focused_node_rect_.IsEmpty())
162 return;
163
164 // Make the focused node visible.
165 views::View::ScrollRectToVisible(focused_node_rect_);
166 }
167
DidSuppressNavigation(const GURL & url,WindowOpenDisposition disposition,bool from_user_gesture)168 void AssistantCardElementView::DidSuppressNavigation(
169 const GURL& url,
170 WindowOpenDisposition disposition,
171 bool from_user_gesture) {
172 // Proactive suggestion deep links may be invoked without a user gesture to
173 // log view impressions. Those are (currently) the only deep links we allow to
174 // be processed without originating from a user event.
175 if (!from_user_gesture) {
176 DeepLinkType deep_link_type = assistant::util::GetDeepLinkType(url);
177 if (deep_link_type != DeepLinkType::kProactiveSuggestions) {
178 NOTREACHED();
179 return;
180 }
181
182 const base::Optional<ProactiveSuggestionsAction> action =
183 assistant::util::GetDeepLinkParamAsProactiveSuggestionsAction(
184 assistant::util::GetDeepLinkParams(url), DeepLinkParam::kAction);
185 if (action != ProactiveSuggestionsAction::kViewImpression) {
186 NOTREACHED();
187 return;
188 }
189 }
190 // We delegate navigation to the AssistantController so that it can apply
191 // special handling to deep links.
192 AssistantController::Get()->OpenUrl(url);
193 }
194
DidChangeFocusedNode(const gfx::Rect & node_bounds_in_screen)195 void AssistantCardElementView::DidChangeFocusedNode(
196 const gfx::Rect& node_bounds_in_screen) {
197 // TODO(b/143985066): Card has element with empty bounds, e.g. the line break.
198 if (node_bounds_in_screen.IsEmpty())
199 return;
200
201 gfx::Point origin = node_bounds_in_screen.origin();
202 ConvertPointFromScreen(this, &origin);
203 focused_node_rect_ = gfx::Rect(origin, node_bounds_in_screen.size());
204 views::View::ScrollRectToVisible(focused_node_rect_);
205 }
206
InitLayout()207 void AssistantCardElementView::InitLayout() {
208 SetLayoutManager(std::make_unique<views::FillLayout>());
209
210 // Contents view.
211 contents_view_ = AddChildView(
212 const_cast<AssistantCardElement*>(card_element_)->MoveContentsView());
213
214 // OverrideDescription() doesn't work. Only names are read automatically.
215 GetViewAccessibility().OverrideName(card_element_->fallback());
216 }
217
CreateAnimator()218 std::unique_ptr<ElementAnimator> AssistantCardElementView::CreateAnimator() {
219 return std::make_unique<AssistantUiElementViewAnimator>(
220 this, assistant::ui::kAssistantCardElementHistogram);
221 }
222
223 } // namespace ash
224