Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(121)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java

Issue 1589953005: Support InputMethodManager#updateCursorAnchorInfo for Android 5.0 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressed comments in #38 Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2012 The Chromium Authors. All rights reserved. 1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.content.browser.input; 5 package org.chromium.content.browser.input;
6 6
7 import android.content.res.Configuration; 7 import android.content.res.Configuration;
8 import android.os.Build;
8 import android.os.ResultReceiver; 9 import android.os.ResultReceiver;
9 import android.os.SystemClock; 10 import android.os.SystemClock;
10 import android.text.SpannableString; 11 import android.text.SpannableString;
11 import android.text.TextUtils; 12 import android.text.TextUtils;
12 import android.text.style.BackgroundColorSpan; 13 import android.text.style.BackgroundColorSpan;
13 import android.text.style.CharacterStyle; 14 import android.text.style.CharacterStyle;
14 import android.text.style.UnderlineSpan; 15 import android.text.style.UnderlineSpan;
15 import android.view.KeyCharacterMap; 16 import android.view.KeyCharacterMap;
16 import android.view.KeyEvent; 17 import android.view.KeyEvent;
17 import android.view.View; 18 import android.view.View;
18 import android.view.inputmethod.BaseInputConnection; 19 import android.view.inputmethod.BaseInputConnection;
19 import android.view.inputmethod.EditorInfo; 20 import android.view.inputmethod.EditorInfo;
20 21
21 import org.chromium.base.Log; 22 import org.chromium.base.Log;
22 import org.chromium.base.VisibleForTesting; 23 import org.chromium.base.VisibleForTesting;
23 import org.chromium.base.annotations.CalledByNative; 24 import org.chromium.base.annotations.CalledByNative;
24 import org.chromium.base.annotations.JNINamespace; 25 import org.chromium.base.annotations.JNINamespace;
25 import org.chromium.blink_public.web.WebInputEventModifier; 26 import org.chromium.blink_public.web.WebInputEventModifier;
26 import org.chromium.blink_public.web.WebInputEventType; 27 import org.chromium.blink_public.web.WebInputEventType;
28 import org.chromium.content.browser.RenderCoordinates;
27 import org.chromium.ui.base.ime.TextInputType; 29 import org.chromium.ui.base.ime.TextInputType;
28 import org.chromium.ui.picker.InputDialogContainer; 30 import org.chromium.ui.picker.InputDialogContainer;
29 31
30 /** 32 /**
31 * Adapts and plumbs android IME service onto the chrome text input API. 33 * Adapts and plumbs android IME service onto the chrome text input API.
32 * ImeAdapter provides an interface in both ways native <-> java: 34 * ImeAdapter provides an interface in both ways native <-> java:
33 * 1. InputConnectionAdapter notifies native code of text composition state and 35 * 1. InputConnectionAdapter notifies native code of text composition state and
34 * dispatch key events from java -> WebKit. 36 * dispatch key events from java -> WebKit.
35 * 2. Native ImeAdapter notifies java side to clear composition text. 37 * 2. Native ImeAdapter notifies java side to clear composition text.
36 * 38 *
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
87 89
88 static char[] sSingleCharArray = new char[1]; 90 static char[] sSingleCharArray = new char[1];
89 static KeyCharacterMap sKeyCharacterMap; 91 static KeyCharacterMap sKeyCharacterMap;
90 92
91 private long mNativeImeAdapterAndroid; 93 private long mNativeImeAdapterAndroid;
92 private InputMethodManagerWrapper mInputMethodManagerWrapper; 94 private InputMethodManagerWrapper mInputMethodManagerWrapper;
93 private ChromiumBaseInputConnection mInputConnection; 95 private ChromiumBaseInputConnection mInputConnection;
94 private ChromiumBaseInputConnection.Factory mInputConnectionFactory; 96 private ChromiumBaseInputConnection.Factory mInputConnectionFactory;
95 97
96 private final ImeAdapterDelegate mViewEmbedder; 98 private final ImeAdapterDelegate mViewEmbedder;
99 // This holds the information necessary for constructing CursorAnchorInfo, a nd notifies to
100 // InputMethodManager on appropriate timing, depending on how IME requested the information
101 // via InputConnection. The update request is per InputConnection, hence for each time it is
102 // re-created, the monitoring status will be reset.
103 private final CursorAnchorInfoController mCursorAnchorInfoController;
97 104
98 private int mTextInputType = TextInputType.NONE; 105 private int mTextInputType = TextInputType.NONE;
99 private int mTextInputFlags; 106 private int mTextInputFlags;
100 107
101 // Keep the current configuration to detect the change when onConfigurationC hanged() is called. 108 // Keep the current configuration to detect the change when onConfigurationC hanged() is called.
102 private Configuration mCurrentConfig; 109 private Configuration mCurrentConfig;
103 110
104 private int mLastSelectionStart; 111 private int mLastSelectionStart;
105 private int mLastSelectionEnd; 112 private int mLastSelectionEnd;
113 private String mLastText;
114 private int mLastCompositionStart;
115 private int mLastCompositionEnd;
106 116
107 /** 117 /**
108 * @param wrapper InputMethodManagerWrapper that should receive all the call directed to 118 * @param wrapper InputMethodManagerWrapper that should receive all the call directed to
109 * InputMethodManager. 119 * InputMethodManager.
110 * @param embedder The view that is used for callbacks from ImeAdapter. 120 * @param embedder The view that is used for callbacks from ImeAdapter.
111 */ 121 */
112 public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embe dder) { 122 public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embe dder) {
113 mInputMethodManagerWrapper = wrapper; 123 mInputMethodManagerWrapper = wrapper;
114 mViewEmbedder = embedder; 124 mViewEmbedder = embedder;
115 resetInputConnectionFactory(); 125 resetInputConnectionFactory();
116 // Deep copy newConfig so that we can notice the difference. 126 // Deep copy newConfig so that we can notice the difference.
117 mCurrentConfig = new Configuration( 127 mCurrentConfig = new Configuration(
118 mViewEmbedder.getAttachedView().getResources().getConfiguration( )); 128 mViewEmbedder.getAttachedView().getResources().getConfiguration( ));
129 // CursorAnchroInfo is supported only after L.
130 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
131 mCursorAnchorInfoController = CursorAnchorInfoController.create(wrap per,
132 new CursorAnchorInfoController.ComposingTextDelegate() {
133 @Override
134 public CharSequence getText() {
135 return mLastText;
136 }
137 @Override
138 public int getSelectionStart() {
139 return mLastSelectionStart;
140 }
141 @Override
142 public int getSelectionEnd() {
143 return mLastSelectionEnd;
144 }
145 @Override
146 public int getComposingTextStart() {
147 return mLastCompositionStart;
148 }
149 @Override
150 public int getComposingTextEnd() {
151 return mLastCompositionEnd;
152 }
153 });
154 } else {
155 mCursorAnchorInfoController = null;
156 }
119 } 157 }
120 158
121 private boolean isImeThreadEnabled() { 159 private boolean isImeThreadEnabled() {
122 if (mNativeImeAdapterAndroid == 0) return false; 160 if (mNativeImeAdapterAndroid == 0) return false;
123 return nativeIsImeThreadEnabled(mNativeImeAdapterAndroid); 161 return nativeIsImeThreadEnabled(mNativeImeAdapterAndroid);
124 } 162 }
125 163
126 private void resetInputConnectionFactory() { 164 private void resetInputConnectionFactory() {
127 if (isImeThreadEnabled()) { 165 if (isImeThreadEnabled()) {
128 mInputConnectionFactory = 166 mInputConnectionFactory =
(...skipping 19 matching lines...) Expand all
148 // Even when InputConnection methods are blocked IMM can still call this. 186 // Even when InputConnection methods are blocked IMM can still call this.
149 if (mInputConnection != null) mInputConnection.unblockOnUiThread(); 187 if (mInputConnection != null) mInputConnection.unblockOnUiThread();
150 mInputConnection = null; 188 mInputConnection = null;
151 if (DEBUG_LOGS) Log.w(TAG, "onCreateInputConnection returns null."); 189 if (DEBUG_LOGS) Log.w(TAG, "onCreateInputConnection returns null.");
152 return null; 190 return null;
153 } 191 }
154 mInputConnection = mInputConnectionFactory.initializeAndGet( 192 mInputConnection = mInputConnectionFactory.initializeAndGet(
155 mViewEmbedder.getAttachedView(), this, mTextInputType, mTextInpu tFlags, 193 mViewEmbedder.getAttachedView(), this, mTextInputType, mTextInpu tFlags,
156 mLastSelectionStart, mLastSelectionEnd, outAttrs); 194 mLastSelectionStart, mLastSelectionEnd, outAttrs);
157 if (DEBUG_LOGS) Log.w(TAG, "onCreateInputConnection: " + mInputConnectio n); 195 if (DEBUG_LOGS) Log.w(TAG, "onCreateInputConnection: " + mInputConnectio n);
196 if (mCursorAnchorInfoController != null) {
197 mCursorAnchorInfoController.resetMonitoringState();
198 }
158 return mInputConnection; 199 return mInputConnection;
159 } 200 }
160 201
161 /** 202 /**
162 * Overrides the InputMethodManagerWrapper that ImeAdapter uses to make call s to 203 * Overrides the InputMethodManagerWrapper that ImeAdapter uses to make call s to
163 * InputMethodManager. 204 * InputMethodManager.
164 * @param immw InputMethodManagerWrapper that should be used to call InputMe thodManager. 205 * @param immw InputMethodManagerWrapper that should be used to call InputMe thodManager.
165 */ 206 */
166 @VisibleForTesting 207 @VisibleForTesting
167 public void setInputMethodManagerWrapperForTest(InputMethodManagerWrapper im mw) { 208 public void setInputMethodManagerWrapperForTest(InputMethodManagerWrapper im mw) {
168 mInputMethodManagerWrapper = immw; 209 mInputMethodManagerWrapper = immw;
210 if (mCursorAnchorInfoController != null) {
211 mCursorAnchorInfoController.setInputMethodManagerWrapperForTest(immw );
212 }
169 } 213 }
170 214
171 @VisibleForTesting 215 @VisibleForTesting
172 void setInputConnectionFactory(ChromiumBaseInputConnection.Factory factory) { 216 void setInputConnectionFactory(ChromiumBaseInputConnection.Factory factory) {
173 mInputConnectionFactory = factory; 217 mInputConnectionFactory = factory;
174 } 218 }
175 219
176 @VisibleForTesting 220 @VisibleForTesting
177 ChromiumBaseInputConnection.Factory getInputConnectionFactoryForTest() { 221 ChromiumBaseInputConnection.Factory getInputConnectionFactoryForTest() {
178 return mInputConnectionFactory; 222 return mInputConnectionFactory;
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
244 * @param selectionEnd The character offset of the selection end, or the car et position if there 288 * @param selectionEnd The character offset of the selection end, or the car et position if there
245 * is no selection. 289 * is no selection.
246 * @param compositionStart The character offset of the composition start, or -1 if there is no 290 * @param compositionStart The character offset of the composition start, or -1 if there is no
247 * composition. 291 * composition.
248 * @param compositionEnd The character offset of the composition end, or -1 if there is no 292 * @param compositionEnd The character offset of the composition end, or -1 if there is no
249 * selection. 293 * selection.
250 * @param isNonImeChange True when the update was caused by non-IME (e.g. Ja vascript). 294 * @param isNonImeChange True when the update was caused by non-IME (e.g. Ja vascript).
251 */ 295 */
252 public void updateState(String text, int selectionStart, int selectionEnd, i nt compositionStart, 296 public void updateState(String text, int selectionStart, int selectionEnd, i nt compositionStart,
253 int compositionEnd, boolean isNonImeChange) { 297 int compositionEnd, boolean isNonImeChange) {
298 if (mCursorAnchorInfoController != null && (!TextUtils.equals(mLastText, text)
299 || mLastSelectionStart != selectionStart || mLastSelectionEnd != selectionEnd
300 || mLastCompositionStart != compositionStart
301 || mLastCompositionEnd != compositionEnd)) {
302 mCursorAnchorInfoController.invalidateLastCursorAnchorInfo();
303 }
304 mLastText = text;
254 mLastSelectionStart = selectionStart; 305 mLastSelectionStart = selectionStart;
255 mLastSelectionEnd = selectionEnd; 306 mLastSelectionEnd = selectionEnd;
307 mLastCompositionStart = compositionStart;
308 mLastCompositionEnd = compositionEnd;
309
256 if (mInputConnection == null) return; 310 if (mInputConnection == null) return;
257 boolean singleLine = mTextInputType != TextInputType.TEXT_AREA 311 boolean singleLine = mTextInputType != TextInputType.TEXT_AREA
258 && mTextInputType != TextInputType.CONTENT_EDITABLE; 312 && mTextInputType != TextInputType.CONTENT_EDITABLE;
259 mInputConnection.updateStateOnUiThread(text, selectionStart, selectionEn d, compositionStart, 313 mInputConnection.updateStateOnUiThread(text, selectionStart, selectionEn d, compositionStart,
260 compositionEnd, singleLine, isNonImeChange); 314 compositionEnd, singleLine, isNonImeChange);
261 } 315 }
262 316
263 /** 317 /**
264 * Attaches the imeAdapter to its native counterpart. This is needed to star t forwarding 318 * Attaches the imeAdapter to its native counterpart. This is needed to star t forwarding
265 * keyboard events to WebKit. 319 * keyboard events to WebKit.
(...skipping 276 matching lines...) Expand 10 before | Expand all | Expand 10 after
542 */ 596 */
543 boolean setComposingRegion(int start, int end) { 597 boolean setComposingRegion(int start, int end) {
544 if (mNativeImeAdapterAndroid == 0) return false; 598 if (mNativeImeAdapterAndroid == 0) return false;
545 nativeSetComposingRegion(mNativeImeAdapterAndroid, start, end); 599 nativeSetComposingRegion(mNativeImeAdapterAndroid, start, end);
546 return true; 600 return true;
547 } 601 }
548 602
549 @CalledByNative 603 @CalledByNative
550 private void focusedNodeChanged(boolean isEditable) { 604 private void focusedNodeChanged(boolean isEditable) {
551 if (DEBUG_LOGS) Log.w(TAG, "focusedNodeChanged: isEditable [%b]", isEdit able); 605 if (DEBUG_LOGS) Log.w(TAG, "focusedNodeChanged: isEditable [%b]", isEdit able);
606
607 // Update controller before the connection is restarted.
608 if (mCursorAnchorInfoController != null) {
609 mCursorAnchorInfoController.focusedNodeChanged(isEditable);
610 }
611
552 if (mTextInputType != TextInputType.NONE && mInputConnection != null && isEditable) { 612 if (mTextInputType != TextInputType.NONE && mInputConnection != null && isEditable) {
553 restartInput(); 613 restartInput();
554 } 614 }
555 } 615 }
556 616
557 /** 617 /**
558 * Send a request to the native counterpart to give the latest text input st ate update. 618 * Send a request to the native counterpart to give the latest text input st ate update.
559 */ 619 */
560 boolean requestTextInputStateUpdate() { 620 boolean requestTextInputStateUpdate() {
561 if (mNativeImeAdapterAndroid == 0) return false; 621 if (mNativeImeAdapterAndroid == 0) return false;
562 // You won't get state update anyways. 622 // You won't get state update anyways.
563 if (mInputConnection == null) return false; 623 if (mInputConnection == null) return false;
564 return nativeRequestTextInputStateUpdate(mNativeImeAdapterAndroid); 624 return nativeRequestTextInputStateUpdate(mNativeImeAdapterAndroid);
565 } 625 }
566 626
627 /**
628 * Notified when IME requested Chrome to change the cursor update mode.
629 */
630 public boolean onRequestCursorUpdates(int cursorUpdateMode) {
631 if (mCursorAnchorInfoController == null) return false;
632 return mCursorAnchorInfoController.onRequestCursorUpdates(cursorUpdateMo de,
633 mViewEmbedder.getAttachedView());
634 }
635
636 /**
637 * Notified when a frame has been produced by the renderer and all the assoc iated metadata.
638 * @param renderCoordinates coordinate information to convert CSS (document) coordinates to
639 * View-local Physical (screen) coordinates
640 * @param hasInsertionMarker Whether the insertion marker is visible or not.
641 * @param insertionMarkerHorizontal X coordinates (in view-local DIP pixels) of the insertion
642 * marker if it exists. Will be ignored oth erwise.
643 * @param insertionMarkerTop Y coordinates (in view-local DIP pixels) of the top of the
644 * insertion marker if it exists. Will be ignored otherwise.
645 * @param insertionMarkerBottom Y coordinates (in view-local DIP pixels) of the bottom of
646 * the insertion marker if it exists. Will be i gnored otherwise.
647 */
648 public void onUpdateFrameInfo(RenderCoordinates renderCoordinates, boolean h asInsertionMarker,
649 boolean isInsertionMarkerVisible, float insertionMarkerHorizontal,
650 float insertionMarkerTop, float insertionMarkerBottom) {
651 if (mCursorAnchorInfoController == null) return;
652 mCursorAnchorInfoController.onUpdateFrameInfo(renderCoordinates, hasInse rtionMarker,
653 isInsertionMarkerVisible, insertionMarkerHorizontal, insertionMa rkerTop,
654 insertionMarkerBottom, mViewEmbedder.getAttachedView());
655 }
656
567 @CalledByNative 657 @CalledByNative
568 private void populateUnderlinesFromSpans(CharSequence text, long underlines) { 658 private void populateUnderlinesFromSpans(CharSequence text, long underlines) {
569 if (DEBUG_LOGS) { 659 if (DEBUG_LOGS) {
570 Log.w(TAG, "populateUnderlinesFromSpans: text [%s], underlines [%d]" , text, underlines); 660 Log.w(TAG, "populateUnderlinesFromSpans: text [%s], underlines [%d]" , text, underlines);
571 } 661 }
572 if (!(text instanceof SpannableString)) return; 662 if (!(text instanceof SpannableString)) return;
573 663
574 SpannableString spannableString = ((SpannableString) text); 664 SpannableString spannableString = ((SpannableString) text);
575 CharacterStyle spans[] = 665 CharacterStyle spans[] =
576 spannableString.getSpans(0, text.length(), CharacterStyle.class) ; 666 spannableString.getSpans(0, text.length(), CharacterStyle.class) ;
577 for (CharacterStyle span : spans) { 667 for (CharacterStyle span : spans) {
578 if (span instanceof BackgroundColorSpan) { 668 if (span instanceof BackgroundColorSpan) {
579 nativeAppendBackgroundColorSpan(underlines, spannableString.getS panStart(span), 669 nativeAppendBackgroundColorSpan(underlines, spannableString.getS panStart(span),
580 spannableString.getSpanEnd(span), 670 spannableString.getSpanEnd(span),
581 ((BackgroundColorSpan) span).getBackgroundColor()); 671 ((BackgroundColorSpan) span).getBackgroundColor());
582 } else if (span instanceof UnderlineSpan) { 672 } else if (span instanceof UnderlineSpan) {
583 nativeAppendUnderlineSpan(underlines, spannableString.getSpanSta rt(span), 673 nativeAppendUnderlineSpan(underlines, spannableString.getSpanSta rt(span),
584 spannableString.getSpanEnd(span)); 674 spannableString.getSpanEnd(span));
585 } 675 }
586 } 676 }
587 } 677 }
588 678
589 @CalledByNative 679 @CalledByNative
590 private void cancelComposition() { 680 private void cancelComposition() {
591 if (DEBUG_LOGS) Log.w(TAG, "cancelComposition"); 681 if (DEBUG_LOGS) Log.w(TAG, "cancelComposition");
592 if (mInputConnection != null) restartInput(); 682 if (mInputConnection != null) restartInput();
593 } 683 }
594 684
595 @CalledByNative 685 @CalledByNative
686 private void setCharacterBounds(float[] characterBounds) {
687 if (mCursorAnchorInfoController == null) return;
688 mCursorAnchorInfoController.setCompositionCharacterBounds(characterBound s);
689 }
690
691 @CalledByNative
596 private void detach() { 692 private void detach() {
597 if (DEBUG_LOGS) Log.w(TAG, "detach"); 693 if (DEBUG_LOGS) Log.w(TAG, "detach");
598 mNativeImeAdapterAndroid = 0; 694 mNativeImeAdapterAndroid = 0;
695 if (mCursorAnchorInfoController != null) {
696 mCursorAnchorInfoController.focusedNodeChanged(false);
697 }
599 } 698 }
600 699
601 private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndr oid, 700 private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndr oid,
602 int eventType, long timestampMs, int keyCode, int modifiers, int uni codeChar); 701 int eventType, long timestampMs, int keyCode, int modifiers, int uni codeChar);
603 private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyE vent event, 702 private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyE vent event,
604 int action, int modifiers, long timestampMs, int keyCode, int scanCo de, 703 int action, int modifiers, long timestampMs, int keyCode, int scanCo de,
605 boolean isSystemKey, int unicodeChar); 704 boolean isSystemKey, int unicodeChar);
606 private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end); 705 private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end);
607 private static native void nativeAppendBackgroundColorSpan(long underlinePtr , int start, 706 private static native void nativeAppendBackgroundColorSpan(long underlinePtr , int start,
608 int end, int backgroundColor); 707 int end, int backgroundColor);
609 private native void nativeSetComposingText(long nativeImeAdapterAndroid, Cha rSequence text, 708 private native void nativeSetComposingText(long nativeImeAdapterAndroid, Cha rSequence text,
610 String textStr, int newCursorPosition); 709 String textStr, int newCursorPosition);
611 private native void nativeCommitText(long nativeImeAdapterAndroid, String te xtStr); 710 private native void nativeCommitText(long nativeImeAdapterAndroid, String te xtStr);
612 private native void nativeFinishComposingText(long nativeImeAdapterAndroid); 711 private native void nativeFinishComposingText(long nativeImeAdapterAndroid);
613 private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid); 712 private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid);
614 private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterA ndroid, 713 private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterA ndroid,
615 int start, int end); 714 int start, int end);
616 private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, i nt start, int end); 715 private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, i nt start, int end);
617 private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid , 716 private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid ,
618 int before, int after); 717 int before, int after);
619 private native void nativeResetImeAdapter(long nativeImeAdapterAndroid); 718 private native void nativeResetImeAdapter(long nativeImeAdapterAndroid);
620 private native boolean nativeRequestTextInputStateUpdate(long nativeImeAdapt erAndroid); 719 private native boolean nativeRequestTextInputStateUpdate(long nativeImeAdapt erAndroid);
621 private native boolean nativeIsImeThreadEnabled(long nativeImeAdapterAndroid ); 720 private native boolean nativeIsImeThreadEnabled(long nativeImeAdapterAndroid );
622 } 721 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698