1 // Copyright 2013 The Flutter 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 #define RAPIDJSON_HAS_STDSTRING 1
6
7 #include "platform_view.h"
8
9 #include <sstream>
10
11 #include "flutter/fml/logging.h"
12 #include "flutter/lib/ui/compositing/scene_host.h"
13 #include "flutter/lib/ui/window/pointer_data.h"
14 #include "logging.h"
15 #include "rapidjson/document.h"
16 #include "rapidjson/stringbuffer.h"
17 #include "rapidjson/writer.h"
18 #include "runtime/dart/utils/inlines.h"
19 #include "vsync_waiter.h"
20
21 namespace flutter_runner {
22
23 namespace {
24
Add(const fuchsia::ui::gfx::vec3 & a,const fuchsia::ui::gfx::vec3 & b)25 inline fuchsia::ui::gfx::vec3 Add(const fuchsia::ui::gfx::vec3& a,
26 const fuchsia::ui::gfx::vec3& b) {
27 return {.x = a.x + b.x, .y = a.y + b.y, .z = a.z + b.z};
28 }
29
Subtract(const fuchsia::ui::gfx::vec3 & a,const fuchsia::ui::gfx::vec3 & b)30 inline fuchsia::ui::gfx::vec3 Subtract(const fuchsia::ui::gfx::vec3& a,
31 const fuchsia::ui::gfx::vec3& b) {
32 return {.x = a.x - b.x, .y = a.y - b.y, .z = a.z - b.z};
33 }
34
InsetBy(const fuchsia::ui::gfx::BoundingBox & box,const fuchsia::ui::gfx::vec3 & inset_from_min,const fuchsia::ui::gfx::vec3 & inset_from_max)35 inline fuchsia::ui::gfx::BoundingBox InsetBy(
36 const fuchsia::ui::gfx::BoundingBox& box,
37 const fuchsia::ui::gfx::vec3& inset_from_min,
38 const fuchsia::ui::gfx::vec3& inset_from_max) {
39 return {.min = Add(box.min, inset_from_min),
40 .max = Subtract(box.max, inset_from_max)};
41 }
42
ViewPropertiesLayoutBox(const fuchsia::ui::gfx::ViewProperties & view_properties)43 inline fuchsia::ui::gfx::BoundingBox ViewPropertiesLayoutBox(
44 const fuchsia::ui::gfx::ViewProperties& view_properties) {
45 return InsetBy(view_properties.bounding_box, view_properties.inset_from_min,
46 view_properties.inset_from_max);
47 }
48
Max(const fuchsia::ui::gfx::vec3 & v,float min_val)49 inline fuchsia::ui::gfx::vec3 Max(const fuchsia::ui::gfx::vec3& v,
50 float min_val) {
51 return {.x = std::max(v.x, min_val),
52 .y = std::max(v.y, min_val),
53 .z = std::max(v.z, min_val)};
54 }
55
56 } // end namespace
57
58 static constexpr char kFlutterPlatformChannel[] = "flutter/platform";
59 static constexpr char kTextInputChannel[] = "flutter/textinput";
60 static constexpr char kKeyEventChannel[] = "flutter/keyevent";
61 static constexpr char kAccessibilityChannel[] = "flutter/accessibility";
62
63 // FL(77): Terminate engine if Fuchsia system FIDL connections have error.
64 template <class T>
SetInterfaceErrorHandler(fidl::InterfacePtr<T> & interface,std::string name)65 void SetInterfaceErrorHandler(fidl::InterfacePtr<T>& interface,
66 std::string name) {
67 interface.set_error_handler([name](zx_status_t status) {
68 FML_LOG(ERROR) << "Interface error on: " << name;
69 });
70 }
71 template <class T>
SetInterfaceErrorHandler(fidl::Binding<T> & binding,std::string name)72 void SetInterfaceErrorHandler(fidl::Binding<T>& binding, std::string name) {
73 binding.set_error_handler([name](zx_status_t status) {
74 FML_LOG(ERROR) << "Interface error on: " << name;
75 });
76 }
77
PlatformView(PlatformView::Delegate & delegate,std::string debug_label,flutter::TaskRunners task_runners,fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> parent_environment_service_provider_handle,fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener> session_listener_request,fit::closure session_listener_error_callback,OnMetricsUpdate session_metrics_did_change_callback,OnSizeChangeHint session_size_change_hint_callback,zx_handle_t vsync_event_handle)78 PlatformView::PlatformView(
79 PlatformView::Delegate& delegate,
80 std::string debug_label,
81 flutter::TaskRunners task_runners,
82 fidl::InterfaceHandle<fuchsia::sys::ServiceProvider>
83 parent_environment_service_provider_handle,
84 fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener>
85 session_listener_request,
86 fit::closure session_listener_error_callback,
87 OnMetricsUpdate session_metrics_did_change_callback,
88 OnSizeChangeHint session_size_change_hint_callback,
89 zx_handle_t vsync_event_handle)
90 : flutter::PlatformView(delegate, std::move(task_runners)),
91 debug_label_(std::move(debug_label)),
92 session_listener_binding_(this, std::move(session_listener_request)),
93 session_listener_error_callback_(
94 std::move(session_listener_error_callback)),
95 metrics_changed_callback_(std::move(session_metrics_did_change_callback)),
96 size_change_hint_callback_(std::move(session_size_change_hint_callback)),
97 ime_client_(this),
98 surface_(std::make_unique<Surface>(debug_label_)),
99 vsync_event_handle_(vsync_event_handle) {
100 // Register all error handlers.
101 SetInterfaceErrorHandler(session_listener_binding_, "SessionListener");
102 SetInterfaceErrorHandler(ime_, "Input Method Editor");
103 SetInterfaceErrorHandler(text_sync_service_, "Text Sync Service");
104 SetInterfaceErrorHandler(clipboard_, "Clipboard");
105 SetInterfaceErrorHandler(parent_environment_service_provider_,
106 "Parent Environment Service Provider");
107 // Access the clipboard.
108 parent_environment_service_provider_ =
109 parent_environment_service_provider_handle.Bind();
110 parent_environment_service_provider_.get()->ConnectToService(
111 fuchsia::modular::Clipboard::Name_,
112 clipboard_.NewRequest().TakeChannel());
113
114 parent_environment_service_provider_.get()->ConnectToService(
115 fuchsia::ui::input::ImeService::Name_,
116 text_sync_service_.NewRequest().TakeChannel());
117
118 // Finally! Register the native platform message handlers.
119 RegisterPlatformMessageHandlers();
120
121 // TODO(SCN-975): Re-enable. Likely that Engine should clone the ViewToken
122 // and pass the clone in here.
123 // view_->GetToken(std::bind(&PlatformView::ConnectSemanticsProvider, this,
124 // std::placeholders::_1));
125 }
126
127 PlatformView::~PlatformView() = default;
128
RegisterPlatformMessageHandlers()129 void PlatformView::RegisterPlatformMessageHandlers() {
130 platform_message_handlers_[kFlutterPlatformChannel] =
131 std::bind(&PlatformView::HandleFlutterPlatformChannelPlatformMessage,
132 this, std::placeholders::_1);
133 platform_message_handlers_[kTextInputChannel] =
134 std::bind(&PlatformView::HandleFlutterTextInputChannelPlatformMessage,
135 this, std::placeholders::_1);
136 platform_message_handlers_[kAccessibilityChannel] =
137 std::bind(&PlatformView::HandleAccessibilityChannelPlatformMessage, this,
138 std::placeholders::_1);
139 }
140
OnPropertiesChanged(const fuchsia::ui::gfx::ViewProperties & view_properties)141 void PlatformView::OnPropertiesChanged(
142 const fuchsia::ui::gfx::ViewProperties& view_properties) {
143 fuchsia::ui::gfx::BoundingBox layout_box =
144 ViewPropertiesLayoutBox(view_properties);
145
146 fuchsia::ui::gfx::vec3 logical_size =
147 Max(Subtract(layout_box.max, layout_box.min), 0.f);
148
149 metrics_.size.width = logical_size.x;
150 metrics_.size.height = logical_size.y;
151 metrics_.size.depth = logical_size.z;
152 metrics_.padding.left = view_properties.inset_from_min.x;
153 metrics_.padding.top = view_properties.inset_from_min.y;
154 metrics_.padding.front = view_properties.inset_from_min.z;
155 metrics_.padding.right = view_properties.inset_from_max.x;
156 metrics_.padding.bottom = view_properties.inset_from_max.y;
157 metrics_.padding.back = view_properties.inset_from_max.z;
158
159 FlushViewportMetrics();
160 }
161
162 // TODO(SCN-975): Re-enable.
163 // void PlatformView::ConnectSemanticsProvider(
164 // fuchsia::ui::viewsv1token::ViewToken token) {
165 // semantics_bridge_.SetupEnvironment(
166 // token.value, parent_environment_service_provider_.get());
167 // }
168
UpdateViewportMetrics(const fuchsia::ui::gfx::Metrics & metrics)169 void PlatformView::UpdateViewportMetrics(
170 const fuchsia::ui::gfx::Metrics& metrics) {
171 metrics_.scale = metrics.scale_x;
172 metrics_.scale_z = metrics.scale_z;
173
174 FlushViewportMetrics();
175 }
176
FlushViewportMetrics()177 void PlatformView::FlushViewportMetrics() {
178 const auto scale = metrics_.scale;
179 const auto scale_z = metrics_.scale_z;
180
181 SetViewportMetrics({
182 scale, // device_pixel_ratio
183 metrics_.size.width * scale, // physical_width
184 metrics_.size.height * scale, // physical_height
185 metrics_.size.depth * scale_z, // physical_depth
186 metrics_.padding.top * scale, // physical_padding_top
187 metrics_.padding.right * scale, // physical_padding_right
188 metrics_.padding.bottom * scale, // physical_padding_bottom
189 metrics_.padding.left * scale, // physical_padding_left
190 metrics_.view_inset.front * scale_z, // physical_view_inset_front
191 metrics_.view_inset.back * scale_z, // physical_view_inset_back
192 metrics_.view_inset.top * scale, // physical_view_inset_top
193 metrics_.view_inset.right * scale, // physical_view_inset_right
194 metrics_.view_inset.bottom * scale, // physical_view_inset_bottom
195 metrics_.view_inset.left * scale // physical_view_inset_left
196 });
197 }
198
199 // |fuchsia::ui::input::InputMethodEditorClient|
DidUpdateState(fuchsia::ui::input::TextInputState state,std::unique_ptr<fuchsia::ui::input::InputEvent> input_event)200 void PlatformView::DidUpdateState(
201 fuchsia::ui::input::TextInputState state,
202 std::unique_ptr<fuchsia::ui::input::InputEvent> input_event) {
203 rapidjson::Document document;
204 auto& allocator = document.GetAllocator();
205 rapidjson::Value encoded_state(rapidjson::kObjectType);
206 encoded_state.AddMember("text", state.text, allocator);
207 encoded_state.AddMember("selectionBase", state.selection.base, allocator);
208 encoded_state.AddMember("selectionExtent", state.selection.extent, allocator);
209 switch (state.selection.affinity) {
210 case fuchsia::ui::input::TextAffinity::UPSTREAM:
211 encoded_state.AddMember("selectionAffinity",
212 rapidjson::Value("TextAffinity.upstream"),
213 allocator);
214 break;
215 case fuchsia::ui::input::TextAffinity::DOWNSTREAM:
216 encoded_state.AddMember("selectionAffinity",
217 rapidjson::Value("TextAffinity.downstream"),
218 allocator);
219 break;
220 }
221 encoded_state.AddMember("selectionIsDirectional", true, allocator);
222 encoded_state.AddMember("composingBase", state.composing.start, allocator);
223 encoded_state.AddMember("composingExtent", state.composing.end, allocator);
224
225 rapidjson::Value args(rapidjson::kArrayType);
226 args.PushBack(current_text_input_client_, allocator);
227 args.PushBack(encoded_state, allocator);
228
229 document.SetObject();
230 document.AddMember("method",
231 rapidjson::Value("TextInputClient.updateEditingState"),
232 allocator);
233 document.AddMember("args", args, allocator);
234
235 rapidjson::StringBuffer buffer;
236 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
237 document.Accept(writer);
238
239 const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
240 DispatchPlatformMessage(fml::MakeRefCounted<flutter::PlatformMessage>(
241 kTextInputChannel, // channel
242 std::vector<uint8_t>(data, data + buffer.GetSize()), // message
243 nullptr) // response
244 );
245 last_text_state_ =
246 std::make_unique<fuchsia::ui::input::TextInputState>(state);
247
248 // Handle keyboard input events for HID keys only.
249 // TODO(SCN-1189): Are we done here?
250 if (input_event && input_event->keyboard().hid_usage != 0) {
251 OnHandleKeyboardEvent(input_event->keyboard());
252 }
253 }
254
255 // |fuchsia::ui::input::InputMethodEditorClient|
OnAction(fuchsia::ui::input::InputMethodAction action)256 void PlatformView::OnAction(fuchsia::ui::input::InputMethodAction action) {
257 rapidjson::Document document;
258 auto& allocator = document.GetAllocator();
259
260 rapidjson::Value args(rapidjson::kArrayType);
261 args.PushBack(current_text_input_client_, allocator);
262
263 // Done is currently the only text input action defined by Flutter.
264 args.PushBack("TextInputAction.done", allocator);
265
266 document.SetObject();
267 document.AddMember(
268 "method", rapidjson::Value("TextInputClient.performAction"), allocator);
269 document.AddMember("args", args, allocator);
270
271 rapidjson::StringBuffer buffer;
272 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
273 document.Accept(writer);
274
275 const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
276 DispatchPlatformMessage(fml::MakeRefCounted<flutter::PlatformMessage>(
277 kTextInputChannel, // channel
278 std::vector<uint8_t>(data, data + buffer.GetSize()), // message
279 nullptr) // response
280 );
281 }
282
OnScenicError(std::string error)283 void PlatformView::OnScenicError(std::string error) {
284 FML_LOG(ERROR) << "Session error: " << error;
285 session_listener_error_callback_();
286 }
287
OnScenicEvent(std::vector<fuchsia::ui::scenic::Event> events)288 void PlatformView::OnScenicEvent(
289 std::vector<fuchsia::ui::scenic::Event> events) {
290 TRACE_EVENT0("flutter", "PlatformView::OnScenicEvent");
291 for (const auto& event : events) {
292 switch (event.Which()) {
293 case fuchsia::ui::scenic::Event::Tag::kGfx:
294 switch (event.gfx().Which()) {
295 case fuchsia::ui::gfx::Event::Tag::kMetrics: {
296 if (!fidl::Equals(event.gfx().metrics().metrics, scenic_metrics_)) {
297 scenic_metrics_ = std::move(event.gfx().metrics().metrics);
298 metrics_changed_callback_(scenic_metrics_);
299 UpdateViewportMetrics(scenic_metrics_);
300 }
301 break;
302 }
303 case fuchsia::ui::gfx::Event::Tag::kSizeChangeHint: {
304 size_change_hint_callback_(
305 event.gfx().size_change_hint().width_change_factor,
306 event.gfx().size_change_hint().height_change_factor);
307 break;
308 }
309 case fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged: {
310 OnPropertiesChanged(
311 std::move(event.gfx().view_properties_changed().properties));
312 break;
313 }
314 case fuchsia::ui::gfx::Event::Tag::kViewConnected:
315 OnChildViewConnected(event.gfx().view_connected().view_holder_id);
316 break;
317 case fuchsia::ui::gfx::Event::Tag::kViewDisconnected:
318 OnChildViewDisconnected(
319 event.gfx().view_disconnected().view_holder_id);
320 break;
321 case fuchsia::ui::gfx::Event::Tag::kViewStateChanged:
322 OnChildViewStateChanged(
323 event.gfx().view_state_changed().view_holder_id,
324 event.gfx().view_state_changed().state.is_rendering);
325 break;
326 case fuchsia::ui::gfx::Event::Tag::Invalid:
327 FML_DCHECK(false) << "Flutter PlatformView::OnScenicEvent: Got "
328 "an invalid GFX event.";
329 break;
330 default:
331 // We don't care about some event types, so not handling them is OK.
332 break;
333 }
334 break;
335 case fuchsia::ui::scenic::Event::Tag::kInput:
336 switch (event.input().Which()) {
337 case fuchsia::ui::input::InputEvent::Tag::kFocus: {
338 OnHandleFocusEvent(event.input().focus());
339 break;
340 }
341 case fuchsia::ui::input::InputEvent::Tag::kPointer: {
342 OnHandlePointerEvent(event.input().pointer());
343 break;
344 }
345 case fuchsia::ui::input::InputEvent::Tag::kKeyboard: {
346 OnHandleKeyboardEvent(event.input().keyboard());
347 break;
348 }
349 case fuchsia::ui::input::InputEvent::Tag::Invalid: {
350 FML_DCHECK(false)
351 << "Flutter PlatformView::OnScenicEvent: Got an invalid INPUT "
352 "event.";
353 }
354 }
355 break;
356 default: {
357 break;
358 }
359 }
360 }
361 }
362
OnChildViewConnected(scenic::ResourceId view_holder_id)363 void PlatformView::OnChildViewConnected(scenic::ResourceId view_holder_id) {
364 task_runners_.GetUITaskRunner()->PostTask([view_holder_id]() {
365 flutter::SceneHost::OnViewConnected(view_holder_id);
366 });
367 }
368
OnChildViewDisconnected(scenic::ResourceId view_holder_id)369 void PlatformView::OnChildViewDisconnected(scenic::ResourceId view_holder_id) {
370 task_runners_.GetUITaskRunner()->PostTask([view_holder_id]() {
371 flutter::SceneHost::OnViewDisconnected(view_holder_id);
372 });
373 }
374
OnChildViewStateChanged(scenic::ResourceId view_holder_id,bool state)375 void PlatformView::OnChildViewStateChanged(scenic::ResourceId view_holder_id,
376 bool state) {
377 task_runners_.GetUITaskRunner()->PostTask([view_holder_id, state]() {
378 flutter::SceneHost::OnViewStateChanged(view_holder_id, state);
379 });
380 }
381
GetChangeFromPointerEventPhase(fuchsia::ui::input::PointerEventPhase phase)382 static flutter::PointerData::Change GetChangeFromPointerEventPhase(
383 fuchsia::ui::input::PointerEventPhase phase) {
384 switch (phase) {
385 case fuchsia::ui::input::PointerEventPhase::ADD:
386 return flutter::PointerData::Change::kAdd;
387 case fuchsia::ui::input::PointerEventPhase::HOVER:
388 return flutter::PointerData::Change::kHover;
389 case fuchsia::ui::input::PointerEventPhase::DOWN:
390 return flutter::PointerData::Change::kDown;
391 case fuchsia::ui::input::PointerEventPhase::MOVE:
392 return flutter::PointerData::Change::kMove;
393 case fuchsia::ui::input::PointerEventPhase::UP:
394 return flutter::PointerData::Change::kUp;
395 case fuchsia::ui::input::PointerEventPhase::REMOVE:
396 return flutter::PointerData::Change::kRemove;
397 case fuchsia::ui::input::PointerEventPhase::CANCEL:
398 return flutter::PointerData::Change::kCancel;
399 default:
400 return flutter::PointerData::Change::kCancel;
401 }
402 }
403
GetKindFromPointerType(fuchsia::ui::input::PointerEventType type)404 static flutter::PointerData::DeviceKind GetKindFromPointerType(
405 fuchsia::ui::input::PointerEventType type) {
406 switch (type) {
407 case fuchsia::ui::input::PointerEventType::TOUCH:
408 return flutter::PointerData::DeviceKind::kTouch;
409 case fuchsia::ui::input::PointerEventType::MOUSE:
410 return flutter::PointerData::DeviceKind::kMouse;
411 default:
412 return flutter::PointerData::DeviceKind::kTouch;
413 }
414 }
415
416 // TODO(SCN-1278): Remove this.
417 // Turns two floats (high bits, low bits) into a 64-bit uint.
PointerTraceHACK(float fa,float fb)418 static trace_flow_id_t PointerTraceHACK(float fa, float fb) {
419 uint32_t ia, ib;
420 memcpy(&ia, &fa, sizeof(uint32_t));
421 memcpy(&ib, &fb, sizeof(uint32_t));
422 return (((uint64_t)ia) << 32) | ib;
423 }
424
OnHandlePointerEvent(const fuchsia::ui::input::PointerEvent & pointer)425 bool PlatformView::OnHandlePointerEvent(
426 const fuchsia::ui::input::PointerEvent& pointer) {
427 TRACE_EVENT0("flutter", "PlatformView::OnHandlePointerEvent");
428
429 // TODO(SCN-1278): Use proper trace_id for tracing flow.
430 trace_flow_id_t trace_id =
431 PointerTraceHACK(pointer.radius_major, pointer.radius_minor);
432 TRACE_FLOW_END("input", "dispatch_event_to_client", trace_id);
433
434 flutter::PointerData pointer_data;
435 pointer_data.Clear();
436 pointer_data.time_stamp = pointer.event_time / 1000;
437 pointer_data.change = GetChangeFromPointerEventPhase(pointer.phase);
438 pointer_data.kind = GetKindFromPointerType(pointer.type);
439 pointer_data.device = pointer.pointer_id;
440 pointer_data.physical_x = pointer.x * metrics_.scale;
441 pointer_data.physical_y = pointer.y * metrics_.scale;
442 // Buttons are single bit values starting with kMousePrimaryButton = 1.
443 pointer_data.buttons = static_cast<uint64_t>(pointer.buttons);
444
445 switch (pointer_data.change) {
446 case flutter::PointerData::Change::kDown:
447 down_pointers_.insert(pointer_data.device);
448 break;
449 case flutter::PointerData::Change::kCancel:
450 case flutter::PointerData::Change::kUp:
451 down_pointers_.erase(pointer_data.device);
452 break;
453 case flutter::PointerData::Change::kMove:
454 if (down_pointers_.count(pointer_data.device) == 0) {
455 pointer_data.change = flutter::PointerData::Change::kHover;
456 }
457 break;
458 case flutter::PointerData::Change::kAdd:
459 if (down_pointers_.count(pointer_data.device) != 0) {
460 FML_DLOG(ERROR) << "Received add event for down pointer.";
461 }
462 break;
463 case flutter::PointerData::Change::kRemove:
464 if (down_pointers_.count(pointer_data.device) != 0) {
465 FML_DLOG(ERROR) << "Received remove event for down pointer.";
466 }
467 break;
468 case flutter::PointerData::Change::kHover:
469 if (down_pointers_.count(pointer_data.device) != 0) {
470 FML_DLOG(ERROR) << "Received hover event for down pointer.";
471 }
472 break;
473 }
474
475 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
476 packet->SetPointerData(0, pointer_data);
477 DispatchPointerDataPacket(std::move(packet));
478 return true;
479 }
480
OnHandleKeyboardEvent(const fuchsia::ui::input::KeyboardEvent & keyboard)481 bool PlatformView::OnHandleKeyboardEvent(
482 const fuchsia::ui::input::KeyboardEvent& keyboard) {
483 const char* type = nullptr;
484 if (keyboard.phase == fuchsia::ui::input::KeyboardEventPhase::PRESSED) {
485 type = "keydown";
486 } else if (keyboard.phase == fuchsia::ui::input::KeyboardEventPhase::REPEAT) {
487 type = "keydown"; // TODO change this to keyrepeat
488 } else if (keyboard.phase ==
489 fuchsia::ui::input::KeyboardEventPhase::RELEASED) {
490 type = "keyup";
491 }
492
493 if (type == nullptr) {
494 FML_DLOG(ERROR) << "Unknown key event phase.";
495 return false;
496 }
497
498 rapidjson::Document document;
499 auto& allocator = document.GetAllocator();
500 document.SetObject();
501 document.AddMember("type", rapidjson::Value(type, strlen(type)), allocator);
502 document.AddMember("keymap", rapidjson::Value("fuchsia"), allocator);
503 document.AddMember("hidUsage", keyboard.hid_usage, allocator);
504 document.AddMember("codePoint", keyboard.code_point, allocator);
505 document.AddMember("modifiers", keyboard.modifiers, allocator);
506 rapidjson::StringBuffer buffer;
507 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
508 document.Accept(writer);
509
510 const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
511 DispatchPlatformMessage(fml::MakeRefCounted<flutter::PlatformMessage>(
512 kKeyEventChannel, // channel
513 std::vector<uint8_t>(data, data + buffer.GetSize()), // data
514 nullptr) // response
515 );
516
517 return true;
518 }
519
OnHandleFocusEvent(const fuchsia::ui::input::FocusEvent & focus)520 bool PlatformView::OnHandleFocusEvent(
521 const fuchsia::ui::input::FocusEvent& focus) {
522 // Ensure last_text_state_ is set to make sure Flutter actually wants an IME.
523 if (focus.focused && last_text_state_ != nullptr) {
524 ActivateIme();
525 return true;
526 } else if (!focus.focused) {
527 DeactivateIme();
528 return true;
529 }
530 return false;
531 }
532
ActivateIme()533 void PlatformView::ActivateIme() {
534 DEBUG_CHECK(last_text_state_ != nullptr, LOG_TAG, "");
535
536 text_sync_service_->GetInputMethodEditor(
537 fuchsia::ui::input::KeyboardType::TEXT, // keyboard type
538 fuchsia::ui::input::InputMethodAction::DONE, // input method action
539 *last_text_state_, // initial state
540 ime_client_.NewBinding(), // client
541 ime_.NewRequest() // editor
542 );
543 }
544
DeactivateIme()545 void PlatformView::DeactivateIme() {
546 if (ime_) {
547 text_sync_service_->HideKeyboard();
548 ime_ = nullptr;
549 }
550 if (ime_client_.is_bound()) {
551 ime_client_.Unbind();
552 }
553 }
554
555 // |flutter::PlatformView|
CreateVSyncWaiter()556 std::unique_ptr<flutter::VsyncWaiter> PlatformView::CreateVSyncWaiter() {
557 return std::make_unique<flutter_runner::VsyncWaiter>(
558 debug_label_, vsync_event_handle_, task_runners_);
559 }
560
561 // |flutter::PlatformView|
CreateRenderingSurface()562 std::unique_ptr<flutter::Surface> PlatformView::CreateRenderingSurface() {
563 // This platform does not repeatly lose and gain a surface connection. So the
564 // surface is setup once during platform view setup and and returned to the
565 // shell on the initial (and only) |NotifyCreated| call.
566 return std::move(surface_);
567 }
568
569 // |flutter::PlatformView|
HandlePlatformMessage(fml::RefPtr<flutter::PlatformMessage> message)570 void PlatformView::HandlePlatformMessage(
571 fml::RefPtr<flutter::PlatformMessage> message) {
572 if (!message) {
573 return;
574 }
575 auto found = platform_message_handlers_.find(message->channel());
576 if (found == platform_message_handlers_.end()) {
577 FML_LOG(ERROR)
578 << "Platform view received message on channel '" << message->channel()
579 << "' with no registered handler. And empty response will be "
580 "generated. Please implement the native message handler.";
581 flutter::PlatformView::HandlePlatformMessage(std::move(message));
582 return;
583 }
584 found->second(std::move(message));
585 }
586
587 // |flutter::PlatformView|
UpdateSemantics(flutter::SemanticsNodeUpdates update,flutter::CustomAccessibilityActionUpdates actions)588 void PlatformView::UpdateSemantics(
589 flutter::SemanticsNodeUpdates update,
590 flutter::CustomAccessibilityActionUpdates actions) {
591 // TODO(MIT-1539): Uncomment/Reimplement following code, to add A11y support.
592 // semantics_bridge_.UpdateSemantics(update);
593 }
594
595 // Channel handler for kAccessibilityChannel
HandleAccessibilityChannelPlatformMessage(fml::RefPtr<flutter::PlatformMessage> message)596 void PlatformView::HandleAccessibilityChannelPlatformMessage(
597 fml::RefPtr<flutter::PlatformMessage> message) {
598 FML_DCHECK(message->channel() == kAccessibilityChannel);
599 }
600
601 // Channel handler for kFlutterPlatformChannel
HandleFlutterPlatformChannelPlatformMessage(fml::RefPtr<flutter::PlatformMessage> message)602 void PlatformView::HandleFlutterPlatformChannelPlatformMessage(
603 fml::RefPtr<flutter::PlatformMessage> message) {
604 FML_DCHECK(message->channel() == kFlutterPlatformChannel);
605 const auto& data = message->data();
606 rapidjson::Document document;
607 document.Parse(reinterpret_cast<const char*>(data.data()), data.size());
608 if (document.HasParseError() || !document.IsObject()) {
609 return;
610 }
611
612 auto root = document.GetObject();
613 auto method = root.FindMember("method");
614 if (method == root.MemberEnd() || !method->value.IsString()) {
615 return;
616 }
617
618 fml::RefPtr<flutter::PlatformMessageResponse> response = message->response();
619 if (method->value == "Clipboard.setData") {
620 auto text = root["args"]["text"].GetString();
621 clipboard_->Push(text);
622 response->CompleteEmpty();
623 } else if (method->value == "Clipboard.getData") {
624 clipboard_->Peek([response](fidl::StringPtr text) {
625 rapidjson::StringBuffer json_buffer;
626 rapidjson::Writer<rapidjson::StringBuffer> writer(json_buffer);
627 writer.StartArray();
628 writer.StartObject();
629 writer.Key("text");
630 writer.String(text.get());
631 writer.EndObject();
632 writer.EndArray();
633 std::string result = json_buffer.GetString();
634 response->Complete(std::make_unique<fml::DataMapping>(
635 std::vector<uint8_t>{result.begin(), result.end()}));
636 });
637 } else {
638 response->CompleteEmpty();
639 }
640 }
641
642 // Channel handler for kTextInputChannel
HandleFlutterTextInputChannelPlatformMessage(fml::RefPtr<flutter::PlatformMessage> message)643 void PlatformView::HandleFlutterTextInputChannelPlatformMessage(
644 fml::RefPtr<flutter::PlatformMessage> message) {
645 FML_DCHECK(message->channel() == kTextInputChannel);
646 const auto& data = message->data();
647 rapidjson::Document document;
648 document.Parse(reinterpret_cast<const char*>(data.data()), data.size());
649 if (document.HasParseError() || !document.IsObject()) {
650 return;
651 }
652 auto root = document.GetObject();
653 auto method = root.FindMember("method");
654 if (method == root.MemberEnd() || !method->value.IsString()) {
655 return;
656 }
657
658 if (method->value == "TextInput.show") {
659 if (ime_) {
660 text_sync_service_->ShowKeyboard();
661 }
662 } else if (method->value == "TextInput.hide") {
663 if (ime_) {
664 text_sync_service_->HideKeyboard();
665 }
666 } else if (method->value == "TextInput.setClient") {
667 current_text_input_client_ = 0;
668 DeactivateIme();
669 auto args = root.FindMember("args");
670 if (args == root.MemberEnd() || !args->value.IsArray() ||
671 args->value.Size() != 2)
672 return;
673 const auto& configuration = args->value[1];
674 if (!configuration.IsObject()) {
675 return;
676 }
677 // TODO(abarth): Read the keyboard type from the configuration.
678 current_text_input_client_ = args->value[0].GetInt();
679
680 auto initial_text_input_state = fuchsia::ui::input::TextInputState{};
681 initial_text_input_state.text = "";
682 last_text_state_ = std::make_unique<fuchsia::ui::input::TextInputState>(
683 initial_text_input_state);
684 ActivateIme();
685 } else if (method->value == "TextInput.setEditingState") {
686 if (ime_) {
687 auto args_it = root.FindMember("args");
688 if (args_it == root.MemberEnd() || !args_it->value.IsObject()) {
689 return;
690 }
691 const auto& args = args_it->value;
692 fuchsia::ui::input::TextInputState state;
693 state.text = "";
694 // TODO(abarth): Deserialize state.
695 auto text = args.FindMember("text");
696 if (text != args.MemberEnd() && text->value.IsString())
697 state.text = text->value.GetString();
698 auto selection_base = args.FindMember("selectionBase");
699 if (selection_base != args.MemberEnd() && selection_base->value.IsInt())
700 state.selection.base = selection_base->value.GetInt();
701 auto selection_extent = args.FindMember("selectionExtent");
702 if (selection_extent != args.MemberEnd() &&
703 selection_extent->value.IsInt())
704 state.selection.extent = selection_extent->value.GetInt();
705 auto selection_affinity = args.FindMember("selectionAffinity");
706 if (selection_affinity != args.MemberEnd() &&
707 selection_affinity->value.IsString() &&
708 selection_affinity->value == "TextAffinity.upstream")
709 state.selection.affinity = fuchsia::ui::input::TextAffinity::UPSTREAM;
710 else
711 state.selection.affinity = fuchsia::ui::input::TextAffinity::DOWNSTREAM;
712 // We ignore selectionIsDirectional because that concept doesn't exist on
713 // Fuchsia.
714 auto composing_base = args.FindMember("composingBase");
715 if (composing_base != args.MemberEnd() && composing_base->value.IsInt())
716 state.composing.start = composing_base->value.GetInt();
717 auto composing_extent = args.FindMember("composingExtent");
718 if (composing_extent != args.MemberEnd() &&
719 composing_extent->value.IsInt())
720 state.composing.end = composing_extent->value.GetInt();
721 ime_->SetState(std::move(state));
722 }
723 } else if (method->value == "TextInput.clearClient") {
724 current_text_input_client_ = 0;
725 last_text_state_ = nullptr;
726 DeactivateIme();
727 } else {
728 FML_DLOG(ERROR) << "Unknown " << message->channel() << " method "
729 << method->value.GetString();
730 }
731 }
732
733 } // namespace flutter_runner
734