aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/org/kelar/inputmethod/event
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/org/kelar/inputmethod/event')
-rw-r--r--java/src/org/kelar/inputmethod/event/Combiner.java51
-rw-r--r--java/src/org/kelar/inputmethod/event/CombinerChain.java137
-rw-r--r--java/src/org/kelar/inputmethod/event/DeadKeyCombiner.java303
-rw-r--r--java/src/org/kelar/inputmethod/event/Event.java319
-rw-r--r--java/src/org/kelar/inputmethod/event/EventDecoder.java24
-rw-r--r--java/src/org/kelar/inputmethod/event/HardwareEventDecoder.java26
-rw-r--r--java/src/org/kelar/inputmethod/event/HardwareKeyboardEventDecoder.java81
-rw-r--r--java/src/org/kelar/inputmethod/event/InputTransaction.java116
8 files changed, 1057 insertions, 0 deletions
diff --git a/java/src/org/kelar/inputmethod/event/Combiner.java b/java/src/org/kelar/inputmethod/event/Combiner.java
new file mode 100644
index 000000000..45945d460
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/event/Combiner.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.event;
+
+import java.util.ArrayList;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A generic interface for combiners. Combiners are objects that transform chains of input events
+ * into committable strings and manage feedback to show to the user on the combining state.
+ */
+public interface Combiner {
+ /**
+ * Process an event, possibly combining it with the existing state and return the new event.
+ *
+ * If this event does not result in any new event getting passed down the chain, this method
+ * returns null. It may also modify the previous event list if appropriate.
+ *
+ * @param previousEvents the previous events in this composition.
+ * @param event the event to combine with the existing state.
+ * @return the resulting event.
+ */
+ @Nonnull
+ Event processEvent(ArrayList<Event> previousEvents, Event event);
+
+ /**
+ * Get the feedback that should be shown to the user for the current state of this combiner.
+ * @return A CharSequence representing the feedback to show users. It may include styles.
+ */
+ CharSequence getCombiningStateFeedback();
+
+ /**
+ * Reset the state of this combiner, for example when the cursor was moved.
+ */
+ void reset();
+}
diff --git a/java/src/org/kelar/inputmethod/event/CombinerChain.java b/java/src/org/kelar/inputmethod/event/CombinerChain.java
new file mode 100644
index 000000000..afd992e62
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/event/CombinerChain.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.event;
+
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+
+import org.kelar.inputmethod.latin.common.Constants;
+
+import java.util.ArrayList;
+
+import javax.annotation.Nonnull;
+
+/**
+ * This class implements the logic chain between receiving events and generating code points.
+ *
+ * Event sources are multiple. It may be a hardware keyboard, a D-PAD, a software keyboard,
+ * or any exotic input source.
+ * This class will orchestrate the composing chain that starts with an event as its input. Each
+ * composer will be given turns one after the other.
+ * The output is composed of two sequences of code points: the first, representing the already
+ * finished combining part, will be shown normally as the composing string, while the second is
+ * feedback on the composing state and will typically be shown with different styling such as
+ * a colored background.
+ */
+public class CombinerChain {
+ // The already combined text, as described above
+ private StringBuilder mCombinedText;
+ // The feedback on the composing state, as described above
+ private SpannableStringBuilder mStateFeedback;
+ private final ArrayList<Combiner> mCombiners;
+
+ /**
+ * Create an combiner chain.
+ *
+ * The combiner chain takes events as inputs and outputs code points and combining state.
+ * For example, if the input language is Japanese, the combining chain will typically perform
+ * kana conversion. This takes a string for initial text, taken to be present before the
+ * cursor: we'll start after this.
+ *
+ * @param initialText The text that has already been combined so far.
+ */
+ public CombinerChain(final String initialText) {
+ mCombiners = new ArrayList<>();
+ // The dead key combiner is always active, and always first
+ mCombiners.add(new DeadKeyCombiner());
+ mCombinedText = new StringBuilder(initialText);
+ mStateFeedback = new SpannableStringBuilder();
+ }
+
+ public void reset() {
+ mCombinedText.setLength(0);
+ mStateFeedback.clear();
+ for (final Combiner c : mCombiners) {
+ c.reset();
+ }
+ }
+
+ private void updateStateFeedback() {
+ mStateFeedback.clear();
+ for (int i = mCombiners.size() - 1; i >= 0; --i) {
+ mStateFeedback.append(mCombiners.get(i).getCombiningStateFeedback());
+ }
+ }
+
+ /**
+ * Process an event through the combining chain, and return a processed event to apply.
+ * @param previousEvents the list of previous events in this composition
+ * @param newEvent the new event to process
+ * @return the processed event. It may be the same event, or a consumed event, or a completely
+ * new event. However it may never be null.
+ */
+ @Nonnull
+ public Event processEvent(final ArrayList<Event> previousEvents,
+ @Nonnull final Event newEvent) {
+ final ArrayList<Event> modifiablePreviousEvents = new ArrayList<>(previousEvents);
+ Event event = newEvent;
+ for (final Combiner combiner : mCombiners) {
+ // A combiner can never return more than one event; it can return several
+ // code points, but they should be encapsulated within one event.
+ event = combiner.processEvent(modifiablePreviousEvents, event);
+ if (event.isConsumed()) {
+ // If the event is consumed, then we don't pass it to subsequent combiners:
+ // they should not see it at all.
+ break;
+ }
+ }
+ updateStateFeedback();
+ return event;
+ }
+
+ /**
+ * Apply a processed event.
+ * @param event the event to be applied
+ */
+ public void applyProcessedEvent(final Event event) {
+ if (null != event) {
+ // TODO: figure out the generic way of doing this
+ if (Constants.CODE_DELETE == event.mKeyCode) {
+ final int length = mCombinedText.length();
+ if (length > 0) {
+ final int lastCodePoint = mCombinedText.codePointBefore(length);
+ mCombinedText.delete(length - Character.charCount(lastCodePoint), length);
+ }
+ } else {
+ final CharSequence textToCommit = event.getTextToCommit();
+ if (!TextUtils.isEmpty(textToCommit)) {
+ mCombinedText.append(textToCommit);
+ }
+ }
+ }
+ updateStateFeedback();
+ }
+
+ /**
+ * Get the char sequence that should be displayed as the composing word. It may include
+ * styling spans.
+ */
+ public CharSequence getComposingWordWithCombiningFeedback() {
+ final SpannableStringBuilder s = new SpannableStringBuilder(mCombinedText);
+ return s.append(mStateFeedback);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/event/DeadKeyCombiner.java b/java/src/org/kelar/inputmethod/event/DeadKeyCombiner.java
new file mode 100644
index 000000000..562fc2761
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/event/DeadKeyCombiner.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.event;
+
+import android.text.TextUtils;
+import android.util.SparseIntArray;
+
+import org.kelar.inputmethod.latin.common.Constants;
+
+import java.text.Normalizer;
+import java.util.ArrayList;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A combiner that handles dead keys.
+ */
+public class DeadKeyCombiner implements Combiner {
+
+ private static class Data {
+ // This class data taken from KeyCharacterMap.java.
+
+ /* Characters used to display placeholders for dead keys. */
+ private static final int ACCENT_ACUTE = '\u00B4';
+ private static final int ACCENT_BREVE = '\u02D8';
+ private static final int ACCENT_CARON = '\u02C7';
+ private static final int ACCENT_CEDILLA = '\u00B8';
+ private static final int ACCENT_CIRCUMFLEX = '\u02C6';
+ private static final int ACCENT_COMMA_ABOVE = '\u1FBD';
+ private static final int ACCENT_COMMA_ABOVE_RIGHT = '\u02BC';
+ private static final int ACCENT_DOT_ABOVE = '\u02D9';
+ private static final int ACCENT_DOT_BELOW = Constants.CODE_PERIOD; // approximate
+ private static final int ACCENT_DOUBLE_ACUTE = '\u02DD';
+ private static final int ACCENT_GRAVE = '\u02CB';
+ private static final int ACCENT_HOOK_ABOVE = '\u02C0';
+ private static final int ACCENT_HORN = Constants.CODE_SINGLE_QUOTE; // approximate
+ private static final int ACCENT_MACRON = '\u00AF';
+ private static final int ACCENT_MACRON_BELOW = '\u02CD';
+ private static final int ACCENT_OGONEK = '\u02DB';
+ private static final int ACCENT_REVERSED_COMMA_ABOVE = '\u02BD';
+ private static final int ACCENT_RING_ABOVE = '\u02DA';
+ private static final int ACCENT_STROKE = Constants.CODE_DASH; // approximate
+ private static final int ACCENT_TILDE = '\u02DC';
+ private static final int ACCENT_TURNED_COMMA_ABOVE = '\u02BB';
+ private static final int ACCENT_UMLAUT = '\u00A8';
+ private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8';
+ private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC';
+
+ /* Legacy dead key display characters used in previous versions of the API (before L)
+ * We still support these characters by mapping them to their non-legacy version. */
+ private static final int ACCENT_GRAVE_LEGACY = Constants.CODE_GRAVE_ACCENT;
+ private static final int ACCENT_CIRCUMFLEX_LEGACY = Constants.CODE_CIRCUMFLEX_ACCENT;
+ private static final int ACCENT_TILDE_LEGACY = Constants.CODE_TILDE;
+
+ /**
+ * Maps Unicode combining diacritical to display-form dead key.
+ */
+ static final SparseIntArray sCombiningToAccent = new SparseIntArray();
+ static final SparseIntArray sAccentToCombining = new SparseIntArray();
+ static {
+ // U+0300: COMBINING GRAVE ACCENT
+ addCombining('\u0300', ACCENT_GRAVE);
+ // U+0301: COMBINING ACUTE ACCENT
+ addCombining('\u0301', ACCENT_ACUTE);
+ // U+0302: COMBINING CIRCUMFLEX ACCENT
+ addCombining('\u0302', ACCENT_CIRCUMFLEX);
+ // U+0303: COMBINING TILDE
+ addCombining('\u0303', ACCENT_TILDE);
+ // U+0304: COMBINING MACRON
+ addCombining('\u0304', ACCENT_MACRON);
+ // U+0306: COMBINING BREVE
+ addCombining('\u0306', ACCENT_BREVE);
+ // U+0307: COMBINING DOT ABOVE
+ addCombining('\u0307', ACCENT_DOT_ABOVE);
+ // U+0308: COMBINING DIAERESIS
+ addCombining('\u0308', ACCENT_UMLAUT);
+ // U+0309: COMBINING HOOK ABOVE
+ addCombining('\u0309', ACCENT_HOOK_ABOVE);
+ // U+030A: COMBINING RING ABOVE
+ addCombining('\u030A', ACCENT_RING_ABOVE);
+ // U+030B: COMBINING DOUBLE ACUTE ACCENT
+ addCombining('\u030B', ACCENT_DOUBLE_ACUTE);
+ // U+030C: COMBINING CARON
+ addCombining('\u030C', ACCENT_CARON);
+ // U+030D: COMBINING VERTICAL LINE ABOVE
+ addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE);
+ // U+030E: COMBINING DOUBLE VERTICAL LINE ABOVE
+ //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE);
+ // U+030F: COMBINING DOUBLE GRAVE ACCENT
+ //addCombining('\u030F', ACCENT_DOUBLE_GRAVE);
+ // U+0310: COMBINING CANDRABINDU
+ //addCombining('\u0310', ACCENT_CANDRABINDU);
+ // U+0311: COMBINING INVERTED BREVE
+ //addCombining('\u0311', ACCENT_INVERTED_BREVE);
+ // U+0312: COMBINING TURNED COMMA ABOVE
+ addCombining('\u0312', ACCENT_TURNED_COMMA_ABOVE);
+ // U+0313: COMBINING COMMA ABOVE
+ addCombining('\u0313', ACCENT_COMMA_ABOVE);
+ // U+0314: COMBINING REVERSED COMMA ABOVE
+ addCombining('\u0314', ACCENT_REVERSED_COMMA_ABOVE);
+ // U+0315: COMBINING COMMA ABOVE RIGHT
+ addCombining('\u0315', ACCENT_COMMA_ABOVE_RIGHT);
+ // U+031B: COMBINING HORN
+ addCombining('\u031B', ACCENT_HORN);
+ // U+0323: COMBINING DOT BELOW
+ addCombining('\u0323', ACCENT_DOT_BELOW);
+ // U+0326: COMBINING COMMA BELOW
+ //addCombining('\u0326', ACCENT_COMMA_BELOW);
+ // U+0327: COMBINING CEDILLA
+ addCombining('\u0327', ACCENT_CEDILLA);
+ // U+0328: COMBINING OGONEK
+ addCombining('\u0328', ACCENT_OGONEK);
+ // U+0329: COMBINING VERTICAL LINE BELOW
+ addCombining('\u0329', ACCENT_VERTICAL_LINE_BELOW);
+ // U+0331: COMBINING MACRON BELOW
+ addCombining('\u0331', ACCENT_MACRON_BELOW);
+ // U+0335: COMBINING SHORT STROKE OVERLAY
+ addCombining('\u0335', ACCENT_STROKE);
+ // U+0342: COMBINING GREEK PERISPOMENI
+ //addCombining('\u0342', ACCENT_PERISPOMENI);
+ // U+0344: COMBINING GREEK DIALYTIKA TONOS
+ //addCombining('\u0344', ACCENT_DIALYTIKA_TONOS);
+ // U+0345: COMBINING GREEK YPOGEGRAMMENI
+ //addCombining('\u0345', ACCENT_YPOGEGRAMMENI);
+
+ // One-way mappings to equivalent preferred accents.
+ // U+0340: COMBINING GRAVE TONE MARK
+ sCombiningToAccent.append('\u0340', ACCENT_GRAVE);
+ // U+0341: COMBINING ACUTE TONE MARK
+ sCombiningToAccent.append('\u0341', ACCENT_ACUTE);
+ // U+0343: COMBINING GREEK KORONIS
+ sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE);
+
+ // One-way legacy mappings to preserve compatibility with older applications.
+ // U+0300: COMBINING GRAVE ACCENT
+ sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300');
+ // U+0302: COMBINING CIRCUMFLEX ACCENT
+ sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302');
+ // U+0303: COMBINING TILDE
+ sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303');
+ }
+
+ private static void addCombining(int combining, int accent) {
+ sCombiningToAccent.append(combining, accent);
+ sAccentToCombining.append(accent, combining);
+ }
+
+ // Caution! This may only contain chars, not supplementary code points. It's unlikely
+ // it will ever need to, but if it does we'll have to change this
+ private static final SparseIntArray sNonstandardDeadCombinations = new SparseIntArray();
+ static {
+ // Non-standard decompositions.
+ // Stroke modifier for Finnish multilingual keyboard and others.
+ // U+0110: LATIN CAPITAL LETTER D WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'D', '\u0110');
+ // U+01E4: LATIN CAPITAL LETTER G WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'G', '\u01e4');
+ // U+0126: LATIN CAPITAL LETTER H WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'H', '\u0126');
+ // U+0197: LATIN CAPITAL LETTER I WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'I', '\u0197');
+ // U+0141: LATIN CAPITAL LETTER L WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'L', '\u0141');
+ // U+00D8: LATIN CAPITAL LETTER O WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'O', '\u00d8');
+ // U+0166: LATIN CAPITAL LETTER T WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'T', '\u0166');
+ // U+0111: LATIN SMALL LETTER D WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'd', '\u0111');
+ // U+01E5: LATIN SMALL LETTER G WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'g', '\u01e5');
+ // U+0127: LATIN SMALL LETTER H WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'h', '\u0127');
+ // U+0268: LATIN SMALL LETTER I WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'i', '\u0268');
+ // U+0142: LATIN SMALL LETTER L WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'l', '\u0142');
+ // U+00F8: LATIN SMALL LETTER O WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'o', '\u00f8');
+ // U+0167: LATIN SMALL LETTER T WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 't', '\u0167');
+ }
+
+ private static void addNonStandardDeadCombination(final int deadCodePoint,
+ final int spacingCodePoint, final int result) {
+ final int combination = (deadCodePoint << 16) | spacingCodePoint;
+ sNonstandardDeadCombinations.put(combination, result);
+ }
+
+ public static final int NOT_A_CHAR = 0;
+ public static final int BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION = 16;
+ // Get a non-standard combination
+ public static char getNonstandardCombination(final int deadCodePoint,
+ final int spacingCodePoint) {
+ final int combination = spacingCodePoint |
+ (deadCodePoint << BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION);
+ return (char)sNonstandardDeadCombinations.get(combination, NOT_A_CHAR);
+ }
+ }
+
+ // TODO: make this a list of events instead
+ final StringBuilder mDeadSequence = new StringBuilder();
+
+ @Nonnull
+ private static Event createEventChainFromSequence(final @Nonnull CharSequence text,
+ @Nonnull final Event originalEvent) {
+ int index = text.length();
+ if (index <= 0) {
+ return originalEvent;
+ }
+ Event lastEvent = null;
+ do {
+ final int codePoint = Character.codePointBefore(text, index);
+ lastEvent = Event.createHardwareKeypressEvent(codePoint,
+ originalEvent.mKeyCode, lastEvent, false /* isKeyRepeat */);
+ index -= Character.charCount(codePoint);
+ } while (index > 0);
+ return lastEvent;
+ }
+
+ @Override
+ @Nonnull
+ public Event processEvent(final ArrayList<Event> previousEvents, final Event event) {
+ if (TextUtils.isEmpty(mDeadSequence)) {
+ // No dead char is currently being tracked: this is the most common case.
+ if (event.isDead()) {
+ // The event was a dead key. Start tracking it.
+ mDeadSequence.appendCodePoint(event.mCodePoint);
+ return Event.createConsumedEvent(event);
+ }
+ // Regular keystroke when not keeping track of a dead key. Simply said, there are
+ // no dead keys at all in the current input, so this combiner has nothing to do and
+ // simply returns the event as is. The majority of events will go through this path.
+ return event;
+ }
+ if (Character.isWhitespace(event.mCodePoint)
+ || event.mCodePoint == mDeadSequence.codePointBefore(mDeadSequence.length())) {
+ // When whitespace or twice the same dead key, we should output the dead sequence as is.
+ final Event resultEvent = createEventChainFromSequence(mDeadSequence.toString(),
+ event);
+ mDeadSequence.setLength(0);
+ return resultEvent;
+ }
+ if (event.isFunctionalKeyEvent()) {
+ if (Constants.CODE_DELETE == event.mKeyCode) {
+ // Remove the last code point
+ final int trimIndex = mDeadSequence.length() - Character.charCount(
+ mDeadSequence.codePointBefore(mDeadSequence.length()));
+ mDeadSequence.setLength(trimIndex);
+ return Event.createConsumedEvent(event);
+ }
+ return event;
+ }
+ if (event.isDead()) {
+ mDeadSequence.appendCodePoint(event.mCodePoint);
+ return Event.createConsumedEvent(event);
+ }
+ // Combine normally.
+ final StringBuilder sb = new StringBuilder();
+ sb.appendCodePoint(event.mCodePoint);
+ int codePointIndex = 0;
+ while (codePointIndex < mDeadSequence.length()) {
+ final int deadCodePoint = mDeadSequence.codePointAt(codePointIndex);
+ final char replacementSpacingChar =
+ Data.getNonstandardCombination(deadCodePoint, event.mCodePoint);
+ if (Data.NOT_A_CHAR != replacementSpacingChar) {
+ sb.setCharAt(0, replacementSpacingChar);
+ } else {
+ final int combining = Data.sAccentToCombining.get(deadCodePoint);
+ sb.appendCodePoint(0 == combining ? deadCodePoint : combining);
+ }
+ codePointIndex += Character.isSupplementaryCodePoint(deadCodePoint) ? 2 : 1;
+ }
+ final String normalizedString = Normalizer.normalize(sb, Normalizer.Form.NFC);
+ final Event resultEvent = createEventChainFromSequence(normalizedString, event);
+ mDeadSequence.setLength(0);
+ return resultEvent;
+ }
+
+ @Override
+ public void reset() {
+ mDeadSequence.setLength(0);
+ }
+
+ @Override
+ public CharSequence getCombiningStateFeedback() {
+ return mDeadSequence;
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/event/Event.java b/java/src/org/kelar/inputmethod/event/Event.java
new file mode 100644
index 000000000..17c9717c5
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/event/Event.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.event;
+
+import org.kelar.inputmethod.annotations.ExternallyReferenced;
+import org.kelar.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import org.kelar.inputmethod.latin.common.Constants;
+import org.kelar.inputmethod.latin.common.StringUtils;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Class representing a generic input event as handled by Latin IME.
+ *
+ * This contains information about the origin of the event, but it is generalized and should
+ * represent a software keypress, hardware keypress, or d-pad move alike.
+ * Very importantly, this does not necessarily result in inputting one character, or even anything
+ * at all - it may be a dead key, it may be a partial input, it may be a special key on the
+ * keyboard, it may be a cancellation of a keypress (e.g. in a soft keyboard the finger of the
+ * user has slid out of the key), etc. It may also be a batch input from a gesture or handwriting
+ * for example.
+ * The combiner should figure out what to do with this.
+ */
+public class Event {
+ // Should the types below be represented by separate classes instead? It would be cleaner
+ // but probably a bit too much
+ // An event we don't handle in Latin IME, for example pressing Ctrl on a hardware keyboard.
+ final public static int EVENT_TYPE_NOT_HANDLED = 0;
+ // A key press that is part of input, for example pressing an alphabetic character on a
+ // hardware qwerty keyboard. It may be part of a sequence that will be re-interpreted later
+ // through combination.
+ final public static int EVENT_TYPE_INPUT_KEYPRESS = 1;
+ // A toggle event is triggered by a key that affects the previous character. An example would
+ // be a numeric key on a 10-key keyboard, which would toggle between 1 - a - b - c with
+ // repeated presses.
+ final public static int EVENT_TYPE_TOGGLE = 2;
+ // A mode event instructs the combiner to change modes. The canonical example would be the
+ // hankaku/zenkaku key on a Japanese keyboard, or even the caps lock key on a qwerty keyboard
+ // if handled at the combiner level.
+ final public static int EVENT_TYPE_MODE_KEY = 3;
+ // An event corresponding to a gesture.
+ final public static int EVENT_TYPE_GESTURE = 4;
+ // An event corresponding to the manual pick of a suggestion.
+ final public static int EVENT_TYPE_SUGGESTION_PICKED = 5;
+ // An event corresponding to a string generated by some software process.
+ final public static int EVENT_TYPE_SOFTWARE_GENERATED_STRING = 6;
+ // An event corresponding to a cursor move
+ final public static int EVENT_TYPE_CURSOR_MOVE = 7;
+
+ // 0 is a valid code point, so we use -1 here.
+ final public static int NOT_A_CODE_POINT = -1;
+ // -1 is a valid key code, so we use 0 here.
+ final public static int NOT_A_KEY_CODE = 0;
+
+ final private static int FLAG_NONE = 0;
+ // This event is a dead character, usually input by a dead key. Examples include dead-acute
+ // or dead-abovering.
+ final private static int FLAG_DEAD = 0x1;
+ // This event is coming from a key repeat, software or hardware.
+ final private static int FLAG_REPEAT = 0x2;
+ // This event has already been consumed.
+ final private static int FLAG_CONSUMED = 0x4;
+
+ final private int mEventType; // The type of event - one of the constants above
+ // The code point associated with the event, if relevant. This is a unicode code point, and
+ // has nothing to do with other representations of the key. It is only relevant if this event
+ // is of KEYPRESS type, but for a mode key like hankaku/zenkaku or ctrl, there is no code point
+ // associated so this should be NOT_A_CODE_POINT to avoid unintentional use of its value when
+ // it's not relevant.
+ final public int mCodePoint;
+
+ // If applicable, this contains the string that should be input.
+ final public CharSequence mText;
+
+ // The key code associated with the event, if relevant. This is relevant whenever this event
+ // has been triggered by a key press, but not for a gesture for example. This has conceptually
+ // no link to the code point, although keys that enter a straight code point may often set
+ // this to be equal to mCodePoint for convenience. If this is not a key, this must contain
+ // NOT_A_KEY_CODE.
+ final public int mKeyCode;
+
+ // Coordinates of the touch event, if relevant. If useful, we may want to replace this with
+ // a MotionEvent or something in the future. This is only relevant when the keypress is from
+ // a software keyboard obviously, unless there are touch-sensitive hardware keyboards in the
+ // future or some other awesome sauce.
+ final public int mX;
+ final public int mY;
+
+ // Some flags that can't go into the key code. It's a bit field of FLAG_*
+ final private int mFlags;
+
+ // If this is of type EVENT_TYPE_SUGGESTION_PICKED, this must not be null (and must be null in
+ // other cases).
+ final public SuggestedWordInfo mSuggestedWordInfo;
+
+ // The next event, if any. Null if there is no next event yet.
+ final public Event mNextEvent;
+
+ // This method is private - to create a new event, use one of the create* utility methods.
+ private Event(final int type, final CharSequence text, final int codePoint, final int keyCode,
+ final int x, final int y, final SuggestedWordInfo suggestedWordInfo, final int flags,
+ final Event next) {
+ mEventType = type;
+ mText = text;
+ mCodePoint = codePoint;
+ mKeyCode = keyCode;
+ mX = x;
+ mY = y;
+ mSuggestedWordInfo = suggestedWordInfo;
+ mFlags = flags;
+ mNextEvent = next;
+ // Validity checks
+ // mSuggestedWordInfo is non-null if and only if the type is SUGGESTION_PICKED
+ if (EVENT_TYPE_SUGGESTION_PICKED == mEventType) {
+ if (null == mSuggestedWordInfo) {
+ throw new RuntimeException("Wrong event: SUGGESTION_PICKED event must have a "
+ + "non-null SuggestedWordInfo");
+ }
+ } else {
+ if (null != mSuggestedWordInfo) {
+ throw new RuntimeException("Wrong event: only SUGGESTION_PICKED events may have " +
+ "a non-null SuggestedWordInfo");
+ }
+ }
+ }
+
+ @Nonnull
+ public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode,
+ final int x, final int y, final boolean isKeyRepeat) {
+ return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, x, y,
+ null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, null);
+ }
+
+ @Nonnull
+ public static Event createHardwareKeypressEvent(final int codePoint, final int keyCode,
+ final Event next, final boolean isKeyRepeat) {
+ return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
+ Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
+ null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, next);
+ }
+
+ // This creates an input event for a dead character. @see {@link #FLAG_DEAD}
+ @ExternallyReferenced
+ @Nonnull
+ public static Event createDeadEvent(final int codePoint, final int keyCode, final Event next) {
+ // TODO: add an argument or something if we ever create a software layout with dead keys.
+ return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
+ Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
+ null /* suggestedWordInfo */, FLAG_DEAD, next);
+ }
+
+ /**
+ * Create an input event with nothing but a code point. This is the most basic possible input
+ * event; it contains no information on many things the IME requires to function correctly,
+ * so avoid using it unless really nothing is known about this input.
+ * @param codePoint the code point.
+ * @return an event for this code point.
+ */
+ @Nonnull
+ public static Event createEventForCodePointFromUnknownSource(final int codePoint) {
+ // TODO: should we have a different type of event for this? After all, it's not a key press.
+ return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+ null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
+ }
+
+ /**
+ * Creates an input event with a code point and x, y coordinates. This is typically used when
+ * resuming a previously-typed word, when the coordinates are still known.
+ * @param codePoint the code point to input.
+ * @param x the X coordinate.
+ * @param y the Y coordinate.
+ * @return an event for this code point and coordinates.
+ */
+ @Nonnull
+ public static Event createEventForCodePointFromAlreadyTypedText(final int codePoint,
+ final int x, final int y) {
+ // TODO: should we have a different type of event for this? After all, it's not a key press.
+ return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE,
+ x, y, null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
+ }
+
+ /**
+ * Creates an input event representing the manual pick of a suggestion.
+ * @return an event for this suggestion pick.
+ */
+ @Nonnull
+ public static Event createSuggestionPickedEvent(final SuggestedWordInfo suggestedWordInfo) {
+ return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord,
+ NOT_A_CODE_POINT, NOT_A_KEY_CODE,
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
+ suggestedWordInfo, FLAG_NONE, null /* next */);
+ }
+
+ /**
+ * Creates an input event with a CharSequence. This is used by some software processes whose
+ * output is a string, possibly with styling. Examples include press on a multi-character key,
+ * or combination that outputs a string.
+ * @param text the CharSequence associated with this event.
+ * @param keyCode the key code, or NOT_A_KEYCODE if not applicable.
+ * @return an event for this text.
+ */
+ @Nonnull
+ public static Event createSoftwareTextEvent(final CharSequence text, final int keyCode) {
+ return new Event(EVENT_TYPE_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+ null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
+ }
+
+ /**
+ * Creates an input event representing the manual pick of a punctuation suggestion.
+ * @return an event for this suggestion pick.
+ */
+ @Nonnull
+ public static Event createPunctuationSuggestionPickedEvent(
+ final SuggestedWordInfo suggestedWordInfo) {
+ final int primaryCode = suggestedWordInfo.mWord.charAt(0);
+ return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, primaryCode,
+ NOT_A_KEY_CODE, Constants.SUGGESTION_STRIP_COORDINATE,
+ Constants.SUGGESTION_STRIP_COORDINATE, suggestedWordInfo, FLAG_NONE,
+ null /* next */);
+ }
+
+ /**
+ * Creates an input event representing moving the cursor. The relative move amount is stored
+ * in mX.
+ * @param moveAmount the relative move amount.
+ * @return an event for this cursor move.
+ */
+ @Nonnull
+ public static Event createCursorMovedEvent(final int moveAmount) {
+ return new Event(EVENT_TYPE_CURSOR_MOVE, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
+ moveAmount, Constants.NOT_A_COORDINATE, null, FLAG_NONE, null);
+ }
+
+ /**
+ * Creates an event identical to the passed event, but that has already been consumed.
+ * @param source the event to copy the properties of.
+ * @return an identical event marked as consumed.
+ */
+ @Nonnull
+ public static Event createConsumedEvent(final Event source) {
+ // A consumed event should not input any text at all, so we pass the empty string as text.
+ return new Event(source.mEventType, source.mText, source.mCodePoint, source.mKeyCode,
+ source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags | FLAG_CONSUMED,
+ source.mNextEvent);
+ }
+
+ @Nonnull
+ public static Event createNotHandledEvent() {
+ return new Event(EVENT_TYPE_NOT_HANDLED, null /* text */, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+ null /* suggestedWordInfo */, FLAG_NONE, null);
+ }
+
+ // Returns whether this is a function key like backspace, ctrl, settings... as opposed to keys
+ // that result in input like letters or space.
+ public boolean isFunctionalKeyEvent() {
+ // This logic may need to be refined in the future
+ return NOT_A_CODE_POINT == mCodePoint;
+ }
+
+ // Returns whether this event is for a dead character. @see {@link #FLAG_DEAD}
+ public boolean isDead() {
+ return 0 != (FLAG_DEAD & mFlags);
+ }
+
+ public boolean isKeyRepeat() {
+ return 0 != (FLAG_REPEAT & mFlags);
+ }
+
+ public boolean isConsumed() { return 0 != (FLAG_CONSUMED & mFlags); }
+
+ public boolean isGesture() { return EVENT_TYPE_GESTURE == mEventType; }
+
+ // Returns whether this is a fake key press from the suggestion strip. This happens with
+ // punctuation signs selected from the suggestion strip.
+ public boolean isSuggestionStripPress() {
+ return EVENT_TYPE_SUGGESTION_PICKED == mEventType;
+ }
+
+ public boolean isHandled() {
+ return EVENT_TYPE_NOT_HANDLED != mEventType;
+ }
+
+ public CharSequence getTextToCommit() {
+ if (isConsumed()) {
+ return ""; // A consumed event should input no text.
+ }
+ switch (mEventType) {
+ case EVENT_TYPE_MODE_KEY:
+ case EVENT_TYPE_NOT_HANDLED:
+ case EVENT_TYPE_TOGGLE:
+ case EVENT_TYPE_CURSOR_MOVE:
+ return "";
+ case EVENT_TYPE_INPUT_KEYPRESS:
+ return StringUtils.newSingleCodePointString(mCodePoint);
+ case EVENT_TYPE_GESTURE:
+ case EVENT_TYPE_SOFTWARE_GENERATED_STRING:
+ case EVENT_TYPE_SUGGESTION_PICKED:
+ return mText;
+ }
+ throw new RuntimeException("Unknown event type: " + mEventType);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/event/EventDecoder.java b/java/src/org/kelar/inputmethod/event/EventDecoder.java
new file mode 100644
index 000000000..3826f0608
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/event/EventDecoder.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.event;
+
+/**
+ * A generic interface for event decoders.
+ */
+public interface EventDecoder {
+
+}
diff --git a/java/src/org/kelar/inputmethod/event/HardwareEventDecoder.java b/java/src/org/kelar/inputmethod/event/HardwareEventDecoder.java
new file mode 100644
index 000000000..0b75ab8b3
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/event/HardwareEventDecoder.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.event;
+
+import android.view.KeyEvent;
+
+/**
+ * An event decoder for hardware events.
+ */
+public interface HardwareEventDecoder extends EventDecoder {
+ public Event decodeHardwareKey(final KeyEvent keyEvent);
+}
diff --git a/java/src/org/kelar/inputmethod/event/HardwareKeyboardEventDecoder.java b/java/src/org/kelar/inputmethod/event/HardwareKeyboardEventDecoder.java
new file mode 100644
index 000000000..c8cfbc2cc
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/event/HardwareKeyboardEventDecoder.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.event;
+
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import org.kelar.inputmethod.latin.common.Constants;
+
+/**
+ * A hardware event decoder for a hardware qwerty-ish keyboard.
+ *
+ * The events are always hardware keypresses, but they can be key down or key up events, they
+ * can be dead keys, they can be meta keys like shift or ctrl... This does not deal with
+ * 10-key like keyboards; a different decoder is used for this.
+ */
+public class HardwareKeyboardEventDecoder implements HardwareEventDecoder {
+ final int mDeviceId;
+
+ public HardwareKeyboardEventDecoder(final int deviceId) {
+ mDeviceId = deviceId;
+ // TODO: get the layout for this hardware keyboard
+ }
+
+ @Override
+ public Event decodeHardwareKey(final KeyEvent keyEvent) {
+ // KeyEvent#getUnicodeChar() does not exactly returns a unicode char, but rather a value
+ // that includes both the unicode char in the lower 21 bits and flags in the upper bits,
+ // hence the name "codePointAndFlags". {@see KeyEvent#getUnicodeChar()} for more info.
+ final int codePointAndFlags = keyEvent.getUnicodeChar();
+ // The keyCode is the abstraction used by the KeyEvent to represent different keys that
+ // do not necessarily map to a unicode character. This represents a physical key, like
+ // the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock.
+ final int keyCode = keyEvent.getKeyCode();
+ final boolean isKeyRepeat = (0 != keyEvent.getRepeatCount());
+ if (KeyEvent.KEYCODE_DEL == keyCode) {
+ return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT, Constants.CODE_DELETE,
+ null /* next */, isKeyRepeat);
+ }
+ if (keyEvent.isPrintingKey() || KeyEvent.KEYCODE_SPACE == keyCode
+ || KeyEvent.KEYCODE_ENTER == keyCode) {
+ if (0 != (codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT)) {
+ // A dead key.
+ return Event.createDeadEvent(
+ codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT_MASK, keyCode,
+ null /* next */);
+ }
+ if (KeyEvent.KEYCODE_ENTER == keyCode) {
+ // The Enter key. If the Shift key is not being pressed, this should send a
+ // CODE_ENTER to trigger the action if any, or a carriage return otherwise. If the
+ // Shift key is being pressed, this should send a CODE_SHIFT_ENTER and let
+ // Latin IME decide what to do with it.
+ if (keyEvent.isShiftPressed()) {
+ return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT,
+ Constants.CODE_SHIFT_ENTER, null /* next */, isKeyRepeat);
+ }
+ return Event.createHardwareKeypressEvent(Constants.CODE_ENTER, keyCode,
+ null /* next */, isKeyRepeat);
+ }
+ // If not Enter, then this is just a regular keypress event for a normal character
+ // that can be committed right away, taking into account the current state.
+ return Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, null /* next */,
+ isKeyRepeat);
+ }
+ return Event.createNotHandledEvent();
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/event/InputTransaction.java b/java/src/org/kelar/inputmethod/event/InputTransaction.java
new file mode 100644
index 000000000..096ae68f5
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/event/InputTransaction.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.event;
+
+import org.kelar.inputmethod.latin.settings.SettingsValues;
+
+/**
+ * An object encapsulating a single transaction for input.
+ */
+public class InputTransaction {
+ // UPDATE_LATER is stronger than UPDATE_NOW. The reason for this is, if we have to update later,
+ // it's because something will change that we can't evaluate now, which means that even if we
+ // re-evaluate now we'll have to do it again later. The only case where that wouldn't apply
+ // would be if we needed to update now to find out the new state right away, but then we
+ // can't do it with this deferred mechanism anyway.
+ public static final int SHIFT_NO_UPDATE = 0;
+ public static final int SHIFT_UPDATE_NOW = 1;
+ public static final int SHIFT_UPDATE_LATER = 2;
+
+ // Initial conditions
+ public final SettingsValues mSettingsValues;
+ public final Event mEvent;
+ public final long mTimestamp;
+ public final int mSpaceState;
+ public final int mShiftState;
+
+ // Outputs
+ private int mRequiredShiftUpdate = SHIFT_NO_UPDATE;
+ private boolean mRequiresUpdateSuggestions = false;
+ private boolean mDidAffectContents = false;
+ private boolean mDidAutoCorrect = false;
+
+ public InputTransaction(final SettingsValues settingsValues, final Event event,
+ final long timestamp, final int spaceState, final int shiftState) {
+ mSettingsValues = settingsValues;
+ mEvent = event;
+ mTimestamp = timestamp;
+ mSpaceState = spaceState;
+ mShiftState = shiftState;
+ }
+
+ /**
+ * Indicate that this transaction requires some type of shift update.
+ * @param updateType What type of shift update this requires.
+ */
+ public void requireShiftUpdate(final int updateType) {
+ mRequiredShiftUpdate = Math.max(mRequiredShiftUpdate, updateType);
+ }
+
+ /**
+ * Gets what type of shift update this transaction requires.
+ * @return The shift update type.
+ */
+ public int getRequiredShiftUpdate() {
+ return mRequiredShiftUpdate;
+ }
+
+ /**
+ * Indicate that this transaction requires updating the suggestions.
+ */
+ public void setRequiresUpdateSuggestions() {
+ mRequiresUpdateSuggestions = true;
+ }
+
+ /**
+ * Find out whether this transaction requires updating the suggestions.
+ * @return Whether this transaction requires updating the suggestions.
+ */
+ public boolean requiresUpdateSuggestions() {
+ return mRequiresUpdateSuggestions;
+ }
+
+ /**
+ * Indicate that this transaction affected the contents of the editor.
+ */
+ public void setDidAffectContents() {
+ mDidAffectContents = true;
+ }
+
+ /**
+ * Find out whether this transaction affected contents of the editor.
+ * @return Whether this transaction affected contents of the editor.
+ */
+ public boolean didAffectContents() {
+ return mDidAffectContents;
+ }
+
+ /**
+ * Indicate that this transaction performed an auto-correction.
+ */
+ public void setDidAutoCorrect() {
+ mDidAutoCorrect = true;
+ }
+
+ /**
+ * Find out whether this transaction performed an auto-correction.
+ * @return Whether this transaction performed an auto-correction.
+ */
+ public boolean didAutoCorrect() {
+ return mDidAutoCorrect;
+ }
+}