2011-11-18 10:28:17 -08:00
|
|
|
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
2012-05-21 04:12:37 -07:00
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
2011-11-18 10:28:17 -08:00
|
|
|
|
|
|
|
package org.mozilla.gecko;
|
|
|
|
|
2012-07-27 17:53:54 -07:00
|
|
|
import org.mozilla.gecko.gfx.InputConnectionHandler;
|
|
|
|
import org.mozilla.gecko.gfx.LayerController;
|
|
|
|
|
2012-02-27 16:29:35 -08:00
|
|
|
import android.R;
|
2012-07-19 11:00:07 -07:00
|
|
|
import android.content.Context;
|
2012-06-08 10:57:16 -07:00
|
|
|
import android.os.Build;
|
|
|
|
import android.os.SystemClock;
|
2012-02-27 16:29:35 -08:00
|
|
|
import android.text.Editable;
|
|
|
|
import android.text.InputType;
|
|
|
|
import android.text.Selection;
|
|
|
|
import android.text.Spannable;
|
|
|
|
import android.text.SpannableStringBuilder;
|
|
|
|
import android.text.Spanned;
|
|
|
|
import android.text.TextUtils;
|
|
|
|
import android.text.TextWatcher;
|
|
|
|
import android.text.method.KeyListener;
|
|
|
|
import android.text.method.TextKeyListener;
|
|
|
|
import android.text.style.BackgroundColorSpan;
|
|
|
|
import android.text.style.CharacterStyle;
|
|
|
|
import android.text.style.ForegroundColorSpan;
|
|
|
|
import android.text.style.UnderlineSpan;
|
2012-05-23 18:53:42 -07:00
|
|
|
import android.util.DisplayMetrics;
|
2012-02-27 16:29:35 -08:00
|
|
|
import android.util.Log;
|
|
|
|
import android.util.LogPrinter;
|
2012-04-18 14:57:55 -07:00
|
|
|
import android.view.KeyCharacterMap;
|
2012-02-27 16:29:35 -08:00
|
|
|
import android.view.KeyEvent;
|
|
|
|
import android.view.View;
|
|
|
|
import android.view.inputmethod.BaseInputConnection;
|
|
|
|
import android.view.inputmethod.CompletionInfo;
|
|
|
|
import android.view.inputmethod.EditorInfo;
|
|
|
|
import android.view.inputmethod.ExtractedText;
|
|
|
|
import android.view.inputmethod.ExtractedTextRequest;
|
|
|
|
import android.view.inputmethod.InputConnection;
|
|
|
|
import android.view.inputmethod.InputMethodManager;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-02-27 16:29:35 -08:00
|
|
|
import java.util.Timer;
|
|
|
|
import java.util.TimerTask;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
|
|
|
public class GeckoInputConnection
|
|
|
|
extends BaseInputConnection
|
2012-02-27 16:29:22 -08:00
|
|
|
implements TextWatcher, InputConnectionHandler {
|
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
private static final boolean DEBUG = false;
|
|
|
|
protected static final String LOGTAG = "GeckoInputConnection";
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-02-27 16:29:44 -08:00
|
|
|
// IME stuff
|
|
|
|
public static final int IME_STATE_DISABLED = 0;
|
|
|
|
public static final int IME_STATE_ENABLED = 1;
|
|
|
|
public static final int IME_STATE_PASSWORD = 2;
|
|
|
|
public static final int IME_STATE_PLUGIN = 3;
|
|
|
|
|
|
|
|
private static final int NOTIFY_IME_RESETINPUTSTATE = 0;
|
|
|
|
private static final int NOTIFY_IME_SETOPENSTATE = 1;
|
|
|
|
private static final int NOTIFY_IME_CANCELCOMPOSITION = 2;
|
|
|
|
private static final int NOTIFY_IME_FOCUSCHANGE = 3;
|
|
|
|
|
2012-03-23 10:32:42 -07:00
|
|
|
private static final int NO_COMPOSITION_STRING = -1;
|
|
|
|
|
2012-05-23 18:53:42 -07:00
|
|
|
private static final int INLINE_IME_MIN_DISPLAY_SIZE = 480;
|
|
|
|
|
2012-06-08 10:57:16 -07:00
|
|
|
private static final char UNICODE_BULLET = '\u2022';
|
|
|
|
private static final char UNICODE_CENT_SIGN = '\u00a2';
|
|
|
|
private static final char UNICODE_COPYRIGHT_SIGN = '\u00a9';
|
2012-07-18 15:26:15 -07:00
|
|
|
private static final char UNICODE_CRARR = '\u21b2'; // ↵
|
2012-06-08 10:57:16 -07:00
|
|
|
private static final char UNICODE_DIVISION_SIGN = '\u00f7';
|
|
|
|
private static final char UNICODE_DOUBLE_LOW_QUOTATION_MARK = '\u201e';
|
|
|
|
private static final char UNICODE_ELLIPSIS = '\u2026';
|
|
|
|
private static final char UNICODE_EURO_SIGN = '\u20ac';
|
|
|
|
private static final char UNICODE_INVERTED_EXCLAMATION_MARK = '\u00a1';
|
|
|
|
private static final char UNICODE_MULTIPLICATION_SIGN = '\u00d7';
|
|
|
|
private static final char UNICODE_PI = '\u03a0';
|
|
|
|
private static final char UNICODE_PILCROW_SIGN = '\u00b6';
|
|
|
|
private static final char UNICODE_POUND_SIGN = '\u00a3';
|
|
|
|
private static final char UNICODE_REGISTERED_SIGN = '\u00ae';
|
|
|
|
private static final char UNICODE_SQUARE_ROOT = '\u221a';
|
|
|
|
private static final char UNICODE_TRADEMARK_SIGN = '\u2122';
|
|
|
|
private static final char UNICODE_WHITE_BULLET = '\u25e6';
|
|
|
|
private static final char UNICODE_YEN_SIGN = '\u00a5';
|
|
|
|
|
2012-02-27 16:29:44 -08:00
|
|
|
private static final Timer mIMETimer = new Timer("GeckoInputConnection Timer");
|
|
|
|
private static int mIMEState;
|
|
|
|
private static String mIMETypeHint;
|
|
|
|
private static String mIMEActionHint;
|
|
|
|
|
2012-07-19 11:00:07 -07:00
|
|
|
private String mCurrentInputMethod;
|
|
|
|
|
2012-02-27 16:29:44 -08:00
|
|
|
// Is a composition active?
|
2012-03-23 10:32:42 -07:00
|
|
|
private int mCompositionStart = NO_COMPOSITION_STRING;
|
2012-04-18 14:57:55 -07:00
|
|
|
private boolean mCommittingText;
|
|
|
|
private KeyCharacterMap mKeyCharacterMap;
|
2012-02-27 16:29:44 -08:00
|
|
|
private Editable mEditable;
|
|
|
|
private Editable.Factory mEditableFactory;
|
|
|
|
private boolean mBatchMode;
|
|
|
|
private ExtractedTextRequest mUpdateRequest;
|
|
|
|
private final ExtractedText mUpdateExtract = new ExtractedText();
|
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
public static GeckoInputConnection create(View targetView) {
|
|
|
|
if (DEBUG)
|
|
|
|
return new DebugGeckoInputConnection(targetView);
|
|
|
|
else
|
|
|
|
return new GeckoInputConnection(targetView);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
protected GeckoInputConnection(View targetView) {
|
2011-11-18 10:28:17 -08:00
|
|
|
super(targetView, true);
|
|
|
|
|
|
|
|
mEditableFactory = Editable.Factory.getInstance();
|
|
|
|
initEditable("");
|
|
|
|
mIMEState = IME_STATE_DISABLED;
|
|
|
|
mIMETypeHint = "";
|
|
|
|
mIMEActionHint = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean beginBatchEdit() {
|
|
|
|
mBatchMode = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-12-15 13:35:45 -08:00
|
|
|
public boolean endBatchEdit() {
|
|
|
|
mBatchMode = false;
|
2011-11-18 10:28:17 -08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-12-15 13:35:45 -08:00
|
|
|
public boolean commitCompletion(CompletionInfo text) {
|
|
|
|
return commitText(text.getText(), 1);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2011-12-15 13:35:45 -08:00
|
|
|
public boolean commitText(CharSequence text, int newCursorPosition) {
|
2012-05-16 22:29:23 -07:00
|
|
|
if (mCommittingText)
|
2012-06-29 15:49:48 -07:00
|
|
|
Log.e(LOGTAG, "Please report this bug:",
|
|
|
|
new IllegalStateException("commitText, but already committing text?!"));
|
2012-05-16 22:29:23 -07:00
|
|
|
|
2012-04-18 14:57:55 -07:00
|
|
|
mCommittingText = true;
|
2011-12-15 13:35:45 -08:00
|
|
|
replaceText(text, newCursorPosition, false);
|
2012-04-18 14:57:55 -07:00
|
|
|
mCommittingText = false;
|
2012-01-17 23:17:51 -08:00
|
|
|
|
2012-03-23 10:32:42 -07:00
|
|
|
if (hasCompositionString()) {
|
2012-01-17 23:17:51 -08:00
|
|
|
if (DEBUG) Log.d(LOGTAG, ". . . commitText: endComposition");
|
|
|
|
endComposition();
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean finishComposingText() {
|
2012-05-16 22:29:23 -07:00
|
|
|
// finishComposingText() is sometimes called even when we are not composing text.
|
2012-03-23 10:32:42 -07:00
|
|
|
if (hasCompositionString()) {
|
2012-01-17 23:17:51 -08:00
|
|
|
if (DEBUG) Log.d(LOGTAG, ". . . finishComposingText: endComposition");
|
|
|
|
endComposition();
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
final Editable content = getEditable();
|
|
|
|
if (content != null) {
|
|
|
|
beginBatchEdit();
|
|
|
|
removeComposingSpans(content);
|
|
|
|
endBatchEdit();
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Editable getEditable() {
|
2011-12-15 13:35:45 -08:00
|
|
|
return mEditable;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean performContextMenuAction(int id) {
|
2011-12-15 13:35:45 -08:00
|
|
|
final Editable content = getEditable();
|
|
|
|
if (content == null)
|
2011-11-18 10:28:17 -08:00
|
|
|
return false;
|
2011-12-15 13:35:45 -08:00
|
|
|
|
|
|
|
String text = content.toString();
|
2012-07-17 22:49:25 -07:00
|
|
|
Span selection = getSelection();
|
2011-11-18 10:28:17 -08:00
|
|
|
|
|
|
|
switch (id) {
|
|
|
|
case R.id.selectAll:
|
|
|
|
setSelection(0, text.length());
|
|
|
|
break;
|
|
|
|
case R.id.cut:
|
|
|
|
// Fill the clipboard
|
|
|
|
GeckoAppShell.setClipboardText(text);
|
2011-12-15 13:35:45 -08:00
|
|
|
// If selection is empty, we'll select everything
|
2012-06-19 12:13:41 -07:00
|
|
|
if (selection.length == 0)
|
2011-11-18 10:28:17 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION, 0, text.length()));
|
2011-11-18 10:28:17 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoEvent.createIMEEvent(GeckoEvent.IME_DELETE_TEXT, 0, 0));
|
2011-11-18 10:28:17 -08:00
|
|
|
break;
|
|
|
|
case R.id.paste:
|
|
|
|
commitText(GeckoAppShell.getClipboardText(), 1);
|
|
|
|
break;
|
|
|
|
case R.id.copy:
|
2012-06-19 12:13:41 -07:00
|
|
|
// Copy the current selection or the empty string if nothing is selected.
|
|
|
|
String copiedText = selection.length > 0
|
|
|
|
? text.substring(selection.start, selection.end)
|
|
|
|
: "";
|
|
|
|
GeckoAppShell.setClipboardText(text);
|
2011-11-18 10:28:17 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ExtractedText getExtractedText(ExtractedTextRequest req, int flags) {
|
|
|
|
if (req == null)
|
|
|
|
return null;
|
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
final Editable content = getEditable();
|
|
|
|
if (content == null)
|
2011-11-18 10:28:17 -08:00
|
|
|
return null;
|
|
|
|
|
2012-01-06 18:27:09 -08:00
|
|
|
if ((flags & GET_EXTRACTED_TEXT_MONITOR) != 0)
|
|
|
|
mUpdateRequest = req;
|
|
|
|
|
2012-07-17 22:49:25 -07:00
|
|
|
Span selection = getSelection();
|
2012-06-19 12:13:41 -07:00
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
ExtractedText extract = new ExtractedText();
|
|
|
|
extract.flags = 0;
|
|
|
|
extract.partialStartOffset = -1;
|
|
|
|
extract.partialEndOffset = -1;
|
2012-06-19 12:13:41 -07:00
|
|
|
extract.selectionStart = selection.start;
|
|
|
|
extract.selectionEnd = selection.end;
|
2011-12-15 13:35:45 -08:00
|
|
|
extract.startOffset = 0;
|
2012-06-13 17:07:49 -07:00
|
|
|
extract.text = content.toString();
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
return extract;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-01-06 18:27:09 -08:00
|
|
|
@Override
|
|
|
|
public boolean setSelection(int start, int end) {
|
2012-06-19 12:13:48 -07:00
|
|
|
// Some IMEs call setSelection() with negative or stale indexes, so clamp them.
|
|
|
|
Span newSelection = Span.clamp(start, end, getEditable());
|
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION,
|
|
|
|
newSelection.start,
|
|
|
|
newSelection.length));
|
|
|
|
return super.setSelection(newSelection.start, newSelection.end);
|
2012-01-06 18:27:09 -08:00
|
|
|
}
|
|
|
|
|
2012-03-08 17:21:18 -08:00
|
|
|
@Override
|
|
|
|
public CharSequence getTextBeforeCursor(int length, int flags) {
|
2012-06-19 12:12:27 -07:00
|
|
|
// Avoid underrunning text buffer.
|
2012-07-17 22:49:25 -07:00
|
|
|
Span selection = getSelection();
|
2012-06-19 12:12:27 -07:00
|
|
|
if (length > selection.start) {
|
|
|
|
length = selection.start;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (length < 1) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2012-03-08 17:21:18 -08:00
|
|
|
return super.getTextBeforeCursor(length, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public CharSequence getTextAfterCursor(int length, int flags) {
|
2012-06-19 12:12:27 -07:00
|
|
|
// Avoid overrunning text buffer.
|
2012-07-17 22:49:25 -07:00
|
|
|
Span selection = getSelection();
|
2012-06-19 12:12:27 -07:00
|
|
|
int contentLength = getEditable().length();
|
|
|
|
if (selection.end + length > contentLength) {
|
|
|
|
length = contentLength - selection.end;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (length < 1) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2012-03-08 17:21:18 -08:00
|
|
|
return super.getTextAfterCursor(length, flags);
|
|
|
|
}
|
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
@Override
|
|
|
|
public boolean setComposingText(CharSequence text, int newCursorPosition) {
|
2012-07-16 14:28:09 -07:00
|
|
|
// setComposingText() places the given text into the editable, replacing any existing
|
|
|
|
// composing text. This method will likely be called multiple times while we are composing
|
|
|
|
// text.
|
|
|
|
|
|
|
|
// If the replacement composition string is empty and we have no active composition string
|
|
|
|
// to replace, then just ignore the empty string. Some VKBs, such as TouchPal Keyboard,
|
|
|
|
// send us empty strings at inopportune times, deleting committed text. See bug 768106.
|
|
|
|
if (text.length() == 0 && !hasCompositionString())
|
|
|
|
return true;
|
|
|
|
|
2012-03-08 17:21:18 -08:00
|
|
|
return super.setComposingText(text, newCursorPosition);
|
|
|
|
}
|
|
|
|
|
2012-07-23 11:52:55 -07:00
|
|
|
private static View getView() {
|
Backout d2ee4c12c0b3 (bug 777351), 5aa6f94160dd (bug 777351), b47c470168fc (bug 777351), 5fb303ba52f7 (bug 777351), be81e4c3d928 (bug 777351), abc5b9a922dc (bug 777075), 8f1fc980f1f1 (bug 777075), 0b194a7f47d4 (bug 777075), d10df9bfef60 (bug 777075), 65393fe32cce (bug 777075), b52dc1df2fde (bug 777075), 8aeda525c094 (bug 777075) for Android native R1 failures on a CLOSED TREE
2012-08-01 10:42:05 -07:00
|
|
|
LayerController controller = GeckoApp.mAppContext.getLayerController();
|
|
|
|
return (controller == null ? null : controller.getView());
|
2012-07-23 11:52:55 -07:00
|
|
|
}
|
|
|
|
|
2012-07-17 22:49:25 -07:00
|
|
|
private Span getSelection() {
|
2012-03-08 17:21:18 -08:00
|
|
|
Editable content = getEditable();
|
2012-07-17 22:49:25 -07:00
|
|
|
int start = Selection.getSelectionStart(content);
|
|
|
|
int end = Selection.getSelectionEnd(content);
|
|
|
|
return Span.clamp(start, end, content);
|
2011-12-15 13:35:45 -08:00
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
private void replaceText(CharSequence text, int newCursorPosition, boolean composing) {
|
2012-02-27 16:29:55 -08:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, String.format("IME: replaceText(\"%s\", %d, %b)",
|
|
|
|
text, newCursorPosition, composing));
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2012-02-27 16:29:55 -08:00
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
if (text == null)
|
|
|
|
text = "";
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
final Editable content = getEditable();
|
|
|
|
if (content == null) {
|
|
|
|
return;
|
|
|
|
}
|
2012-02-27 16:29:22 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
beginBatchEdit();
|
2012-02-27 16:29:22 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
// delete composing text set previously.
|
2012-06-19 12:17:20 -07:00
|
|
|
int a;
|
|
|
|
int b;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-06-19 12:17:20 -07:00
|
|
|
Span composingSpan = getComposingSpan();
|
|
|
|
if (composingSpan != null) {
|
2011-12-15 13:35:45 -08:00
|
|
|
removeComposingSpans(content);
|
2012-06-19 12:17:20 -07:00
|
|
|
a = composingSpan.start;
|
|
|
|
b = composingSpan.end;
|
|
|
|
composingSpan = null;
|
2011-12-15 13:35:45 -08:00
|
|
|
} else {
|
2012-07-17 22:49:25 -07:00
|
|
|
Span selection = getSelection();
|
2012-06-19 12:13:41 -07:00
|
|
|
a = selection.start;
|
|
|
|
b = selection.end;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
if (composing) {
|
|
|
|
Spannable sp = null;
|
|
|
|
if (!(text instanceof Spannable)) {
|
|
|
|
sp = new SpannableStringBuilder(text);
|
|
|
|
text = sp;
|
2012-03-23 10:32:42 -07:00
|
|
|
// Underline the active composition string.
|
|
|
|
sp.setSpan(new UnderlineSpan(), 0, sp.length(),
|
2012-01-17 23:17:51 -08:00
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
|
2011-12-15 13:35:45 -08:00
|
|
|
} else {
|
2012-02-27 16:29:22 -08:00
|
|
|
sp = (Spannable) text;
|
2011-12-15 13:35:45 -08:00
|
|
|
}
|
|
|
|
setComposingSpans(sp);
|
|
|
|
}
|
2012-02-27 16:29:22 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
if (DEBUG) Log.d(LOGTAG, "Replacing from " + a + " to " + b + " with \""
|
|
|
|
+ text + "\", composing=" + composing
|
|
|
|
+ ", type=" + text.getClass().getCanonicalName());
|
2012-02-27 16:29:22 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
if (DEBUG) {
|
|
|
|
LogPrinter lp = new LogPrinter(Log.VERBOSE, LOGTAG);
|
|
|
|
lp.println("Current text:");
|
|
|
|
TextUtils.dumpSpans(content, lp, " ");
|
|
|
|
lp.println("Composing text:");
|
|
|
|
TextUtils.dumpSpans(text, lp, " ");
|
|
|
|
}
|
2012-02-27 16:29:22 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
// Position the cursor appropriately, so that after replacing the
|
|
|
|
// desired range of text it will be located in the correct spot.
|
|
|
|
// This allows us to deal with filters performing edits on the text
|
|
|
|
// we are providing here.
|
|
|
|
if (newCursorPosition > 0) {
|
|
|
|
newCursorPosition += b - 1;
|
2011-11-18 10:28:17 -08:00
|
|
|
} else {
|
2011-12-15 13:35:45 -08:00
|
|
|
newCursorPosition += a;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
2011-12-15 13:35:45 -08:00
|
|
|
if (newCursorPosition < 0) newCursorPosition = 0;
|
|
|
|
if (newCursorPosition > content.length())
|
|
|
|
newCursorPosition = content.length();
|
|
|
|
Selection.setSelection(content, newCursorPosition);
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
content.replace(a, b, text);
|
2012-02-27 16:29:22 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
if (DEBUG) {
|
|
|
|
LogPrinter lp = new LogPrinter(Log.VERBOSE, LOGTAG);
|
|
|
|
lp.println("Final text:");
|
|
|
|
TextUtils.dumpSpans(content, lp, " ");
|
|
|
|
}
|
2012-02-27 16:29:22 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
endBatchEdit();
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-01-17 23:17:51 -08:00
|
|
|
@Override
|
|
|
|
public boolean setComposingRegion(int start, int end) {
|
2012-03-23 10:32:42 -07:00
|
|
|
if (hasCompositionString()) {
|
2012-01-17 23:17:51 -08:00
|
|
|
if (DEBUG) Log.d(LOGTAG, ". . . setComposingRegion: endComposition");
|
|
|
|
endComposition();
|
|
|
|
}
|
|
|
|
|
2012-06-19 12:19:30 -07:00
|
|
|
Span newComposingRegion = Span.clamp(start, end, getEditable());
|
|
|
|
return super.setComposingRegion(newComposingRegion.start, newComposingRegion.end);
|
2012-01-17 23:17:51 -08:00
|
|
|
}
|
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
public String getComposingText() {
|
|
|
|
final Editable content = getEditable();
|
|
|
|
if (content == null) {
|
|
|
|
return null;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
2012-02-27 16:29:22 -08:00
|
|
|
|
2012-06-19 12:17:20 -07:00
|
|
|
Span composingSpan = getComposingSpan();
|
|
|
|
if (composingSpan == null || composingSpan.length == 0) {
|
|
|
|
return "";
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-06-19 12:17:20 -07:00
|
|
|
return TextUtils.substring(content, composingSpan.start, composingSpan.end);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
2012-02-27 16:29:22 -08:00
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
public boolean onKeyDel() {
|
|
|
|
// Some IMEs don't update us on deletions
|
|
|
|
// In that case we are not updated when a composition
|
|
|
|
// is destroyed, and Bad Things happen
|
|
|
|
|
2012-03-23 10:32:42 -07:00
|
|
|
if (!hasCompositionString())
|
2011-11-18 10:28:17 -08:00
|
|
|
return false;
|
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
String text = getComposingText();
|
|
|
|
|
|
|
|
if (text != null && text.length() > 1) {
|
|
|
|
text = text.substring(0, text.length() - 1);
|
|
|
|
replaceText(text, 1, false);
|
|
|
|
return false;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
commitText(null, 1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-02-27 16:29:55 -08:00
|
|
|
private static InputMethodManager getInputMethodManager() {
|
2012-07-26 10:53:51 -07:00
|
|
|
View view = getView();
|
|
|
|
if (view == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
Context context = view.getContext();
|
2012-07-22 00:18:14 -07:00
|
|
|
return InputMethods.getInputMethodManager(context);
|
2012-02-27 16:29:55 -08:00
|
|
|
}
|
|
|
|
|
2012-07-09 13:03:38 -07:00
|
|
|
protected void notifyTextChange(InputMethodManager imm, String text,
|
|
|
|
int start, int oldEnd, int newEnd) {
|
2012-01-06 18:27:09 -08:00
|
|
|
if (!mBatchMode) {
|
|
|
|
if (!text.contentEquals(mEditable)) {
|
2012-07-18 15:26:15 -07:00
|
|
|
if (DEBUG) Log.d(LOGTAG, ". . . notifyTextChange: current mEditable="
|
|
|
|
+ prettyPrintString(mEditable));
|
|
|
|
|
2012-07-09 13:03:38 -07:00
|
|
|
// Editable will be updated by IME event
|
|
|
|
if (!hasCompositionString())
|
|
|
|
setEditable(text);
|
2012-01-06 18:27:09 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mUpdateRequest == null)
|
2011-11-18 10:28:17 -08:00
|
|
|
return;
|
|
|
|
|
2012-07-23 11:52:55 -07:00
|
|
|
View v = getView();
|
2012-01-06 18:27:09 -08:00
|
|
|
|
|
|
|
if (imm == null) {
|
2012-02-27 16:29:55 -08:00
|
|
|
imm = getInputMethodManager();
|
2012-01-06 18:27:09 -08:00
|
|
|
if (imm == null)
|
|
|
|
return;
|
2011-12-15 13:35:45 -08:00
|
|
|
}
|
2012-01-06 18:27:09 -08:00
|
|
|
|
|
|
|
mUpdateExtract.flags = 0;
|
|
|
|
|
|
|
|
// We update from (0, oldEnd) to (0, newEnd) because some Android IMEs
|
|
|
|
// assume that updates start at zero, according to jchen.
|
|
|
|
mUpdateExtract.partialStartOffset = 0;
|
|
|
|
mUpdateExtract.partialEndOffset = oldEnd;
|
|
|
|
|
2012-07-19 11:34:42 -07:00
|
|
|
String updatedText = (newEnd > text.length() ? text : text.substring(0, newEnd));
|
|
|
|
int updatedTextLength = updatedText.length();
|
|
|
|
|
2012-01-06 18:27:09 -08:00
|
|
|
// Faster to not query for selection
|
2012-07-19 11:34:42 -07:00
|
|
|
mUpdateExtract.selectionStart = updatedTextLength;
|
|
|
|
mUpdateExtract.selectionEnd = updatedTextLength;
|
2012-01-06 18:27:09 -08:00
|
|
|
|
2012-07-19 11:34:42 -07:00
|
|
|
mUpdateExtract.text = updatedText;
|
2012-01-06 18:27:09 -08:00
|
|
|
mUpdateExtract.startOffset = 0;
|
|
|
|
|
|
|
|
imm.updateExtractedText(v, mUpdateRequest.token, mUpdateExtract);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
protected void notifySelectionChange(InputMethodManager imm, int start, int end) {
|
2012-01-06 18:27:09 -08:00
|
|
|
if (!mBatchMode) {
|
|
|
|
final Editable content = getEditable();
|
2012-03-08 17:21:18 -08:00
|
|
|
|
2012-06-19 12:13:41 -07:00
|
|
|
Span newSelection = Span.clamp(start, end, content);
|
|
|
|
start = newSelection.start;
|
|
|
|
end = newSelection.end;
|
2012-03-08 17:21:18 -08:00
|
|
|
|
2012-07-17 22:49:25 -07:00
|
|
|
Span currentSelection = getSelection();
|
2012-06-19 12:13:41 -07:00
|
|
|
int a = currentSelection.start;
|
|
|
|
int b = currentSelection.end;
|
2012-03-08 17:21:18 -08:00
|
|
|
|
2012-01-06 18:27:09 -08:00
|
|
|
if (start != a || end != b) {
|
2012-02-27 16:29:55 -08:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, String.format(
|
2012-06-26 17:06:26 -07:00
|
|
|
". . . notifySelectionChange: current editable selection: [%d, %d)",
|
2012-02-27 16:29:55 -08:00
|
|
|
a, b));
|
|
|
|
}
|
|
|
|
|
2012-01-06 18:27:09 -08:00
|
|
|
super.setSelection(start, end);
|
2012-01-17 23:17:51 -08:00
|
|
|
|
|
|
|
// Check if the selection is inside composing span
|
2012-06-19 12:17:20 -07:00
|
|
|
Span composingSpan = getComposingSpan();
|
|
|
|
if (composingSpan != null) {
|
|
|
|
int ca = composingSpan.start;
|
|
|
|
int cb = composingSpan.end;
|
|
|
|
if (start < ca || start > cb || end < ca || end > cb) {
|
|
|
|
if (DEBUG) Log.d(LOGTAG, ". . . notifySelectionChange: removeComposingSpans");
|
|
|
|
removeComposingSpans(content);
|
|
|
|
}
|
2012-01-17 23:17:51 -08:00
|
|
|
}
|
2012-01-06 18:27:09 -08:00
|
|
|
}
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-01-06 18:27:09 -08:00
|
|
|
if (imm != null && imm.isFullscreenMode()) {
|
2012-07-23 11:52:55 -07:00
|
|
|
View v = getView();
|
2012-08-06 00:28:46 -07:00
|
|
|
if (hasCompositionString()) {
|
|
|
|
Span span = getComposingSpan();
|
|
|
|
imm.updateSelection(v, start, end, span.start, span.end);
|
|
|
|
} else {
|
|
|
|
imm.updateSelection(v, start, end, -1, -1);
|
|
|
|
}
|
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-01 11:09:29 -07:00
|
|
|
protected void resetCompositionState() {
|
2012-03-23 10:32:42 -07:00
|
|
|
mCompositionStart = NO_COMPOSITION_STRING;
|
2011-11-18 10:28:17 -08:00
|
|
|
mBatchMode = false;
|
2012-01-06 18:27:09 -08:00
|
|
|
mUpdateRequest = null;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// TextWatcher
|
2012-02-27 16:29:22 -08:00
|
|
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
2012-03-23 10:32:42 -07:00
|
|
|
if (hasCompositionString() && mCompositionStart != start) {
|
2012-01-25 17:34:12 -08:00
|
|
|
// Changed range is different from the composition, need to reset the composition
|
|
|
|
endComposition();
|
|
|
|
}
|
|
|
|
|
2012-04-18 14:57:55 -07:00
|
|
|
CharSequence changedText = s.subSequence(start, start + count);
|
2012-06-08 10:57:16 -07:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, "onTextChanged: changedText=\"" + changedText + "\"");
|
|
|
|
}
|
|
|
|
|
2012-04-18 14:57:55 -07:00
|
|
|
if (changedText.length() == 1) {
|
|
|
|
char changedChar = changedText.charAt(0);
|
|
|
|
|
2012-02-14 17:08:47 -08:00
|
|
|
// Some IMEs (e.g. SwiftKey X) send a string with '\n' when Enter is pressed
|
|
|
|
// Such string cannot be handled by Gecko, so we convert it to a key press instead
|
2012-04-18 14:57:55 -07:00
|
|
|
if (changedChar == '\n') {
|
|
|
|
processKeyDown(KeyEvent.KEYCODE_ENTER, new KeyEvent(KeyEvent.ACTION_DOWN,
|
|
|
|
KeyEvent.KEYCODE_ENTER), false);
|
|
|
|
processKeyUp(KeyEvent.KEYCODE_ENTER, new KeyEvent(KeyEvent.ACTION_UP,
|
|
|
|
KeyEvent.KEYCODE_ENTER), false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we are committing a single character and didn't have an active composition string,
|
|
|
|
// we can send Gecko keydown/keyup events instead of composition events.
|
2012-06-08 10:57:16 -07:00
|
|
|
if (mCommittingText && !hasCompositionString() && sendKeyEventsToGecko(changedChar)) {
|
2012-04-18 14:57:55 -07:00
|
|
|
// Block this thread until all pending events are processed
|
|
|
|
GeckoAppShell.geckoEventSync();
|
|
|
|
return;
|
|
|
|
}
|
2012-02-14 17:08:47 -08:00
|
|
|
}
|
|
|
|
|
2012-05-16 22:29:23 -07:00
|
|
|
boolean startCompositionString = !hasCompositionString();
|
|
|
|
if (startCompositionString) {
|
2012-01-17 23:17:51 -08:00
|
|
|
if (DEBUG) Log.d(LOGTAG, ". . . onTextChanged: IME_COMPOSITION_BEGIN");
|
|
|
|
GeckoAppShell.sendEventToGecko(
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoEvent.createIMEEvent(GeckoEvent.IME_COMPOSITION_BEGIN, 0, 0));
|
2012-01-25 17:34:12 -08:00
|
|
|
mCompositionStart = start;
|
2012-01-17 23:17:51 -08:00
|
|
|
|
2012-02-27 16:29:55 -08:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, ". . . onTextChanged: IME_SET_SELECTION, start=" + start + ", len="
|
|
|
|
+ before);
|
|
|
|
}
|
|
|
|
|
2012-01-17 23:17:51 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION, start, before));
|
2012-01-17 23:17:51 -08:00
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-04-18 14:57:55 -07:00
|
|
|
sendTextToGecko(changedText, start + count);
|
2011-12-20 17:03:54 -08:00
|
|
|
|
2012-02-27 16:29:55 -08:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, ". . . onTextChanged: IME_SET_SELECTION, start=" + (start + count)
|
|
|
|
+ ", 0");
|
|
|
|
}
|
|
|
|
|
2012-01-17 23:17:51 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION, start + count, 0));
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-03-21 10:26:53 -07:00
|
|
|
// End composition if all characters in the word have been deleted.
|
|
|
|
// This fixes autocomplete results not appearing.
|
2012-05-16 22:29:23 -07:00
|
|
|
if (count == 0 || (startCompositionString && mCommittingText))
|
2012-03-21 10:26:53 -07:00
|
|
|
endComposition();
|
|
|
|
|
2012-01-17 23:17:51 -08:00
|
|
|
// Block this thread until all pending events are processed
|
|
|
|
GeckoAppShell.geckoEventSync();
|
|
|
|
}
|
|
|
|
|
2012-06-08 10:57:16 -07:00
|
|
|
private boolean sendKeyEventsToGecko(char inputChar) {
|
2012-04-18 14:57:55 -07:00
|
|
|
// Synthesize VKB key events that could plausibly generate the input character.
|
2012-06-08 10:57:16 -07:00
|
|
|
KeyEvent[] events = synthesizeKeyEvents(inputChar);
|
2012-04-18 14:57:55 -07:00
|
|
|
if (events == null) {
|
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, "synthesizeKeyEvents: char '" + inputChar
|
|
|
|
+ "' has no virtual key mapping");
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean sentKeyEvents = false;
|
|
|
|
|
|
|
|
for (KeyEvent event : events) {
|
|
|
|
if (!KeyEvent.isModifierKey(event.getKeyCode())) {
|
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, "synthesizeKeyEvents: char '" + inputChar
|
|
|
|
+ "' -> action=" + event.getAction()
|
|
|
|
+ ", keyCode=" + event.getKeyCode()
|
|
|
|
+ ", UnicodeChar='" + (char) event.getUnicodeChar() + "'");
|
|
|
|
}
|
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createKeyEvent(event));
|
|
|
|
sentKeyEvents = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return sentKeyEvents;
|
|
|
|
}
|
|
|
|
|
2012-06-08 10:57:16 -07:00
|
|
|
private KeyEvent[] synthesizeKeyEvents(char inputChar) {
|
|
|
|
// Some symbol characters produce unusual key events on Froyo and Gingerbread.
|
|
|
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
|
|
|
|
switch (inputChar) {
|
|
|
|
case '&':
|
2012-06-29 12:06:03 -07:00
|
|
|
// Some Gingerbread devices' KeyCharacterMaps return ALT+7 instead of SHIFT+7,
|
|
|
|
// but some devices like the Droid Bionic treat SHIFT+7 as '7'. So just return
|
|
|
|
// null and onTextChanged() will send "&" as a composition string instead of
|
|
|
|
// KEY_DOWN + KEY_UP event pair. This may break web content listening for '&'
|
|
|
|
// key events, but they will still receive "&" input event.
|
|
|
|
return null;
|
2012-06-08 10:57:16 -07:00
|
|
|
|
|
|
|
case '<':
|
|
|
|
case '>':
|
|
|
|
// We can't synthesize KeyEvents for '<' or '>' because Froyo and Gingerbread
|
|
|
|
// return incorrect shifted char codes from KeyEvent.getUnicodeChar().
|
|
|
|
// Send these characters as composition strings, not key events.
|
|
|
|
return null;
|
|
|
|
|
|
|
|
// Some symbol characters produce key events on Froyo and Gingerbread, but not
|
|
|
|
// Honeycomb and ICS. Send these characters as composition strings, not key events,
|
|
|
|
// to more closely mimic Honeycomb and ICS.
|
|
|
|
case UNICODE_BULLET:
|
|
|
|
case UNICODE_CENT_SIGN:
|
|
|
|
case UNICODE_COPYRIGHT_SIGN:
|
|
|
|
case UNICODE_DIVISION_SIGN:
|
|
|
|
case UNICODE_DOUBLE_LOW_QUOTATION_MARK:
|
|
|
|
case UNICODE_ELLIPSIS:
|
|
|
|
case UNICODE_EURO_SIGN:
|
|
|
|
case UNICODE_INVERTED_EXCLAMATION_MARK:
|
|
|
|
case UNICODE_MULTIPLICATION_SIGN:
|
|
|
|
case UNICODE_PI:
|
|
|
|
case UNICODE_PILCROW_SIGN:
|
|
|
|
case UNICODE_POUND_SIGN:
|
|
|
|
case UNICODE_REGISTERED_SIGN:
|
|
|
|
case UNICODE_SQUARE_ROOT:
|
|
|
|
case UNICODE_TRADEMARK_SIGN:
|
|
|
|
case UNICODE_WHITE_BULLET:
|
|
|
|
case UNICODE_YEN_SIGN:
|
|
|
|
return null;
|
|
|
|
|
|
|
|
default:
|
|
|
|
// Look up the character's key events in KeyCharacterMap below.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mKeyCharacterMap == null) {
|
|
|
|
mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
|
|
|
}
|
|
|
|
|
|
|
|
char[] inputChars = { inputChar };
|
|
|
|
return mKeyCharacterMap.getEvents(inputChars);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static KeyEvent[] createKeyDownKeyUpEvents(int keyCode, int metaState) {
|
|
|
|
long now = SystemClock.uptimeMillis();
|
|
|
|
KeyEvent keyDown = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, metaState);
|
|
|
|
KeyEvent keyUp = KeyEvent.changeAction(keyDown, KeyEvent.ACTION_UP);
|
|
|
|
KeyEvent[] events = { keyDown, keyUp };
|
|
|
|
return events;
|
|
|
|
}
|
|
|
|
|
2012-01-17 23:17:51 -08:00
|
|
|
private void endComposition() {
|
2012-06-29 15:49:48 -07:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, "IME: endComposition: IME_COMPOSITION_END");
|
|
|
|
GeckoApp.assertOnUiThread();
|
|
|
|
}
|
2012-05-16 22:29:23 -07:00
|
|
|
|
|
|
|
if (!hasCompositionString())
|
2012-06-29 15:49:48 -07:00
|
|
|
Log.e(LOGTAG, "Please report this bug:",
|
|
|
|
new IllegalStateException("endComposition, but not composing text?!"));
|
2012-05-16 22:29:23 -07:00
|
|
|
|
2012-01-17 23:17:51 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoEvent.createIMEEvent(GeckoEvent.IME_COMPOSITION_END, 0, 0));
|
2012-05-16 22:29:23 -07:00
|
|
|
|
2012-03-23 10:32:42 -07:00
|
|
|
mCompositionStart = NO_COMPOSITION_STRING;
|
2012-01-17 23:17:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
private void sendTextToGecko(CharSequence text, int caretPos) {
|
2012-06-29 15:49:48 -07:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, "IME: sendTextToGecko(\"" + text + "\")");
|
|
|
|
GeckoApp.assertOnUiThread();
|
|
|
|
}
|
2012-01-17 23:17:51 -08:00
|
|
|
|
|
|
|
// Handle composition text styles
|
|
|
|
if (text != null && text instanceof Spanned) {
|
|
|
|
Spanned span = (Spanned) text;
|
|
|
|
int spanStart = 0, spanEnd = 0;
|
|
|
|
boolean pastSelStart = false, pastSelEnd = false;
|
|
|
|
|
|
|
|
do {
|
|
|
|
int rangeType = GeckoEvent.IME_RANGE_CONVERTEDTEXT;
|
|
|
|
int rangeStyles = 0, rangeForeColor = 0, rangeBackColor = 0;
|
|
|
|
|
|
|
|
// Find next offset where there is a style transition
|
|
|
|
spanEnd = span.nextSpanTransition(spanStart + 1, text.length(),
|
|
|
|
CharacterStyle.class);
|
|
|
|
|
|
|
|
// Empty range, continue
|
|
|
|
if (spanEnd <= spanStart)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Get and iterate through list of span objects within range
|
2012-02-27 16:29:22 -08:00
|
|
|
CharacterStyle[] styles = span.getSpans(spanStart, spanEnd, CharacterStyle.class);
|
2012-01-17 23:17:51 -08:00
|
|
|
|
|
|
|
for (CharacterStyle style : styles) {
|
|
|
|
if (style instanceof UnderlineSpan) {
|
|
|
|
// Text should be underlined
|
|
|
|
rangeStyles |= GeckoEvent.IME_RANGE_UNDERLINE;
|
|
|
|
} else if (style instanceof ForegroundColorSpan) {
|
|
|
|
// Text should be of a different foreground color
|
|
|
|
rangeStyles |= GeckoEvent.IME_RANGE_FORECOLOR;
|
2012-02-27 16:29:22 -08:00
|
|
|
rangeForeColor = ((ForegroundColorSpan) style).getForegroundColor();
|
2012-01-17 23:17:51 -08:00
|
|
|
} else if (style instanceof BackgroundColorSpan) {
|
|
|
|
// Text should be of a different background color
|
|
|
|
rangeStyles |= GeckoEvent.IME_RANGE_BACKCOLOR;
|
2012-02-27 16:29:22 -08:00
|
|
|
rangeBackColor = ((BackgroundColorSpan) style).getBackgroundColor();
|
2012-01-17 23:17:51 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add range to array, the actual styles are
|
|
|
|
// applied when IME_SET_TEXT is sent
|
2012-02-27 16:29:55 -08:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, String.format(
|
|
|
|
". . . sendTextToGecko: IME_ADD_RANGE, %d, %d, %d, %d, %d, %d",
|
|
|
|
spanStart, spanEnd - spanStart, rangeType, rangeStyles, rangeForeColor,
|
|
|
|
rangeBackColor));
|
|
|
|
}
|
|
|
|
|
2012-01-17 23:17:51 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoEvent.createIMERangeEvent(spanStart, spanEnd - spanStart,
|
|
|
|
rangeType, rangeStyles,
|
|
|
|
rangeForeColor, rangeBackColor));
|
2011-12-20 17:03:54 -08:00
|
|
|
|
2012-01-17 23:17:51 -08:00
|
|
|
spanStart = spanEnd;
|
|
|
|
} while (spanStart < text.length());
|
|
|
|
} else {
|
2012-02-27 16:29:22 -08:00
|
|
|
if (DEBUG) Log.d(LOGTAG, ". . . sendTextToGecko: IME_ADD_RANGE, 0, " + text.length()
|
|
|
|
+ ", IME_RANGE_RAWINPUT, IME_RANGE_UNDERLINE)");
|
2011-11-18 10:28:17 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoEvent.createIMERangeEvent(0, text == null ? 0 : text.length(),
|
|
|
|
GeckoEvent.IME_RANGE_RAWINPUT,
|
|
|
|
GeckoEvent.IME_RANGE_UNDERLINE, 0, 0));
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-01-17 23:17:51 -08:00
|
|
|
// Change composition (treating selection end as where the caret is)
|
2012-02-27 16:29:55 -08:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, ". . . sendTextToGecko: IME_SET_TEXT, IME_RANGE_CARETPOSITION, \""
|
|
|
|
+ text + "\")");
|
|
|
|
}
|
|
|
|
|
2012-01-17 23:17:51 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoEvent.createIMERangeEvent(caretPos, 0,
|
|
|
|
GeckoEvent.IME_RANGE_CARETPOSITION, 0, 0, 0,
|
|
|
|
text.toString()));
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-02-27 16:29:22 -08:00
|
|
|
public void afterTextChanged(Editable s) {
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-02-27 16:29:22 -08:00
|
|
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-02-27 16:29:22 -08:00
|
|
|
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
2011-11-18 10:28:17 -08:00
|
|
|
outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
|
|
|
|
outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
|
|
|
|
outAttrs.actionLabel = null;
|
|
|
|
|
|
|
|
if (mIMEState == IME_STATE_PASSWORD)
|
|
|
|
outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
|
|
|
|
else if (mIMETypeHint.equalsIgnoreCase("url"))
|
|
|
|
outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_URI;
|
|
|
|
else if (mIMETypeHint.equalsIgnoreCase("email"))
|
|
|
|
outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
|
|
|
|
else if (mIMETypeHint.equalsIgnoreCase("search"))
|
|
|
|
outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
|
|
|
|
else if (mIMETypeHint.equalsIgnoreCase("tel"))
|
|
|
|
outAttrs.inputType = InputType.TYPE_CLASS_PHONE;
|
|
|
|
else if (mIMETypeHint.equalsIgnoreCase("number") ||
|
|
|
|
mIMETypeHint.equalsIgnoreCase("range"))
|
2012-06-29 15:49:48 -07:00
|
|
|
outAttrs.inputType = InputType.TYPE_CLASS_NUMBER
|
|
|
|
| InputType.TYPE_NUMBER_FLAG_SIGNED
|
|
|
|
| InputType.TYPE_NUMBER_FLAG_DECIMAL;
|
2011-11-18 10:28:17 -08:00
|
|
|
else if (mIMETypeHint.equalsIgnoreCase("datetime") ||
|
|
|
|
mIMETypeHint.equalsIgnoreCase("datetime-local"))
|
2012-06-29 15:49:48 -07:00
|
|
|
outAttrs.inputType = InputType.TYPE_CLASS_DATETIME
|
|
|
|
| InputType.TYPE_DATETIME_VARIATION_NORMAL;
|
2011-11-18 10:28:17 -08:00
|
|
|
else if (mIMETypeHint.equalsIgnoreCase("date"))
|
2012-06-29 15:49:48 -07:00
|
|
|
outAttrs.inputType = InputType.TYPE_CLASS_DATETIME
|
|
|
|
| InputType.TYPE_DATETIME_VARIATION_DATE;
|
2011-11-18 10:28:17 -08:00
|
|
|
else if (mIMETypeHint.equalsIgnoreCase("time"))
|
2012-06-29 15:49:48 -07:00
|
|
|
outAttrs.inputType = InputType.TYPE_CLASS_DATETIME
|
|
|
|
| InputType.TYPE_DATETIME_VARIATION_TIME;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
|
|
|
if (mIMEActionHint.equalsIgnoreCase("go"))
|
|
|
|
outAttrs.imeOptions = EditorInfo.IME_ACTION_GO;
|
|
|
|
else if (mIMEActionHint.equalsIgnoreCase("done"))
|
|
|
|
outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
|
|
|
|
else if (mIMEActionHint.equalsIgnoreCase("next"))
|
|
|
|
outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
|
|
|
|
else if (mIMEActionHint.equalsIgnoreCase("search"))
|
|
|
|
outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
|
|
|
|
else if (mIMEActionHint.equalsIgnoreCase("send"))
|
|
|
|
outAttrs.imeOptions = EditorInfo.IME_ACTION_SEND;
|
|
|
|
else if (mIMEActionHint != null && mIMEActionHint.length() != 0)
|
|
|
|
outAttrs.actionLabel = mIMEActionHint;
|
|
|
|
|
2012-08-01 14:42:11 -07:00
|
|
|
GeckoApp app = GeckoApp.mAppContext;
|
2012-08-01 14:56:26 -07:00
|
|
|
DisplayMetrics metrics = app.getResources().getDisplayMetrics();
|
2012-05-23 18:53:42 -07:00
|
|
|
if (Math.min(metrics.widthPixels, metrics.heightPixels) > INLINE_IME_MIN_DISPLAY_SIZE) {
|
|
|
|
// prevent showing full-screen keyboard only when the screen is tall enough
|
|
|
|
// to show some reasonable amount of the page (see bug 752709)
|
|
|
|
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
|
|
|
| EditorInfo.IME_FLAG_NO_FULLSCREEN;
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-06-01 11:09:29 -07:00
|
|
|
// onCreateInputConnection() can be called during composition when input focus
|
|
|
|
// is restored from a VKB popup window (such as for entering accented characters)
|
|
|
|
// back to our IME. We want to commit our active composition string. Bug 756429
|
|
|
|
if (hasCompositionString()) {
|
|
|
|
endComposition();
|
|
|
|
}
|
|
|
|
|
2012-07-19 11:00:07 -07:00
|
|
|
String prevInputMethod = mCurrentInputMethod;
|
2012-08-01 14:42:11 -07:00
|
|
|
mCurrentInputMethod = InputMethods.getCurrentInputMethod(app);
|
2012-07-19 11:00:07 -07:00
|
|
|
|
2012-07-22 00:18:14 -07:00
|
|
|
// If the user has changed IMEs, then notify input method observers.
|
2012-07-19 11:00:07 -07:00
|
|
|
if (mCurrentInputMethod != prevInputMethod) {
|
2012-08-01 14:42:11 -07:00
|
|
|
FormAssistPopup popup = app.mFormAssistPopup;
|
2012-07-19 11:00:07 -07:00
|
|
|
if (popup != null) {
|
2012-07-22 00:18:14 -07:00
|
|
|
popup.onInputMethodChanged(mCurrentInputMethod);
|
2012-07-19 11:00:07 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-01 11:09:29 -07:00
|
|
|
resetCompositionState();
|
2011-11-18 10:28:17 -08:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
|
|
|
switch (event.getAction()) {
|
|
|
|
case KeyEvent.ACTION_DOWN:
|
|
|
|
return processKeyDown(keyCode, event, true);
|
|
|
|
case KeyEvent.ACTION_UP:
|
|
|
|
return processKeyUp(keyCode, event, true);
|
|
|
|
case KeyEvent.ACTION_MULTIPLE:
|
|
|
|
return onKeyMultiple(keyCode, event.getRepeatCount(), event);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
|
|
return processKeyDown(keyCode, event, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean processKeyDown(int keyCode, KeyEvent event, boolean isPreIme) {
|
2012-02-27 16:29:55 -08:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, "IME: processKeyDown(keyCode=" + keyCode + ", event=" + event + ", "
|
|
|
|
+ isPreIme + ")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2012-02-27 16:29:55 -08:00
|
|
|
}
|
2011-12-15 13:35:45 -08:00
|
|
|
|
2012-06-28 11:03:53 -07:00
|
|
|
if (keyCode > KeyEvent.getMaxKeyCode())
|
|
|
|
return false;
|
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
switch (keyCode) {
|
|
|
|
case KeyEvent.KEYCODE_MENU:
|
|
|
|
case KeyEvent.KEYCODE_BACK:
|
|
|
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
|
|
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
|
|
|
case KeyEvent.KEYCODE_SEARCH:
|
|
|
|
return false;
|
|
|
|
case KeyEvent.KEYCODE_DEL:
|
|
|
|
// See comments in GeckoInputConnection.onKeyDel
|
|
|
|
if (onKeyDel()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case KeyEvent.KEYCODE_ENTER:
|
|
|
|
if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 &&
|
|
|
|
mIMEActionHint.equalsIgnoreCase("next"))
|
|
|
|
event = new KeyEvent(event.getAction(), KeyEvent.KEYCODE_TAB);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isPreIme && mIMEState != IME_STATE_DISABLED &&
|
|
|
|
(event.getMetaState() & KeyEvent.META_ALT_ON) != 0)
|
|
|
|
// Let active IME process pre-IME key events
|
|
|
|
return false;
|
|
|
|
|
2012-07-23 11:52:55 -07:00
|
|
|
View view = getView();
|
2012-03-12 16:02:06 -07:00
|
|
|
KeyListener keyListener = TextKeyListener.getInstance();
|
2011-11-18 10:28:17 -08:00
|
|
|
|
|
|
|
// KeyListener returns true if it handled the event for us.
|
|
|
|
if (mIMEState == IME_STATE_DISABLED ||
|
2012-01-06 18:27:09 -08:00
|
|
|
keyCode == KeyEvent.KEYCODE_ENTER ||
|
|
|
|
keyCode == KeyEvent.KEYCODE_DEL ||
|
|
|
|
keyCode == KeyEvent.KEYCODE_TAB ||
|
|
|
|
(event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
|
2012-03-12 16:02:06 -07:00
|
|
|
!keyListener.onKeyDown(view, mEditable, keyCode, event)) {
|
2012-01-06 18:27:09 -08:00
|
|
|
// Make sure selection in Gecko is up-to-date
|
2012-07-17 22:49:25 -07:00
|
|
|
Span selection = getSelection();
|
2012-06-19 12:13:41 -07:00
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION,
|
|
|
|
selection.start,
|
|
|
|
selection.length));
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createKeyEvent(event));
|
2012-01-06 18:27:09 -08:00
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
|
|
|
return processKeyUp(keyCode, event, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean processKeyUp(int keyCode, KeyEvent event, boolean isPreIme) {
|
2012-02-27 16:29:55 -08:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(LOGTAG, "IME: processKeyUp(keyCode=" + keyCode + ", event=" + event + ", "
|
|
|
|
+ isPreIme + ")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2012-02-27 16:29:55 -08:00
|
|
|
}
|
2011-12-15 13:35:45 -08:00
|
|
|
|
2012-06-28 11:03:53 -07:00
|
|
|
if (keyCode > KeyEvent.getMaxKeyCode())
|
|
|
|
return false;
|
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
switch (keyCode) {
|
|
|
|
case KeyEvent.KEYCODE_BACK:
|
|
|
|
case KeyEvent.KEYCODE_SEARCH:
|
|
|
|
case KeyEvent.KEYCODE_MENU:
|
|
|
|
return false;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isPreIme && mIMEState != IME_STATE_DISABLED &&
|
|
|
|
(event.getMetaState() & KeyEvent.META_ALT_ON) != 0)
|
|
|
|
// Let active IME process pre-IME key events
|
|
|
|
return false;
|
|
|
|
|
2012-07-23 11:52:55 -07:00
|
|
|
View view = getView();
|
2012-03-12 16:02:06 -07:00
|
|
|
KeyListener keyListener = TextKeyListener.getInstance();
|
2011-11-18 10:28:17 -08:00
|
|
|
|
|
|
|
if (mIMEState == IME_STATE_DISABLED ||
|
|
|
|
keyCode == KeyEvent.KEYCODE_ENTER ||
|
|
|
|
keyCode == KeyEvent.KEYCODE_DEL ||
|
|
|
|
(event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
|
2012-03-12 16:02:06 -07:00
|
|
|
!keyListener.onKeyUp(view, mEditable, keyCode, event)) {
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createKeyEvent(event));
|
2012-03-12 16:02:06 -07:00
|
|
|
}
|
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createKeyEvent(event));
|
2011-11-18 10:28:17 -08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
|
2012-07-23 11:52:55 -07:00
|
|
|
View v = getView();
|
2011-11-18 10:28:17 -08:00
|
|
|
switch (keyCode) {
|
|
|
|
case KeyEvent.KEYCODE_MENU:
|
2012-02-27 16:29:55 -08:00
|
|
|
InputMethodManager imm = getInputMethodManager();
|
2011-11-18 10:28:17 -08:00
|
|
|
imm.toggleSoftInputFromWindow(v.getWindowToken(),
|
2012-02-14 12:28:27 -08:00
|
|
|
InputMethodManager.SHOW_FORCED, 0);
|
2011-11-18 10:28:17 -08:00
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-12-03 19:59:27 -08:00
|
|
|
public boolean isIMEEnabled() {
|
|
|
|
// make sure this picks up PASSWORD and PLUGIN states as well
|
|
|
|
return mIMEState != IME_STATE_DISABLED;
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
public void notifyIME(final int type, final int state) {
|
|
|
|
postToUiThread(new Runnable() {
|
|
|
|
public void run() {
|
2012-07-23 11:52:55 -07:00
|
|
|
View v = getView();
|
2012-07-09 11:00:09 -07:00
|
|
|
if (v == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case NOTIFY_IME_RESETINPUTSTATE:
|
|
|
|
if (DEBUG) Log.d(LOGTAG, ". . . notifyIME: reset");
|
|
|
|
|
|
|
|
// Gecko just cancelled the current composition from underneath us,
|
|
|
|
// so abandon our active composition string WITHOUT committing it!
|
|
|
|
resetCompositionState();
|
|
|
|
|
|
|
|
// Don't use IMEStateUpdater for reset.
|
|
|
|
// Because IME may not work showSoftInput()
|
|
|
|
// after calling restartInput() immediately.
|
|
|
|
// So we have to call showSoftInput() delay.
|
|
|
|
InputMethodManager imm = getInputMethodManager();
|
|
|
|
if (imm == null) {
|
|
|
|
// no way to reset IME status directly
|
|
|
|
IMEStateUpdater.resetIME();
|
|
|
|
} else {
|
|
|
|
imm.restartInput(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
// keep current enabled state
|
|
|
|
IMEStateUpdater.enableIME();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NOTIFY_IME_CANCELCOMPOSITION:
|
|
|
|
if (DEBUG) Log.d(LOGTAG, ". . . notifyIME: cancel");
|
|
|
|
IMEStateUpdater.resetIME();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NOTIFY_IME_FOCUSCHANGE:
|
|
|
|
if (DEBUG) Log.d(LOGTAG, ". . . notifyIME: focus");
|
|
|
|
IMEStateUpdater.resetIME();
|
|
|
|
break;
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
2012-07-09 11:00:09 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public void notifyIMEEnabled(final int state, final String typeHint, final String actionHint) {
|
|
|
|
postToUiThread(new Runnable() {
|
|
|
|
public void run() {
|
2012-07-23 11:52:55 -07:00
|
|
|
View v = getView();
|
2012-07-09 11:00:09 -07:00
|
|
|
if (v == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* When IME is 'disabled', IME processing is disabled.
|
|
|
|
In addition, the IME UI is hidden */
|
|
|
|
mIMEState = state;
|
|
|
|
mIMETypeHint = typeHint;
|
|
|
|
mIMEActionHint = actionHint;
|
|
|
|
IMEStateUpdater.enableIME();
|
|
|
|
}
|
|
|
|
});
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
public final void notifyIMEChange(final String text, final int start, final int end,
|
|
|
|
final int newEnd) {
|
|
|
|
postToUiThread(new Runnable() {
|
|
|
|
public void run() {
|
|
|
|
InputMethodManager imm = getInputMethodManager();
|
|
|
|
if (imm == null)
|
|
|
|
return;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
if (newEnd < 0)
|
|
|
|
notifySelectionChange(imm, start, end);
|
|
|
|
else
|
|
|
|
notifyTextChange(imm, text, start, end, newEnd);
|
|
|
|
}
|
|
|
|
});
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Delay updating IME states (see bug 573800) */
|
2012-02-27 16:29:22 -08:00
|
|
|
private static final class IMEStateUpdater extends TimerTask {
|
2012-02-27 16:29:44 -08:00
|
|
|
private static IMEStateUpdater instance;
|
|
|
|
private boolean mEnable;
|
|
|
|
private boolean mReset;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-02-27 16:29:44 -08:00
|
|
|
private static IMEStateUpdater getInstance() {
|
2011-11-18 10:28:17 -08:00
|
|
|
if (instance == null) {
|
|
|
|
instance = new IMEStateUpdater();
|
|
|
|
mIMETimer.schedule(instance, 200);
|
|
|
|
}
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
2012-02-27 16:29:44 -08:00
|
|
|
public static synchronized void enableIME() {
|
2011-11-18 10:28:17 -08:00
|
|
|
getInstance().mEnable = true;
|
|
|
|
}
|
|
|
|
|
2012-02-27 16:29:44 -08:00
|
|
|
public static synchronized void resetIME() {
|
2011-11-18 10:28:17 -08:00
|
|
|
getInstance().mReset = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void run() {
|
2011-12-15 13:35:45 -08:00
|
|
|
if (DEBUG) Log.d(LOGTAG, "IME: run()");
|
2012-02-27 16:29:22 -08:00
|
|
|
synchronized (IMEStateUpdater.class) {
|
2011-11-18 10:28:17 -08:00
|
|
|
instance = null;
|
|
|
|
}
|
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
// TimerTask.run() is running on a random background thread, so post to UI thread.
|
|
|
|
postToUiThread(new Runnable() {
|
|
|
|
public void run() {
|
2012-07-23 11:52:55 -07:00
|
|
|
final View v = getView();
|
|
|
|
if (v == null)
|
|
|
|
return;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
final InputMethodManager imm = getInputMethodManager();
|
|
|
|
if (imm == null)
|
|
|
|
return;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
if (mReset)
|
|
|
|
imm.restartInput(v);
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
if (!mEnable)
|
|
|
|
return;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
if (mIMEState != IME_STATE_DISABLED) {
|
|
|
|
imm.showSoftInput(v, 0);
|
2012-07-16 15:07:46 -07:00
|
|
|
} else if (imm.isActive(v)) {
|
2012-07-09 11:00:09 -07:00
|
|
|
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
private void setEditable(String contents) {
|
2011-11-18 10:28:17 -08:00
|
|
|
mEditable.removeSpan(this);
|
|
|
|
mEditable.replace(0, mEditable.length(), contents);
|
|
|
|
mEditable.setSpan(this, 0, contents.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
|
|
|
Selection.setSelection(mEditable, contents.length());
|
|
|
|
}
|
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
private void initEditable(String contents) {
|
2011-11-18 10:28:17 -08:00
|
|
|
mEditable = mEditableFactory.newEditable(contents);
|
|
|
|
mEditable.setSpan(this, 0, contents.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
|
|
|
Selection.setSelection(mEditable, contents.length());
|
|
|
|
}
|
2012-03-23 10:32:42 -07:00
|
|
|
|
2012-06-01 11:09:29 -07:00
|
|
|
protected final boolean hasCompositionString() {
|
2012-03-23 10:32:42 -07:00
|
|
|
return mCompositionStart != NO_COMPOSITION_STRING;
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-06-19 12:17:20 -07:00
|
|
|
private Span getComposingSpan() {
|
|
|
|
Editable content = getEditable();
|
|
|
|
int start = getComposingSpanStart(content);
|
|
|
|
int end = getComposingSpanEnd(content);
|
|
|
|
|
|
|
|
// Does the editable have a composing span?
|
|
|
|
if (start < 0 || end < 0) {
|
|
|
|
if (start != -1 || end != -1) {
|
2012-06-26 17:06:26 -07:00
|
|
|
throw new IndexOutOfBoundsException("Bad composing span [" + start + "," + end
|
|
|
|
+ "), contentLength=" + content.length());
|
2012-06-19 12:17:20 -07:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Span(start, end, content);
|
|
|
|
}
|
|
|
|
|
2012-07-18 15:26:15 -07:00
|
|
|
private static String prettyPrintString(CharSequence s) {
|
|
|
|
// Quote string and replace newlines with CR arrows.
|
|
|
|
return "\"" + s.toString().replace('\n', UNICODE_CRARR) + "\"";
|
|
|
|
}
|
|
|
|
|
2012-07-09 11:00:09 -07:00
|
|
|
private static void postToUiThread(Runnable runnable) {
|
|
|
|
// postToUiThread() is called by the Gecko and TimerTask threads.
|
|
|
|
// The UI thread does not need to post Runnables to itself.
|
|
|
|
GeckoApp.mAppContext.mMainHandler.post(runnable);
|
|
|
|
}
|
|
|
|
|
2012-06-19 12:13:41 -07:00
|
|
|
private static final class Span {
|
|
|
|
public final int start;
|
|
|
|
public final int end;
|
|
|
|
public final int length;
|
|
|
|
|
|
|
|
public static Span clamp(int start, int end, Editable content) {
|
|
|
|
return new Span(start, end, content);
|
|
|
|
}
|
|
|
|
|
|
|
|
private Span(int a, int b, Editable content) {
|
|
|
|
if (a > b) {
|
|
|
|
int tmp = a;
|
|
|
|
a = b;
|
|
|
|
b = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
final int contentLength = content.length();
|
|
|
|
|
|
|
|
if (a < 0) {
|
|
|
|
a = 0;
|
|
|
|
} else if (a > contentLength) {
|
|
|
|
a = contentLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (b < 0) {
|
|
|
|
b = 0;
|
|
|
|
} else if (b > contentLength) {
|
|
|
|
b = contentLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
start = a;
|
|
|
|
end = b;
|
|
|
|
length = end - start;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-01 11:09:29 -07:00
|
|
|
private static final class DebugGeckoInputConnection extends GeckoInputConnection {
|
2011-12-15 13:35:45 -08:00
|
|
|
public DebugGeckoInputConnection(View targetView) {
|
|
|
|
super(targetView);
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean beginBatchEdit() {
|
|
|
|
Log.d(LOGTAG, "IME: beginBatchEdit");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.beginBatchEdit();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean endBatchEdit() {
|
|
|
|
Log.d(LOGTAG, "IME: endBatchEdit");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.endBatchEdit();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean commitCompletion(CompletionInfo text) {
|
|
|
|
Log.d(LOGTAG, "IME: commitCompletion");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.commitCompletion(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean commitText(CharSequence text, int newCursorPosition) {
|
|
|
|
Log.d(LOGTAG, String.format("IME: commitText(\"%s\", %d)", text, newCursorPosition));
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.commitText(text, newCursorPosition);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean deleteSurroundingText(int leftLength, int rightLength) {
|
2012-02-27 16:29:55 -08:00
|
|
|
Log.d(LOGTAG, "IME: deleteSurroundingText(leftLen=" + leftLength + ", rightLen="
|
|
|
|
+ rightLength + ")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.deleteSurroundingText(leftLength, rightLength);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean finishComposingText() {
|
|
|
|
Log.d(LOGTAG, "IME: finishComposingText");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.finishComposingText();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Editable getEditable() {
|
2012-02-27 16:29:55 -08:00
|
|
|
Editable editable = super.getEditable();
|
2012-07-18 15:26:15 -07:00
|
|
|
Log.d(LOGTAG, "IME: getEditable -> " + prettyPrintString(editable));
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2012-02-27 16:29:55 -08:00
|
|
|
return editable;
|
2011-12-15 13:35:45 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean performContextMenuAction(int id) {
|
|
|
|
Log.d(LOGTAG, "IME: performContextMenuAction");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.performContextMenuAction(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ExtractedText getExtractedText(ExtractedTextRequest req, int flags) {
|
|
|
|
Log.d(LOGTAG, "IME: getExtractedText");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
ExtractedText extract = super.getExtractedText(req, flags);
|
|
|
|
if (extract != null)
|
2012-02-27 16:29:55 -08:00
|
|
|
Log.d(LOGTAG, String.format(
|
|
|
|
". . . getExtractedText: extract.text=\"%s\", selStart=%d, selEnd=%d",
|
|
|
|
extract.text, extract.selectionStart, extract.selectionEnd));
|
2011-12-15 13:35:45 -08:00
|
|
|
return extract;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public CharSequence getTextAfterCursor(int length, int flags) {
|
|
|
|
Log.d(LOGTAG, "IME: getTextAfterCursor(length=" + length + ", flags=" + flags + ")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
CharSequence s = super.getTextAfterCursor(length, flags);
|
|
|
|
Log.d(LOGTAG, ". . . getTextAfterCursor returns \"" + s + "\"");
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public CharSequence getTextBeforeCursor(int length, int flags) {
|
|
|
|
Log.d(LOGTAG, "IME: getTextBeforeCursor");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
CharSequence s = super.getTextBeforeCursor(length, flags);
|
|
|
|
Log.d(LOGTAG, ". . . getTextBeforeCursor returns \"" + s + "\"");
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean setComposingText(CharSequence text, int newCursorPosition) {
|
|
|
|
Log.d(LOGTAG, String.format("IME: setComposingText(\"%s\", %d)", text, newCursorPosition));
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.setComposingText(text, newCursorPosition);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean setComposingRegion(int start, int end) {
|
|
|
|
Log.d(LOGTAG, "IME: setComposingRegion(start=" + start + ", end=" + end + ")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.setComposingRegion(start, end);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean setSelection(int start, int end) {
|
|
|
|
Log.d(LOGTAG, "IME: setSelection(start=" + start + ", end=" + end + ")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.setSelection(start, end);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getComposingText() {
|
|
|
|
Log.d(LOGTAG, "IME: getComposingText");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
String s = super.getComposingText();
|
|
|
|
Log.d(LOGTAG, ". . . getComposingText: Composing text = \"" + s + "\"");
|
|
|
|
return s;
|
|
|
|
}
|
2012-02-27 16:29:22 -08:00
|
|
|
|
2011-12-15 13:35:45 -08:00
|
|
|
@Override
|
|
|
|
public boolean onKeyDel() {
|
|
|
|
Log.d(LOGTAG, "IME: onKeyDel");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.onKeyDel();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2012-07-09 11:00:09 -07:00
|
|
|
protected void notifyTextChange(InputMethodManager imm, String text,
|
|
|
|
int start, int oldEnd, int newEnd) {
|
2012-06-29 15:49:48 -07:00
|
|
|
// notifyTextChange() call is posted to UI thread from notifyIMEChange().
|
|
|
|
GeckoApp.assertOnUiThread();
|
2012-07-18 15:26:15 -07:00
|
|
|
String msg = String.format("IME: >notifyTextChange(%s, start=%d, oldEnd=%d, newEnd=%d)",
|
|
|
|
prettyPrintString(text), start, oldEnd, newEnd);
|
2012-07-19 11:34:42 -07:00
|
|
|
Log.d(LOGTAG, msg);
|
|
|
|
if (start < 0 || oldEnd < start || newEnd < start || newEnd > text.length()) {
|
|
|
|
throw new IllegalArgumentException("BUG! " + msg);
|
|
|
|
}
|
2011-12-15 13:35:45 -08:00
|
|
|
super.notifyTextChange(imm, text, start, oldEnd, newEnd);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2012-07-09 11:00:09 -07:00
|
|
|
protected void notifySelectionChange(InputMethodManager imm, int start, int end) {
|
2012-06-29 15:49:48 -07:00
|
|
|
// notifySelectionChange() call is posted to UI thread from notifyIMEChange().
|
|
|
|
GeckoApp.assertOnUiThread();
|
2012-01-06 18:27:09 -08:00
|
|
|
Log.d(LOGTAG, String.format("IME: >notifySelectionChange(start=%d, end=%d)", start, end));
|
2011-12-15 13:35:45 -08:00
|
|
|
super.notifySelectionChange(imm, start, end);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2012-06-01 11:09:29 -07:00
|
|
|
protected void resetCompositionState() {
|
|
|
|
Log.d(LOGTAG, "IME: resetCompositionState");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2012-06-01 11:09:29 -07:00
|
|
|
if (hasCompositionString()) {
|
|
|
|
Log.d(LOGTAG, "resetCompositionState() is abandoning an active composition string");
|
|
|
|
}
|
|
|
|
super.resetCompositionState();
|
2011-12-15 13:35:45 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2012-02-27 16:29:22 -08:00
|
|
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
2012-02-27 16:29:55 -08:00
|
|
|
Log.d(LOGTAG, String.format("IME: onTextChanged(\"%s\" start=%d, before=%d, count=%d)",
|
|
|
|
s, start, before, count));
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
super.onTextChanged(s, start, before, count);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2012-02-27 16:29:22 -08:00
|
|
|
public void afterTextChanged(Editable s) {
|
2011-12-15 13:35:45 -08:00
|
|
|
Log.d(LOGTAG, "IME: afterTextChanged(\"" + s + "\")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
super.afterTextChanged(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2012-02-27 16:29:22 -08:00
|
|
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
2012-02-27 16:29:55 -08:00
|
|
|
Log.d(LOGTAG, String.format("IME: beforeTextChanged(\"%s\", start=%d, count=%d, after=%d)",
|
|
|
|
s, start, count, after));
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
super.beforeTextChanged(s, start, count, after);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2012-02-27 16:29:22 -08:00
|
|
|
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
2012-02-27 16:29:55 -08:00
|
|
|
Log.d(LOGTAG, "IME: onCreateInputConnection called");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.onCreateInputConnection(outAttrs);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
|
|
|
Log.d(LOGTAG, "IME: onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.onKeyPreIme(keyCode, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
|
|
Log.d(LOGTAG, "IME: onKeyDown(keyCode=" + keyCode + ", event=" + event + ")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.onKeyDown(keyCode, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
|
|
|
Log.d(LOGTAG, "IME: onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.onKeyUp(keyCode, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
|
2012-02-27 16:29:55 -08:00
|
|
|
Log.d(LOGTAG, "IME: onKeyMultiple(keyCode=" + keyCode + ", repeatCount=" + repeatCount
|
|
|
|
+ ", event=" + event + ")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.onKeyMultiple(keyCode, repeatCount, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
|
|
|
|
Log.d(LOGTAG, "IME: onKeyLongPress(keyCode=" + keyCode + ", event=" + event + ")");
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnUiThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
return super.onKeyLongPress(keyCode, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void notifyIME(int type, int state) {
|
2012-01-06 18:27:09 -08:00
|
|
|
Log.d(LOGTAG, String.format("IME: >notifyIME(type=%d, state=%d)", type, state));
|
2012-06-29 15:49:48 -07:00
|
|
|
GeckoApp.assertOnGeckoThread();
|
2011-12-15 13:35:45 -08:00
|
|
|
super.notifyIME(type, state);
|
|
|
|
}
|
2012-06-01 11:09:29 -07:00
|
|
|
}
|
2011-12-15 13:35:45 -08:00
|
|
|
|
|
|
|
}
|