aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java51
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java5
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java86
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java2
-rw-r--r--java/src/com/android/inputmethod/latin/CapsModeUtils.java266
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java4
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java55
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java2
-rw-r--r--java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java2
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java2
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsValues.java5
-rw-r--r--java/src/com/android/inputmethod/latin/StringUtils.java263
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java10
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java2
-rw-r--r--java/src/com/android/inputmethod/research/LogStatement.java4
-rw-r--r--java/src/com/android/inputmethod/research/LogUnit.java8
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLog.java13
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java20
21 files changed, 473 insertions, 340 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 509068ac4..b1813a141 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -53,8 +53,6 @@ public final class KeySpecParser {
private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
// Constants for parsing.
- private static int COMMA = ',';
- private static final char ESCAPE_CHAR = '\\';
private static final char LABEL_END = '|';
private static final String PREFIX_TEXT = "!text/";
static final String PREFIX_ICON = "!icon/";
@@ -80,14 +78,14 @@ public final class KeySpecParser {
}
private static String parseEscape(final String text) {
- if (text.indexOf(ESCAPE_CHAR) < 0) {
+ if (text.indexOf(Constants.CSV_ESCAPE) < 0) {
return text;
}
final int length = text.length();
final StringBuilder sb = new StringBuilder();
for (int pos = 0; pos < length; pos++) {
final char c = text.charAt(pos);
- if (c == ESCAPE_CHAR && pos + 1 < length) {
+ if (c == Constants.CSV_ESCAPE && pos + 1 < length) {
// Skip escape char
pos++;
sb.append(text.charAt(pos));
@@ -99,7 +97,7 @@ public final class KeySpecParser {
}
private static int indexOfLabelEnd(final String moreKeySpec, final int start) {
- if (moreKeySpec.indexOf(ESCAPE_CHAR, start) < 0) {
+ if (moreKeySpec.indexOf(Constants.CSV_ESCAPE, start) < 0) {
final int end = moreKeySpec.indexOf(LABEL_END, start);
if (end == 0) {
throw new KeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec);
@@ -109,7 +107,7 @@ public final class KeySpecParser {
final int length = moreKeySpec.length();
for (int pos = start; pos < length; pos++) {
final char c = moreKeySpec.charAt(pos);
- if (c == ESCAPE_CHAR && pos + 1 < length) {
+ if (c == Constants.CSV_ESCAPE && pos + 1 < length) {
// Skip escape char
pos++;
} else if (c == LABEL_END) {
@@ -353,7 +351,7 @@ public final class KeySpecParser {
final String name = text.substring(pos + prefixLen, end);
sb.append(textsSet.getText(name));
pos = end - 1;
- } else if (c == ESCAPE_CHAR) {
+ } else if (c == Constants.CSV_ESCAPE) {
if (sb != null) {
// Append both escape character and escaped character.
sb.append(text.substring(pos, Math.min(pos + 2, size)));
@@ -385,45 +383,6 @@ public final class KeySpecParser {
return size;
}
- public static String[] parseCsvString(final String rawText, final KeyboardTextsSet textsSet) {
- final String text = resolveTextReference(rawText, textsSet);
- final int size = text.length();
- if (size == 0) {
- return null;
- }
- if (StringUtils.codePointCount(text) == 1) {
- return text.codePointAt(0) == COMMA ? null : new String[] { text };
- }
-
- ArrayList<String> list = null;
- int start = 0;
- for (int pos = 0; pos < size; pos++) {
- final char c = text.charAt(pos);
- if (c == COMMA) {
- // Skip empty entry.
- if (pos - start > 0) {
- if (list == null) {
- list = CollectionUtils.newArrayList();
- }
- list.add(text.substring(start, pos));
- }
- // Skip comma
- start = pos + 1;
- } else if (c == ESCAPE_CHAR) {
- // Skip escape character and escaped character.
- pos++;
- }
- }
- final String remain = (size - start > 0) ? text.substring(start) : null;
- if (list == null) {
- return remain != null ? new String[] { remain } : null;
- }
- if (remain != null) {
- list.add(remain);
- }
- return list.toArray(new String[list.size()]);
- }
-
public static int getIntValue(final String[] moreKeys, final String key,
final int defaultValue) {
if (moreKeys == null) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
index fe75e20ae..5db3ebbd1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
@@ -18,6 +18,8 @@ package com.android.inputmethod.keyboard.internal;
import android.content.res.TypedArray;
+import com.android.inputmethod.latin.StringUtils;
+
public abstract class KeyStyle {
private final KeyboardTextsSet mTextsSet;
@@ -39,7 +41,8 @@ public abstract class KeyStyle {
protected String[] parseStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) {
- return KeySpecParser.parseCsvString(a.getString(index), mTextsSet);
+ final String text = KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
+ return StringUtils.parseCsvString(text);
}
return null;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index e087a4565..ab851bd64 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -164,10 +164,10 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
try {
parseKeyboard(parser);
} catch (XmlPullParserException e) {
- Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
+ Log.w(BUILDER_TAG, "keyboard XML parse error", e);
throw new IllegalArgumentException(e);
} catch (IOException e) {
- Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
+ Log.w(BUILDER_TAG, "keyboard XML parse error", e);
throw new RuntimeException(e);
} finally {
parser.close();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index 6ad9d286f..0ec6b0176 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -74,6 +74,7 @@ public final class KeyboardCodesSet {
private static final int CODE_LEFT_CURLY_BRACKET = '{';
private static final int CODE_RIGHT_CURLY_BRACKET = '}';
+ // This array should be aligned with the array RTL below.
private static final int[] DEFAULT = {
Constants.CODE_TAB,
Constants.CODE_ENTER,
@@ -117,6 +118,7 @@ public final class KeyboardCodesSet {
DEFAULT[12],
DEFAULT[13],
DEFAULT[14],
+ DEFAULT[15],
CODE_RIGHT_PARENTHESIS,
CODE_LEFT_PARENTHESIS,
CODE_GREATER_THAN_SIGN,
@@ -140,6 +142,9 @@ public final class KeyboardCodesSet {
};
static {
+ if (DEFAULT.length != RTL.length) {
+ throw new RuntimeException("Internal inconsistency");
+ }
for (int i = 0; i < ID_TO_NAME.length; i++) {
sNameToIdMap.put(ID_TO_NAME[i], i);
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index d4cdc6c5c..165116ae0 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -16,14 +16,19 @@
package com.android.inputmethod.latin;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
+import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.latin.DictionaryInfoUtils.DictionaryInfo;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
@@ -32,6 +37,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -68,6 +74,10 @@ public final class BinaryDictionaryFileDumper {
// The path fragment to append after the client ID for dictionary info requests.
private static final String QUERY_PATH_DICT_INFO = "dict";
+ // The path fragment to append after the client ID for updating the metadata URI.
+ private static final String QUERY_PATH_METADATA = "metadata";
+ private static final String INSERT_METADATA_CLIENT_ID_COLUMN = "clientid";
+ private static final String INSERT_METADATA_METADATA_URI_COLUMN = "uri";
// Prevents this class to be accidentally instantiated.
private BinaryDictionaryFileDumper() {
@@ -91,25 +101,31 @@ public final class BinaryDictionaryFileDumper {
*/
private static List<WordListInfo> getWordListWordListInfos(final Locale locale,
final Context context, final boolean hasDefaultWordList) {
- final ContentResolver resolver = context.getContentResolver();
final String clientId = context.getString(R.string.dictionary_pack_client_id);
final Uri.Builder builder = getProviderUriBuilder(clientId);
builder.appendPath(QUERY_PATH_DICT_INFO);
builder.appendPath(locale.toString());
builder.appendQueryParameter(QUERY_PARAMETER_PROTOCOL, QUERY_PARAMETER_PROTOCOL_VALUE);
if (!hasDefaultWordList) {
- builder.appendQueryParameter(QUERY_PARAMETER_MAY_PROMPT_USER, QUERY_PARAMETER_TRUE);
+ builder.appendQueryParameter(QUERY_PARAMETER_MAY_PROMPT_USER,
+ QUERY_PARAMETER_TRUE);
}
final Uri dictionaryPackUri = builder.build();
- final Cursor c = resolver.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null, null);
- if (null == c) return Collections.<WordListInfo>emptyList();
- if (c.getCount() <= 0 || !c.moveToFirst()) {
- c.close();
- return Collections.<WordListInfo>emptyList();
- }
-
+ final ContentProviderClient client = context.getContentResolver().
+ acquireContentProviderClient(getProviderUriBuilder("").build());
+ if (null == client) return Collections.<WordListInfo>emptyList();
try {
+ final Cursor c = client.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null,
+ null);
+ if (null == c) {
+ reinitializeClientRecordInDictionaryContentProvider(context, client, clientId);
+ return Collections.<WordListInfo>emptyList();
+ }
+ if (c.getCount() <= 0 || !c.moveToFirst()) {
+ c.close();
+ return Collections.<WordListInfo>emptyList();
+ }
final List<WordListInfo> list = CollectionUtils.newArrayList();
do {
final String wordListId = c.getString(0);
@@ -119,11 +135,20 @@ public final class BinaryDictionaryFileDumper {
} while (c.moveToNext());
c.close();
return list;
+ } catch (RemoteException e) {
+ // The documentation is unclear as to in which cases this may happen, but it probably
+ // happens when the content provider got suddenly killed because it crashed or because
+ // the user disabled it through Settings.
+ Log.e(TAG, "RemoteException: communication with the dictionary pack cut", e);
+ return Collections.<WordListInfo>emptyList();
} catch (Exception e) {
- // Just in case we hit a problem in communication with the dictionary pack.
- // We don't want to die.
- Log.e(TAG, "Exception communicating with the dictionary pack : " + e);
+ // A crash here is dangerous because crashing here would brick any encrypted device -
+ // we need the keyboard to be up and working to enter the password, so we don't want
+ // to die no matter what. So let's be as safe as possible.
+ Log.e(TAG, "Unexpected exception communicating with the dictionary pack", e);
return Collections.<WordListInfo>emptyList();
+ } finally {
+ client.release();
}
}
@@ -237,7 +262,7 @@ public final class BinaryDictionaryFileDumper {
return AssetFileAddress.makeFromFileName(finalFileName);
} catch (Exception e) {
if (DEBUG) {
- Log.i(TAG, "Can't open word list in mode " + mode + " : " + e);
+ Log.i(TAG, "Can't open word list in mode " + mode, e);
}
if (null != outputFile) {
// This may or may not fail. The file may not have been created if the
@@ -255,12 +280,12 @@ public final class BinaryDictionaryFileDumper {
if (null != decryptedStream) decryptedStream.close();
if (null != bufferedInputStream) bufferedInputStream.close();
} catch (Exception e) {
- Log.e(TAG, "Exception while closing a file descriptor : " + e);
+ Log.e(TAG, "Exception while closing a file descriptor", e);
}
try {
if (null != bufferedOutputStream) bufferedOutputStream.close();
} catch (Exception e) {
- Log.e(TAG, "Exception while closing a file : " + e);
+ Log.e(TAG, "Exception while closing a file", e);
}
}
}
@@ -335,4 +360,35 @@ public final class BinaryDictionaryFileDumper {
output.write(buffer, 0, readBytes);
input.close();
}
+
+ private static void reinitializeClientRecordInDictionaryContentProvider(final Context context,
+ final ContentProviderClient client, final String clientId) throws RemoteException {
+ final String metadataFileUri = context.getString(R.string.dictionary_pack_metadata_uri);
+ if (TextUtils.isEmpty(metadataFileUri)) return;
+ // Tell the content provider to reset all information about this client id
+ final Uri metadataContentUri = getProviderUriBuilder(clientId)
+ .appendPath(QUERY_PATH_METADATA)
+ .appendQueryParameter(QUERY_PARAMETER_PROTOCOL, QUERY_PARAMETER_PROTOCOL_VALUE)
+ .build();
+ client.delete(metadataContentUri, null, null);
+ // Update the metadata URI
+ final ContentValues metadataValues = new ContentValues();
+ metadataValues.put(INSERT_METADATA_CLIENT_ID_COLUMN, clientId);
+ metadataValues.put(INSERT_METADATA_METADATA_URI_COLUMN, metadataFileUri);
+ client.insert(metadataContentUri, metadataValues);
+
+ // Update the dictionary list.
+ final Uri dictionaryContentUriBase = getProviderUriBuilder(clientId)
+ .appendPath(QUERY_PATH_DICT_INFO)
+ .appendQueryParameter(QUERY_PARAMETER_PROTOCOL, QUERY_PARAMETER_PROTOCOL_VALUE)
+ .build();
+ final ArrayList<DictionaryInfo> dictionaryList =
+ DictionaryInfoUtils.getCurrentDictionaryFileNameAndVersionInfo(context);
+ final int length = dictionaryList.size();
+ for (int i = 0; i < length; ++i) {
+ final DictionaryInfo info = dictionaryList.get(i);
+ client.insert(Uri.withAppendedPath(dictionaryContentUriBase, info.mId),
+ info.toContentValues());
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 1cdc3b564..a96738b3e 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -210,7 +210,7 @@ final class BinaryDictionaryGetter {
}
}
} catch (java.io.IOException e) {
- Log.e(TAG, "IOException trying to cleanup files : " + e);
+ Log.e(TAG, "IOException trying to cleanup files", e);
}
}
diff --git a/java/src/com/android/inputmethod/latin/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/CapsModeUtils.java
new file mode 100644
index 000000000..1012cd519
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/CapsModeUtils.java
@@ -0,0 +1,266 @@
+/*
+ * 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 com.android.inputmethod.latin;
+
+import android.text.InputType;
+import android.text.TextUtils;
+
+import java.util.Locale;
+
+public final class CapsModeUtils {
+ private CapsModeUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ /**
+ * Apply an auto-caps mode to a string.
+ *
+ * This intentionally does NOT apply manual caps mode. It only changes the capitalization if
+ * the mode is one of the auto-caps modes.
+ * @param s The string to capitalize.
+ * @param capitalizeMode The mode in which to capitalize.
+ * @param locale The locale for capitalizing.
+ * @return The capitalized string.
+ */
+ public static String applyAutoCapsMode(final String s, final int capitalizeMode,
+ final Locale locale) {
+ if (WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == capitalizeMode) {
+ return s.toUpperCase(locale);
+ } else if (WordComposer.CAPS_MODE_AUTO_SHIFTED == capitalizeMode) {
+ return StringUtils.toTitleCase(s, locale);
+ } else {
+ return s;
+ }
+ }
+
+ /**
+ * Return whether a constant represents an auto-caps mode (either auto-shift or auto-shift-lock)
+ * @param mode The mode to test for
+ * @return true if this represents an auto-caps mode, false otherwise
+ */
+ public static boolean isAutoCapsMode(final int mode) {
+ return WordComposer.CAPS_MODE_AUTO_SHIFTED == mode
+ || WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == mode;
+ }
+
+ /**
+ * Determine what caps mode should be in effect at the current offset in
+ * the text. Only the mode bits set in <var>reqModes</var> will be
+ * checked. Note that the caps mode flags here are explicitly defined
+ * to match those in {@link InputType}.
+ *
+ * This code is a straight copy of TextUtils.getCapsMode (modulo namespace and formatting
+ * issues). This will change in the future as we simplify the code for our use and fix bugs.
+ *
+ * @param cs The text that should be checked for caps modes.
+ * @param reqModes The modes to be checked: may be any combination of
+ * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
+ * {@link TextUtils#CAP_MODE_SENTENCES}.
+ * @param locale The locale to consider for capitalization rules
+ * @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs
+ *
+ * @return Returns the actual capitalization modes that can be in effect
+ * at the current position, which is any combination of
+ * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
+ * {@link TextUtils#CAP_MODE_SENTENCES}.
+ */
+ public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale,
+ final boolean hasSpaceBefore) {
+ // Quick description of what we want to do:
+ // CAP_MODE_CHARACTERS is always on.
+ // CAP_MODE_WORDS is on if there is some whitespace before the cursor.
+ // CAP_MODE_SENTENCES is on if there is some whitespace before the cursor, and the end
+ // of a sentence just before that.
+ // We ignore opening parentheses and the like just before the cursor for purposes of
+ // finding whitespace for WORDS and SENTENCES modes.
+ // The end of a sentence ends with a period, question mark or exclamation mark. If it's
+ // a period, it also needs not to be an abbreviation, which means it also needs to either
+ // be immediately preceded by punctuation, or by a string of only letters with single
+ // periods interleaved.
+
+ // Step 1 : check for cap MODE_CHARACTERS. If it's looked for, it's always on.
+ if ((reqModes & (TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES)) == 0) {
+ // Here we are not looking for MODE_WORDS or MODE_SENTENCES, so since we already
+ // evaluated MODE_CHARACTERS, we can return.
+ return TextUtils.CAP_MODE_CHARACTERS & reqModes;
+ }
+
+ // Step 2 : Skip (ignore at the end of input) any opening punctuation. This includes
+ // opening parentheses, brackets, opening quotes, everything that *opens* a span of
+ // text in the linguistic sense. In RTL languages, this is still an opening sign, although
+ // it may look like a right parenthesis for example. We also include double quote and
+ // single quote since they aren't start punctuation in the unicode sense, but should still
+ // be skipped for English. TODO: does this depend on the language?
+ int i;
+ if (hasSpaceBefore) {
+ i = cs.length() + 1;
+ } else {
+ for (i = cs.length(); i > 0; i--) {
+ final char c = cs.charAt(i - 1);
+ if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE
+ && Character.getType(c) != Character.START_PUNCTUATION) {
+ break;
+ }
+ }
+ }
+
+ // We are now on the character that precedes any starting punctuation, so in the most
+ // frequent case this will be whitespace or a letter, although it may occasionally be a
+ // start of line, or some symbol.
+
+ // Step 3 : Search for the start of a paragraph. From the starting point computed in step 2,
+ // we go back over any space or tab char sitting there. We find the start of a paragraph
+ // if the first char that's not a space or tab is a start of line (as in \n, start of text,
+ // or some other similar characters).
+ int j = i;
+ char prevChar = Constants.CODE_SPACE;
+ if (hasSpaceBefore) --j;
+ while (j > 0) {
+ prevChar = cs.charAt(j - 1);
+ if (!Character.isSpaceChar(prevChar) && prevChar != Constants.CODE_TAB) break;
+ j--;
+ }
+ if (j <= 0 || Character.isWhitespace(prevChar)) {
+ // There are only spacing chars between the start of the paragraph and the cursor,
+ // defined as a isWhitespace() char that is neither a isSpaceChar() nor a tab. Both
+ // MODE_WORDS and MODE_SENTENCES should be active.
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+ | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ }
+ if (i == j) {
+ // If we don't have whitespace before index i, it means neither MODE_WORDS
+ // nor mode sentences should be on so we can return right away.
+ return TextUtils.CAP_MODE_CHARACTERS & reqModes;
+ }
+ if ((reqModes & TextUtils.CAP_MODE_SENTENCES) == 0) {
+ // Here we know we have whitespace before the cursor (if not, we returned in the above
+ // if i == j clause), so we need MODE_WORDS to be on. And we don't need to evaluate
+ // MODE_SENTENCES so we can return right away.
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ }
+ // Please note that because of the reqModes & CAP_MODE_SENTENCES test a few lines above,
+ // we know that MODE_SENTENCES is being requested.
+
+ // Step 4 : Search for MODE_SENTENCES.
+ // English is a special case in that "American typography" rules, which are the most common
+ // in English, state that a sentence terminator immediately following a quotation mark
+ // should be swapped with it and de-duplicated (included in the quotation mark),
+ // e.g. <<Did he say, "let's go home?">>
+ // No other language has such a rule as far as I know, instead putting inside the quotation
+ // mark as the exact thing quoted and handling the surrounding punctuation independently,
+ // e.g. <<Did he say, "let's go home"?>>
+ // Hence, specifically for English, we treat this special case here.
+ if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) {
+ for (; j > 0; j--) {
+ // Here we look to go over any closing punctuation. This is because in dominant
+ // variants of English, the final period is placed within double quotes and maybe
+ // other closing punctuation signs. This is generally not true in other languages.
+ final char c = cs.charAt(j - 1);
+ if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE
+ && Character.getType(c) != Character.END_PUNCTUATION) {
+ break;
+ }
+ }
+ }
+
+ if (j <= 0) return TextUtils.CAP_MODE_CHARACTERS & reqModes;
+ char c = cs.charAt(--j);
+
+ // We found the next interesting chunk of text ; next we need to determine if it's the
+ // end of a sentence. If we have a question mark or an exclamation mark, it's the end of
+ // a sentence. If it's neither, the only remaining case is the period so we get the opposite
+ // case out of the way.
+ if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ }
+ if (c != Constants.CODE_PERIOD || j <= 0) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ }
+
+ // We found out that we have a period. We need to determine if this is a full stop or
+ // otherwise sentence-ending period, or an abbreviation like "e.g.". An abbreviation
+ // looks like (\w\.){2,}
+ // To find out, we will have a simple state machine with the following states :
+ // START, WORD, PERIOD, ABBREVIATION
+ // On START : (just before the first period)
+ // letter => WORD
+ // whitespace => end with no caps (it was a stand-alone period)
+ // otherwise => end with caps (several periods/symbols in a row)
+ // On WORD : (within the word just before the first period)
+ // letter => WORD
+ // period => PERIOD
+ // otherwise => end with caps (it was a word with a full stop at the end)
+ // On PERIOD : (period within a potential abbreviation)
+ // letter => LETTER
+ // otherwise => end with caps (it was not an abbreviation)
+ // On LETTER : (letter within a potential abbreviation)
+ // letter => LETTER
+ // period => PERIOD
+ // otherwise => end with no caps (it was an abbreviation)
+ // "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This
+ // should capitalize.
+
+ final int START = 0;
+ final int WORD = 1;
+ final int PERIOD = 2;
+ final int LETTER = 3;
+ final int caps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+ | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ final int noCaps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ int state = START;
+ while (j > 0) {
+ c = cs.charAt(--j);
+ switch (state) {
+ case START:
+ if (Character.isLetter(c)) {
+ state = WORD;
+ } else if (Character.isWhitespace(c)) {
+ return noCaps;
+ } else {
+ return caps;
+ }
+ break;
+ case WORD:
+ if (Character.isLetter(c)) {
+ state = WORD;
+ } else if (c == Constants.CODE_PERIOD) {
+ state = PERIOD;
+ } else {
+ return caps;
+ }
+ break;
+ case PERIOD:
+ if (Character.isLetter(c)) {
+ state = LETTER;
+ } else {
+ return caps;
+ }
+ break;
+ case LETTER:
+ if (Character.isLetter(c)) {
+ state = LETTER;
+ } else if (c == Constants.CODE_PERIOD) {
+ state = PERIOD;
+ } else {
+ return noCaps;
+ }
+ }
+ }
+ // Here we arrived at the start of the line. This should behave exactly like whitespace.
+ return (START == state || LETTER == state) ? noCaps : caps;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 85cc55232..422448edf 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -215,6 +215,10 @@ public final class Constants {
}
}
+ // Constants for CSV parsing.
+ public static final char CSV_SEPARATOR = ',';
+ public static final char CSV_ESCAPE = '\\';
+
private Constants() {
// This utility class is not publicly instantiable.
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
index 8f16a8e4d..d2a946bf5 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
@@ -16,9 +16,11 @@
package com.android.inputmethod.latin;
+import android.content.ContentValues;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
+import android.text.format.DateUtils;
import android.util.Log;
import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
@@ -39,19 +41,40 @@ public class DictionaryInfoUtils {
private static final String RESOURCE_PACKAGE_NAME =
DictionaryInfoUtils.class.getPackage().getName();
private static final String DEFAULT_MAIN_DICT = "main";
+ private static final String ID_CATEGORY_SEPARATOR =
+ BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR;
private static final String MAIN_DICT_PREFIX = "main_";
// 6 digits - unicode is limited to 21 bits
private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
public static class DictionaryInfo {
+ private static final String LOCALE_COLUMN = "locale";
+ private static final String WORDLISTID_COLUMN = "id";
+ private static final String LOCAL_FILENAME_COLUMN = "filename";
+ private static final String DATE_COLUMN = "date";
+ private static final String FILESIZE_COLUMN = "filesize";
+ private static final String VERSION_COLUMN = "version";
public final Locale mLocale;
public final AssetFileAddress mFileAddress;
public final int mVersion;
+ public final String mId;
public DictionaryInfo(final Locale locale, final AssetFileAddress fileAddress,
final int version) {
mLocale = locale;
mFileAddress = fileAddress;
mVersion = version;
+ mId = DEFAULT_MAIN_DICT + ID_CATEGORY_SEPARATOR + mLocale;
+ }
+ public ContentValues toContentValues() {
+ final ContentValues values = new ContentValues();
+ values.put(WORDLISTID_COLUMN, mId);
+ values.put(LOCALE_COLUMN, mLocale.toString());
+ values.put(LOCAL_FILENAME_COLUMN, mFileAddress.mFilename);
+ values.put(DATE_COLUMN,
+ new File(mFileAddress.mFilename).lastModified() / DateUtils.SECOND_IN_MILLIS);
+ values.put(FILESIZE_COLUMN, mFileAddress.mLength);
+ values.put(VERSION_COLUMN, mVersion);
+ return values;
}
}
@@ -284,21 +307,23 @@ public class DictionaryInfoUtils {
// Retrieve downloaded dictionaries
final File[] directoryList = getCachedDirectoryList(context);
- for (final File directory : directoryList) {
- final String localeString = getWordListIdFromFileName(directory.getName());
- File[] dicts = BinaryDictionaryGetter.getCachedWordLists(localeString, context);
- for (final File dict : dicts) {
- final String wordListId = getWordListIdFromFileName(dict.getName());
- if (!DictionaryInfoUtils.isMainWordListId(wordListId)) continue;
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
- final AssetFileAddress fileAddress = AssetFileAddress.makeFromFile(dict);
- final DictionaryInfo dictionaryInfo =
- createDictionaryInfoFromFileAddress(fileAddress);
- // Protect against cases of a less-specific dictionary being found, like an
- // en dictionary being used for an en_US locale. In this case, the en dictionary
- // should be used for en_US but discounted for listing purposes.
- if (!dictionaryInfo.mLocale.equals(locale)) continue;
- addOrUpdateDictInfo(dictList, dictionaryInfo);
+ if (null != directoryList) {
+ for (final File directory : directoryList) {
+ final String localeString = getWordListIdFromFileName(directory.getName());
+ File[] dicts = BinaryDictionaryGetter.getCachedWordLists(localeString, context);
+ for (final File dict : dicts) {
+ final String wordListId = getWordListIdFromFileName(dict.getName());
+ if (!DictionaryInfoUtils.isMainWordListId(wordListId)) continue;
+ final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+ final AssetFileAddress fileAddress = AssetFileAddress.makeFromFile(dict);
+ final DictionaryInfo dictionaryInfo =
+ createDictionaryInfoFromFileAddress(fileAddress);
+ // Protect against cases of a less-specific dictionary being found, like an
+ // en dictionary being used for an en_US locale. In this case, the en dictionary
+ // should be used for en_US but discounted for listing purposes.
+ if (!dictionaryInfo.mLocale.equals(locale)) continue;
+ addOrUpdateDictInfo(dictList, dictionaryInfo);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 28ed88a73..97dc6a8ac 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -321,9 +321,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
tempFile.renameTo(file);
clearFusionDictionary();
} catch (IOException e) {
- Log.e(TAG, "IO exception while writing file: " + e);
+ Log.e(TAG, "IO exception while writing file", e);
} catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported format: " + e);
+ Log.e(TAG, "Unsupported format", e);
} finally {
if (out != null) {
try {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 1c49bb0cc..73ace2bfa 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1228,7 +1228,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
return;
}
final String wordToEdit;
- if (StringUtils.isAutoCapsMode(mLastComposedWord.mCapitalizedMode)) {
+ if (CapsModeUtils.isAutoCapsMode(mLastComposedWord.mCapitalizedMode)) {
wordToEdit = word.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
} else {
wordToEdit = word;
diff --git a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
index 9fdbf8703..a8800007a 100644
--- a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
+++ b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
@@ -96,7 +96,7 @@ public final class PositionalInfoForUserDictPendingAddition {
if (currentCursorPosition != mCursorPos) return true;
// We have made all the checks : do the replacement and report success
// If this was auto-capitalized, we need to restore the case before committing
- final String wordWithCaseFixed = StringUtils.applyAutoCapsMode(mActualWordBeingAdded,
+ final String wordWithCaseFixed = CapsModeUtils.applyAutoCapsMode(mActualWordBeingAdded,
mCapitalizedMode, locale);
connection.setComposingRegion(currentCursorPosition - mOriginalWord.length(),
currentCursorPosition);
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 521cea977..7300dbd23 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -221,7 +221,7 @@ public final class RichInputConnection {
}
// This never calls InputConnection#getCapsMode - in fact, it's a static method that
// never blocks or initiates IPC.
- return StringUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType, locale,
+ return CapsModeUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType, locale,
hasSpaceBefore);
}
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 728f6b281..d05868029 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -89,8 +89,8 @@ public final class SettingsValues {
mWordConnectors =
StringUtils.toCodePointArray(res.getString(R.string.symbols_word_connectors));
Arrays.sort(mWordConnectors);
- final String[] suggestPuncsSpec = KeySpecParser.parseCsvString(
- res.getString(R.string.suggested_punctuations), null);
+ final String[] suggestPuncsSpec = StringUtils.parseCsvString(res.getString(
+ R.string.suggested_punctuations));
mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
mWordSeparators = res.getString(R.string.symbols_word_separators);
mHintToSaveText = res.getText(R.string.hint_add_to_dictionary);
@@ -211,6 +211,7 @@ public final class SettingsValues {
final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList();
if (puncs != null) {
for (final String puncSpec : puncs) {
+ // TODO: Stop using KeySpceParser.getLabel().
puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_HARDCODED,
Dictionary.TYPE_HARDCODED));
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index d00edbe92..90c3fcdd2 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -16,7 +16,6 @@
package com.android.inputmethod.latin;
-import android.text.InputType;
import android.text.TextUtils;
import java.util.ArrayList;
@@ -103,37 +102,6 @@ public final class StringUtils {
}
}
- /**
- * Apply an auto-caps mode to a string.
- *
- * This intentionally does NOT apply manual caps mode. It only changes the capitalization if
- * the mode is one of the auto-caps modes.
- * @param s The string to capitalize.
- * @param capitalizeMode The mode in which to capitalize.
- * @param locale The locale for capitalizing.
- * @return The capitalized string.
- */
- public static String applyAutoCapsMode(final String s, final int capitalizeMode,
- final Locale locale) {
- if (WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == capitalizeMode) {
- return s.toUpperCase(locale);
- } else if (WordComposer.CAPS_MODE_AUTO_SHIFTED == capitalizeMode) {
- return toTitleCase(s, locale);
- } else {
- return s;
- }
- }
-
- /**
- * Return whether a constant represents an auto-caps mode (either auto-shift or auto-shift-lock)
- * @param mode The mode to test for
- * @return true if this represents an auto-caps mode, false otherwise
- */
- public static boolean isAutoCapsMode(final int mode) {
- return WordComposer.CAPS_MODE_AUTO_SHIFTED == mode
- || WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == mode;
- }
-
public static String toTitleCase(final String s, final Locale locale) {
if (s.length() <= 1) {
// TODO: is this really correct? Shouldn't this be s.toUpperCase()?
@@ -166,210 +134,41 @@ public final class StringUtils {
return codePoints;
}
- /**
- * Determine what caps mode should be in effect at the current offset in
- * the text. Only the mode bits set in <var>reqModes</var> will be
- * checked. Note that the caps mode flags here are explicitly defined
- * to match those in {@link InputType}.
- *
- * This code is a straight copy of TextUtils.getCapsMode (modulo namespace and formatting
- * issues). This will change in the future as we simplify the code for our use and fix bugs.
- *
- * @param cs The text that should be checked for caps modes.
- * @param reqModes The modes to be checked: may be any combination of
- * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
- * {@link TextUtils#CAP_MODE_SENTENCES}.
- * @param locale The locale to consider for capitalization rules
- * @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs
- *
- * @return Returns the actual capitalization modes that can be in effect
- * at the current position, which is any combination of
- * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
- * {@link TextUtils#CAP_MODE_SENTENCES}.
- */
- public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale,
- final boolean hasSpaceBefore) {
- // Quick description of what we want to do:
- // CAP_MODE_CHARACTERS is always on.
- // CAP_MODE_WORDS is on if there is some whitespace before the cursor.
- // CAP_MODE_SENTENCES is on if there is some whitespace before the cursor, and the end
- // of a sentence just before that.
- // We ignore opening parentheses and the like just before the cursor for purposes of
- // finding whitespace for WORDS and SENTENCES modes.
- // The end of a sentence ends with a period, question mark or exclamation mark. If it's
- // a period, it also needs not to be an abbreviation, which means it also needs to either
- // be immediately preceded by punctuation, or by a string of only letters with single
- // periods interleaved.
-
- // Step 1 : check for cap MODE_CHARACTERS. If it's looked for, it's always on.
- if ((reqModes & (TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES)) == 0) {
- // Here we are not looking for MODE_WORDS or MODE_SENTENCES, so since we already
- // evaluated MODE_CHARACTERS, we can return.
- return TextUtils.CAP_MODE_CHARACTERS & reqModes;
- }
-
- // Step 2 : Skip (ignore at the end of input) any opening punctuation. This includes
- // opening parentheses, brackets, opening quotes, everything that *opens* a span of
- // text in the linguistic sense. In RTL languages, this is still an opening sign, although
- // it may look like a right parenthesis for example. We also include double quote and
- // single quote since they aren't start punctuation in the unicode sense, but should still
- // be skipped for English. TODO: does this depend on the language?
- int i;
- if (hasSpaceBefore) {
- i = cs.length() + 1;
- } else {
- for (i = cs.length(); i > 0; i--) {
- final char c = cs.charAt(i - 1);
- if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE
- && Character.getType(c) != Character.START_PUNCTUATION) {
- break;
+ public static String[] parseCsvString(final String text) {
+ final int size = text.length();
+ if (size == 0) {
+ return null;
+ }
+ if (codePointCount(text) == 1) {
+ return text.codePointAt(0) == Constants.CSV_SEPARATOR ? null : new String[] { text };
+ }
+
+ ArrayList<String> list = null;
+ int start = 0;
+ for (int pos = 0; pos < size; pos++) {
+ final char c = text.charAt(pos);
+ if (c == Constants.CSV_SEPARATOR) {
+ // Skip empty entry.
+ if (pos - start > 0) {
+ if (list == null) {
+ list = CollectionUtils.newArrayList();
+ }
+ list.add(text.substring(start, pos));
}
+ // Skip comma
+ start = pos + 1;
+ } else if (c == Constants.CSV_ESCAPE) {
+ // Skip escape character and escaped character.
+ pos++;
}
}
-
- // We are now on the character that precedes any starting punctuation, so in the most
- // frequent case this will be whitespace or a letter, although it may occasionally be a
- // start of line, or some symbol.
-
- // Step 3 : Search for the start of a paragraph. From the starting point computed in step 2,
- // we go back over any space or tab char sitting there. We find the start of a paragraph
- // if the first char that's not a space or tab is a start of line (as in \n, start of text,
- // or some other similar characters).
- int j = i;
- char prevChar = Constants.CODE_SPACE;
- if (hasSpaceBefore) --j;
- while (j > 0) {
- prevChar = cs.charAt(j - 1);
- if (!Character.isSpaceChar(prevChar) && prevChar != Constants.CODE_TAB) break;
- j--;
- }
- if (j <= 0 || Character.isWhitespace(prevChar)) {
- // There are only spacing chars between the start of the paragraph and the cursor,
- // defined as a isWhitespace() char that is neither a isSpaceChar() nor a tab. Both
- // MODE_WORDS and MODE_SENTENCES should be active.
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
- | TextUtils.CAP_MODE_SENTENCES) & reqModes;
- }
- if (i == j) {
- // If we don't have whitespace before index i, it means neither MODE_WORDS
- // nor mode sentences should be on so we can return right away.
- return TextUtils.CAP_MODE_CHARACTERS & reqModes;
- }
- if ((reqModes & TextUtils.CAP_MODE_SENTENCES) == 0) {
- // Here we know we have whitespace before the cursor (if not, we returned in the above
- // if i == j clause), so we need MODE_WORDS to be on. And we don't need to evaluate
- // MODE_SENTENCES so we can return right away.
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
- }
- // Please note that because of the reqModes & CAP_MODE_SENTENCES test a few lines above,
- // we know that MODE_SENTENCES is being requested.
-
- // Step 4 : Search for MODE_SENTENCES.
- // English is a special case in that "American typography" rules, which are the most common
- // in English, state that a sentence terminator immediately following a quotation mark
- // should be swapped with it and de-duplicated (included in the quotation mark),
- // e.g. <<Did he say, "let's go home?">>
- // No other language has such a rule as far as I know, instead putting inside the quotation
- // mark as the exact thing quoted and handling the surrounding punctuation independently,
- // e.g. <<Did he say, "let's go home"?>>
- // Hence, specifically for English, we treat this special case here.
- if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) {
- for (; j > 0; j--) {
- // Here we look to go over any closing punctuation. This is because in dominant
- // variants of English, the final period is placed within double quotes and maybe
- // other closing punctuation signs. This is generally not true in other languages.
- final char c = cs.charAt(j - 1);
- if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE
- && Character.getType(c) != Character.END_PUNCTUATION) {
- break;
- }
- }
- }
-
- if (j <= 0) return TextUtils.CAP_MODE_CHARACTERS & reqModes;
- char c = cs.charAt(--j);
-
- // We found the next interesting chunk of text ; next we need to determine if it's the
- // end of a sentence. If we have a question mark or an exclamation mark, it's the end of
- // a sentence. If it's neither, the only remaining case is the period so we get the opposite
- // case out of the way.
- if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) {
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
- }
- if (c != Constants.CODE_PERIOD || j <= 0) {
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ final String remain = (size - start > 0) ? text.substring(start) : null;
+ if (list == null) {
+ return remain != null ? new String[] { remain } : null;
}
-
- // We found out that we have a period. We need to determine if this is a full stop or
- // otherwise sentence-ending period, or an abbreviation like "e.g.". An abbreviation
- // looks like (\w\.){2,}
- // To find out, we will have a simple state machine with the following states :
- // START, WORD, PERIOD, ABBREVIATION
- // On START : (just before the first period)
- // letter => WORD
- // whitespace => end with no caps (it was a stand-alone period)
- // otherwise => end with caps (several periods/symbols in a row)
- // On WORD : (within the word just before the first period)
- // letter => WORD
- // period => PERIOD
- // otherwise => end with caps (it was a word with a full stop at the end)
- // On PERIOD : (period within a potential abbreviation)
- // letter => LETTER
- // otherwise => end with caps (it was not an abbreviation)
- // On LETTER : (letter within a potential abbreviation)
- // letter => LETTER
- // period => PERIOD
- // otherwise => end with no caps (it was an abbreviation)
- // "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This
- // should capitalize.
-
- final int START = 0;
- final int WORD = 1;
- final int PERIOD = 2;
- final int LETTER = 3;
- final int caps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
- | TextUtils.CAP_MODE_SENTENCES) & reqModes;
- final int noCaps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
- int state = START;
- while (j > 0) {
- c = cs.charAt(--j);
- switch (state) {
- case START:
- if (Character.isLetter(c)) {
- state = WORD;
- } else if (Character.isWhitespace(c)) {
- return noCaps;
- } else {
- return caps;
- }
- break;
- case WORD:
- if (Character.isLetter(c)) {
- state = WORD;
- } else if (c == Constants.CODE_PERIOD) {
- state = PERIOD;
- } else {
- return caps;
- }
- break;
- case PERIOD:
- if (Character.isLetter(c)) {
- state = LETTER;
- } else {
- return caps;
- }
- break;
- case LETTER:
- if (Character.isLetter(c)) {
- state = LETTER;
- } else if (c == Constants.CODE_PERIOD) {
- state = PERIOD;
- } else {
- return noCaps;
- }
- }
+ if (remain != null) {
+ list.add(remain);
}
- // Here we arrived at the start of the line. This should behave exactly like whitespace.
- return (START == state || LETTER == state) ? noCaps : caps;
+ return list.toArray(new String[list.size()]);
}
}
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
index eb5c387a8..62f2a9750 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
@@ -122,9 +122,9 @@ public final class UserHistoryDictIOUtils {
BinaryDictInputOutput.writeDictionaryBinary(destination, fusionDict, formatOptions);
Log.d(TAG, "end writing");
} catch (IOException e) {
- Log.e(TAG, "IO exception while writing file: " + e);
+ Log.e(TAG, "IO exception while writing file", e);
} catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported fomat: " + e);
+ Log.e(TAG, "Unsupported format", e);
}
}
@@ -184,11 +184,11 @@ public final class UserHistoryDictIOUtils {
BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies,
bigrams);
} catch (IOException e) {
- Log.e(TAG, "IO exception while reading file: " + e);
+ Log.e(TAG, "IO exception while reading file", e);
} catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported format: " + e);
+ Log.e(TAG, "Unsupported format", e);
} catch (ArrayIndexOutOfBoundsException e) {
- Log.e(TAG, "ArrayIndexOutOfBoundsException while reading file: " + e);
+ Log.e(TAG, "ArrayIndexOutOfBoundsException while reading file", e);
}
addWordsFromWordMap(unigrams, frequencies, bigrams, dict);
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index b0e471643..cd3f9e442 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -352,7 +352,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
if (DBG) {
throw e;
} else {
- Log.e(TAG, "Exception while spellcheking: " + e);
+ Log.e(TAG, "Exception while spellcheking", e);
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
}
}
diff --git a/java/src/com/android/inputmethod/research/LogStatement.java b/java/src/com/android/inputmethod/research/LogStatement.java
index 059146ae6..09b12fcfa 100644
--- a/java/src/com/android/inputmethod/research/LogStatement.java
+++ b/java/src/com/android/inputmethod/research/LogStatement.java
@@ -35,7 +35,7 @@ import java.io.IOException;
* associated with the {@code String[] keys} are likely to reveal information about the user. The
* actual values are stored separately.
*/
-class LogStatement {
+public class LogStatement {
private static final String TAG = LogStatement.class.getSimpleName();
private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
@@ -166,6 +166,8 @@ class LogStatement {
/**
* Write the contents out through jsonWriter.
*
+ * The JsonWriter class must have already had {@code JsonWriter.beginArray} called on it.
+ *
* Note that this method is not thread safe for the same jsonWriter. Callers must ensure
* thread safety.
*/
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index a584a3af6..1a9a720f3 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -110,7 +110,13 @@ import java.util.List;
}
/**
- * Publish the contents of this LogUnit to researchLog.
+ * Publish the contents of this LogUnit to {@code researchLog}.
+ *
+ * For each publishable {@code LogStatement}, invoke {@link LogStatement#outputToLocked}.
+ *
+ * @param researchLog where to publish the contents of this {@code LogUnit}
+ * @param canIncludePrivateData whether the private data in this {@code LogUnit} should be
+ * included
*/
public synchronized void publishTo(final ResearchLog researchLog,
final boolean canIncludePrivateData) {
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 24bf7d15f..5114977d8 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -81,10 +81,7 @@ public class ResearchLog {
}
}
- public ResearchLog(final File outputFile, Context context) {
- if (outputFile == null) {
- throw new IllegalArgumentException();
- }
+ public ResearchLog(final File outputFile, final Context context) {
mExecutor = Executors.newSingleThreadScheduledExecutor();
mFile = outputFile;
mContext = context;
@@ -112,7 +109,7 @@ public class ResearchLog {
Log.d(TAG, "error when closing ResearchLog:");
e.printStackTrace();
} finally {
- if (mFile.exists()) {
+ if (mFile != null && mFile.exists()) {
mFile.setWritable(false, false);
}
if (onClosed != null) {
@@ -139,7 +136,9 @@ public class ResearchLog {
mHasWrittenData = false;
}
} finally {
- mIsAbortSuccessful = mFile.delete();
+ if (mFile != null) {
+ mIsAbortSuccessful = mFile.delete();
+ }
}
return null;
}
@@ -209,7 +208,7 @@ public class ResearchLog {
*/
public JsonWriter getValidJsonWriterLocked() {
try {
- if (mJsonWriter == NULL_JSON_WRITER) {
+ if (mJsonWriter == NULL_JSON_WRITER && mFile != null) {
final FileOutputStream fos =
mContext.openFileOutput(mFile.getName(), Context.MODE_PRIVATE);
mJsonWriter = new JsonWriter(new BufferedWriter(new OutputStreamWriter(fos)));
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 364ab2da2..45212913e 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -763,18 +763,26 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (isIncludingRecording) {
// Try to read recording from recently written json file
if (mUserRecordingFile != null) {
+ FileChannel channel = null;
try {
- final FileChannel channel =
- new FileInputStream(mUserRecordingFile).getChannel();
+ channel = new FileInputStream(mUserRecordingFile).getChannel();
final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0,
channel.size());
// Android's openFileOutput() creates the file, so we use Android's default
// Charset (UTF-8) here to read it.
recording = Charset.defaultCharset().decode(buffer).toString();
} catch (FileNotFoundException e) {
- e.printStackTrace();
+ Log.e(TAG, "Could not find recording file", e);
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TAG, "Error reading recording file", e);
+ } finally {
+ if (channel != null) {
+ try {
+ channel.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Error closing recording file", e);
+ }
+ }
}
}
}
@@ -1395,7 +1403,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
*/
private static final LogStatement LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY =
new LogStatement("LatinIMEPickSuggestionManually", true, false, "replacedWord", "index",
- "suggestion", "x", "y");
+ "suggestion", "x", "y", "isBatchMode");
public static void latinIME_pickSuggestionManually(final String replacedWord,
final int index, final String suggestion, final boolean isBatchMode) {
final ResearchLogger researchLogger = getInstance();
@@ -1408,7 +1416,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY,
scrubDigitsFromString(replacedWord), index,
suggestion == null ? null : scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE,
- Constants.SUGGESTION_STRIP_COORDINATE);
+ Constants.SUGGESTION_STRIP_COORDINATE, isBatchMode);
researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode);
researchLogger.mStatistics.recordManualSuggestion(SystemClock.uptimeMillis());
}