1 // Copyright 2015 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 "components/spellcheck/browser/spellchecker_session_bridge_android.h"
6
7 #include <stddef.h>
8 #include <utility>
9
10 #include "base/android/jni_array.h"
11 #include "base/android/jni_string.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "components/spellcheck/browser/android/jni_headers/SpellCheckerSessionBridge_jni.h"
14 #include "components/spellcheck/common/spellcheck_result.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/render_process_host.h"
17
18 using base::android::JavaParamRef;
19
20 namespace {
21
RecordAvailabilityUMA(bool spellcheck_available)22 void RecordAvailabilityUMA(bool spellcheck_available) {
23 UMA_HISTOGRAM_BOOLEAN("Spellcheck.Android.Available", spellcheck_available);
24 }
25
26 } // namespace
27
SpellCheckerSessionBridge()28 SpellCheckerSessionBridge::SpellCheckerSessionBridge()
29 : java_object_initialization_failed_(false), active_session_(false) {}
30
~SpellCheckerSessionBridge()31 SpellCheckerSessionBridge::~SpellCheckerSessionBridge() {
32 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
33 // Clean-up java side to avoid any stale JNI callbacks.
34 DisconnectSession();
35 }
36
RequestTextCheck(const base::string16 & text,RequestTextCheckCallback callback)37 void SpellCheckerSessionBridge::RequestTextCheck(
38 const base::string16& text,
39 RequestTextCheckCallback callback) {
40 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
41
42 // This allows us to discard |callback| safely in case it's not run due to
43 // failures in initialization of |java_object_|.
44 std::unique_ptr<SpellingRequest> incoming_request =
45 std::make_unique<SpellingRequest>(text, std::move(callback));
46
47 // SpellCheckerSessionBridge#create() will return null if spell checker
48 // service is unavailable.
49 if (java_object_initialization_failed_) {
50 if (!active_session_) {
51 RecordAvailabilityUMA(false);
52 active_session_ = true;
53 }
54 return;
55 }
56
57 // RequestTextCheck API call arrives at the SpellCheckHost before
58 // DisconnectSessionBridge when the user focuses an input field that already
59 // contains completed text. We need to initialize the spellchecker here
60 // rather than in response to DisconnectSessionBridge so that the existing
61 // text will be spellchecked immediately.
62 if (java_object_.is_null()) {
63 java_object_.Reset(Java_SpellCheckerSessionBridge_create(
64 base::android::AttachCurrentThread(),
65 reinterpret_cast<intptr_t>(this)));
66 if (!active_session_) {
67 RecordAvailabilityUMA(!java_object_.is_null());
68 active_session_ = true;
69 }
70 if (java_object_.is_null()) {
71 java_object_initialization_failed_ = true;
72 return;
73 }
74 }
75
76 // Save incoming requests to run at the end of the currently active request.
77 // If multiple requests arrive during one active request, only the most
78 // recent request will run (the others get overwritten).
79 if (active_request_) {
80 pending_request_ = std::move(incoming_request);
81 return;
82 }
83
84 active_request_ = std::move(incoming_request);
85
86 JNIEnv* env = base::android::AttachCurrentThread();
87 Java_SpellCheckerSessionBridge_requestTextCheck(
88 env, java_object_, base::android::ConvertUTF16ToJavaString(env, text));
89 }
90
ProcessSpellCheckResults(JNIEnv * env,const JavaParamRef<jobject> & jobj,const JavaParamRef<jintArray> & offset_array,const JavaParamRef<jintArray> & length_array,const JavaParamRef<jobjectArray> & suggestions_array)91 void SpellCheckerSessionBridge::ProcessSpellCheckResults(
92 JNIEnv* env,
93 const JavaParamRef<jobject>& jobj,
94 const JavaParamRef<jintArray>& offset_array,
95 const JavaParamRef<jintArray>& length_array,
96 const JavaParamRef<jobjectArray>& suggestions_array) {
97 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
98 std::vector<int> offsets;
99 std::vector<int> lengths;
100
101 base::android::JavaIntArrayToIntVector(env, offset_array, &offsets);
102 base::android::JavaIntArrayToIntVector(env, length_array, &lengths);
103
104 std::vector<SpellCheckResult> results;
105 for (size_t i = 0; i < offsets.size(); i++) {
106 base::android::ScopedJavaLocalRef<jobjectArray> suggestions_for_word_array(
107 env, static_cast<jobjectArray>(
108 env->GetObjectArrayElement(suggestions_array, i)));
109 std::vector<base::string16> suggestions_for_word;
110 base::android::AppendJavaStringArrayToStringVector(
111 env, suggestions_for_word_array, &suggestions_for_word);
112 results.push_back(SpellCheckResult(SpellCheckResult::SPELLING, offsets[i],
113 lengths[i], suggestions_for_word));
114 }
115
116 std::move(active_request_->callback_).Run(results);
117
118 active_request_ = std::move(pending_request_);
119 if (active_request_) {
120 JNIEnv* env = base::android::AttachCurrentThread();
121 Java_SpellCheckerSessionBridge_requestTextCheck(
122 env, java_object_,
123 base::android::ConvertUTF16ToJavaString(env, active_request_->text_));
124 }
125 }
126
DisconnectSession()127 void SpellCheckerSessionBridge::DisconnectSession() {
128 // Needs to be executed on the same thread as the RequestTextCheck and
129 // ProcessSpellCheckResults methods, which is the UI thread.
130 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
131
132 active_request_.reset();
133 pending_request_.reset();
134 active_session_ = false;
135
136 if (!java_object_.is_null()) {
137 Java_SpellCheckerSessionBridge_disconnect(
138 base::android::AttachCurrentThread(), java_object_);
139 java_object_.Reset();
140 }
141 }
142
SpellingRequest(const base::string16 & text,RequestTextCheckCallback callback)143 SpellCheckerSessionBridge::SpellingRequest::SpellingRequest(
144 const base::string16& text,
145 RequestTextCheckCallback callback)
146 : text_(text), callback_(std::move(callback)) {}
147
~SpellingRequest()148 SpellCheckerSessionBridge::SpellingRequest::~SpellingRequest() {
149 // Ensure that we don't clear an uncalled RequestTextCheckCallback
150 if (callback_)
151 std::move(callback_).Run(std::vector<SpellCheckResult>());
152 }
153