Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(933)

Unified Diff: ui/base/ime/android/cursor_anchor_info_controller.cc

Issue 699333003: Support InputMethodManager#updateCursorAnchorInfo for Android 5.0 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase and handle FocusedNodeChanged for performance optimization Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/base/ime/android/cursor_anchor_info_controller.cc
diff --git a/ui/base/ime/android/cursor_anchor_info_controller.cc b/ui/base/ime/android/cursor_anchor_info_controller.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ca4488dabace7fad9cab7fe1149ef8b984140718
--- /dev/null
+++ b/ui/base/ime/android/cursor_anchor_info_controller.cc
@@ -0,0 +1,329 @@
+// Copyright (c) 2014 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 "ui/base/ime/android/cursor_anchor_info_controller.h"
+
+#include "base/android/build_info.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "cc/output/compositor_frame_metadata.h"
+#include "cc/output/viewport_selection_bound.h"
+#include "ui/base/ime/android/cursor_anchor_info_builder.h"
+#include "ui/base/ime/android/cursor_anchor_info_sender.h"
+
+namespace ui {
+namespace {
+
+class CursorAnchorInfoControllerImpl final
+ : public CursorAnchorInfoController {
+ public:
+ CursorAnchorInfoControllerImpl(
+ CursorAnchorInfoSender* sender,
+ scoped_ptr<CursorAnchorInfoBuilder> builder);
+
+ ~CursorAnchorInfoControllerImpl() override;
+
+ void OnFocusedNodeChanged(bool is_editable_node) override;
+
+ void OnTextInputStateChanged(
+ const base::string16& text,
+ const gfx::Range& selection_range,
+ const gfx::Range& composition_range) override;
+
+ void OnCompositionRangeChanged(
+ const gfx::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) override;
+
+ void OnFrameMetadataUpdated(
+ const cc::CompositorFrameMetadata& frame_metadata,
+ const gfx::Vector2d& view_offset) override;
+
+ bool OnRequestCursorUpdates(uint32 cursor_update_mode) override;
+
+ private:
+ void UpdateBuilder();
+ void ScheduleUpdate();
+ bool HasPendingUpdate() const;
+ void ClearPendingUpdate();
+ void SetMonitorMode(bool enabled);
+
+ // Doesn't own.
+ CursorAnchorInfoSender* cursor_anchor_info_sender_;
+ scoped_ptr<CursorAnchorInfoBuilder> cursor_anchor_info_builder_;
+
+ bool editable_;
+
+ base::string16 text_;
+ gfx::Range selection_range_;
+ gfx::Range composition_range_;
+ std::vector<gfx::Rect> character_bounds_;
+ gfx::Range character_bounds_range_;
+ bool has_insertion_marker_;
+ bool is_insertion_marker_visible_;
+ gfx::RectF insertion_marker_rect_;
+ bool has_coordinate_info_;
+ float scale_;
+ gfx::Vector2dF translation_;
+
+ bool has_pending_request_;
+ bool monitor_mode_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(CursorAnchorInfoControllerImpl);
+};
+
+CursorAnchorInfoControllerImpl::CursorAnchorInfoControllerImpl(
+ CursorAnchorInfoSender* sender,
+ scoped_ptr<CursorAnchorInfoBuilder> builder)
+ : cursor_anchor_info_sender_(sender),
+ cursor_anchor_info_builder_(builder.Pass()),
+ editable_(false),
+ selection_range_(gfx::Range::InvalidRange()),
+ composition_range_(gfx::Range::InvalidRange()),
+ has_insertion_marker_(false),
+ is_insertion_marker_visible_(false),
+ has_coordinate_info_(false),
+ scale_(1.0f),
+ has_pending_request_(false),
+ monitor_mode_enabled_(false) {
+}
+
+CursorAnchorInfoControllerImpl::~CursorAnchorInfoControllerImpl() {
+}
+
+void CursorAnchorInfoControllerImpl::OnFocusedNodeChanged(
+ bool is_editable_node) {
+ editable_ = is_editable_node;
+ text_.clear();
+ selection_range_ = gfx::Range::InvalidRange();
+ composition_range_ = gfx::Range::InvalidRange();
+ character_bounds_.clear();
+ character_bounds_range_ = gfx::Range::InvalidRange();
+ has_insertion_marker_ = false;
+ is_insertion_marker_visible_= false;
+ insertion_marker_rect_.SetRect(0.0f, 0.0f, 0.0f, 0.0f);
+ has_coordinate_info_ = false;
+ scale_ = 1.0f;
+ translation_ = gfx::Vector2d();
+}
+
+void CursorAnchorInfoControllerImpl::OnTextInputStateChanged(
+ const base::string16& text,
+ const gfx::Range& selection_range,
+ const gfx::Range& composition_range) {
+ if (!editable_)
+ return;
+
+ if (monitor_mode_enabled_) {
+ const bool changed =
+ text_ != text
+ || selection_range_ != selection_range
+ || composition_range_ != composition_range;
+ if (changed)
+ has_pending_request_ = true;
+ }
+
+ text_ = text;
+ selection_range_ = selection_range;
+ composition_range_ = composition_range;
+}
+
+void CursorAnchorInfoControllerImpl::OnCompositionRangeChanged(
+ const gfx::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) {
+ if (!editable_)
+ return;
+
+ if (monitor_mode_enabled_) {
+ const bool changed =
+ character_bounds_ != character_bounds
+ || character_bounds_range_ != range;
+ if (changed)
+ has_pending_request_ = true;
+ }
+
+ character_bounds_ = character_bounds;
+ character_bounds_range_ = range;
+}
+
+void CursorAnchorInfoControllerImpl::OnFrameMetadataUpdated(
+ const cc::CompositorFrameMetadata& frame_metadata,
+ const gfx::Vector2d& view_offset) {
+ if (!editable_)
+ return;
+
+ const auto& selection_start = frame_metadata.selection_start;
+
+ const float scale =
+ frame_metadata.device_scale_factor * frame_metadata.page_scale_factor;
+ const auto translation =
+ gfx::ScaleVector2d(frame_metadata.root_scroll_offset, -scale)
jdduke (slow) 2015/01/12 16:33:44 Hmm, it looks like you're trying to synchronize th
yukawa 2015/01/12 17:08:30 I cannot check it right now as I'm in my home, but
+ + gfx::Vector2dF(view_offset.x(), view_offset.y())
+ + frame_metadata.location_bar_offset
+ + frame_metadata.location_bar_content_translation;
+
+ const bool has_insertion_marker =
+ selection_start.type == cc::SELECTION_BOUND_CENTER;
+ const bool is_insertion_marker_visible = selection_start.visible;
+ const gfx::PointF insertion_marker_origin = has_insertion_marker
+ ? selection_start.edge_top
+ : gfx::PointF(0.0f, 0.0f);
+ const float insertion_marker_height = has_insertion_marker
+ ? selection_start.edge_bottom.y() - selection_start.edge_top.y()
+ : 0.0f;
+ const gfx::SizeF insertion_marker_size(0.0f, insertion_marker_height);
+ const gfx::RectF insertion_marker_rect(insertion_marker_origin,
+ insertion_marker_size);
+ if (monitor_mode_enabled_) {
+ const bool changed =
+ !has_coordinate_info_
+ || scale_ != scale
+ || translation_ != translation
+ || has_insertion_marker_ != has_insertion_marker
+ || is_insertion_marker_visible_ != is_insertion_marker_visible
+ || insertion_marker_rect_ != insertion_marker_rect;
+ if (changed)
+ has_pending_request_ = true;
+ }
+
+ scale_ = scale;
+ translation_ = translation;
+ has_insertion_marker_ = has_insertion_marker;
+ is_insertion_marker_visible_ = is_insertion_marker_visible;
+ insertion_marker_rect_ = insertion_marker_rect;
+ has_coordinate_info_ = true;
+
+ if (cursor_anchor_info_sender_
+ && cursor_anchor_info_builder_
+ && HasPendingUpdate()) {
+ UpdateBuilder();
+ cursor_anchor_info_sender_->SendCursorAnchorInfo(
+ cursor_anchor_info_builder_.get());
+ ClearPendingUpdate();
+ }
+}
+
+bool CursorAnchorInfoControllerImpl::OnRequestCursorUpdates(
+ uint32 cursor_update_mode) {
+ if (!editable_)
+ return false;
+
+ // Should return false if at least one unknown bit is set, because we don't
+ // know what is requested by such a bit.
+ const static uint32 kKnownFlags =
+ kCursorUpdateModeImmediate | kCursorUpdateModeMonitor;
+ if ((cursor_update_mode & ~kKnownFlags) != 0)
+ return false;
+
+ const bool update_immediate = cursor_update_mode & kCursorUpdateModeImmediate;
+ const bool update_monitor = cursor_update_mode & kCursorUpdateModeMonitor;
+
+ SetMonitorMode(update_monitor);
+ if (update_immediate)
+ ScheduleUpdate();
+
+ if (cursor_anchor_info_sender_
+ && cursor_anchor_info_builder_
+ && HasPendingUpdate()) {
+ UpdateBuilder();
+ cursor_anchor_info_sender_->SendCursorAnchorInfo(
+ cursor_anchor_info_builder_.get());
+ ClearPendingUpdate();
+ }
+
+ return true;
+}
+
+void CursorAnchorInfoControllerImpl::ScheduleUpdate() {
+ has_pending_request_ = true;
+}
+
+bool CursorAnchorInfoControllerImpl::HasPendingUpdate() const {
+ return has_pending_request_ && has_coordinate_info_;
+}
+
+void CursorAnchorInfoControllerImpl::ClearPendingUpdate() {
+ has_pending_request_ = false;
+}
+
+void CursorAnchorInfoControllerImpl::SetMonitorMode(bool enabled) {
+ monitor_mode_enabled_ = enabled;
+}
+
+void CursorAnchorInfoControllerImpl::UpdateBuilder() {
+ DCHECK(HasPendingUpdate());
jdduke (slow) 2015/01/14 17:17:09 Is it necessary to blast all values to the builder
yukawa 2015/01/14 22:21:59 Basically we can, but one problem is that the resu
+
+ cursor_anchor_info_builder_->Reset();
+
+ if (selection_range_.IsValid())
+ cursor_anchor_info_builder_->SetSelectionRange(selection_range_);
+
+ if (composition_range_.IsValid()) {
+ const auto composition_text = base::StringPiece16(text_).substr(
+ composition_range_.start(),
+ composition_range_.length());
+ cursor_anchor_info_builder_->SetComposingText(
+ composition_range_.start(),
+ composition_text);
+ }
+ if (character_bounds_range_.IsValid()) {
+ const int index_offset = character_bounds_range_.start();
+ for (size_t i = 0; i < character_bounds_.size(); ++i) {
+ cursor_anchor_info_builder_->AddCharacterBounds(
+ index_offset + i,
+ character_bounds_[i],
+ CursorAnchorInfoBuilder::kFlagHasVisibleRegion);
+ }
+ }
+
+ if (has_insertion_marker_) {
+ const uint32 flags = is_insertion_marker_visible_
+ ? CursorAnchorInfoBuilder::kFlagHasVisibleRegion
+ : CursorAnchorInfoBuilder::kFlagHasInvisibleRegion;
+ cursor_anchor_info_builder_->SetInsertionMarkerLocation(
+ insertion_marker_rect_.x(),
+ insertion_marker_rect_.y(),
+ insertion_marker_rect_.bottom(),
+ insertion_marker_rect_.bottom(),
+ flags);
+ }
+
+ gfx::Matrix3F matrix = gfx::Matrix3F::Zeros();
+ matrix.set(scale_, 0.0f, translation_.x(),
+ 0.0f, scale_, translation_.y(),
+ 0.0f, 0.0f, 1.0f);
+ cursor_anchor_info_builder_->SetMatrix(matrix);
+}
+
+} // namespace
+
+CursorAnchorInfoController::CursorAnchorInfoController() {
+}
+
+CursorAnchorInfoController::~CursorAnchorInfoController() {
+}
+
+// static
+scoped_ptr<CursorAnchorInfoController>
+CursorAnchorInfoController::Create(CursorAnchorInfoSender* sender) {
+ static bool use_cursor_anchor_info =
+ base::android::BuildInfo::GetInstance()->sdk_int() >= 21;
+ if (!use_cursor_anchor_info)
+ return scoped_ptr<ui::CursorAnchorInfoController>(nullptr);
+
+ return scoped_ptr<ui::CursorAnchorInfoController>(
+ new ui::CursorAnchorInfoControllerImpl(
+ sender,
+ CursorAnchorInfoBuilder::Create()));
+}
+
+// static
+scoped_ptr<CursorAnchorInfoController>
+CursorAnchorInfoController::CreateForTest(
+ CursorAnchorInfoSender* sender,
+ scoped_ptr<ui::CursorAnchorInfoBuilder> builder) {
+ return scoped_ptr<CursorAnchorInfoController>(
+ new ui::CursorAnchorInfoControllerImpl(sender, builder.Pass()));
+}
+
+} // namespace ui

Powered by Google App Engine
This is Rietveld 408576698