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