Commit 2a54625e authored by Jing Wang's avatar Jing Wang Committed by Commit Bot

Implement keep-typing feature for personal info suggester.

keep-typing feature: when the suggestion pops up and the user keeps
typing, if what they type is a prefix of the suggestion, the popup will
not disappear, and in the suggestion text the typed prefix will be
displayed in black and bold. The popup will disappear anyway if the user
has typed more than 10 characters.

Test: tested with Linux emulator
Bug: 1042084
Change-Id: I157cd92cf29bb308e09764109d2eb0333183af7a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2153181
Commit-Queue: Jing Wang <jiwan@chromium.org>
Reviewed-by: default avatarKeith Lee <keithlee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#760375}
parent a5637c5c
......@@ -61,8 +61,6 @@ bool AssistiveSuggester::OnKeyEvent(
if (context_id_ == -1)
return false;
// If the user pressed Tab after we show suggestion, we adopt the suggestion,
// otherwise we dismiss it.
// We only track keydown event because the suggesting action is triggered by
// surrounding text change, which is triggered by a keydown event. As a
// result, the next key event after suggesting would be a keyup event of the
......@@ -76,7 +74,7 @@ bool AssistiveSuggester::OnKeyEvent(
return true;
case SuggestionStatus::kDismiss:
current_suggester_ = nullptr;
return false;
return true;
case SuggestionStatus::kBrowsing:
return true;
default:
......@@ -108,37 +106,40 @@ bool AssistiveSuggester::OnSurroundingTextChanged(const base::string16& text,
if (context_id_ == -1)
return false;
if (IsSuggestionShown()) {
if (!Suggest(text, cursor_pos, anchor_pos)) {
DismissSuggestion();
}
Suggest(text, cursor_pos, anchor_pos);
return IsSuggestionShown();
}
void AssistiveSuggester::Suggest(const base::string16& text,
bool AssistiveSuggester::Suggest(const base::string16& text,
int cursor_pos,
int anchor_pos) {
int len = static_cast<int>(text.length());
if (cursor_pos > 0 && cursor_pos <= len &&
base::IsAsciiWhitespace(text[cursor_pos - 1]) &&
cursor_pos == anchor_pos &&
(cursor_pos == len || base::IsAsciiWhitespace(text[cursor_pos]))) {
if (cursor_pos > 0 && cursor_pos <= len && cursor_pos == anchor_pos &&
(cursor_pos == len || base::IsAsciiWhitespace(text[cursor_pos])) &&
(base::IsAsciiWhitespace(text[cursor_pos - 1]) || IsSuggestionShown())) {
// |text| could be very long, we get at most |kMaxTextBeforeCursorLength|
// characters before cursor.
int start_pos = std::max(0, cursor_pos - kMaxTextBeforeCursorLength);
base::string16 text_before_cursor =
text.substr(start_pos, cursor_pos - start_pos);
if (IsSuggestionShown()) {
return current_suggester_->Suggest(text_before_cursor);
}
if (IsAssistPersonalInfoEnabled() &&
personal_info_suggester_.Suggest(text_before_cursor)) {
current_suggester_ = &personal_info_suggester_;
return true;
} else if (IsEmojiSuggestAdditionEnabled() &&
emoji_suggester_.Suggest(text_before_cursor)) {
current_suggester_ = &emoji_suggester_;
RecordAssistiveCoverage(current_suggester_->GetProposeActionType());
} else {
current_suggester_ = nullptr;
return true;
}
}
return false;
}
void AssistiveSuggester::DismissSuggestion() {
......
......@@ -50,9 +50,9 @@ class AssistiveSuggester {
const ::input_method::InputMethodEngineBase::KeyboardEvent& event);
private:
// Check if any suggestion text should be displayed according to the
// Returns if any suggestion text should be displayed according to the
// surrounding text information.
void Suggest(const base::string16& text, int cursor_pos, int anchor_pos);
bool Suggest(const base::string16& text, int cursor_pos, int anchor_pos);
void DismissSuggestion();
......
......@@ -78,6 +78,7 @@ void AssistiveWindowControllerImpl::ShowSuggestion(
if (!suggestion_window_view_)
Init();
suggestion_text_ = text;
confirmed_length_ = confirmed_length;
suggestion_window_view_->Show(text, confirmed_length, show_tab);
}
......@@ -85,5 +86,9 @@ base::string16 AssistiveWindowControllerImpl::GetSuggestionText() const {
return suggestion_text_;
}
size_t AssistiveWindowControllerImpl::GetConfirmedLength() const {
return confirmed_length_;
}
} // namespace input_method
} // namespace chromeos
......@@ -37,6 +37,7 @@ class AssistiveWindowControllerImpl
const bool show_tab) override;
void HideSuggestion() override;
base::string16 GetSuggestionText() const override;
size_t GetConfirmedLength() const override;
void FocusStateChanged() override;
void OnWidgetClosing(views::Widget* widget) override;
......@@ -44,6 +45,7 @@ class AssistiveWindowControllerImpl
ui::ime::SuggestionWindowView* suggestion_window_view_ = nullptr;
base::string16 suggestion_text_;
size_t confirmed_length_;
DISALLOW_COPY_AND_ASSIGN(AssistiveWindowControllerImpl);
};
......
......@@ -127,7 +127,7 @@ SuggestionStatus EmojiSuggester::HandleKeyEvent(
: candidate_id_ = static_cast<int>(candidates_.size()) - 1;
engine_->SetCursorPosition(context_id_, candidate_id_, &error);
status = SuggestionStatus::kBrowsing;
} else {
} else if (event.key == "Esc") {
DismissSuggestion();
suggestion_shown_ = false;
status = SuggestionStatus::kDismiss;
......@@ -139,18 +139,19 @@ SuggestionStatus EmojiSuggester::HandleKeyEvent(
}
bool EmojiSuggester::Suggest(const base::string16& text) {
if (emoji_map_.empty())
if (emoji_map_.empty() || !base::IsAsciiWhitespace(text[text.length() - 1]))
return false;
std::string last_word = GetLastWord(base::UTF16ToUTF8(text));
if (!last_word.empty() && emoji_map_.count(last_word)) {
ShowSuggestion(last_word);
suggestion_shown_ = true;
return true;
}
return suggestion_shown_;
return false;
}
void EmojiSuggester::ShowSuggestion(const std::string& text) {
std::string error;
suggestion_shown_ = true;
candidates_.clear();
const std::vector<std::string>& candidates = emoji_map_.at(text);
for (size_t i = 0; i < candidates.size(); i++) {
......@@ -173,6 +174,7 @@ void EmojiSuggester::ShowSuggestion(const std::string& text) {
void EmojiSuggester::DismissSuggestion() {
std::string error;
suggestion_shown_ = false;
engine_->SetCandidateWindowVisible(false, &error);
if (!error.empty()) {
LOG(ERROR) << "Failed to dismiss suggestion. " << error;
......
......@@ -270,6 +270,11 @@ bool InputMethodEngine::AcceptSuggestion(int context_id, std::string* error) {
return false;
}
FinishComposingText(context_id_, error);
if (!error->empty()) {
return false;
}
IMEAssistiveWindowHandlerInterface* aw_handler =
ui::IMEBridge::Get()->GetAssistiveWindowHandler();
if (aw_handler) {
......@@ -278,6 +283,11 @@ bool InputMethodEngine::AcceptSuggestion(int context_id, std::string* error) {
*error = kSuggestionNotFound;
return false;
}
size_t confirmed_length = aw_handler->GetConfirmedLength();
if (confirmed_length > 0) {
DeleteSurroundingText(context_id_, -confirmed_length, confirmed_length,
error);
}
CommitText(context_id_, (base::UTF16ToUTF8(suggestion_text)).c_str(),
error);
aw_handler->HideSuggestion();
......
......@@ -16,6 +16,7 @@ namespace chromeos {
namespace {
const size_t kMaxConfirmedTextLength = 10;
const char kAssistEmailPrefix[] = "my email is ";
const char kAssistNamePrefix[] = "my name is ";
const char kAssistAddressPrefix[] = "my address is ";
......@@ -64,62 +65,86 @@ void PersonalInfoSuggester::OnBlur() {
SuggestionStatus PersonalInfoSuggester::HandleKeyEvent(
const InputMethodEngineBase::KeyboardEvent& event) {
if (suggestion_shown_) {
suggestion_shown_ = false;
if (event.key == "Tab" || event.key == "Right") {
AcceptSuggestion();
return SuggestionStatus::kAccept;
} else if (event.key == "Esc") {
DismissSuggestion();
return SuggestionStatus::kDismiss;
}
DismissSuggestion();
return SuggestionStatus::kDismiss;
}
return SuggestionStatus::kNotHandled;
}
bool PersonalInfoSuggester::Suggest(const base::string16& text) {
if (suggestion_shown_) {
size_t text_length = text.length();
bool matched = false;
for (size_t offset = 0;
offset < suggestion_.length() && offset < text_length &&
offset < kMaxConfirmedTextLength;
offset++) {
base::string16 text_before = text.substr(0, text_length - offset);
base::string16 confirmed_text = text.substr(text_length - offset);
if (base::StartsWith(suggestion_, confirmed_text,
base::CompareCase::INSENSITIVE_ASCII) &&
suggestion_ == GetSuggestion(text_before)) {
matched = true;
ShowSuggestion(suggestion_, offset);
break;
}
}
return matched;
} else {
suggestion_ = GetSuggestion(text);
if (!suggestion_.empty())
ShowSuggestion(suggestion_, 0);
return suggestion_shown_;
}
}
base::string16 PersonalInfoSuggester::GetSuggestion(
const base::string16& text) {
proposed_action_type_ = ProposeAssistiveAction(text);
if (proposed_action_type_ == AssistiveType::kGenericAction)
return false;
return base::EmptyString16();
if (proposed_action_type_ == AssistiveType::kPersonalEmail)
return base::UTF8ToUTF16(profile_->GetProfileUserName());
auto autofill_profiles = personal_data_manager_->GetProfilesToSuggest();
if (autofill_profiles.empty())
return base::EmptyString16();
// Currently, we are just picking the first candidate, will improve the
// strategy in the future.
auto* profile = autofill_profiles[0];
base::string16 suggestion;
if (proposed_action_type_ == AssistiveType::kPersonalEmail) {
suggestion = base::UTF8ToUTF16(profile_->GetProfileUserName());
} else {
auto autofill_profiles = personal_data_manager_->GetProfilesToSuggest();
if (autofill_profiles.empty())
return false;
// Currently, we are just picking the first candidate, will improve the
// strategy in the future.
auto* profile = autofill_profiles[0];
switch (proposed_action_type_) {
case AssistiveType::kPersonalName:
suggestion = profile->GetRawInfo(autofill::ServerFieldType::NAME_FULL);
break;
case AssistiveType::kPersonalAddress:
suggestion = profile->GetRawInfo(
autofill::ServerFieldType::ADDRESS_HOME_STREET_ADDRESS);
break;
case AssistiveType::kPersonalPhoneNumber:
suggestion = profile->GetRawInfo(
autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER);
break;
default:
NOTREACHED();
break;
}
switch (proposed_action_type_) {
case AssistiveType::kPersonalName:
suggestion = profile->GetRawInfo(autofill::ServerFieldType::NAME_FULL);
break;
case AssistiveType::kPersonalAddress:
suggestion = profile->GetRawInfo(
autofill::ServerFieldType::ADDRESS_HOME_STREET_ADDRESS);
break;
case AssistiveType::kPersonalPhoneNumber:
suggestion = profile->GetRawInfo(
autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER);
break;
default:
NOTREACHED();
break;
}
if (suggestion.empty())
return false;
ShowSuggestion(suggestion);
suggestion_shown_ = true;
return true;
return suggestion;
}
void PersonalInfoSuggester::ShowSuggestion(const base::string16& text) {
void PersonalInfoSuggester::ShowSuggestion(const base::string16& text,
const size_t confirmed_length) {
std::string error;
engine_->SetSuggestion(context_id_, text, 0, true, &error);
suggestion_shown_ = true;
engine_->SetSuggestion(context_id_, text, confirmed_length, true, &error);
if (!error.empty()) {
LOG(ERROR) << "Fail to show suggestion. " << error;
}
......@@ -131,6 +156,7 @@ AssistiveType PersonalInfoSuggester::GetProposeActionType() {
void PersonalInfoSuggester::AcceptSuggestion() {
std::string error;
suggestion_shown_ = false;
engine_->AcceptSuggestion(context_id_, &error);
if (!error.empty()) {
LOG(ERROR) << "Failed to accept suggestion. " << error;
......@@ -139,6 +165,7 @@ void PersonalInfoSuggester::AcceptSuggestion() {
void PersonalInfoSuggester::DismissSuggestion() {
std::string error;
suggestion_shown_ = false;
engine_->DismissSuggestion(context_id_, &error);
if (!error.empty()) {
LOG(ERROR) << "Failed to dismiss suggestion. " << error;
......
......@@ -40,11 +40,11 @@ class PersonalInfoSuggester : public Suggester {
AssistiveType GetProposeActionType() override;
private:
// Get the suggestion according to |text_before_cursor|.
base::string16 GetPersonalInfoSuggestion(
const base::string16& text_before_cursor);
// Get the suggestion according to |text|.
base::string16 GetSuggestion(const base::string16& text);
void ShowSuggestion(const base::string16& text);
void ShowSuggestion(const base::string16& text,
const size_t confirmed_length);
void AcceptSuggestion();
......@@ -64,6 +64,9 @@ class PersonalInfoSuggester : public Suggester {
// If we are showing a suggestion right now.
bool suggestion_shown_ = false;
// The current suggestion text shown.
base::string16 suggestion_;
};
} // namespace chromeos
......
......@@ -30,6 +30,9 @@ class COMPONENT_EXPORT(UI_BASE_IME) IMEAssistiveWindowHandlerInterface {
// Called to get the current suggestion text.
virtual base::string16 GetSuggestionText() const = 0;
// Called to get length of the confirmed part of suggestion text.
virtual size_t GetConfirmedLength() const = 0;
// Called when the application changes its caret bounds.
virtual void SetBounds(const gfx::Rect& cursor_bounds) = 0;
......
......@@ -70,7 +70,9 @@ void SuggestionView::SetView(const base::string16& text,
void SuggestionView::SetSuggestionText(const base::string16& text,
const size_t confirmed_length) {
// SetText clears the existing style.
// SetText clears the existing style only if the text to set is different from
// the previous one.
suggestion_label_->SetText(base::EmptyString16());
suggestion_label_->SetText(text);
if (confirmed_length != 0) {
views::StyledLabel::RangeStyleInfo confirmed_style;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment