• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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