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