1 // Copyright (c) 2012 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 "content/browser/renderer_host/ime_adapter_android.h"
6
7 #include <algorithm>
8 #include <android/input.h>
9 #include <vector>
10
11 #include "base/android/jni_android.h"
12 #include "base/android/jni_string.h"
13 #include "base/android/scoped_java_ref.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/time/time.h"
16 #include "content/browser/frame_host/frame_tree.h"
17 #include "content/browser/frame_host/frame_tree_node.h"
18 #include "content/browser/frame_host/render_frame_host_impl.h"
19 #include "content/browser/renderer_host/render_view_host_delegate.h"
20 #include "content/browser/renderer_host/render_view_host_impl.h"
21 #include "content/browser/renderer_host/render_widget_host_impl.h"
22 #include "content/browser/renderer_host/render_widget_host_view_android.h"
23 #include "content/common/frame_messages.h"
24 #include "content/common/view_messages.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/native_web_keyboard_event.h"
27 #include "content/public/browser/web_contents.h"
28 #include "jni/ImeAdapter_jni.h"
29 #include "third_party/WebKit/public/web/WebCompositionUnderline.h"
30 #include "third_party/WebKit/public/web/WebInputEvent.h"
31
32 using base::android::AttachCurrentThread;
33 using base::android::ConvertJavaStringToUTF16;
34
35 namespace content {
36 namespace {
37
38 // Maps a java KeyEvent into a NativeWebKeyboardEvent.
39 // |java_key_event| is used to maintain a globalref for KeyEvent.
40 // |action| will help determine the WebInputEvent type.
41 // type, |modifiers|, |time_ms|, |key_code|, |unicode_char| is used to create
42 // WebKeyboardEvent. |key_code| is also needed ad need to treat the enter key
43 // as a key press of character \r.
NativeWebKeyboardEventFromKeyEvent(JNIEnv * env,jobject java_key_event,int action,int modifiers,long time_ms,int key_code,bool is_system_key,int unicode_char)44 NativeWebKeyboardEvent NativeWebKeyboardEventFromKeyEvent(
45 JNIEnv* env,
46 jobject java_key_event,
47 int action,
48 int modifiers,
49 long time_ms,
50 int key_code,
51 bool is_system_key,
52 int unicode_char) {
53 blink::WebInputEvent::Type type = blink::WebInputEvent::Undefined;
54 if (action == AKEY_EVENT_ACTION_DOWN)
55 type = blink::WebInputEvent::RawKeyDown;
56 else if (action == AKEY_EVENT_ACTION_UP)
57 type = blink::WebInputEvent::KeyUp;
58 return NativeWebKeyboardEvent(java_key_event, type, modifiers,
59 time_ms / 1000.0, key_code, unicode_char, is_system_key);
60 }
61
62 } // anonymous namespace
63
RegisterImeAdapter(JNIEnv * env)64 bool RegisterImeAdapter(JNIEnv* env) {
65 if (!RegisterNativesImpl(env))
66 return false;
67
68 Java_ImeAdapter_initializeWebInputEvents(env,
69 blink::WebInputEvent::RawKeyDown,
70 blink::WebInputEvent::KeyUp,
71 blink::WebInputEvent::Char,
72 blink::WebInputEvent::ShiftKey,
73 blink::WebInputEvent::AltKey,
74 blink::WebInputEvent::ControlKey,
75 blink::WebInputEvent::CapsLockOn,
76 blink::WebInputEvent::NumLockOn);
77 Java_ImeAdapter_initializeTextInputTypes(
78 env,
79 ui::TEXT_INPUT_TYPE_NONE,
80 ui::TEXT_INPUT_TYPE_TEXT,
81 ui::TEXT_INPUT_TYPE_TEXT_AREA,
82 ui::TEXT_INPUT_TYPE_PASSWORD,
83 ui::TEXT_INPUT_TYPE_SEARCH,
84 ui::TEXT_INPUT_TYPE_URL,
85 ui::TEXT_INPUT_TYPE_EMAIL,
86 ui::TEXT_INPUT_TYPE_TELEPHONE,
87 ui::TEXT_INPUT_TYPE_NUMBER,
88 ui::TEXT_INPUT_TYPE_CONTENT_EDITABLE);
89 return true;
90 }
91
92 // Callback from Java to convert BackgroundColorSpan data to a
93 // blink::WebCompositionUnderline instance, and append it to |underlines_ptr|.
AppendBackgroundColorSpan(JNIEnv *,jclass,jlong underlines_ptr,jint start,jint end,jint background_color)94 void AppendBackgroundColorSpan(JNIEnv*,
95 jclass,
96 jlong underlines_ptr,
97 jint start,
98 jint end,
99 jint background_color) {
100 DCHECK(start >= 0);
101 DCHECK(end >= 0);
102 // Do not check |background_color|.
103 std::vector<blink::WebCompositionUnderline>* underlines =
104 reinterpret_cast<std::vector<blink::WebCompositionUnderline>*>(
105 underlines_ptr);
106 underlines->push_back(
107 blink::WebCompositionUnderline(static_cast<unsigned>(start),
108 static_cast<unsigned>(end),
109 SK_ColorTRANSPARENT,
110 false,
111 static_cast<unsigned>(background_color)));
112 }
113
114 // Callback from Java to convert UnderlineSpan data to a
115 // blink::WebCompositionUnderline instance, and append it to |underlines_ptr|.
AppendUnderlineSpan(JNIEnv *,jclass,jlong underlines_ptr,jint start,jint end)116 void AppendUnderlineSpan(JNIEnv*,
117 jclass,
118 jlong underlines_ptr,
119 jint start,
120 jint end) {
121 DCHECK(start >= 0);
122 DCHECK(end >= 0);
123 std::vector<blink::WebCompositionUnderline>* underlines =
124 reinterpret_cast<std::vector<blink::WebCompositionUnderline>*>(
125 underlines_ptr);
126 underlines->push_back(
127 blink::WebCompositionUnderline(static_cast<unsigned>(start),
128 static_cast<unsigned>(end),
129 SK_ColorBLACK,
130 false,
131 SK_ColorTRANSPARENT));
132 }
133
ImeAdapterAndroid(RenderWidgetHostViewAndroid * rwhva)134 ImeAdapterAndroid::ImeAdapterAndroid(RenderWidgetHostViewAndroid* rwhva)
135 : rwhva_(rwhva) {
136 }
137
~ImeAdapterAndroid()138 ImeAdapterAndroid::~ImeAdapterAndroid() {
139 JNIEnv* env = AttachCurrentThread();
140 base::android::ScopedJavaLocalRef<jobject> obj = java_ime_adapter_.get(env);
141 if (!obj.is_null())
142 Java_ImeAdapter_detach(env, obj.obj());
143 }
144
SendSyntheticKeyEvent(JNIEnv *,jobject,int type,long time_ms,int key_code,int text)145 bool ImeAdapterAndroid::SendSyntheticKeyEvent(JNIEnv*,
146 jobject,
147 int type,
148 long time_ms,
149 int key_code,
150 int text) {
151 NativeWebKeyboardEvent event(static_cast<blink::WebInputEvent::Type>(type),
152 0 /* modifiers */, time_ms / 1000.0, key_code,
153 text, false /* is_system_key */);
154 rwhva_->SendKeyEvent(event);
155 return true;
156 }
157
SendKeyEvent(JNIEnv * env,jobject,jobject original_key_event,int action,int modifiers,long time_ms,int key_code,bool is_system_key,int unicode_char)158 bool ImeAdapterAndroid::SendKeyEvent(JNIEnv* env, jobject,
159 jobject original_key_event,
160 int action, int modifiers,
161 long time_ms, int key_code,
162 bool is_system_key, int unicode_char) {
163 NativeWebKeyboardEvent event = NativeWebKeyboardEventFromKeyEvent(
164 env, original_key_event, action, modifiers,
165 time_ms, key_code, is_system_key, unicode_char);
166 bool key_down_text_insertion =
167 event.type == blink::WebInputEvent::RawKeyDown && event.text[0];
168 // If we are going to follow up with a synthetic Char event, then that's the
169 // one we expect to test if it's handled or unhandled, so skip handling the
170 // "real" event in the browser.
171 event.skip_in_browser = key_down_text_insertion;
172 rwhva_->SendKeyEvent(event);
173 if (key_down_text_insertion) {
174 // Send a Char event, but without an os_event since we don't want to
175 // roundtrip back to java such synthetic event.
176 NativeWebKeyboardEvent char_event(blink::WebInputEvent::Char, modifiers,
177 time_ms / 1000.0, key_code, unicode_char,
178 is_system_key);
179 char_event.skip_in_browser = key_down_text_insertion;
180 rwhva_->SendKeyEvent(char_event);
181 }
182 return true;
183 }
184
SetComposingText(JNIEnv * env,jobject obj,jobject text,jstring text_str,int new_cursor_pos)185 void ImeAdapterAndroid::SetComposingText(JNIEnv* env,
186 jobject obj,
187 jobject text,
188 jstring text_str,
189 int new_cursor_pos) {
190 RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
191 if (!rwhi)
192 return;
193
194 base::string16 text16 = ConvertJavaStringToUTF16(env, text_str);
195
196 std::vector<blink::WebCompositionUnderline> underlines;
197 // Iterate over spans in |text|, dispatch those that we care about (e.g.,
198 // BackgroundColorSpan) to a matching callback (e.g.,
199 // AppendBackgroundColorSpan()), and populate |underlines|.
200 Java_ImeAdapter_populateUnderlinesFromSpans(
201 env, obj, text, reinterpret_cast<jlong>(&underlines));
202
203 // Default to plain underline if we didn't find any span that we care about.
204 if (underlines.empty()) {
205 underlines.push_back(blink::WebCompositionUnderline(
206 0, text16.length(), SK_ColorBLACK, false, SK_ColorTRANSPARENT));
207 }
208 // Sort spans by |.startOffset|.
209 std::sort(underlines.begin(), underlines.end());
210
211 // new_cursor_position is as described in the Android API for
212 // InputConnection#setComposingText, whereas the parameters for
213 // ImeSetComposition are relative to the start of the composition.
214 if (new_cursor_pos > 0)
215 new_cursor_pos = text16.length() + new_cursor_pos - 1;
216
217 rwhi->ImeSetComposition(text16, underlines, new_cursor_pos, new_cursor_pos);
218 }
219
CommitText(JNIEnv * env,jobject,jstring text_str)220 void ImeAdapterAndroid::CommitText(JNIEnv* env, jobject, jstring text_str) {
221 RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
222 if (!rwhi)
223 return;
224
225 base::string16 text16 = ConvertJavaStringToUTF16(env, text_str);
226 rwhi->ImeConfirmComposition(text16, gfx::Range::InvalidRange(), false);
227 }
228
FinishComposingText(JNIEnv * env,jobject)229 void ImeAdapterAndroid::FinishComposingText(JNIEnv* env, jobject) {
230 RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
231 if (!rwhi)
232 return;
233
234 rwhi->ImeConfirmComposition(base::string16(), gfx::Range::InvalidRange(),
235 true);
236 }
237
AttachImeAdapter(JNIEnv * env,jobject java_object)238 void ImeAdapterAndroid::AttachImeAdapter(JNIEnv* env, jobject java_object) {
239 java_ime_adapter_ = JavaObjectWeakGlobalRef(env, java_object);
240 }
241
CancelComposition()242 void ImeAdapterAndroid::CancelComposition() {
243 base::android::ScopedJavaLocalRef<jobject> obj =
244 java_ime_adapter_.get(AttachCurrentThread());
245 if (!obj.is_null())
246 Java_ImeAdapter_cancelComposition(AttachCurrentThread(), obj.obj());
247 }
248
FocusedNodeChanged(bool is_editable_node)249 void ImeAdapterAndroid::FocusedNodeChanged(bool is_editable_node) {
250 base::android::ScopedJavaLocalRef<jobject> obj =
251 java_ime_adapter_.get(AttachCurrentThread());
252 if (!obj.is_null()) {
253 Java_ImeAdapter_focusedNodeChanged(AttachCurrentThread(),
254 obj.obj(),
255 is_editable_node);
256 }
257 }
258
SetEditableSelectionOffsets(JNIEnv *,jobject,int start,int end)259 void ImeAdapterAndroid::SetEditableSelectionOffsets(JNIEnv*, jobject,
260 int start, int end) {
261 RenderFrameHost* rfh = GetFocusedFrame();
262 if (!rfh)
263 return;
264
265 rfh->Send(new FrameMsg_SetEditableSelectionOffsets(rfh->GetRoutingID(),
266 start, end));
267 }
268
SetComposingRegion(JNIEnv *,jobject,int start,int end)269 void ImeAdapterAndroid::SetComposingRegion(JNIEnv*, jobject,
270 int start, int end) {
271 RenderFrameHost* rfh = GetFocusedFrame();
272 if (!rfh)
273 return;
274
275 std::vector<blink::WebCompositionUnderline> underlines;
276 underlines.push_back(blink::WebCompositionUnderline(
277 0, end - start, SK_ColorBLACK, false, SK_ColorTRANSPARENT));
278
279 rfh->Send(new FrameMsg_SetCompositionFromExistingText(
280 rfh->GetRoutingID(), start, end, underlines));
281 }
282
DeleteSurroundingText(JNIEnv *,jobject,int before,int after)283 void ImeAdapterAndroid::DeleteSurroundingText(JNIEnv*, jobject,
284 int before, int after) {
285 RenderFrameHostImpl* rfh =
286 static_cast<RenderFrameHostImpl*>(GetFocusedFrame());
287 if (rfh)
288 rfh->ExtendSelectionAndDelete(before, after);
289 }
290
Unselect(JNIEnv * env,jobject)291 void ImeAdapterAndroid::Unselect(JNIEnv* env, jobject) {
292 WebContents* wc = GetWebContents();
293 if (wc)
294 wc->Unselect();
295 }
296
SelectAll(JNIEnv * env,jobject)297 void ImeAdapterAndroid::SelectAll(JNIEnv* env, jobject) {
298 WebContents* wc = GetWebContents();
299 if (wc)
300 wc->SelectAll();
301 }
302
Cut(JNIEnv * env,jobject)303 void ImeAdapterAndroid::Cut(JNIEnv* env, jobject) {
304 WebContents* wc = GetWebContents();
305 if (wc)
306 wc->Cut();
307 }
308
Copy(JNIEnv * env,jobject)309 void ImeAdapterAndroid::Copy(JNIEnv* env, jobject) {
310 WebContents* wc = GetWebContents();
311 if (wc)
312 wc->Copy();
313 }
314
Paste(JNIEnv * env,jobject)315 void ImeAdapterAndroid::Paste(JNIEnv* env, jobject) {
316 WebContents* wc = GetWebContents();
317 if (wc)
318 wc->Paste();
319 }
320
ResetImeAdapter(JNIEnv * env,jobject)321 void ImeAdapterAndroid::ResetImeAdapter(JNIEnv* env, jobject) {
322 java_ime_adapter_.reset();
323 }
324
GetRenderWidgetHostImpl()325 RenderWidgetHostImpl* ImeAdapterAndroid::GetRenderWidgetHostImpl() {
326 DCHECK_CURRENTLY_ON(BrowserThread::UI);
327 DCHECK(rwhva_);
328 RenderWidgetHost* rwh = rwhva_->GetRenderWidgetHost();
329 if (!rwh)
330 return NULL;
331
332 return RenderWidgetHostImpl::From(rwh);
333 }
334
GetFocusedFrame()335 RenderFrameHost* ImeAdapterAndroid::GetFocusedFrame() {
336 RenderWidgetHostImpl* rwh = GetRenderWidgetHostImpl();
337 if (!rwh)
338 return NULL;
339 if (!rwh->IsRenderView())
340 return NULL;
341 RenderViewHost* rvh = RenderViewHost::From(rwh);
342 FrameTreeNode* focused_frame =
343 rvh->GetDelegate()->GetFrameTree()->GetFocusedFrame();
344 if (!focused_frame)
345 return NULL;
346
347 return focused_frame->current_frame_host();
348 }
349
GetWebContents()350 WebContents* ImeAdapterAndroid::GetWebContents() {
351 RenderWidgetHostImpl* rwh = GetRenderWidgetHostImpl();
352 if (!rwh)
353 return NULL;
354 if (!rwh->IsRenderView())
355 return NULL;
356 return WebContents::FromRenderViewHost(RenderViewHost::From(rwh));
357 }
358
359 } // namespace content
360