EMMA Coverage Report (generated Fri Aug 23 16:39:17 PDT 2013)
[all classes][org.chromium.content.browser.input]

COVERAGE SUMMARY FOR SOURCE FILE [AdapterInputConnection.java]

nameclass, %method, %block, %line, %
AdapterInputConnection.java100% (1/1)75%  (15/20)64%  (399/627)64%  (95.8/150)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class AdapterInputConnection100% (1/1)75%  (15/20)64%  (399/627)64%  (95.8/150)
deleteSurroundingText (int, int): boolean 0%   (0/1)0%   (0/13)0%   (0/3)
getExtractedText (ExtractedTextRequest, int): ExtractedText 0%   (0/1)0%   (0/33)0%   (0/8)
isActive (): boolean 0%   (0/1)0%   (0/6)0%   (0/1)
performContextMenuAction (int): boolean 0%   (0/1)0%   (0/20)0%   (0/6)
sendKeyEvent (KeyEvent): boolean 0%   (0/1)0%   (0/62)0%   (0/17)
performEditorAction (int): boolean 100% (1/1)43%  (10/23)43%  (3/7)
AdapterInputConnection (View, ImeAdapter, EditorInfo): void 100% (1/1)67%  (109/162)74%  (28/38)
setComposingRegion (int, int): boolean 100% (1/1)76%  (26/34)79%  (6.3/8)
updateSelection (int, int, int, int): void 100% (1/1)77%  (30/39)81%  (6.5/8)
setSelection (int, int): boolean 100% (1/1)88%  (15/17)89%  (2.7/3)
endBatchEdit (): boolean 100% (1/1)90%  (18/20)90%  (3.6/4)
setEditableText (String, int, int, int, int): void 100% (1/1)94%  (89/95)95%  (21.8/23)
commitText (CharSequence, int): boolean 100% (1/1)94%  (17/18)97%  (1.9/2)
beginBatchEdit (): boolean 100% (1/1)100% (15/15)100% (3/3)
finishComposingText (): boolean 100% (1/1)100% (18/18)100% (6/6)
getInputMethodManagerWrapper (): InputMethodManagerWrapper 100% (1/1)100% (4/4)100% (1/1)
isIgnoringTextInputStateUpdates (): boolean 100% (1/1)100% (3/3)100% (1/1)
restartInput (): void 100% (1/1)100% (12/12)100% (4/4)
setComposingText (CharSequence, int): boolean 100% (1/1)100% (13/13)100% (2/2)
setIgnoreTextInputStateUpdates (boolean): void 100% (1/1)100% (20/20)100% (5/5)

1// Copyright (c) 2013 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 
5package org.chromium.content.browser.input;
6 
7import com.google.common.annotations.VisibleForTesting;
8 
9import android.text.Editable;
10import android.text.InputType;
11import android.text.Selection;
12import android.util.Log;
13import android.view.KeyEvent;
14import android.view.View;
15import android.view.inputmethod.BaseInputConnection;
16import android.view.inputmethod.EditorInfo;
17import android.view.inputmethod.ExtractedText;
18import android.view.inputmethod.ExtractedTextRequest;
19 
20/**
21 * InputConnection is created by ContentView.onCreateInputConnection.
22 * It then adapts android's IME to chrome's RenderWidgetHostView using the
23 * native ImeAdapterAndroid via the class ImeAdapter.
24 */
25public class AdapterInputConnection extends BaseInputConnection {
26    private static final String TAG =
27            "org.chromium.content.browser.input.AdapterInputConnection";
28    private static final boolean DEBUG = false;
29    /**
30     * Selection value should be -1 if not known. See EditorInfo.java for details.
31     */
32    public static final int INVALID_SELECTION = -1;
33    public static final int INVALID_COMPOSITION = -1;
34 
35    private final View mInternalView;
36    private final ImeAdapter mImeAdapter;
37 
38    private boolean mSingleLine;
39    private int mNumNestedBatchEdits = 0;
40    private boolean mIgnoreTextInputStateUpdates = false;
41 
42    private int mLastUpdateSelectionStart = INVALID_SELECTION;
43    private int mLastUpdateSelectionEnd = INVALID_SELECTION;
44    private int mLastUpdateCompositionStart = INVALID_COMPOSITION;
45    private int mLastUpdateCompositionEnd = INVALID_COMPOSITION;
46 
47    @VisibleForTesting
48    AdapterInputConnection(View view, ImeAdapter imeAdapter, EditorInfo outAttrs) {
49        super(view, true);
50        mInternalView = view;
51        mImeAdapter = imeAdapter;
52        mImeAdapter.setInputConnection(this);
53        mSingleLine = true;
54        outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN
55                | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
56        outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
57                | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
58 
59        if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeText) {
60            // Normal text field
61            outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
62        } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeTextArea ||
63                imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeContentEditable) {
64            // TextArea or contenteditable.
65            outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
66                    | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
67                    | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
68            outAttrs.imeOptions |= EditorInfo.IME_ACTION_NONE;
69            mSingleLine = false;
70        } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypePassword) {
71            // Password
72            outAttrs.inputType = InputType.TYPE_CLASS_TEXT
73                    | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
74            outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
75        } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeSearch) {
76            // Search
77            outAttrs.imeOptions |= EditorInfo.IME_ACTION_SEARCH;
78        } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeUrl) {
79            // Url
80            // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so
81            // exclude it for now.
82            outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
83        } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeEmail) {
84            // Email
85            outAttrs.inputType = InputType.TYPE_CLASS_TEXT
86                    | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
87            outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
88        } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeTel) {
89            // Telephone
90            // Number and telephone do not have both a Tab key and an
91            // action in default OSK, so set the action to NEXT
92            outAttrs.inputType = InputType.TYPE_CLASS_PHONE;
93            outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
94        } else if (imeAdapter.getTextInputType() == ImeAdapter.sTextInputTypeNumber) {
95            // Number
96            outAttrs.inputType = InputType.TYPE_CLASS_NUMBER
97                    | InputType.TYPE_NUMBER_VARIATION_NORMAL;
98            outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
99        }
100        outAttrs.initialSelStart = imeAdapter.getInitialSelectionStart();
101        outAttrs.initialSelEnd = imeAdapter.getInitialSelectionStart();
102    }
103 
104    /**
105     * Updates the AdapterInputConnection's internal representation of the text
106     * being edited and its selection and composition properties. The resulting
107     * Editable is accessible through the getEditable() method.
108     * If the text has not changed, this also calls updateSelection on the InputMethodManager.
109     * @param text The String contents of the field being edited
110     * @param selectionStart The character offset of the selection start, or the caret
111     * position if there is no selection
112     * @param selectionEnd The character offset of the selection end, or the caret
113     * position if there is no selection
114     * @param compositionStart The character offset of the composition start, or -1
115     * if there is no composition
116     * @param compositionEnd The character offset of the composition end, or -1
117     * if there is no selection
118     */
119    public void setEditableText(String text, int selectionStart, int selectionEnd,
120            int compositionStart, int compositionEnd) {
121        if (DEBUG) {
122            Log.w(TAG, "setEditableText [" + text + "] [" + selectionStart + " " + selectionEnd
123                    + "] [" + compositionStart + " " + compositionEnd + "]");
124        }
125        // Non-breaking spaces can cause the IME to get confused. Replace with normal spaces.
126        text = text.replace('\u00A0', ' ');
127 
128        selectionStart = Math.min(selectionStart, text.length());
129        selectionEnd = Math.min(selectionEnd, text.length());
130        compositionStart = Math.min(compositionStart, text.length());
131        compositionEnd = Math.min(compositionEnd, text.length());
132 
133        Editable editable = getEditable();
134        String prevText = editable.toString();
135        boolean textUnchanged = prevText.equals(text);
136 
137        if (!textUnchanged) {
138            editable.replace(0, editable.length(), text);
139        }
140 
141        int prevSelectionStart = Selection.getSelectionStart(editable);
142        int prevSelectionEnd = Selection.getSelectionEnd(editable);
143        int prevCompositionStart = getComposingSpanStart(editable);
144        int prevCompositionEnd = getComposingSpanEnd(editable);
145 
146        if (prevSelectionStart == selectionStart && prevSelectionEnd == selectionEnd
147                && prevCompositionStart == compositionStart
148                && prevCompositionEnd == compositionEnd) {
149            // Nothing has changed; don't need to do anything
150            return;
151        }
152 
153        Selection.setSelection(editable, selectionStart, selectionEnd);
154 
155        if (compositionStart == compositionEnd) {
156            removeComposingSpans(editable);
157        } else {
158            super.setComposingRegion(compositionStart, compositionEnd);
159        }
160 
161        if (mIgnoreTextInputStateUpdates) return;
162        updateSelection(selectionStart, selectionEnd, compositionStart, compositionEnd);
163    }
164 
165    @VisibleForTesting
166    protected void updateSelection(
167            int selectionStart, int selectionEnd,
168            int compositionStart, int compositionEnd) {
169        // Avoid sending update if we sent an exact update already previously.
170        if (mLastUpdateSelectionStart == selectionStart &&
171                mLastUpdateSelectionEnd == selectionEnd &&
172                mLastUpdateCompositionStart == compositionStart &&
173                mLastUpdateCompositionEnd == compositionEnd) {
174            return;
175        }
176        if (DEBUG) {
177            Log.w(TAG, "updateSelection [" + selectionStart + " " + selectionEnd + "] ["
178                    + compositionStart + " " + compositionEnd + "]");
179        }
180        // updateSelection should be called every time the selection or composition changes
181        // if it happens not within a batch edit, or at the end of each top level batch edit.
182        getInputMethodManagerWrapper().updateSelection(mInternalView,
183                selectionStart, selectionEnd, compositionStart, compositionEnd);
184        mLastUpdateSelectionStart = selectionStart;
185        mLastUpdateSelectionEnd = selectionEnd;
186        mLastUpdateCompositionStart = compositionStart;
187        mLastUpdateCompositionEnd = compositionEnd;
188    }
189 
190    /**
191     * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int)
192     */
193    @Override
194    public boolean setComposingText(CharSequence text, int newCursorPosition) {
195        if (DEBUG) Log.w(TAG, "setComposingText [" + text + "] [" + newCursorPosition + "]");
196        super.setComposingText(text, newCursorPosition);
197        return mImeAdapter.checkCompositionQueueAndCallNative(text.toString(),
198                newCursorPosition, false);
199    }
200 
201    /**
202     * @see BaseInputConnection#commitText(java.lang.CharSequence, int)
203     */
204    @Override
205    public boolean commitText(CharSequence text, int newCursorPosition) {
206        if (DEBUG) Log.w(TAG, "commitText [" + text + "] [" + newCursorPosition + "]");
207        super.commitText(text, newCursorPosition);
208        return mImeAdapter.checkCompositionQueueAndCallNative(text.toString(),
209                newCursorPosition, text.length() > 0);
210    }
211 
212    /**
213     * @see BaseInputConnection#performEditorAction(int)
214     */
215    @Override
216    public boolean performEditorAction(int actionCode) {
217        if (DEBUG) Log.w(TAG, "performEditorAction [" + actionCode + "]");
218        if (actionCode == EditorInfo.IME_ACTION_NEXT) {
219            restartInput();
220            // Send TAB key event
221            long timeStampMs = System.currentTimeMillis();
222            mImeAdapter.sendSyntheticKeyEvent(
223                    ImeAdapter.sEventTypeRawKeyDown, timeStampMs, KeyEvent.KEYCODE_TAB, 0);
224        } else {
225            mImeAdapter.sendKeyEventWithKeyCode(KeyEvent.KEYCODE_ENTER,
226                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
227                    | KeyEvent.FLAG_EDITOR_ACTION);
228        }
229        return true;
230    }
231 
232    /**
233     * @see BaseInputConnection#performContextMenuAction(int)
234     */
235    @Override
236    public boolean performContextMenuAction(int id) {
237        if (DEBUG) Log.w(TAG, "performContextMenuAction [" + id + "]");
238        switch (id) {
239            case android.R.id.selectAll:
240                return mImeAdapter.selectAll();
241            case android.R.id.cut:
242                return mImeAdapter.cut();
243            case android.R.id.copy:
244                return mImeAdapter.copy();
245            case android.R.id.paste:
246                return mImeAdapter.paste();
247            default:
248                return false;
249        }
250    }
251 
252    /**
253     * @see BaseInputConnection#getExtractedText(android.view.inputmethod.ExtractedTextRequest,
254     *                                           int)
255     */
256    @Override
257    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
258        if (DEBUG) Log.w(TAG, "getExtractedText");
259        ExtractedText et = new ExtractedText();
260        Editable editable = getEditable();
261        et.text = editable.toString();
262        et.partialEndOffset = editable.length();
263        et.selectionStart = Selection.getSelectionStart(editable);
264        et.selectionEnd = Selection.getSelectionEnd(editable);
265        et.flags = mSingleLine ? ExtractedText.FLAG_SINGLE_LINE : 0;
266        return et;
267    }
268 
269    /**
270     * @see BaseInputConnection#beginBatchEdit()
271     */
272    @Override
273    public boolean beginBatchEdit() {
274        if (DEBUG) Log.w(TAG, "beginBatchEdit [" + (mNumNestedBatchEdits == 0) + "]");
275        if (mNumNestedBatchEdits == 0) mImeAdapter.batchStateChanged(true);
276 
277        mNumNestedBatchEdits++;
278        return false;
279    }
280 
281    /**
282     * @see BaseInputConnection#endBatchEdit()
283     */
284    @Override
285    public boolean endBatchEdit() {
286        if (mNumNestedBatchEdits == 0) return false;
287 
288        --mNumNestedBatchEdits;
289        if (DEBUG) Log.w(TAG, "endBatchEdit [" + (mNumNestedBatchEdits == 0) + "]");
290        if (mNumNestedBatchEdits == 0) mImeAdapter.batchStateChanged(false);
291        return false;
292    }
293 
294    /**
295     * @see BaseInputConnection#deleteSurroundingText(int, int)
296     */
297    @Override
298    public boolean deleteSurroundingText(int leftLength, int rightLength) {
299        if (DEBUG) {
300            Log.w(TAG, "deleteSurroundingText [" + leftLength + " " + rightLength + "]");
301        }
302        if (!super.deleteSurroundingText(leftLength, rightLength)) {
303            return false;
304        }
305        return mImeAdapter.deleteSurroundingText(leftLength, rightLength);
306    }
307 
308    /**
309     * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent)
310     */
311    @Override
312    public boolean sendKeyEvent(KeyEvent event) {
313        if (DEBUG) Log.w(TAG, "sendKeyEvent [" + event.getAction() + "]");
314 
315        // If this is a key-up, and backspace/del or if the key has a character representation,
316        // need to update the underlying Editable (i.e. the local representation of the text
317        // being edited).
318        if (event.getAction() == KeyEvent.ACTION_UP) {
319            if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
320                super.deleteSurroundingText(1, 0);
321            } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) {
322                super.deleteSurroundingText(0, 1);
323            } else {
324                int unicodeChar = event.getUnicodeChar();
325                if (unicodeChar != 0) {
326                    Editable editable = getEditable();
327                    int selectionStart = Selection.getSelectionStart(editable);
328                    int selectionEnd = Selection.getSelectionEnd(editable);
329                    if (selectionStart > selectionEnd) {
330                        int temp = selectionStart;
331                        selectionStart = selectionEnd;
332                        selectionEnd = temp;
333                    }
334                    editable.replace(selectionStart, selectionEnd,
335                            Character.toString((char)unicodeChar));
336                }
337            }
338        }
339        mImeAdapter.translateAndSendNativeEvents(event);
340        return true;
341    }
342 
343    /**
344     * @see BaseInputConnection#finishComposingText()
345     */
346    @Override
347    public boolean finishComposingText() {
348        if (DEBUG) Log.w(TAG, "finishComposingText");
349        Editable editable = getEditable();
350        if (getComposingSpanStart(editable) == getComposingSpanEnd(editable)) {
351            return true;
352        }
353 
354        super.finishComposingText();
355        mImeAdapter.finishComposingText();
356 
357        return true;
358    }
359 
360    /**
361     * @see BaseInputConnection#setSelection(int, int)
362     */
363    @Override
364    public boolean setSelection(int start, int end) {
365        if (DEBUG) Log.w(TAG, "setSelection");
366        if (start < 0 || end < 0) return true;
367        super.setSelection(start, end);
368        return mImeAdapter.setEditableSelectionOffsets(start, end);
369    }
370 
371    /**
372     * Informs the InputMethodManager and InputMethodSession (i.e. the IME) that the text
373     * state is no longer what the IME has and that it needs to be updated.
374     */
375    void restartInput() {
376        if (DEBUG) Log.w(TAG, "restartInput");
377        getInputMethodManagerWrapper().restartInput(mInternalView);
378        mIgnoreTextInputStateUpdates = false;
379        mNumNestedBatchEdits = 0;
380    }
381 
382    /**
383     * @see BaseInputConnection#setComposingRegion(int, int)
384     */
385    @Override
386    public boolean setComposingRegion(int start, int end) {
387        if (DEBUG) Log.w(TAG, "setComposingRegion [" + start + " " + end + "]");
388        int a = Math.min(start, end);
389        int b = Math.max(start, end);
390        if (a < 0) a = 0;
391        if (b < 0) b = 0;
392 
393        if (a == b) {
394            removeComposingSpans(getEditable());
395        } else {
396            super.setComposingRegion(a, b);
397        }
398        return mImeAdapter.setComposingRegion(a, b);
399    }
400 
401    boolean isActive() {
402        return getInputMethodManagerWrapper().isActive(mInternalView);
403    }
404 
405    public void setIgnoreTextInputStateUpdates(boolean shouldIgnore) {
406        mIgnoreTextInputStateUpdates = shouldIgnore;
407        if (shouldIgnore) return;
408 
409        Editable editable = getEditable();
410        updateSelection(Selection.getSelectionStart(editable),
411                Selection.getSelectionEnd(editable),
412                getComposingSpanStart(editable),
413                getComposingSpanEnd(editable));
414    }
415 
416    @VisibleForTesting
417    protected boolean isIgnoringTextInputStateUpdates() {
418        return mIgnoreTextInputStateUpdates;
419    }
420 
421    private InputMethodManagerWrapper getInputMethodManagerWrapper() {
422        return mImeAdapter.getInputMethodManagerWrapper();
423    }
424}

[all classes][org.chromium.content.browser.input]
EMMA 2.0.5312 (C) Vladimir Roubtsov