WordComposer.java revision 66bb563535dbe3672f99f75bd71763a551444867
1923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/*
2443c360d0afdbab091994244f045f4756feaf2b4Jean-Baptiste Queru * Copyright (C) 2008 The Android Open Source Project
30fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard *
4923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * use this file except in compliance with the License. You may obtain a copy of
6923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * the License at
70fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard *
8923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0
90fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard *
10923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
11923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * License for the specific language governing permissions and limitations under
14923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * the License.
15923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
16923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
17923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectpackage com.android.inputmethod.latin;
18923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
196b1f500da451de56932a8b2a99c63857994ece85Jean Chalardimport com.android.inputmethod.keyboard.Key;
20887f11ee43ad621aa6ad93d535ab7f48dec73fc7Tadashi G. Takaokaimport com.android.inputmethod.keyboard.KeyDetector;
213708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaokaimport com.android.inputmethod.keyboard.Keyboard;
22887f11ee43ad621aa6ad93d535ab7f48dec73fc7Tadashi G. Takaoka
23923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectimport java.util.ArrayList;
24c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalardimport java.util.Arrays;
25923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
26923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/**
27923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * A place to store the currently composing word with information such as adjacent key codes as well
28923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
29923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectpublic class WordComposer {
308fbd55229243cb66c03d5ea1f79dfb39f596590dsatok
31887f11ee43ad621aa6ad93d535ab7f48dec73fc7Tadashi G. Takaoka    public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
328fbd55229243cb66c03d5ea1f79dfb39f596590dsatok    public static final int NOT_A_COORDINATE = -1;
33887f11ee43ad621aa6ad93d535ab7f48dec73fc7Tadashi G. Takaoka
34e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok    final static int N = BinaryDictionary.MAX_WORD_LENGTH;
358fbd55229243cb66c03d5ea1f79dfb39f596590dsatok
36be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard    private ArrayList<int[]> mCodes;
37be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard    private int[] mXCoordinates;
38be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard    private int[] mYCoordinates;
39be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard    private StringBuilder mTypedWord;
40be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard    private CharSequence mAutoCorrection;
414a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani
42be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard    // Cache these values for performance
434a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani    private int mCapsCount;
441c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani    private boolean mAutoCapitalized;
45117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard    private int mTrailingSingleQuotesCount;
46c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard
47923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
480b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa     * Whether the user chose to capitalize the first char of the word.
49923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
500b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    private boolean mIsFirstCharCapitalized;
51923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
52979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public WordComposer() {
53be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mCodes = new ArrayList<int[]>(N);
54be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mTypedWord = new StringBuilder(N);
55be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mXCoordinates = new int[N];
56be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mYCoordinates = new int[N];
57be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = null;
58117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        mTrailingSingleQuotesCount = 0;
59923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
60923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
61f733074aaecdfd6e89cfee2daff8a9c1233b60f1satok    public WordComposer(WordComposer source) {
629fbfd5877305ed19a20663630b498b6b3fdae942satok        init(source);
639fbfd5877305ed19a20663630b498b6b3fdae942satok    }
649fbfd5877305ed19a20663630b498b6b3fdae942satok
659fbfd5877305ed19a20663630b498b6b3fdae942satok    public void init(WordComposer source) {
66be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mCodes = new ArrayList<int[]>(source.mCodes);
67be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mTypedWord = new StringBuilder(source.mTypedWord);
68be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mXCoordinates = Arrays.copyOf(source.mXCoordinates, source.mXCoordinates.length);
69be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mYCoordinates = Arrays.copyOf(source.mYCoordinates, source.mYCoordinates.length);
70ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        mCapsCount = source.mCapsCount;
71ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
72ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        mAutoCapitalized = source.mAutoCapitalized;
73117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
74979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
75979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
76923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
77923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Clear out the keys registered so far.
78923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
79923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public void reset() {
80be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mCodes.clear();
81be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mTypedWord.setLength(0);
82be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = null;
834a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani        mCapsCount = 0;
84ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        mIsFirstCharCapitalized = false;
85117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        mTrailingSingleQuotesCount = 0;
86923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
87923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
88923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
89923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Number of keystrokes in the composing word.
90923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * @return the number of keystrokes
91923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
92ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka    public final int size() {
939159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard        return mCodes.size();
94923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
95923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
96196d82cdd740580ed79d801483dbc282be85d076Jean Chalard    public final boolean isComposingWord() {
979159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard        return mCodes.size() > 0;
98196d82cdd740580ed79d801483dbc282be85d076Jean Chalard    }
99196d82cdd740580ed79d801483dbc282be85d076Jean Chalard
100923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
101923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Returns the codes at a particular position in the word.
102923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * @param index the position in the word
103923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * @return the unicode for the pressed and surrounding keys
104923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
105923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public int[] getCodesAt(int index) {
106be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        return mCodes.get(index);
107923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
108923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
1098fbd55229243cb66c03d5ea1f79dfb39f596590dsatok    public int[] getXCoordinates() {
110be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        return mXCoordinates;
1118fbd55229243cb66c03d5ea1f79dfb39f596590dsatok    }
1128fbd55229243cb66c03d5ea1f79dfb39f596590dsatok
1138fbd55229243cb66c03d5ea1f79dfb39f596590dsatok    public int[] getYCoordinates() {
114be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        return mYCoordinates;
1158fbd55229243cb66c03d5ea1f79dfb39f596590dsatok    }
1168fbd55229243cb66c03d5ea1f79dfb39f596590dsatok
117ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka    private static boolean isFirstCharCapitalized(int index, int codePoint, boolean previous) {
118ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        if (index == 0) return Character.isUpperCase(codePoint);
119436a645ea837d36f7e0f81948d343fa6e166f33aTadashi G. Takaoka        return previous && !Character.isUpperCase(codePoint);
120ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka    }
121ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka
122923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
123923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of
124923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * the array containing unicode for adjacent keys, sorted by reducing probability/proximity.
125923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * @param codes the array of unicode values
126923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
1278fbd55229243cb66c03d5ea1f79dfb39f596590dsatok    public void add(int primaryCode, int[] codes, int x, int y) {
1289159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard        final int newIndex = mCodes.size();
1299159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard        mTypedWord.appendCodePoint(primaryCode);
130231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani        correctPrimaryJuxtapos(primaryCode, codes);
131be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mCodes.add(codes);
132ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) {
133be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard            mXCoordinates[newIndex] = x;
134be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard            mYCoordinates[newIndex] = y;
1358fbd55229243cb66c03d5ea1f79dfb39f596590dsatok        }
136ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        mIsFirstCharCapitalized = isFirstCharCapitalized(
137ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka                newIndex, primaryCode, mIsFirstCharCapitalized);
138ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        if (Character.isUpperCase(primaryCode)) mCapsCount++;
139117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) {
140117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard            ++mTrailingSingleQuotesCount;
141117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        } else {
142117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard            mTrailingSingleQuotesCount = 0;
143117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        }
144be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = null;
145923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
146923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
147923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
1486b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     * Internal method to retrieve reasonable proximity info for a character.
1496b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     */
1503708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka    private void addKeyInfo(final int codePoint, final Keyboard keyboard,
1516b1f500da451de56932a8b2a99c63857994ece85Jean Chalard            final KeyDetector keyDetector) {
1526b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        for (final Key key : keyboard.mKeys) {
1536b1f500da451de56932a8b2a99c63857994ece85Jean Chalard            if (key.mCode == codePoint) {
1546b1f500da451de56932a8b2a99c63857994ece85Jean Chalard                final int x = key.mX + key.mWidth / 2;
1556b1f500da451de56932a8b2a99c63857994ece85Jean Chalard                final int y = key.mY + key.mHeight / 2;
1566b1f500da451de56932a8b2a99c63857994ece85Jean Chalard                final int[] codes = keyDetector.newCodeArray();
157e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka                keyDetector.getKeyAndNearbyCodes(x, y, codes);
1586b1f500da451de56932a8b2a99c63857994ece85Jean Chalard                add(codePoint, codes, x, y);
1596b1f500da451de56932a8b2a99c63857994ece85Jean Chalard                return;
1606b1f500da451de56932a8b2a99c63857994ece85Jean Chalard            }
1616b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        }
1626b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        add(codePoint, new int[] { codePoint },
1636b1f500da451de56932a8b2a99c63857994ece85Jean Chalard                WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
1646b1f500da451de56932a8b2a99c63857994ece85Jean Chalard    }
1656b1f500da451de56932a8b2a99c63857994ece85Jean Chalard
1666b1f500da451de56932a8b2a99c63857994ece85Jean Chalard    /**
1676b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     * Set the currently composing word to the one passed as an argument.
1686b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
1696b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     */
1703708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka    public void setComposingWord(final CharSequence word, final Keyboard keyboard,
1716b1f500da451de56932a8b2a99c63857994ece85Jean Chalard            final KeyDetector keyDetector) {
1726b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        reset();
1736b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        final int length = word.length();
1749159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard        for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
1759159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            int codePoint = Character.codePointAt(word, i);
1766b1f500da451de56932a8b2a99c63857994ece85Jean Chalard            addKeyInfo(codePoint, keyboard, keyDetector);
1776b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        }
1786b1f500da451de56932a8b2a99c63857994ece85Jean Chalard    }
1796b1f500da451de56932a8b2a99c63857994ece85Jean Chalard
1806b1f500da451de56932a8b2a99c63857994ece85Jean Chalard    /**
1816b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     * Shortcut for the above method, this will create a new KeyDetector for the passed keyboard.
1826b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     */
1833708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka    public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
1846b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        final KeyDetector keyDetector = new KeyDetector(0);
1856b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        keyDetector.setKeyboard(keyboard, 0, 0);
1866b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        keyDetector.setProximityCorrectionEnabled(true);
1876b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        keyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
1886b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        setComposingWord(word, keyboard, keyDetector);
1896b1f500da451de56932a8b2a99c63857994ece85Jean Chalard    }
1906b1f500da451de56932a8b2a99c63857994ece85Jean Chalard
1916b1f500da451de56932a8b2a99c63857994ece85Jean Chalard    /**
192231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani     * Swaps the first and second values in the codes array if the primary code is not the first
193231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani     * value in the array but the second. This happens when the preferred key is not the key that
194231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani     * the user released the finger on.
195231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani     * @param primaryCode the preferred character
196231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani     * @param codes array of codes based on distance from touch point
197231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani     */
1988fbf29e2d54027a17993cd0d4ad486e3454b56f6Tadashi G. Takaoka    private static void correctPrimaryJuxtapos(int primaryCode, int[] codes) {
199231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani        if (codes.length < 2) return;
200231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani        if (codes[0] > 0 && codes[1] > 0 && codes[0] != primaryCode && codes[1] == primaryCode) {
201231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani            codes[1] = codes[0];
202231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani            codes[0] = primaryCode;
203231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani        }
204231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani    }
205231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani
206231cacd08075e88a2bcdf25f025206de524e880bAmith Yamasani    /**
207923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Delete the last keystroke as a result of hitting backspace.
208923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
209923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public void deleteLast() {
2109159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard        final int size = mCodes.size();
211ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        if (size > 0) {
2129159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            mCodes.remove(size - 1);
2139159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            // Note: mTypedWord.length() and mCodes.length differ when there are surrogate pairs
2149159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            final int stringBuilderLength = mTypedWord.length();
2159159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            if (stringBuilderLength < size) {
2169159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard                throw new RuntimeException(
2179159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard                        "In WordComposer: mCodes and mTypedWords have non-matching lengths");
2189159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            }
2199159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            final int lastChar = mTypedWord.codePointBefore(stringBuilderLength);
2209159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            if (Character.isSupplementaryCodePoint(lastChar)) {
2219159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard                mTypedWord.delete(stringBuilderLength - 2, stringBuilderLength);
2229159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            } else {
2239159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard                mTypedWord.deleteCharAt(stringBuilderLength - 1);
2249159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            }
225ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka            if (Character.isUpperCase(lastChar)) mCapsCount--;
226d1a8e3088bb6267a31e3351d304796d1507e3af6Tadashi G. Takaoka        }
2279159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard        // We may have deleted the last one.
2289159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard        if (0 == mCodes.size()) {
229ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka            mIsFirstCharCapitalized = false;
230117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        }
231117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        if (mTrailingSingleQuotesCount > 0) {
232117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard            --mTrailingSingleQuotesCount;
233c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard        } else {
234825e2bbd910cce3055a4ca808d3744bc0b2ceddaJean Chalard            int i = mTypedWord.length();
235825e2bbd910cce3055a4ca808d3744bc0b2ceddaJean Chalard            while (i > 0) {
236825e2bbd910cce3055a4ca808d3744bc0b2ceddaJean Chalard                i = mTypedWord.offsetByCodePoints(i, -1);
237be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard                if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
238117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard                ++mTrailingSingleQuotesCount;
239117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard            }
2408fbd55229243cb66c03d5ea1f79dfb39f596590dsatok        }
241be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = null;
242923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
243923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
244923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
245923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Returns the word as it was typed, without any correction applied.
246117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard     * @return the word that was typed so far. Never returns null.
247923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
2485c08151c227d98031abe27c3f0a8f43a7126ae9dJean Chalard    public String getTypedWord() {
249be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        return mTypedWord.toString();
250923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
251923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
252923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
253923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Whether or not the user typed a capital letter as the first letter in the word
254923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * @return capitalization preference
255923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
2560b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    public boolean isFirstCharCapitalized() {
2570b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa        return mIsFirstCharCapitalized;
258923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
2590b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa
260117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard    public int trailingSingleQuotesCount() {
261117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        return mTrailingSingleQuotesCount;
262c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard    }
263c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard
2640b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    /**
2650b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa     * Whether or not all of the user typed chars are upper case
2660b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa     * @return true if all user typed chars are upper case, false otherwise
2670b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa     */
2680b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    public boolean isAllUpperCase() {
2690b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa        return (mCapsCount > 0) && (mCapsCount == size());
2700b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    }
2710b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa
272923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
2734a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani     * Returns true if more than one character is upper case, otherwise returns false.
2744a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani     */
2754a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani    public boolean isMostlyCaps() {
2764a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani        return mCapsCount > 1;
2774a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani    }
2781c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani
2790fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard    /**
2801c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani     * Saves the reason why the word is capitalized - whether it was automatic or
2811c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani     * due to the user hitting shift in the middle of a sentence.
2821c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani     * @param auto whether it was an automatic capitalization due to start of sentence
2831c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani     */
2841c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani    public void setAutoCapitalized(boolean auto) {
2851c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani        mAutoCapitalized = auto;
2861c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani    }
2871c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani
2881c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani    /**
2891c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani     * Returns whether the word was automatically capitalized.
2901c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani     * @return whether the word was automatically capitalized
2911c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani     */
2921c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani    public boolean isAutoCapitalized() {
2931c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani        return mAutoCapitalized;
2941c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani    }
295117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard
296117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard    /**
297117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard     * Sets the auto-correction for this word.
298117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard     */
299117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard    public void setAutoCorrection(final CharSequence correction) {
300be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = correction;
301117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard    }
302117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard
303117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard    /**
304f7d6517d6b1a1dd88e2142e1a15703bb839be01bJean Chalard     * @return the auto-correction for this word, or null if none.
305117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard     */
306117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard    public CharSequence getAutoCorrectionOrNull() {
307be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        return mAutoCorrection;
308117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard    }
309c73c26790fa9dcd836a918774d6efa39a05c0152Jean Chalard
310267563d1bb4d8091293fbd8774f0f95ef59f03c4Jean Chalard    // `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
31166bb563535dbe3672f99f75bd71763a551444867Jean Chalard    public LastComposedWord commitWord(final int type, final String committedWord,
31266bb563535dbe3672f99f75bd71763a551444867Jean Chalard            final int separatorCode) {
3130d0f01da674e89e294d14061837711996dc5a693Ken Wakasa        // Note: currently, we come here whenever we commit a word. If it's any *other* kind than
314cf9d92629cae88273805eaf7984fcfdd8afd11f5Jean Chalard        // DECIDED_WORD, we should deactivate the last composed word so that we don't attempt to
315cf9d92629cae88273805eaf7984fcfdd8afd11f5Jean Chalard        // cancel later.
3160fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard        // If it's a DECIDED_WORD, it may be an actual auto-correction by the IME, or what the user
3170fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard        // typed because the IME decided *not* to auto-correct for whatever reason.
3180fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard        // Ideally we would also null it when it was a DECIDED_WORD that was not an auto-correct.
3190fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard        // As it happens these two cases should behave differently, because the former can be
3200fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard        // canceled while the latter can't. Currently, we figure this out in
3212712f23acbb197af3b125da4cc47108e71b7446dJean Chalard        // LastComposedWord#didAutoCorrectToAnotherWord with #equals(). It would be marginally
3222712f23acbb197af3b125da4cc47108e71b7446dJean Chalard        // cleaner to do it here, but it would be slower (since we would #equals() for each commit,
3232712f23acbb197af3b125da4cc47108e71b7446dJean Chalard        // instead of only on cancel), and ultimately we want to figure it out even earlier anyway.
324a7f2500001c53dc5a6de9c2525a75229cc7c6645Jean Chalard        final ArrayList<int[]> codes = mCodes;
325a7f2500001c53dc5a6de9c2525a75229cc7c6645Jean Chalard        final int[] xCoordinates = mXCoordinates;
326a7f2500001c53dc5a6de9c2525a75229cc7c6645Jean Chalard        final int[] yCoordinates = mYCoordinates;
327a7f2500001c53dc5a6de9c2525a75229cc7c6645Jean Chalard        mCodes = new ArrayList<int[]>(N);
328a7f2500001c53dc5a6de9c2525a75229cc7c6645Jean Chalard        mXCoordinates = new int[N];
329a7f2500001c53dc5a6de9c2525a75229cc7c6645Jean Chalard        mYCoordinates = new int[N];
330a7f2500001c53dc5a6de9c2525a75229cc7c6645Jean Chalard        final LastComposedWord lastComposedWord = new LastComposedWord(codes,
33166bb563535dbe3672f99f75bd71763a551444867Jean Chalard                xCoordinates, yCoordinates, mTypedWord.toString(), committedWord, separatorCode);
332449415c72f437f523a49a9ccfcde8a3c0f583a18Jean Chalard        if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD) {
333449415c72f437f523a49a9ccfcde8a3c0f583a18Jean Chalard            lastComposedWord.deactivate();
334449415c72f437f523a49a9ccfcde8a3c0f583a18Jean Chalard        }
335be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mTypedWord.setLength(0);
336be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = null;
3371f8fc62ccb5018716457dc309ab11ad3e1506ad1Jean Chalard        return lastComposedWord;
338c73c26790fa9dcd836a918774d6efa39a05c0152Jean Chalard    }
339c73c26790fa9dcd836a918774d6efa39a05c0152Jean Chalard
3402712f23acbb197af3b125da4cc47108e71b7446dJean Chalard    public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
341be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mCodes = lastComposedWord.mCodes;
342be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mXCoordinates = lastComposedWord.mXCoordinates;
343be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mYCoordinates = lastComposedWord.mYCoordinates;
344be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mTypedWord.setLength(0);
345be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mTypedWord.append(lastComposedWord.mTypedWord);
346cf9d92629cae88273805eaf7984fcfdd8afd11f5Jean Chalard        mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
3479e8761c4402ddc11c942ed2e583bd7d58f70c5eaJean Chalard    }
348923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project}
349