aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/compat/DownloadManagerCompatUtils.java38
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ActionBatch.java45
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java12
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java14
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryService.java19
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java3
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java3
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java11
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java111
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java66
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java49
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitator.java21
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java81
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java2
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java145
-rw-r--r--java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java39
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java63
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java3
-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/spellcheck/AndroidSpellCheckerSession.java3
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java32
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java69
24 files changed, 324 insertions, 549 deletions
diff --git a/java/src/com/android/inputmethod/compat/DownloadManagerCompatUtils.java b/java/src/com/android/inputmethod/compat/DownloadManagerCompatUtils.java
new file mode 100644
index 000000000..6209b60b3
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/DownloadManagerCompatUtils.java
@@ -0,0 +1,38 @@
+/*
+ * 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.compat;
+
+import android.app.DownloadManager;
+
+import java.lang.reflect.Method;
+
+public final class DownloadManagerCompatUtils {
+ // DownloadManager.Request#setAllowedOverMetered() has been introduced
+ // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
+ private static final Method METHOD_setAllowedOverMetered = CompatUtils.getMethod(
+ DownloadManager.Request.class, "setAllowedOverMetered", boolean.class);
+
+ public static DownloadManager.Request setAllowedOverMetered(
+ final DownloadManager.Request request, final boolean allowOverMetered) {
+ return (DownloadManager.Request)CompatUtils.invoke(request,
+ request /* default return value */, METHOD_setAllowedOverMetered, allowOverMetered);
+ }
+
+ public static final boolean hasSetAllowedOverMetered() {
+ return null != METHOD_setAllowedOverMetered;
+ }
+}
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index 1b526d453..09f8032cc 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -25,9 +25,8 @@ import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
-import com.android.inputmethod.latin.BinaryDictionaryFileDumper;
+import com.android.inputmethod.compat.DownloadManagerCompatUtils;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.common.LocaleUtils;
import com.android.inputmethod.latin.utils.ApplicationUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
@@ -85,7 +84,7 @@ public final class ActionBatch {
* Execute this action NOW.
* @param context the context to get system services, resources, databases
*/
- void execute(final Context context);
+ public void execute(final Context context);
}
/**
@@ -97,10 +96,13 @@ public final class ActionBatch {
private final String mClientId;
// The data to download. May not be null.
final WordListMetadata mWordList;
- public StartDownloadAction(final String clientId, final WordListMetadata wordList) {
+ final boolean mForceStartNow;
+ public StartDownloadAction(final String clientId,
+ final WordListMetadata wordList, final boolean forceStartNow) {
DebugLogUtils.l("New download action for client ", clientId, " : ", wordList);
mClientId = clientId;
mWordList = wordList;
+ mForceStartNow = forceStartNow;
}
@Override
@@ -139,9 +141,32 @@ public final class ActionBatch {
final Request request = new Request(uri);
final Resources res = context.getResources();
- request.setAllowedNetworkTypes(Request.NETWORK_WIFI | Request.NETWORK_MOBILE);
+ if (!mForceStartNow) {
+ if (DownloadManagerCompatUtils.hasSetAllowedOverMetered()) {
+ final boolean allowOverMetered;
+ switch (UpdateHandler.getDownloadOverMeteredSetting(context)) {
+ case UpdateHandler.DOWNLOAD_OVER_METERED_DISALLOWED:
+ // User said no: don't allow.
+ allowOverMetered = false;
+ break;
+ case UpdateHandler.DOWNLOAD_OVER_METERED_ALLOWED:
+ // User said yes: allow.
+ allowOverMetered = true;
+ break;
+ default: // UpdateHandler.DOWNLOAD_OVER_METERED_SETTING_UNKNOWN
+ // Don't know: use the default value from configuration.
+ allowOverMetered = res.getBoolean(R.bool.allow_over_metered);
+ }
+ DownloadManagerCompatUtils.setAllowedOverMetered(request, allowOverMetered);
+ } else {
+ request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
+ }
+ request.setAllowedOverRoaming(res.getBoolean(R.bool.allow_over_roaming));
+ } // if mForceStartNow, then allow all network types and roaming, which is the default.
request.setTitle(mWordList.mDescription);
- request.setNotificationVisibility(Request.VISIBILITY_HIDDEN);
+ request.setNotificationVisibility(
+ res.getBoolean(R.bool.display_notification_for_auto_update)
+ ? Request.VISIBILITY_VISIBLE : Request.VISIBILITY_HIDDEN);
request.setVisibleInDownloadsUi(
res.getBoolean(R.bool.dict_downloads_visible_in_download_UI));
@@ -185,17 +210,9 @@ public final class ActionBatch {
+ " for an InstallAfterDownload action. Bailing out.");
return;
}
-
DebugLogUtils.l("Setting word list as installed");
final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
MetadataDbHelper.markEntryAsFinishedDownloadingAndInstalled(db, mWordListValues);
-
- // Install the downloaded file by un-compressing and moving it to the staging
- // directory. Ideally, we should do this before updating the DB, but the
- // installDictToStagingFromContentProvider() relies on the db being updated.
- final String localeString = mWordListValues.getAsString(MetadataDbHelper.LOCALE_COLUMN);
- BinaryDictionaryFileDumper.installDictToStagingFromContentProvider(
- LocaleUtils.constructLocaleFromString(localeString), context, false);
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java b/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
index 3d0e29ed0..3cd822a3c 100644
--- a/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
+++ b/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
@@ -22,6 +22,8 @@ import android.content.SharedPreferences;
public final class CommonPreferences {
private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs";
+ public static final String PREF_FORCE_DOWNLOAD_DICT = "pref_key_force_download_dict";
+
public static SharedPreferences getCommonPreferences(final Context context) {
return context.getSharedPreferences(COMMON_PREFERENCES_NAME, 0);
}
@@ -37,4 +39,14 @@ public final class CommonPreferences {
editor.putBoolean(id, false);
editor.apply();
}
+
+ public static boolean isForceDownloadDict(Context context) {
+ return getCommonPreferences(context).getBoolean(PREF_FORCE_DOWNLOAD_DICT, false);
+ }
+
+ public static void setForceDownloadDict(Context context, boolean forceDownload) {
+ SharedPreferences.Editor editor = getCommonPreferences(context).edit();
+ editor.putBoolean(PREF_FORCE_DOWNLOAD_DICT, forceDownload);
+ editor.apply();
+ }
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
index 308b123e1..659fe5c51 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
@@ -243,8 +243,14 @@ public final class DictionaryProvider extends ContentProvider {
// Fall through
case DICTIONARY_V1_DICT_INFO:
final String locale = uri.getLastPathSegment();
+ // If LatinIME does not have a dictionary for this locale at all, it will
+ // send us true for this value. In this case, we may prompt the user for
+ // a decision about downloading a dictionary even over a metered connection.
+ final String mayPromptValue =
+ uri.getQueryParameter(QUERY_PARAMETER_MAY_PROMPT_USER);
+ final boolean mayPrompt = QUERY_PARAMETER_TRUE.equals(mayPromptValue);
final Collection<WordListInfo> dictFiles =
- getDictionaryWordListsForLocale(clientId, locale);
+ getDictionaryWordListsForLocale(clientId, locale, mayPrompt);
// TODO: pass clientId to the following function
DictionaryService.updateNowIfNotUpdatedInAVeryLongTime(getContext());
if (null != dictFiles && dictFiles.size() > 0) {
@@ -337,10 +343,11 @@ public final class DictionaryProvider extends ContentProvider {
*
* @param clientId the ID of the client requesting the list
* @param locale the locale for which we want the list, as a String
+ * @param mayPrompt true if we are allowed to prompt the user for arbitration via notification
* @return a collection of ids. It is guaranteed to be non-null, but may be empty.
*/
private Collection<WordListInfo> getDictionaryWordListsForLocale(final String clientId,
- final String locale) {
+ final String locale, final boolean mayPrompt) {
final Context context = getContext();
final Cursor results =
MetadataDbHelper.queryInstalledOrDeletingOrAvailableDictionaryMetadata(context,
@@ -405,7 +412,8 @@ public final class DictionaryProvider extends ContentProvider {
}
} else if (MetadataDbHelper.STATUS_AVAILABLE == wordListStatus) {
// The locale is the id for the main dictionary.
- UpdateHandler.installIfNeverRequested(context, clientId, wordListId);
+ UpdateHandler.installIfNeverRequested(context, clientId, wordListId,
+ mayPrompt);
continue;
}
final WordListInfo currentBestMatch = dicts.get(wordListCategory);
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index fe988ac70..bbdf2a380 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -192,22 +192,27 @@ public final class DictionaryService extends Service {
}
static void dispatchBroadcast(final Context context, final Intent intent) {
- final String action = intent.getAction();
- if (DATE_CHANGED_INTENT_ACTION.equals(action)) {
+ if (DATE_CHANGED_INTENT_ACTION.equals(intent.getAction())) {
+ // Do not force download dictionaries on date change updates.
+ CommonPreferences.setForceDownloadDict(context, false);
// This happens when the date of the device changes. This normally happens
// at midnight local time, but it may happen if the user changes the date
// by hand or something similar happens.
checkTimeAndMaybeSetupUpdateAlarm(context);
- } else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(action)) {
+ } else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) {
// Intent to trigger an update now.
- UpdateHandler.tryUpdate(context);
- } else if (DictionaryPackConstants.INIT_AND_UPDATE_NOW_INTENT_ACTION.equals(action)) {
+ UpdateHandler.tryUpdate(context, CommonPreferences.isForceDownloadDict(context));
+ } else if (DictionaryPackConstants.INIT_AND_UPDATE_NOW_INTENT_ACTION.equals(
+ intent.getAction())) {
+ // Enable force download of dictionaries irrespective of wifi or metered connection.
+ CommonPreferences.setForceDownloadDict(context, true);
+
// Initialize the client Db.
final String mClientId = context.getString(R.string.dictionary_pack_client_id);
BinaryDictionaryFileDumper.initializeClientRecordHelper(context, mClientId);
// Updates the metadata and the download the dictionaries.
- UpdateHandler.tryUpdate(context);
+ UpdateHandler.tryUpdate(context, true);
} else {
UpdateHandler.downloadFinished(context, intent);
}
@@ -258,7 +263,7 @@ public final class DictionaryService extends Service {
*/
public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) {
if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME_MILLIS)) return;
- UpdateHandler.tryUpdate(context);
+ UpdateHandler.tryUpdate(context, CommonPreferences.isForceDownloadDict(context));
}
/**
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index 35b46a978..88ea4e6c3 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -384,7 +384,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment
// We call tryUpdate(), which returns whether we could successfully start an update.
// If we couldn't, we'll never receive the end callback, so we stop the loading
// animation and return to the previous screen.
- if (!UpdateHandler.tryUpdate(activity)) {
+ if (!UpdateHandler.tryUpdate(activity, true)) {
stopLoadingAnimation();
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java b/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java
index 6f6b02637..3dbbc9b9b 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java
@@ -27,8 +27,6 @@ import android.util.Log;
import java.io.FileNotFoundException;
-import javax.annotation.Nullable;
-
/**
* A class to help with calling DownloadManager methods.
*
@@ -80,7 +78,6 @@ public class DownloadManagerWrapper {
throw new FileNotFoundException();
}
- @Nullable
public Cursor query(final Query query) {
try {
if (null != mDownloadManager) {
diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java b/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
index 908d931a0..91ed673ae 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
@@ -80,7 +80,8 @@ public final class DownloadOverMeteredDialog extends Activity {
@SuppressWarnings("unused")
public void onClickAllow(final View v) {
UpdateHandler.setDownloadOverMeteredSetting(this, true);
- UpdateHandler.installIfNeverRequested(this, mClientId, mWordListToDownload);
+ UpdateHandler.installIfNeverRequested(this, mClientId, mWordListToDownload,
+ false /* mayPrompt */);
finish();
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
index 7d01351b4..fbc899192 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -50,7 +50,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
private static final int METADATA_DATABASE_VERSION_WITH_CLIENTID = 6;
// The current database version.
// This MUST be increased every time the dictionary pack metadata URL changes.
- private static final int CURRENT_METADATA_DATABASE_VERSION = 16;
+ private static final int CURRENT_METADATA_DATABASE_VERSION = 14;
private final static long NOT_A_DOWNLOAD_ID = -1;
@@ -266,6 +266,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
*/
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ // Allow automatic download of dictionaries on upgrading the database.
+ CommonPreferences.setForceDownloadDict(mContext, true);
if (METADATA_DATABASE_INITIAL_VERSION == oldVersion
&& METADATA_DATABASE_VERSION_WITH_CLIENTID <= newVersion
&& CURRENT_METADATA_DATABASE_VERSION >= newVersion) {
@@ -343,8 +345,6 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
return null != getMetadataUriAsString(context, clientId);
}
- private static final MetadataUriGetter sMetadataUriGetter = new MetadataUriGetter();
-
/**
* Returns the metadata URI as a string.
*
@@ -358,12 +358,13 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
public static String getMetadataUriAsString(final Context context, final String clientId) {
SQLiteDatabase defaultDb = MetadataDbHelper.getDb(context, null);
final Cursor cursor = defaultDb.query(MetadataDbHelper.CLIENT_TABLE_NAME,
- new String[] { MetadataDbHelper.CLIENT_METADATA_URI_COLUMN },
+ new String[] { MetadataDbHelper.CLIENT_METADATA_URI_COLUMN,
+ MetadataDbHelper.CLIENT_METADATA_ADDITIONAL_ID_COLUMN },
MetadataDbHelper.CLIENT_CLIENT_ID_COLUMN + " = ?", new String[] { clientId },
null, null, null, null);
try {
if (!cursor.moveToFirst()) return null;
- return sMetadataUriGetter.getUri(context, cursor.getString(0));
+ return MetadataUriGetter.getUri(context, cursor.getString(0), cursor.getString(1));
} finally {
cursor.close();
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index a02203d31..e61547a9d 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -36,6 +36,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.compat.ConnectivityManagerCompatUtils;
+import com.android.inputmethod.compat.DownloadManagerCompatUtils;
import com.android.inputmethod.compat.NotificationCompatUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.common.LocaleUtils;
@@ -105,9 +106,9 @@ public final class UpdateHandler {
* This is chiefly used by the dictionary manager UI.
*/
public interface UpdateEventListener {
- void downloadedMetadata(boolean succeeded);
- void wordListDownloadFinished(String wordListId, boolean succeeded);
- void updateCycleCompleted();
+ public void downloadedMetadata(boolean succeeded);
+ public void wordListDownloadFinished(String wordListId, boolean succeeded);
+ public void updateCycleCompleted();
}
/**
@@ -178,9 +179,10 @@ public final class UpdateHandler {
/**
* Download latest metadata from the server through DownloadManager for all known clients
* @param context The context for retrieving resources
+ * @param updateNow Whether we should update NOW, or respect bandwidth policies
* @return true if an update successfully started, false otherwise.
*/
- public static boolean tryUpdate(final Context context) {
+ public static boolean tryUpdate(final Context context, final boolean updateNow) {
// TODO: loop through all clients instead of only doing the default one.
final TreeSet<String> uris = new TreeSet<>();
final Cursor cursor = MetadataDbHelper.queryClientIds(context);
@@ -206,7 +208,7 @@ public final class UpdateHandler {
// it should have been rejected at the time of client registration; if there
// is a bug and it happens anyway, doing nothing is the right thing to do.
// For more information, {@see DictionaryProvider#insert(Uri, ContentValues)}.
- updateClientsWithMetadataUri(context, metadataUri);
+ updateClientsWithMetadataUri(context, updateNow, metadataUri);
started = true;
}
}
@@ -217,11 +219,12 @@ public final class UpdateHandler {
* Download latest metadata from the server through DownloadManager for all relevant clients
*
* @param context The context for retrieving resources
+ * @param updateNow Whether we should update NOW, or respect bandwidth policies
* @param metadataUri The client to update
*/
- private static void updateClientsWithMetadataUri(
- final Context context, final String metadataUri) {
- Log.i(TAG, "updateClientsWithMetadataUri() : MetadataUri = " + metadataUri);
+ private static void updateClientsWithMetadataUri(final Context context,
+ final boolean updateNow, final String metadataUri) {
+ PrivateLog.log("Update for metadata URI " + DebugLogUtils.s(metadataUri));
// Adding a disambiguator to circumvent a bug in older versions of DownloadManager.
// DownloadManager also stupidly cuts the extension to replace with its own that it
// gets from the content-type. We need to circumvent this.
@@ -231,10 +234,25 @@ public final class UpdateHandler {
DebugLogUtils.l("Request =", metadataRequest);
final Resources res = context.getResources();
- metadataRequest.setAllowedNetworkTypes(Request.NETWORK_WIFI | Request.NETWORK_MOBILE);
+ // By default, download over roaming is allowed and all network types are allowed too.
+ if (!updateNow) {
+ final boolean allowedOverMetered = res.getBoolean(R.bool.allow_over_metered);
+ // If we don't have to update NOW, then only do it over non-metered connections.
+ if (DownloadManagerCompatUtils.hasSetAllowedOverMetered()) {
+ DownloadManagerCompatUtils.setAllowedOverMetered(metadataRequest,
+ allowedOverMetered);
+ } else if (!allowedOverMetered) {
+ metadataRequest.setAllowedNetworkTypes(Request.NETWORK_WIFI);
+ }
+ metadataRequest.setAllowedOverRoaming(res.getBoolean(R.bool.allow_over_roaming));
+ }
+ final boolean notificationVisible = updateNow
+ ? res.getBoolean(R.bool.display_notification_for_user_requested_update)
+ : res.getBoolean(R.bool.display_notification_for_auto_update);
+
metadataRequest.setTitle(res.getString(R.string.download_description));
- // Do not show the notification when downloading the metadata.
- metadataRequest.setNotificationVisibility(Request.VISIBILITY_HIDDEN);
+ metadataRequest.setNotificationVisibility(notificationVisible
+ ? Request.VISIBILITY_VISIBLE : Request.VISIBILITY_HIDDEN);
metadataRequest.setVisibleInDownloadsUi(
res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI));
@@ -255,7 +273,7 @@ public final class UpdateHandler {
// method will ignore it.
writeMetadataDownloadId(context, metadataUri, downloadId);
}
- Log.i(TAG, "updateClientsWithMetadataUri() : DownloadId = " + downloadId);
+ PrivateLog.log("Requested download with id " + downloadId);
}
/**
@@ -327,11 +345,11 @@ public final class UpdateHandler {
*/
public static long registerDownloadRequest(final DownloadManagerWrapper manager,
final Request request, final SQLiteDatabase db, final String id, final int version) {
- Log.i(TAG, "registerDownloadRequest() : Id = " + id + " : Version = " + version);
+ DebugLogUtils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version);
final long downloadId;
synchronized (sSharedIdProtector) {
downloadId = manager.enqueue(request);
- Log.i(TAG, "registerDownloadRequest() : DownloadId = " + downloadId);
+ DebugLogUtils.l("Download requested with id", downloadId);
MetadataDbHelper.markEntryAsDownloading(db, id, version, downloadId);
}
return downloadId;
@@ -416,7 +434,8 @@ public final class UpdateHandler {
/* package */ static void downloadFinished(final Context context, final Intent intent) {
// Get and check the ID of the file that was downloaded
final long fileId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, NOT_AN_ID);
- Log.i(TAG, "downloadFinished() : DownloadId = " + fileId);
+ PrivateLog.log("Download finished with id " + fileId);
+ DebugLogUtils.l("DownloadFinished with id", fileId);
if (NOT_AN_ID == fileId) return; // Spurious wake-up: ignore
final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
@@ -432,27 +451,31 @@ public final class UpdateHandler {
// download, so we are pretty sure it's alive. It's theoretically possible that it's
// disabled right inbetween the firing of the intent and the control reaching here.
+ boolean dictionaryDownloaded = false;
+
for (final DownloadRecord record : recordList) {
// downloadSuccessful is not final because we may still have exceptions from now on
boolean downloadSuccessful = false;
try {
if (downloadInfo.wasSuccessful()) {
downloadSuccessful = handleDownloadedFile(context, record, manager, fileId);
- Log.i(TAG, "downloadFinished() : Success = " + downloadSuccessful);
}
} finally {
- final String resultMessage = downloadSuccessful ? "Success" : "Failure";
if (record.isMetadata()) {
- Log.i(TAG, "downloadFinished() : Metadata " + resultMessage);
publishUpdateMetadataCompleted(context, downloadSuccessful);
} else {
- Log.i(TAG, "downloadFinished() : WordList " + resultMessage);
final SQLiteDatabase db = MetadataDbHelper.getDb(context, record.mClientId);
publishUpdateWordListCompleted(context, downloadSuccessful, fileId,
db, record.mAttributes, record.mClientId);
+ dictionaryDownloaded = true;
}
}
}
+
+ if (dictionaryDownloaded) {
+ // Disable the force download after downloading the dictionaries.
+ CommonPreferences.setForceDownloadDict(context, false);
+ }
// Now that we're done using it, we can remove this download from DLManager
manager.remove(fileId);
}
@@ -569,8 +592,6 @@ public final class UpdateHandler {
* Warn Android Keyboard that the state of dictionaries changed and it should refresh its data.
*/
private static void signalNewDictionaryState(final Context context) {
- // TODO: Also provide the locale of the updated dictionary so that the LatinIme
- // does not have to reset if it is a different locale.
final Intent newDictBroadcast =
new Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
context.sendBroadcast(newDictBroadcast);
@@ -585,7 +606,7 @@ public final class UpdateHandler {
* @throws BadFormatException if the metadata is not in a known format.
* @throws IOException if the downloaded file can't be read from the disk
*/
- public static void handleMetadata(final Context context, final InputStream stream,
+ private static void handleMetadata(final Context context, final InputStream stream,
final String clientId) throws IOException, BadFormatException {
DebugLogUtils.l("Entering handleMetadata");
final List<WordListMetadata> newMetadata;
@@ -809,7 +830,8 @@ public final class UpdateHandler {
actions.add(new ActionBatch.MakeAvailableAction(clientId, newInfo));
if (status == MetadataDbHelper.STATUS_INSTALLED
|| status == MetadataDbHelper.STATUS_DISABLED) {
- actions.add(new ActionBatch.StartDownloadAction(clientId, newInfo));
+ actions.add(new ActionBatch.StartDownloadAction(
+ clientId, newInfo, CommonPreferences.isForceDownloadDict(context)));
} else {
// Pass true to ForgetAction: this is indeed an update to a non-installed
// word list, so activate status == AVAILABLE check
@@ -907,9 +929,7 @@ public final class UpdateHandler {
// list because it may only install the latest version we know about for this specific
// word list ID / client ID combination.
public static void installIfNeverRequested(final Context context, final String clientId,
- final String wordlistId) {
- Log.i(TAG, "installIfNeverRequested() : ClientId = " + clientId
- + " : WordListId = " + wordlistId);
+ final String wordlistId, final boolean mayPrompt) {
final String[] idArray = wordlistId.split(DictionaryProvider.ID_CATEGORY_SEPARATOR);
// If we have a new-format dictionary id (category:manual_id), then use the
// specified category. Otherwise, it is a main dictionary, so force the
@@ -942,6 +962,17 @@ public final class UpdateHandler {
return;
}
+ if (mayPrompt
+ && DOWNLOAD_OVER_METERED_SETTING_UNKNOWN
+ == getDownloadOverMeteredSetting(context)) {
+ final ConnectivityManager cm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (ConnectivityManagerCompatUtils.isActiveNetworkMetered(cm)) {
+ showDictionaryAvailableNotification(context, clientId, installCandidate);
+ return;
+ }
+ }
+
// We decided against prompting the user for a decision. This may be because we were
// explicitly asked not to, or because we are currently on wi-fi anyway, or because we
// already know the answer to the question. We'll enqueue a request ; StartDownloadAction
@@ -953,18 +984,21 @@ public final class UpdateHandler {
// change the shared preferences. So there is no way for a word list that has been
// auto-installed once to get auto-installed again, and that's what we want.
final ActionBatch actions = new ActionBatch();
- WordListMetadata metadata = WordListMetadata.createFromContentValues(installCandidate);
- actions.add(new ActionBatch.StartDownloadAction(clientId, metadata));
+ actions.add(new ActionBatch.StartDownloadAction(
+ clientId,
+ WordListMetadata.createFromContentValues(installCandidate),
+ CommonPreferences.isForceDownloadDict(context)));
final String localeString = installCandidate.getAsString(MetadataDbHelper.LOCALE_COLUMN);
// We are in a content provider: we can't do any UI at all. We have to defer the displaying
// itself to the service. Also, we only display this when the user does not have a
- // dictionary for this language already.
- final Intent intent = new Intent();
- intent.setClass(context, DictionaryService.class);
- intent.setAction(DictionaryService.SHOW_DOWNLOAD_TOAST_INTENT_ACTION);
- intent.putExtra(DictionaryService.LOCALE_INTENT_ARGUMENT, localeString);
- context.startService(intent);
- Log.i(TAG, "installIfNeverRequested() : StartDownloadAction for " + metadata);
+ // dictionary for this language already: we know that from the mayPrompt argument.
+ if (mayPrompt) {
+ final Intent intent = new Intent();
+ intent.setClass(context, DictionaryService.class);
+ intent.setAction(DictionaryService.SHOW_DOWNLOAD_TOAST_INTENT_ACTION);
+ intent.putExtra(DictionaryService.LOCALE_INTENT_ARGUMENT, localeString);
+ context.startService(intent);
+ }
actions.execute(context, new LogProblemReporter(TAG));
}
@@ -999,7 +1033,9 @@ public final class UpdateHandler {
|| MetadataDbHelper.STATUS_DELETING == status) {
actions.add(new ActionBatch.EnableAction(clientId, wordListMetaData));
} else if (MetadataDbHelper.STATUS_AVAILABLE == status) {
- actions.add(new ActionBatch.StartDownloadAction(clientId, wordListMetaData));
+ boolean forceDownloadDict = CommonPreferences.isForceDownloadDict(context);
+ actions.add(new ActionBatch.StartDownloadAction(clientId, wordListMetaData,
+ forceDownloadDict || allowDownloadOnMeteredData));
} else {
Log.e(TAG, "Unexpected state of the word list for markAsUsed : " + status);
}
@@ -1114,7 +1150,8 @@ public final class UpdateHandler {
}
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.StartDownloadAction(clientId, wordListMetaData));
+ actions.add(new ActionBatch.StartDownloadAction(
+ clientId, wordListMetaData, CommonPreferences.isForceDownloadDict(context)));
actions.execute(context, new LogProblemReporter(TAG));
} else {
if (DEBUG) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index d25c1d373..bc62f3ae3 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -29,8 +29,6 @@ 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;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils.DictionaryInfo;
@@ -222,11 +220,11 @@ public final class BinaryDictionaryFileDumper {
}
/**
- * Stages a word list the id of which is passed as an argument. This will write the file
+ * Caches a word list the id of which is passed as an argument. This will write the file
* to the cache file name designated by its id and locale, overwriting it if already present
* and creating it (and its containing directory) if necessary.
*/
- private static void installWordListToStaging(final String wordlistId, final String locale,
+ private static void cacheWordList(final String wordlistId, final String locale,
final String rawChecksum, final ContentProviderClient providerClient,
final Context context) {
final int COMPRESSED_CRYPTED_COMPRESSED = 0;
@@ -248,7 +246,7 @@ public final class BinaryDictionaryFileDumper {
return;
}
final String finalFileName =
- DictionaryInfoUtils.getStagingFileName(wordlistId, locale, context);
+ DictionaryInfoUtils.getCacheFileName(wordlistId, locale, context);
String tempFileName;
try {
tempFileName = BinaryDictionaryGetter.getTempFileName(wordlistId, context);
@@ -322,21 +320,23 @@ public final class BinaryDictionaryFileDumper {
}
}
- // move the output file to the final staging file.
final File finalFile = new File(finalFileName);
- FileUtils.renameTo(outputFile, finalFile);
-
+ finalFile.delete();
+ if (!outputFile.renameTo(finalFile)) {
+ throw new IOException("Can't move the file to its final name");
+ }
wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
QUERY_PARAMETER_SUCCESS);
if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
Log.e(TAG, "Could not have the dictionary pack delete a word list");
}
- Log.d(TAG, "Successfully copied file for wordlist ID " + wordlistId);
+ BinaryDictionaryGetter.removeFilesWithIdExcept(context, wordlistId, finalFile);
+ Log.e(TAG, "Successfully copied file for wordlist ID " + wordlistId);
// Success! Close files (through the finally{} clause) and return.
return;
} catch (Exception e) {
if (DEBUG) {
- Log.e(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
@@ -403,7 +403,7 @@ public final class BinaryDictionaryFileDumper {
}
/**
- * Queries a content provider for word list data for some locale and stage the returned files
+ * Queries a content provider for word list data for some locale and cache the returned files
*
* This will query a content provider for word list data for a given locale, and copy the
* files locally so that they can be mmap'ed. This may overwrite previously cached word lists
@@ -411,7 +411,7 @@ public final class BinaryDictionaryFileDumper {
* @throw FileNotFoundException if the provider returns non-existent data.
* @throw IOException if the provider-returned data could not be read.
*/
- public static void installDictToStagingFromContentProvider(final Locale locale,
+ public static void cacheWordListsFromContentProvider(final Locale locale,
final Context context, final boolean hasDefaultWordList) {
final ContentProviderClient providerClient;
try {
@@ -429,8 +429,7 @@ public final class BinaryDictionaryFileDumper {
final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
hasDefaultWordList);
for (WordListInfo id : idList) {
- installWordListToStaging(id.mId, id.mLocale, id.mRawChecksum, providerClient,
- context);
+ cacheWordList(id.mId, id.mLocale, id.mRawChecksum, providerClient, context);
}
} finally {
providerClient.release();
@@ -438,18 +437,6 @@ public final class BinaryDictionaryFileDumper {
}
/**
- * Downloads the dictionary if it was never requested/used.
- *
- * @param locale locale to download
- * @param context the context for resources and providers.
- * @param hasDefaultWordList whether the default wordlist exists in the resources.
- */
- public static void downloadDictIfNeverRequested(final Locale locale,
- final Context context, final boolean hasDefaultWordList) {
- getWordListWordListInfos(locale, context, hasDefaultWordList);
- }
-
- /**
* Copies the data in an input stream to a target file if the magic number matches.
*
* If the magic number does not match the expected value, this method throws an
@@ -488,8 +475,6 @@ 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)
@@ -514,34 +499,9 @@ 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/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 60016371b..5f2a112ba 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -195,6 +195,39 @@ final public class BinaryDictionaryGetter {
return result;
}
+ /**
+ * Remove all files with the passed id, except the passed file.
+ *
+ * If a dictionary with a given ID has a metadata change that causes it to change
+ * path, we need to remove the old version. The only way to do this is to check all
+ * installed files for a matching ID in a different directory.
+ */
+ public static void removeFilesWithIdExcept(final Context context, final String id,
+ final File fileToKeep) {
+ try {
+ final File canonicalFileToKeep = fileToKeep.getCanonicalFile();
+ final File[] directoryList = DictionaryInfoUtils.getCachedDirectoryList(context);
+ if (null == directoryList) return;
+ for (File directory : directoryList) {
+ // There is one directory per locale. See #getCachedDirectoryList
+ if (!directory.isDirectory()) continue;
+ final File[] wordLists = directory.listFiles();
+ if (null == wordLists) continue;
+ for (File wordList : wordLists) {
+ final String fileId =
+ DictionaryInfoUtils.getWordListIdFromFileName(wordList.getName());
+ if (fileId.equals(id)) {
+ if (!canonicalFileToKeep.equals(wordList.getCanonicalFile())) {
+ wordList.delete();
+ }
+ }
+ }
+ }
+ } catch (java.io.IOException e) {
+ Log.e(TAG, "IOException trying to cleanup files", e);
+ }
+ }
+
// ## HACK ## we prevent usage of a dictionary before version 18. The reason for this is, since
// those do not include whitelist entries, the new code with an old version of the dictionary
// would lose whitelist functionality.
@@ -241,18 +274,12 @@ final public class BinaryDictionaryGetter {
*/
public static ArrayList<AssetFileAddress> getDictionaryFiles(final Locale locale,
final Context context, boolean notifyDictionaryPackForUpdates) {
- if (notifyDictionaryPackForUpdates) {
- final boolean hasDefaultWordList = DictionaryInfoUtils.isDictionaryAvailable(
- context, locale);
- // It makes sure that the first time keyboard comes up and the dictionaries are reset,
- // the DB is populated with the appropriate values for each locale. Helps in downloading
- // the dictionaries when the user enables and switches new languages before the
- // DictionaryService runs.
- BinaryDictionaryFileDumper.downloadDictIfNeverRequested(
- locale, context, hasDefaultWordList);
- // Move a staging files to the cache ddirectories if any.
- DictionaryInfoUtils.moveStagingFilesIfExists(context);
+ final boolean hasDefaultWordList = DictionaryInfoUtils.isDictionaryAvailable(
+ context, locale);
+ if (notifyDictionaryPackForUpdates) {
+ BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context,
+ hasDefaultWordList);
}
final File[] cachedWordLists = getCachedWordLists(locale.toString(), context);
final String mainDictId = DictionaryInfoUtils.getMainDictId(locale);
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 02015da09..ff798abd6 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin;
import android.content.Context;
-import android.util.LruCache;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.Keyboard;
@@ -56,18 +55,6 @@ public interface DictionaryFacilitator {
Dictionary.TYPE_USER};
/**
- * The facilitator will put words into the cache whenever it decodes them.
- * @param cache
- */
- void setValidSpellingWordReadCache(final LruCache<String, Boolean> cache);
-
- /**
- * The facilitator will get words from the cache whenever it needs to check their spelling.
- * @param cache
- */
- void setValidSpellingWordWriteCache(final LruCache<String, Boolean> cache);
-
- /**
* Returns whether this facilitator is exactly for this locale.
*
* @param locale the locale to test against
@@ -101,16 +88,12 @@ public interface DictionaryFacilitator {
*
* WARNING: The service methods that call start/finish are very spammy.
*/
- void onFinishInput(Context context);
+ void onFinishInput();
boolean isActive();
Locale getLocale();
- boolean usesContacts();
-
- String getAccount();
-
void resetDictionaries(
final Context context,
final Locale newLocale,
@@ -166,7 +149,7 @@ public interface DictionaryFacilitator {
boolean isValidSuggestionWord(final String word);
- boolean clearUserHistoryDictionary(final Context context);
+ void clearUserHistoryDictionary(final Context context);
String dump(final Context context);
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
index c7115c9d9..7233d27ab 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
@@ -19,7 +19,6 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
-import android.util.LruCache;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.Keyboard;
@@ -27,7 +26,6 @@ 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;
@@ -84,19 +82,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
new Class[] { Context.class, Locale.class, File.class, String.class, String.class };
- private LruCache<String, Boolean> mValidSpellingWordReadCache;
- private LruCache<String, Boolean> mValidSpellingWordWriteCache;
-
- @Override
- public void setValidSpellingWordReadCache(final LruCache<String, Boolean> cache) {
- mValidSpellingWordReadCache = cache;
- }
-
- @Override
- public void setValidSpellingWordWriteCache(final LruCache<String, Boolean> cache) {
- mValidSpellingWordWriteCache = cache;
- }
-
@Override
public boolean isForLocale(final Locale locale) {
return locale != null && locale.equals(mDictionaryGroup.mLocale);
@@ -222,7 +207,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
}
@Override
- public void onFinishInput(Context context) {
+ public void onFinishInput() {
}
@Override
@@ -235,16 +220,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
return mDictionaryGroup.mLocale;
}
- @Override
- public boolean usesContacts() {
- return mDictionaryGroup.getSubDict(Dictionary.TYPE_CONTACTS) != null;
- }
-
- @Override
- public String getAccount() {
- return null;
- }
-
@Nullable
private static ExpandableBinaryDictionary getSubDict(final String dictType,
final Context context, final Locale locale, final File dictFile,
@@ -366,10 +341,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
dictionarySetToCleanup.closeDict(dictType);
}
}
-
- if (mValidSpellingWordWriteCache != null) {
- mValidSpellingWordWriteCache.evictAll();
- }
}
private void asyncReloadUninitializedMainDictionaries(final Context context,
@@ -493,10 +464,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
@Nonnull final NgramContext ngramContext, final long timeStampInSeconds,
final boolean blockPotentiallyOffensive) {
- // Update the spelling cache before learning. Words that are not yet added to user history
- // and appear in no other language model are not considered valid.
- putWordIntoValidSpellingWordCache("addToUserHistory", suggestion);
-
final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
NgramContext ngramContextForCurrentWord = ngramContext;
for (int i = 0; i < words.length; i++) {
@@ -510,29 +477,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
}
}
- 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,
final NgramContext ngramContext, final String word, final boolean wasAutoCapitalized,
final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
@@ -599,10 +543,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
if (eventType != Constants.EVENT_BACKSPACE) {
removeWord(Dictionary.TYPE_USER_HISTORY, word);
}
-
- // Update the spelling cache after unlearning. Words that are removed from user history
- // and appear in no other language model are not considered valid.
- putWordIntoValidSpellingWordCache("unlearnFromUserHistory", word.toLowerCase());
}
// TODO: Revise the way to fusion suggestion results.
@@ -637,13 +577,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
}
public boolean isValidSpellingWord(final String word) {
- if (mValidSpellingWordReadCache != null) {
- final Boolean cachedValue = mValidSpellingWordReadCache.get(word);
- if (cachedValue != null) {
- return cachedValue;
- }
- }
-
return isValidWord(word, ALL_DICTIONARY_TYPES);
}
@@ -687,18 +620,16 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
return maxFreq;
}
- private boolean clearSubDictionary(final String dictName) {
+ private void clearSubDictionary(final String dictName) {
final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName);
- if (dictionary == null) {
- return false;
+ if (dictionary != null) {
+ dictionary.clear();
}
- dictionary.clear();
- return true;
}
@Override
- public boolean clearUserHistoryDictionary(final Context context) {
- return clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
+ public void clearUserHistoryDictionary(final Context context) {
+ clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 089670ebf..330be377b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -972,7 +972,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
void onFinishInputInternal() {
super.onFinishInput();
- mDictionaryFacilitator.onFinishInput(this);
+ mDictionaryFacilitator.onFinishInput();
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
mainKeyboardView.closing();
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index a10f2bdb0..a123d282b 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -16,10 +16,11 @@
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;
@@ -36,6 +37,7 @@ 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;
@@ -43,11 +45,8 @@ 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;
@@ -60,42 +59,17 @@ import javax.annotation.Nullable;
* for example.
*/
public final class RichInputConnection implements PrivateCommandPerformer {
- private static final String TAG = "RichInputConnection";
+ private static final String TAG = RichInputConnection.class.getSimpleName();
private static final boolean DBG = false;
private static final boolean DEBUG_PREVIOUS_TEXT = false;
private static final boolean DEBUG_BATCH_NESTING = false;
- private static final int NUM_CHARS_TO_GET_BEFORE_CURSOR = 40;
- private static final int NUM_CHARS_TO_GET_AFTER_CURSOR = 40;
+ // 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 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
@@ -111,7 +85,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();
/**
@@ -126,13 +100,8 @@ public final class RichInputConnection implements PrivateCommandPerformer {
private SpannableStringBuilder mTempObjectForCommitText = new SpannableStringBuilder();
private final InputMethodService mParent;
- private InputConnection mIC;
- private int mNestLevel;
-
- /**
- * The timestamp of the last slow InputConnection operation
- */
- private long mLastSlowInputConnectionTime = -SLOW_INPUTCONNECTION_PERSIST_MS;
+ InputConnection mIC;
+ int mNestLevel;
public RichInputConnection(final InputMethodService parent) {
mParent = parent;
@@ -144,19 +113,6 @@ 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;
@@ -255,11 +211,9 @@ 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 = getTextBeforeCursorAndDetectLaggyConnection(
- OPERATION_RELOAD_TEXT_CACHE,
- SLOW_INPUT_CONNECTION_ON_FULL_RELOAD_MS,
- Constants.EDITOR_CONTENTS_CACHE_SIZE,
- 0 /* flags */);
+ final CharSequence textBeforeCursor = isConnected()
+ ? mIC.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0)
+ : null;
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.
@@ -423,54 +377,16 @@ 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();
- if (!isConnected()) {
- return null;
- }
- final long startTime = SystemClock.uptimeMillis();
- final CharSequence result = mIC.getTextBeforeCursor(n, flags);
- detectLaggyConnection(operation, timeout, startTime);
- return result;
+ return isConnected() ? mIC.getTextBeforeCursor(n, flags) : null;
}
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();
- 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();
- }
+ return isConnected() ? mIC.getTextAfterCursor(n, flags) : null;
}
- public void deleteTextBeforeCursor(final int beforeLength) {
+ public void deleteSurroundingText(final int beforeLength, final int afterLength) {
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
@@ -495,7 +411,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
mExpectedSelStart = 0;
}
if (isConnected()) {
- mIC.deleteSurroundingText(beforeLength, 0);
+ mIC.deleteSurroundingText(beforeLength, afterLength);
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -660,9 +576,9 @@ public final class RichInputConnection implements PrivateCommandPerformer {
if (!isConnected()) {
return NgramContext.EMPTY_PREV_WORDS_INFO;
}
- final CharSequence prev = getTextBeforeCursor(NUM_CHARS_TO_GET_BEFORE_CURSOR, 0);
+ final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
if (DEBUG_PREVIOUS_TEXT && null != prev) {
- final int checkLength = NUM_CHARS_TO_GET_BEFORE_CURSOR - 1;
+ final int checkLength = LOOKBACK_CHARACTER_NUM - 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
@@ -705,15 +621,9 @@ public final class RichInputConnection implements PrivateCommandPerformer {
if (!isConnected()) {
return null;
}
- final CharSequence before = getTextBeforeCursorAndDetectLaggyConnection(
- OPERATION_GET_WORD_RANGE_AT_CURSOR,
- SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS,
- NUM_CHARS_TO_GET_BEFORE_CURSOR,
+ final CharSequence before = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM,
InputConnection.GET_TEXT_WITH_STYLES);
- final CharSequence after = getTextAfterCursorAndDetectLaggyConnection(
- OPERATION_GET_WORD_RANGE_AT_CURSOR,
- SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS,
- NUM_CHARS_TO_GET_AFTER_CURSOR,
+ final CharSequence after = mIC.getTextAfterCursor(LOOKBACK_CHARACTER_NUM,
InputConnection.GET_TEXT_WITH_STYLES);
if (before == null || after == null) {
return null;
@@ -756,9 +666,8 @@ public final class RichInputConnection implements PrivateCommandPerformer {
hasUrlSpans);
}
- public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations,
- boolean checkTextAfter) {
- if (checkTextAfter && isCursorFollowedByWordCharacter(spacingAndPunctuations)) {
+ public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations) {
+ if (isCursorFollowedByWordCharacter(spacingAndPunctuations)) {
// If what's after the cursor is a word character, then we're touching a word.
return true;
}
@@ -795,7 +704,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
final int codePointBeforeCursor = getCodePointBeforeCursor();
if (Constants.CODE_SPACE == codePointBeforeCursor) {
- deleteTextBeforeCursor(1);
+ deleteSurroundingText(1, 0);
}
}
@@ -821,7 +730,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.
- deleteTextBeforeCursor(2);
+ deleteSurroundingText(2, 0);
final String singleSpace = " ";
commitText(singleSpace, 1);
return true;
@@ -843,7 +752,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
+ "find a space just before the cursor.");
return false;
}
- deleteTextBeforeCursor(2);
+ deleteSurroundingText(2, 0);
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 90221512f..0d081e0d2 100644
--- a/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
@@ -16,7 +16,6 @@
package com.android.inputmethod.latin;
-import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -24,15 +23,14 @@ 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;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.dictionarypack.CommonPreferences;
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;
@@ -77,12 +75,7 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
final InputMethodSubtype[] additionalSubtypes = richImm.getAdditionalSubtypes();
richImm.setAdditionalInputMethodSubtypes(additionalSubtypes);
toggleAppIcon(context);
-
- // 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);
+ downloadLatestDictionaries(context);
} else if (Intent.ACTION_BOOT_COMPLETED.equals(intentAction)) {
Log.i(TAG, "Boot has been completed");
toggleAppIcon(context);
@@ -110,39 +103,13 @@ 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);
context.sendBroadcast(updateIntent);
}
- public static void toggleAppIcon(final Context context) {
+ private static void toggleAppIcon(final Context context) {
final int appInfoFlags = context.getApplicationInfo().flags;
final boolean isSystemApp = (appInfoFlags & ApplicationInfo.FLAG_SYSTEM) > 0;
if (Log.isLoggable(TAG, Log.INFO)) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 1dd5850f8..f7dbc0a4d 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -139,7 +139,6 @@ 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.
@@ -399,8 +398,9 @@ public final class InputLogic {
if (!TextUtils.isEmpty(mWordBeingCorrectedByCursor)) {
final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
System.currentTimeMillis());
- performAdditionToUserHistoryDictionary(settingsValues, mWordBeingCorrectedByCursor,
- NgramContext.EMPTY_PREV_WORDS_INFO);
+ mDictionaryFacilitator.addToUserHistory(mWordBeingCorrectedByCursor, false,
+ NgramContext.EMPTY_PREV_WORDS_INFO, timeStampInSeconds,
+ settingsValues.mBlockPotentiallyOffensive);
}
} else {
// resetEntireInputState calls resetCachesUponCursorMove, but forcing the
@@ -473,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 (!mConnection.hasSlowInputConnection() && !mWordComposer.isComposingWord()
+ if (!mWordComposer.isComposingWord()
&& (settingsValues.isWordCodePoint(processedEvent.mCodePoint) ||
processedEvent.mKeyCode == Constants.CODE_DELETE)) {
mWordBeingCorrectedByCursor = getWordAtCursor(
@@ -833,14 +833,8 @@ 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.
- // 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 */))) {
+ (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)
+ || !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces)) {
// 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
@@ -1060,7 +1054,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.deleteTextBeforeCursor(mEnteredText.length());
+ mConnection.deleteSurroundingText(mEnteredText.length(), 0);
StatsUtils.onDeleteMultiCharInput(mEnteredText.length());
mEnteredText = null;
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
@@ -1105,7 +1099,7 @@ public final class InputLogic {
- mConnection.getExpectedSelectionStart();
mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
mConnection.getExpectedSelectionEnd());
- mConnection.deleteTextBeforeCursor(numCharsDeleted);
+ mConnection.deleteSurroundingText(numCharsDeleted, 0);
StatsUtils.onBackspaceSelectedText(numCharsDeleted);
} else {
// There is no selection, just delete one character.
@@ -1145,13 +1139,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.deleteTextBeforeCursor(1);
+ mConnection.deleteSurroundingText(1, 0);
// TODO: Add a new StatsUtils method onBackspaceWhenNoText()
return;
}
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
- mConnection.deleteTextBeforeCursor(lengthToDelete);
+ mConnection.deleteSurroundingText(lengthToDelete, 0);
int totalDeletedLength = lengthToDelete;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
// If this is an accelerated (i.e., double) deletion, then we need to
@@ -1164,7 +1158,7 @@ public final class InputLogic {
if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
codePointBeforeCursorToDeleteAgain) ? 2 : 1;
- mConnection.deleteTextBeforeCursor(lengthToDeleteAgain);
+ mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
totalDeletedLength += lengthToDeleteAgain;
}
}
@@ -1176,9 +1170,7 @@ public final class InputLogic {
unlearnWordBeingDeleted(
inputTransaction.mSettingsValues, currentKeyboardScriptId);
}
- if (mConnection.hasSlowInputConnection()) {
- mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
- } else if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings()
+ if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings()
&& inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mCurrentLanguageHasSpaces
&& !mConnection.isCursorFollowedByWordCharacter(
@@ -1205,13 +1197,6 @@ 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.
@@ -1257,7 +1242,7 @@ public final class InputLogic {
if (Constants.CODE_SPACE != codePointBeforeCursor) {
return false;
}
- mConnection.deleteTextBeforeCursor(1);
+ mConnection.deleteSurroundingText(1, 0);
final String text = event.getTextToCommit() + " ";
mConnection.commitText(text, 1);
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
@@ -1347,7 +1332,7 @@ public final class InputLogic {
Character.codePointAt(lastTwo, length - 3) : lastTwo.charAt(length - 2);
if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) {
cancelDoubleSpacePeriodCountdown();
- mConnection.deleteTextBeforeCursor(1);
+ mConnection.deleteSurroundingText(1, 0);
final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mSentenceSeparatorAndSpace;
mConnection.commitText(textToInsert, 1);
@@ -1415,7 +1400,7 @@ public final class InputLogic {
mConnection.finishComposingText();
mRecapitalizeStatus.rotate();
mConnection.setSelection(selectionEnd, selectionEnd);
- mConnection.deleteTextBeforeCursor(numCharsSelected);
+ mConnection.deleteSurroundingText(numCharsSelected, 0);
mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0);
mConnection.setSelection(mRecapitalizeStatus.getNewCursorStart(),
mRecapitalizeStatus.getNewCursorEnd());
@@ -1427,12 +1412,6 @@ 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 =
@@ -1536,8 +1515,7 @@ public final class InputLogic {
return;
}
final int expectedCursorPosition = mConnection.getExpectedSelectionStart();
- if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations,
- true /* checkTextAfter */)) {
+ if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)) {
// Show predictions.
mWordComposer.setCapitalizedModeAtStartComposingTime(WordComposer.CAPS_MODE_OFF);
mLatinIME.mHandler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_RECORRECTION);
@@ -1660,7 +1638,7 @@ public final class InputLogic {
+ "\", but before the cursor we found \"" + wordBeforeCursor + "\"");
}
}
- mConnection.deleteTextBeforeCursor(deleteLength);
+ mConnection.deleteSurroundingText(deleteLength, 0);
if (!TextUtils.isEmpty(committedWord)) {
unlearnWord(committedWordString, inputTransaction.mSettingsValues,
Constants.EVENT_REVERT);
@@ -2158,10 +2136,9 @@ 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 = chosenWord;
- // b/21926256
- // SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
- // suggestedWords, locale);
+ final CharSequence chosenWordWithSuggestions =
+ 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/AdvancedSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
index a6fb7f1f1..f2e1aed4c 100644
--- a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
@@ -26,7 +26,6 @@ import android.preference.Preference;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SystemBroadcastReceiver;
import com.android.inputmethod.latin.define.ProductionFlags;
/**
@@ -107,8 +106,6 @@ public final class AdvancedSettingsFragment extends SubScreenFragment {
if (key.equals(Settings.PREF_POPUP_ON)) {
setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
Settings.readKeyPreviewPopupEnabled(prefs, res));
- } else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
- SystemBroadcastReceiver.toggleAppIcon(getActivity());
}
updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
refreshEnablingsOfKeypressSoundAndVibrationSettings();
diff --git a/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java
index d28e703fe..aa73a9a83 100644
--- a/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java
@@ -16,27 +16,20 @@
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
@@ -66,39 +59,5 @@ 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 940f1bdfc..694f43d3f 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -56,7 +56,6 @@ 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/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index c7622e7a1..2c690aea7 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -84,7 +84,8 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
if (TextUtils.isEmpty(splitText)) {
continue;
}
- if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString()) == null) {
+ if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString(), ngramContext)
+ == null) {
continue;
}
final int newLength = splitText.length();
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 9223923a7..1322ce240 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -71,26 +71,30 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
}
protected static final class SuggestionsCache {
+ private static final char CHAR_DELIMITER = '\uFFFC';
private static final int MAX_CACHE_SIZE = 50;
private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache =
new LruCache<>(MAX_CACHE_SIZE);
- private static String generateKey(final String query) {
- return query + "";
+ private static String generateKey(final String query, final NgramContext ngramContext) {
+ if (TextUtils.isEmpty(query) || !ngramContext.isValid()) {
+ return query;
+ }
+ return query + CHAR_DELIMITER + ngramContext;
}
- public SuggestionsParams getSuggestionsFromCache(final String query) {
- return mUnigramSuggestionsInfoCache.get(query);
+ public SuggestionsParams getSuggestionsFromCache(String query,
+ final NgramContext ngramContext) {
+ return mUnigramSuggestionsInfoCache.get(generateKey(query, ngramContext));
}
- public void putSuggestionsToCache(
- final String query, final String[] suggestions, final int flags) {
+ public void putSuggestionsToCache(final String query, final NgramContext ngramContext,
+ final String[] suggestions, final int flags) {
if (suggestions == null || TextUtils.isEmpty(query)) {
return;
}
mUnigramSuggestionsInfoCache.put(
- generateKey(query),
- new SuggestionsParams(suggestions, flags));
+ generateKey(query, ngramContext), new SuggestionsParams(suggestions, flags));
}
public void clearCache() {
@@ -228,7 +232,16 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
AndroidSpellCheckerService.SINGLE_QUOTE).
replaceAll("^" + quotesRegexp, "").
replaceAll(quotesRegexp + "$", "");
+ final SuggestionsParams cachedSuggestionsParams =
+ mSuggestionsCache.getSuggestionsFromCache(text, ngramContext);
+
+ if (cachedSuggestionsParams != null) {
+ Log.d(TAG, "onGetSuggestionsInternal() : Cache hit for [" + text + "]");
+ return new SuggestionsInfo(
+ cachedSuggestionsParams.mFlags, cachedSuggestionsParams.mSuggestions);
+ }
+ // If spell checking is impossible, return early.
if (!mService.hasMainDictionaryForLocale(mLocale)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
false /* reportAsTypo */);
@@ -316,7 +329,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
.getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS()
: 0);
final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions);
- mSuggestionsCache.putSuggestionsToCache(text, result.mSuggestions, flags);
+ mSuggestionsCache.putSuggestionsToCache(text, ngramContext, result.mSuggestions,
+ flags);
return retval;
} catch (RuntimeException e) {
// Don't kill the keyboard if there is a bug in the spell checker
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 11cccd5fa..cfa977a46 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -30,7 +30,6 @@ import com.android.inputmethod.latin.AssetFileAddress;
import com.android.inputmethod.latin.BinaryDictionaryGetter;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.common.FileUtils;
import com.android.inputmethod.latin.common.LocaleUtils;
import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
@@ -54,7 +53,7 @@ import javax.annotation.Nullable;
*/
public class DictionaryInfoUtils {
private static final String TAG = DictionaryInfoUtils.class.getSimpleName();
- public static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
+ private 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,13 +102,6 @@ public class DictionaryInfoUtils {
values.put(VERSION_COLUMN, mVersion);
return values;
}
-
- @Override
- public String toString() {
- return "DictionaryInfo : Id = '" + mId
- + "' : Locale=" + mLocale
- + " : Version=" + mVersion;
- }
}
private DictionaryInfoUtils() {
@@ -161,13 +153,6 @@ public class DictionaryInfoUtils {
}
/**
- * Helper method to get the top level cache directory.
- */
- public static String getWordListStagingDirectory(final Context context) {
- return context.getFilesDir() + File.separator + "staging";
- }
-
- /**
* Helper method to get the top level temp directory.
*/
public static String getWordListTempDirectory(final Context context) {
@@ -203,10 +188,6 @@ public class DictionaryInfoUtils {
return new File(DictionaryInfoUtils.getWordListCacheDirectory(context)).listFiles();
}
- public static File[] getStagingDirectoryList(final Context context) {
- return new File(DictionaryInfoUtils.getWordListStagingDirectory(context)).listFiles();
- }
-
@Nullable
public static File[] getUnusedDictionaryList(final Context context) {
return context.getFilesDir().listFiles(new FilenameFilter() {
@@ -240,7 +221,7 @@ public class DictionaryInfoUtils {
/**
* Find out the cache directory associated with a specific locale.
*/
- public static String getCacheDirectoryForLocale(final String locale, final Context context) {
+ private static String getCacheDirectoryForLocale(final String locale, final Context context) {
final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale);
final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator
+ relativeDirectoryName;
@@ -273,52 +254,6 @@ public class DictionaryInfoUtils {
return getCacheDirectoryForLocale(locale, context) + File.separator + fileName;
}
- public static String getStagingFileName(String id, String locale, Context context) {
- final String stagingDirectory = getWordListStagingDirectory(context);
- // create the directory if it does not exist.
- final File directory = new File(stagingDirectory);
- if (!directory.exists()) {
- if (!directory.mkdirs()) {
- Log.e(TAG, "Could not create the staging directory.");
- }
- }
- // e.g. id="main:en_in", locale ="en_IN"
- final String fileName = replaceFileNameDangerousCharacters(
- locale + TEMP_DICT_FILE_SUB + id);
- return stagingDirectory + File.separator + fileName;
- }
-
- public static void moveStagingFilesIfExists(Context context) {
- final File[] stagingFiles = DictionaryInfoUtils.getStagingDirectoryList(context);
- if (stagingFiles != null && stagingFiles.length > 0) {
- for (final File stagingFile : stagingFiles) {
- final String fileName = stagingFile.getName();
- final int index = fileName.indexOf(TEMP_DICT_FILE_SUB);
- if (index == -1) {
- // This should never happen.
- Log.e(TAG, "Staging file does not have ___ substring.");
- continue;
- }
- final String[] localeAndFileId = fileName.split(TEMP_DICT_FILE_SUB);
- if (localeAndFileId.length != 2) {
- Log.e(TAG, String.format("malformed staging file %s. Deleting.",
- stagingFile.getAbsoluteFile()));
- stagingFile.delete();
- continue;
- }
-
- final String locale = localeAndFileId[0];
- // already escaped while moving to staging.
- final String fileId = localeAndFileId[1];
- final String cacheDirectoryForLocale = getCacheDirectoryForLocale(locale, context);
- final String cacheFilename = cacheDirectoryForLocale + File.separator + fileId;
- final File cacheFile = new File(cacheFilename);
- // move the staging file to cache file.
- FileUtils.renameTo(stagingFile, cacheFile);
- }
- }
- }
-
public static boolean isMainWordListId(final String id) {
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
// An id is supposed to be in format category:locale, so splitting on the separator