aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java34
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java29
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java145
-rw-r--r--java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java54
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java58
-rw-r--r--java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java41
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java1
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java14
8 files changed, 303 insertions, 73 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 4b242c5f5..1fe0a4cce 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -29,6 +29,7 @@ import android.util.Log;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
import com.android.inputmethod.dictionarypack.MD5Calculator;
+import com.android.inputmethod.dictionarypack.UpdateHandler;
import com.android.inputmethod.latin.common.FileUtils;
import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
@@ -323,7 +324,10 @@ public final class BinaryDictionaryFileDumper {
// move the output file to the final staging file.
final File finalFile = new File(finalFileName);
- FileUtils.renameTo(outputFile, finalFile);
+ if (!FileUtils.renameTo(outputFile, finalFile)) {
+ Log.e(TAG, String.format("Failed to rename from %s to %s.",
+ outputFile.getAbsoluteFile(), finalFile.getAbsoluteFile()));
+ }
wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
QUERY_PARAMETER_SUCCESS);
@@ -445,7 +449,6 @@ public final class BinaryDictionaryFileDumper {
*/
public static void downloadDictIfNeverRequested(final Locale locale,
final Context context, final boolean hasDefaultWordList) {
- Log.d("inamul_tag", "BinaryDictionaryFileDumper.downloadDictIfNeverRequested()");
getWordListWordListInfos(locale, context, hasDefaultWordList);
}
@@ -488,6 +491,8 @@ public final class BinaryDictionaryFileDumper {
private static void reinitializeClientRecordInDictionaryContentProvider(final Context context,
final ContentProviderClient client, final String clientId) throws RemoteException {
final String metadataFileUri = MetadataFileUriGetter.getMetadataUri(context);
+ Log.i(TAG, "reinitializeClientRecordInDictionaryContentProvider() : MetadataFileUri = "
+ + metadataFileUri);
final String metadataAdditionalId = MetadataFileUriGetter.getMetadataAdditionalId(context);
// Tell the content provider to reset all information about this client id
final Uri metadataContentUri = getProviderUriBuilder(clientId)
@@ -512,9 +517,34 @@ public final class BinaryDictionaryFileDumper {
final int length = dictionaryList.size();
for (int i = 0; i < length; ++i) {
final DictionaryInfo info = dictionaryList.get(i);
+ Log.i(TAG, "reinitializeClientRecordInDictionaryContentProvider() : Insert " + info);
client.insert(Uri.withAppendedPath(dictionaryContentUriBase, info.mId),
info.toContentValues());
}
+
+ // Read from metadata file in resources to get the baseline dictionary info.
+ // This ensures we start with a sane list of available dictionaries.
+ final int metadataResourceId = context.getResources().getIdentifier("metadata",
+ "raw", DictionaryInfoUtils.RESOURCE_PACKAGE_NAME);
+ if (metadataResourceId == 0) {
+ Log.w(TAG, "Missing metadata.json resource");
+ return;
+ }
+ InputStream inputStream = null;
+ try {
+ inputStream = context.getResources().openRawResource(metadataResourceId);
+ UpdateHandler.handleMetadata(context, inputStream, clientId);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to read metadata.json from resources", e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to close metadata.json", e);
+ }
+ }
+ }
}
/**
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
index 63064ba3e..c7115c9d9 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
@@ -27,6 +27,7 @@ import com.android.inputmethod.latin.NgramContext.WordInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.common.ComposedData;
import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import com.android.inputmethod.latin.utils.ExecutorUtils;
@@ -509,10 +510,27 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
}
}
- private void putWordIntoValidSpellingWordCache(final String caller, final String word) {
- final String spellingWord = word.toLowerCase(getLocale());
- final boolean isValid = isValidSpellingWord(spellingWord);
- mValidSpellingWordWriteCache.put(spellingWord, isValid);
+ private void putWordIntoValidSpellingWordCache(
+ @Nonnull final String caller,
+ @Nonnull final String originalWord) {
+ if (mValidSpellingWordWriteCache == null) {
+ return;
+ }
+
+ final String lowerCaseWord = originalWord.toLowerCase(getLocale());
+ final boolean lowerCaseValid = isValidSpellingWord(lowerCaseWord);
+ mValidSpellingWordWriteCache.put(lowerCaseWord, lowerCaseValid);
+
+ final String capitalWord =
+ StringUtils.capitalizeFirstAndDowncaseRest(originalWord, getLocale());
+ final boolean capitalValid;
+ if (lowerCaseValid) {
+ // The lower case form of the word is valid, so the upper case must be valid.
+ capitalValid = true;
+ } else {
+ capitalValid = isValidSpellingWord(capitalWord);
+ }
+ mValidSpellingWordWriteCache.put(capitalWord, capitalValid);
}
private void addWordToUserHistory(final DictionaryGroup dictionaryGroup,
@@ -620,8 +638,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
public boolean isValidSpellingWord(final String word) {
if (mValidSpellingWordReadCache != null) {
- final String spellingWord = word.toLowerCase(getLocale());
- final Boolean cachedValue = mValidSpellingWordReadCache.get(spellingWord);
+ final Boolean cachedValue = mValidSpellingWordReadCache.get(word);
if (cachedValue != null) {
return cachedValue;
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index a123d282b..a10f2bdb0 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -16,11 +16,10 @@
package com.android.inputmethod.latin;
-import static com.android.inputmethod.latin.define.DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH;
-
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.Bundle;
+import android.os.SystemClock;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.CharacterStyle;
@@ -37,7 +36,6 @@ import com.android.inputmethod.compat.InputConnectionCompatUtils;
import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.common.UnicodeSurrogate;
import com.android.inputmethod.latin.common.StringUtils;
-import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import com.android.inputmethod.latin.inputlogic.PrivateCommandPerformer;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.utils.CapsModeUtils;
@@ -45,8 +43,11 @@ import com.android.inputmethod.latin.utils.DebugLogUtils;
import com.android.inputmethod.latin.utils.NgramContextUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
import com.android.inputmethod.latin.utils.SpannableStringUtils;
+import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.TextRange;
+import java.util.concurrent.TimeUnit;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -59,17 +60,42 @@ import javax.annotation.Nullable;
* for example.
*/
public final class RichInputConnection implements PrivateCommandPerformer {
- private static final String TAG = RichInputConnection.class.getSimpleName();
+ private static final String TAG = "RichInputConnection";
private static final boolean DBG = false;
private static final boolean DEBUG_PREVIOUS_TEXT = false;
private static final boolean DEBUG_BATCH_NESTING = false;
- // Provision for realistic N-grams like "Hello, how are you?" and "I'm running 5 late".
- // Technically, this will not handle 5-grams composed of long words, but in practice,
- // our language models don't include that much data.
- private static final int LOOKBACK_CHARACTER_NUM = 80;
+ private static final int NUM_CHARS_TO_GET_BEFORE_CURSOR = 40;
+ private static final int NUM_CHARS_TO_GET_AFTER_CURSOR = 40;
private static final int INVALID_CURSOR_POSITION = -1;
/**
+ * The amount of time a {@link #reloadTextCache} call needs to take for the keyboard to enter
+ * the {@link #hasSlowInputConnection} state.
+ */
+ private static final long SLOW_INPUT_CONNECTION_ON_FULL_RELOAD_MS = 1000;
+ /**
+ * The amount of time a {@link #getTextBeforeCursor} or {@link #getTextAfterCursor} call needs
+ * to take for the keyboard to enter the {@link #hasSlowInputConnection} state.
+ */
+ private static final long SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS = 200;
+
+ private static final int OPERATION_GET_TEXT_BEFORE_CURSOR = 0;
+ private static final int OPERATION_GET_TEXT_AFTER_CURSOR = 1;
+ private static final int OPERATION_GET_WORD_RANGE_AT_CURSOR = 2;
+ private static final int OPERATION_RELOAD_TEXT_CACHE = 3;
+ private static final String[] OPERATION_NAMES = new String[] {
+ "GET_TEXT_BEFORE_CURSOR",
+ "GET_TEXT_AFTER_CURSOR",
+ "GET_WORD_RANGE_AT_CURSOR",
+ "RELOAD_TEXT_CACHE"};
+
+ /**
+ * The amount of time the keyboard will persist in the {@link #hasSlowInputConnection} state
+ * after observing a slow InputConnection event.
+ */
+ private static final long SLOW_INPUTCONNECTION_PERSIST_MS = TimeUnit.MINUTES.toMillis(10);
+
+ /**
* This variable contains an expected value for the selection start position. This is where the
* cursor or selection start may end up after all the keyboard-triggered updates have passed. We
* keep this to compare it to the actual selection start to guess whether the move was caused by
@@ -85,7 +111,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
private int mExpectedSelEnd = INVALID_CURSOR_POSITION; // in chars, not code points
/**
* This contains the committed text immediately preceding the cursor and the composing
- * text if any. It is refreshed when the cursor moves by calling upon the TextView.
+ * text, if any. It is refreshed when the cursor moves by calling upon the TextView.
*/
private final StringBuilder mCommittedTextBeforeComposingText = new StringBuilder();
/**
@@ -100,8 +126,13 @@ public final class RichInputConnection implements PrivateCommandPerformer {
private SpannableStringBuilder mTempObjectForCommitText = new SpannableStringBuilder();
private final InputMethodService mParent;
- InputConnection mIC;
- int mNestLevel;
+ private InputConnection mIC;
+ private int mNestLevel;
+
+ /**
+ * The timestamp of the last slow InputConnection operation
+ */
+ private long mLastSlowInputConnectionTime = -SLOW_INPUTCONNECTION_PERSIST_MS;
public RichInputConnection(final InputMethodService parent) {
mParent = parent;
@@ -113,6 +144,19 @@ public final class RichInputConnection implements PrivateCommandPerformer {
return mIC != null;
}
+ /**
+ * Returns whether or not the underlying InputConnection is slow. When true, we want to avoid
+ * calling InputConnection methods that trigger an IPC round-trip (e.g., getTextAfterCursor).
+ */
+ public boolean hasSlowInputConnection() {
+ return (SystemClock.uptimeMillis() - mLastSlowInputConnectionTime)
+ <= SLOW_INPUTCONNECTION_PERSIST_MS;
+ }
+
+ public void onStartInput() {
+ mLastSlowInputConnectionTime = -SLOW_INPUTCONNECTION_PERSIST_MS;
+ }
+
private void checkConsistencyForDebug() {
final ExtractedTextRequest r = new ExtractedTextRequest();
r.hintMaxChars = 0;
@@ -211,9 +255,11 @@ public final class RichInputConnection implements PrivateCommandPerformer {
mIC = mParent.getCurrentInputConnection();
// Call upon the inputconnection directly since our own method is using the cache, and
// we want to refresh it.
- final CharSequence textBeforeCursor = isConnected()
- ? mIC.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0)
- : null;
+ final CharSequence textBeforeCursor = getTextBeforeCursorAndDetectLaggyConnection(
+ OPERATION_RELOAD_TEXT_CACHE,
+ SLOW_INPUT_CONNECTION_ON_FULL_RELOAD_MS,
+ Constants.EDITOR_CONTENTS_CACHE_SIZE,
+ 0 /* flags */);
if (null == textBeforeCursor) {
// For some reason the app thinks we are not connected to it. This looks like a
// framework bug... Fall back to ground state and return false.
@@ -377,16 +423,54 @@ public final class RichInputConnection implements PrivateCommandPerformer {
}
return s;
}
+ return getTextBeforeCursorAndDetectLaggyConnection(
+ OPERATION_GET_TEXT_BEFORE_CURSOR,
+ SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS,
+ n, flags);
+ }
+
+ private CharSequence getTextBeforeCursorAndDetectLaggyConnection(
+ final int operation, final long timeout, final int n, final int flags) {
mIC = mParent.getCurrentInputConnection();
- return isConnected() ? mIC.getTextBeforeCursor(n, flags) : null;
+ if (!isConnected()) {
+ return null;
+ }
+ final long startTime = SystemClock.uptimeMillis();
+ final CharSequence result = mIC.getTextBeforeCursor(n, flags);
+ detectLaggyConnection(operation, timeout, startTime);
+ return result;
}
public CharSequence getTextAfterCursor(final int n, final int flags) {
+ return getTextAfterCursorAndDetectLaggyConnection(
+ OPERATION_GET_TEXT_AFTER_CURSOR,
+ SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS,
+ n, flags);
+ }
+
+ private CharSequence getTextAfterCursorAndDetectLaggyConnection(
+ final int operation, final long timeout, final int n, final int flags) {
mIC = mParent.getCurrentInputConnection();
- return isConnected() ? mIC.getTextAfterCursor(n, flags) : null;
+ if (!isConnected()) {
+ return null;
+ }
+ final long startTime = SystemClock.uptimeMillis();
+ final CharSequence result = mIC.getTextAfterCursor(n, flags);
+ detectLaggyConnection(operation, timeout, startTime);
+ return result;
+ }
+
+ private void detectLaggyConnection(final int operation, final long timeout, final long startTime) {
+ final long duration = SystemClock.uptimeMillis() - startTime;
+ if (duration >= timeout) {
+ final String operationName = OPERATION_NAMES[operation];
+ Log.w(TAG, "Slow InputConnection: " + operationName + " took " + duration + " ms.");
+ StatsUtils.onInputConnectionLaggy(operation, duration);
+ mLastSlowInputConnectionTime = SystemClock.uptimeMillis();
+ }
}
- public void deleteSurroundingText(final int beforeLength, final int afterLength) {
+ public void deleteTextBeforeCursor(final int beforeLength) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
// TODO: the following is incorrect if the cursor is not immediately after the composition.
// Right now we never come here in this case because we reset the composing state before we
@@ -411,7 +495,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
mExpectedSelStart = 0;
}
if (isConnected()) {
- mIC.deleteSurroundingText(beforeLength, afterLength);
+ mIC.deleteSurroundingText(beforeLength, 0);
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -576,9 +660,9 @@ public final class RichInputConnection implements PrivateCommandPerformer {
if (!isConnected()) {
return NgramContext.EMPTY_PREV_WORDS_INFO;
}
- final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
+ final CharSequence prev = getTextBeforeCursor(NUM_CHARS_TO_GET_BEFORE_CURSOR, 0);
if (DEBUG_PREVIOUS_TEXT && null != prev) {
- final int checkLength = LOOKBACK_CHARACTER_NUM - 1;
+ final int checkLength = NUM_CHARS_TO_GET_BEFORE_CURSOR - 1;
final String reference = prev.length() <= checkLength ? prev.toString()
: prev.subSequence(prev.length() - checkLength, prev.length()).toString();
// TODO: right now the following works because mComposingText holds the part of the
@@ -621,9 +705,15 @@ public final class RichInputConnection implements PrivateCommandPerformer {
if (!isConnected()) {
return null;
}
- final CharSequence before = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM,
+ final CharSequence before = getTextBeforeCursorAndDetectLaggyConnection(
+ OPERATION_GET_WORD_RANGE_AT_CURSOR,
+ SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS,
+ NUM_CHARS_TO_GET_BEFORE_CURSOR,
InputConnection.GET_TEXT_WITH_STYLES);
- final CharSequence after = mIC.getTextAfterCursor(LOOKBACK_CHARACTER_NUM,
+ final CharSequence after = getTextAfterCursorAndDetectLaggyConnection(
+ OPERATION_GET_WORD_RANGE_AT_CURSOR,
+ SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS,
+ NUM_CHARS_TO_GET_AFTER_CURSOR,
InputConnection.GET_TEXT_WITH_STYLES);
if (before == null || after == null) {
return null;
@@ -666,8 +756,9 @@ public final class RichInputConnection implements PrivateCommandPerformer {
hasUrlSpans);
}
- public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations) {
- if (isCursorFollowedByWordCharacter(spacingAndPunctuations)) {
+ public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations,
+ boolean checkTextAfter) {
+ if (checkTextAfter && isCursorFollowedByWordCharacter(spacingAndPunctuations)) {
// If what's after the cursor is a word character, then we're touching a word.
return true;
}
@@ -704,7 +795,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
final int codePointBeforeCursor = getCodePointBeforeCursor();
if (Constants.CODE_SPACE == codePointBeforeCursor) {
- deleteSurroundingText(1, 0);
+ deleteTextBeforeCursor(1);
}
}
@@ -730,7 +821,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
}
// Double-space results in ". ". A backspace to cancel this should result in a single
// space in the text field, so we replace ". " with a single space.
- deleteSurroundingText(2, 0);
+ deleteTextBeforeCursor(2);
final String singleSpace = " ";
commitText(singleSpace, 1);
return true;
@@ -752,7 +843,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
+ "find a space just before the cursor.");
return false;
}
- deleteSurroundingText(2, 0);
+ deleteTextBeforeCursor(2);
final String text = " " + textBeforeCursor.subSequence(0, 1);
commitText(text, 1);
return true;
diff --git a/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
index 26c5e0034..90221512f 100644
--- a/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin;
+import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -23,6 +24,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.database.Cursor;
import android.os.Process;
import android.preference.PreferenceManager;
import android.util.Log;
@@ -30,13 +32,12 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
+import com.android.inputmethod.dictionarypack.DownloadManagerWrapper;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.setup.SetupActivity;
import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils;
-import java.util.Locale;
-
/**
* This class detects the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME
* package has been replaced by a newer version of the same package. This class also detects
@@ -76,23 +77,12 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
final InputMethodSubtype[] additionalSubtypes = richImm.getAdditionalSubtypes();
richImm.setAdditionalInputMethodSubtypes(additionalSubtypes);
toggleAppIcon(context);
- downloadLatestDictionaries(context);
- // TODO: This is only for dogfood builds. Remove this from the final release.
- DictionaryFacilitator keyboardFacilitator =
- DictionaryFacilitatorProvider.getDictionaryFacilitator(false);
- boolean historyCleared = keyboardFacilitator.clearUserHistoryDictionary(context);
- Locale keyboardLocale = keyboardFacilitator.getLocale();
- if (historyCleared && keyboardLocale != null) {
- keyboardFacilitator.resetDictionaries(
- context,
- keyboardLocale,
- keyboardFacilitator.usesContacts(),
- true /* history */,
- true /* force */,
- keyboardFacilitator.getAccount(),
- "" /* prefix */,
- null /* listener */);
- }
+
+ // Remove all the previously scheduled downloads. This will also makes sure
+ // that any erroneously stuck downloads will get cleared. (b/21797386)
+ removeOldDownloads(context);
+ // b/21797386
+ // downloadLatestDictionaries(context);
} else if (Intent.ACTION_BOOT_COMPLETED.equals(intentAction)) {
Log.i(TAG, "Boot has been completed");
toggleAppIcon(context);
@@ -120,6 +110,32 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
}
}
+ private void removeOldDownloads(Context context) {
+ try {
+ Log.i(TAG, "Removing the old downloads in progress of the previous keyboard version.");
+ final DownloadManagerWrapper downloadManagerWrapper = new DownloadManagerWrapper(
+ context);
+ final DownloadManager.Query q = new DownloadManager.Query();
+ // Query all the download statuses except the succeeded ones.
+ q.setFilterByStatus(DownloadManager.STATUS_FAILED
+ | DownloadManager.STATUS_PAUSED
+ | DownloadManager.STATUS_PENDING
+ | DownloadManager.STATUS_RUNNING);
+ final Cursor c = downloadManagerWrapper.query(q);
+ if (c != null) {
+ for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
+ final long downloadId = c
+ .getLong(c.getColumnIndex(DownloadManager.COLUMN_ID));
+ downloadManagerWrapper.remove(downloadId);
+ Log.i(TAG, "Removed the download with Id: " + downloadId);
+ }
+ c.close();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while removing old downloads.");
+ }
+ }
+
private void downloadLatestDictionaries(Context context) {
final Intent updateIntent = new Intent(
DictionaryPackConstants.INIT_AND_UPDATE_NOW_INTENT_ACTION);
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 975ed7c01..1dd5850f8 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -139,6 +139,7 @@ public final class InputLogic {
public void startInput(final String combiningSpec, final SettingsValues settingsValues) {
mEnteredText = null;
mWordBeingCorrectedByCursor = null;
+ mConnection.onStartInput();
if (!mWordComposer.getTypedWord().isEmpty()) {
// For messaging apps that offer send button, the IME does not get the opportunity
// to capture the last word. This block should capture those uncommitted words.
@@ -472,7 +473,7 @@ public final class InputLogic {
}
// Try to record the word being corrected when the user enters a word character or
// the backspace key.
- if (!mWordComposer.isComposingWord()
+ if (!mConnection.hasSlowInputConnection() && !mWordComposer.isComposingWord()
&& (settingsValues.isWordCodePoint(processedEvent.mCodePoint) ||
processedEvent.mKeyCode == Constants.CODE_DELETE)) {
mWordBeingCorrectedByCursor = getWordAtCursor(
@@ -832,8 +833,14 @@ public final class InputLogic {
&& settingsValues.needsToLookupSuggestions() &&
// In languages with spaces, we only start composing a word when we are not already
// touching a word. In languages without spaces, the above conditions are sufficient.
- (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)
- || !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces)) {
+ // NOTE: If the InputConnection is slow, we skip the text-after-cursor check since it
+ // can incur a very expensive getTextAfterCursor() lookup, potentially making the
+ // keyboard UI slow and non-responsive.
+ // TODO: Cache the text after the cursor so we don't need to go to the InputConnection
+ // each time. We are already doing this for getTextBeforeCursor().
+ (!settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
+ || !mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations,
+ !mConnection.hasSlowInputConnection() /* checkTextAfter */))) {
// Reset entirely the composing state anyway, then start composing a new word unless
// the character is a word connector. The idea here is, word connectors are not
// separators and they should be treated as normal characters, except in the first
@@ -1053,7 +1060,7 @@ public final class InputLogic {
// Cancel multi-character input: remove the text we just entered.
// This is triggered on backspace after a key that inputs multiple characters,
// like the smiley key or the .com key.
- mConnection.deleteSurroundingText(mEnteredText.length(), 0);
+ mConnection.deleteTextBeforeCursor(mEnteredText.length());
StatsUtils.onDeleteMultiCharInput(mEnteredText.length());
mEnteredText = null;
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
@@ -1098,7 +1105,7 @@ public final class InputLogic {
- mConnection.getExpectedSelectionStart();
mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
mConnection.getExpectedSelectionEnd());
- mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ mConnection.deleteTextBeforeCursor(numCharsDeleted);
StatsUtils.onBackspaceSelectedText(numCharsDeleted);
} else {
// There is no selection, just delete one character.
@@ -1138,13 +1145,13 @@ public final class InputLogic {
// broken apps expect something to happen in this case so that they can
// catch it and have their broken interface react. If you need the keyboard
// to do this, you're doing it wrong -- please fix your app.
- mConnection.deleteSurroundingText(1, 0);
+ mConnection.deleteTextBeforeCursor(1);
// TODO: Add a new StatsUtils method onBackspaceWhenNoText()
return;
}
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
- mConnection.deleteSurroundingText(lengthToDelete, 0);
+ mConnection.deleteTextBeforeCursor(lengthToDelete);
int totalDeletedLength = lengthToDelete;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
// If this is an accelerated (i.e., double) deletion, then we need to
@@ -1157,7 +1164,7 @@ public final class InputLogic {
if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
codePointBeforeCursorToDeleteAgain) ? 2 : 1;
- mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
+ mConnection.deleteTextBeforeCursor(lengthToDeleteAgain);
totalDeletedLength += lengthToDeleteAgain;
}
}
@@ -1169,7 +1176,9 @@ public final class InputLogic {
unlearnWordBeingDeleted(
inputTransaction.mSettingsValues, currentKeyboardScriptId);
}
- if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings()
+ if (mConnection.hasSlowInputConnection()) {
+ mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
+ } else if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings()
&& inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mCurrentLanguageHasSpaces
&& !mConnection.isCursorFollowedByWordCharacter(
@@ -1196,6 +1205,13 @@ public final class InputLogic {
boolean unlearnWordBeingDeleted(
final SettingsValues settingsValues, final int currentKeyboardScriptId) {
+ if (mConnection.hasSlowInputConnection()) {
+ // TODO: Refactor unlearning so that it does not incur any extra calls
+ // to the InputConnection. That way it can still be performed on a slow
+ // InputConnection.
+ Log.w(TAG, "Skipping unlearning due to slow InputConnection.");
+ return false;
+ }
// If we just started backspacing to delete a previous word (but have not
// entered the composing state yet), unlearn the word.
// TODO: Consider tracking whether or not this word was typed by the user.
@@ -1241,7 +1257,7 @@ public final class InputLogic {
if (Constants.CODE_SPACE != codePointBeforeCursor) {
return false;
}
- mConnection.deleteSurroundingText(1, 0);
+ mConnection.deleteTextBeforeCursor(1);
final String text = event.getTextToCommit() + " ";
mConnection.commitText(text, 1);
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
@@ -1331,7 +1347,7 @@ public final class InputLogic {
Character.codePointAt(lastTwo, length - 3) : lastTwo.charAt(length - 2);
if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) {
cancelDoubleSpacePeriodCountdown();
- mConnection.deleteSurroundingText(1, 0);
+ mConnection.deleteTextBeforeCursor(1);
final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mSentenceSeparatorAndSpace;
mConnection.commitText(textToInsert, 1);
@@ -1399,7 +1415,7 @@ public final class InputLogic {
mConnection.finishComposingText();
mRecapitalizeStatus.rotate();
mConnection.setSelection(selectionEnd, selectionEnd);
- mConnection.deleteSurroundingText(numCharsSelected, 0);
+ mConnection.deleteTextBeforeCursor(numCharsSelected);
mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0);
mConnection.setSelection(mRecapitalizeStatus.getNewCursorStart(),
mRecapitalizeStatus.getNewCursorEnd());
@@ -1411,6 +1427,12 @@ public final class InputLogic {
// That's to avoid unintended additions in some sensitive fields, or fields that
// expect to receive non-words.
if (!settingsValues.mAutoCorrectionEnabledPerUserSettings) return;
+ if (mConnection.hasSlowInputConnection()) {
+ // Since we don't unlearn when the user backspaces on a slow InputConnection,
+ // turn off learning to guard against adding typos that the user later deletes.
+ Log.w(TAG, "Skipping learning due to slow InputConnection.");
+ return;
+ }
if (TextUtils.isEmpty(suggestion)) return;
final boolean wasAutoCapitalized =
@@ -1514,7 +1536,8 @@ public final class InputLogic {
return;
}
final int expectedCursorPosition = mConnection.getExpectedSelectionStart();
- if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)) {
+ if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations,
+ true /* checkTextAfter */)) {
// Show predictions.
mWordComposer.setCapitalizedModeAtStartComposingTime(WordComposer.CAPS_MODE_OFF);
mLatinIME.mHandler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_RECORRECTION);
@@ -1637,7 +1660,7 @@ public final class InputLogic {
+ "\", but before the cursor we found \"" + wordBeforeCursor + "\"");
}
}
- mConnection.deleteSurroundingText(deleteLength, 0);
+ mConnection.deleteTextBeforeCursor(deleteLength);
if (!TextUtils.isEmpty(committedWord)) {
unlearnWord(committedWordString, inputTransaction.mSettingsValues,
Constants.EVENT_REVERT);
@@ -2135,9 +2158,10 @@ public final class InputLogic {
final SuggestedWords suggestedWords = mSuggestedWords;
// TODO: Locale should be determined based on context and the text given.
final Locale locale = getDictionaryFacilitatorLocale();
- final CharSequence chosenWordWithSuggestions =
- SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
- suggestedWords, locale);
+ final CharSequence chosenWordWithSuggestions = chosenWord;
+ // b/21926256
+ // SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
+ // suggestedWords, locale);
if (DebugFlags.DEBUG_ENABLED) {
long runTimeMillis = System.currentTimeMillis() - startTimeMillis;
Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run "
diff --git a/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java
index aa73a9a83..d28e703fe 100644
--- a/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java
@@ -16,20 +16,27 @@
package com.android.inputmethod.latin.settings;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.Bundle;
import android.preference.Preference;
import com.android.inputmethod.dictionarypack.DictionarySettingsActivity;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
+import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
+
+import java.util.TreeSet;
/**
* "Text correction" settings sub screen.
*
* This settings sub screen handles the following text correction preferences.
+ * - Personal dictionary
* - Add-on dictionaries
* - Block offensive words
* - Auto-correction
@@ -59,5 +66,39 @@ public final class CorrectionSettingsFragment extends SubScreenFragment {
if (0 >= number) {
removePreference(Settings.PREF_CONFIGURE_DICTIONARIES_KEY);
}
+
+ final Preference editPersonalDictionary =
+ findPreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY);
+ final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent();
+ final ResolveInfo ri = USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS ? null
+ : pm.resolveActivity(
+ editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (ri == null) {
+ overwriteUserDictionaryPreference(editPersonalDictionary);
+ }
+ }
+
+ private void overwriteUserDictionaryPreference(final Preference userDictionaryPreference) {
+ final Activity activity = getActivity();
+ final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity);
+ if (null == localeList) {
+ // The locale list is null if and only if the user dictionary service is
+ // not present or disabled. In this case we need to remove the preference.
+ getPreferenceScreen().removePreference(userDictionaryPreference);
+ } else if (localeList.size() <= 1) {
+ userDictionaryPreference.setFragment(UserDictionarySettings.class.getName());
+ // If the size of localeList is 0, we don't set the locale parameter in the
+ // extras. This will be interpreted by the UserDictionarySettings class as
+ // meaning "the current locale".
+ // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesSet()
+ // the locale list always has at least one element, since it always includes the current
+ // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesSet().
+ if (localeList.size() == 1) {
+ final String locale = (String)localeList.toArray()[0];
+ userDictionaryPreference.getExtras().putString("locale", locale);
+ }
+ } else {
+ userDictionaryPreference.setFragment(UserDictionaryList.class.getName());
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 694f43d3f..940f1bdfc 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -56,6 +56,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
// PREF_VOICE_MODE_OBSOLETE is obsolete. Use PREF_VOICE_INPUT_KEY instead.
public static final String PREF_VOICE_MODE_OBSOLETE = "voice_mode";
public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key";
+ public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key";
// PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE is obsolete. Use PREF_AUTO_CORRECTION instead.
public static final String PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE =
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 096a545e5..cea2e13b1 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -54,7 +54,7 @@ import javax.annotation.Nullable;
*/
public class DictionaryInfoUtils {
private static final String TAG = DictionaryInfoUtils.class.getSimpleName();
- private static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
+ public static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
private static final String DEFAULT_MAIN_DICT = "main";
private static final String MAIN_DICT_PREFIX = "main_";
private static final String DECODER_DICT_SUFFIX = DecoderSpecificConstants.DECODER_DICT_SUFFIX;
@@ -103,6 +103,13 @@ public class DictionaryInfoUtils {
values.put(VERSION_COLUMN, mVersion);
return values;
}
+
+ @Override
+ public String toString() {
+ return "DictionaryInfo : Id = '" + mId
+ + "' : Locale=" + mLocale
+ + " : Version=" + mVersion;
+ }
}
private DictionaryInfoUtils() {
@@ -307,7 +314,10 @@ public class DictionaryInfoUtils {
final String cacheFilename = cacheDirectoryForLocale + File.separator + fileId;
final File cacheFile = new File(cacheFilename);
// move the staging file to cache file.
- FileUtils.renameTo(stagingFile, cacheFile);
+ if (!FileUtils.renameTo(stagingFile, cacheFile)) {
+ Log.e(TAG, String.format("Failed to rename from %s to %s.",
+ stagingFile.getAbsoluteFile(), cacheFile.getAbsoluteFile()));
+ }
}
}
}