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. 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 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|. 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|. 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 134 ImeAdapterAndroid::ImeAdapterAndroid(RenderWidgetHostViewAndroid* rwhva) 135 : rwhva_(rwhva) { 136 } 137 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 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 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 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 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 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 238 void ImeAdapterAndroid::AttachImeAdapter(JNIEnv* env, jobject java_object) { 239 java_ime_adapter_ = JavaObjectWeakGlobalRef(env, java_object); 240 } 241 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 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 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 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 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 291 void ImeAdapterAndroid::Unselect(JNIEnv* env, jobject) { 292 WebContents* wc = GetWebContents(); 293 if (wc) 294 wc->Unselect(); 295 } 296 297 void ImeAdapterAndroid::SelectAll(JNIEnv* env, jobject) { 298 WebContents* wc = GetWebContents(); 299 if (wc) 300 wc->SelectAll(); 301 } 302 303 void ImeAdapterAndroid::Cut(JNIEnv* env, jobject) { 304 WebContents* wc = GetWebContents(); 305 if (wc) 306 wc->Cut(); 307 } 308 309 void ImeAdapterAndroid::Copy(JNIEnv* env, jobject) { 310 WebContents* wc = GetWebContents(); 311 if (wc) 312 wc->Copy(); 313 } 314 315 void ImeAdapterAndroid::Paste(JNIEnv* env, jobject) { 316 WebContents* wc = GetWebContents(); 317 if (wc) 318 wc->Paste(); 319 } 320 321 void ImeAdapterAndroid::ResetImeAdapter(JNIEnv* env, jobject) { 322 java_ime_adapter_.reset(); 323 } 324 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 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 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