aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java23
-rw-r--r--java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java2
-rw-r--r--java/src/com/android/inputmethod/compat/CharacterCompat.java47
-rw-r--r--java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java5
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ActionBatch.java19
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java6
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryService.java28
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java29
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java94
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java19
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataParser.java1
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java127
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java12
-rw-r--r--java/src/com/android/inputmethod/event/DeadKeyCombiner.java265
-rw-r--r--java/src/com/android/inputmethod/event/Event.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java85
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java17
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java35
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java21
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardTheme.java67
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java17
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java11
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java14
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java28
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java92
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java1632
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeysCache.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java48
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java6
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java89
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java11
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java12
-rw-r--r--java/src/com/android/inputmethod/latin/DicTraverseSession.java2
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java52
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java28
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitator.java513
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java156
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java6
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryStats.java43
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java93
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java11
-rw-r--r--java/src/com/android/inputmethod/latin/InputPointers.java6
-rw-r--r--java/src/com/android/inputmethod/latin/LastComposedWord.java6
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java184
-rw-r--r--java/src/com/android/inputmethod/latin/NgramContext.java (renamed from java/src/com/android/inputmethod/latin/PrevWordsInfo.java)125
-rw-r--r--java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java185
-rw-r--r--java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java12
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java38
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java13
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java125
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java50
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java34
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java15
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java15
-rw-r--r--java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java72
-rw-r--r--java/src/com/android/inputmethod/latin/accounts/AuthUtils.java67
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java185
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java1
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java99
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/NgramProperty.java26
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/WordProperty.java57
-rw-r--r--java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java99
-rw-r--r--java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java213
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java8
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java40
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java193
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java10
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java7
-rw-r--r--java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java18
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java5
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java5
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java29
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java15
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java11
-rw-r--r--java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java55
-rw-r--r--java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java30
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java133
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java14
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java27
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java5
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java10
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java10
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java24
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java20
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java7
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilter.java44
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java220
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java12
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FragmentUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java74
-rw-r--r--java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java (renamed from java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java)20
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java58
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java44
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SuggestionResults.java12
102 files changed, 4574 insertions, 2023 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 7a3510ee1..edcdd4c4c 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -37,6 +37,8 @@ final class KeyCodeDescriptionMapper {
private static final String SPOKEN_LETTER_RESOURCE_NAME_FORMAT = "spoken_accented_letter_%04X";
private static final String SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT = "spoken_symbol_%04X";
private static final String SPOKEN_EMOJI_RESOURCE_NAME_FORMAT = "spoken_emoji_%04X";
+ private static final String SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX = "spoken_emoticon";
+ private static final String SPOKEN_EMOTICON_CODE_POINT_FORMAT = "_%02X";
// The resource ID of the string spoken for obscured keys
private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot;
@@ -109,7 +111,9 @@ final class KeyCodeDescriptionMapper {
}
if (code == Constants.CODE_OUTPUT_TEXT) {
- return key.getOutputText();
+ final String outputText = key.getOutputText();
+ final String description = getSpokenEmoticonDescription(context, outputText);
+ return TextUtils.isEmpty(description) ? outputText : description;
}
// Just attempt to speak the description.
@@ -340,4 +344,21 @@ final class KeyCodeDescriptionMapper {
}
return resId;
}
+
+ // TODO: Remove this method once TTS supports emoticon verbalization.
+ private String getSpokenEmoticonDescription(final Context context, final String outputText) {
+ final StringBuilder sb = new StringBuilder(SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX);
+ final int textLength = outputText.length();
+ for (int index = 0; index < textLength; index = outputText.offsetByCodePoints(index, 1)) {
+ final int codePoint = outputText.codePointAt(index);
+ sb.append(String.format(Locale.ROOT, SPOKEN_EMOTICON_CODE_POINT_FORMAT, codePoint));
+ }
+ final String resourceName = sb.toString();
+ final Resources resources = context.getResources();
+ // Note that the resource package name may differ from the context package name.
+ final String resourcePackageName = resources.getResourcePackageName(
+ R.string.spoken_description_unknown);
+ final int resId = resources.getIdentifier(resourceName, "string", resourcePackageName);
+ return (resId == 0) ? null : resources.getString(resId);
+ }
}
diff --git a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
index b84d402fb..94a1ee6eb 100644
--- a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
+++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
@@ -121,7 +121,7 @@ public final class MainKeyboardAccessibilityDelegate
*/
private void announceKeyboardLanguage(final Keyboard keyboard) {
final String languageText = SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(
- keyboard.mId.mSubtype);
+ keyboard.mId.mSubtype.getRawSubtype());
sendWindowStateChanged(languageText);
}
diff --git a/java/src/com/android/inputmethod/compat/CharacterCompat.java b/java/src/com/android/inputmethod/compat/CharacterCompat.java
new file mode 100644
index 000000000..609fe1638
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/CharacterCompat.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import java.lang.reflect.Method;
+
+public final class CharacterCompat {
+ // Note that Character.isAlphabetic(int), has been introduced in API level 19
+ // (Build.VERSION_CODE.KITKAT).
+ private static final Method METHOD_isAlphabetic = CompatUtils.getMethod(
+ Character.class, "isAlphabetic", int.class);
+
+ private CharacterCompat() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static boolean isAlphabetic(final int code) {
+ if (METHOD_isAlphabetic != null) {
+ return (Boolean)CompatUtils.invoke(null, false, METHOD_isAlphabetic, code);
+ }
+ switch (Character.getType(code)) {
+ case Character.UPPERCASE_LETTER:
+ case Character.LOWERCASE_LETTER:
+ case Character.TITLECASE_LETTER:
+ case Character.MODIFIER_LETTER:
+ case Character.OTHER_LETTER:
+ case Character.LETTER_NUMBER:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
index 365867257..b9a536721 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
@@ -21,6 +21,7 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
@@ -64,6 +65,10 @@ public final class InputMethodSubtypeCompatUtils {
overridesImplicitlyEnabledSubtype, id);
}
+ public static boolean isAsciiCapable(final RichInputMethodSubtype subtype) {
+ return isAsciiCapable(subtype.getRawSubtype());
+ }
+
public static boolean isAsciiCapable(final InputMethodSubtype subtype) {
return isAsciiCapableWithAPI(subtype)
|| subtype.containsExtraValueKey(Constants.Subtype.ExtraValue.ASCII_CAPABLE);
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index 3d294acd7..3aa026e77 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -120,9 +120,10 @@ public final class ActionBatch {
if (MetadataDbHelper.STATUS_DOWNLOADING == status) {
// The word list is still downloading. Cancel the download and revert the
// word list status to "available".
- manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
+ manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
- } else if (MetadataDbHelper.STATUS_AVAILABLE != status) {
+ } else if (MetadataDbHelper.STATUS_AVAILABLE != status
+ && MetadataDbHelper.STATUS_RETRYING != status) {
// Should never happen
Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' : " + status
+ " for an upgrade action. Fall back to download.");
@@ -325,8 +326,8 @@ public final class ActionBatch {
mWordList.mId, mWordList.mLocale, mWordList.mDescription,
null == mWordList.mLocalFilename ? "" : mWordList.mLocalFilename,
mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
- mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
- mWordList.mFormatVersion);
+ mWordList.mChecksum, mWordList.mRetryCount, mWordList.mFileSize,
+ mWordList.mVersion, mWordList.mFormatVersion);
PrivateLog.log("Insert 'available' record for " + mWordList.mDescription
+ " and locale " + mWordList.mLocale);
db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values);
@@ -374,9 +375,9 @@ public final class ActionBatch {
final ContentValues values = MetadataDbHelper.makeContentValues(0,
MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_INSTALLED,
mWordList.mId, mWordList.mLocale, mWordList.mDescription,
- "", mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
- mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
- mWordList.mFormatVersion);
+ "", mWordList.mRemoteFilename, mWordList.mLastUpdate,
+ mWordList.mRawChecksum, mWordList.mChecksum, mWordList.mRetryCount,
+ mWordList.mFileSize, mWordList.mVersion, mWordList.mFormatVersion);
PrivateLog.log("Insert 'preinstalled' record for " + mWordList.mDescription
+ " and locale " + mWordList.mLocale);
db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values);
@@ -417,8 +418,8 @@ public final class ActionBatch {
mWordList.mId, mWordList.mLocale, mWordList.mDescription,
oldValues.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN),
mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
- mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
- mWordList.mFormatVersion);
+ mWordList.mChecksum, mWordList.mRetryCount, mWordList.mFileSize,
+ mWordList.mVersion, mWordList.mFormatVersion);
PrivateLog.log("Updating record for " + mWordList.mDescription
+ " and locale " + mWordList.mLocale);
db.update(MetadataDbHelper.METADATA_TABLE_NAME, values,
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
index f5bd84c8c..e748321e2 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
@@ -470,7 +470,11 @@ public final class DictionaryProvider extends ContentProvider {
} else if (MetadataDbHelper.STATUS_INSTALLED == status) {
final String result = uri.getQueryParameter(QUERY_PARAMETER_DELETE_RESULT);
if (QUERY_PARAMETER_FAILURE.equals(result)) {
- UpdateHandler.markAsBroken(getContext(), clientId, wordlistId, version);
+ if (DEBUG) {
+ Log.d(TAG,
+ "Dictionary is broken, attempting to retry download & installation.");
+ }
+ UpdateHandler.markAsBrokenOrRetrying(getContext(), clientId, wordlistId, version);
}
final String localFilename =
wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index 41916b614..568c80abd 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -76,19 +76,29 @@ public final class DictionaryService extends Service {
* How often, in milliseconds, we want to update the metadata. This is a
* floor value; actually, it may happen several hours later, or even more.
*/
- private static final long UPDATE_FREQUENCY = TimeUnit.DAYS.toMillis(4);
+ private static final long UPDATE_FREQUENCY_MILLIS = TimeUnit.DAYS.toMillis(4);
/**
* We are waked around midnight, local time. We want to wake between midnight and 6 am,
* roughly. So use a random time between 0 and this delay.
*/
- private static final int MAX_ALARM_DELAY = (int)TimeUnit.HOURS.toMillis(6);
+ private static final int MAX_ALARM_DELAY_MILLIS = (int)TimeUnit.HOURS.toMillis(6);
/**
* How long we consider a "very long time". If no update took place in this time,
* the content provider will trigger an update in the background.
*/
- private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14);
+ private static final long VERY_LONG_TIME_MILLIS = TimeUnit.DAYS.toMillis(14);
+
+ /**
+ * After starting a download, how long we wait before considering it may be stuck. After this
+ * period is elapsed, if the keyboard tries to download again, then we cancel and re-register
+ * the request; if it's within this time, we just leave it be.
+ * It's important to note that we do not re-submit the request merely because the time is up.
+ * This is only to decide whether to cancel the old one and re-requesting when the keyboard
+ * fires a new request for the same data.
+ */
+ public static final long NO_CANCEL_DOWNLOAD_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(30);
/**
* An executor that serializes tasks given to it.
@@ -188,16 +198,16 @@ public final class DictionaryService extends Service {
*/
private static void checkTimeAndMaybeSetupUpdateAlarm(final Context context) {
// Of all clients, if the one that hasn't been updated for the longest
- // is still more recent than UPDATE_FREQUENCY, do nothing.
- if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY)) return;
+ // is still more recent than UPDATE_FREQUENCY_MILLIS, do nothing.
+ if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY_MILLIS)) return;
PrivateLog.log("Date changed - registering alarm");
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
- // Best effort to wake between midnight and MAX_ALARM_DELAY in the morning.
+ // Best effort to wake between midnight and MAX_ALARM_DELAY_MILLIS in the morning.
// It doesn't matter too much if this is very inexact.
final long now = System.currentTimeMillis();
- final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY);
+ final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY_MILLIS);
final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
updateIntent, PendingIntent.FLAG_CANCEL_CURRENT);
@@ -223,11 +233,11 @@ public final class DictionaryService extends Service {
/**
* Refreshes data if it hasn't been refreshed in a very long time.
*
- * This will check the last update time, and if it's been more than VERY_LONG_TIME,
+ * This will check the last update time, and if it's been more than VERY_LONG_TIME_MILLIS,
* update metadata now - and possibly take subsequent update actions.
*/
public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) {
- if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return;
+ if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME_MILLIS)) return;
UpdateHandler.tryUpdate(context, false);
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java b/java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java
new file mode 100644
index 000000000..6247a15e2
--- /dev/null
+++ b/java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.dictionarypack;
+
+/**
+ * A simple container of download ID and download start date.
+ */
+public class DownloadIdAndStartDate {
+ public final long mId;
+ public final long mStartDate;
+ public DownloadIdAndStartDate(final long id, final long startDate) {
+ mId = id;
+ mStartDate = startDate;
+ }
+}
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
index 17dd781d5..c9e8f9118 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -47,10 +47,13 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// used to identify the versions for upgrades. This should never change going forward.
private static final int METADATA_DATABASE_VERSION_WITH_CLIENTID = 6;
// The current database version.
- private static final int CURRENT_METADATA_DATABASE_VERSION = 9;
+ private static final int CURRENT_METADATA_DATABASE_VERSION = 10;
private final static long NOT_A_DOWNLOAD_ID = -1;
+ // The number of retries allowed when attempting to download a broken dictionary.
+ public static final int DICTIONARY_RETRY_THRESHOLD = 2;
+
public static final String METADATA_TABLE_NAME = "pendingUpdates";
static final String CLIENT_TABLE_NAME = "clients";
public static final String PENDINGID_COLUMN = "pendingid"; // Download Manager ID
@@ -68,7 +71,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
public static final String FORMATVERSION_COLUMN = "formatversion";
public static final String FLAGS_COLUMN = "flags";
public static final String RAW_CHECKSUM_COLUMN = "rawChecksum";
- public static final int COLUMN_COUNT = 14;
+ public static final String RETRY_COUNT_COLUMN = "remainingRetries";
+ public static final int COLUMN_COUNT = 15;
private static final String CLIENT_CLIENT_ID_COLUMN = "clientid";
private static final String CLIENT_METADATA_URI_COLUMN = "uri";
@@ -98,6 +102,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// Deleting: the user marked this word list to be deleted, but it has not been yet because
// Latin IME is not up yet.
public static final int STATUS_DELETING = 5;
+ // Retry: dictionary got corrupted, so an attempt must be done to download & install it again.
+ public static final int STATUS_RETRYING = 6;
// Types, for storing in the TYPE_COLUMN
// This is metadata about what is available.
@@ -124,6 +130,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
+ FORMATVERSION_COLUMN + " INTEGER, "
+ FLAGS_COLUMN + " INTEGER, "
+ RAW_CHECKSUM_COLUMN + " TEXT,"
+ + RETRY_COUNT_COLUMN + " INTEGER, "
+ "PRIMARY KEY (" + WORDLISTID_COLUMN + "," + VERSION_COLUMN + "));";
private static final String METADATA_CREATE_CLIENT_TABLE =
"CREATE TABLE IF NOT EXISTS " + CLIENT_TABLE_NAME + " ("
@@ -140,7 +147,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
STATUS_COLUMN, WORDLISTID_COLUMN, LOCALE_COLUMN, DESCRIPTION_COLUMN,
LOCAL_FILENAME_COLUMN, REMOTE_FILENAME_COLUMN, DATE_COLUMN, CHECKSUM_COLUMN,
FILESIZE_COLUMN, VERSION_COLUMN, FORMATVERSION_COLUMN, FLAGS_COLUMN,
- RAW_CHECKSUM_COLUMN };
+ RAW_CHECKSUM_COLUMN, RETRY_COUNT_COLUMN };
// List of all client table columns.
static final String[] CLIENT_TABLE_COLUMNS = { CLIENT_CLIENT_ID_COLUMN,
CLIENT_METADATA_URI_COLUMN, CLIENT_PENDINGID_COLUMN, FLAGS_COLUMN };
@@ -219,7 +226,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
createClientTable(db);
}
- private void addRawChecksumColumnUnlessPresent(final SQLiteDatabase db, final String clientId) {
+ private void addRawChecksumColumnUnlessPresent(final SQLiteDatabase db) {
try {
db.execSQL("SELECT " + RAW_CHECKSUM_COLUMN + " FROM "
+ METADATA_TABLE_NAME + " LIMIT 0;");
@@ -230,6 +237,17 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
}
}
+ private void addRetryCountColumnUnlessPresent(final SQLiteDatabase db) {
+ try {
+ db.execSQL("SELECT " + RETRY_COUNT_COLUMN + " FROM "
+ + METADATA_TABLE_NAME + " LIMIT 0;");
+ } catch (SQLiteException e) {
+ Log.i(TAG, "No " + RETRY_COUNT_COLUMN + " column : creating it");
+ db.execSQL("ALTER TABLE " + METADATA_TABLE_NAME + " ADD COLUMN "
+ + RETRY_COUNT_COLUMN + " INTEGER DEFAULT " + DICTIONARY_RETRY_THRESHOLD + ";");
+ }
+ }
+
/**
* Upgrade the database. Upgrade from version 3 is supported.
* Version 3 has a DB named METADATA_DATABASE_NAME_STEM containing a table METADATA_TABLE_NAME.
@@ -280,7 +298,14 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// strengthen the system against corrupted dictionary files.
// The most secure way to upgrade a database is to just test for the column presence, and
// add it if it's not there.
- addRawChecksumColumnUnlessPresent(db, mClientId);
+ addRawChecksumColumnUnlessPresent(db);
+
+ // A retry count column that did not exist in the previous versions was added that
+ // corresponds to the number of download & installation attempts that have been made
+ // in order to strengthen the system recovery from corrupted dictionary files.
+ // The most secure way to upgrade a database is to just test for the column presence, and
+ // add it if it's not there.
+ addRetryCountColumnUnlessPresent(db);
}
/**
@@ -408,18 +433,18 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
*
* @param context a context instance to open the database on
* @param uri the URI to retrieve the metadata download ID of
- * @return the metadata download ID, or NOT_AN_ID if no download is in progress
+ * @return the download id and start date, or null if the URL is not known
*/
- public static long getMetadataDownloadIdForURI(final Context context,
- final String uri) {
+ public static DownloadIdAndStartDate getMetadataDownloadIdAndStartDateForURI(
+ final Context context, final String uri) {
SQLiteDatabase defaultDb = getDb(context, null);
final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME,
- new String[] { CLIENT_PENDINGID_COLUMN },
+ new String[] { CLIENT_PENDINGID_COLUMN, CLIENT_LAST_UPDATE_DATE_COLUMN },
CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri },
null, null, null, null);
try {
- if (!cursor.moveToFirst()) return UpdateHandler.NOT_AN_ID;
- return cursor.getInt(0); // Only one column, return it
+ if (!cursor.moveToFirst()) return null;
+ return new DownloadIdAndStartDate(cursor.getInt(0), cursor.getLong(1));
} finally {
cursor.close();
}
@@ -452,8 +477,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
public static ContentValues makeContentValues(final int pendingId, final int type,
final int status, final String wordlistId, final String locale,
final String description, final String filename, final String url, final long date,
- final String rawChecksum, final String checksum, final long filesize, final int version,
- final int formatVersion) {
+ final String rawChecksum, final String checksum, final int retryCount,
+ final long filesize, final int version, final int formatVersion) {
final ContentValues result = new ContentValues(COLUMN_COUNT);
result.put(PENDINGID_COLUMN, pendingId);
result.put(TYPE_COLUMN, type);
@@ -465,6 +490,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
result.put(REMOTE_FILENAME_COLUMN, url);
result.put(DATE_COLUMN, date);
result.put(RAW_CHECKSUM_COLUMN, rawChecksum);
+ result.put(RETRY_COUNT_COLUMN, retryCount);
result.put(CHECKSUM_COLUMN, checksum);
result.put(FILESIZE_COLUMN, filesize);
result.put(VERSION_COLUMN, version);
@@ -502,6 +528,9 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
if (null == result.get(DATE_COLUMN)) result.put(DATE_COLUMN, 0);
// Raw checksum unknown unless specified
if (null == result.get(RAW_CHECKSUM_COLUMN)) result.put(RAW_CHECKSUM_COLUMN, "");
+ // Retry column 0 unless specified
+ if (null == result.get(RETRY_COUNT_COLUMN)) result.put(RETRY_COUNT_COLUMN,
+ DICTIONARY_RETRY_THRESHOLD);
// Checksum unknown unless specified
if (null == result.get(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, "");
// No filesize unless specified
@@ -551,6 +580,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
putIntResult(result, cursor, DATE_COLUMN);
putStringResult(result, cursor, RAW_CHECKSUM_COLUMN);
putStringResult(result, cursor, CHECKSUM_COLUMN);
+ putIntResult(result, cursor, RETRY_COUNT_COLUMN);
putIntResult(result, cursor, FILESIZE_COLUMN);
putIntResult(result, cursor, VERSION_COLUMN);
putIntResult(result, cursor, FORMATVERSION_COLUMN);
@@ -676,8 +706,16 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final String id, final int version) {
final Cursor cursor = db.query(METADATA_TABLE_NAME,
METADATA_TABLE_COLUMNS,
- WORDLISTID_COLUMN + "= ? AND " + VERSION_COLUMN + "= ?",
- new String[] { id, Integer.toString(version) }, null, null, null);
+ WORDLISTID_COLUMN + "= ? AND " + VERSION_COLUMN + "= ? AND "
+ + FORMATVERSION_COLUMN + "<= ?",
+ new String[]
+ { id,
+ Integer.toString(version),
+ Integer.toString(UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION)
+ },
+ null /* groupBy */,
+ null /* having */,
+ FORMATVERSION_COLUMN + " DESC"/* orderBy */);
if (null == cursor) {
return null;
}
@@ -706,7 +744,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
return null;
}
try {
- // This is a lookup by primary key, so there can't be more than one result.
+ // Return the first result from the list of results.
return getFirstLineAsContentValues(cursor);
} finally {
cursor.close();
@@ -884,6 +922,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final long downloadId) {
final ContentValues values = new ContentValues();
values.put(CLIENT_PENDINGID_COLUMN, downloadId);
+ values.put(CLIENT_LAST_UPDATE_DATE_COLUMN, System.currentTimeMillis());
final SQLiteDatabase defaultDb = getDb(context, "");
final Cursor cursor = MetadataDbHelper.queryClientIds(context);
if (null == cursor) return;
@@ -1085,4 +1124,27 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final int version) {
markEntryAs(db, id, version, STATUS_DELETING, NOT_A_DOWNLOAD_ID);
}
+
+ /**
+ * Checks retry counts and marks the word list as retrying if retry is possible.
+ *
+ * @param db the metadata database.
+ * @param id the id of the word list.
+ * @param version the version of the word list.
+ * @return {@code true} if the retry is possible.
+ */
+ public static boolean maybeMarkEntryAsRetrying(final SQLiteDatabase db, final String id,
+ final int version) {
+ final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, id, version);
+ int retryCount = values.getAsInteger(MetadataDbHelper.RETRY_COUNT_COLUMN);
+ if (retryCount > 1) {
+ values.put(STATUS_COLUMN, STATUS_RETRYING);
+ values.put(RETRY_COUNT_COLUMN, retryCount - 1);
+ db.update(METADATA_TABLE_NAME, values,
+ WORDLISTID_COLUMN + " = ? AND " + VERSION_COLUMN + " = ?",
+ new String[] { id, Integer.toString(version) });
+ return true;
+ }
+ return false;
+ }
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
index d66b69050..639d904a0 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.dictionarypack;
+import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -55,6 +56,7 @@ public class MetadataHandler {
final int rawChecksumIndex =
results.getColumnIndex(MetadataDbHelper.RAW_CHECKSUM_COLUMN);
final int checksumIndex = results.getColumnIndex(MetadataDbHelper.CHECKSUM_COLUMN);
+ final int retryCountIndex = results.getColumnIndex(MetadataDbHelper.RETRY_COUNT_COLUMN);
final int localFilenameIndex =
results.getColumnIndex(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
final int remoteFilenameIndex =
@@ -70,6 +72,7 @@ public class MetadataHandler {
results.getLong(fileSizeIndex),
results.getString(rawChecksumIndex),
results.getString(checksumIndex),
+ results.getInt(retryCountIndex),
results.getString(localFilenameIndex),
results.getString(remoteFilenameIndex),
results.getInt(versionIndex),
@@ -102,6 +105,22 @@ public class MetadataHandler {
}
/**
+ * Gets the metadata, for a specific dictionary.
+ *
+ * @param context The context to open files over.
+ * @param clientId the client id for retrieving the database. null for default (deprecated).
+ * @param wordListId the word list ID.
+ * @param version the word list version.
+ * @return the current metaData
+ */
+ public static WordListMetadata getCurrentMetadataForWordList(final Context context,
+ final String clientId, final String wordListId, final int version) {
+ final ContentValues contentValues = MetadataDbHelper.getContentValuesByWordListId(
+ MetadataDbHelper.getDb(context, clientId), wordListId, version);
+ return WordListMetadata.createFromContentValues(contentValues);
+ }
+
+ /**
* Read metadata from a stream.
* @param input The stream to read from.
* @return The read metadata.
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java b/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
index 52290cadc..2b67ae9ff 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
@@ -83,6 +83,7 @@ public class MetadataParser {
Long.parseLong(arguments.get(FILESIZE_FIELD_NAME)),
arguments.get(RAW_CHECKSUM_FIELD_NAME),
arguments.get(CHECKSUM_FIELD_NAME),
+ MetadataDbHelper.DICTIONARY_RETRY_THRESHOLD /* retryCount */,
null,
arguments.get(REMOTE_FILENAME_FIELD_NAME),
Integer.parseInt(arguments.get(VERSION_FIELD_NAME)),
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index 6fbca44c5..90a750493 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -31,7 +31,6 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.ConnectivityManager;
import android.net.Uri;
-import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;
@@ -252,12 +251,16 @@ public final class UpdateHandler {
res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI));
final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
- cancelUpdateWithDownloadManager(context, metadataUri, manager);
+ if (maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager,
+ DictionaryService.NO_CANCEL_DOWNLOAD_PERIOD_MILLIS)) {
+ // We already have a recent download in progress. Don't register a new download.
+ return;
+ }
final long downloadId;
synchronized (sSharedIdProtector) {
downloadId = manager.enqueue(metadataRequest);
DebugLogUtils.l("Metadata download requested with id", downloadId);
- // If there is already a download in progress, it's been there for a while and
+ // If there is still a download in progress, it's been there for a while and
// there is probably something wrong with download manager. It's best to just
// overwrite the id and request it again. If the old one happens to finish
// anyway, we don't know about its ID any more, so the downloadFinished
@@ -268,21 +271,29 @@ public final class UpdateHandler {
}
/**
- * Cancels downloading a file, if there is one for this URI.
+ * Cancels downloading a file if there is one for this URI and it's too long.
*
* If we are not currently downloading the file at this URI, this is a no-op.
*
* @param context the context to open the database on
* @param metadataUri the URI to cancel
* @param manager an wrapped instance of DownloadManager
+ * @param graceTime if there was a download started less than this many milliseconds, don't
+ * cancel and return true
+ * @return whether the download is still active
*/
- private static void cancelUpdateWithDownloadManager(final Context context,
- final String metadataUri, final DownloadManagerWrapper manager) {
+ private static boolean maybeCancelUpdateAndReturnIfStillRunning(final Context context,
+ final String metadataUri, final DownloadManagerWrapper manager, final long graceTime) {
synchronized (sSharedIdProtector) {
- final long metadataDownloadId =
- MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri);
- if (NOT_AN_ID == metadataDownloadId) return;
- manager.remove(metadataDownloadId);
+ final DownloadIdAndStartDate metadataDownloadIdAndStartDate =
+ MetadataDbHelper.getMetadataDownloadIdAndStartDateForURI(context, metadataUri);
+ if (null == metadataDownloadIdAndStartDate) return false;
+ if (NOT_AN_ID == metadataDownloadIdAndStartDate.mId) return false;
+ if (metadataDownloadIdAndStartDate.mStartDate + graceTime
+ > System.currentTimeMillis()) {
+ return true;
+ }
+ manager.remove(metadataDownloadIdAndStartDate.mId);
writeMetadataDownloadId(context, metadataUri, NOT_AN_ID);
}
// Consider a cancellation as a failure. As such, inform listeners that the download
@@ -290,6 +301,7 @@ public final class UpdateHandler {
for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) {
listener.downloadedMetadata(false);
}
+ return false;
}
/**
@@ -304,7 +316,7 @@ public final class UpdateHandler {
public static void cancelUpdate(final Context context, final String clientId) {
final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId);
- cancelUpdateWithDownloadManager(context, metadataUri, manager);
+ maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager, 0 /* graceTime */);
}
/**
@@ -388,7 +400,7 @@ public final class UpdateHandler {
// If any of these is metadata, we should update the DB
boolean hasMetadata = false;
for (DownloadRecord record : downloadRecords) {
- if (null == record.mAttributes) {
+ if (record.isMetadata()) {
hasMetadata = true;
break;
}
@@ -785,6 +797,10 @@ public final class UpdateHandler {
} else {
final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId);
if (newInfo.mVersion == currentInfo.mVersion) {
+ if (newInfo.mRemoteFilename == currentInfo.mRemoteFilename) {
+ // If the dictionary url hasn't changed, we should preserve the retryCount.
+ newInfo.mRetryCount = currentInfo.mRetryCount;
+ }
// If it's the same id/version, we update the DB with the new values.
// It doesn't matter too much if they didn't change.
actions.add(new ActionBatch.UpdateDataAction(clientId, newInfo));
@@ -987,16 +1003,17 @@ public final class UpdateHandler {
public static void markAsUsed(final Context context, final String clientId,
final String wordlistId, final int version,
final int status, final boolean allowDownloadOnMeteredData) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- WordListMetadata wordList = MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
+
final ActionBatch actions = new ActionBatch();
if (MetadataDbHelper.STATUS_DISABLED == status
|| MetadataDbHelper.STATUS_DELETING == status) {
- actions.add(new ActionBatch.EnableAction(clientId, wordList));
+ actions.add(new ActionBatch.EnableAction(clientId, wordListMetaData));
} else if (MetadataDbHelper.STATUS_AVAILABLE == status) {
- actions.add(new ActionBatch.StartDownloadAction(clientId, wordList,
+ actions.add(new ActionBatch.StartDownloadAction(clientId, wordListMetaData,
allowDownloadOnMeteredData));
} else {
Log.e(TAG, "Unexpected state of the word list for markAsUsed : " + status);
@@ -1022,13 +1039,13 @@ public final class UpdateHandler {
// markAsUsed for consistency.
public static void markAsUnused(final Context context, final String clientId,
final String wordlistId, final int version, final int status) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- final WordListMetadata wordList =
- MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.DisableAction(clientId, wordList));
+ actions.add(new ActionBatch.DisableAction(clientId, wordListMetaData));
actions.execute(context, new LogProblemReporter(TAG));
signalNewDictionaryState(context);
}
@@ -1051,14 +1068,14 @@ public final class UpdateHandler {
*/
public static void markAsDeleting(final Context context, final String clientId,
final String wordlistId, final int version, final int status) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- final WordListMetadata wordList =
- MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.DisableAction(clientId, wordList));
- actions.add(new ActionBatch.StartDeleteAction(clientId, wordList));
+ actions.add(new ActionBatch.DisableAction(clientId, wordListMetaData));
+ actions.add(new ActionBatch.StartDeleteAction(clientId, wordListMetaData));
actions.execute(context, new LogProblemReporter(TAG));
signalNewDictionaryState(context);
}
@@ -1076,33 +1093,47 @@ public final class UpdateHandler {
*/
public static void markAsDeleted(final Context context, final String clientId,
final String wordlistId, final int version, final int status) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- final WordListMetadata wordList =
- MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
+
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.FinishDeleteAction(clientId, wordList));
+ actions.add(new ActionBatch.FinishDeleteAction(clientId, wordListMetaData));
actions.execute(context, new LogProblemReporter(TAG));
signalNewDictionaryState(context);
}
/**
- * Marks the word list with the passed id as broken.
- *
- * This effectively deletes the entry from the metadata. It doesn't prevent the same
- * word list to be downloaded again at a later time if the same or a new version is
- * available the next time we download the metadata.
+ * Checks whether the word list should be downloaded again; in which case an download &
+ * installation attempt is made. Otherwise the word list is marked broken.
*
* @param context the context to open the database on.
* @param clientId the id of the client.
- * @param wordlistId the id of the word list to mark as broken.
- * @param version the version of the word list to mark as deleted.
+ * @param wordlistId the id of the word list which is broken.
+ * @param version the version of the broken word list.
*/
- public static void markAsBroken(final Context context, final String clientId,
+ public static void markAsBrokenOrRetrying(final Context context, final String clientId,
final String wordlistId, final int version) {
- // TODO: do this on another thread to avoid blocking the UI.
- MetadataDbHelper.deleteEntry(MetadataDbHelper.getDb(context, clientId),
- wordlistId, version);
+ boolean isRetryPossible = MetadataDbHelper.maybeMarkEntryAsRetrying(
+ MetadataDbHelper.getDb(context, clientId), wordlistId, version);
+
+ if (isRetryPossible) {
+ if (DEBUG) {
+ Log.d(TAG, "Attempting to download & install the wordlist again.");
+ }
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ final ActionBatch actions = new ActionBatch();
+ actions.add(new ActionBatch.StartDownloadAction(clientId, wordListMetaData, false));
+ actions.execute(context, new LogProblemReporter(TAG));
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Retries for wordlist exhausted, deleting the wordlist from table.");
+ }
+ MetadataDbHelper.deleteEntry(MetadataDbHelper.getDb(context, clientId),
+ wordlistId, version);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java b/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
index 9e510a68b..59f75e4ed 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
@@ -36,6 +36,7 @@ public class WordListMetadata {
public final String mRemoteFilename;
public final int mVersion; // version of this word list
public final int mFlags; // Always 0 in this version, reserved for future use
+ public int mRetryCount;
// The locale is matched against the locale requested by the client. The matching algorithm
// is a standard locale matching with fallback; it is implemented in
@@ -51,8 +52,9 @@ public class WordListMetadata {
public WordListMetadata(final String id, final int type,
final String description, final long lastUpdate, final long fileSize,
- final String rawChecksum, final String checksum, final String localFilename,
- final String remoteFilename, final int version, final int formatVersion,
+ final String rawChecksum, final String checksum, final int retryCount,
+ final String localFilename, final String remoteFilename,
+ final int version, final int formatVersion,
final int flags, final String locale) {
mId = id;
mType = type;
@@ -61,6 +63,7 @@ public class WordListMetadata {
mFileSize = fileSize;
mRawChecksum = rawChecksum;
mChecksum = checksum;
+ mRetryCount = retryCount;
mLocalFilename = localFilename;
mRemoteFilename = remoteFilename;
mVersion = version;
@@ -82,6 +85,7 @@ public class WordListMetadata {
final Long fileSize = values.getAsLong(MetadataDbHelper.FILESIZE_COLUMN);
final String rawChecksum = values.getAsString(MetadataDbHelper.RAW_CHECKSUM_COLUMN);
final String checksum = values.getAsString(MetadataDbHelper.CHECKSUM_COLUMN);
+ final int retryCount = values.getAsInteger(MetadataDbHelper.RETRY_COUNT_COLUMN);
final String localFilename = values.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
final String remoteFilename = values.getAsString(MetadataDbHelper.REMOTE_FILENAME_COLUMN);
final Integer version = values.getAsInteger(MetadataDbHelper.VERSION_COLUMN);
@@ -103,7 +107,8 @@ public class WordListMetadata {
throw new IllegalArgumentException();
}
return new WordListMetadata(id, type, description, lastUpdate, fileSize, rawChecksum,
- checksum, localFilename, remoteFilename, version, formatVersion, flags, locale);
+ checksum, retryCount, localFilename, remoteFilename, version, formatVersion,
+ flags, locale);
}
@Override
@@ -116,6 +121,7 @@ public class WordListMetadata {
sb.append("\nFileSize : ").append(mFileSize);
sb.append("\nRawChecksum : ").append(mRawChecksum);
sb.append("\nChecksum : ").append(mChecksum);
+ sb.append("\nRetryCount: ").append(mRetryCount);
sb.append("\nLocalFilename : ").append(mLocalFilename);
sb.append("\nRemoteFilename : ").append(mRemoteFilename);
sb.append("\nVersion : ").append(mVersion);
diff --git a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
index 4f3f4d25f..88c70630d 100644
--- a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
+++ b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
@@ -17,10 +17,12 @@
package com.android.inputmethod.event;
import android.text.TextUtils;
+import android.util.SparseIntArray;
import android.view.KeyCharacterMap;
import com.android.inputmethod.latin.Constants;
+import java.text.Normalizer;
import java.util.ArrayList;
import javax.annotation.Nonnull;
@@ -29,9 +31,209 @@ import javax.annotation.Nonnull;
* A combiner that handles dead keys.
*/
public class DeadKeyCombiner implements Combiner {
+
+ private static class Data {
+ // This class data taken from KeyCharacterMap.java.
+
+ /* Characters used to display placeholders for dead keys. */
+ private static final int ACCENT_ACUTE = '\u00B4';
+ private static final int ACCENT_BREVE = '\u02D8';
+ private static final int ACCENT_CARON = '\u02C7';
+ private static final int ACCENT_CEDILLA = '\u00B8';
+ private static final int ACCENT_CIRCUMFLEX = '\u02C6';
+ private static final int ACCENT_COMMA_ABOVE = '\u1FBD';
+ private static final int ACCENT_COMMA_ABOVE_RIGHT = '\u02BC';
+ private static final int ACCENT_DOT_ABOVE = '\u02D9';
+ private static final int ACCENT_DOT_BELOW = Constants.CODE_PERIOD; // approximate
+ private static final int ACCENT_DOUBLE_ACUTE = '\u02DD';
+ private static final int ACCENT_GRAVE = '\u02CB';
+ private static final int ACCENT_HOOK_ABOVE = '\u02C0';
+ private static final int ACCENT_HORN = Constants.CODE_SINGLE_QUOTE; // approximate
+ private static final int ACCENT_MACRON = '\u00AF';
+ private static final int ACCENT_MACRON_BELOW = '\u02CD';
+ private static final int ACCENT_OGONEK = '\u02DB';
+ private static final int ACCENT_REVERSED_COMMA_ABOVE = '\u02BD';
+ private static final int ACCENT_RING_ABOVE = '\u02DA';
+ private static final int ACCENT_STROKE = Constants.CODE_DASH; // approximate
+ private static final int ACCENT_TILDE = '\u02DC';
+ private static final int ACCENT_TURNED_COMMA_ABOVE = '\u02BB';
+ private static final int ACCENT_UMLAUT = '\u00A8';
+ private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8';
+ private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC';
+
+ /* Legacy dead key display characters used in previous versions of the API (before L)
+ * We still support these characters by mapping them to their non-legacy version. */
+ private static final int ACCENT_GRAVE_LEGACY = Constants.CODE_GRAVE_ACCENT;
+ private static final int ACCENT_CIRCUMFLEX_LEGACY = Constants.CODE_CIRCUMFLEX_ACCENT;
+ private static final int ACCENT_TILDE_LEGACY = Constants.CODE_TILDE;
+
+ /**
+ * Maps Unicode combining diacritical to display-form dead key.
+ */
+ private static final SparseIntArray sCombiningToAccent = new SparseIntArray();
+ private static final SparseIntArray sAccentToCombining = new SparseIntArray();
+ static {
+ // U+0300: COMBINING GRAVE ACCENT
+ addCombining('\u0300', ACCENT_GRAVE);
+ // U+0301: COMBINING ACUTE ACCENT
+ addCombining('\u0301', ACCENT_ACUTE);
+ // U+0302: COMBINING CIRCUMFLEX ACCENT
+ addCombining('\u0302', ACCENT_CIRCUMFLEX);
+ // U+0303: COMBINING TILDE
+ addCombining('\u0303', ACCENT_TILDE);
+ // U+0304: COMBINING MACRON
+ addCombining('\u0304', ACCENT_MACRON);
+ // U+0306: COMBINING BREVE
+ addCombining('\u0306', ACCENT_BREVE);
+ // U+0307: COMBINING DOT ABOVE
+ addCombining('\u0307', ACCENT_DOT_ABOVE);
+ // U+0308: COMBINING DIAERESIS
+ addCombining('\u0308', ACCENT_UMLAUT);
+ // U+0309: COMBINING HOOK ABOVE
+ addCombining('\u0309', ACCENT_HOOK_ABOVE);
+ // U+030A: COMBINING RING ABOVE
+ addCombining('\u030A', ACCENT_RING_ABOVE);
+ // U+030B: COMBINING DOUBLE ACUTE ACCENT
+ addCombining('\u030B', ACCENT_DOUBLE_ACUTE);
+ // U+030C: COMBINING CARON
+ addCombining('\u030C', ACCENT_CARON);
+ // U+030D: COMBINING VERTICAL LINE ABOVE
+ addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE);
+ // U+030E: COMBINING DOUBLE VERTICAL LINE ABOVE
+ //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE);
+ // U+030F: COMBINING DOUBLE GRAVE ACCENT
+ //addCombining('\u030F', ACCENT_DOUBLE_GRAVE);
+ // U+0310: COMBINING CANDRABINDU
+ //addCombining('\u0310', ACCENT_CANDRABINDU);
+ // U+0311: COMBINING INVERTED BREVE
+ //addCombining('\u0311', ACCENT_INVERTED_BREVE);
+ // U+0312: COMBINING TURNED COMMA ABOVE
+ addCombining('\u0312', ACCENT_TURNED_COMMA_ABOVE);
+ // U+0313: COMBINING COMMA ABOVE
+ addCombining('\u0313', ACCENT_COMMA_ABOVE);
+ // U+0314: COMBINING REVERSED COMMA ABOVE
+ addCombining('\u0314', ACCENT_REVERSED_COMMA_ABOVE);
+ // U+0315: COMBINING COMMA ABOVE RIGHT
+ addCombining('\u0315', ACCENT_COMMA_ABOVE_RIGHT);
+ // U+031B: COMBINING HORN
+ addCombining('\u031B', ACCENT_HORN);
+ // U+0323: COMBINING DOT BELOW
+ addCombining('\u0323', ACCENT_DOT_BELOW);
+ // U+0326: COMBINING COMMA BELOW
+ //addCombining('\u0326', ACCENT_COMMA_BELOW);
+ // U+0327: COMBINING CEDILLA
+ addCombining('\u0327', ACCENT_CEDILLA);
+ // U+0328: COMBINING OGONEK
+ addCombining('\u0328', ACCENT_OGONEK);
+ // U+0329: COMBINING VERTICAL LINE BELOW
+ addCombining('\u0329', ACCENT_VERTICAL_LINE_BELOW);
+ // U+0331: COMBINING MACRON BELOW
+ addCombining('\u0331', ACCENT_MACRON_BELOW);
+ // U+0335: COMBINING SHORT STROKE OVERLAY
+ addCombining('\u0335', ACCENT_STROKE);
+ // U+0342: COMBINING GREEK PERISPOMENI
+ //addCombining('\u0342', ACCENT_PERISPOMENI);
+ // U+0344: COMBINING GREEK DIALYTIKA TONOS
+ //addCombining('\u0344', ACCENT_DIALYTIKA_TONOS);
+ // U+0345: COMBINING GREEK YPOGEGRAMMENI
+ //addCombining('\u0345', ACCENT_YPOGEGRAMMENI);
+
+ // One-way mappings to equivalent preferred accents.
+ // U+0340: COMBINING GRAVE TONE MARK
+ sCombiningToAccent.append('\u0340', ACCENT_GRAVE);
+ // U+0341: COMBINING ACUTE TONE MARK
+ sCombiningToAccent.append('\u0341', ACCENT_ACUTE);
+ // U+0343: COMBINING GREEK KORONIS
+ sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE);
+
+ // One-way legacy mappings to preserve compatibility with older applications.
+ // U+0300: COMBINING GRAVE ACCENT
+ sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300');
+ // U+0302: COMBINING CIRCUMFLEX ACCENT
+ sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302');
+ // U+0303: COMBINING TILDE
+ sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303');
+ }
+
+ private static void addCombining(int combining, int accent) {
+ sCombiningToAccent.append(combining, accent);
+ sAccentToCombining.append(accent, combining);
+ }
+
+ // Caution! This may only contain chars, not supplementary code points. It's unlikely
+ // it will ever need to, but if it does we'll have to change this
+ private static final SparseIntArray sNonstandardDeadCombinations = new SparseIntArray();
+ static {
+ // Non-standard decompositions.
+ // Stroke modifier for Finnish multilingual keyboard and others.
+ // U+0110: LATIN CAPITAL LETTER D WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'D', '\u0110');
+ // U+01E4: LATIN CAPITAL LETTER G WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'G', '\u01e4');
+ // U+0126: LATIN CAPITAL LETTER H WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'H', '\u0126');
+ // U+0197: LATIN CAPITAL LETTER I WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'I', '\u0197');
+ // U+0141: LATIN CAPITAL LETTER L WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'L', '\u0141');
+ // U+00D8: LATIN CAPITAL LETTER O WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'O', '\u00d8');
+ // U+0166: LATIN CAPITAL LETTER T WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'T', '\u0166');
+ // U+0111: LATIN SMALL LETTER D WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'd', '\u0111');
+ // U+01E5: LATIN SMALL LETTER G WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'g', '\u01e5');
+ // U+0127: LATIN SMALL LETTER H WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'h', '\u0127');
+ // U+0268: LATIN SMALL LETTER I WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'i', '\u0268');
+ // U+0142: LATIN SMALL LETTER L WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'l', '\u0142');
+ // U+00F8: LATIN SMALL LETTER O WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'o', '\u00f8');
+ // U+0167: LATIN SMALL LETTER T WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 't', '\u0167');
+ }
+
+ private static void addNonStandardDeadCombination(final int deadCodePoint,
+ final int spacingCodePoint, final int result) {
+ final int combination = (deadCodePoint << 16) | spacingCodePoint;
+ sNonstandardDeadCombinations.put(combination, result);
+ }
+
+ public static final int NOT_A_CHAR = 0;
+ public static final int BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION = 16;
+ // Get a non-standard combination
+ public static char getNonstandardCombination(final int deadCodePoint,
+ final int spacingCodePoint) {
+ final int combination = spacingCodePoint |
+ (deadCodePoint << BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION);
+ return (char)sNonstandardDeadCombinations.get(combination, NOT_A_CHAR);
+ }
+ }
+
// TODO: make this a list of events instead
final StringBuilder mDeadSequence = new StringBuilder();
+ @Nonnull
+ private Event createEventChainFromSequence(final @Nonnull CharSequence text,
+ final Event originalEvent) {
+ if (text.length() <= 0) {
+ return originalEvent;
+ } else {
+ Event lastEvent = null;
+ int codePoint = 0;
+ for (int i = text.length(); i > 0; i -= Character.charCount(codePoint)) {
+ codePoint = Character.codePointBefore(text, i);
+ final Event thisEvent = Event.createHardwareKeypressEvent(codePoint,
+ originalEvent.mKeyCode, lastEvent, false /* isKeyRepeat */);
+ lastEvent = thisEvent;
+ }
+ return lastEvent;
+ }
+ }
+
@Override
@Nonnull
public Event processEvent(final ArrayList<Event> previousEvents, final Event event) {
@@ -47,29 +249,48 @@ public class DeadKeyCombiner implements Combiner {
// simply returns the event as is. The majority of events will go through this path.
return event;
} else {
- // TODO: Allow combining for several dead chars rather than only the first one.
- // The framework doesn't know how to do this now.
- final int deadCodePoint = mDeadSequence.codePointAt(0);
- mDeadSequence.setLength(0);
- final int resultingCodePoint =
- KeyCharacterMap.getDeadChar(deadCodePoint, event.mCodePoint);
- if (0 == resultingCodePoint) {
- // We can't combine both characters. We need to commit the dead key as a separate
- // character, and the next char too unless it's a space (because as a special case,
- // dead key + space should result in only the dead key being committed - that's
- // how dead keys work).
- // If the event is a space, we should commit the dead char alone, but if it's
- // not, we need to commit both.
- // TODO: this is not necessarily triggered by hardware key events, so it's not
- // a good idea to masquerade as one. This should be typed as a software
- // composite event or something.
- return Event.createHardwareKeypressEvent(deadCodePoint, event.mKeyCode,
- Constants.CODE_SPACE == event.mCodePoint ? null : event /* next */,
- false /* isKeyRepeat */);
+ if (Character.isWhitespace(event.mCodePoint)
+ || event.mCodePoint == mDeadSequence.codePointBefore(mDeadSequence.length())) {
+ // When whitespace or twice the same dead key, we should output the dead sequence
+ // as is.
+ final Event resultEvent = createEventChainFromSequence(mDeadSequence.toString(),
+ event);
+ mDeadSequence.setLength(0);
+ return resultEvent;
+ } else if (event.isFunctionalKeyEvent()) {
+ if (Constants.CODE_DELETE == event.mKeyCode) {
+ // Remove the last code point
+ final int trimIndex = mDeadSequence.length() - Character.charCount(
+ mDeadSequence.codePointBefore(mDeadSequence.length()));
+ mDeadSequence.setLength(trimIndex);
+ return Event.createConsumedEvent(event);
+ } else {
+ return event;
+ }
+ } else if (event.isDead()) {
+ mDeadSequence.appendCodePoint(event.mCodePoint);
+ return Event.createConsumedEvent(event);
} else {
- // We could combine the characters.
- return Event.createHardwareKeypressEvent(resultingCodePoint, event.mKeyCode,
- null /* next */, false /* isKeyRepeat */);
+ // Combine normally.
+ final StringBuilder sb = new StringBuilder();
+ sb.appendCodePoint(event.mCodePoint);
+ int codePointIndex = 0;
+ while (codePointIndex < mDeadSequence.length()) {
+ final int deadCodePoint = mDeadSequence.codePointAt(codePointIndex);
+ final char replacementSpacingChar =
+ Data.getNonstandardCombination(deadCodePoint, event.mCodePoint);
+ if (Data.NOT_A_CHAR != replacementSpacingChar) {
+ sb.setCharAt(0, replacementSpacingChar);
+ } else {
+ final int combining = Data.sAccentToCombining.get(deadCodePoint);
+ sb.appendCodePoint(0 == combining ? deadCodePoint : combining);
+ }
+ codePointIndex += Character.isSupplementaryCodePoint(deadCodePoint) ? 2 : 1;
+ }
+ final String normalizedString = Normalizer.normalize(sb, Normalizer.Form.NFC);
+ final Event resultEvent = createEventChainFromSequence(normalizedString, event);
+ mDeadSequence.setLength(0);
+ return resultEvent;
}
}
}
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index ef5b04747..ff6f88066 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.event;
+import com.android.inputmethod.annotations.ExternallyReferenced;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.utils.StringUtils;
@@ -147,6 +148,7 @@ public class Event {
}
// This creates an input event for a dead character. @see {@link #FLAG_DEAD}
+ @ExternallyReferenced
public static Event createDeadEvent(final int codePoint, final int keyCode, final Event next) {
// TODO: add an argument or something if we ever create a software layout with dead keys.
return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 81ea90a4d..bf29b5ffe 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -94,13 +94,23 @@ public class Key implements Comparable<Key> {
/** Icon to display instead of a label. Icon takes precedence over a label */
private final int mIconId;
- /** Width of the key, not including the gap */
+ /** Width of the key, excluding the gap */
private final int mWidth;
- /** Height of the key, not including the gap */
+ /** Height of the key, excluding the gap */
private final int mHeight;
- /** X coordinate of the key in the keyboard layout */
+ /**
+ * The combined width in pixels of the horizontal gaps belonging to this key, both to the left
+ * and to the right. I.e., mWidth + mHorizontalGap = total width belonging to the key.
+ */
+ private final int mHorizontalGap;
+ /**
+ * The combined height in pixels of the vertical gaps belonging to this key, both above and
+ * below. I.e., mHeight + mVerticalGap = total height belonging to the key.
+ */
+ private final int mVerticalGap;
+ /** X coordinate of the top-left corner of the key in the keyboard layout, excluding the gap. */
private final int mX;
- /** Y coordinate of the key in the keyboard layout */
+ /** Y coordinate of the top-left corner of the key in the keyboard layout, excluding the gap. */
private final int mY;
/** Hit bounding box of the key */
private final Rect mHitBox = new Rect();
@@ -198,8 +208,10 @@ public class Key implements Comparable<Key> {
final String hintLabel, final int labelFlags, final int backgroundType, final int x,
final int y, final int width, final int height, final int horizontalGap,
final int verticalGap) {
- mHeight = height - verticalGap;
mWidth = width - horizontalGap;
+ mHeight = height - verticalGap;
+ mHorizontalGap = horizontalGap;
+ mVerticalGap = verticalGap;
mHintLabel = hintLabel;
mLabelFlags = labelFlags;
mBackgroundType = backgroundType;
@@ -214,7 +226,7 @@ public class Key implements Comparable<Key> {
mEnabled = (code != CODE_UNSPECIFIED);
mIconId = iconId;
// Horizontal gap is divided equally to both sides of the key.
- mX = x + horizontalGap / 2;
+ mX = x + mHorizontalGap / 2;
mY = y;
mHitBox.set(x, y, x + width + 1, y + height);
mKeyVisualAttributes = null;
@@ -235,18 +247,21 @@ public class Key implements Comparable<Key> {
*/
public Key(final String keySpec, final TypedArray keyAttr, final KeyStyle style,
final KeyboardParams params, final KeyboardRow row) {
- final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
+ mHorizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
+ mVerticalGap = params.mVerticalGap;
+
+ final float horizontalGapFloat = mHorizontalGap;
final int rowHeight = row.getRowHeight();
- mHeight = rowHeight - params.mVerticalGap;
+ mHeight = rowHeight - mVerticalGap;
final float keyXPos = row.getKeyX(keyAttr);
final float keyWidth = row.getKeyWidth(keyAttr, keyXPos);
final int keyYPos = row.getKeyY();
// Horizontal gap is divided equally to both sides of the key.
- mX = Math.round(keyXPos + horizontalGap / 2);
+ mX = Math.round(keyXPos + horizontalGapFloat / 2);
mY = keyYPos;
- mWidth = Math.round(keyWidth - horizontalGap);
+ mWidth = Math.round(keyWidth - horizontalGapFloat);
mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1,
keyYPos + rowHeight);
// Update row to have current x coordinate.
@@ -380,6 +395,10 @@ public class Key implements Comparable<Key> {
* @param key the original key.
*/
protected Key(final Key key) {
+ this(key, key.mMoreKeys);
+ }
+
+ private Key(final Key key, final MoreKeySpec[] moreKeys) {
// Final attributes.
mCode = key.mCode;
mLabel = key.mLabel;
@@ -388,10 +407,12 @@ public class Key implements Comparable<Key> {
mIconId = key.mIconId;
mWidth = key.mWidth;
mHeight = key.mHeight;
+ mHorizontalGap = key.mHorizontalGap;
+ mVerticalGap = key.mVerticalGap;
mX = key.mX;
mY = key.mY;
mHitBox.set(key.mHitBox);
- mMoreKeys = key.mMoreKeys;
+ mMoreKeys = moreKeys;
mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags;
mBackgroundType = key.mBackgroundType;
mActionFlags = key.mActionFlags;
@@ -403,6 +424,14 @@ public class Key implements Comparable<Key> {
mEnabled = key.mEnabled;
}
+ public static Key removeRedundantMoreKeys(final Key key,
+ final MoreKeySpec.LettersOnBaseLayout lettersOnBaseLayout) {
+ final MoreKeySpec[] moreKeys = key.getMoreKeys();
+ final MoreKeySpec[] filteredMoreKeys = MoreKeySpec.removeRedundantMoreKeys(
+ moreKeys, lettersOnBaseLayout);
+ return (filteredMoreKeys == moreKeys) ? key : new Key(key, filteredMoreKeys);
+ }
+
private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) {
if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
switch (keyboardElementId) {
@@ -771,18 +800,52 @@ public class Key implements Comparable<Key> {
return iconSet.getIconDrawable(getIconId());
}
+ /**
+ * Gets the width of the key in pixels, excluding the gap.
+ * @return The width of the key in pixels, excluding the gap.
+ */
public int getWidth() {
return mWidth;
}
+ /**
+ * Gets the height of the key in pixels, excluding the gap.
+ * @return The height of the key in pixels, excluding the gap.
+ */
public int getHeight() {
return mHeight;
}
+ /**
+ * The combined width in pixels of the horizontal gaps belonging to this key, both above and
+ * below. I.e., getWidth() + getHorizontalGap() = total width belonging to the key.
+ * @return Horizontal gap belonging to this key.
+ */
+ public int getHorizontalGap() {
+ return mHorizontalGap;
+ }
+
+ /**
+ * The combined height in pixels of the vertical gaps belonging to this key, both above and
+ * below. I.e., getHeight() + getVerticalGap() = total height belonging to the key.
+ * @return Vertical gap belonging to this key.
+ */
+ public int getVerticalGap() {
+ return mVerticalGap;
+ }
+
+ /**
+ * Gets the x-coordinate of the top-left corner of the key in pixels, excluding the gap.
+ * @return The x-coordinate of the top-left corner of the key in pixels, excluding the gap.
+ */
public int getX() {
return mX;
}
+ /**
+ * Gets the y-coordinate of the top-left corner of the key in pixels, excluding the gap.
+ * @return The y-coordinate of the top-left corner of the key in pixels, excluding the gap.
+ */
public int getY() {
return mY;
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 3c1167538..f9cf3535e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -21,9 +21,9 @@ import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOAR
import android.text.InputType;
import android.text.TextUtils;
import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
@@ -62,7 +62,7 @@ public final class KeyboardId {
public static final int ELEMENT_EMOJI_CATEGORY5 = 15;
public static final int ELEMENT_EMOJI_CATEGORY6 = 16;
- public final InputMethodSubtype mSubtype;
+ public final RichInputMethodSubtype mSubtype;
public final Locale mLocale;
public final int mWidth;
public final int mHeight;
@@ -73,6 +73,7 @@ public final class KeyboardId {
public final boolean mLanguageSwitchKeyEnabled;
public final String mCustomActionLabel;
public final boolean mHasShortcutKey;
+ public final boolean mIsSplitLayout;
private final int mHashCode;
@@ -89,6 +90,7 @@ public final class KeyboardId {
mCustomActionLabel = (mEditorInfo.actionLabel != null)
? mEditorInfo.actionLabel.toString() : null;
mHasShortcutKey = params.mVoiceInputKeyEnabled;
+ mIsSplitLayout = params.mIsSplitLayoutEnabled;
mHashCode = computeHashCode(this);
}
@@ -108,7 +110,8 @@ public final class KeyboardId {
id.mCustomActionLabel,
id.navigateNext(),
id.navigatePrevious(),
- id.mSubtype
+ id.mSubtype,
+ id.mIsSplitLayout
});
}
@@ -128,7 +131,8 @@ public final class KeyboardId {
&& TextUtils.equals(other.mCustomActionLabel, mCustomActionLabel)
&& other.navigateNext() == navigateNext()
&& other.navigatePrevious() == navigatePrevious()
- && other.mSubtype.equals(mSubtype);
+ && other.mSubtype.equals(mSubtype)
+ && other.mIsSplitLayout == mIsSplitLayout;
}
private static boolean isAlphabetKeyboard(final int elementId) {
@@ -175,7 +179,7 @@ public final class KeyboardId {
@Override
public String toString() {
- return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s]",
+ return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s%s]",
elementIdToName(mElementId),
mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
mWidth, mHeight,
@@ -187,7 +191,8 @@ public final class KeyboardId {
(passwordInput() ? " passwordInput" : ""),
(mHasShortcutKey ? " hasShortcutKey" : ""),
(mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
- (isMultiLine() ? " isMultiLine" : "")
+ (isMultiLine() ? " isMultiLine" : ""),
+ (mIsSplitLayout ? " isSplitLayout" : "")
);
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index feb79efe9..52b9284be 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -28,7 +28,6 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
@@ -37,6 +36,7 @@ import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.keyboard.internal.KeysCache;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.define.DebugFlags;
import com.android.inputmethod.latin.utils.InputTypeUtils;
@@ -96,6 +96,8 @@ public final class KeyboardLayoutSet {
private static final class ElementParams {
int mKeyboardXmlId;
boolean mProximityCharsCorrectionEnabled;
+ boolean mSupportsSplitLayout;
+ boolean mAllowRedundantMoreKeys;
public ElementParams() {}
}
@@ -109,11 +111,17 @@ public final class KeyboardLayoutSet {
boolean mVoiceInputKeyEnabled;
boolean mNoSettingsKey;
boolean mLanguageSwitchKeyEnabled;
- InputMethodSubtype mSubtype;
+ RichInputMethodSubtype mSubtype;
boolean mIsSpellChecker;
int mKeyboardWidth;
int mKeyboardHeight;
int mScriptId = ScriptUtils.SCRIPT_LATIN;
+ // Indicates if the user has enabled the split-layout preference
+ // and the required ProductionFlags are enabled.
+ boolean mIsSplitLayoutEnabledByUser;
+ // Indicates if split layout is actually enabled, taking into account
+ // whether the user has enabled it, and the keyboard layout supports it.
+ boolean mIsSplitLayoutEnabled;
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
new SparseArray<>();
@@ -168,6 +176,9 @@ public final class KeyboardLayoutSet {
// attribute in a keyboard_layout_set XML file. Also each keyboard layout XML resource is
// specified as an elementKeyboard attribute in the file.
// The KeyboardId is an internal key for a Keyboard object.
+
+ mParams.mIsSplitLayoutEnabled = mParams.mIsSplitLayoutEnabledByUser
+ && elementParams.mSupportsSplitLayout;
final KeyboardId id = new KeyboardId(keyboardLayoutSetElementId, mParams);
try {
return getKeyboard(elementParams, id);
@@ -192,6 +203,7 @@ public final class KeyboardLayoutSet {
if (id.isAlphabetKeyboard()) {
builder.setAutoGenerate(sKeysCache);
}
+ builder.setAllowRedundantMoreKes(elementParams.mAllowRedundantMoreKeys);
final int keyboardXmlId = elementParams.mKeyboardXmlId;
builder.load(keyboardXmlId, id);
if (mParams.mDisableTouchPositionCorrectionDataForTest) {
@@ -253,7 +265,7 @@ public final class KeyboardLayoutSet {
return this;
}
- public Builder setSubtype(final InputMethodSubtype subtype) {
+ public Builder setSubtype(final RichInputMethodSubtype subtype) {
final boolean asciiCapable = InputMethodSubtypeCompatUtils.isAsciiCapable(subtype);
// TODO: Consolidate with {@link InputAttributes}.
@SuppressWarnings("deprecation")
@@ -262,7 +274,7 @@ public final class KeyboardLayoutSet {
final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii(
mParams.mEditorInfo.imeOptions)
|| deprecatedForceAscii;
- final InputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable)
+ final RichInputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable)
? SubtypeSwitcher.getInstance().getNoLanguageSubtype()
: subtype;
mParams.mSubtype = keyboardSubtype;
@@ -286,12 +298,19 @@ public final class KeyboardLayoutSet {
return this;
}
- public void disableTouchPositionCorrectionData() {
+ public Builder disableTouchPositionCorrectionData() {
mParams.mDisableTouchPositionCorrectionDataForTest = true;
+ return this;
}
- public void setScriptId(final int scriptId) {
+ public Builder setScriptId(final int scriptId) {
mParams.mScriptId = scriptId;
+ return this;
+ }
+
+ public Builder setSplitLayoutEnabledByUser(final boolean enabled) {
+ mParams.mIsSplitLayoutEnabledByUser = enabled;
+ return this;
}
public KeyboardLayoutSet build() {
@@ -376,6 +395,10 @@ public final class KeyboardLayoutSet {
elementParams.mProximityCharsCorrectionEnabled = a.getBoolean(
R.styleable.KeyboardLayoutSet_Element_enableProximityCharsCorrection,
false);
+ elementParams.mSupportsSplitLayout = a.getBoolean(
+ R.styleable.KeyboardLayoutSet_Element_supportsSplitLayout, false);
+ elementParams.mAllowRedundantMoreKeys = a.getBoolean(
+ R.styleable.KeyboardLayoutSet_Element_allowRedundantMoreKeys, true);
mParams.mKeyboardLayoutSetElementIdToParamsMap.put(elementName, elementParams);
} finally {
a.recycle();
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 60665f8de..246d11bac 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -17,9 +17,7 @@
package com.android.inputmethod.keyboard;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Resources;
-import android.preference.PreferenceManager;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -27,6 +25,7 @@ import android.view.View;
import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
+import com.android.inputmethod.event.Event;
import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
import com.android.inputmethod.keyboard.emoji.EmojiPalettesView;
import com.android.inputmethod.keyboard.internal.KeyboardState;
@@ -37,6 +36,7 @@ import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.define.ProductionFlags;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.utils.ResourceUtils;
@@ -46,7 +46,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
private SubtypeSwitcher mSubtypeSwitcher;
- private SharedPreferences mPrefs;
private InputView mCurrentInputView;
private View mMainKeyboardFrame;
@@ -75,13 +74,11 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
public static void init(final LatinIME latinIme) {
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(latinIme);
- sInstance.initInternal(latinIme, prefs);
+ sInstance.initInternal(latinIme);
}
- private void initInternal(final LatinIME latinIme, final SharedPreferences prefs) {
+ private void initInternal(final LatinIME latinIme) {
mLatinIME = latinIme;
- mPrefs = prefs;
mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mState = new KeyboardState(this);
mIsHardwareAcceleratedDrawingEnabled =
@@ -90,7 +87,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
public void updateKeyboardTheme() {
final boolean themeUpdated = updateKeyboardThemeAndContextThemeWrapper(
- mLatinIME, KeyboardTheme.getKeyboardTheme(mPrefs));
+ mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */));
if (themeUpdated && mKeyboardView != null) {
mLatinIME.setInputView(onCreateInputView(mIsHardwareAcceleratedDrawingEnabled));
}
@@ -118,6 +115,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
builder.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey);
builder.setLanguageSwitchKeyEnabled(mLatinIME.shouldShowLanguageSwitchKey());
+ builder.setSplitLayoutEnabledByUser(ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED
+ && settingsValues.mIsSplitKeyboardEnabled);
mKeyboardLayoutSet = builder.build();
try {
mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState);
@@ -304,9 +303,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
/**
* Updates state machine to figure out when to automatically switch back to the previous mode.
*/
- public void onCodeInput(final int code, final int currentAutoCapsState,
+ public void onEvent(final Event event, final int currentAutoCapsState,
final int currentRecapitalizeState) {
- mState.onCodeInput(code, currentAutoCapsState, currentRecapitalizeState);
+ mState.onEvent(event, currentAutoCapsState, currentRecapitalizeState);
}
public boolean isShowingEmojiPalettes() {
@@ -347,7 +346,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
updateKeyboardThemeAndContextThemeWrapper(
- mLatinIME, KeyboardTheme.getKeyboardTheme(mPrefs));
+ mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */));
mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
R.layout.input_view, null);
mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
index 7161d3f26..6d8c8b76f 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
@@ -16,14 +16,17 @@
package com.android.inputmethod.keyboard;
+import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build.VERSION_CODES;
+import android.preference.PreferenceManager;
import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.BuildCompatUtils;
import com.android.inputmethod.latin.R;
+import java.util.ArrayList;
import java.util.Arrays;
public final class KeyboardTheme implements Comparable<KeyboardTheme> {
@@ -40,7 +43,10 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
public static final int THEME_ID_LXX_DARK = 4;
public static final int DEFAULT_THEME_ID = THEME_ID_KLP;
- private static final KeyboardTheme[] KEYBOARD_THEMES = {
+ private static KeyboardTheme[] AVAILABLE_KEYBOARD_THEMES;
+
+ @UsedForTesting
+ static final KeyboardTheme[] KEYBOARD_THEMES = {
new KeyboardTheme(THEME_ID_ICS, "ICS", R.style.KeyboardTheme_ICS,
// This has never been selected because we support ICS or later.
VERSION_CODES.BASE),
@@ -93,9 +99,10 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
}
@UsedForTesting
- static KeyboardTheme searchKeyboardThemeById(final int themeId) {
+ static KeyboardTheme searchKeyboardThemeById(final int themeId,
+ final KeyboardTheme[] availableThemeIds) {
// TODO: This search algorithm isn't optimal if there are many themes.
- for (final KeyboardTheme theme : KEYBOARD_THEMES) {
+ for (final KeyboardTheme theme : availableThemeIds) {
if (theme.mThemeId == themeId) {
return theme;
}
@@ -105,13 +112,14 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
@UsedForTesting
static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs,
- final int sdkVersion) {
+ final int sdkVersion, final KeyboardTheme[] availableThemeArray) {
final String klpThemeIdString = prefs.getString(KLP_KEYBOARD_THEME_KEY, null);
if (klpThemeIdString != null) {
if (sdkVersion <= VERSION_CODES.KITKAT) {
try {
final int themeId = Integer.parseInt(klpThemeIdString);
- final KeyboardTheme theme = searchKeyboardThemeById(themeId);
+ final KeyboardTheme theme = searchKeyboardThemeById(themeId,
+ availableThemeArray);
if (theme != null) {
return theme;
}
@@ -125,22 +133,21 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
prefs.edit().remove(KLP_KEYBOARD_THEME_KEY).apply();
}
// TODO: This search algorithm isn't optimal if there are many themes.
- for (final KeyboardTheme theme : KEYBOARD_THEMES) {
+ for (final KeyboardTheme theme : availableThemeArray) {
if (sdkVersion >= theme.mMinApiVersion) {
return theme;
}
}
- return searchKeyboardThemeById(DEFAULT_THEME_ID);
+ return searchKeyboardThemeById(DEFAULT_THEME_ID, availableThemeArray);
}
public static String getKeyboardThemeName(final int themeId) {
- final KeyboardTheme theme = searchKeyboardThemeById(themeId);
+ final KeyboardTheme theme = searchKeyboardThemeById(themeId, KEYBOARD_THEMES);
return theme.mThemeName;
}
- public static void saveKeyboardThemeId(final String themeIdString,
- final SharedPreferences prefs) {
- saveKeyboardThemeId(themeIdString, prefs, BuildCompatUtils.EFFECTIVE_SDK_INT);
+ public static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs) {
+ saveKeyboardThemeId(themeId, prefs, BuildCompatUtils.EFFECTIVE_SDK_INT);
}
@UsedForTesting
@@ -152,25 +159,45 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
}
@UsedForTesting
- static void saveKeyboardThemeId(final String themeIdString,
- final SharedPreferences prefs, final int sdkVersion) {
+ static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs,
+ final int sdkVersion) {
final String prefKey = getPreferenceKey(sdkVersion);
- prefs.edit().putString(prefKey, themeIdString).apply();
+ prefs.edit().putString(prefKey, Integer.toString(themeId)).apply();
}
- public static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs) {
- return getKeyboardTheme(prefs, BuildCompatUtils.EFFECTIVE_SDK_INT);
+ public static KeyboardTheme getKeyboardTheme(final Context context) {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ final KeyboardTheme[] availableThemeArray = getAvailableThemeArray(context);
+ return getKeyboardTheme(prefs, BuildCompatUtils.EFFECTIVE_SDK_INT, availableThemeArray);
+ }
+
+ static KeyboardTheme[] getAvailableThemeArray(final Context context) {
+ if (AVAILABLE_KEYBOARD_THEMES == null) {
+ final int[] availableThemeIdStringArray = context.getResources().getIntArray(
+ R.array.keyboard_theme_ids);
+ final ArrayList<KeyboardTheme> availableThemeList = new ArrayList<>();
+ for (final int id : availableThemeIdStringArray) {
+ final KeyboardTheme theme = searchKeyboardThemeById(id, KEYBOARD_THEMES);
+ if (theme != null) {
+ availableThemeList.add(theme);
+ }
+ }
+ AVAILABLE_KEYBOARD_THEMES = availableThemeList.toArray(
+ new KeyboardTheme[availableThemeList.size()]);
+ }
+ return AVAILABLE_KEYBOARD_THEMES;
}
@UsedForTesting
- static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion) {
+ static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion,
+ final KeyboardTheme[] availableThemeArray) {
final String lxxThemeIdString = prefs.getString(LXX_KEYBOARD_THEME_KEY, null);
if (lxxThemeIdString == null) {
- return getDefaultKeyboardTheme(prefs, sdkVersion);
+ return getDefaultKeyboardTheme(prefs, sdkVersion, availableThemeArray);
}
try {
final int themeId = Integer.parseInt(lxxThemeIdString);
- final KeyboardTheme theme = searchKeyboardThemeById(themeId);
+ final KeyboardTheme theme = searchKeyboardThemeById(themeId, availableThemeArray);
if (theme != null) {
return theme;
}
@@ -180,6 +207,6 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
}
// Remove preference that contains unknown or illegal theme id.
prefs.edit().remove(LXX_KEYBOARD_THEME_KEY).apply();
- return getDefaultKeyboardTheme(prefs, sdkVersion);
+ return getDefaultKeyboardTheme(prefs, sdkVersion, availableThemeArray);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 6d0b2c2f1..cded2cb8c 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -34,7 +34,6 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.accessibility.MainKeyboardAccessibilityDelegate;
@@ -54,10 +53,10 @@ import com.android.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview;
import com.android.inputmethod.keyboard.internal.TimerHandler;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.settings.DebugSettings;
import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.SpacebarLanguageUtils;
import com.android.inputmethod.latin.utils.TypefaceUtils;
import java.util.WeakHashMap;
@@ -756,7 +755,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
public void onHideWindow() {
onDismissMoreKeysPanel();
final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
accessibilityDelegate.onHideWindow();
}
}
@@ -767,7 +767,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
public boolean onHoverEvent(final MotionEvent event) {
final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
return accessibilityDelegate.onHoverEvent(event);
}
return super.onHoverEvent(event);
@@ -873,16 +874,16 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
// Layout language name on spacebar.
private String layoutLanguageOnSpacebar(final Paint paint,
- final InputMethodSubtype subtype, final int width) {
+ final RichInputMethodSubtype subtype, final int width) {
// Choose appropriate language name to fit into the width.
if (mLanguageOnSpacebarFormatType == LanguageOnSpacebarHelper.FORMAT_TYPE_FULL_LOCALE) {
- final String fullText = SpacebarLanguageUtils.getFullDisplayName(subtype);
+ final String fullText = subtype.getFullDisplayName();
if (fitsTextIntoWidth(width, fullText, paint)) {
return fullText;
}
}
- final String middleText = SpacebarLanguageUtils.getMiddleDisplayName(subtype);
+ final String middleText = subtype.getMiddleDisplayName();
if (fitsTextIntoWidth(width, middleText, paint)) {
return middleText;
}
@@ -896,7 +897,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
paint.setTextAlign(Align.CENTER);
paint.setTypeface(Typeface.DEFAULT);
paint.setTextSize(mLanguageOnSpacebarTextSize);
- final InputMethodSubtype subtype = getKeyboard().mId.mSubtype;
+ final RichInputMethodSubtype subtype = getKeyboard().mId.mSubtype;
final String language = layoutLanguageOnSpacebar(paint, subtype, width);
// Draw language text with shadow
final float descent = paint.descent();
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index a9d1239ed..841283b7f 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -105,7 +105,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
- if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+ if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
if (mAccessibilityDelegate == null) {
mAccessibilityDelegate = new MoreKeysKeyboardAccessibilityDelegate(
this, mKeyDetector);
@@ -142,7 +142,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
mOriginY = y + container.getPaddingTop();
controller.onShowMoreKeysPanel(this);
final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
accessibilityDelegate.onShowMoreKeysKeyboard();
}
}
@@ -239,7 +240,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
return;
}
final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
accessibilityDelegate.onDismissMoreKeysKeyboard();
}
mController.onDismissMoreKeysPanel();
@@ -285,7 +287,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
@Override
public boolean onHoverEvent(final MotionEvent event) {
final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
return accessibilityDelegate.onHoverEvent(event);
}
return super.onHoverEvent(event);
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
index 17dfc9cce..925ec6bfb 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
@@ -104,7 +104,8 @@ final class EmojiPageKeyboardView extends KeyboardView implements
public boolean onHoverEvent(final MotionEvent event) {
final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> accessibilityDelegate =
mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
return accessibilityDelegate.onHoverEvent(event);
}
return super.onHoverEvent(event);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
index df82becae..4f8a105d5 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
@@ -49,7 +49,7 @@ public class DrawingHandler extends LeakGuardHandlerWrapper<Callbacks> {
callbacks.dismissKeyPreviewWithoutDelay((Key)msg.obj);
break;
case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
- callbacks.showGestureFloatingPreviewText(SuggestedWords.EMPTY);
+ callbacks.showGestureFloatingPreviewText(SuggestedWords.getEmptyInstance());
break;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java
index fd84856b7..37ea0f17b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java
@@ -98,7 +98,7 @@ public class GestureFloatingTextDrawingPreview extends AbstractDrawingPreview {
private final RectF mGesturePreviewRectangle = new RectF();
private int mPreviewTextX;
private int mPreviewTextY;
- private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+ private SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance();
private final int[] mLastPointerCoords = CoordinateUtils.newInstance();
public GestureFloatingTextDrawingPreview(final TypedArray mainKeyboardViewAttr) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index fa4192790..50385555c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -162,6 +162,10 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
mParams.mKeysCache = keysCache;
}
+ public void setAllowRedundantMoreKes(final boolean enabled) {
+ mParams.mAllowRedundantMoreKeys = enabled;
+ }
+
public KeyboardBuilder<KP> load(final int xmlId, final KeyboardId id) {
mParams.mId = id;
final XmlResourceParser parser = mResources.getXml(xmlId);
@@ -674,15 +678,18 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
final boolean countryCodeMatched = matchString(caseAttr,
R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
+ final boolean splitLayoutMatched = matchBoolean(caseAttr,
+ R.styleable.Keyboard_Case_isSplitLayout, id.mIsSplitLayout);
final boolean selected = keyboardLayoutSetMatched && keyboardLayoutSetElementMatched
&& keyboardThemeMacthed && modeMatched && navigateNextMatched
&& navigatePreviousMatched && passwordInputMatched && clobberSettingsKeyMatched
&& hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
&& isMultiLineMatched && imeActionMatched && isIconDefinedMatched
- && localeCodeMatched && languageCodeMatched && countryCodeMatched;
+ && localeCodeMatched && languageCodeMatched && countryCodeMatched
+ && splitLayoutMatched;
if (DEBUG) {
- startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+ startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
textAttr(caseAttr.getString(
R.styleable.Keyboard_Case_keyboardLayoutSet), "keyboardLayoutSet"),
textAttr(caseAttr.getString(
@@ -707,6 +714,8 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
"languageSwitchKeyEnabled"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_isMultiLine,
"isMultiLine"),
+ booleanAttr(caseAttr, R.styleable.Keyboard_Case_isSplitLayout,
+ "splitLayout"),
textAttr(caseAttr.getString(R.styleable.Keyboard_Case_isIconDefined),
"isIconDefined"),
textAttr(caseAttr.getString(R.styleable.Keyboard_Case_localeCode),
@@ -846,6 +855,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
}
private void endKeyboard() {
+ mParams.removeRedundantMoreKeys();
// {@link #parseGridRows(XmlPullParser,boolean)} may populate keyboard rows higher than
// previously expected.
final int actualHeight = mCurrentY - mParams.mVerticalGap + mParams.mBottomPadding;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
index 5df9d3ece..71ce768a9 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -27,6 +27,9 @@ import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
public class KeyboardParams {
public KeyboardId mId;
public int mThemeId;
@@ -67,7 +70,8 @@ public class KeyboardParams {
public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet);
- public KeysCache mKeysCache;
+ @Nullable public KeysCache mKeysCache;
+ public boolean mAllowRedundantMoreKeys;
public int mMostCommonKeyHeight = 0;
public int mMostCommonKeyWidth = 0;
@@ -95,7 +99,7 @@ public class KeyboardParams {
clearHistogram();
}
- public void onAddKey(final Key newKey) {
+ public void onAddKey(@Nonnull final Key newKey) {
final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
final boolean isSpacer = key.isSpacer();
if (isSpacer && key.getWidth() == 0) {
@@ -115,6 +119,26 @@ public class KeyboardParams {
}
}
+ public void removeRedundantMoreKeys() {
+ if (mAllowRedundantMoreKeys) {
+ return;
+ }
+ final MoreKeySpec.LettersOnBaseLayout lettersOnBaseLayout =
+ new MoreKeySpec.LettersOnBaseLayout();
+ for (final Key key : mSortedKeys) {
+ lettersOnBaseLayout.addLetter(key);
+ }
+ final ArrayList<Key> allKeys = new ArrayList<>(mSortedKeys);
+ mSortedKeys.clear();
+ for (final Key key : allKeys) {
+ final Key filteredKey = Key.removeRedundantMoreKeys(key, lettersOnBaseLayout);
+ if (mKeysCache != null) {
+ mKeysCache.replace(key, filteredKey);
+ }
+ mSortedKeys.add(filteredKey);
+ }
+ }
+
private int mMaxHeightCount = 0;
private int mMaxWidthCount = 0;
private final SparseIntArray mHeightHistogram = new SparseIntArray();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index b98ced97c..5f4d55bdb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -19,6 +19,7 @@ package com.android.inputmethod.keyboard.internal;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.event.Event;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
@@ -29,7 +30,7 @@ import com.android.inputmethod.latin.utils.RecapitalizeStatus;
*
* The input events are {@link #onLoadKeyboard(int, int)}, {@link #onSaveKeyboardState()},
* {@link #onPressKey(int,boolean,int,int)}, {@link #onReleaseKey(int,boolean,int,int)},
- * {@link #onCodeInput(int,int,int)}, {@link #onFinishSlidingInput(int,int)},
+ * {@link #onEvent(Event,int,int)}, {@link #onFinishSlidingInput(int,int)},
* {@link #onUpdateShiftState(int,int)}, {@link #onResetKeyboardStateToAlphabet(int,int)}.
*
* The actions are {@link SwitchActions}'s methods.
@@ -610,10 +611,11 @@ public final class KeyboardState {
return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
}
- public void onCodeInput(final int code, final int currentAutoCapsState,
+ public void onEvent(final Event event, final int currentAutoCapsState,
final int currentRecapitalizeState) {
+ final int code = event.isFunctionalKeyEvent() ? event.mKeyCode : event.mCodePoint;
if (DEBUG_EVENT) {
- Log.d(TAG, "onCodeInput: code=" + Constants.printableCode(code)
+ Log.d(TAG, "onEvent: code=" + Constants.printableCode(code)
+ " autoCaps=" + currentAutoCapsState + " " + this);
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index cd6abeed3..f9297ac27 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -25,49 +25,42 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.utils.RunInLocale;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
-import java.util.HashMap;
import java.util.Locale;
+// TODO: Make this an immutable class.
public final class KeyboardTextsSet {
public static final String PREFIX_TEXT = "!text/";
+ private static final String PREFIX_RESOURCE = "!string/";
public static final String SWITCH_TO_ALPHA_KEY_LABEL = "keylabel_to_alpha";
private static final char BACKSLASH = Constants.CODE_BACKSLASH;
- private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
+ private static final int MAX_REFERENCE_INDIRECTION = 10;
+ private Resources mResources;
+ private Locale mResourceLocale;
+ private String mResourcePackageName;
private String[] mTextsTable;
- // Resource name to text map.
- private HashMap<String, String> mResourceNameToTextsMap = new HashMap<>();
public void setLocale(final Locale locale, final Context context) {
- mTextsTable = KeyboardTextsTable.getTextsTable(locale);
final Resources res = context.getResources();
- final int referenceId = context.getApplicationInfo().labelRes;
- final String resourcePackageName = res.getResourcePackageName(referenceId);
- final RunInLocale<Void> job = new RunInLocale<Void>() {
- @Override
- protected Void job(final Resources resource) {
- loadStringResourcesInternal(res, RESOURCE_NAMES, resourcePackageName);
- return null;
- }
- };
// Null means the current system locale.
- job.runInLocale(res,
- SubtypeLocaleUtils.NO_LANGUAGE.equals(locale.toString()) ? null : locale);
+ final String resourcePackageName = res.getResourcePackageName(
+ context.getApplicationInfo().labelRes);
+ setLocale(locale, res, resourcePackageName);
}
@UsedForTesting
- void loadStringResourcesInternal(final Resources res, final String[] resourceNames,
+ public void setLocale(final Locale locale, final Resources res,
final String resourcePackageName) {
- for (final String resName : resourceNames) {
- final int resId = res.getIdentifier(resName, "string", resourcePackageName);
- mResourceNameToTextsMap.put(resName, res.getString(resId));
- }
+ mResources = res;
+ // Null means the current system locale.
+ mResourceLocale = SubtypeLocaleUtils.NO_LANGUAGE.equals(locale.toString()) ? null : locale;
+ mResourcePackageName = resourcePackageName;
+ mTextsTable = KeyboardTextsTable.getTextsTable(locale);
}
public String getText(final String name) {
- final String text = mResourceNameToTextsMap.get(name);
- return (text != null) ? text : KeyboardTextsTable.getText(name, mTextsTable);
+ return KeyboardTextsTable.getText(name, mTextsTable);
}
private static int searchTextNameEnd(final String text, final int start) {
@@ -93,13 +86,14 @@ public final class KeyboardTextsSet {
StringBuilder sb;
do {
level++;
- if (level >= MAX_STRING_REFERENCE_INDIRECTION) {
- throw new RuntimeException("Too many " + PREFIX_TEXT + "name indirection: " + text);
+ if (level >= MAX_REFERENCE_INDIRECTION) {
+ throw new RuntimeException("Too many " + PREFIX_TEXT + " or " + PREFIX_RESOURCE +
+ " reference indirection: " + text);
}
- final int prefixLen = PREFIX_TEXT.length();
+ final int prefixLength = PREFIX_TEXT.length();
final int size = text.length();
- if (size < prefixLen) {
+ if (size < prefixLength) {
break;
}
@@ -110,10 +104,12 @@ public final class KeyboardTextsSet {
if (sb == null) {
sb = new StringBuilder(text.substring(0, pos));
}
- final int end = searchTextNameEnd(text, pos + prefixLen);
- final String name = text.substring(pos + prefixLen, end);
- sb.append(getText(name));
- pos = end - 1;
+ pos = expandReference(text, pos, PREFIX_TEXT, sb);
+ } else if (text.startsWith(PREFIX_RESOURCE, pos)) {
+ if (sb == null) {
+ sb = new StringBuilder(text.substring(0, pos));
+ }
+ pos = expandReference(text, pos, PREFIX_RESOURCE, sb);
} else if (c == BACKSLASH) {
if (sb != null) {
// Append both escape character and escaped character.
@@ -132,18 +128,24 @@ public final class KeyboardTextsSet {
return TextUtils.isEmpty(text) ? null : text;
}
- // These texts' name should be aligned with the @string/<name> in
- // values*/strings-action-keys.xml.
- static final String[] RESOURCE_NAMES = {
- // Labels for action.
- "label_go_key",
- "label_send_key",
- "label_next_key",
- "label_done_key",
- "label_search_key",
- "label_previous_key",
- // Other labels.
- "label_pause_key",
- "label_wait_key",
- };
+ private int expandReference(final String text, final int pos, final String prefix,
+ final StringBuilder sb) {
+ final int prefixLength = prefix.length();
+ final int end = searchTextNameEnd(text, pos + prefixLength);
+ final String name = text.substring(pos + prefixLength, end);
+ if (prefix.equals(PREFIX_TEXT)) {
+ sb.append(getText(name));
+ } else { // PREFIX_RESOURCE
+ final String resourcePackageName = mResourcePackageName;
+ final RunInLocale<String> getTextJob = new RunInLocale<String>() {
+ @Override
+ protected String job(final Resources res) {
+ final int resId = res.getIdentifier(name, "string", resourcePackageName);
+ return res.getString(resId);
+ }
+ };
+ sb.append(getTextJob.runInLocale(mResources, mResourceLocale));
+ }
+ return end - 1;
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
index 31bc549ca..978194a3b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
@@ -83,24 +83,24 @@ public final class KeyboardTextsTable {
private static final String[] NAMES = {
// /* index:histogram */ "name",
- /* 0:32 */ "morekeys_a",
- /* 1:32 */ "morekeys_o",
- /* 2:30 */ "morekeys_u",
- /* 3:30 */ "keylabel_to_alpha",
- /* 4:29 */ "morekeys_e",
- /* 5:28 */ "morekeys_i",
- /* 6:23 */ "morekeys_c",
- /* 7:23 */ "double_quotes",
- /* 8:22 */ "morekeys_n",
- /* 9:22 */ "single_quotes",
- /* 10:20 */ "morekeys_s",
- /* 11:17 */ "keyspec_currency",
- /* 12:14 */ "morekeys_y",
- /* 13:13 */ "morekeys_d",
- /* 14:12 */ "morekeys_z",
+ /* 0:33 */ "morekeys_a",
+ /* 1:33 */ "morekeys_o",
+ /* 2:32 */ "morekeys_e",
+ /* 3:31 */ "morekeys_u",
+ /* 4:31 */ "keylabel_to_alpha",
+ /* 5:30 */ "morekeys_i",
+ /* 6:25 */ "morekeys_n",
+ /* 7:25 */ "morekeys_c",
+ /* 8:23 */ "double_quotes",
+ /* 9:22 */ "morekeys_s",
+ /* 10:22 */ "single_quotes",
+ /* 11:19 */ "keyspec_currency",
+ /* 12:17 */ "morekeys_y",
+ /* 13:16 */ "morekeys_z",
+ /* 14:14 */ "morekeys_d",
/* 15:10 */ "morekeys_t",
/* 16:10 */ "morekeys_l",
- /* 17: 9 */ "morekeys_g",
+ /* 17:10 */ "morekeys_g",
/* 18: 9 */ "single_angle_quotes",
/* 19: 9 */ "double_angle_quotes",
/* 20: 8 */ "morekeys_r",
@@ -139,118 +139,126 @@ public final class KeyboardTextsTable {
/* 53: 4 */ "morekeys_nordic_row2_11",
/* 54: 4 */ "morekeys_punctuation",
/* 55: 4 */ "keyspec_tablet_comma",
- /* 56: 3 */ "keyspec_swiss_row1_11",
- /* 57: 3 */ "keyspec_swiss_row2_10",
- /* 58: 3 */ "keyspec_swiss_row2_11",
- /* 59: 3 */ "morekeys_swiss_row1_11",
- /* 60: 3 */ "morekeys_swiss_row2_10",
- /* 61: 3 */ "morekeys_swiss_row2_11",
- /* 62: 3 */ "morekeys_star",
- /* 63: 3 */ "keyspec_left_parenthesis",
- /* 64: 3 */ "keyspec_right_parenthesis",
- /* 65: 3 */ "keyspec_left_square_bracket",
- /* 66: 3 */ "keyspec_right_square_bracket",
- /* 67: 3 */ "keyspec_left_curly_bracket",
- /* 68: 3 */ "keyspec_right_curly_bracket",
- /* 69: 3 */ "keyspec_less_than",
- /* 70: 3 */ "keyspec_greater_than",
- /* 71: 3 */ "keyspec_less_than_equal",
- /* 72: 3 */ "keyspec_greater_than_equal",
- /* 73: 3 */ "keyspec_left_double_angle_quote",
- /* 74: 3 */ "keyspec_right_double_angle_quote",
- /* 75: 3 */ "keyspec_left_single_angle_quote",
- /* 76: 3 */ "keyspec_right_single_angle_quote",
- /* 77: 3 */ "keyspec_comma",
- /* 78: 3 */ "morekeys_tablet_comma",
- /* 79: 3 */ "keyhintlabel_period",
- /* 80: 3 */ "morekeys_tablet_period",
- /* 81: 3 */ "morekeys_question",
- /* 82: 2 */ "morekeys_h",
- /* 83: 2 */ "morekeys_w",
- /* 84: 2 */ "morekeys_east_slavic_row2_2",
- /* 85: 2 */ "morekeys_cyrillic_u",
- /* 86: 2 */ "morekeys_cyrillic_en",
- /* 87: 2 */ "morekeys_cyrillic_ghe",
- /* 88: 2 */ "morekeys_cyrillic_o",
- /* 89: 2 */ "morekeys_cyrillic_i",
- /* 90: 2 */ "keyspec_south_slavic_row1_6",
- /* 91: 2 */ "keyspec_south_slavic_row2_11",
- /* 92: 2 */ "keyspec_south_slavic_row3_1",
- /* 93: 2 */ "keyspec_south_slavic_row3_8",
- /* 94: 2 */ "morekeys_tablet_punctuation",
- /* 95: 2 */ "keyspec_spanish_row2_10",
- /* 96: 2 */ "morekeys_bullet",
- /* 97: 2 */ "morekeys_left_parenthesis",
- /* 98: 2 */ "morekeys_right_parenthesis",
- /* 99: 2 */ "morekeys_arabic_diacritics",
- /* 100: 2 */ "keyhintlabel_tablet_comma",
- /* 101: 2 */ "keyspec_period",
- /* 102: 2 */ "morekeys_period",
- /* 103: 2 */ "keyspec_tablet_period",
+ /* 56: 4 */ "morekeys_tablet_period",
+ /* 57: 3 */ "keyspec_swiss_row1_11",
+ /* 58: 3 */ "keyspec_swiss_row2_10",
+ /* 59: 3 */ "keyspec_swiss_row2_11",
+ /* 60: 3 */ "morekeys_swiss_row1_11",
+ /* 61: 3 */ "morekeys_swiss_row2_10",
+ /* 62: 3 */ "morekeys_swiss_row2_11",
+ /* 63: 3 */ "morekeys_star",
+ /* 64: 3 */ "keyspec_left_parenthesis",
+ /* 65: 3 */ "keyspec_right_parenthesis",
+ /* 66: 3 */ "keyspec_left_square_bracket",
+ /* 67: 3 */ "keyspec_right_square_bracket",
+ /* 68: 3 */ "keyspec_left_curly_bracket",
+ /* 69: 3 */ "keyspec_right_curly_bracket",
+ /* 70: 3 */ "keyspec_less_than",
+ /* 71: 3 */ "keyspec_greater_than",
+ /* 72: 3 */ "keyspec_less_than_equal",
+ /* 73: 3 */ "keyspec_greater_than_equal",
+ /* 74: 3 */ "keyspec_left_double_angle_quote",
+ /* 75: 3 */ "keyspec_right_double_angle_quote",
+ /* 76: 3 */ "keyspec_left_single_angle_quote",
+ /* 77: 3 */ "keyspec_right_single_angle_quote",
+ /* 78: 3 */ "keyspec_comma",
+ /* 79: 3 */ "morekeys_tablet_comma",
+ /* 80: 3 */ "keyspec_period",
+ /* 81: 3 */ "keyhintlabel_period",
+ /* 82: 3 */ "morekeys_period",
+ /* 83: 3 */ "keyspec_tablet_period",
+ /* 84: 3 */ "morekeys_question",
+ /* 85: 2 */ "morekeys_h",
+ /* 86: 2 */ "morekeys_w",
+ /* 87: 2 */ "morekeys_east_slavic_row2_2",
+ /* 88: 2 */ "morekeys_cyrillic_u",
+ /* 89: 2 */ "morekeys_cyrillic_en",
+ /* 90: 2 */ "morekeys_cyrillic_ghe",
+ /* 91: 2 */ "morekeys_cyrillic_o",
+ /* 92: 2 */ "morekeys_cyrillic_i",
+ /* 93: 2 */ "keyspec_south_slavic_row1_6",
+ /* 94: 2 */ "keyspec_south_slavic_row2_11",
+ /* 95: 2 */ "keyspec_south_slavic_row3_1",
+ /* 96: 2 */ "keyspec_south_slavic_row3_8",
+ /* 97: 2 */ "morekeys_tablet_punctuation",
+ /* 98: 2 */ "keyspec_spanish_row2_10",
+ /* 99: 2 */ "morekeys_bullet",
+ /* 100: 2 */ "morekeys_left_parenthesis",
+ /* 101: 2 */ "morekeys_right_parenthesis",
+ /* 102: 2 */ "morekeys_arabic_diacritics",
+ /* 103: 2 */ "keyhintlabel_tablet_comma",
/* 104: 2 */ "keyhintlabel_tablet_period",
/* 105: 2 */ "keyspec_symbols_question",
/* 106: 2 */ "keyspec_symbols_semicolon",
/* 107: 2 */ "keyspec_symbols_percent",
/* 108: 2 */ "morekeys_symbols_semicolon",
/* 109: 2 */ "morekeys_symbols_percent",
- /* 110: 1 */ "morekeys_v",
- /* 111: 1 */ "morekeys_j",
- /* 112: 1 */ "morekeys_q",
- /* 113: 1 */ "morekeys_x",
- /* 114: 1 */ "keyspec_q",
- /* 115: 1 */ "keyspec_w",
- /* 116: 1 */ "keyspec_y",
- /* 117: 1 */ "keyspec_x",
- /* 118: 1 */ "morekeys_east_slavic_row2_11",
- /* 119: 1 */ "morekeys_cyrillic_ka",
- /* 120: 1 */ "morekeys_cyrillic_a",
- /* 121: 1 */ "morekeys_currency_dollar",
- /* 122: 1 */ "morekeys_plus",
- /* 123: 1 */ "morekeys_less_than",
- /* 124: 1 */ "morekeys_greater_than",
- /* 125: 1 */ "morekeys_exclamation",
- /* 126: 0 */ "morekeys_currency_generic",
- /* 127: 0 */ "morekeys_symbols_1",
- /* 128: 0 */ "morekeys_symbols_2",
- /* 129: 0 */ "morekeys_symbols_3",
- /* 130: 0 */ "morekeys_symbols_4",
- /* 131: 0 */ "morekeys_symbols_5",
- /* 132: 0 */ "morekeys_symbols_6",
- /* 133: 0 */ "morekeys_symbols_7",
- /* 134: 0 */ "morekeys_symbols_8",
- /* 135: 0 */ "morekeys_symbols_9",
- /* 136: 0 */ "morekeys_symbols_0",
- /* 137: 0 */ "morekeys_am_pm",
- /* 138: 0 */ "keyspec_settings",
- /* 139: 0 */ "keyspec_shortcut",
- /* 140: 0 */ "keyspec_action_next",
- /* 141: 0 */ "keyspec_action_previous",
- /* 142: 0 */ "keylabel_to_more_symbol",
- /* 143: 0 */ "keylabel_tablet_to_more_symbol",
- /* 144: 0 */ "keylabel_to_phone_numeric",
- /* 145: 0 */ "keylabel_to_phone_symbols",
- /* 146: 0 */ "keylabel_time_am",
- /* 147: 0 */ "keylabel_time_pm",
- /* 148: 0 */ "keyspec_popular_domain",
- /* 149: 0 */ "morekeys_popular_domain",
- /* 150: 0 */ "keyspecs_left_parenthesis_more_keys",
- /* 151: 0 */ "keyspecs_right_parenthesis_more_keys",
- /* 152: 0 */ "single_laqm_raqm",
- /* 153: 0 */ "single_raqm_laqm",
- /* 154: 0 */ "double_laqm_raqm",
- /* 155: 0 */ "double_raqm_laqm",
- /* 156: 0 */ "single_lqm_rqm",
- /* 157: 0 */ "single_9qm_lqm",
- /* 158: 0 */ "single_9qm_rqm",
- /* 159: 0 */ "single_rqm_9qm",
- /* 160: 0 */ "double_lqm_rqm",
- /* 161: 0 */ "double_9qm_lqm",
- /* 162: 0 */ "double_9qm_rqm",
- /* 163: 0 */ "double_rqm_9qm",
- /* 164: 0 */ "morekeys_single_quote",
- /* 165: 0 */ "morekeys_double_quote",
- /* 166: 0 */ "morekeys_tablet_double_quote",
- /* 167: 0 */ "keyspec_emoji_action_key",
+ /* 110: 2 */ "label_go_key",
+ /* 111: 2 */ "label_send_key",
+ /* 112: 2 */ "label_next_key",
+ /* 113: 2 */ "label_done_key",
+ /* 114: 2 */ "label_search_key",
+ /* 115: 2 */ "label_previous_key",
+ /* 116: 2 */ "label_pause_key",
+ /* 117: 2 */ "label_wait_key",
+ /* 118: 1 */ "morekeys_v",
+ /* 119: 1 */ "morekeys_j",
+ /* 120: 1 */ "morekeys_q",
+ /* 121: 1 */ "morekeys_x",
+ /* 122: 1 */ "keyspec_q",
+ /* 123: 1 */ "keyspec_w",
+ /* 124: 1 */ "keyspec_y",
+ /* 125: 1 */ "keyspec_x",
+ /* 126: 1 */ "morekeys_east_slavic_row2_11",
+ /* 127: 1 */ "morekeys_cyrillic_ka",
+ /* 128: 1 */ "morekeys_cyrillic_a",
+ /* 129: 1 */ "morekeys_currency_dollar",
+ /* 130: 1 */ "morekeys_plus",
+ /* 131: 1 */ "morekeys_less_than",
+ /* 132: 1 */ "morekeys_greater_than",
+ /* 133: 1 */ "morekeys_exclamation",
+ /* 134: 0 */ "morekeys_currency_generic",
+ /* 135: 0 */ "morekeys_symbols_1",
+ /* 136: 0 */ "morekeys_symbols_2",
+ /* 137: 0 */ "morekeys_symbols_3",
+ /* 138: 0 */ "morekeys_symbols_4",
+ /* 139: 0 */ "morekeys_symbols_5",
+ /* 140: 0 */ "morekeys_symbols_6",
+ /* 141: 0 */ "morekeys_symbols_7",
+ /* 142: 0 */ "morekeys_symbols_8",
+ /* 143: 0 */ "morekeys_symbols_9",
+ /* 144: 0 */ "morekeys_symbols_0",
+ /* 145: 0 */ "morekeys_am_pm",
+ /* 146: 0 */ "keyspec_settings",
+ /* 147: 0 */ "keyspec_shortcut",
+ /* 148: 0 */ "keyspec_action_next",
+ /* 149: 0 */ "keyspec_action_previous",
+ /* 150: 0 */ "keylabel_to_more_symbol",
+ /* 151: 0 */ "keylabel_tablet_to_more_symbol",
+ /* 152: 0 */ "keylabel_to_phone_numeric",
+ /* 153: 0 */ "keylabel_to_phone_symbols",
+ /* 154: 0 */ "keylabel_time_am",
+ /* 155: 0 */ "keylabel_time_pm",
+ /* 156: 0 */ "keyspec_popular_domain",
+ /* 157: 0 */ "morekeys_popular_domain",
+ /* 158: 0 */ "keyspecs_left_parenthesis_more_keys",
+ /* 159: 0 */ "keyspecs_right_parenthesis_more_keys",
+ /* 160: 0 */ "single_laqm_raqm",
+ /* 161: 0 */ "single_raqm_laqm",
+ /* 162: 0 */ "double_laqm_raqm",
+ /* 163: 0 */ "double_raqm_laqm",
+ /* 164: 0 */ "single_lqm_rqm",
+ /* 165: 0 */ "single_9qm_lqm",
+ /* 166: 0 */ "single_9qm_rqm",
+ /* 167: 0 */ "single_rqm_9qm",
+ /* 168: 0 */ "double_lqm_rqm",
+ /* 169: 0 */ "double_9qm_lqm",
+ /* 170: 0 */ "double_9qm_rqm",
+ /* 171: 0 */ "double_rqm_9qm",
+ /* 172: 0 */ "morekeys_single_quote",
+ /* 173: 0 */ "morekeys_double_quote",
+ /* 174: 0 */ "morekeys_tablet_double_quote",
+ /* 175: 0 */ "keyspec_emoji_action_key",
};
private static final String EMPTY = "";
@@ -258,17 +266,16 @@ public final class KeyboardTextsTable {
/* Default texts */
private static final String[] TEXTS_DEFAULT = {
/* morekeys_a ~ */
- EMPTY, EMPTY, EMPTY,
+ EMPTY, EMPTY, EMPTY, EMPTY,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
/* keylabel_to_alpha */ "ABC",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
EMPTY, EMPTY, EMPTY,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_lqm_rqm",
- /* morekeys_n */ EMPTY,
- /* single_quotes */ "!text/single_lqm_rqm",
/* morekeys_s */ EMPTY,
+ /* single_quotes */ "!text/single_lqm_rqm",
/* keyspec_currency */ "$",
/* morekeys_y ~ */
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
@@ -295,6 +302,7 @@ public final class KeyboardTextsTable {
/* ~ morekeys_nordic_row2_11 */
/* morekeys_punctuation */ "!autoColumnOrder!8,\\,,?,!,#,!text/keyspec_right_parenthesis,!text/keyspec_left_parenthesis,/,;,',@,:,-,\",+,\\%,&",
/* keyspec_tablet_comma */ ",",
+ /* morekeys_tablet_period */ "!text/morekeys_tablet_punctuation",
/* keyspec_swiss_row1_11 ~ */
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
/* ~ morekeys_swiss_row2_11 */
@@ -327,8 +335,11 @@ public final class KeyboardTextsTable {
// Comma key
/* keyspec_comma */ ",",
/* morekeys_tablet_comma */ EMPTY,
+ // Period key
+ /* keyspec_period */ ".",
/* keyhintlabel_period */ EMPTY,
- /* morekeys_tablet_period */ "!text/morekeys_tablet_punctuation",
+ /* morekeys_period */ "!text/morekeys_punctuation",
+ /* keyspec_tablet_period */ ".",
// U+00BF: "¿" INVERTED QUESTION MARK
/* morekeys_question */ "\u00BF",
/* morekeys_h ~ */
@@ -345,19 +356,23 @@ public final class KeyboardTextsTable {
/* morekeys_bullet */ "\u266A,\u2665,\u2660,\u2666,\u2663",
/* morekeys_left_parenthesis */ "!fixedColumnOrder!3,!text/keyspecs_left_parenthesis_more_keys",
/* morekeys_right_parenthesis */ "!fixedColumnOrder!3,!text/keyspecs_right_parenthesis_more_keys",
- /* morekeys_arabic_diacritics */ EMPTY,
- /* keyhintlabel_tablet_comma */ EMPTY,
- // Period key
- /* keyspec_period */ ".",
- /* morekeys_period */ "!text/morekeys_punctuation",
- /* keyspec_tablet_period */ ".",
- /* keyhintlabel_tablet_period */ EMPTY,
+ /* morekeys_arabic_diacritics ~ */
+ EMPTY, EMPTY, EMPTY,
+ /* ~ keyhintlabel_tablet_period */
/* keyspec_symbols_question */ "?",
/* keyspec_symbols_semicolon */ ";",
/* keyspec_symbols_percent */ "%",
/* morekeys_symbols_semicolon */ EMPTY,
// U+2030: "‰" PER MILLE SIGN
/* morekeys_symbols_percent */ "\u2030",
+ /* label_go_key */ "!string/label_go_key",
+ /* label_send_key */ "!string/label_send_key",
+ /* label_next_key */ "!string/label_next_key",
+ /* label_done_key */ "!string/label_done_key",
+ /* label_search_key */ "!string/label_search_key",
+ /* label_previous_key */ "!string/label_previous_key",
+ /* label_pause_key */ "!string/label_pause_key",
+ /* label_wait_key */ "!string/label_wait_key",
/* morekeys_v ~ */
EMPTY, EMPTY, EMPTY, EMPTY,
/* ~ morekeys_x */
@@ -488,13 +503,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -503,6 +511,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -511,13 +526,11 @@ public final class KeyboardTextsTable {
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_i */ "\u00ED,\u00EC,\u00EF,\u00EE,\u012F,\u012B,\u0133",
- /* morekeys_c */ null,
- /* double_quotes */ null,
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
/* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes ~ */
- null, null, null,
+ /* morekeys_c ~ */
+ null, null, null, null, null,
/* ~ keyspec_currency */
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+0133: "ij" LATIN SMALL LIGATURE IJ
@@ -527,7 +540,7 @@ public final class KeyboardTextsTable {
/* Locale ar: Arabic */
private static final String[] TEXTS_ar = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
@@ -535,9 +548,9 @@ public final class KeyboardTextsTable {
// U+0628: "ب" ARABIC LETTER BEH
// U+062C: "ج" ARABIC LETTER JEEM
/* keylabel_to_alpha */ "\u0623\u200C\u0628\u200C\u062C",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_cyrillic_soft_sign */
// U+0661: "١" ARABIC-INDIC DIGIT ONE
/* keyspec_symbols_1 */ "\u0661",
@@ -580,6 +593,7 @@ public final class KeyboardTextsTable {
// U+060C: "،" ARABIC COMMA
// U+061B: "؛" ARABIC SEMICOLON
/* keyspec_tablet_comma */ "\u060C",
+ /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics",
/* keyspec_swiss_row1_11 ~ */
null, null, null, null, null, null,
/* ~ morekeys_swiss_row2_11 */
@@ -609,9 +623,11 @@ public final class KeyboardTextsTable {
// U+060C: "،" ARABIC COMMA
/* keyspec_comma */ "\u060C",
/* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,\",\'",
+ /* keyspec_period */ null,
// U+0651: "ّ" ARABIC SHADDA
/* keyhintlabel_period */ "\u0651",
- /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics",
+ /* morekeys_period */ "!text/morekeys_arabic_diacritics",
+ /* keyspec_tablet_period */ null,
// U+00BF: "¿" INVERTED QUESTION MARK
/* morekeys_question */ "?,\u00BF",
/* morekeys_h ~ */
@@ -643,9 +659,6 @@ public final class KeyboardTextsTable {
// Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
/* morekeys_arabic_diacritics */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
/* keyhintlabel_tablet_comma */ "\u061F",
- /* keyspec_period */ null,
- /* morekeys_period */ "!text/morekeys_arabic_diacritics",
- /* keyspec_tablet_period */ null,
/* keyhintlabel_tablet_period */ "\u0651",
/* keyspec_symbols_question */ "\u061F",
/* keyspec_symbols_semicolon */ "\u061B",
@@ -658,8 +671,11 @@ public final class KeyboardTextsTable {
/* Locale az_AZ: Azerbaijani (Azerbaijan) */
private static final String[] TEXTS_az_AZ = {
+ // This is the same as Turkish
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- /* morekeys_a */ "\u00E2",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ /* morekeys_a */ "\u00E2,\u00E4,\u00E1",
// U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+0153: "œ" LATIN SMALL LIGATURE OE
@@ -669,6 +685,9 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D",
+ // U+0259: "ə" LATIN SMALL LETTER SCHWA
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ /* morekeys_e */ "\u0259,\u00E9",
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -676,8 +695,6 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
/* keylabel_to_alpha */ null,
- // U+0259: "ə" LATIN SMALL LETTER SCHWA
- /* morekeys_e */ "\u0259",
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -686,20 +703,27 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u0148,\u00F1",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes ~ */
- null, null, null,
- /* ~ single_quotes */
+ /* double_quotes */ null,
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u015F,\u00DF,\u015B,\u0161",
- /* keyspec_currency ~ */
- null, null, null, null, null, null,
+ /* single_quotes */ null,
+ /* keyspec_currency */ null,
+ // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+ /* morekeys_y */ "\u00FD",
+ // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+ /* morekeys_z */ "\u017E",
+ /* morekeys_d ~ */
+ null, null, null,
/* ~ morekeys_l */
// U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
/* morekeys_g */ "\u011F",
@@ -708,21 +732,21 @@ public final class KeyboardTextsTable {
/* Locale be_BY: Belarusian (Belarus) */
private static final String[] TEXTS_be_BY = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_lqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_k */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* morekeys_cyrillic_ie */ "\u0451",
@@ -744,33 +768,50 @@ public final class KeyboardTextsTable {
/* Locale bg: Bulgarian */
private static final String[] TEXTS_bg = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
// single_quotes of Bulgarian is default single_quotes_right_left.
/* double_quotes */ "!text/double_9qm_lqm",
};
+ /* Locale bn_BD: Bengali (Bangladesh) */
+ private static final String[] TEXTS_bn_BD = {
+ /* morekeys_a ~ */
+ null, null, null, null,
+ /* ~ morekeys_u */
+ // Label for "switch to alphabetic" key.
+ // U+0995: "क" BENGALI LETTER KA
+ // U+0996: "ख" BENGALI LETTER KHA
+ // U+0997: "ग" BENGALI LETTER GA
+ /* keylabel_to_alpha */ "\u0995\u0996\u0997",
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // U+09F3: "৳" BENGALI RUPEE SIGN
+ /* keyspec_currency */ "\u09F3",
+ };
+
/* Locale bn_IN: Bengali (India) */
private static final String[] TEXTS_bn_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0995: "क" BENGALI LETTER KA
// U+0996: "ख" BENGALI LETTER KHA
// U+0997: "ग" BENGALI LETTER GA
/* keylabel_to_alpha */ "\u0995\u0996\u0997",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
};
@@ -798,13 +839,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F2,\u00F3,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
@@ -813,6 +847,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E8,\u00E9,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -820,16 +861,15 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes ~ */
- null, null, null, null, null, null, null,
+ /* double_quotes ~ */
+ null, null, null, null, null, null, null, null,
/* ~ morekeys_t */
// U+00B7: "·" MIDDLE DOT
// U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
@@ -844,7 +884,7 @@ public final class KeyboardTextsTable {
/* keyspec_tablet_comma ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null,
/* ~ keyspec_south_slavic_row3_8 */
/* morekeys_tablet_punctuation */ "!autoColumnOrder!8,\\,,',\u00B7,#,),(,/,;,@,:,-,\",+,\\%,&",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
@@ -871,14 +911,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+011B: "ě" LATIN SMALL LETTER E WITH CARON
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
@@ -888,6 +920,14 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u011B,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -895,30 +935,30 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u00EC,\u012F,\u012B",
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u0148,\u00F1,\u0144",
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
/* morekeys_c */ "\u010D,\u00E7,\u0107",
/* double_quotes */ "!text/double_9qm_lqm",
- // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u0148,\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
/* morekeys_s */ "\u0161,\u00DF,\u015B",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u010F",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
/* morekeys_z */ "\u017E,\u017A,\u017C",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u010F",
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
/* morekeys_t */ "\u0165",
/* morekeys_l */ null,
@@ -931,20 +971,27 @@ public final class KeyboardTextsTable {
/* Locale da: Danish */
private static final String[] TEXTS_da = {
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
// U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
// U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
// U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
// U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* morekeys_a */ "\u00E1,\u00E4,\u00E0,\u00E2,\u00E3,\u0101",
+ /* morekeys_a */ "\u00E5,\u00E6,\u00E1,\u00E4,\u00E0,\u00E2,\u00E3,\u0101",
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
// U+0153: "œ" LATIN SMALL LIGATURE OE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* morekeys_o */ "\u00F3,\u00F4,\u00F2,\u00F5,\u0153,\u014D",
+ /* morekeys_o */ "\u00F8,\u00F6,\u00F3,\u00F4,\u00F2,\u00F5,\u0153,\u014D",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ /* morekeys_e */ "\u00E9,\u00EB",
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
@@ -952,29 +999,26 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- /* morekeys_e */ "\u00E9,\u00EB",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
/* morekeys_i */ "\u00ED,\u00EF",
- /* morekeys_c */ null,
- /* double_quotes */ "!text/double_9qm_lqm",
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
/* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
+ /* morekeys_c */ null,
+ /* double_quotes */ "!text/double_9qm_lqm",
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u00DF,\u015B,\u0161",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
+ /* morekeys_z */ null,
// U+00F0: "ð" LATIN SMALL LETTER ETH
/* morekeys_d */ "\u00F0",
- /* morekeys_z */ null,
/* morekeys_t */ null,
// U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
/* morekeys_l */ "\u0142",
@@ -1020,6 +1064,12 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F6,%,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u00F8,\u014D",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0117",
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -1027,23 +1077,17 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FC,%,\u00FB,\u00F9,\u00FA,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0117",
/* morekeys_i */ null,
- /* morekeys_c */ null,
- /* double_quotes */ "!text/double_9qm_lqm",
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
/* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
+ /* morekeys_c */ null,
+ /* double_quotes */ "!text/double_9qm_lqm",
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u00DF,\u015B,\u0161",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency ~ */
null, null, null, null, null, null, null,
/* ~ morekeys_g */
@@ -1052,8 +1096,8 @@ public final class KeyboardTextsTable {
/* morekeys_r ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
- /* ~ keyspec_tablet_comma */
+ null, null, null, null, null, null, null,
+ /* ~ morekeys_tablet_period */
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
/* keyspec_swiss_row1_11 */ "\u00FC",
// U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
@@ -1071,7 +1115,7 @@ public final class KeyboardTextsTable {
/* Locale el: Greek */
private static final String[] TEXTS_el = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0391: "Α" GREEK CAPITAL LETTER ALPHA
@@ -1100,6 +1144,12 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
/* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u0153,\u00F8,\u014D,\u00F5",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113",
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
@@ -1107,24 +1157,17 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
/* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u012B,\u00EC",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u00F1",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
/* morekeys_c */ "\u00E7",
/* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* morekeys_n */ "\u00F1",
- /* single_quotes */ null,
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
/* morekeys_s */ "\u00DF",
};
@@ -1154,6 +1197,15 @@ public final class KeyboardTextsTable {
// U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D,\u0151,\u00BA",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ /* morekeys_e */ "\u00E9,\u011B,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
@@ -1166,15 +1218,6 @@ public final class KeyboardTextsTable {
// U+00B5: "µ" MICRO SIGN
/* morekeys_u */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B,\u0169,\u0171,\u0173,\u00B5",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* morekeys_e */ "\u00E9,\u011B,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -1185,12 +1228,6 @@ public final class KeyboardTextsTable {
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u0129,\u00EC,\u012F,\u012B,\u0131,\u0133",
- // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
- // U+010D: "č" LATIN SMALL LETTER C WITH CARON
- // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE
- /* morekeys_c */ "\u0107,\u010D,\u00E7,\u010B",
- /* double_quotes */ null,
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
// U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
@@ -1198,27 +1235,33 @@ public final class KeyboardTextsTable {
// U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
// U+014B: "ŋ" LATIN SMALL LETTER ENG
/* morekeys_n */ "\u00F1,\u0144,\u0146,\u0148,\u0149,\u014B",
- /* single_quotes */ null,
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE
+ /* morekeys_c */ "\u0107,\u010D,\u00E7,\u010B",
+ /* double_quotes */ null,
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
/* morekeys_s */ "\u00DF,\u0161,\u015B,\u0219,\u015F",
+ /* single_quotes */ null,
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
// U+00FE: "þ" LATIN SMALL LETTER THORN
/* morekeys_y */ "y,\u00FD,\u0177,\u00FF,\u00FE",
- // U+00F0: "ð" LATIN SMALL LETTER ETH
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
- /* morekeys_d */ "\u00F0,\u010F,\u0111",
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
/* morekeys_z */ "\u017A,\u017C,\u017E",
+ // U+00F0: "ð" LATIN SMALL LETTER ETH
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ /* morekeys_d */ "\u00F0,\u010F,\u0111",
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
// U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
@@ -1248,6 +1291,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null,
/* ~ morekeys_question */
// U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX
// U+0127: "ħ" LATIN SMALL LETTER H WITH STROKE
@@ -1260,8 +1304,9 @@ public final class KeyboardTextsTable {
// U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
/* keyspec_spanish_row2_10 */ "\u0135",
/* morekeys_bullet ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~ morekeys_symbols_percent */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null,
+ /* ~ label_wait_key */
// U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
/* morekeys_v */ "w,\u0175",
/* morekeys_j */ null,
@@ -1300,13 +1345,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
@@ -1315,6 +1353,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -1322,18 +1367,18 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes ~ */
+ /* double_quotes ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null,
/* ~ morekeys_nordic_row2_11 */
// U+00A1: "¡" INVERTED EXCLAMATION MARK
// U+00BF: "¿" INVERTED QUESTION MARK
@@ -1361,6 +1406,15 @@ public final class KeyboardTextsTable {
// U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
/* morekeys_o */ "\u00F6,\u00F5,\u00F2,\u00F3,\u00F4,\u0153,\u0151,\u00F8",
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ /* morekeys_e */ "\u0113,\u00E8,\u0117,\u00E9,\u00EA,\u00EB,\u0119,\u011B",
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
// U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
@@ -1371,15 +1425,6 @@ public final class KeyboardTextsTable {
// U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
/* morekeys_u */ "\u00FC,\u016B,\u0173,\u00F9,\u00FA,\u00FB,\u016F,\u0171",
/* keylabel_to_alpha */ null,
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- /* morekeys_e */ "\u0113,\u00E8,\u0117,\u00E9,\u00EA,\u00EB,\u0119,\u011B",
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
@@ -1388,31 +1433,31 @@ public final class KeyboardTextsTable {
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
/* morekeys_i */ "\u012B,\u00EC,\u012F,\u00ED,\u00EE,\u00EF,\u0131",
+ // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u0146,\u00F1,\u0144",
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
/* morekeys_c */ "\u010D,\u00E7,\u0107",
/* double_quotes */ "!text/double_9qm_lqm",
- // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u0146,\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
/* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u010F",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
/* morekeys_z */ "\u017E,\u017C,\u017A",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u010F",
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
/* morekeys_t */ "\u0163,\u0165",
@@ -1466,13 +1511,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
@@ -1481,6 +1519,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -1488,20 +1533,19 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
};
/* Locale fa: Persian */
private static final String[] TEXTS_fa = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0627: "ا" ARABIC LETTER ALEF
@@ -1509,9 +1553,9 @@ public final class KeyboardTextsTable {
// U+0628: "ب" ARABIC LETTER BEH
// U+067E: "پ" ARABIC LETTER PEH
/* keylabel_to_alpha */ "\u0627\u200C\u0628\u200C\u067E",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+FDFC: "﷼" RIAL SIGN
/* keyspec_currency */ "\uFDFC",
/* morekeys_y ~ */
@@ -1561,6 +1605,7 @@ public final class KeyboardTextsTable {
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
/* keyspec_tablet_comma */ "\u060C",
+ /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics",
/* keyspec_swiss_row1_11 ~ */
null, null, null, null, null, null,
/* ~ morekeys_swiss_row2_11 */
@@ -1584,9 +1629,11 @@ public final class KeyboardTextsTable {
// U+060C: "،" ARABIC COMMA
/* keyspec_comma */ "\u060C",
/* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,!text/keyspec_left_double_angle_quote,!text/keyspec_right_double_angle_quote",
+ /* keyspec_period */ null,
// U+064B: "ً" ARABIC FATHATAN
/* keyhintlabel_period */ "\u064B",
- /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics",
+ /* morekeys_period */ "!text/morekeys_arabic_diacritics",
+ /* keyspec_tablet_period */ null,
// U+00BF: "¿" INVERTED QUESTION MARK
/* morekeys_question */ "?,\u00BF",
/* morekeys_h ~ */
@@ -1618,9 +1665,6 @@ public final class KeyboardTextsTable {
// Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
/* morekeys_arabic_diacritics */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
/* keyhintlabel_tablet_comma */ "\u061F",
- /* keyspec_period */ null,
- /* morekeys_period */ "!text/morekeys_arabic_diacritics",
- /* keyspec_tablet_period */ null,
/* keyhintlabel_tablet_period */ "\u064B",
/* keyspec_symbols_question */ "\u061F",
/* keyspec_symbols_semicolon */ "\u061B",
@@ -1629,8 +1673,9 @@ public final class KeyboardTextsTable {
/* morekeys_symbols_semicolon */ ";",
// U+2030: "‰" PER MILLE SIGN
/* morekeys_symbols_percent */ "\\%,\u2030",
- /* morekeys_v ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* label_go_key ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null,
/* ~ morekeys_plus */
// U+2264: "≤" LESS-THAN OR EQUAL TO
// U+2265: "≥" GREATER-THAN EQUAL TO
@@ -1644,13 +1689,16 @@ public final class KeyboardTextsTable {
/* Locale fi: Finnish */
private static final String[] TEXTS_fi = {
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
// U+00E6: "æ" LATIN SMALL LETTER AE
// U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
// U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
// U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* morekeys_a */ "\u00E6,\u00E0,\u00E1,\u00E2,\u00E3,\u0101",
+ /* morekeys_a */ "\u00E4,\u00E5,\u00E6,\u00E0,\u00E1,\u00E2,\u00E3,\u0101",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
@@ -1658,25 +1706,26 @@ public final class KeyboardTextsTable {
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
// U+0153: "œ" LATIN SMALL LIGATURE OE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* morekeys_o */ "\u00F8,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u014D",
+ /* morekeys_o */ "\u00F6,\u00F8,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u014D",
+ /* morekeys_e */ null,
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
/* morekeys_u */ "\u00FC",
/* keylabel_to_alpha ~ */
- null, null, null, null, null, null, null,
- /* ~ single_quotes */
+ null, null, null, null, null,
+ /* ~ double_quotes */
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
/* morekeys_s */ "\u0161,\u00DF,\u015B",
- /* keyspec_currency ~ */
+ /* single_quotes ~ */
null, null, null,
- /* ~ morekeys_d */
+ /* ~ morekeys_y */
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
/* morekeys_z */ "\u017E,\u017A,\u017C",
- /* morekeys_t ~ */
- null, null, null, null, null, null, null, null,
+ /* morekeys_d ~ */
+ null, null, null, null, null, null, null, null, null,
/* ~ morekeys_cyrillic_ie */
// U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
/* keyspec_nordic_row1_11 */ "\u00E5",
@@ -1716,13 +1765,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F4,\u0153,%,\u00F6,\u00F2,\u00F3,\u00F5,\u00F8,\u014D,\u00BA",
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00F9,\u00FB,%,\u00FC,\u00FA,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -1731,6 +1773,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,%,\u0119,\u0117,\u0113",
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00F9,\u00FB,%,\u00FC,\u00FA,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -1738,20 +1787,21 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00EE,%,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ /* morekeys_n */ null,
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,%,\u0107,\u010D",
/* double_quotes ~ */
- null, null, null, null, null,
+ null, null, null, null,
/* ~ keyspec_currency */
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "%,\u00FF",
- /* morekeys_d ~ */
+ /* morekeys_z ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~ keyspec_tablet_comma */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_tablet_period */
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
/* keyspec_swiss_row1_11 */ "\u00E8",
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
@@ -1789,13 +1839,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
@@ -1804,6 +1847,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -1811,29 +1861,28 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
};
/* Locale hi: Hindi */
private static final String[] TEXTS_hi = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0915: "क" DEVANAGARI LETTER KA
// U+0916: "ख" DEVANAGARI LETTER KHA
// U+0917: "ग" DEVANAGARI LETTER GA
/* keylabel_to_alpha */ "\u0915\u0916\u0917",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
/* morekeys_y ~ */
@@ -1872,6 +1921,45 @@ public final class KeyboardTextsTable {
/* additional_morekeys_symbols_8 */ "8",
/* additional_morekeys_symbols_9 */ "9",
/* additional_morekeys_symbols_0 */ "0",
+ /* morekeys_nordic_row2_11 ~ */
+ null, null, null,
+ /* ~ keyspec_tablet_comma */
+ /* morekeys_tablet_period */ "!autoColumnOrder!8,\\,,.,',#,),(,/,;,@,:,-,\",+,\\%,&",
+ /* keyspec_swiss_row1_11 ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null,
+ /* ~ morekeys_tablet_comma */
+ // U+0964: "।" DEVANAGARI DANDA
+ /* keyspec_period */ "\u0964",
+ /* keyhintlabel_period */ null,
+ /* morekeys_period */ "!autoColumnOrder!9,\\,,.,?,!,#,),(,/,;,',@,:,-,\",+,\\%,&",
+ /* keyspec_tablet_period */ "\u0964",
+ };
+
+ /* Locale hi_ZZ: Hindi (ZZ) */
+ private static final String[] TEXTS_hi_ZZ = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // U+20B9: "₹" INDIAN RUPEE SIGN
+ /* keyspec_currency */ "\u20B9",
+ /* morekeys_y ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null,
+ /* ~ morekeys_symbols_percent */
+ /* label_go_key */ "Go",
+ /* label_send_key */ "Send",
+ /* label_next_key */ "Next",
+ /* label_done_key */ "Done",
+ /* label_search_key */ "Search",
+ /* label_previous_key */ "Prev",
+ /* label_pause_key */ "Pause",
+ /* label_wait_key */ "Wait",
};
/* Locale hr: Croatian */
@@ -1879,27 +1967,27 @@ public final class KeyboardTextsTable {
/* morekeys_a ~ */
null, null, null, null, null, null,
/* ~ morekeys_i */
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
/* morekeys_c */ "\u010D,\u0107,\u00E7",
/* double_quotes */ "!text/double_9qm_rqm",
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_rqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
/* morekeys_s */ "\u0161,\u015B,\u00DF",
+ /* single_quotes */ "!text/single_9qm_rqm",
/* keyspec_currency */ null,
/* morekeys_y */ null,
- // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
- /* morekeys_d */ "\u0111",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
/* morekeys_z */ "\u017E,\u017A,\u017C",
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ /* morekeys_d */ "\u0111",
/* morekeys_t ~ */
null, null, null,
/* ~ morekeys_g */
@@ -1928,14 +2016,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F6,\u0151,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u0171,\u00FB,\u00F9,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -1944,6 +2024,14 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u0171,\u00FB,\u00F9,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -1951,12 +2039,13 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u00EC,\u012F,\u012B",
+ /* morekeys_n */ null,
/* morekeys_c */ null,
/* double_quotes */ "!text/double_9qm_rqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_rqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null,
/* ~ morekeys_g */
/* single_angle_quotes */ "!text/single_raqm_laqm",
/* double_angle_quotes */ "!text/double_raqm_laqm",
@@ -1965,18 +2054,18 @@ public final class KeyboardTextsTable {
/* Locale hy_AM: Armenian (Armenia) */
private static final String[] TEXTS_hy_AM = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0531: "Ա" ARMENIAN CAPITAL LETTER AYB
// U+0532: "Բ" ARMENIAN CAPITAL LETTER BEN
// U+0533: "Գ" ARMENIAN CAPITAL LETTER GIM
/* keylabel_to_alpha */ "\u0531\u0532\u0533",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
+ null, null, null, null,
/* ~ morekeys_nordic_row2_11 */
// U+055E: "՞" ARMENIAN QUESTION MARK
// U+055C: "՜" ARMENIAN EXCLAMATION MARK
@@ -1990,6 +2079,7 @@ public final class KeyboardTextsTable {
// U+055F: "՟" ARMENIAN ABBREVIATION MARK
/* morekeys_punctuation */ "!autoColumnOrder!8,\\,,\u055E,\u055C,.,\u055A,\u0559,?,!,\u055D,\u055B,\u058A,\u00BB,\u00AB,\u055F,;,:",
/* keyspec_tablet_comma */ "\u055D",
+ /* morekeys_tablet_period */ "!text/morekeys_punctuation",
/* keyspec_swiss_row1_11 ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null,
@@ -2001,22 +2091,19 @@ public final class KeyboardTextsTable {
// U+055D: "՝" ARMENIAN COMMA
/* keyspec_comma */ "\u055D",
/* morekeys_tablet_comma */ null,
+ // U+0589: "։" ARMENIAN FULL STOP
+ /* keyspec_period */ "\u0589",
/* keyhintlabel_period */ null,
- /* morekeys_tablet_period */ "!text/morekeys_punctuation",
+ /* morekeys_period */ null,
+ /* keyspec_tablet_period */ "\u0589",
// U+055E: "՞" ARMENIAN QUESTION MARK
// U+00BF: "¿" INVERTED QUESTION MARK
/* morekeys_question */ "\u055E,\u00BF",
/* morekeys_h ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null,
- /* ~ keyhintlabel_tablet_comma */
- // U+0589: "։" ARMENIAN FULL STOP
- /* keyspec_period */ "\u0589",
- /* morekeys_period */ null,
- /* keyspec_tablet_period */ "\u0589",
- /* keyhintlabel_tablet_period ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null,
/* ~ morekeys_greater_than */
// U+055C: "՜" ARMENIAN EXCLAMATION MARK
// U+00A1: "¡" INVERTED EXCLAMATION MARK
@@ -2043,13 +2130,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
@@ -2058,6 +2138,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00EB,\u00E8,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
@@ -2065,18 +2152,18 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EE,\u00EC,\u012F,\u012B",
+ /* morekeys_n */ null,
/* morekeys_c */ null,
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
- /* single_quotes */ "!text/single_9qm_lqm",
/* morekeys_s */ null,
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
+ /* morekeys_z */ null,
// U+00F0: "ð" LATIN SMALL LETTER ETH
/* morekeys_d */ "\u00F0",
- /* morekeys_z */ null,
// U+00FE: "þ" LATIN SMALL LETTER THORN
/* morekeys_t */ "\u00FE",
};
@@ -2103,13 +2190,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F2,\u00F3,\u00F4,\u00F6,\u00F5,\u0153,\u00F8,\u014D,\u00BA",
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00F9,\u00FA,\u00FB,\u00FC,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -2118,6 +2198,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0119,\u0117,\u0113",
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00F9,\u00FA,\u00FB,\u00FC,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
@@ -2125,12 +2212,12 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00EC,\u00ED,\u00EE,\u00EF,\u012F,\u012B",
- /* morekeys_c ~ */
+ /* morekeys_n ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
- /* ~ keyspec_tablet_comma */
+ null, null, null, null, null, null,
+ /* ~ morekeys_tablet_period */
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
/* keyspec_swiss_row1_11 */ "\u00FC",
// U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
@@ -2148,27 +2235,26 @@ public final class KeyboardTextsTable {
/* Locale iw: Hebrew */
private static final String[] TEXTS_iw = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+05D0: "א" HEBREW LETTER ALEF
// U+05D1: "ב" HEBREW LETTER BET
// U+05D2: "ג" HEBREW LETTER GIMEL
/* keylabel_to_alpha */ "\u05D0\u05D1\u05D2",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_rqm_9qm",
- /* morekeys_n */ null,
- /* single_quotes */ "!text/single_rqm_9qm",
/* morekeys_s */ null,
+ /* single_quotes */ "!text/single_rqm_9qm",
// U+20AA: "₪" NEW SHEQEL SIGN
/* keyspec_currency */ "\u20AA",
/* morekeys_y ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
+ null, null, null, null, null, null,
/* ~ morekeys_swiss_row2_11 */
// U+2605: "★" BLACK STAR
/* morekeys_star */ "\u2605",
@@ -2198,6 +2284,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null,
/* ~ morekeys_currency_dollar */
// U+00B1: "±" PLUS-MINUS SIGN
// U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
@@ -2207,34 +2294,34 @@ public final class KeyboardTextsTable {
/* Locale ka_GE: Georgian (Georgia) */
private static final String[] TEXTS_ka_GE = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+10D0: "ა" GEORGIAN LETTER AN
// U+10D1: "ბ" GEORGIAN LETTER BAN
// U+10D2: "გ" GEORGIAN LETTER GAN
/* keylabel_to_alpha */ "\u10D0\u10D1\u10D2",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_lqm",
};
/* Locale kk: Kazakh */
private static final String[] TEXTS_kk = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
+ null, null,
/* ~ morekeys_k */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* morekeys_cyrillic_ie */ "\u0451",
@@ -2255,7 +2342,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_w */
// U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
/* morekeys_east_slavic_row2_2 */ "\u0456",
@@ -2270,7 +2357,8 @@ public final class KeyboardTextsTable {
/* morekeys_cyrillic_o */ "\u04E9",
/* morekeys_cyrillic_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null,
/* ~ keyspec_x */
// U+04BB: "һ" CYRILLIC SMALL LETTER SHHA
/* morekeys_east_slavic_row2_11 */ "\u04BB",
@@ -2283,14 +2371,14 @@ public final class KeyboardTextsTable {
/* Locale km_KH: Khmer (Cambodia) */
private static final String[] TEXTS_km_KH = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+1780: "ក" KHMER LETTER KA
// U+1781: "ខ" KHMER LETTER KHA
// U+1782: "គ" KHMER LETTER KO
/* keylabel_to_alpha */ "\u1780\u1781\u1782",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
@@ -2298,7 +2386,8 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null,
/* ~ morekeys_cyrillic_a */
// U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL
/* morekeys_currency_dollar */ "\u17DB,\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
@@ -2307,16 +2396,16 @@ public final class KeyboardTextsTable {
/* Locale kn_IN: Kannada (India) */
private static final String[] TEXTS_kn_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0C85: "ಅ" KANNADA LETTER A
// U+0C86: "ಆ" KANNADA LETTER AA
// U+0C87: "ಇ" KANNADA LETTER I
/* keylabel_to_alpha */ "\u0C85\u0C86\u0C87",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
};
@@ -2324,16 +2413,16 @@ public final class KeyboardTextsTable {
/* Locale ky: Kirghiz */
private static final String[] TEXTS_ky = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
+ null, null,
/* ~ morekeys_k */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* morekeys_cyrillic_ie */ "\u0451",
@@ -2354,7 +2443,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_east_slavic_row2_2 */
// U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
/* morekeys_cyrillic_u */ "\u04AF",
@@ -2368,16 +2457,16 @@ public final class KeyboardTextsTable {
/* Locale lo_LA: Lao (Laos) */
private static final String[] TEXTS_lo_LA = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0E81: "ກ" LAO LETTER KO
// U+0E82: "ຂ" LAO LETTER KHO SUNG
// U+0E84: "ຄ" LAO LETTER KHO TAM
/* keylabel_to_alpha */ "\u0E81\u0E82\u0E84",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20AD: "₭" KIP SIGN
/* keyspec_currency */ "\u20AD",
};
@@ -2403,6 +2492,15 @@ public final class KeyboardTextsTable {
// U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
/* morekeys_o */ "\u00F6,\u00F5,\u00F2,\u00F3,\u00F4,\u0153,\u0151,\u00F8",
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ /* morekeys_e */ "\u0117,\u0119,\u0113,\u00E8,\u00E9,\u00EA,\u00EB,\u011B",
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
// U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
@@ -2414,15 +2512,6 @@ public final class KeyboardTextsTable {
// U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
/* morekeys_u */ "\u016B,\u0173,\u00FC,\u016B,\u00F9,\u00FA,\u00FB,\u016F,\u0171",
/* keylabel_to_alpha */ null,
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- /* morekeys_e */ "\u0117,\u0119,\u0113,\u00E8,\u00E9,\u00EA,\u00EB,\u011B",
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -2431,31 +2520,31 @@ public final class KeyboardTextsTable {
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
/* morekeys_i */ "\u012F,\u012B,\u00EC,\u00ED,\u00EE,\u00EF,\u0131",
+ // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u0146,\u00F1,\u0144",
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
/* morekeys_c */ "\u010D,\u00E7,\u0107",
/* double_quotes */ "!text/double_9qm_lqm",
- // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u0146,\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
/* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u010F",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
/* morekeys_z */ "\u017E,\u017C,\u017A",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u010F",
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
/* morekeys_t */ "\u0163,\u0165",
@@ -2498,6 +2587,15 @@ public final class KeyboardTextsTable {
// U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
/* morekeys_o */ "\u00F2,\u00F3,\u00F4,\u00F5,\u00F6,\u0153,\u0151,\u00F8",
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ /* morekeys_e */ "\u0113,\u0117,\u00E8,\u00E9,\u00EA,\u00EB,\u0119,\u011B",
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
// U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -2508,15 +2606,6 @@ public final class KeyboardTextsTable {
// U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
/* morekeys_u */ "\u016B,\u0173,\u00F9,\u00FA,\u00FB,\u00FC,\u016F,\u0171",
/* keylabel_to_alpha */ null,
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- /* morekeys_e */ "\u0113,\u0117,\u00E8,\u00E9,\u00EA,\u00EB,\u0119,\u011B",
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -2525,31 +2614,31 @@ public final class KeyboardTextsTable {
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
/* morekeys_i */ "\u012B,\u012F,\u00EC,\u00ED,\u00EE,\u00EF,\u0131",
+ // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u0146,\u00F1,\u0144",
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
/* morekeys_c */ "\u010D,\u00E7,\u0107",
/* double_quotes */ "!text/double_9qm_lqm",
- // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u0146,\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
/* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u010F",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
/* morekeys_z */ "\u017E,\u017C,\u017A",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u010F",
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
/* morekeys_t */ "\u0163,\u0165",
@@ -2574,21 +2663,21 @@ public final class KeyboardTextsTable {
/* Locale mk: Macedonian */
private static final String[] TEXTS_mk = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_lqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_k */
// U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE
/* morekeys_cyrillic_ie */ "\u0450",
@@ -2597,7 +2686,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null,
/* ~ morekeys_cyrillic_o */
// U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
/* morekeys_cyrillic_i */ "\u045D",
@@ -2614,14 +2703,14 @@ public final class KeyboardTextsTable {
/* Locale ml_IN: Malayalam (India) */
private static final String[] TEXTS_ml_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0D05: "അ" MALAYALAM LETTER A
/* keylabel_to_alpha */ "\u0D05",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
};
@@ -2629,16 +2718,16 @@ public final class KeyboardTextsTable {
/* Locale mn_MN: Mongolian (Mongolia) */
private static final String[] TEXTS_mn_MN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20AE: "₮" TUGRIK SIGN
/* keyspec_currency */ "\u20AE",
};
@@ -2646,16 +2735,16 @@ public final class KeyboardTextsTable {
/* Locale mr_IN: Marathi (India) */
private static final String[] TEXTS_mr_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0915: "क" DEVANAGARI LETTER KA
// U+0916: "ख" DEVANAGARI LETTER KHA
// U+0917: "ग" DEVANAGARI LETTER GA
/* keylabel_to_alpha */ "\u0915\u0916\u0917",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
/* morekeys_y ~ */
@@ -2699,65 +2788,58 @@ public final class KeyboardTextsTable {
/* Locale my_MM: Burmese (Myanmar) */
private static final String[] TEXTS_my_MM = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+1000: "က" MYANMAR LETTER KA
// U+1001: "ခ" MYANMAR LETTER KHA
// U+1002: "ဂ" MYANMAR LETTER GA
/* keylabel_to_alpha */ "\u1000\u1001\u1002",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
+ null, null, null, null,
/* ~ morekeys_nordic_row2_11 */
/* morekeys_punctuation */ "!autoColumnOrder!9,\u104A,.,?,!,#,),(,/,;,...,',@,:,-,\",+,\\%,&",
// U+104A: "၊" MYANMAR SIGN LITTLE SECTION
// U+104B: "။" MYANMAR SIGN SECTION
/* keyspec_tablet_comma */ "\u104A",
- /* keyspec_swiss_row1_11 ~ */
+ /* morekeys_tablet_period ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null,
/* ~ keyspec_comma */
/* morekeys_tablet_comma */ "\\,",
- /* keyhintlabel_period */ "\u104A",
- /* morekeys_tablet_period ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~ keyspec_south_slavic_row3_8 */
- /* morekeys_tablet_punctuation */ "!autoColumnOrder!8,.,',#,),(,/,;,@,...,:,-,\",+,\\%,&",
- /* keyspec_spanish_row2_10 ~ */
- null, null, null, null, null, null,
- /* ~ keyhintlabel_tablet_comma */
/* keyspec_period */ "\u104B",
+ /* keyhintlabel_period */ "\u104A",
/* morekeys_period */ null,
/* keyspec_tablet_period */ "\u104B",
+ /* morekeys_question ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ keyspec_south_slavic_row3_8 */
+ /* morekeys_tablet_punctuation */ "!autoColumnOrder!8,.,',#,),(,/,;,@,...,:,-,\",+,\\%,&",
};
/* Locale nb: Norwegian Bokmål */
private static final String[] TEXTS_nb = {
- // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
// U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
// U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
// U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* morekeys_a */ "\u00E0,\u00E4,\u00E1,\u00E2,\u00E3,\u0101",
+ /* morekeys_a */ "\u00E5,\u00E6,\u00E4,\u00E0,\u00E1,\u00E2,\u00E3,\u0101",
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
// U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
// U+0153: "œ" LATIN SMALL LIGATURE OE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* morekeys_o */ "\u00F4,\u00F2,\u00F3,\u00F6,\u00F5,\u0153,\u014D",
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
- /* keylabel_to_alpha */ null,
+ /* morekeys_o */ "\u00F8,\u00F6,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u014D",
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -2766,13 +2848,20 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
- /* morekeys_i */ null,
- /* morekeys_c */ null,
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
+ /* keylabel_to_alpha ~ */
+ null, null, null, null,
+ /* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_rqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_rqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_cyrillic_ie */
// U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
/* keyspec_nordic_row1_11 */ "\u00E5",
@@ -2793,16 +2882,16 @@ public final class KeyboardTextsTable {
/* Locale ne_NP: Nepali (Nepal) */
private static final String[] TEXTS_ne_NP = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0915: "क" DEVANAGARI LETTER KA
// U+0916: "ख" DEVANAGARI LETTER KHA
// U+0917: "ग" DEVANAGARI LETTER GA
/* keylabel_to_alpha */ "\u0915\u0916\u0917",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
/* keyspec_currency */ "\u0930\u0941.",
/* morekeys_y ~ */
@@ -2863,13 +2952,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -2878,6 +2960,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00EB,\u00EA,\u00E8,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -2886,13 +2975,13 @@ public final class KeyboardTextsTable {
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B,\u0133",
- /* morekeys_c */ null,
- /* double_quotes */ "!text/double_9qm_rqm",
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
/* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_rqm",
+ /* morekeys_c */ null,
+ /* double_quotes */ "!text/double_9qm_rqm",
/* morekeys_s */ null,
+ /* single_quotes */ "!text/single_9qm_rqm",
/* keyspec_currency */ null,
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_y */ "\u0133",
@@ -2919,8 +3008,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- /* morekeys_u */ null,
- /* keylabel_to_alpha */ null,
// U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
@@ -2929,27 +3016,29 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u0119,\u00E8,\u00E9,\u00EA,\u00EB,\u0117,\u0113",
- /* morekeys_i */ null,
+ /* morekeys_u ~ */
+ null, null, null,
+ /* ~ morekeys_i */
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u0144,\u00F1",
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u0107,\u00E7,\u010D",
/* double_quotes */ "!text/double_9qm_rqm",
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* morekeys_n */ "\u0144,\u00F1",
- /* single_quotes */ "!text/single_9qm_rqm",
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u015B,\u00DF,\u0161",
- /* keyspec_currency ~ */
- null, null, null,
- /* ~ morekeys_d */
+ /* single_quotes */ "!text/single_9qm_rqm",
+ /* keyspec_currency */ null,
+ /* morekeys_y */ null,
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
/* morekeys_z */ "\u017C,\u017A,\u017E",
+ /* morekeys_d */ null,
/* morekeys_t */ null,
// U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
/* morekeys_l */ "\u0142",
@@ -2976,13 +3065,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F5,\u00F4,\u00F2,\u00F6,\u0153,\u00F8,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
@@ -2991,6 +3073,13 @@ public final class KeyboardTextsTable {
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
/* morekeys_e */ "\u00E9,\u00EA,\u00E8,\u0119,\u0117,\u0113,\u00EB",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -2998,6 +3087,7 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EE,\u00EC,\u00EF,\u012F,\u012B",
+ /* morekeys_n */ null,
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
@@ -3019,19 +3109,19 @@ public final class KeyboardTextsTable {
/* Locale ro: Romanian */
private static final String[] TEXTS_ro = {
+ // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
// U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
// U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
// U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
// U+00E6: "æ" LATIN SMALL LETTER AE
// U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
// U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* morekeys_a */ "\u00E2,\u00E3,\u0103,\u00E0,\u00E1,\u00E4,\u00E6,\u00E5,\u0101",
+ /* morekeys_a */ "\u0103,\u00E2,\u00E3,\u00E0,\u00E1,\u00E4,\u00E6,\u00E5,\u0101",
/* morekeys_o ~ */
null, null, null, null,
- /* ~ morekeys_e */
+ /* ~ keylabel_to_alpha */
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -3039,18 +3129,18 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ /* morekeys_n */ null,
/* morekeys_c */ null,
/* double_quotes */ "!text/double_9qm_rqm",
- /* morekeys_n */ null,
- /* single_quotes */ "!text/single_9qm_rqm",
// U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u0219,\u00DF,\u015B,\u0161",
+ /* single_quotes */ "!text/single_9qm_rqm",
/* keyspec_currency ~ */
null, null, null, null,
- /* ~ morekeys_z */
+ /* ~ morekeys_d */
// U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW
/* morekeys_t */ "\u021B",
};
@@ -3058,21 +3148,21 @@ public final class KeyboardTextsTable {
/* Locale ru: Russian */
private static final String[] TEXTS_ru = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_lqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_k */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* morekeys_cyrillic_ie */ "\u0451",
@@ -3094,15 +3184,15 @@ public final class KeyboardTextsTable {
/* Locale si_LK: Sinhalese (Sri Lanka) */
private static final String[] TEXTS_si_LK = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0D85: "අ" SINHALA LETTER AYANNA
// U+0D86: "ආ" SINHALA LETTER AAYANNA
/* keylabel_to_alpha */ "\u0D85,\u0D86",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+0DBB/U+0DD4: "රු" SINHALA LETTER RAYANNA/SINHALA VOWEL SIGN KETTI PAA-PILLA
/* keyspec_currency */ "\u0DBB\u0DD4",
};
@@ -3128,6 +3218,15 @@ public final class KeyboardTextsTable {
// U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
/* morekeys_o */ "\u00F4,\u00F3,\u00F6,\u00F2,\u00F5,\u0153,\u0151,\u00F8",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ /* morekeys_e */ "\u00E9,\u011B,\u0113,\u0117,\u00E8,\u00EA,\u00EB,\u0119",
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
@@ -3138,15 +3237,6 @@ public final class KeyboardTextsTable {
// U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
/* morekeys_u */ "\u00FA,\u016F,\u00FC,\u016B,\u0173,\u00F9,\u00FB,\u0171",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- /* morekeys_e */ "\u00E9,\u011B,\u0113,\u0117,\u00E8,\u00EA,\u00EB,\u0119",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
@@ -3155,32 +3245,32 @@ public final class KeyboardTextsTable {
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
/* morekeys_i */ "\u00ED,\u012B,\u012F,\u00EC,\u00EE,\u00EF,\u0131",
- // U+010D: "č" LATIN SMALL LETTER C WITH CARON
- // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
- /* morekeys_c */ "\u010D,\u00E7,\u0107",
- /* double_quotes */ "!text/double_9qm_lqm",
// U+0148: "ň" LATIN SMALL LETTER N WITH CARON
// U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
/* morekeys_n */ "\u0148,\u0146,\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ /* morekeys_c */ "\u010D,\u00E7,\u0107",
+ /* double_quotes */ "!text/double_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
/* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u010F",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
/* morekeys_z */ "\u017E,\u017C,\u017A",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u010F",
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
/* morekeys_t */ "\u0165,\u0163",
@@ -3205,22 +3295,21 @@ public final class KeyboardTextsTable {
/* Locale sl: Slovenian */
private static final String[] TEXTS_sl = {
/* morekeys_a ~ */
- null, null, null, null, null, null,
- /* ~ morekeys_i */
+ null, null, null, null, null, null, null,
+ /* ~ morekeys_n */
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
/* morekeys_c */ "\u010D,\u0107",
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
- /* single_quotes */ "!text/single_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u0161",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
/* morekeys_y */ null,
- // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
- /* morekeys_d */ "\u0111",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
/* morekeys_z */ "\u017E",
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ /* morekeys_d */ "\u0111",
/* morekeys_t ~ */
null, null, null,
/* ~ morekeys_g */
@@ -3231,7 +3320,7 @@ public final class KeyboardTextsTable {
/* Locale sr: Serbian */
private static final String[] TEXTS_sr = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// END: More keys definitions for Serbian (Cyrillic)
// Label for "switch to alphabetic" key.
@@ -3239,14 +3328,14 @@ public final class KeyboardTextsTable {
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_lqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null,
/* ~ morekeys_g */
/* single_angle_quotes */ "!text/single_raqm_laqm",
/* double_angle_quotes */ "!text/double_raqm_laqm",
@@ -3259,7 +3348,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null,
/* ~ morekeys_cyrillic_o */
// U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
/* morekeys_cyrillic_i */ "\u045D",
@@ -3291,20 +3380,75 @@ public final class KeyboardTextsTable {
/* keyspec_south_slavic_row3_8 */ "\u0452",
};
+ /* Locale sr_ZZ: Serbian (ZZ) */
+ private static final String[] TEXTS_sr_ZZ = {
+ /* morekeys_a */ null,
+ /* morekeys_o */ null,
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ /* morekeys_e */ "\u00E8",
+ /* morekeys_u */ null,
+ /* keylabel_to_alpha */ null,
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ /* morekeys_i */ "\u00EC",
+ /* morekeys_n */ null,
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ /* morekeys_c */ "\u010D,\u0107,%",
+ /* double_quotes */ null,
+ // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+ /* morekeys_s */ "\u0161,%",
+ /* single_quotes ~ */
+ null, null, null,
+ /* ~ morekeys_y */
+ // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+ /* morekeys_z */ "\u017E,%",
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ /* morekeys_d */ "\u0111,%",
+ /* morekeys_t ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null,
+ /* ~ morekeys_symbols_percent */
+ /* label_go_key */ "Idi",
+ /* label_send_key */ "\u0160alji",
+ /* label_next_key */ "Sled",
+ /* label_done_key */ "Gotov",
+ /* label_search_key */ "Tra\u017Ei",
+ /* label_previous_key */ "Preth",
+ /* label_pause_key */ "Pauza",
+ /* label_wait_key */ "\u010Cekaj",
+ };
+
/* Locale sv: Swedish */
private static final String[] TEXTS_sv = {
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING
+ // U+00E6: "æ" LATIN SMALL LETTER AE
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
// U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
// U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
// U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- /* morekeys_a */ "\u00E1,\u00E0,\u00E2,\u0105,\u00E3",
+ /* morekeys_a */ "\u00E4,\u00E5,\u00E6,\u00E1,\u00E0,\u00E2,\u0105,\u00E3",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
// U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
// U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* morekeys_o */ "\u00F3,\u00F2,\u00F4,\u00F5,\u014D",
+ /* morekeys_o */ "\u00F6,\u00F8,\u0153,\u00F3,\u00F2,\u00F4,\u00F5,\u014D",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119",
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -3312,43 +3456,37 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FC,\u00FA,\u00F9,\u00FB,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
/* morekeys_i */ "\u00ED,\u00EC,\u00EE,\u00EF",
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ /* morekeys_n */ "\u0144,\u00F1,\u0148",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
/* double_quotes */ null,
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
- /* morekeys_n */ "\u0144,\u00F1,\u0148",
- /* single_quotes */ null,
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
/* morekeys_s */ "\u015B,\u0161,\u015F,\u00DF",
+ /* single_quotes */ null,
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+00F0: "ð" LATIN SMALL LETTER ETH
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u00F0,\u010F",
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
/* morekeys_z */ "\u017A,\u017E,\u017C",
+ // U+00F0: "ð" LATIN SMALL LETTER ETH
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u00F0,\u010F",
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
// U+00FE: "þ" LATIN SMALL LETTER THORN
/* morekeys_t */ "\u0165,\u00FE",
@@ -3399,6 +3537,12 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
/* morekeys_o */ "\u00F4,\u00F6,\u00F2,\u00F3,\u0153,\u00F8,\u014D,\u00F5",
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113",
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -3406,28 +3550,21 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113",
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
/* morekeys_i */ "\u00EE,\u00EF,\u00ED,\u012B,\u00EC",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u00F1",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
/* morekeys_c */ "\u00E7",
/* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* morekeys_n */ "\u00F1",
- /* single_quotes */ null,
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
/* morekeys_s */ "\u00DF",
- /* keyspec_currency ~ */
- null, null, null, null, null, null,
+ /* single_quotes ~ */
+ null, null, null, null, null, null, null,
/* ~ morekeys_l */
/* morekeys_g */ "g\'",
};
@@ -3435,16 +3572,16 @@ public final class KeyboardTextsTable {
/* Locale ta_IN: Tamil (India) */
private static final String[] TEXTS_ta_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0BA4: "த" TAMIL LETTER TA
// U+0BAE/U+0BBF: "மி" TAMIL LETTER MA/TAMIL VOWEL SIGN I
// U+0BB4/U+0BCD: "ழ்" TAMIL LETTER LLLA/TAMIL SIGN VIRAMA
/* keylabel_to_alpha */ "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+0BF9: "௹" TAMIL RUPEE SIGN
/* keyspec_currency */ "\u0BF9",
};
@@ -3452,16 +3589,16 @@ public final class KeyboardTextsTable {
/* Locale ta_LK: Tamil (Sri Lanka) */
private static final String[] TEXTS_ta_LK = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0BA4: "த" TAMIL LETTER TA
// U+0BAE/U+0BBF: "மி" TAMIL LETTER MA/TAMIL VOWEL SIGN I
// U+0BB4/U+0BCD: "ழ்" TAMIL LETTER LLLA/TAMIL SIGN VIRAMA
/* keylabel_to_alpha */ "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+0DBB/U+0DD4: "රු" SINHALA LETTER RAYANNA/SINHALA VOWEL SIGN KETTI PAA-PILLA
/* keyspec_currency */ "\u0DBB\u0DD4",
};
@@ -3469,7 +3606,7 @@ public final class KeyboardTextsTable {
/* Locale ta_SG: Tamil (Singapore) */
private static final String[] TEXTS_ta_SG = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0BA4: "த" TAMIL LETTER TA
@@ -3481,16 +3618,16 @@ public final class KeyboardTextsTable {
/* Locale te_IN: Telugu (India) */
private static final String[] TEXTS_te_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0C05: "అ" TELUGU LETTER A
// U+0C06: "ఆ" TELUGU LETTER AA
// U+0C07: "ఇ" TELUGU LETTER I
/* keylabel_to_alpha */ "\u0C05\u0C06\u0C07",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
};
@@ -3498,16 +3635,16 @@ public final class KeyboardTextsTable {
/* Locale th: Thai */
private static final String[] TEXTS_th = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0E01: "ก" THAI CHARACTER KO KAI
// U+0E02: "ข" THAI CHARACTER KHO KHAI
// U+0E04: "ค" THAI CHARACTER KHO KHWAI
/* keylabel_to_alpha */ "\u0E01\u0E02\u0E04",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
/* keyspec_currency */ "\u0E3F",
};
@@ -3535,13 +3672,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
@@ -3550,6 +3680,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -3557,20 +3694,21 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
};
/* Locale tr: Turkish */
private static final String[] TEXTS_tr = {
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- /* morekeys_a */ "\u00E2",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ /* morekeys_a */ "\u00E2,\u00E4,\u00E1",
// U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+0153: "œ" LATIN SMALL LIGATURE OE
@@ -3580,6 +3718,9 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D",
+ // U+0259: "ə" LATIN SMALL LETTER SCHWA
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ /* morekeys_e */ "\u0259,\u00E9",
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -3587,7 +3728,6 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
/* keylabel_to_alpha */ null,
- /* morekeys_e */ null,
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -3596,20 +3736,27 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u0148,\u00F1",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes ~ */
- null, null, null,
- /* ~ single_quotes */
+ /* double_quotes */ null,
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u015F,\u00DF,\u015B,\u0161",
- /* keyspec_currency ~ */
- null, null, null, null, null, null,
+ /* single_quotes */ null,
+ /* keyspec_currency */ null,
+ // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+ /* morekeys_y */ "\u00FD",
+ // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+ /* morekeys_z */ "\u017E",
+ /* morekeys_d ~ */
+ null, null, null,
/* ~ morekeys_l */
// U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
/* morekeys_g */ "\u011F",
@@ -3618,20 +3765,19 @@ public final class KeyboardTextsTable {
/* Locale uk: Ukrainian */
private static final String[] TEXTS_uk = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
- /* single_quotes */ "!text/single_9qm_lqm",
/* morekeys_s */ null,
+ /* single_quotes */ "!text/single_9qm_lqm",
// U+20B4: "₴" HRYVNIA SIGN
/* keyspec_currency */ "\u20B4",
/* morekeys_y ~ */
@@ -3651,7 +3797,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_w */
// U+0457: "ї" CYRILLIC SMALL LETTER YI
/* morekeys_east_slavic_row2_2 */ "\u0457",
@@ -3661,6 +3807,66 @@ public final class KeyboardTextsTable {
/* morekeys_cyrillic_ghe */ "\u0491",
};
+ /* Locale uz_UZ: Uzbek (Uzbekistan) */
+ private static final String[] TEXTS_uz_UZ = {
+ // This is the same as Turkish
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ /* morekeys_a */ "\u00E2,\u00E4,\u00E1",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+ /* morekeys_o */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D",
+ // U+0259: "ə" LATIN SMALL LETTER SCHWA
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ /* morekeys_e */ "\u0259,\u00E9",
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
+ /* keylabel_to_alpha */ null,
+ // U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+ /* morekeys_i */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u0148,\u00F1",
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ /* morekeys_c */ "\u00E7,\u0107,\u010D",
+ /* double_quotes */ null,
+ // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+ // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+ // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+ // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+ /* morekeys_s */ "\u015F,\u00DF,\u015B,\u0161",
+ /* single_quotes */ null,
+ /* keyspec_currency */ null,
+ // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+ /* morekeys_y */ "\u00FD",
+ // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+ /* morekeys_z */ "\u017E",
+ /* morekeys_d ~ */
+ null, null, null,
+ /* ~ morekeys_l */
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ /* morekeys_g */ "\u011F",
+ };
+
/* Locale vi: Vietnamese */
private static final String[] TEXTS_vi = {
// U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
@@ -3699,6 +3905,18 @@ public final class KeyboardTextsTable {
// U+1EE1: "ỡ" LATIN SMALL LETTER O WITH HORN AND TILDE
// U+1EE3: "ợ" LATIN SMALL LETTER O WITH HORN AND DOT BELOW
/* morekeys_o */ "\u00F2,\u00F3,\u1ECF,\u00F5,\u1ECD,\u00F4,\u1ED3,\u1ED1,\u1ED5,\u1ED7,\u1ED9,\u01A1,\u1EDD,\u1EDB,\u1EDF,\u1EE1,\u1EE3",
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+1EBB: "ẻ" LATIN SMALL LETTER E WITH HOOK ABOVE
+ // U+1EBD: "ẽ" LATIN SMALL LETTER E WITH TILDE
+ // U+1EB9: "ẹ" LATIN SMALL LETTER E WITH DOT BELOW
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+1EC1: "ề" LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE
+ // U+1EBF: "ế" LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE
+ // U+1EC3: "ể" LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+ // U+1EC5: "ễ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE
+ // U+1EC7: "ệ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+ /* morekeys_e */ "\u00E8,\u00E9,\u1EBB,\u1EBD,\u1EB9,\u00EA,\u1EC1,\u1EBF,\u1EC3,\u1EC5,\u1EC7",
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+1EE7: "ủ" LATIN SMALL LETTER U WITH HOOK ABOVE
@@ -3712,27 +3930,15 @@ public final class KeyboardTextsTable {
// U+1EF1: "ự" LATIN SMALL LETTER U WITH HORN AND DOT BELOW
/* morekeys_u */ "\u00F9,\u00FA,\u1EE7,\u0169,\u1EE5,\u01B0,\u1EEB,\u1EE9,\u1EED,\u1EEF,\u1EF1",
/* keylabel_to_alpha */ null,
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+1EBB: "ẻ" LATIN SMALL LETTER E WITH HOOK ABOVE
- // U+1EBD: "ẽ" LATIN SMALL LETTER E WITH TILDE
- // U+1EB9: "ẹ" LATIN SMALL LETTER E WITH DOT BELOW
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+1EC1: "ề" LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE
- // U+1EBF: "ế" LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE
- // U+1EC3: "ể" LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
- // U+1EC5: "ễ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE
- // U+1EC7: "ệ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW
- /* morekeys_e */ "\u00E8,\u00E9,\u1EBB,\u1EBD,\u1EB9,\u00EA,\u1EC1,\u1EBF,\u1EC3,\u1EC5,\u1EC7",
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+1EC9: "ỉ" LATIN SMALL LETTER I WITH HOOK ABOVE
// U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE
// U+1ECB: "ị" LATIN SMALL LETTER I WITH DOT BELOW
/* morekeys_i */ "\u00EC,\u00ED,\u1EC9,\u0129,\u1ECB",
- /* morekeys_c ~ */
+ /* morekeys_n ~ */
null, null, null, null, null,
- /* ~ morekeys_s */
+ /* ~ single_quotes */
// U+20AB: "₫" DONG SIGN
/* keyspec_currency */ "\u20AB",
// U+1EF3: "ỳ" LATIN SMALL LETTER Y WITH GRAVE
@@ -3741,6 +3947,7 @@ public final class KeyboardTextsTable {
// U+1EF9: "ỹ" LATIN SMALL LETTER Y WITH TILDE
// U+1EF5: "ỵ" LATIN SMALL LETTER Y WITH DOT BELOW
/* morekeys_y */ "\u1EF3,\u00FD,\u1EF7,\u1EF9,\u1EF5",
+ /* morekeys_z */ null,
// U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
/* morekeys_d */ "\u0111",
};
@@ -3766,6 +3973,12 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
/* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u0153,\u00F8,\u014D,\u00F5",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113",
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
@@ -3773,24 +3986,17 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
/* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u012B,\u00EC",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u00F1",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
/* morekeys_c */ "\u00E7",
/* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* morekeys_n */ "\u00F1",
- /* single_quotes */ null,
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
/* morekeys_s */ "\u00DF",
};
@@ -3821,6 +4027,16 @@ public final class KeyboardTextsTable {
// U+0153: "œ" LATIN SMALL LIGATURE OE
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F2,\u00F3,\u00F4,\u00F5,\u00F6,\u00F8,\u014D,\u014F,\u0151,\u0153,\u00BA",
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ // U+0115: "ĕ" LATIN SMALL LETTER E WITH BREVE
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113,\u0115,\u0117,\u0119,\u011B",
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
@@ -3833,16 +4049,6 @@ public final class KeyboardTextsTable {
// U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
/* morekeys_u */ "\u00F9,\u00FA,\u00FB,\u00FC,\u0169,\u016B,\u016D,\u016F,\u0171,\u0173",
/* keylabel_to_alpha */ null,
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- // U+0115: "ĕ" LATIN SMALL LETTER E WITH BREVE
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113,\u0115,\u0117,\u0119,\u011B",
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
@@ -3854,13 +4060,6 @@ public final class KeyboardTextsTable {
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_i */ "\u00EC,\u00ED,\u00EE,\u00EF,\u0129,\u012B,\u012D,\u012F,\u0131,\u0133",
- // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
- // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
- // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE
- // U+010D: "č" LATIN SMALL LETTER C WITH CARON
- /* morekeys_c */ "\u00E7,\u0107,\u0109,\u010B,\u010D",
- /* double_quotes */ null,
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
// U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
@@ -3868,7 +4067,13 @@ public final class KeyboardTextsTable {
// U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
// U+014B: "ŋ" LATIN SMALL LETTER ENG
/* morekeys_n */ "\u00F1,\u0144,\u0146,\u0148,\u0149,\u014B",
- /* single_quotes */ null,
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
+ // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ /* morekeys_c */ "\u00E7,\u0107,\u0109,\u010B,\u010D",
+ /* double_quotes */ null,
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
@@ -3876,20 +4081,21 @@ public final class KeyboardTextsTable {
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+017F: "ſ" LATIN SMALL LETTER LONG S
/* morekeys_s */ "\u00DF,\u015B,\u015D,\u015F,\u0161,\u017F",
+ /* single_quotes */ null,
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_y */ "\u00FD,\u0177,\u00FF,\u0133",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
- // U+00F0: "ð" LATIN SMALL LETTER ETH
- /* morekeys_d */ "\u010F,\u0111,\u00F0",
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
/* morekeys_z */ "\u017A,\u017C,\u017E",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ // U+00F0: "ð" LATIN SMALL LETTER ETH
+ /* morekeys_d */ "\u010F,\u0111,\u00F0",
// U+00FE: "þ" LATIN SMALL LETTER THORN
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
@@ -3920,6 +4126,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null,
/* ~ morekeys_question */
// U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX
/* morekeys_h */ "\u0125",
@@ -3927,7 +4134,8 @@ public final class KeyboardTextsTable {
/* morekeys_w */ "\u0175",
/* morekeys_east_slavic_row2_2 ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null,
/* ~ morekeys_v */
// U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
/* morekeys_j */ "\u0135",
@@ -3935,72 +4143,76 @@ public final class KeyboardTextsTable {
private static final Object[] LOCALES_AND_TEXTS = {
// "locale", TEXT_ARRAY, /* numberOfNonNullText/lengthOf_TEXT_ARRAY localeName */
- "DEFAULT", TEXTS_DEFAULT, /* 168/168 DEFAULT */
+ "DEFAULT", TEXTS_DEFAULT, /* 176/176 DEFAULT */
"af" , TEXTS_af, /* 7/ 13 Afrikaans */
"ar" , TEXTS_ar, /* 55/110 Arabic */
- "az_AZ" , TEXTS_az_AZ, /* 8/ 18 Azerbaijani (Azerbaijan) */
+ "az_AZ" , TEXTS_az_AZ, /* 11/ 18 Azerbaijani (Azerbaijan) */
"be_BY" , TEXTS_be_BY, /* 9/ 32 Belarusian (Belarus) */
- "bg" , TEXTS_bg, /* 2/ 8 Bulgarian */
+ "bg" , TEXTS_bg, /* 2/ 9 Bulgarian */
+ "bn_BD" , TEXTS_bn_BD, /* 2/ 12 Bengali (Bangladesh) */
"bn_IN" , TEXTS_bn_IN, /* 2/ 12 Bengali (India) */
- "ca" , TEXTS_ca, /* 11/ 96 Catalan */
+ "ca" , TEXTS_ca, /* 11/ 99 Catalan */
"cs" , TEXTS_cs, /* 17/ 21 Czech */
"da" , TEXTS_da, /* 19/ 54 Danish */
- "de" , TEXTS_de, /* 16/ 62 German */
- "el" , TEXTS_el, /* 1/ 4 Greek */
- "en" , TEXTS_en, /* 8/ 11 English */
- "eo" , TEXTS_eo, /* 26/118 Esperanto */
+ "de" , TEXTS_de, /* 16/ 63 German */
+ "el" , TEXTS_el, /* 1/ 5 Greek */
+ "en" , TEXTS_en, /* 8/ 10 English */
+ "eo" , TEXTS_eo, /* 26/126 Esperanto */
"es" , TEXTS_es, /* 8/ 55 Spanish */
"et_EE" , TEXTS_et_EE, /* 22/ 27 Estonian (Estonia) */
- "eu_ES" , TEXTS_eu_ES, /* 7/ 9 Basque (Spain) */
- "fa" , TEXTS_fa, /* 58/125 Persian */
+ "eu_ES" , TEXTS_eu_ES, /* 7/ 8 Basque (Spain) */
+ "fa" , TEXTS_fa, /* 58/133 Persian */
"fi" , TEXTS_fi, /* 10/ 54 Finnish */
- "fr" , TEXTS_fr, /* 13/ 62 French */
- "gl_ES" , TEXTS_gl_ES, /* 7/ 9 Gallegan (Spain) */
- "hi" , TEXTS_hi, /* 23/ 53 Hindi */
+ "fr" , TEXTS_fr, /* 13/ 63 French */
+ "gl_ES" , TEXTS_gl_ES, /* 7/ 8 Gallegan (Spain) */
+ "hi" , TEXTS_hi, /* 27/ 84 Hindi */
+ "hi_ZZ" , TEXTS_hi_ZZ, /* 9/118 Hindi (ZZ) */
"hr" , TEXTS_hr, /* 9/ 20 Croatian */
"hu" , TEXTS_hu, /* 9/ 20 Hungarian */
- "hy_AM" , TEXTS_hy_AM, /* 9/126 Armenian (Armenia) */
+ "hy_AM" , TEXTS_hy_AM, /* 9/134 Armenian (Armenia) */
"is" , TEXTS_is, /* 10/ 16 Icelandic */
- "it" , TEXTS_it, /* 11/ 62 Italian */
- "iw" , TEXTS_iw, /* 20/123 Hebrew */
- "ka_GE" , TEXTS_ka_GE, /* 3/ 10 Georgian (Georgia) */
- "kk" , TEXTS_kk, /* 15/121 Kazakh */
- "km_KH" , TEXTS_km_KH, /* 2/122 Khmer (Cambodia) */
+ "it" , TEXTS_it, /* 11/ 63 Italian */
+ "iw" , TEXTS_iw, /* 20/131 Hebrew */
+ "ka_GE" , TEXTS_ka_GE, /* 3/ 11 Georgian (Georgia) */
+ "kk" , TEXTS_kk, /* 15/129 Kazakh */
+ "km_KH" , TEXTS_km_KH, /* 2/130 Khmer (Cambodia) */
"kn_IN" , TEXTS_kn_IN, /* 2/ 12 Kannada (India) */
- "ky" , TEXTS_ky, /* 10/ 89 Kirghiz */
+ "ky" , TEXTS_ky, /* 10/ 92 Kirghiz */
"lo_LA" , TEXTS_lo_LA, /* 2/ 12 Lao (Laos) */
"lt" , TEXTS_lt, /* 18/ 22 Lithuanian */
"lv" , TEXTS_lv, /* 18/ 22 Latvian */
- "mk" , TEXTS_mk, /* 9/ 94 Macedonian */
+ "mk" , TEXTS_mk, /* 9/ 97 Macedonian */
"ml_IN" , TEXTS_ml_IN, /* 2/ 12 Malayalam (India) */
"mn_MN" , TEXTS_mn_MN, /* 2/ 12 Mongolian (Mongolia) */
"mr_IN" , TEXTS_mr_IN, /* 23/ 53 Marathi (India) */
- "my_MM" , TEXTS_my_MM, /* 8/104 Burmese (Myanmar) */
+ "my_MM" , TEXTS_my_MM, /* 8/ 98 Burmese (Myanmar) */
"nb" , TEXTS_nb, /* 11/ 54 Norwegian Bokmål */
"ne_NP" , TEXTS_ne_NP, /* 23/ 53 Nepali (Nepal) */
"nl" , TEXTS_nl, /* 9/ 13 Dutch */
"pl" , TEXTS_pl, /* 10/ 17 Polish */
- "pt" , TEXTS_pt, /* 6/ 7 Portuguese */
+ "pt" , TEXTS_pt, /* 6/ 8 Portuguese */
"rm" , TEXTS_rm, /* 1/ 2 Raeto-Romance */
"ro" , TEXTS_ro, /* 6/ 16 Romanian */
"ru" , TEXTS_ru, /* 9/ 32 Russian */
"si_LK" , TEXTS_si_LK, /* 2/ 12 Sinhalese (Sri Lanka) */
"sk" , TEXTS_sk, /* 20/ 22 Slovak */
"sl" , TEXTS_sl, /* 8/ 20 Slovenian */
- "sr" , TEXTS_sr, /* 11/ 94 Serbian */
+ "sr" , TEXTS_sr, /* 11/ 97 Serbian */
+ "sr_ZZ" , TEXTS_sr_ZZ, /* 14/118 Serbian (ZZ) */
"sv" , TEXTS_sv, /* 21/ 54 Swedish */
"sw" , TEXTS_sw, /* 9/ 18 Swahili */
"ta_IN" , TEXTS_ta_IN, /* 2/ 12 Tamil (India) */
"ta_LK" , TEXTS_ta_LK, /* 2/ 12 Tamil (Sri Lanka) */
- "ta_SG" , TEXTS_ta_SG, /* 1/ 4 Tamil (Singapore) */
+ "ta_SG" , TEXTS_ta_SG, /* 1/ 5 Tamil (Singapore) */
"te_IN" , TEXTS_te_IN, /* 2/ 12 Telugu (India) */
"th" , TEXTS_th, /* 2/ 12 Thai */
- "tl" , TEXTS_tl, /* 7/ 9 Tagalog */
- "tr" , TEXTS_tr, /* 7/ 18 Turkish */
- "uk" , TEXTS_uk, /* 11/ 88 Ukrainian */
- "vi" , TEXTS_vi, /* 8/ 14 Vietnamese */
- "zu" , TEXTS_zu, /* 8/ 11 Zulu */
- "zz" , TEXTS_zz, /* 19/112 Alphabet */
+ "tl" , TEXTS_tl, /* 7/ 8 Tagalog */
+ "tr" , TEXTS_tr, /* 11/ 18 Turkish */
+ "uk" , TEXTS_uk, /* 11/ 91 Ukrainian */
+ "uz_UZ" , TEXTS_uz_UZ, /* 11/ 18 Uzbek (Uzbekistan) */
+ "vi" , TEXTS_vi, /* 8/ 15 Vietnamese */
+ "zu" , TEXTS_zu, /* 8/ 10 Zulu */
+ "zz" , TEXTS_zz, /* 19/120 Alphabet */
};
static {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
index 7743d4744..e8678637b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
@@ -36,4 +36,12 @@ public final class KeysCache {
mMap.put(key, key);
return key;
}
+
+ public Key replace(final Key oldKey, final Key newKey) {
+ if (oldKey.equals(newKey)) {
+ return oldKey;
+ }
+ mMap.remove(oldKey);
+ return get(newKey);
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java b/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java
index 6400a2440..72ad2bd97 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java
@@ -18,6 +18,7 @@ package com.android.inputmethod.keyboard.internal;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Collections;
@@ -34,8 +35,8 @@ public final class LanguageOnSpacebarHelper {
private List<InputMethodSubtype> mEnabledSubtypes = Collections.emptyList();
private boolean mIsSystemLanguageSameAsInputLanguage;
- public int getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype) {
- if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
+ public int getLanguageOnSpacebarFormatType(final RichInputMethodSubtype subtype) {
+ if (subtype.isNoLanguage()) {
return FORMAT_TYPE_FULL_LOCALE;
}
// Only this subtype is enabled and equals to the system locale.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
index 625a0c283..0cd031e5f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -17,7 +17,9 @@
package com.android.inputmethod.keyboard.internal;
import android.text.TextUtils;
+import android.util.SparseIntArray;
+import com.android.inputmethod.compat.CharacterCompat;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.define.DebugFlags;
@@ -26,6 +28,7 @@ import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Locale;
/**
@@ -110,6 +113,51 @@ public final class MoreKeySpec {
}
}
+ public static class LettersOnBaseLayout {
+ private final SparseIntArray mCodes = new SparseIntArray();
+ private final HashSet<String> mTexts = new HashSet<>();
+
+ public void addLetter(final Key key) {
+ final int code = key.getCode();
+ if (CharacterCompat.isAlphabetic(code)) {
+ mCodes.put(code, 0);
+ } else if (code == Constants.CODE_OUTPUT_TEXT) {
+ mTexts.add(key.getOutputText());
+ }
+ }
+
+ public boolean contains(final MoreKeySpec moreKey) {
+ final int code = moreKey.mCode;
+ if (CharacterCompat.isAlphabetic(code) && mCodes.indexOfKey(code) >= 0) {
+ return true;
+ } else if (code == Constants.CODE_OUTPUT_TEXT && mTexts.contains(moreKey.mOutputText)) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public static MoreKeySpec[] removeRedundantMoreKeys(final MoreKeySpec[] moreKeys,
+ final LettersOnBaseLayout lettersOnBaseLayout) {
+ if (moreKeys == null) {
+ return null;
+ }
+ final ArrayList<MoreKeySpec> filteredMoreKeys = new ArrayList<>();
+ for (final MoreKeySpec moreKey : moreKeys) {
+ if (!lettersOnBaseLayout.contains(moreKey)) {
+ filteredMoreKeys.add(moreKey);
+ }
+ }
+ final int size = filteredMoreKeys.size();
+ if (size == moreKeys.length) {
+ return moreKeys;
+ }
+ if (size == 0) {
+ return null;
+ }
+ return filteredMoreKeys.toArray(new MoreKeySpec[size]);
+ }
+
private static final boolean DEBUG = DebugFlags.DEBUG_ENABLED;
// Constants for parsing.
private static final char COMMA = Constants.CODE_COMMA;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 8e89e61ea..556d74f4b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -95,7 +95,7 @@ public final class PointerTrackerQueue {
public void releaseAllPointersOlderThan(final Element pointer, final long eventTime) {
synchronized (mExpandableArrayOfActivePointers) {
if (DEBUG) {
- Log.d(TAG, "releaseAllPoniterOlderThan: " + pointer + " " + this);
+ Log.d(TAG, "releaseAllPointerOlderThan: " + pointer + " " + this);
}
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
final int arraySize = mArraySize;
@@ -144,9 +144,9 @@ public final class PointerTrackerQueue {
synchronized (mExpandableArrayOfActivePointers) {
if (DEBUG) {
if (pointer == null) {
- Log.d(TAG, "releaseAllPoniters: " + this);
+ Log.d(TAG, "releaseAllPointers: " + this);
} else {
- Log.d(TAG, "releaseAllPoniterExcept: " + pointer + " " + this);
+ Log.d(TAG, "releaseAllPointerExcept: " + pointer + " " + this);
}
}
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 693e1cdcc..6f3c48c47 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -83,7 +83,6 @@ public final class BinaryDictionary extends Dictionary {
public static final String DIR_NAME_SUFFIX_FOR_RECORD_MIGRATION = ".migrating";
private long mNativeDict;
- private final Locale mLocale;
private final long mDictSize;
private final String mDictFilePath;
private final boolean mUseFullEditDistance;
@@ -117,8 +116,7 @@ public final class BinaryDictionary extends Dictionary {
public BinaryDictionary(final String filename, final long offset, final long length,
final boolean useFullEditDistance, final Locale locale, final String dictType,
final boolean isUpdatable) {
- super(dictType);
- mLocale = locale;
+ super(dictType, locale);
mDictSize = length;
mDictFilePath = filename;
mIsUpdatable = isUpdatable;
@@ -138,8 +136,7 @@ public final class BinaryDictionary extends Dictionary {
public BinaryDictionary(final String filename, final boolean useFullEditDistance,
final Locale locale, final String dictType, final long formatVersion,
final Map<String, String> attributeMap) {
- super(dictType);
- mLocale = locale;
+ super(dictType, locale);
mDictSize = 0;
mDictFilePath = filename;
// On memory dictionary is always updatable.
@@ -189,9 +186,10 @@ public final class BinaryDictionary extends Dictionary {
long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions,
int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray,
- int[] outputSuggestionCount, int[] outputCodePoints, int[] outputScores,
- int[] outputIndices, int[] outputTypes, int[] outputAutoCommitFirstWordConfidence,
- float[] inOutLanguageWeight);
+ int prevWordCount, int[] outputSuggestionCount, int[] outputCodePoints,
+ int[] outputScores, int[] outputIndices, int[] outputTypes,
+ int[] outputAutoCommitFirstWordConfidence,
+ float[] inOutWeightOfLangModelVsSpatialModel);
private static native boolean addUnigramEntryNative(long dict, int[] word, int probability,
int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence,
boolean isNotAWord, boolean isBlacklisted, int timestamp);
@@ -201,6 +199,9 @@ public final class BinaryDictionary extends Dictionary {
int[] word, int probability, int timestamp);
private static native boolean removeNgramEntryNative(long dict,
int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray, int[] word);
+ private static native boolean updateCounterNative(long dict,
+ int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray,
+ int[] word, boolean isValidWord, int count, int timestamp);
private static native int addMultipleDictionaryEntriesNative(long dict,
LanguageModelParam[] languageModelParams, int startIndex);
private static native String getPropertyNative(long dict, String query);
@@ -257,15 +258,16 @@ public final class BinaryDictionary extends Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int sessionId, final float[] inOutLanguageWeight) {
+ final int sessionId, final float weightForLocale,
+ final float[] inOutWeightOfLangModelVsSpatialModel) {
if (!isValidDictionary()) {
return null;
}
final DicTraverseSession session = getTraverseSession(sessionId);
Arrays.fill(session.mInputCodePoints, Constants.NOT_A_CODE);
- prevWordsInfo.outputToArray(session.mPrevWordCodePointArrays,
+ ngramContext.outputToArray(session.mPrevWordCodePointArrays,
session.mIsBeginningOfSentenceArray);
final InputPointers inputPointers = composer.getInputPointers();
final boolean isGesture = composer.isBatchMode();
@@ -287,10 +289,12 @@ public final class BinaryDictionary extends Dictionary {
settingsValuesForSuggestion.mSpaceAwareGestureEnabled);
session.mNativeSuggestOptions.setAdditionalFeaturesOptions(
settingsValuesForSuggestion.mAdditionalFeaturesSettingValues);
- if (inOutLanguageWeight != null) {
- session.mInputOutputLanguageWeight[0] = inOutLanguageWeight[0];
+ if (inOutWeightOfLangModelVsSpatialModel != null) {
+ session.mInputOutputWeightOfLangModelVsSpatialModel[0] =
+ inOutWeightOfLangModelVsSpatialModel[0];
} else {
- session.mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT;
+ session.mInputOutputWeightOfLangModelVsSpatialModel[0] =
+ Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL;
}
// TOOD: Pass multiple previous words information for n-gram.
getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
@@ -298,12 +302,14 @@ public final class BinaryDictionary extends Dictionary {
inputPointers.getYCoordinates(), inputPointers.getTimes(),
inputPointers.getPointerIds(), session.mInputCodePoints, inputSize,
session.mNativeSuggestOptions.getOptions(), session.mPrevWordCodePointArrays,
- session.mIsBeginningOfSentenceArray, session.mOutputSuggestionCount,
- session.mOutputCodePoints, session.mOutputScores, session.mSpaceIndices,
- session.mOutputTypes, session.mOutputAutoCommitFirstWordConfidence,
- session.mInputOutputLanguageWeight);
- if (inOutLanguageWeight != null) {
- inOutLanguageWeight[0] = session.mInputOutputLanguageWeight[0];
+ session.mIsBeginningOfSentenceArray, ngramContext.getPrevWordCount(),
+ session.mOutputSuggestionCount, session.mOutputCodePoints, session.mOutputScores,
+ session.mSpaceIndices, session.mOutputTypes,
+ session.mOutputAutoCommitFirstWordConfidence,
+ session.mInputOutputWeightOfLangModelVsSpatialModel);
+ if (inOutWeightOfLangModelVsSpatialModel != null) {
+ inOutWeightOfLangModelVsSpatialModel[0] =
+ session.mInputOutputWeightOfLangModelVsSpatialModel[0];
}
final int count = session.mOutputSuggestionCount[0];
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
@@ -317,7 +323,8 @@ public final class BinaryDictionary extends Dictionary {
if (len > 0) {
suggestions.add(new SuggestedWordInfo(
new String(session.mOutputCodePoints, start, len),
- session.mOutputScores[j], session.mOutputTypes[j], this /* sourceDict */,
+ (int)(session.mOutputScores[j] * weightForLocale), session.mOutputTypes[j],
+ this /* sourceDict */,
session.mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
session.mOutputAutoCommitFirstWordConfidence[0]));
}
@@ -353,18 +360,17 @@ public final class BinaryDictionary extends Dictionary {
}
@UsedForTesting
- public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
- return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY;
+ public boolean isValidNgram(final NgramContext ngramContext, final String word) {
+ return getNgramProbability(ngramContext, word) != NOT_A_PROBABILITY;
}
- public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) {
- if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
+ public int getNgramProbability(final NgramContext ngramContext, final String word) {
+ if (!ngramContext.isValid() || TextUtils.isEmpty(word)) {
return NOT_A_PROBABILITY;
}
- final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
- final boolean[] isBeginningOfSentenceArray =
- new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
- prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
+ final int[][] prevWordCodePointArrays = new int[ngramContext.getPrevWordCount()][];
+ final boolean[] isBeginningOfSentenceArray = new boolean[ngramContext.getPrevWordCount()];
+ ngramContext.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
final int[] wordCodePoints = StringUtils.toCodePointArray(word);
return getNgramProbabilityNative(mNativeDict, prevWordCodePointArrays,
isBeginningOfSentenceArray, wordCodePoints);
@@ -453,15 +459,14 @@ public final class BinaryDictionary extends Dictionary {
}
// Add an n-gram entry to the binary dictionary with timestamp in native code.
- public boolean addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
+ public boolean addNgramEntry(final NgramContext ngramContext, final String word,
final int probability, final int timestamp) {
- if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
+ if (!ngramContext.isValid() || TextUtils.isEmpty(word)) {
return false;
}
- final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
- final boolean[] isBeginningOfSentenceArray =
- new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
- prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
+ final int[][] prevWordCodePointArrays = new int[ngramContext.getPrevWordCount()][];
+ final boolean[] isBeginningOfSentenceArray = new boolean[ngramContext.getPrevWordCount()];
+ ngramContext.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
final int[] wordCodePoints = StringUtils.toCodePointArray(word);
if (!addNgramEntryNative(mNativeDict, prevWordCodePointArrays,
isBeginningOfSentenceArray, wordCodePoints, probability, timestamp)) {
@@ -472,14 +477,13 @@ public final class BinaryDictionary extends Dictionary {
}
// Remove an n-gram entry from the binary dictionary in native code.
- public boolean removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) {
- if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
+ public boolean removeNgramEntry(final NgramContext ngramContext, final String word) {
+ if (!ngramContext.isValid() || TextUtils.isEmpty(word)) {
return false;
}
- final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
- final boolean[] isBeginningOfSentenceArray =
- new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
- prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
+ final int[][] prevWordCodePointArrays = new int[ngramContext.getPrevWordCount()][];
+ final boolean[] isBeginningOfSentenceArray = new boolean[ngramContext.getPrevWordCount()];
+ ngramContext.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
final int[] wordCodePoints = StringUtils.toCodePointArray(word);
if (!removeNgramEntryNative(mNativeDict, prevWordCodePointArrays,
isBeginningOfSentenceArray, wordCodePoints)) {
@@ -489,6 +493,7 @@ public final class BinaryDictionary extends Dictionary {
return true;
}
+ @UsedForTesting
public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
if (!isValidDictionary()) return;
int processedParamCount = 0;
@@ -596,7 +601,7 @@ public final class BinaryDictionary extends Dictionary {
}
@UsedForTesting
- public String getPropertyForTest(final String query) {
+ public String getPropertyForGettingStats(final String query) {
if (!isValidDictionary()) return "";
return getPropertyNative(mNativeDict, query);
}
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 43af66eb7..fc7f95c7b 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -57,6 +57,13 @@ public final class Constants {
@SuppressWarnings("dep-ann")
public static final String FORCE_ASCII = "forceAscii";
+ /**
+ * The private IME option used to suppress the floating gesture preview for a given text
+ * field. This overrides the corresponding keyboard settings preference.
+ * {@link com.android.inputmethod.latin.settings.SettingsValues#mGestureFloatingPreviewTextEnabled}
+ */
+ public static final String NO_FLOATING_GESTURE_PREVIEW = "noGestureFloatingPreview";
+
private ImeOption() {
// This utility class is not publicly instantiable.
}
@@ -217,10 +224,12 @@ public final class Constants {
public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
public static final int CODE_INVERTED_QUESTION_MARK = 0xBF; // ¿
public static final int CODE_INVERTED_EXCLAMATION_MARK = 0xA1; // ¡
+ public static final int CODE_GRAVE_ACCENT = '`';
+ public static final int CODE_CIRCUMFLEX_ACCENT = '^';
+ public static final int CODE_TILDE = '~';
public static final String REGEXP_PERIOD = "\\.";
public static final String STRING_SPACE = " ";
- public static final String STRING_PERIOD_AND_SPACE = ". ";
/**
* Special keys code. Must be negative.
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 162a209e3..78c6cbd24 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -218,7 +218,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
*/
private void addNameLocked(final String name) {
int len = StringUtils.codePointCount(name);
- PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ NgramContext ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO;
// TODO: Better tokenization for non-Latin writing systems
for (int i = 0; i < len; i++) {
if (Character.isLetter(name.codePointAt(i))) {
@@ -233,19 +233,19 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
final int wordLen = StringUtils.codePointCount(word);
if (wordLen <= MAX_WORD_LENGTH && wordLen > 1) {
if (DEBUG) {
- Log.d(TAG, "addName " + name + ", " + word + ", " + prevWordsInfo);
+ Log.d(TAG, "addName " + name + ", " + word + ", " + ngramContext);
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
addUnigramLocked(word, FREQUENCY_FOR_CONTACTS,
null /* shortcut */, 0 /* shortcutFreq */, false /* isNotAWord */,
false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
- if (!prevWordsInfo.isValid() && mUseFirstLastBigrams) {
+ if (!ngramContext.isValid() && mUseFirstLastBigrams) {
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addNgramEntryLocked(prevWordsInfo, word, FREQUENCY_FOR_CONTACTS_BIGRAM,
+ addNgramEntryLocked(ngramContext, word, FREQUENCY_FOR_CONTACTS_BIGRAM,
BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
- prevWordsInfo = prevWordsInfo.getNextPrevWordsInfo(
- new PrevWordsInfo.WordInfo(word));
+ ngramContext = ngramContext.getNextNgramContext(
+ new NgramContext.WordInfo(word));
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index b341f623e..2751c1250 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -40,7 +40,7 @@ public final class DicTraverseSession {
public final int[] mOutputTypes = new int[MAX_RESULTS];
// Only one result is ever used
public final int[] mOutputAutoCommitFirstWordConfidence = new int[1];
- public final float[] mInputOutputLanguageWeight = new float[1];
+ public final float[] mInputOutputWeightOfLangModelVsSpatialModel = new float[1];
public final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 560ced9c4..43561ba4b 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -16,12 +16,14 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Arrays;
+import java.util.HashSet;
/**
* Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key
@@ -29,7 +31,7 @@ import java.util.ArrayList;
*/
public abstract class Dictionary {
public static final int NOT_A_PROBABILITY = -1;
- public static final float NOT_A_LANGUAGE_WEIGHT = -1.0f;
+ public static final float NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL = -1.0f;
// The following types do not actually come from real dictionary instances, so we create
// corresponding instances.
@@ -62,28 +64,41 @@ public abstract class Dictionary {
// Contextual dictionary.
public static final String TYPE_CONTEXTUAL = "contextual";
public final String mDictType;
+ // The locale for this dictionary. May be null if unknown (phony dictionary for example).
+ public final Locale mLocale;
- public Dictionary(final String dictType) {
+ /**
+ * Set out of the dictionary types listed above that are based on data specific to the user,
+ * e.g., the user's contacts.
+ */
+ private static final HashSet<String> sUserSpecificDictionaryTypes =
+ new HashSet(Arrays.asList(new String[] { TYPE_USER_TYPED, TYPE_USER, TYPE_CONTACTS,
+ TYPE_USER_HISTORY, TYPE_PERSONALIZATION, TYPE_CONTEXTUAL }));
+
+ public Dictionary(final String dictType, final Locale locale) {
mDictType = dictType;
+ mLocale = locale;
}
/**
- * Searches for suggestions for a given context. For the moment the context is only the
- * previous word.
+ * Searches for suggestions for a given context.
* @param composer the key sequence to match with coordinate info, as a WordComposer
- * @param prevWordsInfo the information of previous words.
+ * @param ngramContext the context for n-gram.
* @param proximityInfo the object for key proximity. May be ignored by some implementations.
* @param settingsValuesForSuggestion the settings values used for the suggestion.
* @param sessionId the session id.
- * @param inOutLanguageWeight the language weight used for generating suggestions.
- * inOutLanguageWeight is a float array that has only one element. This can be updated when the
- * different language weight is used.
+ * @param weightForLocale the weight given to this locale, to multiply the output scores for
+ * multilingual input.
+ * @param inOutWeightOfLangModelVsSpatialModel the weight of the language model as a ratio of
+ * the spatial model, used for generating suggestions. inOutWeightOfLangModelVsSpatialModel is
+ * a float array that has only one element. This can be updated when a different value is used.
* @return the list of suggestions (possibly null if none)
*/
abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int sessionId, final float[] inOutLanguageWeight);
+ final int sessionId, final float weightForLocale,
+ final float[] inOutWeightOfLangModelVsSpatialModel);
/**
* Checks if the given word has to be treated as a valid word. Please note that some
@@ -156,20 +171,29 @@ public abstract class Dictionary {
}
/**
+ * Whether this dictionary is based on data specific to the user, e.g., the user's contacts.
+ * @return Whether this dictionary is specific to the user.
+ */
+ public boolean isUserSpecific() {
+ return sUserSpecificDictionaryTypes.contains(mDictType);
+ }
+
+ /**
* Not a true dictionary. A placeholder used to indicate suggestions that don't come from any
* real dictionary.
*/
private static class PhonyDictionary extends Dictionary {
// This class is not publicly instantiable.
private PhonyDictionary(final String type) {
- super(type);
+ super(type, null);
}
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int sessionId, final float[] inOutLanguageWeight) {
+ final int sessionId, final float weightForLocale,
+ final float[] inOutWeightOfLangModelVsSpatialModel) {
return null;
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 2b4c54d48..a6d7205e2 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -25,6 +25,7 @@ import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -34,13 +35,14 @@ public final class DictionaryCollection extends Dictionary {
private final String TAG = DictionaryCollection.class.getSimpleName();
protected final CopyOnWriteArrayList<Dictionary> mDictionaries;
- public DictionaryCollection(final String dictType) {
- super(dictType);
+ public DictionaryCollection(final String dictType, final Locale locale) {
+ super(dictType, locale);
mDictionaries = new CopyOnWriteArrayList<>();
}
- public DictionaryCollection(final String dictType, final Dictionary... dictionaries) {
- super(dictType);
+ public DictionaryCollection(final String dictType, final Locale locale,
+ final Dictionary... dictionaries) {
+ super(dictType, locale);
if (null == dictionaries) {
mDictionaries = new CopyOnWriteArrayList<>();
} else {
@@ -49,30 +51,32 @@ public final class DictionaryCollection extends Dictionary {
}
}
- public DictionaryCollection(final String dictType, final Collection<Dictionary> dictionaries) {
- super(dictType);
+ public DictionaryCollection(final String dictType, final Locale locale,
+ final Collection<Dictionary> dictionaries) {
+ super(dictType, locale);
mDictionaries = new CopyOnWriteArrayList<>(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int sessionId, final float[] inOutLanguageWeight) {
+ final int sessionId, final float weightForLocale,
+ final float[] inOutWeightOfLangModelVsSpatialModel) {
final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries;
if (dictionaries.isEmpty()) return null;
// To avoid creating unnecessary objects, we get the list out of the first
// dictionary and add the rest to it if not null, hence the get(0)
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
- prevWordsInfo, proximityInfo, settingsValuesForSuggestion, sessionId,
- inOutLanguageWeight);
+ ngramContext, proximityInfo, settingsValuesForSuggestion, sessionId,
+ weightForLocale, inOutWeightOfLangModelVsSpatialModel);
if (null == suggestions) suggestions = new ArrayList<>();
final int length = dictionaries.size();
for (int i = 1; i < length; ++ i) {
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
- prevWordsInfo, proximityInfo, settingsValuesForSuggestion, sessionId,
- inOutLanguageWeight);
+ ngramContext, proximityInfo, settingsValuesForSuggestion, sessionId,
+ weightForLocale, inOutWeightOfLangModelVsSpatialModel);
if (null != sugg) suggestions.addAll(sugg);
}
return suggestions;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index fd1f51dd6..1f0317288 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -19,11 +19,13 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.PrevWordsInfo.WordInfo;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback;
+import com.android.inputmethod.latin.NgramContext.WordInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.personalization.ContextualDictionary;
import com.android.inputmethod.latin.personalization.PersonalizationDataChunk;
@@ -32,9 +34,9 @@ import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatchesAndSuggestions;
import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary;
import com.android.inputmethod.latin.utils.ExecutorUtils;
-import com.android.inputmethod.latin.utils.LanguageModelParam;
import com.android.inputmethod.latin.utils.SuggestionResults;
import java.io.File;
@@ -59,12 +61,13 @@ public class DictionaryFacilitator {
// dictionary.
private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
- private Dictionaries mDictionaries = new Dictionaries();
+ private DictionaryGroup[] mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
private boolean mIsUserDictEnabled = false;
- private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
- // To synchronize assigning mDictionaries to ensure closing dictionaries.
+ private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
+ // To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
private final Object mLock = new Object();
private final DistracterFilter mDistracterFilter;
+ private final PersonalizationHelperForDictionaryFacilitator mPersonalizationHelper;
private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS =
new String[] {
@@ -96,22 +99,47 @@ public class DictionaryFacilitator {
DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length);
/**
- * Class contains dictionaries for a locale.
+ * Returns whether this facilitator is exactly for this list of locales.
+ * @param locales the list of locales to test against
+ * @return true if this facilitator handles exactly this list of locales, false otherwise
*/
- private static class Dictionaries {
+ public boolean isForLocales(final Locale[] locales) {
+ if (locales.length != mDictionaryGroups.length) {
+ return false;
+ }
+ for (final Locale locale : locales) {
+ boolean found = false;
+ for (final DictionaryGroup group : mDictionaryGroups) {
+ if (locale.equals(group.mLocale)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * A group of dictionaries that work together for a single language.
+ */
+ private static class DictionaryGroup {
public final Locale mLocale;
private Dictionary mMainDict;
+ public float mWeightForLocale = 1.0f;
public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
new ConcurrentHashMap<>();
- public Dictionaries() {
+ public DictionaryGroup() {
mLocale = null;
}
- public Dictionaries(final Locale locale, final Dictionary mainDict,
+ public DictionaryGroup(final Locale locale, final Dictionary mainDict,
final Map<String, ExpandableBinaryDictionary> subDicts) {
mLocale = locale;
- // Main dictionary can be asynchronously loaded.
+ // The main dictionary can be asynchronously loaded.
setMainDict(mainDict);
for (final Map.Entry<String, ExpandableBinaryDictionary> entry : subDicts.entrySet()) {
setSubDict(entry.getKey(), entry.getValue());
@@ -172,18 +200,39 @@ public class DictionaryFacilitator {
public DictionaryFacilitator() {
mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER;
+ mPersonalizationHelper = null;
}
- public DictionaryFacilitator(final DistracterFilter distracterFilter) {
- mDistracterFilter = distracterFilter;
+ public DictionaryFacilitator(final Context context) {
+ mDistracterFilter = new DistracterFilterCheckingExactMatchesAndSuggestions(context);
+ mPersonalizationHelper =
+ new PersonalizationHelperForDictionaryFacilitator(context, mDistracterFilter);
}
public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
mDistracterFilter.updateEnabledSubtypes(enabledSubtypes);
+ mPersonalizationHelper.updateEnabledSubtypes(enabledSubtypes);
+ }
+
+ public void setIsMonolingualUser(final boolean isMonolingualUser) {
+ mPersonalizationHelper.setIsMonolingualUser(isMonolingualUser);
}
+ // TODO: remove this, replace with version returning multiple locales
public Locale getLocale() {
- return mDictionaries.mLocale;
+ return mDictionaryGroups[0].mLocale;
+ }
+
+ /**
+ * Returns the primary locale among all currently active locales. BE CAREFUL using this.
+ *
+ * DO NOT USE THIS just because it's convenient. Use it when it's correct, for example when
+ * choosing what dictionary to put a word in, or when changing the capitalization of a typed
+ * string.
+ * @return the primary active locale
+ */
+ public Locale getPrimaryLocale() {
+ return mDictionaryGroups[0].mLocale;
}
private static ExpandableBinaryDictionary getSubDict(final String dictType,
@@ -215,97 +264,151 @@ public class DictionaryFacilitator {
usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */);
}
- public void resetDictionariesWithDictNamePrefix(final Context context, final Locale newLocale,
+ private DictionaryGroup findDictionaryGroupWithLocale(final DictionaryGroup[] dictionaryGroups,
+ final Locale locale) {
+ for (int i = 0; i < dictionaryGroups.length; ++i) {
+ if (locale.equals(dictionaryGroups[i].mLocale)) {
+ return dictionaryGroups[i];
+ }
+ }
+ return null;
+ }
+
+ private DictionaryGroup getDictionaryGroupForActiveLanguage() {
+ // TODO: implement this
+ return mDictionaryGroups[0];
+ }
+
+ public void resetDictionariesWithDictNamePrefix(final Context context,
+ final Locale newLocaleToUse,
final boolean useContactsDict, final boolean usePersonalizedDicts,
final boolean forceReloadMainDictionary,
final DictionaryInitializationListener listener,
final String dictNamePrefix) {
- final boolean localeHasBeenChanged = !newLocale.equals(mDictionaries.mLocale);
- // We always try to have the main dictionary. Other dictionaries can be unused.
- final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary;
+ final HashMap<Locale, ArrayList<String>> existingDictsToCleanup = new HashMap<>();
+ // TODO: use several locales
+ final Locale[] newLocales = new Locale[] { newLocaleToUse };
// TODO: Make subDictTypesToUse configurable by resource or a static final list.
final HashSet<String> subDictTypesToUse = new HashSet<>();
+ subDictTypesToUse.add(Dictionary.TYPE_USER);
if (useContactsDict) {
subDictTypesToUse.add(Dictionary.TYPE_CONTACTS);
}
- subDictTypesToUse.add(Dictionary.TYPE_USER);
if (usePersonalizedDicts) {
subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION);
subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL);
}
- final Dictionary newMainDict;
- if (reloadMainDictionary) {
- // The main dictionary will be asynchronously loaded.
- newMainDict = null;
- } else {
- newMainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
- }
-
- final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
- for (final String dictType : SUB_DICT_TYPES) {
- if (!subDictTypesToUse.contains(dictType)) {
- // This dictionary will not be used.
+ // Gather all dictionaries. We'll remove them from the list to clean up later.
+ for (final Locale newLocale : newLocales) {
+ final ArrayList<String> dictsForLocale = new ArrayList<>();
+ existingDictsToCleanup.put(newLocale, dictsForLocale);
+ final DictionaryGroup currentDictionaryGroupForLocale =
+ findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
+ if (null == currentDictionaryGroupForLocale) {
continue;
}
- final ExpandableBinaryDictionary dict;
- if (!localeHasBeenChanged && mDictionaries.hasDict(dictType)) {
- // Continue to use current dictionary.
- dict = mDictionaries.getSubDict(dictType);
+ for (final String dictType : SUB_DICT_TYPES) {
+ if (currentDictionaryGroupForLocale.hasDict(dictType)) {
+ dictsForLocale.add(dictType);
+ }
+ }
+ if (currentDictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN)) {
+ dictsForLocale.add(Dictionary.TYPE_MAIN);
+ }
+ }
+
+ final DictionaryGroup[] newDictionaryGroups = new DictionaryGroup[newLocales.length];
+ for (int i = 0; i < newLocales.length; ++i) {
+ final Locale newLocale = newLocales[i];
+ final DictionaryGroup dictionaryGroupForLocale =
+ findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
+ final ArrayList<String> dictsToCleanupForLocale = existingDictsToCleanup.get(newLocale);
+ final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale);
+
+ final Dictionary mainDict;
+ if (forceReloadMainDictionary || noExistingDictsForThisLocale
+ || !dictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN)) {
+ mainDict = null;
} else {
- // Start to use new dictionary.
- dict = getSubDict(dictType, context, newLocale, null /* dictFile */,
- dictNamePrefix);
+ mainDict = dictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN);
+ dictsToCleanupForLocale.remove(Dictionary.TYPE_MAIN);
+ }
+
+ final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
+ for (final String subDictType : subDictTypesToUse) {
+ final ExpandableBinaryDictionary subDict;
+ if (noExistingDictsForThisLocale
+ || !dictionaryGroupForLocale.hasDict(subDictType)) {
+ // Create a new dictionary.
+ subDict = getSubDict(subDictType, context, newLocale, null /* dictFile */,
+ dictNamePrefix);
+ } else {
+ // Reuse the existing dictionary, and don't close it at the end
+ subDict = dictionaryGroupForLocale.getSubDict(subDictType);
+ dictsToCleanupForLocale.remove(subDictType);
+ }
+ subDicts.put(subDictType, subDict);
}
- subDicts.put(dictType, dict);
+ newDictionaryGroups[i] = new DictionaryGroup(newLocale, mainDict, subDicts);
}
// Replace Dictionaries.
- final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict, subDicts);
- final Dictionaries oldDictionaries;
+ final DictionaryGroup[] oldDictionaryGroups;
synchronized (mLock) {
- oldDictionaries = mDictionaries;
- mDictionaries = newDictionaries;
+ oldDictionaryGroups = mDictionaryGroups;
+ mDictionaryGroups = newDictionaryGroups;
mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context);
- if (reloadMainDictionary) {
- asyncReloadMainDictionary(context, newLocale, listener);
+ if (hasAtLeastOneUninitializedMainDictionary()) {
+ asyncReloadUninitializedMainDictionaries(context, newLocales, listener);
}
}
if (listener != null) {
- listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
+ listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
}
+
// Clean up old dictionaries.
- if (reloadMainDictionary) {
- oldDictionaries.closeDict(Dictionary.TYPE_MAIN);
- }
- for (final String dictType : SUB_DICT_TYPES) {
- if (localeHasBeenChanged || !subDictTypesToUse.contains(dictType)) {
- oldDictionaries.closeDict(dictType);
+ for (final Locale localeToCleanUp : existingDictsToCleanup.keySet()) {
+ final ArrayList<String> dictTypesToCleanUp =
+ existingDictsToCleanup.get(localeToCleanUp);
+ final DictionaryGroup dictionarySetToCleanup =
+ findDictionaryGroupWithLocale(oldDictionaryGroups, localeToCleanUp);
+ for (final String dictType : dictTypesToCleanUp) {
+ dictionarySetToCleanup.closeDict(dictType);
}
}
- oldDictionaries.mSubDictMap.clear();
}
- private void asyncReloadMainDictionary(final Context context, final Locale locale,
- final DictionaryInitializationListener listener) {
+ private void asyncReloadUninitializedMainDictionaries(final Context context,
+ final Locale[] locales, final DictionaryInitializationListener listener) {
final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
- mLatchForWaitingLoadingMainDictionary = latchForWaitingLoadingMainDictionary;
+ mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary;
ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() {
@Override
public void run() {
- final Dictionary mainDict =
- DictionaryFactory.createMainDictionaryFromManager(context, locale);
- synchronized (mLock) {
- if (locale.equals(mDictionaries.mLocale)) {
- mDictionaries.setMainDict(mainDict);
- } else {
- // Dictionary facilitator has been reset for another locale.
- mainDict.close();
+ for (final Locale locale : locales) {
+ final DictionaryGroup dictionaryGroup =
+ findDictionaryGroupWithLocale(mDictionaryGroups, locale);
+ if (null == dictionaryGroup) {
+ // This should never happen, but better safe than crashy
+ Log.w(TAG, "Expected a dictionary group for " + locale + " but none found");
+ continue;
+ }
+ final Dictionary mainDict =
+ DictionaryFactory.createMainDictionaryFromManager(context, locale);
+ synchronized (mLock) {
+ if (locale.equals(dictionaryGroup.mLocale)) {
+ dictionaryGroup.setMainDict(mainDict);
+ } else {
+ // Dictionary facilitator has been reset for another locale.
+ mainDict.close();
+ }
}
}
if (listener != null) {
- listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
+ listener.onUpdateMainDictionaryAvailability(
+ hasAtLeastOneInitializedMainDictionary());
}
latchForWaitingLoadingMainDictionary.countDown();
}
@@ -338,57 +441,94 @@ public class DictionaryFacilitator {
subDicts.put(dictType, dict);
}
}
- mDictionaries = new Dictionaries(locale, mainDictionary, subDicts);
+ mDictionaryGroups = new DictionaryGroup[] {
+ new DictionaryGroup(locale, mainDictionary, subDicts) };
}
public void closeDictionaries() {
- final Dictionaries dictionaries;
+ final DictionaryGroup[] dictionaryGroups;
synchronized (mLock) {
- dictionaries = mDictionaries;
- mDictionaries = new Dictionaries();
+ dictionaryGroups = mDictionaryGroups;
+ mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
}
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- dictionaries.closeDict(dictType);
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ dictionaryGroup.closeDict(dictType);
+ }
}
mDistracterFilter.close();
+ if (mPersonalizationHelper != null) {
+ mPersonalizationHelper.close();
+ }
}
@UsedForTesting
public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
- return mDictionaries.getSubDict(dictName);
+ return mDictionaryGroups[0].getSubDict(dictName);
}
- // The main dictionary could have been loaded asynchronously. Don't cache the return value
- // of this method.
- public boolean hasInitializedMainDictionary() {
- final Dictionary mainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
- return mainDict != null && mainDict.isInitialized();
+ // The main dictionaries are loaded asynchronously. Don't cache the return value
+ // of these methods.
+ public boolean hasAtLeastOneInitializedMainDictionary() {
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
+ if (mainDict != null && mainDict.isInitialized()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasAtLeastOneUninitializedMainDictionary() {
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
+ if (mainDict == null || !mainDict.isInitialized()) {
+ return true;
+ }
+ }
+ return false;
}
public boolean hasPersonalizationDictionary() {
- return mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION);
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ if (dictionaryGroup.hasDict(Dictionary.TYPE_PERSONALIZATION)) {
+ return true;
+ }
+ }
+ return false;
}
public void flushPersonalizationDictionary() {
- final ExpandableBinaryDictionary personalizationDict =
- mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
- if (personalizationDict != null) {
- personalizationDict.asyncFlushBinaryDictionary();
- }
+ final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion =
+ new HashSet<>();
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final ExpandableBinaryDictionary personalizationDictUsedForSuggestion =
+ dictionaryGroup.getSubDict(Dictionary.TYPE_PERSONALIZATION);
+ personalizationDictsUsedForSuggestion.add(personalizationDictUsedForSuggestion);
+ }
+ mPersonalizationHelper.flushPersonalizationDictionariesToUpdate(
+ personalizationDictsUsedForSuggestion);
+ mDistracterFilter.close();
}
- public void waitForLoadingMainDictionary(final long timeout, final TimeUnit unit)
+ public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
throws InterruptedException {
- mLatchForWaitingLoadingMainDictionary.await(timeout, unit);
+ mLatchForWaitingLoadingMainDictionaries.await(timeout, unit);
}
@UsedForTesting
public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
throws InterruptedException {
- waitForLoadingMainDictionary(timeout, unit);
- final Map<String, ExpandableBinaryDictionary> dictMap = mDictionaries.mSubDictMap;
- for (final ExpandableBinaryDictionary dict : dictMap.values()) {
- dict.waitAllTasksForTests();
+ waitForLoadingMainDictionaries(timeout, unit);
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final ExpandableBinaryDictionary dict : dictionaryGroup.mSubDictMap.values()) {
+ dict.waitAllTasksForTests();
+ }
}
}
@@ -405,26 +545,26 @@ public class DictionaryFacilitator {
}
public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
- final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds,
+ final NgramContext ngramContext, final int timeStampInSeconds,
final boolean blockPotentiallyOffensive) {
- final Dictionaries dictionaries = mDictionaries;
+ final DictionaryGroup dictionaryGroup = getDictionaryGroupForActiveLanguage();
final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
- PrevWordsInfo prevWordsInfoForCurrentWord = prevWordsInfo;
+ NgramContext ngramContextForCurrentWord = ngramContext;
for (int i = 0; i < words.length; i++) {
final String currentWord = words[i];
final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
- addWordToUserHistory(dictionaries, prevWordsInfoForCurrentWord, currentWord,
+ addWordToUserHistory(dictionaryGroup, ngramContextForCurrentWord, currentWord,
wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive);
- prevWordsInfoForCurrentWord =
- prevWordsInfoForCurrentWord.getNextPrevWordsInfo(new WordInfo(currentWord));
+ ngramContextForCurrentWord =
+ ngramContextForCurrentWord.getNextNgramContext(new WordInfo(currentWord));
}
}
- private void addWordToUserHistory(final Dictionaries dictionaries,
- final PrevWordsInfo prevWordsInfo, final String word, final boolean wasAutoCapitalized,
+ private void addWordToUserHistory(final DictionaryGroup dictionaryGroup,
+ final NgramContext ngramContext, final String word, final boolean wasAutoCapitalized,
final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
final ExpandableBinaryDictionary userHistoryDictionary =
- dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
+ dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY);
if (userHistoryDictionary == null) {
return;
}
@@ -432,7 +572,7 @@ public class DictionaryFacilitator {
if (maxFreq == 0 && blockPotentiallyOffensive) {
return;
}
- final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
+ final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
final String secondWord;
if (wasAutoCapitalized) {
if (isValidWord(word, false /* ignoreCase */)
@@ -453,8 +593,8 @@ public class DictionaryFacilitator {
// History dictionary in order to avoid suggesting them until the dictionary
// consolidation is done.
// TODO: Remove this hack when ready.
- final int lowerCaseFreqInMainDict = dictionaries.hasDict(Dictionary.TYPE_MAIN) ?
- dictionaries.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
+ final int lowerCaseFreqInMainDict = dictionaryGroup.hasDict(Dictionary.TYPE_MAIN) ?
+ dictionaryGroup.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
Dictionary.NOT_A_PROBABILITY;
if (maxFreq < lowerCaseFreqInMainDict
&& lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
@@ -467,14 +607,15 @@ public class DictionaryFacilitator {
// We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
// We don't add words with 0-frequency (assuming they would be profanity etc.).
final boolean isValid = maxFreq > 0;
- UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWordsInfo, secondWord,
+ UserHistoryDictionary.addToDictionary(userHistoryDictionary, ngramContext, secondWord,
isValid, timeStampInSeconds,
new DistracterFilterCheckingIsInDictionary(
mDistracterFilter, userHistoryDictionary));
}
private void removeWord(final String dictName, final String word) {
- final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName);
+ final ExpandableBinaryDictionary dictionary =
+ getDictionaryGroupForActiveLanguage().getSubDict(dictName);
if (dictionary != null) {
dictionary.removeUnigramEntryDynamically(word);
}
@@ -488,23 +629,26 @@ public class DictionaryFacilitator {
// TODO: Revise the way to fusion suggestion results.
public SuggestionResults getSuggestionResults(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) {
- final Dictionaries dictionaries = mDictionaries;
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
final SuggestionResults suggestionResults = new SuggestionResults(
- dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS,
- prevWordsInfo.mPrevWordsInfo[0].mIsBeginningOfSentence);
- final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT };
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaries.getDict(dictType);
- if (null == dictionary) continue;
- final ArrayList<SuggestedWordInfo> dictionarySuggestions =
- dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
- settingsValuesForSuggestion, sessionId, languageWeight);
- if (null == dictionarySuggestions) continue;
- suggestionResults.addAll(dictionarySuggestions);
- if (null != suggestionResults.mRawSuggestions) {
- suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
+ SuggestedWords.MAX_SUGGESTIONS, ngramContext.isBeginningOfSentenceContext());
+ final float[] weightOfLangModelVsSpatialModel =
+ new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL };
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+ if (null == dictionary) continue;
+ final ArrayList<SuggestedWordInfo> dictionarySuggestions =
+ dictionary.getSuggestions(composer, ngramContext, proximityInfo,
+ settingsValuesForSuggestion, sessionId,
+ dictionaryGroup.mWeightForLocale, weightOfLangModelVsSpatialModel);
+ if (null == dictionarySuggestions) continue;
+ suggestionResults.addAll(dictionarySuggestions);
+ if (null != suggestionResults.mRawSuggestions) {
+ suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
+ }
}
}
return suggestionResults;
@@ -514,20 +658,22 @@ public class DictionaryFacilitator {
if (TextUtils.isEmpty(word)) {
return false;
}
- final Dictionaries dictionaries = mDictionaries;
- if (dictionaries.mLocale == null) {
- return false;
- }
- final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaries.getDict(dictType);
- // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
- // would be immutable once it's finished initializing, but concretely a null test is
- // probably good enough for the time being.
- if (null == dictionary) continue;
- if (dictionary.isValidWord(word)
- || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
- return true;
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ if (dictionaryGroup.mLocale == null) {
+ continue;
+ }
+ final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+ // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
+ // would be immutable once it's finished initializing, but concretely a null test is
+ // probably good enough for the time being.
+ if (null == dictionary) continue;
+ if (dictionary.isValidWord(word)
+ || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
+ return true;
+ }
}
}
return false;
@@ -539,18 +685,20 @@ public class DictionaryFacilitator {
return Dictionary.NOT_A_PROBABILITY;
}
int maxFreq = Dictionary.NOT_A_PROBABILITY;
- final Dictionaries dictionaries = mDictionaries;
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaries.getDict(dictType);
- if (dictionary == null) continue;
- final int tempFreq;
- if (isGettingMaxFrequencyOfExactMatches) {
- tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
- } else {
- tempFreq = dictionary.getFrequency(word);
- }
- if (tempFreq >= maxFreq) {
- maxFreq = tempFreq;
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+ if (dictionary == null) continue;
+ final int tempFreq;
+ if (isGettingMaxFrequencyOfExactMatches) {
+ tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
+ } else {
+ tempFreq = dictionary.getFrequency(word);
+ }
+ if (tempFreq >= maxFreq) {
+ maxFreq = tempFreq;
+ }
}
}
return maxFreq;
@@ -565,9 +713,12 @@ public class DictionaryFacilitator {
}
private void clearSubDictionary(final String dictName) {
- final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName);
- if (dictionary != null) {
- dictionary.clear();
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictName);
+ if (dictionary != null) {
+ dictionary.clear();
+ }
}
}
@@ -579,6 +730,7 @@ public class DictionaryFacilitator {
// personalization dictionary.
public void clearPersonalizationDictionary() {
clearSubDictionary(Dictionary.TYPE_PERSONALIZATION);
+ mPersonalizationHelper.clearDictionariesToUpdate();
}
public void clearContextualDictionary() {
@@ -588,39 +740,22 @@ public class DictionaryFacilitator {
public void addEntriesToPersonalizationDictionary(
final PersonalizationDataChunk personalizationDataChunk,
final SpacingAndPunctuations spacingAndPunctuations,
- final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
- final ExpandableBinaryDictionary personalizationDict =
- mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
- if (personalizationDict == null) {
- if (callback != null) {
- callback.onFinished();
- }
- return;
- }
- final ArrayList<LanguageModelParam> languageModelParams =
- LanguageModelParam.createLanguageModelParamsFrom(
- personalizationDataChunk.mTokens,
- personalizationDataChunk.mTimestampInSeconds,
- this /* dictionaryFacilitator */, spacingAndPunctuations,
- new DistracterFilterCheckingIsInDictionary(
- mDistracterFilter, personalizationDict));
- if (languageModelParams == null || languageModelParams.isEmpty()) {
- if (callback != null) {
- callback.onFinished();
- }
- return;
- }
- personalizationDict.addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
+ final AddMultipleDictionaryEntriesCallback callback) {
+ mPersonalizationHelper.addEntriesToPersonalizationDictionariesToUpdate(
+ getLocale(), personalizationDataChunk, spacingAndPunctuations, callback);
}
+ @UsedForTesting
public void addPhraseToContextualDictionary(final String[] phrase, final int probability,
final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) {
+ // TODO: we're inserting the phrase into the dictionary for the active language. Rethink
+ // this a bit from a theoretical point of view.
final ExpandableBinaryDictionary contextualDict =
- mDictionaries.getSubDict(Dictionary.TYPE_CONTEXTUAL);
+ getDictionaryGroupForActiveLanguage().getSubDict(Dictionary.TYPE_CONTEXTUAL);
if (contextualDict == null) {
return;
}
- PrevWordsInfo prevWordsInfo = PrevWordsInfo.BEGINNING_OF_SENTENCE;
+ NgramContext ngramContext = NgramContext.BEGINNING_OF_SENTENCE;
for (int i = 0; i < phrase.length; i++) {
final String[] subPhrase = Arrays.copyOfRange(phrase, i /* start */, phrase.length);
final String subPhraseStr = TextUtils.join(Constants.WORD_SEPARATOR, subPhrase);
@@ -630,7 +765,7 @@ public class DictionaryFacilitator {
false /* isNotAWord */, false /* isBlacklisted */,
BinaryDictionary.NOT_A_VALID_TIMESTAMP,
DistracterFilter.EMPTY_DISTRACTER_FILTER);
- contextualDict.addNgramEntry(prevWordsInfo, subPhraseStr,
+ contextualDict.addNgramEntry(ngramContext, subPhraseStr,
bigramProbabilityForPhrases, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
if (i < phrase.length - 1) {
@@ -640,21 +775,37 @@ public class DictionaryFacilitator {
false /* isNotAWord */, false /* isBlacklisted */,
BinaryDictionary.NOT_A_VALID_TIMESTAMP,
DistracterFilter.EMPTY_DISTRACTER_FILTER);
- contextualDict.addNgramEntry(prevWordsInfo, phrase[i],
+ contextualDict.addNgramEntry(ngramContext, phrase[i],
bigramProbabilityForWords, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
- prevWordsInfo =
- prevWordsInfo.getNextPrevWordsInfo(new PrevWordsInfo.WordInfo(phrase[i]));
+ ngramContext =
+ ngramContext.getNextNgramContext(new NgramContext.WordInfo(phrase[i]));
}
}
public void dumpDictionaryForDebug(final String dictName) {
- final ExpandableBinaryDictionary dictToDump = mDictionaries.getSubDict(dictName);
- if (dictToDump == null) {
- Log.e(TAG, "Cannot dump " + dictName + ". "
- + "The dictionary is not being used for suggestion or cannot be dumped.");
- return;
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final ExpandableBinaryDictionary dictToDump = dictionaryGroup.getSubDict(dictName);
+ if (dictToDump == null) {
+ Log.e(TAG, "Cannot dump " + dictName + ". "
+ + "The dictionary is not being used for suggestion or cannot be dumped.");
+ return;
+ }
+ dictToDump.dumpAllWordsForDebug();
+ }
+ }
+
+ public ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts() {
+ final ArrayList<Pair<String, DictionaryStats>> statsOfEnabledSubDicts = new ArrayList<>();
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : SUB_DICT_TYPES) {
+ final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictType);
+ if (dictionary == null) continue;
+ statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats()));
+ }
}
- dictToDump.dumpAllWordsForDebug();
+ return statsOfEnabledSubDicts;
}
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
new file mode 100644
index 000000000..ff4a6bde1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.LruCache;
+
+/**
+ * Cache for dictionary facilitators of multiple locales.
+ * This class automatically creates and releases facilitator instances using LRU policy.
+ */
+public class DictionaryFacilitatorLruCache {
+ private static final String TAG = DictionaryFacilitatorLruCache.class.getSimpleName();
+ private static final int WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS = 1000;
+ private static final int MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT = 5;
+
+ /**
+ * Class extends LruCache. This class tracks cached locales and closes evicted dictionaries by
+ * overriding entryRemoved.
+ */
+ private static class DictionaryFacilitatorLruCacheInner extends
+ LruCache<Locale, DictionaryFacilitator> {
+ private final HashSet<Locale> mCachedLocales;
+ public DictionaryFacilitatorLruCacheInner(final HashSet<Locale> cachedLocales,
+ final int maxSize) {
+ super(maxSize);
+ mCachedLocales = cachedLocales;
+ }
+
+ @Override
+ protected void entryRemoved(boolean evicted, Locale key,
+ DictionaryFacilitator oldValue, DictionaryFacilitator newValue) {
+ if (oldValue != null && oldValue != newValue) {
+ oldValue.closeDictionaries();
+ }
+ if (key != null && newValue == null) {
+ // Remove locale from the cache when the dictionary facilitator for the locale is
+ // evicted and new facilitator is not set for the locale.
+ mCachedLocales.remove(key);
+ if (size() >= maxSize()) {
+ Log.w(TAG, "DictionaryFacilitator for " + key.toString()
+ + " has been evicted due to cache size limit."
+ + " size: " + size() + ", maxSize: " + maxSize());
+ }
+ }
+ }
+ }
+
+ private final Context mContext;
+ private final HashSet<Locale> mCachedLocales = new HashSet<>();
+ private final String mDictionaryNamePrefix;
+ private final DictionaryFacilitatorLruCacheInner mLruCache;
+ private final Object mLock = new Object();
+ private boolean mUseContactsDictionary = false;
+
+ public DictionaryFacilitatorLruCache(final Context context, final int maxSize,
+ final String dictionaryNamePrefix) {
+ mContext = context;
+ mLruCache = new DictionaryFacilitatorLruCacheInner(mCachedLocales, maxSize);
+ mDictionaryNamePrefix = dictionaryNamePrefix;
+ }
+
+ private void waitForLoadingMainDictionary(final DictionaryFacilitator dictionaryFacilitator) {
+ for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) {
+ try {
+ dictionaryFacilitator.waitForLoadingMainDictionaries(
+ WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+ return;
+ } catch (final InterruptedException e) {
+ Log.i(TAG, "Interrupted during waiting for loading main dictionary.", e);
+ if (i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT - 1) {
+ Log.i(TAG, "Retry", e);
+ } else {
+ Log.w(TAG, "Give up retrying. Retried "
+ + MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT + " times.", e);
+ }
+ }
+ }
+ }
+
+ private void resetDictionariesForLocaleLocked(final DictionaryFacilitator dictionaryFacilitator,
+ final Locale locale) {
+ dictionaryFacilitator.resetDictionariesWithDictNamePrefix(mContext, locale,
+ mUseContactsDictionary, false /* usePersonalizedDicts */,
+ false /* forceReloadMainDictionary */, null /* listener */,
+ mDictionaryNamePrefix);
+ }
+
+ public void setUseContactsDictionary(final boolean useContectsDictionary) {
+ if (mUseContactsDictionary == useContectsDictionary) {
+ // The value has not been changed.
+ return;
+ }
+ synchronized (mLock) {
+ mUseContactsDictionary = useContectsDictionary;
+ for (final Locale locale : mCachedLocales) {
+ final DictionaryFacilitator dictionaryFacilitator = mLruCache.get(locale);
+ resetDictionariesForLocaleLocked(dictionaryFacilitator, locale);
+ waitForLoadingMainDictionary(dictionaryFacilitator);
+ }
+ }
+ }
+
+ public DictionaryFacilitator get(final Locale locale) {
+ DictionaryFacilitator dictionaryFacilitator = mLruCache.get(locale);
+ if (dictionaryFacilitator != null) {
+ // dictionary falicitator for the locale is in the cache.
+ return dictionaryFacilitator;
+ }
+ synchronized (mLock) {
+ dictionaryFacilitator = mLruCache.get(locale);
+ if (dictionaryFacilitator != null) {
+ return dictionaryFacilitator;
+ }
+ dictionaryFacilitator = new DictionaryFacilitator();
+ resetDictionariesForLocaleLocked(dictionaryFacilitator, locale);
+ waitForLoadingMainDictionary(dictionaryFacilitator);
+ mLruCache.put(locale, dictionaryFacilitator);
+ mCachedLocales.add(locale);
+ return dictionaryFacilitator;
+ }
+ }
+
+ public void evictAll() {
+ synchronized (mLock) {
+ mLruCache.evictAll();
+ mCachedLocales.clear();
+ }
+ }
+
+ @UsedForTesting
+ HashSet<Locale> getCachedLocalesForTesting() {
+ return mCachedLocales;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 59de4f82a..3459b426d 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -50,7 +50,7 @@ public final class DictionaryFactory {
final Locale locale, final boolean useFullEditDistance) {
if (null == locale) {
Log.e(TAG, "No locale defined for dictionary");
- return new DictionaryCollection(Dictionary.TYPE_MAIN,
+ return new DictionaryCollection(Dictionary.TYPE_MAIN, locale,
createReadOnlyBinaryDictionary(context, locale));
}
@@ -75,7 +75,7 @@ public final class DictionaryFactory {
// If the list is empty, that means we should not use any dictionary (for example, the user
// explicitly disabled the main dictionary), so the following is okay. dictList is never
// null, but if for some reason it is, DictionaryCollection handles it gracefully.
- return new DictionaryCollection(Dictionary.TYPE_MAIN, dictList);
+ return new DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList);
}
/**
@@ -188,7 +188,7 @@ public final class DictionaryFactory {
public static Dictionary createDictionaryForTest(final AssetFileAddress[] dictionaryList,
final boolean useFullEditDistance, Locale locale) {
final DictionaryCollection dictionaryCollection =
- new DictionaryCollection(Dictionary.TYPE_MAIN);
+ new DictionaryCollection(Dictionary.TYPE_MAIN, locale);
for (final AssetFileAddress address : dictionaryList) {
final ReadOnlyBinaryDictionary readOnlyBinaryDictionary = new ReadOnlyBinaryDictionary(
address.mFilename, address.mOffset, address.mLength, useFullEditDistance,
diff --git a/java/src/com/android/inputmethod/latin/DictionaryStats.java b/java/src/com/android/inputmethod/latin/DictionaryStats.java
new file mode 100644
index 000000000..5dd39d3d6
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryStats.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import java.io.File;
+import java.util.Locale;
+
+public class DictionaryStats {
+ public static final int NOT_AN_ENTRY_COUNT = -1;
+
+ public final Locale mLocale;
+ public final String mDictName;
+ public final String mDictFilePath;
+ public final long mDictFileSize;
+
+ public final int mUnigramCount;
+ public final int mNgramCount;
+ // TODO: Add more members.
+
+ public DictionaryStats(final Locale locale, final String dictName, final File dictFile,
+ final int unigramCount, final int ngramCount) {
+ mLocale = locale;
+ mDictName = dictName;
+ mDictFilePath = dictFile.getAbsolutePath();
+ mDictFileSize = dictFile.length();
+ mUnigramCount = unigramCount;
+ mNgramCount = ngramCount;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index c11a220a4..1bdadc30b 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -27,6 +27,7 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.makedict.WordProperty;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
+import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.CombinedFormatUtils;
import com.android.inputmethod.latin.utils.DistracterFilter;
import com.android.inputmethod.latin.utils.ExecutorUtils;
@@ -86,9 +87,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
private final String mDictName;
- /** Dictionary locale */
- private final Locale mLocale;
-
/** Dictionary file */
private final File mDictFile;
@@ -137,10 +135,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
public ExpandableBinaryDictionary(final Context context, final String dictName,
final Locale locale, final String dictType, final File dictFile) {
- super(dictType);
+ super(dictType, locale);
mDictName = dictName;
mContext = context;
- mLocale = locale;
mDictFile = getDictFile(context, dictName, dictFile);
mBinaryDictionary = null;
mIsReloading = new AtomicBoolean();
@@ -160,23 +157,25 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
private void asyncExecuteTaskWithWriteLock(final Runnable task) {
- asyncExecuteTaskWithLock(mLock.writeLock(), task);
+ asyncExecuteTaskWithLock(mLock.writeLock(), mDictName /* executorName */, task);
}
- private void asyncExecuteTaskWithLock(final Lock lock, final Runnable task) {
- asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, task);
+ private void asyncExecuteTaskWithLock(final Lock lock, final String executorName,
+ final Runnable task) {
+ asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, executorName, task);
}
private void asyncPreCheckAndExecuteTaskWithWriteLock(
final Callable<Boolean> preCheckTask, final Runnable task) {
- asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask, task);
+ asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask,
+ mDictName /* executorName */, task);
}
// Execute task with lock when the result of preCheckTask is true or preCheckTask is null.
private void asyncPreCheckAndExecuteTaskWithLock(final Lock lock,
- final Callable<Boolean> preCheckTask, final Runnable task) {
- ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
+ final Callable<Boolean> preCheckTask, final String executorName, final Runnable task) {
+ ExecutorUtils.getExecutor(executorName).execute(new Runnable() {
@Override
public void run() {
if (preCheckTask != null) {
@@ -306,7 +305,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
@Override
public Boolean call() throws Exception {
return !distracterFilter.isDistracterToWordsInDictionaries(
- PrevWordsInfo.EMPTY_PREV_WORDS_INFO, word, mLocale);
+ NgramContext.EMPTY_PREV_WORDS_INFO, word, mLocale);
}
},
new Runnable() {
@@ -355,7 +354,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/**
* Adds n-gram information of a word to the dictionary. May overwrite an existing entry.
*/
- public void addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
+ public void addNgramEntry(final NgramContext ngramContext, final String word,
final int frequency, final int timestamp) {
reloadDictionaryIfRequired();
asyncExecuteTaskWithWriteLock(new Runnable() {
@@ -365,17 +364,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return;
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addNgramEntryLocked(prevWordsInfo, word, frequency, timestamp);
+ addNgramEntryLocked(ngramContext, word, frequency, timestamp);
}
});
}
- protected void addNgramEntryLocked(final PrevWordsInfo prevWordsInfo, final String word,
+ protected void addNgramEntryLocked(final NgramContext ngramContext, final String word,
final int frequency, final int timestamp) {
- if (!mBinaryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp)) {
+ if (!mBinaryDictionary.addNgramEntry(ngramContext, word, frequency, timestamp)) {
if (DEBUG) {
Log.i(TAG, "Cannot add n-gram entry.");
- Log.i(TAG, " PrevWordsInfo: " + prevWordsInfo + ", word: " + word);
+ Log.i(TAG, " NgramContext: " + ngramContext + ", word: " + word);
}
}
}
@@ -384,7 +383,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* Dynamically remove the n-gram entry in the dictionary.
*/
@UsedForTesting
- public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word) {
+ public void removeNgramDynamically(final NgramContext ngramContext, final String word) {
reloadDictionaryIfRequired();
asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
@@ -393,10 +392,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return;
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- if (!mBinaryDictionary.removeNgramEntry(prevWordsInfo, word)) {
+ if (!mBinaryDictionary.removeNgramEntry(ngramContext, word)) {
if (DEBUG) {
Log.i(TAG, "Cannot remove n-gram entry.");
- Log.i(TAG, " PrevWordsInfo: " + prevWordsInfo + ", word: " + word);
+ Log.i(TAG, " NgramContext: " + ngramContext + ", word: " + word);
}
}
}
@@ -435,9 +434,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId,
- final float[] inOutLanguageWeight) {
+ final float weightForLocale, final float[] inOutWeightOfLangModelVsSpatialModel) {
reloadDictionaryIfRequired();
boolean lockAcquired = false;
try {
@@ -448,8 +447,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return null;
}
final ArrayList<SuggestedWordInfo> suggestions =
- mBinaryDictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
- settingsValuesForSuggestion, sessionId, inOutLanguageWeight);
+ mBinaryDictionary.getSuggestions(composer, ngramContext, proximityInfo,
+ settingsValuesForSuggestion, sessionId, weightForLocale,
+ inOutWeightOfLangModelVsSpatialModel);
if (mBinaryDictionary.isCorrupted()) {
Log.i(TAG, "Dictionary (" + mDictName +") is corrupted. "
+ "Remove and regenerate it.");
@@ -519,9 +519,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
- protected boolean isValidNgramLocked(final PrevWordsInfo prevWordsInfo, final String word) {
+ protected boolean isValidNgramLocked(final NgramContext ngramContext, final String word) {
if (mBinaryDictionary == null) return false;
- return mBinaryDictionary.isValidNgram(prevWordsInfo, word);
+ return mBinaryDictionary.isValidNgram(ngramContext, word);
}
/**
@@ -644,10 +644,45 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
});
}
+ private static int parseEntryCount(final String entryCountStr) {
+ int entryCount;
+ try {
+ entryCount = Integer.parseInt(entryCountStr);
+ } catch (final NumberFormatException e) {
+ entryCount = DictionaryStats.NOT_AN_ENTRY_COUNT;
+ }
+ return entryCount;
+ }
+
+ public DictionaryStats getDictionaryStats() {
+ reloadDictionaryIfRequired();
+ final AsyncResultHolder<DictionaryStats> result = new AsyncResultHolder<>();
+ asyncExecuteTaskWithLock(mLock.readLock(), mDictName /* executorName */, new Runnable() {
+ @Override
+ public void run() {
+ if (mBinaryDictionary == null) {
+ result.set(new DictionaryStats(mLocale, mDictName, mDictFile,
+ DictionaryStats.NOT_AN_ENTRY_COUNT,
+ DictionaryStats.NOT_AN_ENTRY_COUNT));
+ }
+ final int unigramCount = parseEntryCount(
+ mBinaryDictionary.getPropertyForGettingStats(
+ BinaryDictionary.MAX_UNIGRAM_COUNT_QUERY));
+ // TODO: Get dedicated entry counts for bigram, trigram, and so on.
+ final int ngramCount = parseEntryCount(mBinaryDictionary.getPropertyForGettingStats(
+ BinaryDictionary.MAX_BIGRAM_COUNT_QUERY));
+ // TODO: Get more information from dictionary.
+ result.set(new DictionaryStats(mLocale, mDictName, mDictFile, unigramCount,
+ ngramCount));
+ }
+ });
+ return result.get(null /* defaultValue */, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
+ }
+
@UsedForTesting
public void waitAllTasksForTests() {
final CountDownLatch countDownLatch = new CountDownLatch(1);
- ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
+ asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
public void run() {
countDownLatch.countDown();
@@ -669,10 +704,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
public void dumpAllWordsForDebug() {
reloadDictionaryIfRequired();
- asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
+ asyncExecuteTaskWithLock(mLock.readLock(), "dumpAllWordsForDebug", new Runnable() {
@Override
public void run() {
- Log.d(TAG, "Dump dictionary: " + mDictName);
+ Log.d(TAG, "Dump dictionary: " + mDictName + " for " + mLocale);
try {
final DictionaryHeader header = mBinaryDictionary.getHeader();
Log.d(TAG, "Format version: " + mBinaryDictionary.getFormatVersion());
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index fecb0ef94..ffd363b5d 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin;
+import static com.android.inputmethod.latin.Constants.ImeOption.NO_FLOATING_GESTURE_PREVIEW;
import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE;
import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT;
@@ -42,6 +43,12 @@ public final class InputAttributes {
final public boolean mApplicationSpecifiedCompletionOn;
final public boolean mShouldInsertSpacesAutomatically;
final public boolean mShouldShowVoiceInputKey;
+ /**
+ * Whether the floating gesture preview should be disabled. If true, this should override the
+ * corresponding keyboard settings preference, always suppressing the floating preview text.
+ * {@link com.android.inputmethod.latin.settings.SettingsValues#mGestureFloatingPreviewTextEnabled}
+ */
+ final public boolean mDisableGestureFloatingPreviewText;
final public boolean mIsGeneralTextInput;
final private int mInputType;
final private EditorInfo mEditorInfo;
@@ -77,6 +84,7 @@ public final class InputAttributes {
mApplicationSpecifiedCompletionOn = false;
mShouldInsertSpacesAutomatically = false;
mShouldShowVoiceInputKey = false;
+ mDisableGestureFloatingPreviewText = false;
mIsGeneralTextInput = false;
return;
}
@@ -109,6 +117,9 @@ public final class InputAttributes {
|| hasNoMicrophoneKeyOption();
mShouldShowVoiceInputKey = !noMicrophone;
+ mDisableGestureFloatingPreviewText = InputAttributes.inPrivateImeOptions(
+ mPackageNameForPrivateImeOptions, NO_FLOATING_GESTURE_PREVIEW, editorInfo);
+
// If it's a browser edit field and auto correct is not ON explicitly, then
// disable auto correction, but keep suggestions on.
// If NO_SUGGESTIONS is set, don't do prediction.
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index 790e0d830..d57a881c0 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -145,6 +145,12 @@ public final class InputPointers {
return mPointerIds.getPrimitiveArray();
}
+ /**
+ * Gets the time each point was registered, in milliseconds, relative to the first event in the
+ * sequence.
+ * @return The time each point was registered, in milliseconds, relative to the first event in
+ * the sequence.
+ */
public int[] getTimes() {
if (DebugFlags.DEBUG_ENABLED || DEBUG_TIME) {
if (!isValidTimeStamps()) {
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 8cbf8379b..f3f736fbc 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -48,7 +48,7 @@ public final class LastComposedWord {
public final String mTypedWord;
public final CharSequence mCommittedWord;
public final String mSeparatorString;
- public final PrevWordsInfo mPrevWordsInfo;
+ public final NgramContext mNgramContext;
public final int mCapitalizedMode;
public final InputPointers mInputPointers =
new InputPointers(Constants.DICTIONARY_MAX_WORD_LENGTH);
@@ -64,7 +64,7 @@ public final class LastComposedWord {
public LastComposedWord(final ArrayList<Event> events,
final InputPointers inputPointers, final String typedWord,
final CharSequence committedWord, final String separatorString,
- final PrevWordsInfo prevWordsInfo, final int capitalizedMode) {
+ final NgramContext ngramContext, final int capitalizedMode) {
if (inputPointers != null) {
mInputPointers.copy(inputPointers);
}
@@ -73,7 +73,7 @@ public final class LastComposedWord {
mCommittedWord = committedWord;
mSeparatorString = separatorString;
mActive = true;
- mPrevWordsInfo = prevWordsInfo;
+ mNgramContext = ngramContext;
mCapitalizedMode = capitalizedMode;
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index ba7f967f1..714f62f3e 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -89,12 +89,12 @@ import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.CursorAnchorInfoUtils;
import com.android.inputmethod.latin.utils.DialogUtils;
-import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatchesAndSuggestions;
import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import com.android.inputmethod.latin.utils.StatsUtils;
+import com.android.inputmethod.latin.utils.StatsUtilsManager;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.latin.utils.ViewLayoutUtils;
@@ -132,8 +132,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private final Settings mSettings;
private final DictionaryFacilitator mDictionaryFacilitator =
- new DictionaryFacilitator(
- new DistracterFilterCheckingExactMatchesAndSuggestions(this /* context */));
+ new DictionaryFacilitator(this /* context */);
// TODO: Move from LatinIME.
private final PersonalizationDictionaryUpdater mPersonalizationDictionaryUpdater =
new PersonalizationDictionaryUpdater(this /* context */, mDictionaryFacilitator);
@@ -161,6 +160,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private final SubtypeSwitcher mSubtypeSwitcher;
private final SubtypeState mSubtypeState = new SubtypeState();
private final SpecialKeyDetector mSpecialKeyDetector;
+ private StatsUtilsManager mStatsUtilsManager;
// Working variable for {@link #startShowingInputView()} and
// {@link #onEvaluateInputViewShown()}.
private boolean mIsExecutingStartShowingInputView;
@@ -246,14 +246,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
case MSG_RESUME_SUGGESTIONS:
latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
latinIme.mSettings.getCurrent(),
- msg.arg1 == ARG1_TRUE /* shouldIncludeResumedWordInSuggestions */,
latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId());
break;
case MSG_REOPEN_DICTIONARIES:
// We need to re-evaluate the currently composing word in case the script has
// changed.
postWaitForDictionaryLoad();
- latinIme.resetSuggest();
+ latinIme.resetDictionaryFacilitatorIfNecessary();
break;
case MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED:
latinIme.mInputLogic.onUpdateTailBatchInputCompleted(
@@ -287,8 +286,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES));
}
- public void postResumeSuggestions(final boolean shouldIncludeResumedWordInSuggestions,
- final boolean shouldDelay) {
+ public void postResumeSuggestions(final boolean shouldDelay) {
final LatinIME latinIme = getOwnerInstance();
if (latinIme == null) {
return;
@@ -298,13 +296,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
removeMessages(MSG_RESUME_SUGGESTIONS);
if (shouldDelay) {
- sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS,
- shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
- 0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions);
+ sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS),
+ mDelayInMillisecondsToUpdateSuggestions);
} else {
- sendMessage(obtainMessage(MSG_RESUME_SUGGESTIONS,
- shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
- 0 /* ignored */));
+ sendMessage(obtainMessage(MSG_RESUME_SUGGESTIONS));
}
}
@@ -519,6 +514,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mKeyboardSwitcher = KeyboardSwitcher.getInstance();
mSpecialKeyDetector = new SpecialKeyDetector(this);
+ mStatsUtilsManager = StatsUtilsManager.getInstance();
mIsHardwareAcceleratedDrawingEnabled =
InputMethodServiceCompatUtils.enableHardwareAcceleration(this);
Log.i(TAG, "Hardware accelerated drawing: " + mIsHardwareAcceleratedDrawingEnabled);
@@ -534,16 +530,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
KeyboardSwitcher.init(this);
AudioAndHapticFeedbackManager.init(this);
AccessibilityUtils.init(this);
- StatsUtils.init(this);
-
+ mStatsUtilsManager.onCreate(this /* context */);
super.onCreate();
mHandler.onCreate();
DEBUG = DebugFlags.DEBUG_ENABLED;
- // TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}.
+ // TODO: Resolve mutual dependencies of {@link #loadSettings()} and
+ // {@link #resetDictionaryFacilitatorIfNecessary()}.
loadSettings();
- resetSuggest();
+ resetDictionaryFacilitatorIfNecessary();
// Register to receive ringer mode change and network state change.
// Also receive installation and removal of a dictionary pack.
@@ -567,8 +563,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter);
DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this);
-
- StatsUtils.onCreate(mSettings.getCurrent());
+ StatsUtils.onCreate(mSettings.getCurrent(), mRichImm);
}
// Has to be package-visible for unit tests
@@ -585,19 +580,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// been displayed. Opening dictionaries never affects responsivity as dictionaries are
// asynchronously loaded.
if (!mHandler.hasPendingReopenDictionaries()) {
- resetSuggestForLocale(locale);
+ resetDictionaryFacilitatorForLocale(locale);
}
mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList(
true /* allowsImplicitlySelectedSubtypes */));
refreshPersonalizationDictionarySession(currentSettingsValues);
- StatsUtils.onLoadSettings(currentSettingsValues);
+ mStatsUtilsManager.onLoadSettings(currentSettingsValues);
}
private void refreshPersonalizationDictionarySession(
final SettingsValues currentSettingsValues) {
- mPersonalizationDictionaryUpdater.onLoadSettings(
- currentSettingsValues.mUsePersonalizedDicts,
+ mDictionaryFacilitator.setIsMonolingualUser(
mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes());
+ mPersonalizationDictionaryUpdater.onLoadSettings(
+ currentSettingsValues.mUsePersonalizedDicts);
mContextualDictionaryUpdater.onLoadSettings(currentSettingsValues.mUsePersonalizedDicts);
final boolean shouldKeepUserHistoryDictionaries;
if (currentSettingsValues.mUsePersonalizedDicts) {
@@ -621,13 +617,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
if (mHandler.hasPendingWaitForDictionaryLoad()) {
mHandler.cancelWaitForDictionaryLoad();
- mHandler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */,
- false /* shouldDelay */);
+ mHandler.postResumeSuggestions(false /* shouldDelay */);
}
}
- private void resetSuggest() {
+ private void resetDictionaryFacilitatorIfNecessary() {
final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
+ if (mDictionaryFacilitator.isForLocales(new Locale[] { switcherSubtypeLocale })) {
+ return;
+ }
final String switcherLocaleStr = switcherSubtypeLocale.toString();
final Locale subtypeLocale;
if (TextUtils.isEmpty(switcherLocaleStr)) {
@@ -642,15 +640,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
subtypeLocale = switcherSubtypeLocale;
}
- resetSuggestForLocale(subtypeLocale);
+ resetDictionaryFacilitatorForLocale(subtypeLocale);
}
/**
- * Reset suggest by loading dictionaries for the locale and the current settings values.
+ * Reset the facilitator by loading dictionaries for the locale and the current settings values.
*
* @param locale the locale
*/
- private void resetSuggestForLocale(final Locale locale) {
+ // TODO: make sure the current settings always have the right locale, and read from them
+ private void resetDictionaryFacilitatorForLocale(final Locale locale) {
final SettingsValues settingsValues = mSettings.getCurrent();
mDictionaryFacilitator.resetDictionaries(this /* context */, locale,
settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
@@ -680,7 +679,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
unregisterReceiver(mConnectivityAndRingerModeChangeReceiver);
unregisterReceiver(mDictionaryPackInstallReceiver);
unregisterReceiver(mDictionaryDumpBroadcastReceiver);
- StatsUtils.onDestroy();
+ mStatsUtilsManager.onDestroy();
super.onDestroy();
}
@@ -715,15 +714,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
cleanupInternalStateForFinishInput();
}
}
- // TODO: Remove this test.
- if (!conf.locale.equals(mPersonalizationDictionaryUpdater.getLocale())) {
- refreshPersonalizationDictionarySession(settingsValues);
- }
super.onConfigurationChanged(conf);
}
@Override
public View onCreateInputView() {
+ StatsUtils.onCreateInputView();
return mKeyboardSwitcher.onCreateInputView(mIsHardwareAcceleratedDrawingEnabled);
}
@@ -795,11 +791,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
mHandler.onStartInputView(editorInfo, restarting);
+ mStatsUtilsManager.onStartInputView();
}
@Override
public void onFinishInputView(final boolean finishingInput) {
+ StatsUtils.onFinishInputView();
mHandler.onFinishInputView(finishingInput);
+ mStatsUtilsManager.onFinishInputView();
}
@Override
@@ -811,7 +810,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) {
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
- mSubtypeSwitcher.onSubtypeChanged(subtype);
+ final RichInputMethodSubtype richSubtype = new RichInputMethodSubtype(subtype);
+ mSubtypeSwitcher.onSubtypeChanged(richSubtype);
mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype),
mSettings.getCurrent());
loadKeyboard();
@@ -875,6 +875,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo);
final boolean isDifferentTextField = !restarting || inputTypeChanged;
+
+ StatsUtils.onStartInputView(editorInfo.inputType,
+ Settings.getInstance().getCurrent().mDisplayOrientation,
+ !isDifferentTextField);
+
if (isDifferentTextField) {
mSubtypeSwitcher.updateParametersOnStartInputView();
}
@@ -900,12 +905,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mInputLogic.startInput(mSubtypeSwitcher.getCombiningRulesExtraValueOfCurrentSubtype(),
currentSettingsValues);
- // Note: the following does a round-trip IPC on the main thread: be careful
- final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- if (null != currentLocale && !currentLocale.equals(suggest.getLocale())) {
- // TODO: Do this automatically.
- resetSuggest();
- }
+ resetDictionaryFacilitatorIfNecessary();
// TODO[IL]: Can the following be moved to InputLogic#startInput?
if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess(
@@ -919,11 +919,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// mLastSelection{Start,End} are reset later in this method, no need to do it here
needToCallLoadKeyboardLater = true;
} else {
- // When rotating, initialSelStart and initialSelEnd sometimes are lying. Make a best
- // effort to work around this bug.
+ // When rotating, and when input is starting again in a field from where the focus
+ // didn't move (the keyboard having been closed with the back key),
+ // initialSelStart and initialSelEnd sometimes are lying. Make a best effort to
+ // work around this bug.
mInputLogic.mConnection.tryFixLyingCursorPosition();
- mHandler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */,
- true /* shouldDelay */);
+ mHandler.postResumeSuggestions(true /* shouldDelay */);
needToCallLoadKeyboardLater = false;
}
} else {
@@ -970,7 +971,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip();
mainKeyboardView.setMainDictionaryAvailability(
- mDictionaryFacilitator.hasInitializedMainDictionary());
+ mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary());
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setSlidingKeyInputPreviewEnabled(
@@ -1238,10 +1239,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return mInputLogic.getCurrentRecapitalizeState();
}
- public Locale getCurrentSubtypeLocale() {
- return mSubtypeSwitcher.getCurrentSubtypeLocale();
- }
-
/**
* @param codePoints code points to get coordinates for.
* @return x,y coordinates for this keyboard, as a flattened array.
@@ -1263,13 +1260,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Probably never supposed to happen, but just in case.
return;
}
- final String wordToEdit;
- if (CapsModeUtils.isAutoCapsMode(mInputLogic.mLastComposedWord.mCapitalizedMode)) {
- wordToEdit = word.toLowerCase(getCurrentSubtypeLocale());
- } else {
- wordToEdit = word;
- }
- mDictionaryFacilitator.addWordToUserDictionary(this /* context */, wordToEdit);
+ mDictionaryFacilitator.addWordToUserDictionary(this /* context */, word);
mInputLogic.onAddWordToUserDictionary();
}
@@ -1327,48 +1318,57 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeState.switchSubtype(token, mRichImm);
}
+ // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
+ // alphabetic shift and shift while in symbol layout and get rid of this method.
+ private int getCodePointForKeyboard(final int codePoint) {
+ if (Constants.CODE_SHIFT == codePoint) {
+ final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();
+ if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
+ return codePoint;
+ } else {
+ return Constants.CODE_SYMBOL_SHIFT;
+ }
+ } else {
+ return codePoint;
+ }
+ }
+
// Implementation of {@link KeyboardActionListener}.
@Override
public void onCodeInput(final int codePoint, final int x, final int y,
final boolean isKeyRepeat) {
+ // TODO: this processing does not belong inside LatinIME, the caller should be doing this.
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
// x and y include some padding, but everything down the line (especially native
// code) needs the coordinates in the keyboard frame.
// TODO: We should reconsider which coordinate system should be used to represent
// keyboard event. Also we should pull this up -- LatinIME has no business doing
- // this transformation, it should be done already before calling onCodeInput.
+ // this transformation, it should be done already before calling onEvent.
final int keyX = mainKeyboardView.getKeyX(x);
final int keyY = mainKeyboardView.getKeyY(y);
- final int codeToSend;
- if (Constants.CODE_SHIFT == codePoint) {
- // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
- // alphabetic shift and shift while in symbol layout.
- final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();
- if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
- codeToSend = codePoint;
- } else {
- codeToSend = Constants.CODE_SYMBOL_SHIFT;
- }
- } else {
- codeToSend = codePoint;
- }
- if (Constants.CODE_SHORTCUT == codePoint) {
+ final Event event = createSoftwareKeypressEvent(getCodePointForKeyboard(codePoint),
+ keyX, keyY, isKeyRepeat);
+ onEvent(event);
+ }
+
+ // This method is public for testability of LatinIME, but also in the future it should
+ // completely replace #onCodeInput.
+ public void onEvent(final Event event) {
+ if (Constants.CODE_SHORTCUT == event.mKeyCode) {
mSubtypeSwitcher.switchToShortcutIME(this);
- // Still call the *#onCodeInput methods for readability.
}
- final Event event = createSoftwareKeypressEvent(codeToSend, keyX, keyY, isKeyRepeat);
final InputTransaction completeInputTransaction =
mInputLogic.onCodeInput(mSettings.getCurrent(), event,
mKeyboardSwitcher.getKeyboardShiftMode(),
mKeyboardSwitcher.getCurrentKeyboardScriptId(), mHandler);
updateStateAfterInputTransaction(completeInputTransaction);
- mKeyboardSwitcher.onCodeInput(codePoint, getCurrentAutoCapsState(),
- getCurrentRecapitalizeState());
+ mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
}
// A helper method to split the code point and the key code. Ultimately, they should not be
// squashed into the same variable, and this method should be removed.
- private static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
+ // public for testing, as we don't want to copy the same logic into test code
+ public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
final int keyY, final boolean isKeyRepeat) {
final int keyCode;
final int codePoint;
@@ -1386,13 +1386,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onTextInput(final String rawText) {
// TODO: have the keyboard pass the correct key code when we need it.
- final Event event = Event.createSoftwareTextEvent(rawText, Event.NOT_A_KEY_CODE);
+ final Event event = Event.createSoftwareTextEvent(rawText, Constants.CODE_OUTPUT_TEXT);
final InputTransaction completeInputTransaction =
mInputLogic.onTextInput(mSettings.getCurrent(), event,
mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
updateStateAfterInputTransaction(completeInputTransaction);
- mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT, getCurrentAutoCapsState(),
- getCurrentRecapitalizeState());
+ mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
}
@Override
@@ -1488,7 +1487,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final boolean isEmptyApplicationSpecifiedCompletions =
currentSettingsValues.isApplicationSpecifiedCompletionsOn()
&& suggestedWords.isEmpty();
- final boolean noSuggestionsFromDictionaries = (SuggestedWords.EMPTY == suggestedWords)
+ final boolean noSuggestionsFromDictionaries = suggestedWords.isEmpty()
|| suggestedWords.isPunctuationSuggestions()
|| isEmptyApplicationSpecifiedCompletions;
final boolean isBeginningOfSentencePrediction = (suggestedWords.mInputStyle
@@ -1515,7 +1514,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final OnGetSuggestedWordsCallback callback) {
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
if (keyboard == null) {
- callback.onGetSuggestedWords(SuggestedWords.EMPTY);
+ callback.onGetSuggestedWords(SuggestedWords.getEmptyInstance());
return;
}
mInputLogic.getSuggestedWords(mSettings.getCurrent(), keyboard.getProximityInfo(),
@@ -1523,10 +1522,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
@Override
- public void showSuggestionStrip(final SuggestedWords sourceSuggestedWords) {
- final SuggestedWords suggestedWords =
- sourceSuggestedWords.isEmpty() ? SuggestedWords.EMPTY : sourceSuggestedWords;
- if (SuggestedWords.EMPTY == suggestedWords) {
+ public void showSuggestionStrip(final SuggestedWords suggestedWords) {
+ if (suggestedWords.isEmpty()) {
setNeutralSuggestionStrip();
} else {
setSuggestedWords(suggestedWords);
@@ -1534,7 +1531,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Cache the auto-correction in accessibility code so we can speak it if the user
// touches a key that will insert it.
AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords,
- sourceSuggestedWords.mTypedWord);
+ suggestedWords.mTypedWord);
}
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
@@ -1554,7 +1551,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!hasSuggestionStripView()) {
return;
}
- mSuggestionStripView.showAddToDictionaryHint(word);
+ final String wordToShow;
+ if (CapsModeUtils.isAutoCapsMode(mInputLogic.mLastComposedWord.mCapitalizedMode)) {
+ wordToShow = word.toLowerCase(mDictionaryFacilitator.getPrimaryLocale());
+ } else {
+ wordToShow = word;
+ }
+ mSuggestionStripView.showAddToDictionaryHint(wordToShow);
}
// This will show either an empty suggestion strip (if prediction is enabled) or
@@ -1563,7 +1566,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void setNeutralSuggestionStrip() {
final SettingsValues currentSettings = mSettings.getCurrent();
final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled
- ? SuggestedWords.EMPTY : currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
+ ? SuggestedWords.getEmptyInstance()
+ : currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
setSuggestedWords(neutralSuggestions);
}
@@ -1835,7 +1839,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void dumpDictionaryForDebug(final String dictName) {
if (mDictionaryFacilitator.getLocale() == null) {
- resetSuggest();
+ resetDictionaryFacilitatorIfNecessary();
}
mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
}
diff --git a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java b/java/src/com/android/inputmethod/latin/NgramContext.java
index db877ab7a..6d438584f 100644
--- a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
+++ b/java/src/com/android/inputmethod/latin/NgramContext.java
@@ -18,6 +18,7 @@ package com.android.inputmethod.latin;
import android.text.TextUtils;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.utils.StringUtils;
import java.util.Arrays;
@@ -26,11 +27,11 @@ import java.util.Arrays;
* Class to represent information of previous words. This class is used to add n-gram entries
* into binary dictionaries, to get predictions, and to get suggestions.
*/
-public class PrevWordsInfo {
- public static final PrevWordsInfo EMPTY_PREV_WORDS_INFO =
- new PrevWordsInfo(WordInfo.EMPTY_WORD_INFO);
- public static final PrevWordsInfo BEGINNING_OF_SENTENCE =
- new PrevWordsInfo(WordInfo.BEGINNING_OF_SENTENCE);
+public class NgramContext {
+ public static final NgramContext EMPTY_PREV_WORDS_INFO =
+ new NgramContext(WordInfo.EMPTY_WORD_INFO);
+ public static final NgramContext BEGINNING_OF_SENTENCE =
+ new NgramContext(WordInfo.BEGINNING_OF_SENTENCE);
/**
* Word information used to represent previous words information.
@@ -86,38 +87,66 @@ public class PrevWordsInfo {
// For simplicity of implementation, elements may also be EMPTY_WORD_INFO transiently after the
// WordComposer was reset and before starting a new composing word, but we should never be
// calling getSuggetions* in this situation.
- public WordInfo[] mPrevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ private final WordInfo[] mPrevWordsInfo;
+ private final int mPrevWordsCount;
// Construct from the previous word information.
- public PrevWordsInfo(final WordInfo prevWordInfo) {
- mPrevWordsInfo[0] = prevWordInfo;
+ public NgramContext(final WordInfo... prevWordsInfo) {
+ mPrevWordsInfo = prevWordsInfo;
+ mPrevWordsCount = prevWordsInfo.length;
}
- // Construct from WordInfo array. n-th element represents (n+1)-th previous word's information.
- public PrevWordsInfo(final WordInfo[] prevWordsInfo) {
- for (int i = 0; i < Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM; i++) {
- mPrevWordsInfo[i] =
- (prevWordsInfo.length > i) ? prevWordsInfo[i] : WordInfo.EMPTY_WORD_INFO;
+ // Construct from WordInfo array and size. The caller shouldn't change prevWordsInfo after
+ // calling this method.
+ private NgramContext(final NgramContext ngramContext, final int prevWordsCount) {
+ if (ngramContext.mPrevWordsCount < prevWordsCount) {
+ throw new IndexOutOfBoundsException("ngramContext.mPrevWordsCount ("
+ + ngramContext.mPrevWordsCount + ") is smaller than prevWordsCount ("
+ + prevWordsCount + ")");
}
+ mPrevWordsInfo = ngramContext.mPrevWordsInfo;
+ mPrevWordsCount = prevWordsCount;
}
// Create next prevWordsInfo using current prevWordsInfo.
- public PrevWordsInfo getNextPrevWordsInfo(final WordInfo wordInfo) {
- final WordInfo[] prevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ public NgramContext getNextNgramContext(final WordInfo wordInfo) {
+ final int nextPrevWordCount = Math.min(Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM,
+ mPrevWordsCount + 1);
+ final WordInfo[] prevWordsInfo = new WordInfo[nextPrevWordCount];
prevWordsInfo[0] = wordInfo;
- for (int i = 1; i < prevWordsInfo.length; i++) {
- prevWordsInfo[i] = mPrevWordsInfo[i - 1];
- }
- return new PrevWordsInfo(prevWordsInfo);
+ System.arraycopy(mPrevWordsInfo, 0, prevWordsInfo, 1, nextPrevWordCount - 1);
+ return new NgramContext(prevWordsInfo);
}
public boolean isValid() {
- return mPrevWordsInfo[0].isValid();
+ return mPrevWordsCount > 0 && mPrevWordsInfo[0].isValid();
+ }
+
+ public boolean isBeginningOfSentenceContext() {
+ return mPrevWordsCount > 0 && mPrevWordsInfo[0].mIsBeginningOfSentence;
+ }
+
+ // n is 1-indexed.
+ // TODO: Remove
+ public CharSequence getNthPrevWord(final int n) {
+ if (n <= 0 || n > mPrevWordsCount) {
+ return null;
+ }
+ return mPrevWordsInfo[n - 1].mWord;
+ }
+
+ // n is 1-indexed.
+ @UsedForTesting
+ public boolean isNthPrevWordBeginningOfSontence(final int n) {
+ if (n <= 0 || n > mPrevWordsCount) {
+ return false;
+ }
+ return mPrevWordsInfo[n - 1].mIsBeginningOfSentence;
}
public void outputToArray(final int[][] codePointArrays,
final boolean[] isBeginningOfSentenceArray) {
- for (int i = 0; i < mPrevWordsInfo.length; i++) {
+ for (int i = 0; i < mPrevWordsCount; i++) {
final WordInfo wordInfo = mPrevWordsInfo[i];
if (wordInfo == null || !wordInfo.isValid()) {
codePointArrays[i] = new int[0];
@@ -129,28 +158,70 @@ public class PrevWordsInfo {
}
}
+ public NgramContext getTrimmedNgramContext(final int maxPrevWordCount) {
+ final int newSize = Math.min(maxPrevWordCount, mPrevWordsCount);
+ return new NgramContext(this /* prevWordsInfo */, newSize);
+ }
+
+ public int getPrevWordCount() {
+ return mPrevWordsCount;
+ }
+
@Override
public int hashCode() {
- return Arrays.hashCode(mPrevWordsInfo);
+ int hashValue = 0;
+ for (final WordInfo wordInfo : mPrevWordsInfo) {
+ if (wordInfo == null || !WordInfo.EMPTY_WORD_INFO.equals(wordInfo)) {
+ break;
+ }
+ hashValue ^= wordInfo.hashCode();
+ }
+ return hashValue;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof PrevWordsInfo)) return false;
- final PrevWordsInfo prevWordsInfo = (PrevWordsInfo)o;
- return Arrays.equals(mPrevWordsInfo, prevWordsInfo.mPrevWordsInfo);
+ if (!(o instanceof NgramContext)) return false;
+ final NgramContext prevWordsInfo = (NgramContext)o;
+
+ final int minLength = Math.min(mPrevWordsCount, prevWordsInfo.mPrevWordsCount);
+ for (int i = 0; i < minLength; i++) {
+ if (!mPrevWordsInfo[i].equals(prevWordsInfo.mPrevWordsInfo[i])) {
+ return false;
+ }
+ }
+ final WordInfo[] longerWordsInfo;
+ final int longerWordsInfoCount;
+ if (mPrevWordsCount > prevWordsInfo.mPrevWordsCount) {
+ longerWordsInfo = mPrevWordsInfo;
+ longerWordsInfoCount = mPrevWordsCount;
+ } else {
+ longerWordsInfo = prevWordsInfo.mPrevWordsInfo;
+ longerWordsInfoCount = prevWordsInfo.mPrevWordsCount;
+ }
+ for (int i = minLength; i < longerWordsInfoCount; i++) {
+ if (longerWordsInfo[i] != null
+ && !WordInfo.EMPTY_WORD_INFO.equals(longerWordsInfo[i])) {
+ return false;
+ }
+ }
+ return true;
}
@Override
public String toString() {
final StringBuffer builder = new StringBuffer();
- for (int i = 0; i < mPrevWordsInfo.length; i++) {
+ for (int i = 0; i < mPrevWordsCount; i++) {
final WordInfo wordInfo = mPrevWordsInfo[i];
builder.append("PrevWord[");
builder.append(i);
builder.append("]: ");
- if (wordInfo == null || !wordInfo.isValid()) {
+ if (wordInfo == null) {
+ builder.append("null. ");
+ continue;
+ }
+ if (!wordInfo.isValid()) {
builder.append("Empty. ");
continue;
}
diff --git a/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java
new file mode 100644
index 000000000..396d062f8
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import android.content.Context;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback;
+import com.android.inputmethod.latin.personalization.PersonalizationDataChunk;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary;
+import com.android.inputmethod.latin.utils.LanguageModelParam;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+/**
+ * Class for managing and updating personalization dictionaries.
+ */
+public class PersonalizationHelperForDictionaryFacilitator {
+ private final Context mContext;
+ private final DistracterFilter mDistracterFilter;
+ private final HashMap<String, HashSet<Locale>> mLangToLocalesMap = new HashMap<>();
+ private final HashMap<Locale, ExpandableBinaryDictionary> mPersonalizationDictsToUpdate =
+ new HashMap<>();
+ private boolean mIsMonolingualUser = false;
+
+ PersonalizationHelperForDictionaryFacilitator(final Context context,
+ final DistracterFilter distracterFilter) {
+ mContext = context;
+ mDistracterFilter = distracterFilter;
+ }
+
+ public void close() {
+ mLangToLocalesMap.clear();
+ for (final ExpandableBinaryDictionary dict : mPersonalizationDictsToUpdate.values()) {
+ dict.close();
+ }
+ mPersonalizationDictsToUpdate.clear();
+ }
+
+ public void clearDictionariesToUpdate() {
+ for (final ExpandableBinaryDictionary dict : mPersonalizationDictsToUpdate.values()) {
+ dict.clear();
+ }
+ mPersonalizationDictsToUpdate.clear();
+ }
+
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
+ for (final InputMethodSubtype subtype : enabledSubtypes) {
+ final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
+ final String language = locale.getLanguage();
+ final HashSet<Locale> locales = mLangToLocalesMap.get(language);
+ if (locales != null) {
+ locales.add(locale);
+ } else {
+ final HashSet<Locale> localeSet = new HashSet<>();
+ localeSet.add(locale);
+ mLangToLocalesMap.put(language, localeSet);
+ }
+ }
+ }
+
+ public void setIsMonolingualUser(final boolean isMonolingualUser) {
+ mIsMonolingualUser = isMonolingualUser;
+ }
+
+ /**
+ * Flush personalization dictionaries to dictionary files. Close dictionaries after writing
+ * files except the dictionaries that is used for generating suggestions.
+ *
+ * @param personalizationDictsUsedForSuggestion the personalization dictionaries used for
+ * generating suggestions that won't be closed.
+ */
+ public void flushPersonalizationDictionariesToUpdate(
+ final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion) {
+ for (final ExpandableBinaryDictionary personalizationDict :
+ mPersonalizationDictsToUpdate.values()) {
+ personalizationDict.asyncFlushBinaryDictionary();
+ if (!personalizationDictsUsedForSuggestion.contains(personalizationDict)) {
+ // Close if the dictionary is not being used for suggestion.
+ personalizationDict.close();
+ }
+ }
+ mDistracterFilter.close();
+ mPersonalizationDictsToUpdate.clear();
+ }
+
+ private ExpandableBinaryDictionary getPersonalizationDictToUpdate(final Context context,
+ final Locale locale) {
+ ExpandableBinaryDictionary personalizationDict = mPersonalizationDictsToUpdate.get(locale);
+ if (personalizationDict != null) {
+ return personalizationDict;
+ }
+ personalizationDict = PersonalizationDictionary.getDictionary(context, locale,
+ null /* dictFile */, "" /* dictNamePrefix */);
+ mPersonalizationDictsToUpdate.put(locale, personalizationDict);
+ return personalizationDict;
+ }
+
+ private void addEntriesToPersonalizationDictionariesForLocale(final Locale locale,
+ final PersonalizationDataChunk personalizationDataChunk,
+ final SpacingAndPunctuations spacingAndPunctuations,
+ final AddMultipleDictionaryEntriesCallback callback) {
+ final ExpandableBinaryDictionary personalizationDict =
+ getPersonalizationDictToUpdate(mContext, locale);
+ if (personalizationDict == null) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ final ArrayList<LanguageModelParam> languageModelParams =
+ LanguageModelParam.createLanguageModelParamsFrom(
+ personalizationDataChunk.mTokens,
+ personalizationDataChunk.mTimestampInSeconds, spacingAndPunctuations,
+ locale, new DistracterFilterCheckingIsInDictionary(
+ mDistracterFilter, personalizationDict));
+ if (languageModelParams == null || languageModelParams.isEmpty()) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ personalizationDict.addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
+ }
+
+ public void addEntriesToPersonalizationDictionariesToUpdate(final Locale defaultLocale,
+ final PersonalizationDataChunk personalizationDataChunk,
+ final SpacingAndPunctuations spacingAndPunctuations,
+ final AddMultipleDictionaryEntriesCallback callback) {
+ final String language = personalizationDataChunk.mDetectedLanguage;
+ final HashSet<Locale> locales;
+ if (mIsMonolingualUser && PersonalizationDataChunk.LANGUAGE_UNKNOWN.equals(language)
+ && mLangToLocalesMap.size() == 1) {
+ locales = mLangToLocalesMap.get(defaultLocale.getLanguage());
+ } else {
+ locales = mLangToLocalesMap.get(language);
+ }
+ if (locales == null || locales.isEmpty()) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ final AtomicInteger remainingTaskCount = new AtomicInteger(locales.size());
+ final AddMultipleDictionaryEntriesCallback callbackForLocales =
+ new AddMultipleDictionaryEntriesCallback() {
+ @Override
+ public void onFinished() {
+ if (remainingTaskCount.decrementAndGet() == 0) {
+ // Update tasks for all locales have been finished.
+ if (callback != null) {
+ callback.onFinished();
+ }
+ }
+ }
+ };
+ for (final Locale locale : locales) {
+ addEntriesToPersonalizationDictionariesForLocale(locale, personalizationDataChunk,
+ spacingAndPunctuations, callbackForLocales);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
index 5d4fc5861..bc8bd831c 100644
--- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
@@ -40,7 +40,7 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
public ReadOnlyBinaryDictionary(final String filename, final long offset, final long length,
final boolean useFullEditDistance, final Locale locale, final String dictType) {
- super(dictType);
+ super(dictType, locale);
mBinaryDictionary = new BinaryDictionary(filename, offset, length, useFullEditDistance,
locale, dictType, false /* isUpdatable */);
}
@@ -51,13 +51,15 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int sessionId, final float[] inOutLanguageWeight) {
+ final int sessionId, final float weightForLocale,
+ final float[] inOutWeightOfLangModelVsSpatialModel) {
if (mLock.readLock().tryLock()) {
try {
- return mBinaryDictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
- settingsValuesForSuggestion, sessionId, inOutLanguageWeight);
+ return mBinaryDictionary.getSuggestions(composer, ngramContext, proximityInfo,
+ settingsValuesForSuggestion, sessionId, weightForLocale,
+ inOutWeightOfLangModelVsSpatialModel);
} finally {
mLock.readLock().unlock();
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 744b0321a..a7ea2a1c8 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -36,7 +36,7 @@ import com.android.inputmethod.compat.InputConnectionCompatUtils;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
-import com.android.inputmethod.latin.utils.PrevWordsInfoUtils;
+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.StringUtils;
@@ -593,11 +593,11 @@ public final class RichInputConnection {
}
@SuppressWarnings("unused")
- public PrevWordsInfo getPrevWordsInfoFromNthPreviousWord(
+ public NgramContext getNgramContextFromNthPreviousWord(
final SpacingAndPunctuations spacingAndPunctuations, final int n) {
mIC = mParent.getCurrentInputConnection();
if (null == mIC) {
- return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ return NgramContext.EMPTY_PREV_WORDS_INFO;
}
final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
if (DEBUG_PREVIOUS_TEXT && null != prev) {
@@ -618,7 +618,7 @@ public final class RichInputConnection {
}
}
}
- return PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
+ return NgramContextUtils.getNgramContextFromNthPreviousWord(
prev, spacingAndPunctuations, n);
}
@@ -740,17 +740,19 @@ public final class RichInputConnection {
return TextUtils.equals(text, beforeText);
}
- public boolean revertDoubleSpacePeriod() {
+ public boolean revertDoubleSpacePeriod(final SpacingAndPunctuations spacingAndPunctuations) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
// Here we test whether we indeed have a period and a space before us. This should not
// be needed, but it's there just in case something went wrong.
final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0);
- if (!TextUtils.equals(Constants.STRING_PERIOD_AND_SPACE, textBeforeCursor)) {
+ if (!TextUtils.equals(spacingAndPunctuations.mSentenceSeparatorAndSpace,
+ textBeforeCursor)) {
// Theoretically we should not be coming here if there isn't ". " before the
// cursor, but the application may be changing the text while we are typing, so
// anything goes. We should not crash.
- Log.d(TAG, "Tried to revert double-space combo but we didn't find "
- + "\"" + Constants.STRING_PERIOD_AND_SPACE + "\" just before the cursor.");
+ Log.d(TAG, "Tried to revert double-space combo but we didn't find \""
+ + spacingAndPunctuations.mSentenceSeparatorAndSpace
+ + "\" just before the cursor.");
return false;
}
// Double-space results in ". ". A backspace to cancel this should result in a single
@@ -847,8 +849,9 @@ public final class RichInputConnection {
/**
* Try to get the text from the editor to expose lies the framework may have been
- * telling us. Concretely, when the device rotates, the frameworks tells us about where the
- * cursor used to be initially in the editor at the time it first received the focus; this
+ * telling us. Concretely, when the device rotates and when the keyboard reopens in the same
+ * text field after having been closed with the back key, the frameworks tells us about where
+ * the cursor used to be initially in the editor at the time it first received the focus; this
* may be completely different from the place it is upon rotation. Since we don't have any
* means to get the real value, try at least to ask the text view for some characters and
* detect the most damaging cases: when the cursor position is declared to be much smaller
@@ -857,7 +860,20 @@ public final class RichInputConnection {
public void tryFixLyingCursorPosition() {
final CharSequence textBeforeCursor = getTextBeforeCursor(
Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
- if (null == textBeforeCursor) {
+ final CharSequence selectedText = mIC.getSelectedText(0 /* flags */);
+ if (null == textBeforeCursor ||
+ (!TextUtils.isEmpty(selectedText) && mExpectedSelEnd == mExpectedSelStart)) {
+ // If textBeforeCursor is null, we have no idea what kind of text field we have or if
+ // thinking about the "cursor position" actually makes any sense. In this case we
+ // remember a meaningless cursor position. Contrast this with an empty string, which is
+ // valid and should mean the cursor is at the start of the text.
+ // Also, if we expect we don't have a selection but we DO have non-empty selected text,
+ // then the framework lied to us about the cursor position. In this case, we should just
+ // revert to the most basic behavior possible for the next action (backspace in
+ // particular comes to mind), so we remember a meaningless cursor position which should
+ // result in degraded behavior from the next input.
+ // Interestingly, in either case, chances are any action the user takes next will result
+ // in a call to onUpdateSelection, which should set things right.
mExpectedSelStart = mExpectedSelEnd = Constants.NOT_A_CURSOR_POSITION;
} else {
final int textLength = textBeforeCursor.length();
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 7cf4eff92..0d5ce7d6d 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -40,7 +40,8 @@ import java.util.List;
/**
* Enrichment class for InputMethodManager to simplify interaction and add functionality.
*/
-public final class RichInputMethodManager {
+// non final for easy mocking.
+public class RichInputMethodManager {
private static final String TAG = RichInputMethodManager.class.getSimpleName();
private RichInputMethodManager() {
@@ -297,10 +298,14 @@ public final class RichInputMethodManager {
return INDEX_NOT_FOUND;
}
- public InputMethodSubtype getCurrentInputMethodSubtype(
- final InputMethodSubtype defaultSubtype) {
+ public RichInputMethodSubtype getCurrentInputMethodSubtype(
+ final RichInputMethodSubtype defaultSubtype) {
final InputMethodSubtype currentSubtype = mImmWrapper.mImm.getCurrentInputMethodSubtype();
- return (currentSubtype != null) ? currentSubtype : defaultSubtype;
+ if (currentSubtype == null) {
+ return defaultSubtype;
+ }
+ // TODO: Determine locales to use for multi-lingual use.
+ return new RichInputMethodSubtype(currentSubtype);
}
public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) {
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java
new file mode 100644
index 000000000..0b08c48e5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Enrichment class for InputMethodSubtype to enable concurrent multi-lingual input.
+ *
+ * Right now, this returns the extra value of its primary subtype.
+ */
+public final class RichInputMethodSubtype {
+ private final InputMethodSubtype mSubtype;
+ private final Locale[] mLocales;
+
+ public RichInputMethodSubtype(final InputMethodSubtype subtype, final Locale... locales) {
+ mSubtype = subtype;
+ mLocales = new Locale[locales.length+1];
+ mLocales[0] = LocaleUtils.constructLocaleFromString(mSubtype.getLocale());
+ System.arraycopy(locales, 0, mLocales, 1, locales.length);
+ }
+
+ // Extra values are determined by the primary subtype. This is probably right, but
+ // we may have to revisit this later.
+ public String getExtraValueOf(final String key) {
+ return mSubtype.getExtraValueOf(key);
+ }
+
+ // The mode is also determined by the primary subtype.
+ public String getMode() {
+ return mSubtype.getMode();
+ }
+
+ public boolean isNoLanguage() {
+ if (mLocales.length > 1) {
+ return false;
+ }
+ return SubtypeLocaleUtils.NO_LANGUAGE.equals(mSubtype.getLocale());
+ }
+
+ public String getNameForLogging() {
+ return toString();
+ }
+
+ // InputMethodSubtype's display name for spacebar text in its locale.
+ // isAdditionalSubtype (T=true, F=false)
+ // locale layout | Middle Full
+ // ------ ------- - --------- ----------------------
+ // en_US qwerty F English English (US) exception
+ // en_GB qwerty F English English (UK) exception
+ // es_US spanish F Español Español (EE.UU.) exception
+ // fr azerty F Français Français
+ // fr_CA qwerty F Français Français (Canada)
+ // fr_CH swiss F Français Français (Suisse)
+ // de qwertz F Deutsch Deutsch
+ // de_CH swiss T Deutsch Deutsch (Schweiz)
+ // zz qwerty F QWERTY QWERTY
+ // fr qwertz T Français Français
+ // de qwerty T Deutsch Deutsch
+ // en_US azerty T English English (US)
+ // zz azerty T AZERTY AZERTY
+ // Get the RichInputMethodSubtype's full display name in its locale.
+ public String getFullDisplayName() {
+ if (isNoLanguage()) {
+ return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
+ }
+ return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(mSubtype.getLocale());
+ }
+
+ // Get the RichInputMethodSubtype's middle display name in its locale.
+ public String getMiddleDisplayName() {
+ if (isNoLanguage()) {
+ return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
+ }
+ return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(mSubtype.getLocale());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof RichInputMethodSubtype)) {
+ return false;
+ }
+ final RichInputMethodSubtype other = (RichInputMethodSubtype)o;
+ return mSubtype.equals(other.mSubtype) && Arrays.equals(mLocales, other.mLocales);
+ }
+
+ @Override
+ public int hashCode() {
+ return mSubtype.hashCode() + Arrays.hashCode(mLocales);
+ }
+
+ @Override
+ public String toString() {
+ return "Multi-lingual subtype: " + mSubtype.toString() + ", " + Arrays.toString(mLocales);
+ }
+
+ // TODO: remove this method! We can always have several locales. Multi-lingual input will only
+ // be done when this method is gone.
+ public String getLocale() {
+ return mSubtype.getLocale();
+ }
+
+ // TODO: remove this method
+ public InputMethodSubtype getRawSubtype() { return mSubtype; }
+}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index a725e1611..45d67ff88 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -58,8 +58,8 @@ public final class SubtypeSwitcher {
new LanguageOnSpacebarHelper();
private InputMethodInfo mShortcutInputMethodInfo;
private InputMethodSubtype mShortcutSubtype;
- private InputMethodSubtype mNoLanguageSubtype;
- private InputMethodSubtype mEmojiSubtype;
+ private RichInputMethodSubtype mNoLanguageSubtype;
+ private RichInputMethodSubtype mEmojiSubtype;
private boolean mIsNetworkConnected;
private static final String KEYBOARD_MODE = "keyboard";
@@ -70,26 +70,26 @@ public final class SubtypeSwitcher {
+ "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+ "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
+ "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
- private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
- InputMethodSubtypeCompatUtils.newInputMethodSubtype(
+ private static final RichInputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
+ new RichInputMethodSubtype(InputMethodSubtypeCompatUtils.newInputMethodSubtype(
R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE,
false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
- SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE);
+ SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE));
// Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
// Dummy Emoji subtype. See {@link R.xml.method}.
private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0;
private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE =
"KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
+ "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
- private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE =
+ private static final RichInputMethodSubtype DUMMY_EMOJI_SUBTYPE = new RichInputMethodSubtype(
InputMethodSubtypeCompatUtils.newInputMethodSubtype(
R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE,
false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
- SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE);
+ SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE));
public static SubtypeSwitcher getInstance() {
return sInstance;
@@ -165,18 +165,17 @@ public final class SubtypeSwitcher {
}
// Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
- public void onSubtypeChanged(final InputMethodSubtype newSubtype) {
+ public void onSubtypeChanged(final RichInputMethodSubtype newSubtype) {
if (DBG) {
- Log.w(TAG, "onSubtypeChanged: "
- + SubtypeLocaleUtils.getSubtypeNameForLogging(newSubtype));
+ Log.w(TAG, "onSubtypeChanged: " + newSubtype.getNameForLogging());
}
final Locale newLocale = SubtypeLocaleUtils.getSubtypeLocale(newSubtype);
final Locale systemLocale = mResources.getConfiguration().locale;
final boolean sameLocale = systemLocale.equals(newLocale);
final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage());
- final boolean implicitlyEnabled =
- mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype);
+ final boolean implicitlyEnabled = mRichImm
+ .checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype.getRawSubtype());
mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(
sameLocale || (sameLanguage && implicitlyEnabled));
@@ -250,7 +249,7 @@ public final class SubtypeSwitcher {
// Subtype Switching functions //
//////////////////////////////////
- public int getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype) {
+ public int getLanguageOnSpacebarFormatType(final RichInputMethodSubtype subtype) {
return mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(subtype);
}
@@ -279,10 +278,10 @@ public final class SubtypeSwitcher {
return true;
}
- private static InputMethodSubtype sForcedSubtypeForTesting = null;
+ private static RichInputMethodSubtype sForcedSubtypeForTesting = null;
@UsedForTesting
void forceSubtype(final InputMethodSubtype subtype) {
- sForcedSubtypeForTesting = subtype;
+ sForcedSubtypeForTesting = new RichInputMethodSubtype(subtype);
}
public Locale getCurrentSubtypeLocale() {
@@ -292,17 +291,18 @@ public final class SubtypeSwitcher {
return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype());
}
- public InputMethodSubtype getCurrentSubtype() {
+ public RichInputMethodSubtype getCurrentSubtype() {
if (null != sForcedSubtypeForTesting) {
return sForcedSubtypeForTesting;
}
return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype());
}
- public InputMethodSubtype getNoLanguageSubtype() {
+ public RichInputMethodSubtype getNoLanguageSubtype() {
if (mNoLanguageSubtype == null) {
- mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
- SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
+ mNoLanguageSubtype = new RichInputMethodSubtype(
+ mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+ SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY));
}
if (mNoLanguageSubtype != null) {
return mNoLanguageSubtype;
@@ -313,10 +313,14 @@ public final class SubtypeSwitcher {
return DUMMY_NO_LANGUAGE_SUBTYPE;
}
- public InputMethodSubtype getEmojiSubtype() {
+ public RichInputMethodSubtype getEmojiSubtype() {
if (mEmojiSubtype == null) {
- mEmojiSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
- SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
+ final InputMethodSubtype rawEmojiSubtype =
+ mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+ SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
+ if (null != rawEmojiSubtype) {
+ mEmojiSubtype = new RichInputMethodSubtype(rawEmojiSubtype);
+ }
}
if (mEmojiSubtype != null) {
return mEmojiSubtype;
@@ -328,6 +332,6 @@ public final class SubtypeSwitcher {
}
public String getCombiningRulesExtraValueOfCurrentSubtype() {
- return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype());
+ return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype().getRawSubtype());
}
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index b03818c1d..d2d9b9b1e 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -68,15 +68,15 @@ public final class Suggest {
}
public void getSuggestedWords(final WordComposer wordComposer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
final boolean isCorrectionEnabled, final int inputStyle, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
if (wordComposer.isBatchMode()) {
- getSuggestedWordsForBatchInput(wordComposer, prevWordsInfo, proximityInfo,
+ getSuggestedWordsForBatchInput(wordComposer, ngramContext, proximityInfo,
settingsValuesForSuggestion, inputStyle, sequenceNumber, callback);
} else {
- getSuggestedWordsForNonBatchInput(wordComposer, prevWordsInfo, proximityInfo,
+ getSuggestedWordsForNonBatchInput(wordComposer, ngramContext, proximityInfo,
settingsValuesForSuggestion, inputStyle, isCorrectionEnabled,
sequenceNumber, callback);
}
@@ -84,7 +84,7 @@ public final class Suggest {
private static ArrayList<SuggestedWordInfo> getTransformedSuggestedWordInfoList(
final WordComposer wordComposer, final SuggestionResults results,
- final int trailingSingleQuotesCount) {
+ final int trailingSingleQuotesCount, final Locale defaultLocale) {
final boolean shouldMakeSuggestionsAllUpperCase = wordComposer.isAllUpperCase()
&& !wordComposer.isResumed();
final boolean isOnlyFirstCharCapitalized =
@@ -96,9 +96,11 @@ public final class Suggest {
|| 0 != trailingSingleQuotesCount) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
+ final Locale wordLocale = wordInfo.mSourceDict.mLocale;
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, results.mLocale, shouldMakeSuggestionsAllUpperCase,
- isOnlyFirstCharCapitalized, trailingSingleQuotesCount);
+ wordInfo, null == wordLocale ? defaultLocale : wordLocale,
+ shouldMakeSuggestionsAllUpperCase, isOnlyFirstCharCapitalized,
+ trailingSingleQuotesCount);
suggestionsContainer.set(i, transformedWordInfo);
}
}
@@ -119,7 +121,7 @@ public final class Suggest {
// Retrieves suggestions for non-batch input (typing, recorrection, predictions...)
// and calls the callback function with the suggestions.
private void getSuggestedWordsForNonBatchInput(final WordComposer wordComposer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
final int inputStyleIfNotPrediction, final boolean isCorrectionEnabled,
final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
@@ -130,11 +132,11 @@ public final class Suggest {
: typedWord;
final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- wordComposer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion,
+ wordComposer, ngramContext, proximityInfo, settingsValuesForSuggestion,
SESSION_ID_TYPING);
final ArrayList<SuggestedWordInfo> suggestionsContainer =
getTransformedSuggestedWordInfoList(wordComposer, suggestionResults,
- trailingSingleQuotesCount);
+ trailingSingleQuotesCount, mDictionaryFacilitator.getLocale());
final boolean didRemoveTypedWord =
SuggestedWordInfo.removeDups(wordComposer.getTypedWord(), suggestionsContainer);
@@ -155,7 +157,7 @@ public final class Suggest {
if (!isCorrectionEnabled || !allowsToBeAutoCorrected || resultsArePredictions
|| suggestionResults.isEmpty() || wordComposer.hasDigits()
|| wordComposer.isMostlyCaps() || wordComposer.isResumed()
- || !mDictionaryFacilitator.hasInitializedMainDictionary()
+ || !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()
|| suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) {
// If we don't have a main dictionary, we never want to auto-correct. The reason for
// this is, the user may have a contact whose name happens to match a valid word in
@@ -207,13 +209,14 @@ public final class Suggest {
// Retrieves suggestions for the batch input
// and calls the callback function with the suggestions.
private void getSuggestedWordsForBatchInput(final WordComposer wordComposer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
final int inputStyle, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- wordComposer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion,
+ wordComposer, ngramContext, proximityInfo, settingsValuesForSuggestion,
SESSION_ID_GESTURE);
+ final Locale defaultLocale = mDictionaryFacilitator.getLocale();
final ArrayList<SuggestedWordInfo> suggestionsContainer =
new ArrayList<>(suggestionResults);
final int suggestionsCount = suggestionsContainer.size();
@@ -222,9 +225,10 @@ public final class Suggest {
if (isFirstCharCapitalized || isAllUpperCase) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
+ final Locale wordlocale = wordInfo.mSourceDict.mLocale;
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized,
- 0 /* trailingSingleQuotesCount */);
+ wordInfo, null == wordlocale ? defaultLocale : wordlocale, isAllUpperCase,
+ isFirstCharCapitalized, 0 /* trailingSingleQuotesCount */);
suggestionsContainer.set(i, transformedWordInfo);
}
}
@@ -237,7 +241,7 @@ public final class Suggest {
SuggestedWordInfo.removeDups(null /* typedWord */, suggestionsContainer);
// For some reason some suggestions with MIN_VALUE are making their way here.
- // TODO: Find a more robust way to detect distractors.
+ // TODO: Find a more robust way to detect distracters.
for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
suggestionsContainer.remove(i);
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 1d221b77f..e6fd43a07 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -45,7 +45,7 @@ public class SuggestedWords {
public static final int MAX_SUGGESTIONS = 18;
private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = new ArrayList<>(0);
- public static final SuggestedWords EMPTY = new SuggestedWords(
+ private static final SuggestedWords EMPTY = new SuggestedWords(
EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, false /* typedWordValid */,
false /* willAutoCorrect */, false /* isObsoleteSuggestions */, INPUT_STYLE_NONE);
@@ -142,6 +142,15 @@ public class SuggestedWords {
return mSuggestedWordInfoList.get(index);
}
+ /**
+ * Gets the suggestion index from the suggestions list.
+ * @param suggestedWordInfo The {@link SuggestedWordInfo} to find the index.
+ * @return The position of the suggestion in the suggestion list.
+ */
+ public int indexOf(SuggestedWordInfo suggestedWordInfo) {
+ return mSuggestedWordInfoList.indexOf(suggestedWordInfo);
+ }
+
public String getDebugString(final int pos) {
if (!DebugFlags.DEBUG_ENABLED) {
return null;
@@ -187,6 +196,10 @@ public class SuggestedWords {
return result;
}
+ public static final SuggestedWords getEmptyInstance() {
+ return SuggestedWords.EMPTY;
+ }
+
// Should get rid of the first one (what the user typed previously) from suggestions
// and replace it with what the user currently typed.
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 32d1fe372..f85b34b5e 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -49,6 +49,7 @@ public final class WordComposer {
private final ArrayList<Event> mEvents;
private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
private String mAutoCorrection;
+ private String mAutoCorrectionDictionaryType;
private boolean mIsResumed;
private boolean mIsBatchMode;
// A memory of the last rejected batch mode suggestion, if any. This goes like this: the user
@@ -418,8 +419,9 @@ public final class WordComposer {
/**
* Sets the auto-correction for this word.
*/
- public void setAutoCorrection(final String correction) {
+ public void setAutoCorrection(final String correction, String dictType) {
mAutoCorrection = correction;
+ mAutoCorrectionDictionaryType = dictType;
}
/**
@@ -430,6 +432,13 @@ public final class WordComposer {
}
/**
+ * @return the auto-correction dictionary type or null if none.
+ */
+ public String getAutoCorrectionDictionaryTypeOrNull() {
+ return mAutoCorrectionDictionaryType;
+ }
+
+ /**
* @return whether we started composing this word by resuming suggestion on an existing string
*/
public boolean isResumed() {
@@ -439,13 +448,13 @@ public final class WordComposer {
// `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
// committedWord should contain suggestion spans if applicable.
public LastComposedWord commitWord(final int type, final CharSequence committedWord,
- final String separatorString, final PrevWordsInfo prevWordsInfo) {
+ final String separatorString, final NgramContext ngramContext) {
// Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK
// or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
// the last composed word to ensure this does not happen.
final LastComposedWord lastComposedWord = new LastComposedWord(mEvents,
mInputPointers, mTypedWordCache.toString(), committedWord, separatorString,
- prevWordsInfo, mCapitalizedMode);
+ ngramContext, mCapitalizedMode);
mInputPointers.reset();
if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
&& type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) {
diff --git a/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java b/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java
new file mode 100644
index 000000000..9445ce4c3
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.accounts;
+
+import android.accounts.AccountManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.settings.Settings;
+
+/**
+ * {@link BroadcastReceiver} for {@link AccountManager#LOGIN_ACCOUNTS_CHANGED_ACTION}.
+ */
+public class AccountsChangedReceiver extends BroadcastReceiver {
+ static final String TAG = "AccountsChangedReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(intent.getAction())) {
+ Log.w(TAG, "Received unknown broadcast: " + intent);
+ return;
+ }
+
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ final String currentAccount = prefs.getString(Settings.PREF_ACCOUNT_NAME, null);
+ if (currentAccount != null) {
+ final String[] accounts = getAccountsForLogin(context);
+ boolean accountFound = false;
+ for (String account : accounts) {
+ if (TextUtils.equals(currentAccount, account)) {
+ accountFound = true;
+ break;
+ }
+ }
+ // The current account was not found in the list of accounts, remove it.
+ if (!accountFound) {
+ Log.i(TAG, "The current account was removed from the system: " + currentAccount);
+ prefs.edit()
+ .remove(Settings.PREF_ACCOUNT_NAME)
+ .apply();
+ }
+ }
+ }
+
+ /**
+ * Helper method to help test this receiver.
+ */
+ @UsedForTesting
+ protected String[] getAccountsForLogin(Context context) {
+ return LoginAccountUtils.getAccountsForLogin(context);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/accounts/AuthUtils.java b/java/src/com/android/inputmethod/latin/accounts/AuthUtils.java
new file mode 100644
index 000000000..31aba3631
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/accounts/AuthUtils.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.accounts;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+
+import java.io.IOException;
+
+/**
+ * Utility class that handles generation/invalidation of auth tokens in the app.
+ */
+public class AuthUtils {
+ private final AccountManager mAccountManager;
+
+ public AuthUtils(Context context) {
+ mAccountManager = AccountManager.get(context);
+ }
+
+ /**
+ * @see AccountManager#invalidateAuthToken(String, String)
+ */
+ public void invalidateAuthToken(final String accountType, final String authToken) {
+ mAccountManager.invalidateAuthToken(accountType, authToken);
+ }
+
+ /**
+ * @see AccountManager#getAuthToken(
+ * Account, String, Bundle, boolean, AccountManagerCallback, Handler)
+ */
+ public AccountManagerFuture<Bundle> getAuthToken(final Account account,
+ final String authTokenType, final Bundle options, final boolean notifyAuthFailure,
+ final AccountManagerCallback<Bundle> callback, final Handler handler) {
+ return mAccountManager.getAuthToken(account, authTokenType, options, notifyAuthFailure,
+ callback, handler);
+ }
+
+ /**
+ * @see AccountManager#blockingGetAuthToken(Account, String, boolean)
+ */
+ public String blockingGetAuthToken(final Account account, final String authTokenType,
+ final boolean notifyAuthFailure) throws OperationCanceledException,
+ AuthenticatorException, IOException {
+ return mAccountManager.blockingGetAuthToken(account, authTokenType, notifyAuthFailure);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 21e2a1c10..b4a1c3e65 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -45,7 +45,7 @@ import com.android.inputmethod.latin.DictionaryFacilitator;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LastComposedWord;
import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.RichInputConnection;
import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
@@ -61,6 +61,7 @@ import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
+import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
@@ -85,7 +86,7 @@ public final class InputLogic {
// Current space state of the input method. This can be any of the above constants.
private int mSpaceState;
// Never null
- public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+ public SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance();
public final Suggest mSuggest;
private final DictionaryFacilitator mDictionaryFacilitator;
@@ -144,13 +145,20 @@ public final class InputLogic {
*/
public void startInput(final String combiningSpec, final SettingsValues settingsValues) {
mEnteredText = null;
+ 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.
+ // The timestamp at which it is captured is not accurate but close enough.
+ StatsUtils.onWordCommitUserTyped(
+ mWordComposer.getTypedWord(), mWordComposer.isBatchMode());
+ }
mWordComposer.restartCombining(combiningSpec);
resetComposingState(true /* alsoResetLastComposedWord */);
mDeleteCount = 0;
mSpaceState = SpaceState.NONE;
mRecapitalizeStatus.disable(); // Do not perform recapitalize until the cursor is moved once
mCurrentlyPressedHardwareKeys.clear();
- mSuggestedWords = SuggestedWords.EMPTY;
+ mSuggestedWords = SuggestedWords.getEmptyInstance();
// In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying
// so we try using some heuristics to find out about these and fix them.
mConnection.tryFixLyingCursorPosition();
@@ -204,6 +212,8 @@ public final class InputLogic {
public void finishInput() {
if (mWordComposer.isComposingWord()) {
mConnection.finishComposingText();
+ StatsUtils.onWordCommitUserTyped(
+ mWordComposer.getTypedWord(), mWordComposer.isBatchMode());
}
resetComposingState(true /* alsoResetLastComposedWord */);
mInputLogicHandler.reset();
@@ -250,6 +260,7 @@ public final class InputLogic {
promotePhantomSpace(settingsValues);
}
mConnection.commitText(text, 1);
+ StatsUtils.onWordCommitUserTyped(mEnteredText, mWordComposer.isBatchMode());
mConnection.endBatchEdit();
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.NONE;
@@ -321,7 +332,7 @@ public final class InputLogic {
// however need to reset the suggestion strip right away, because we know we can't take
// the risk of calling commitCompletion twice because we don't know how the app will react.
if (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_APP_DEFINED)) {
- mSuggestedWords = SuggestedWords.EMPTY;
+ mSuggestedWords = SuggestedWords.getEmptyInstance();
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
resetComposingState(true /* alsoResetLastComposedWord */);
@@ -347,6 +358,10 @@ public final class InputLogic {
// That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE.
handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
}
+
+ StatsUtils.onPickSuggestionManually(mSuggestedWords, suggestionInfo);
+ StatsUtils.onWordCommitSuggestionPickedManually(
+ suggestionInfo.mWord, mWordComposer.isBatchMode());
return inputTransaction;
}
@@ -420,8 +435,7 @@ public final class InputLogic {
// removed.
mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
// We moved the cursor. If we are touching a word, we need to resume suggestion.
- mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */,
- true /* shouldDelay */);
+ mLatinIME.mHandler.postResumeSuggestions(true /* shouldDelay */);
// Stop the last recapitalization, if started.
mRecapitalizeStatus.stop();
return true;
@@ -493,7 +507,7 @@ public final class InputLogic {
final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
mInputLogicHandler.onStartBatchInput();
handler.showGesturePreviewAndSuggestionStrip(
- SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
+ SuggestedWords.getEmptyInstance(), false /* dismissGestureFloatingPreviewText */);
handler.cancelUpdateSuggestionStrip();
++mAutoCommitSequenceNumber;
mConnection.beginBatchEdit();
@@ -570,6 +584,7 @@ public final class InputLogic {
batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord);
promotePhantomSpace(settingsValues);
mConnection.commitText(commitParts[0], 0);
+ StatsUtils.onWordCommitUserTyped(commitParts[0], mWordComposer.isBatchMode());
mSpaceState = SpaceState.PHANTOM;
keyboardSwitcher.requestUpdatingShiftState(
getCurrentAutoCapsState(settingsValues), getCurrentRecapitalizeState());
@@ -591,23 +606,30 @@ public final class InputLogic {
public void onCancelBatchInput(final LatinIME.UIHandler handler) {
mInputLogicHandler.onCancelBatchInput();
handler.showGesturePreviewAndSuggestionStrip(
- SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
+ SuggestedWords.getEmptyInstance(), true /* dismissGestureFloatingPreviewText */);
}
// TODO: on the long term, this method should become private, but it will be difficult.
// Especially, how do we deal with InputMethodService.onDisplayCompletions?
public void setSuggestedWords(final SuggestedWords suggestedWords,
final SettingsValues settingsValues, final LatinIME.UIHandler handler) {
- if (SuggestedWords.EMPTY != suggestedWords) {
+ if (!suggestedWords.isEmpty()) {
final String autoCorrection;
+ final String dictType;
if (suggestedWords.mWillAutoCorrect) {
- autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+ SuggestedWordInfo info = suggestedWords.getInfo(
+ SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+ autoCorrection = info.mWord;
+ dictType = info.mSourceDict.mDictType;
} else {
// We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
// because it may differ from mWordComposer.mTypedWord.
autoCorrection = suggestedWords.mTypedWord;
+ dictType = Dictionary.TYPE_USER_TYPED;
}
- mWordComposer.setAutoCorrection(autoCorrection);
+ // TODO: Use the SuggestedWordInfo to set the auto correction when
+ // user typed word is available via SuggestedWordInfo.
+ mWordComposer.setAutoCorrection(autoCorrection, dictType);
}
mSuggestedWords = suggestedWords;
final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect;
@@ -681,7 +703,7 @@ public final class InputLogic {
break;
case Constants.CODE_CAPSLOCK:
// Note: Changing keyboard to shift lock state is handled in
- // {@link KeyboardSwitcher#onCodeInput(int)}.
+ // {@link KeyboardSwitcher#onEvent(Event)}.
break;
case Constants.CODE_SYMBOL_SHIFT:
// Note: Calling back to the keyboard on the symbol Shift key is handled in
@@ -709,11 +731,11 @@ public final class InputLogic {
break;
case Constants.CODE_EMOJI:
// Note: Switching emoji keyboard is being handled in
- // {@link KeyboardState#onCodeInput(int,int)}.
+ // {@link KeyboardState#onEvent(Event,int)}.
break;
case Constants.CODE_ALPHA_FROM_EMOJI:
// Note: Switching back from Emoji keyboard to the main keyboard is being
- // handled in {@link KeyboardState#onCodeInput(int,int)}.
+ // handled in {@link KeyboardState#onEvent(Event,int)}.
break;
case Constants.CODE_SHIFT_ENTER:
// TODO: remove this object
@@ -1041,8 +1063,10 @@ public final class InputLogic {
if (!TextUtils.isEmpty(rejectedSuggestion)) {
mDictionaryFacilitator.removeWordFromPersonalizedDicts(rejectedSuggestion);
}
+ StatsUtils.onBackspaceWordDelete(rejectedSuggestion.length());
} else {
mWordComposer.applyProcessedEvent(event);
+ StatsUtils.onBackspacePressed(1);
}
if (mWordComposer.isComposingWord()) {
setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
@@ -1052,7 +1076,10 @@ public final class InputLogic {
inputTransaction.setRequiresUpdateSuggestions();
} else {
if (mLastComposedWord.canRevertCommit()) {
+ final String lastComposedWord = mLastComposedWord.mTypedWord;
revertCommit(inputTransaction, inputTransaction.mSettingsValues);
+ StatsUtils.onRevertAutoCorrect();
+ StatsUtils.onWordCommitUserTyped(lastComposedWord, mWordComposer.isBatchMode());
return;
}
if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
@@ -1060,6 +1087,7 @@ public final class InputLogic {
// 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);
+ StatsUtils.onDeleteMultiCharInput(mEnteredText.length());
mEnteredText = null;
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
// In addition we know that spaceState is false, and that we should not be
@@ -1068,16 +1096,19 @@ public final class InputLogic {
}
if (SpaceState.DOUBLE == inputTransaction.mSpaceState) {
cancelDoubleSpacePeriodCountdown();
- if (mConnection.revertDoubleSpacePeriod()) {
+ if (mConnection.revertDoubleSpacePeriod(
+ inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
// No need to reset mSpaceState, it has already be done (that's why we
// receive it as a parameter)
inputTransaction.setRequiresUpdateSuggestions();
mWordComposer.setCapitalizedModeAtStartComposingTime(
WordComposer.CAPS_MODE_OFF);
+ StatsUtils.onRevertDoubleSpacePeriod();
return;
}
} else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
if (mConnection.revertSwapPunctuation()) {
+ StatsUtils.onRevertSwapPunctuation();
// Likewise
return;
}
@@ -1092,25 +1123,31 @@ public final class InputLogic {
mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
mConnection.getExpectedSelectionEnd());
mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ StatsUtils.onBackspaceSelectedText(numCharsDeleted);
} else {
// There is no selection, just delete one character.
- if (Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) {
- // This should never happen.
- Log.e(TAG, "Backspace when we don't know the selection position");
- }
- if (inputTransaction.mSettingsValues.isBeforeJellyBean() ||
- inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()) {
- // There are two possible reasons to send a key event: either the field has
+ if (inputTransaction.mSettingsValues.isBeforeJellyBean()
+ || inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()
+ || Constants.NOT_A_CURSOR_POSITION
+ == mConnection.getExpectedSelectionEnd()) {
+ // There are three possible reasons to send a key event: either the field has
// type TYPE_NULL, in which case the keyboard should send events, or we are
- // running in backward compatibility mode. Before Jelly bean, the keyboard
- // would simulate a hardware keyboard event on pressing enter or delete. This
- // is bad for many reasons (there are race conditions with commits) but some
- // applications are relying on this behavior so we continue to support it for
- // older apps, so we retain this behavior if the app has target SDK < JellyBean.
+ // running in backward compatibility mode, or we don't know the cursor position.
+ // Before Jelly bean, the keyboard would simulate a hardware keyboard event on
+ // pressing enter or delete. This is bad for many reasons (there are race
+ // conditions with commits) but some applications are relying on this behavior
+ // so we continue to support it for older apps, so we retain this behavior if
+ // the app has target SDK < JellyBean.
+ // As for the case where we don't know the cursor position, it can happen
+ // because of bugs in the framework. But the framework should know, so the next
+ // best thing is to leave it to whatever it thinks is best.
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ int totalDeletedLength = 1;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ totalDeletedLength++;
}
+ StatsUtils.onBackspacePressed(totalDeletedLength);
} else {
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursor == Constants.NOT_A_CODE) {
@@ -1121,11 +1158,13 @@ public final class InputLogic {
// 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);
+ // TODO: Add a new StatsUtils method onBackspaceWhenNoText()
return;
}
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDelete, 0);
+ int totalDeletedLength = lengthToDelete;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor();
@@ -1133,8 +1172,10 @@ public final class InputLogic {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
codePointBeforeCursorToDeleteAgain) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
+ totalDeletedLength += lengthToDeleteAgain;
}
}
+ StatsUtils.onBackspacePressed(totalDeletedLength);
}
}
if (inputTransaction.mSettingsValues
@@ -1144,7 +1185,7 @@ public final class InputLogic {
&& !mConnection.isCursorFollowedByWordCharacter(
inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
- true /* shouldIncludeResumedWordInSuggestions */, currentKeyboardScriptId);
+ currentKeyboardScriptId);
}
}
}
@@ -1251,7 +1292,9 @@ public final class InputLogic {
if (null == lastTwo) return false;
final int length = lastTwo.length();
if (length < 2) return false;
- if (lastTwo.charAt(length - 1) != Constants.CODE_SPACE) return false;
+ if (lastTwo.charAt(length - 1) != Constants.CODE_SPACE) {
+ return false;
+ }
// We know there is a space in pos -1, and we have at least two chars. If we have only two
// chars, isSurrogatePairs can't return true as charAt(1) is a space, so this is fine.
final int firstCodePoint =
@@ -1334,7 +1377,7 @@ public final class InputLogic {
}
private void performAdditionToUserHistoryDictionary(final SettingsValues settingsValues,
- final String suggestion, final PrevWordsInfo prevWordsInfo) {
+ final String suggestion, final NgramContext ngramContext) {
// If correction is not enabled, we don't add words to the user history dictionary.
// That's to avoid unintended additions in some sensitive fields, or fields that
// expect to receive non-words.
@@ -1346,7 +1389,7 @@ public final class InputLogic {
final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
System.currentTimeMillis());
mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized,
- prevWordsInfo, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
+ ngramContext, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
}
public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
@@ -1358,7 +1401,7 @@ public final class InputLogic {
+ "requested!");
}
// Clear the suggestions strip.
- mSuggestionStripViewAccessor.showSuggestionStrip(SuggestedWords.EMPTY);
+ mSuggestionStripViewAccessor.showSuggestionStrip(SuggestedWords.getEmptyInstance());
return;
}
@@ -1398,12 +1441,10 @@ public final class InputLogic {
* do nothing.
*
* @param settingsValues the current values of the settings.
- * @param shouldIncludeResumedWordInSuggestions whether to include the word on which we resume
* suggestions in the suggestion list.
*/
// TODO: make this private.
public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues,
- final boolean shouldIncludeResumedWordInSuggestions,
// TODO: remove this argument, put it into settingsValues
final int currentKeyboardScriptId) {
// HACK: We may want to special-case some apps that exhibit bad behavior in case of
@@ -1451,13 +1492,6 @@ public final class InputLogic {
if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return;
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
final String typedWord = range.mWord.toString();
- if (shouldIncludeResumedWordInSuggestions) {
- suggestions.add(new SuggestedWordInfo(typedWord,
- SuggestedWords.MAX_SUGGESTIONS + 1,
- SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- }
if (!isResumableWord(settingsValues, typedWord)) {
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
return;
@@ -1477,10 +1511,10 @@ public final class InputLogic {
}
}
final int[] codePoints = StringUtils.toCodePointArray(typedWord);
- // We want the previous word for suggestion. If we have chars in the word
+ // We want the context of preceding words for suggestion. If we have chars in the word
// before the cursor, then we want the word before that, hence 2; otherwise,
// we want the word immediately before the cursor, hence 1.
- final PrevWordsInfo prevWordsInfo = getPrevWordsInfoFromNthPreviousWordForSuggestion(
+ final NgramContext ngramContext = getNgramContextFromNthPreviousWordForSuggestion(
settingsValues.mSpacingAndPunctuations,
0 == numberOfCharsInWordBeforeCursor ? 1 : 2);
mWordComposer.setComposingWord(codePoints,
@@ -1490,7 +1524,7 @@ public final class InputLogic {
mConnection.maybeMoveTheCursorAroundAndRestoreToWorkaroundABug();
mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor());
- if (suggestions.size() <= (shouldIncludeResumedWordInSuggestions ? 1 : 0)) {
+ if (suggestions.size() <= 0) {
// If there weren't any suggestion spans on this word, suggestions#size() will be 1
// if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we
// have no useful suggestions, so we will try to compute some for it instead.
@@ -1500,8 +1534,7 @@ public final class InputLogic {
public void onGetSuggestedWords(
final SuggestedWords suggestedWordsIncludingTypedWord) {
final SuggestedWords suggestedWords;
- if (suggestedWordsIncludingTypedWord.size() > 1
- && !shouldIncludeResumedWordInSuggestions) {
+ if (suggestedWordsIncludingTypedWord.size() > 1) {
// We were able to compute new suggestions for this word.
// Remove the typed word, since we don't want to display it in this
// case. The #getSuggestedWordsExcludingTypedWordForRecorrection()
@@ -1549,6 +1582,10 @@ public final class InputLogic {
final String committedWordString = committedWord.toString();
final int cancelLength = committedWord.length();
final String separatorString = mLastComposedWord.mSeparatorString;
+ // If our separator is a space, we won't actually commit it,
+ // but set the space state to PHANTOM so that a space will be inserted
+ // on the next keypress
+ final boolean usePhantomSpace = separatorString.equals(Constants.STRING_SPACE);
// We want java chars, not codepoints for the following.
final int separatorLength = separatorString.length();
// TODO: should we check our saved separator against the actual contents of the text view?
@@ -1569,7 +1606,8 @@ public final class InputLogic {
if (!TextUtils.isEmpty(committedWord)) {
mDictionaryFacilitator.removeWordFromPersonalizedDicts(committedWordString);
}
- final String stringToCommit = originallyTypedWord + separatorString;
+ final String stringToCommit = originallyTypedWord +
+ (usePhantomSpace ? "" : separatorString);
final SpannableString textToCommit = new SpannableString(stringToCommit);
if (committedWord instanceof SpannableString) {
final SpannableString committedWordWithSuggestionSpans = (SpannableString)committedWord;
@@ -1602,8 +1640,10 @@ public final class InputLogic {
}
}
// Add the suggestion list to the list of suggestions.
- textToCommit.setSpan(new SuggestionSpan(inputTransaction.mSettingsValues.mLocale,
- suggestions.toArray(new String[suggestions.size()]), 0 /* flags */),
+ textToCommit.setSpan(new SuggestionSpan(mLatinIME /* context */,
+ inputTransaction.mSettingsValues.mLocale,
+ suggestions.toArray(new String[suggestions.size()]), 0 /* flags */,
+ null /* notificationTargetClass */),
0 /* start */, lastCharIndex /* end */, 0 /* flags */);
}
@@ -1621,6 +1661,9 @@ public final class InputLogic {
} else {
mConnection.commitText(textToCommit, 1);
}
+ if (usePhantomSpace) {
+ mSpaceState = SpaceState.PHANTOM;
+ }
} else {
// For languages without spaces, we revert the typed string but the cursor is flush
// with the typed word, so we need to resume suggestions right away.
@@ -1718,24 +1761,24 @@ public final class InputLogic {
}
/**
- * Get information fo previous words from the nth previous word before the cursor as context
+ * Get n-gram context from the nth previous word before the cursor as context
* for the suggestion process.
* @param spacingAndPunctuations the current spacing and punctuations settings.
* @param nthPreviousWord reverse index of the word to get (1-indexed)
* @return the information of previous words
*/
// TODO: Make this private
- public PrevWordsInfo getPrevWordsInfoFromNthPreviousWordForSuggestion(
+ public NgramContext getNgramContextFromNthPreviousWordForSuggestion(
final SpacingAndPunctuations spacingAndPunctuations, final int nthPreviousWord) {
if (spacingAndPunctuations.mCurrentLanguageHasSpaces) {
// If we are typing in a language with spaces we can just look up the previous
// word information from textview.
- return mConnection.getPrevWordsInfoFromNthPreviousWord(
+ return mConnection.getNgramContextFromNthPreviousWord(
spacingAndPunctuations, nthPreviousWord);
} else {
return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ?
- PrevWordsInfo.BEGINNING_OF_SENTENCE :
- new PrevWordsInfo(new PrevWordsInfo.WordInfo(
+ NgramContext.BEGINNING_OF_SENTENCE :
+ new NgramContext(new NgramContext.WordInfo(
mLastComposedWord.mCommittedWord.toString()));
}
}
@@ -1850,9 +1893,8 @@ public final class InputLogic {
*/
private SuggestedWords retrieveOlderSuggestions(final String typedWord,
final SuggestedWords previousSuggestedWords) {
- final SuggestedWords oldSuggestedWords =
- previousSuggestedWords.isPunctuationSuggestions() ? SuggestedWords.EMPTY
- : previousSuggestedWords;
+ final SuggestedWords oldSuggestedWords = previousSuggestedWords.isPunctuationSuggestions()
+ ? SuggestedWords.getEmptyInstance() : previousSuggestedWords;
final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords);
return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */,
@@ -1974,6 +2016,8 @@ public final class InputLogic {
final int indexOfLastSpace = batchInputText.lastIndexOf(Constants.CODE_SPACE) + 1;
if (0 != indexOfLastSpace) {
mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), 1);
+ StatsUtils.onWordCommitUserTyped(
+ batchInputText.substring(0, indexOfLastSpace), mWordComposer.isBatchMode());
final SuggestedWords suggestedWordsForLastWordOfPhraseGesture =
suggestedWords.getSuggestedWordsForLastWordOfPhraseGesture();
mLatinIME.showSuggestionStrip(suggestedWordsForLastWordOfPhraseGesture);
@@ -2012,8 +2056,10 @@ public final class InputLogic {
if (!mWordComposer.isComposingWord()) return;
final String typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
+ final boolean isBatchMode = mWordComposer.isBatchMode();
commitChosenWord(settingsValues, typedWord,
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString);
+ StatsUtils.onWordCommitUserTyped(typedWord, isBatchMode);
}
}
@@ -2059,6 +2105,7 @@ public final class InputLogic {
throw new RuntimeException("We have an auto-correction but the typed word "
+ "is empty? Impossible! I must commit suicide.");
}
+ final boolean isBatchMode = mWordComposer.isBatchMode();
commitChosenWord(settingsValues, autoCorrection,
LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator);
if (!typedWord.equals(autoCorrection)) {
@@ -2071,6 +2118,11 @@ public final class InputLogic {
mConnection.commitCorrection(new CorrectionInfo(
mConnection.getExpectedSelectionEnd() - autoCorrection.length(),
typedWord, autoCorrection));
+ StatsUtils.onAutoCorrection(typedWord, autoCorrection, isBatchMode,
+ mWordComposer.getAutoCorrectionDictionaryTypeOrNull());
+ StatsUtils.onWordCommitAutoCorrect(autoCorrection, isBatchMode);
+ } else {
+ StatsUtils.onWordCommitUserTyped(autoCorrection, isBatchMode);
}
}
}
@@ -2089,20 +2141,20 @@ public final class InputLogic {
final CharSequence chosenWordWithSuggestions =
SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
suggestedWords);
- // When we are composing word, get previous words information from the 2nd previous word
- // because the 1st previous word is the word to be committed. Otherwise get previous words
- // information from the 1st previous word.
- final PrevWordsInfo prevWordsInfo = mConnection.getPrevWordsInfoFromNthPreviousWord(
+ // When we are composing word, get n-gram context from the 2nd previous word because the
+ // 1st previous word is the word to be committed. Otherwise get n-gram context from the 1st
+ // previous word.
+ final NgramContext ngramContext = mConnection.getNgramContextFromNthPreviousWord(
settingsValues.mSpacingAndPunctuations, mWordComposer.isComposingWord() ? 2 : 1);
mConnection.commitText(chosenWordWithSuggestions, 1);
// Add the word to the user history dictionary
- performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo);
+ performAdditionToUserHistoryDictionary(settingsValues, chosenWord, ngramContext);
// TODO: figure out here if this is an auto-correct or if the best word is actually
// what user typed. Note: currently this is done much later in
// LastComposedWord#didCommitTypedWord by string equality of the remembered
// strings.
mLastComposedWord = mWordComposer.commitWord(commitType,
- chosenWordWithSuggestions, separatorString, prevWordsInfo);
+ chosenWordWithSuggestions, separatorString, ngramContext);
}
/**
@@ -2135,10 +2187,7 @@ public final class InputLogic {
}
mConnection.tryFixLyingCursorPosition();
if (tryResumeSuggestions) {
- // This is triggered when starting input anew, so we want to include the resumed
- // word in suggestions.
- handler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */,
- true /* shouldDelay */);
+ handler.postResumeSuggestions(true /* shouldDelay */);
}
return true;
}
@@ -2149,7 +2198,7 @@ public final class InputLogic {
mWordComposer.adviseCapitalizedModeBeforeFetchingSuggestions(
getActualCapsMode(settingsValues, keyboardShiftMode));
mSuggest.getSuggestedWords(mWordComposer,
- getPrevWordsInfoFromNthPreviousWordForSuggestion(
+ getNgramContextFromNthPreviousWordForSuggestion(
settingsValues.mSpacingAndPunctuations,
// Get the word on which we should search the bigrams. If we are composing
// a word, it's whatever is *before* the half-committed word in the buffer,
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java
index df447fd75..3f9ffd285 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java
@@ -47,6 +47,7 @@ public final class DictionaryHeader {
public static final String MAX_UNIGRAM_COUNT_KEY = "MAX_UNIGRAM_COUNT";
public static final String MAX_BIGRAM_COUNT_KEY = "MAX_BIGRAM_COUNT";
public static final String ATTRIBUTE_VALUE_TRUE = "1";
+ public static final String CODE_POINT_TABLE_KEY = "codePointTable";
public DictionaryHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
final FormatOptions formatOptions) throws UnsupportedFormatException {
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index a2ae74b20..34edfa0da 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -36,9 +36,7 @@ public final class FormatSpec {
* sion
*
* o |
- * p | not used 3 bits
- * t | each unigram and bigram entry has a time stamp?
- * i | 1 bit, 1 = yes, 0 = no : CONTAINS_TIMESTAMP_FLAG
+ * p | not used, 2 bytes.
* o |
* nflags
*
@@ -48,7 +46,7 @@ public final class FormatSpec {
* d |
* ersize
*
- * | attributes list
+ * attributes list
*
* attributes list is:
* <key> = | string of characters at the char format described below, with the terminator used
@@ -86,11 +84,10 @@ public final class FormatSpec {
*/
/* Node (FusionDictionary.PtNode) layout is as follows:
- * | is moved ? 2 bits, 11 = no : FLAG_IS_NOT_MOVED
- * | This must be the same as FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
- * | 01 = yes : FLAG_IS_MOVED
- * f | the new address is stored in the same place as the parent address
- * l | is deleted? 10 = yes : FLAG_IS_DELETED
+ * | CHILDREN_ADDRESS_TYPE 2 bits, 11 : FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
+ * | 10 : FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES
+ * f | 01 : FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE
+ * l | 00 : FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS
* a | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS
* g | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL
* s | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS
@@ -98,16 +95,6 @@ public final class FormatSpec {
* | is not a word ? 1 bit, 1 = yes, 0 = no : FLAG_IS_NOT_A_WORD
* | is blacklisted ? 1 bit, 1 = yes, 0 = no : FLAG_IS_BLACKLISTED
*
- * p |
- * a | parent address, 3byte
- * r | 1 byte = bbbbbbbb match
- * e | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
- * n | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte
- * t | This address is relative to the head of the PtNode.
- * a | If the node doesn't have a parent, this field is set to 0.
- * d |
- * dress
- *
* c | IF FLAG_HAS_MULTIPLE_CHARS
* h | char, char, char, char n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers
* a | end 1 byte, = 0
@@ -121,15 +108,10 @@ public final class FormatSpec {
* q |
*
* c |
- * h | children address, 3 bytes
- * i | 1 byte = bbbbbbbb match
- * l | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
- * d | otherwise => (bbbbbbbb<<16) + (next byte << 8) + next byte
- * r | if this node doesn't have children, this field is set to 0.
- * e | (see BinaryDictEncoderUtils#writeVariableSignedAddress)
- * n | This address is relative to the position of this field.
- * a |
- * ddress
+ * h | children address, CHILDREN_ADDRESS_TYPE bytes
+ * i | This address is relative to the position of this field.
+ * l |
+ * drenaddress
*
* | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS
* | shortcut string list
@@ -179,17 +161,17 @@ public final class FormatSpec {
public static final int MAGIC_NUMBER = 0x9BC13AFE;
static final int NOT_A_VERSION_NUMBER = -1;
- static final int FIRST_VERSION_WITH_DYNAMIC_UPDATE = 3;
- static final int FIRST_VERSION_WITH_TERMINAL_ID = 4;
// These MUST have the same values as the relevant constants in format_utils.h.
- // From version 4 on, we use version * 100 + revision as a version number. That allows
+ // From version 2.01 on, we use version * 100 + revision as a version number. That allows
// us to change the format during development while having testing devices remove
// older files with each upgrade, while still having a readable versioning scheme.
// When we bump up the dictionary format version, we should update
// ExpandableDictionary.needsToMigrateDictionary() and
// ExpandableDictionary.matchesExpectedBinaryDictFormatVersionForThisType().
public static final int VERSION2 = 2;
+ public static final int VERSION201 = 201;
+ public static final int MINIMUM_SUPPORTED_VERSION_OF_CODE_POINT_TABLE = VERSION201;
// Dictionary version used for testing.
public static final int VERSION4_ONLY_FOR_TESTING = 399;
public static final int VERSION401 = 401;
@@ -202,9 +184,6 @@ public final class FormatSpec {
// use it in the reading code.
static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
- static final int PARENT_ADDRESS_SIZE = 3;
- static final int FORWARD_LINK_ADDRESS_SIZE = 3;
-
// These flags are used only in the static dictionary.
static final int MASK_CHILDREN_ADDRESS_TYPE = 0xC0;
static final int FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS = 0x00;
@@ -220,13 +199,6 @@ public final class FormatSpec {
static final int FLAG_IS_NOT_A_WORD = 0x02;
static final int FLAG_IS_BLACKLISTED = 0x01;
- // These flags are used only in the dynamic dictionary.
- static final int MASK_MOVE_AND_DELETE_FLAG = 0xC0;
- static final int FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE = 0x40;
- static final int FLAG_IS_MOVED = 0x00 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
- static final int FLAG_IS_NOT_MOVED = 0x80 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
- static final int FLAG_IS_DELETED = 0x80;
-
static final int FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT = 0x80;
static final int FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE = 0x40;
static final int MASK_BIGRAM_ATTR_ADDRESS_TYPE = 0x30;
@@ -240,52 +212,12 @@ public final class FormatSpec {
static final int PTNODE_TERMINATOR_SIZE = 1;
static final int PTNODE_FLAGS_SIZE = 1;
static final int PTNODE_FREQUENCY_SIZE = 1;
- static final int PTNODE_TERMINAL_ID_SIZE = 4;
static final int PTNODE_MAX_ADDRESS_SIZE = 3;
static final int PTNODE_ATTRIBUTE_FLAGS_SIZE = 1;
static final int PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE = 3;
static final int PTNODE_SHORTCUT_LIST_SIZE_SIZE = 2;
- // These values are used only by version 4 or later. They MUST match the definitions in
- // ver4_dict_constants.cpp.
- static final String TRIE_FILE_EXTENSION = ".trie";
- public static final String HEADER_FILE_EXTENSION = ".header";
- static final String FREQ_FILE_EXTENSION = ".freq";
- // tat = Terminal Address Table
- static final String TERMINAL_ADDRESS_TABLE_FILE_EXTENSION = ".tat";
- static final String BIGRAM_FILE_EXTENSION = ".bigram";
- static final String SHORTCUT_FILE_EXTENSION = ".shortcut";
- static final String LOOKUP_TABLE_FILE_SUFFIX = "_lookup";
- static final String CONTENT_TABLE_FILE_SUFFIX = "_index";
- static final int FLAGS_IN_FREQ_FILE_SIZE = 1;
- static final int FREQUENCY_AND_FLAGS_SIZE = 2;
- static final int TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE = 3;
- static final int UNIGRAM_TIMESTAMP_SIZE = 4;
- static final int UNIGRAM_COUNTER_SIZE = 1;
- static final int UNIGRAM_LEVEL_SIZE = 1;
-
- // With the English main dictionary as of October 2013, the size of bigram address table is
- // is 345KB with the block size being 16.
- // This is 54% of that of full address table.
- static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 16;
- static final int BIGRAM_CONTENT_COUNT = 1;
- static final int BIGRAM_FREQ_CONTENT_INDEX = 0;
- static final String BIGRAM_FREQ_CONTENT_ID = "_freq";
- static final int BIGRAM_TIMESTAMP_SIZE = 4;
- static final int BIGRAM_COUNTER_SIZE = 1;
- static final int BIGRAM_LEVEL_SIZE = 1;
-
- static final int SHORTCUT_CONTENT_COUNT = 1;
- static final int SHORTCUT_CONTENT_INDEX = 0;
- // With the English main dictionary as of October 2013, the size of shortcut address table is
- // 26KB with the block size being 64.
- // This is only 4.4% of that of full address table.
- static final int SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE = 64;
- static final String SHORTCUT_CONTENT_ID = "_shortcut";
-
static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
- static final int NO_PARENT_ADDRESS = 0;
- static final int NO_FORWARD_LINK_ADDRESS = 0;
static final int INVALID_CHARACTER = -1;
static final int MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT = 0x7F; // 127
@@ -302,14 +234,13 @@ public final class FormatSpec {
// This option needs to be the same numeric value as the one in binary_format.h.
static final int NOT_VALID_WORD = -99;
- static final int SIGNED_CHILDREN_ADDRESS_SIZE = 3;
static final int UINT8_MAX = 0xFF;
static final int UINT16_MAX = 0xFFFF;
static final int UINT24_MAX = 0xFFFFFF;
- static final int SINT24_MAX = 0x7FFFFF;
static final int MSB8 = 0x80;
- static final int MSB24 = 0x800000;
+ static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
+ static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF;
/**
* Options about file format.
diff --git a/java/src/com/android/inputmethod/latin/makedict/NgramProperty.java b/java/src/com/android/inputmethod/latin/makedict/NgramProperty.java
new file mode 100644
index 000000000..99e0e273f
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/NgramProperty.java
@@ -0,0 +1,26 @@
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.latin.NgramContext;
+
+public class NgramProperty {
+ public final WeightedString mTargetWord;
+ public final NgramContext mNgramContext;
+
+ public NgramProperty(final WeightedString targetWord, final NgramContext ngramContext) {
+ mTargetWord = targetWord;
+ mNgramContext = ngramContext;
+ }
+
+ @Override
+ public int hashCode() {
+ return mTargetWord.hashCode() ^ mNgramContext.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof NgramProperty)) return false;
+ final NgramProperty n = (NgramProperty)o;
+ return mTargetWord.equals(n.mTargetWord) && mNgramContext.equals(n.mNgramContext);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
index cd78e2235..46705f9db 100644
--- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
+++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
@@ -18,6 +18,8 @@ package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.NgramContext.WordInfo;
import com.android.inputmethod.latin.utils.CombinedFormatUtils;
import com.android.inputmethod.latin.utils.StringUtils;
@@ -33,16 +35,17 @@ public final class WordProperty implements Comparable<WordProperty> {
public final String mWord;
public final ProbabilityInfo mProbabilityInfo;
public final ArrayList<WeightedString> mShortcutTargets;
- public final ArrayList<WeightedString> mBigrams;
+ public final ArrayList<NgramProperty> mNgrams;
// TODO: Support mIsBeginningOfSentence.
public final boolean mIsBeginningOfSentence;
public final boolean mIsNotAWord;
public final boolean mIsBlacklistEntry;
public final boolean mHasShortcuts;
- public final boolean mHasBigrams;
+ public final boolean mHasNgrams;
private int mHashCode = 0;
+ // TODO: Support n-gram.
@UsedForTesting
public WordProperty(final String word, final ProbabilityInfo probabilityInfo,
final ArrayList<WeightedString> shortcutTargets,
@@ -51,11 +54,17 @@ public final class WordProperty implements Comparable<WordProperty> {
mWord = word;
mProbabilityInfo = probabilityInfo;
mShortcutTargets = shortcutTargets;
- mBigrams = bigrams;
+ mNgrams = new ArrayList<>();
+ final NgramContext ngramContext = new NgramContext(new WordInfo(mWord));
+ if (bigrams != null) {
+ for (final WeightedString bigramTarget : bigrams) {
+ mNgrams.add(new NgramProperty(bigramTarget, ngramContext));
+ }
+ }
mIsBeginningOfSentence = false;
mIsNotAWord = isNotAWord;
mIsBlacklistEntry = isBlacklistEntry;
- mHasBigrams = bigrams != null && !bigrams.isEmpty();
+ mHasNgrams = bigrams != null && !bigrams.isEmpty();
mHasShortcuts = shortcutTargets != null && !shortcutTargets.isEmpty();
}
@@ -78,19 +87,24 @@ public final class WordProperty implements Comparable<WordProperty> {
mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo);
mShortcutTargets = new ArrayList<>();
- mBigrams = new ArrayList<>();
+ mNgrams = new ArrayList<>();
mIsBeginningOfSentence = isBeginningOfSentence;
mIsNotAWord = isNotAWord;
mIsBlacklistEntry = isBlacklisted;
mHasShortcuts = hasShortcuts;
- mHasBigrams = hasBigram;
-
- final int bigramTargetCount = bigramTargets.size();
- for (int i = 0; i < bigramTargetCount; i++) {
- final String bigramTargetString =
+ mHasNgrams = hasBigram;
+
+ final int relatedNgramCount = bigramTargets.size();
+ final WordInfo currentWordInfo =
+ mIsBeginningOfSentence ? WordInfo.BEGINNING_OF_SENTENCE : new WordInfo(mWord);
+ final NgramContext ngramContext = new NgramContext(currentWordInfo);
+ for (int i = 0; i < relatedNgramCount; i++) {
+ final String ngramTargetString =
StringUtils.getStringFromNullTerminatedCodePointArray(bigramTargets.get(i));
- mBigrams.add(new WeightedString(bigramTargetString,
- createProbabilityInfoFromArray(bigramProbabilityInfo.get(i))));
+ final WeightedString ngramTarget = new WeightedString(ngramTargetString,
+ createProbabilityInfoFromArray(bigramProbabilityInfo.get(i)));
+ // TODO: Support n-gram.
+ mNgrams.add(new NgramProperty(ngramTarget, ngramContext));
}
final int shortcutTargetCount = shortcutTargets.size();
@@ -102,6 +116,17 @@ public final class WordProperty implements Comparable<WordProperty> {
}
}
+ // TODO: Remove
+ public ArrayList<WeightedString> getBigrams() {
+ final ArrayList<WeightedString> bigrams = new ArrayList<>();
+ for (final NgramProperty ngram : mNgrams) {
+ if (ngram.mNgramContext.getPrevWordCount() == 1) {
+ bigrams.add(ngram.mTargetWord);
+ }
+ }
+ return bigrams;
+ }
+
public int getProbability() {
return mProbabilityInfo.mProbability;
}
@@ -110,8 +135,8 @@ public final class WordProperty implements Comparable<WordProperty> {
return Arrays.hashCode(new Object[] {
word.mWord,
word.mProbabilityInfo,
- word.mShortcutTargets.hashCode(),
- word.mBigrams.hashCode(),
+ word.mShortcutTargets,
+ word.mNgrams,
word.mIsNotAWord,
word.mIsBlacklistEntry
});
@@ -142,9 +167,9 @@ public final class WordProperty implements Comparable<WordProperty> {
if (!(o instanceof WordProperty)) return false;
WordProperty w = (WordProperty)o;
return mProbabilityInfo.equals(w.mProbabilityInfo) && mWord.equals(w.mWord)
- && mShortcutTargets.equals(w.mShortcutTargets) && mBigrams.equals(w.mBigrams)
+ && mShortcutTargets.equals(w.mShortcutTargets) && mNgrams.equals(w.mNgrams)
&& mIsNotAWord == w.mIsNotAWord && mIsBlacklistEntry == w.mIsBlacklistEntry
- && mHasBigrams == w.mHasBigrams && mHasShortcuts && w.mHasBigrams;
+ && mHasNgrams == w.mHasNgrams && mHasShortcuts && w.mHasNgrams;
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java b/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java
new file mode 100644
index 000000000..0d0cbe169
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.network;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * A client for executing HTTP requests synchronously.
+ * This must never be called from the main thread.
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+@UsedForTesting
+public class BlockingHttpClient {
+ private final HttpURLConnection mConnection;
+
+ /**
+ * Interface that handles processing the response for a request.
+ */
+ public interface ResponseProcessor {
+ /**
+ * Called when the HTTP request fails with an error.
+ *
+ * @param httpStatusCode The status code of the HTTP response.
+ * @param message The HTTP response message, if any, or null.
+ */
+ void onError(int httpStatusCode, @Nullable String message);
+
+ /**
+ * Called when the HTTP request finishes successfully.
+ * The {@link InputStream} is closed by the client after the method finishes,
+ * so any processing must be done in this method itself.
+ *
+ * @param response An input stream that can be used to read the HTTP response.
+ */
+ void onSuccess(InputStream response);
+ }
+
+ /**
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public BlockingHttpClient(HttpURLConnection connection) {
+ mConnection = connection;
+ }
+
+ /**
+ * Executes the request on the underlying {@link HttpURLConnection}.
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ *
+ * @param request The request payload, if any, or null.
+ * @param responeProcessor A processor for the HTTP response.
+ */
+ @UsedForTesting
+ public void execute(@Nullable byte[] request, @Nonnull ResponseProcessor responseProcessor)
+ throws IOException {
+ try {
+ if (request != null) {
+ OutputStream out = new BufferedOutputStream(mConnection.getOutputStream());
+ out.write(request);
+ out.flush();
+ out.close();
+ }
+
+ final int responseCode = mConnection.getResponseCode();
+ if (responseCode != HttpURLConnection.HTTP_OK) {
+ responseProcessor.onError(responseCode, mConnection.getResponseMessage());
+ } else {
+ responseProcessor.onSuccess(mConnection.getInputStream());
+ }
+ } finally {
+ mConnection.disconnect();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java b/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java
new file mode 100644
index 000000000..35b65be56
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.network;
+
+import android.text.TextUtils;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+/**
+ * Builder for {@link HttpURLConnection}s.
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+@UsedForTesting
+public class HttpUrlConnectionBuilder {
+ private static final int DEFAULT_TIMEOUT_MILLIS = 5 * 1000;
+
+ /**
+ * Request header key for cache control.
+ */
+ public static final String KEY_CACHE_CONTROL = "Cache-Control";
+ /**
+ * Request header value for cache control indicating no caching.
+ * @see #KEY_CACHE_CONTROL
+ */
+ public static final String VALUE_NO_CACHE = "no-cache";
+
+ /**
+ * Indicates that the request is unidirectional - upload-only.
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public static final int MODE_UPLOAD_ONLY = 1;
+ /**
+ * Indicates that the request is unidirectional - download only.
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public static final int MODE_DOWNLOAD_ONLY = 2;
+ /**
+ * Indicates that the request is bi-directional.
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public static final int MODE_BI_DIRECTIONAL = 3;
+
+ private final HashMap<String, String> mHeaderMap = new HashMap<>();
+
+ private URL mUrl;
+ private int mConnectTimeoutMillis = DEFAULT_TIMEOUT_MILLIS;
+ private int mReadTimeoutMillis = DEFAULT_TIMEOUT_MILLIS;
+ private int mContentLength = -1;
+ private boolean mUseCache;
+ private int mMode;
+
+ /**
+ * Sets the URL that'll be used for the request.
+ * This *must* be set before calling {@link #build()}
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setUrl(String url) throws MalformedURLException {
+ if (TextUtils.isEmpty(url)) {
+ throw new IllegalArgumentException("URL must not be empty");
+ }
+ mUrl = new URL(url);
+ return this;
+ }
+
+ /**
+ * Sets the connect timeout. Defaults to {@value #DEFAULT_TIMEOUT} milliseconds.
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setConnectTimeout(int timeoutMillis) {
+ if (timeoutMillis < 0) {
+ throw new IllegalArgumentException("connect-timeout must be >= 0, but was "
+ + timeoutMillis);
+ }
+ mConnectTimeoutMillis = timeoutMillis;
+ return this;
+ }
+
+ /**
+ * Sets the read timeout. Defaults to {@value #DEFAULT_TIMEOUT} milliseconds.
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setReadTimeout(int timeoutMillis) {
+ if (timeoutMillis < 0) {
+ throw new IllegalArgumentException("read-timeout must be >= 0, but was "
+ + timeoutMillis);
+ }
+ mReadTimeoutMillis = timeoutMillis;
+ return this;
+ }
+
+ /**
+ * Adds an entry to the request header.
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder addHeader(String key, String value) {
+ mHeaderMap.put(key, value);
+ return this;
+ }
+
+ /**
+ * Sets the request to be executed such that the input is not buffered.
+ * This may be set when the request size is known beforehand.
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setFixedLengthForStreaming(int length) {
+ mContentLength = length;
+ return this;
+ }
+
+ /**
+ * Indicates if the request can use cached responses or not.
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setUseCache(boolean useCache) {
+ mUseCache = useCache;
+ return this;
+ }
+
+ /**
+ * The request mode.
+ * Sets the request mode to be one of: upload-only, download-only or bidirectional.
+ *
+ * @see #MODE_UPLOAD_ONLY
+ * @see #MODE_DOWNLOAD_ONLY
+ * @see #MODE_BI_DIRECTIONAL
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setMode(int mode) {
+ if (mode != MODE_UPLOAD_ONLY
+ && mode != MODE_DOWNLOAD_ONLY
+ && mode != MODE_BI_DIRECTIONAL) {
+ throw new IllegalArgumentException("Invalid mode specified:" + mode);
+ }
+ mMode = mode;
+ return this;
+ }
+
+ /**
+ * Builds the {@link HttpURLConnection} instance that can be used to execute the request.
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public HttpURLConnection build() throws IOException {
+ if (mUrl == null) {
+ throw new IllegalArgumentException("A URL must be specified!");
+ }
+ final HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
+ connection.setConnectTimeout(mConnectTimeoutMillis);
+ connection.setReadTimeout(mReadTimeoutMillis);
+ connection.setUseCaches(mUseCache);
+ switch (mMode) {
+ case MODE_UPLOAD_ONLY:
+ connection.setDoInput(true);
+ connection.setDoOutput(false);
+ break;
+ case MODE_DOWNLOAD_ONLY:
+ connection.setDoInput(false);
+ connection.setDoOutput(true);
+ break;
+ case MODE_BI_DIRECTIONAL:
+ connection.setDoInput(true);
+ connection.setDoOutput(true);
+ break;
+ }
+ for (final Entry<String, String> entry : mHeaderMap.entrySet()) {
+ connection.addRequestProperty(entry.getKey(), entry.getValue());
+ }
+ if (mContentLength >= 0) {
+ connection.setFixedLengthStreamingMode(mContentLength);
+ }
+ return connection;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
index 9d72de8c5..734ed5583 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
@@ -18,20 +18,22 @@ package com.android.inputmethod.latin.personalization;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
public class PersonalizationDataChunk {
+ public static final String LANGUAGE_UNKNOWN = "";
+
public final boolean mInputByUser;
public final List<String> mTokens;
public final int mTimestampInSeconds;
public final String mPackageName;
- public final Locale mlocale = null;
+ public final String mDetectedLanguage;
public PersonalizationDataChunk(boolean inputByUser, final List<String> tokens,
- final int timestampInSeconds, final String packageName) {
+ final int timestampInSeconds, final String packageName, final String detectedLanguage) {
mInputByUser = inputByUser;
mTokens = Collections.unmodifiableList(tokens);
mTimestampInSeconds = timestampInSeconds;
mPackageName = packageName;
+ mDetectedLanguage = detectedLanguage;
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 34d4d4ed7..d61684698 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -23,7 +23,7 @@ import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.ExpandableBinaryDictionary;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.utils.DistracterFilter;
import java.io.File;
@@ -35,6 +35,7 @@ import java.util.Locale;
*/
public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBase {
/* package */ static final String NAME = UserHistoryDictionary.class.getSimpleName();
+ private final static int SUPPORTED_NGRAM = 2; // TODO: 3
// TODO: Make this constructor private
/* package */ UserHistoryDictionary(final Context context, final Locale locale) {
@@ -52,18 +53,16 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas
* Add a word to the user history dictionary.
*
* @param userHistoryDictionary the user history dictionary
- * @param prevWordsInfo the information of previous words
+ * @param ngramContext the n-gram context
* @param word the word the user inputted
* @param isValid whether the word is valid or not
* @param timestamp the timestamp when the word has been inputted
* @param distracterFilter the filter to check whether the word is a distracter
*/
public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
- final PrevWordsInfo prevWordsInfo, final String word, final boolean isValid,
+ final NgramContext ngramContext, final String word, final boolean isValid,
final int timestamp, final DistracterFilter distracterFilter) {
- final CharSequence prevWord = prevWordsInfo.mPrevWordsInfo[0].mWord;
- if (word.length() > Constants.DICTIONARY_MAX_WORD_LENGTH ||
- (prevWord != null && prevWord.length() > Constants.DICTIONARY_MAX_WORD_LENGTH)) {
+ if (word.length() > Constants.DICTIONARY_MAX_WORD_LENGTH) {
return;
}
final int frequency = isValid ?
@@ -71,17 +70,28 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas
userHistoryDictionary.addUnigramEntryWithCheckingDistracter(word, frequency,
null /* shortcutTarget */, 0 /* shortcutFreq */, false /* isNotAWord */,
false /* isBlacklisted */, timestamp, distracterFilter);
- // Do not insert a word as a bigram of itself
- if (TextUtils.equals(word, prevWord)) {
- return;
- }
- if (null != prevWord) {
- if (prevWordsInfo.mPrevWordsInfo[0].mIsBeginningOfSentence) {
- // Beginning-of-Sentence n-gram entry is treated as a n-gram entry of invalid word.
- userHistoryDictionary.addNgramEntry(prevWordsInfo, word,
+
+ final boolean isBeginningOfSentenceContext = ngramContext.isBeginningOfSentenceContext();
+ final NgramContext ngramContextToBeSaved =
+ ngramContext.getTrimmedNgramContext(SUPPORTED_NGRAM - 1);
+ for (int i = 0; i < ngramContextToBeSaved.getPrevWordCount(); i++) {
+ final CharSequence prevWord = ngramContextToBeSaved.getNthPrevWord(1 /* n */);
+ if (prevWord == null || (prevWord.length() > Constants.DICTIONARY_MAX_WORD_LENGTH)) {
+ return;
+ }
+ // Do not insert a word as a bigram of itself
+ if (i == 0 && TextUtils.equals(word, prevWord)) {
+ return;
+ }
+ if (isBeginningOfSentenceContext) {
+ // Beginning-of-Sentence n-gram entry is added as an n-gram entry of an OOV word.
+ userHistoryDictionary.addNgramEntry(
+ ngramContextToBeSaved.getTrimmedNgramContext(i + 1), word,
FREQUENCY_FOR_WORDS_NOT_IN_DICTS, timestamp);
} else {
- userHistoryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp);
+ userHistoryDictionary.addNgramEntry(
+ ngramContextToBeSaved.getTrimmedNgramContext(i + 1), word, frequency,
+ timestamp);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
new file mode 100644
index 000000000..ffb0ad7bf
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.settings;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.text.TextUtils;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.accounts.LoginAccountUtils;
+import com.android.inputmethod.latin.define.ProductionFlags;
+
+import javax.annotation.Nullable;
+
+/**
+ * "Accounts & Privacy" settings sub screen.
+ *
+ * This settings sub screen handles the following preferences:
+ * <li> Account selection/management for IME
+ * <li> TODO: Sync preferences
+ * <li> TODO: Privacy preferences
+ */
+public final class AccountsSettingsFragment extends SubScreenFragment {
+ static final String PREF_ACCCOUNT_SWITCHER = "account_switcher";
+
+ private final DialogInterface.OnClickListener mAccountSelectedListener =
+ new AccountSelectedListener();
+ private final DialogInterface.OnClickListener mAccountSignedOutListener =
+ new AccountSignedOutListener();
+
+ @Override
+ public void onCreate(final Bundle icicle) {
+ super.onCreate(icicle);
+ addPreferencesFromResource(R.xml.prefs_screen_accounts);
+
+ final Resources res = getResources();
+ final Context context = getActivity();
+
+ // When we are called from the Settings application but we are not already running, some
+ // singleton and utility classes may not have been initialized. We have to call
+ // initialization method of these classes here. See {@link LatinIME#onCreate()}.
+ SubtypeSwitcher.init(context);
+
+ if (ProductionFlags.IS_METRICS_LOGGING_SUPPORTED) {
+ final Preference enableMetricsLogging =
+ findPreference(Settings.PREF_ENABLE_METRICS_LOGGING);
+ if (enableMetricsLogging != null) {
+ final String enableMetricsLoggingTitle = res.getString(
+ R.string.enable_metrics_logging, getApplicationName());
+ enableMetricsLogging.setTitle(enableMetricsLoggingTitle);
+ }
+ } else {
+ removePreference(Settings.PREF_ENABLE_METRICS_LOGGING);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshAccountSelection();
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
+ // TODO: Look at the preference that changed before refreshing the view.
+ refreshAccountSelection();
+ }
+
+ private void refreshAccountSelection() {
+ final String currentAccount = getCurrentlySelectedAccount();
+ final Preference accountSwitcher = findPreference(PREF_ACCCOUNT_SWITCHER);
+ if (currentAccount == null) {
+ // No account is currently selected.
+ accountSwitcher.setSummary(getString(R.string.no_accounts_selected));
+ } else {
+ // Set the currently selected account.
+ accountSwitcher.setSummary(getString(R.string.account_selected, currentAccount));
+ }
+ final Context context = getActivity();
+ final String[] accountsForLogin = LoginAccountUtils.getAccountsForLogin(context);
+ accountSwitcher.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (accountsForLogin.length == 0) {
+ // TODO: Handle account addition.
+ Toast.makeText(getActivity(),
+ getString(R.string.account_select_cancel), Toast.LENGTH_SHORT).show();
+ } else {
+ createAccountPicker(accountsForLogin, currentAccount).show();
+ }
+ return true;
+ }
+ });
+
+ // TODO: Depending on the account selection, enable/disable preferences that
+ // depend on an account.
+ }
+
+ @Nullable
+ private String getCurrentlySelectedAccount() {
+ return getSharedPreferences().getString(Settings.PREF_ACCOUNT_NAME, null);
+ }
+
+ /**
+ * Creates an account picker dialog showing the given accounts in a list and selecting
+ * the selected account by default.
+ * The list of accounts must not be null/empty.
+ *
+ * Package-private for testing.
+ */
+ AlertDialog createAccountPicker(final String[] accounts,
+ final String selectedAccount) {
+ if (accounts == null || accounts.length == 0) {
+ throw new IllegalArgumentException("List of accounts must not be empty");
+ }
+
+ // See if the currently selected account is in the list.
+ // If it is, the entry is selected, and a sign-out button is provided.
+ // If it isn't, select the 0th account by default which will get picked up
+ // if the user presses OK.
+ int index = 0;
+ boolean isSignedIn = false;
+ for (int i = 0; i < accounts.length; i++) {
+ if (TextUtils.equals(accounts[i], selectedAccount)) {
+ index = i;
+ isSignedIn = true;
+ break;
+ }
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.account_select_title)
+ .setSingleChoiceItems(accounts, index, null)
+ .setPositiveButton(R.string.account_select_ok, mAccountSelectedListener)
+ .setNegativeButton(R.string.account_select_cancel, null);
+ if (isSignedIn) {
+ builder.setNeutralButton(R.string.account_select_sign_out, mAccountSignedOutListener);
+ }
+ return builder.create();
+ }
+
+ /**
+ * Listener for an account being selected from the picker.
+ * Persists the account to shared preferences.
+ */
+ class AccountSelectedListener implements DialogInterface.OnClickListener {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final ListView lv = ((AlertDialog)dialog).getListView();
+ final Object selectedItem = lv.getItemAtPosition(lv.getCheckedItemPosition());
+ getSharedPreferences()
+ .edit()
+ .putString(Settings.PREF_ACCOUNT_NAME, (String) selectedItem)
+ .apply();
+ }
+ }
+
+ /**
+ * Listener for sign-out being initiated from from the picker.
+ * Removed the account from shared preferences.
+ */
+ class AccountSignedOutListener implements DialogInterface.OnClickListener {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ getSharedPreferences()
+ .edit()
+ .remove(Settings.PREF_ACCOUNT_NAME)
+ .apply();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
index 00f2c73dd..3303ab093 100644
--- a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
@@ -93,20 +93,24 @@ public final class AdvancedSettingsFragment extends SubScreenFragment {
removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
}
+ // If metrics logging isn't supported, or account sign in is enabled
+ // don't show the logging preference.
+ // TODO: Eventually when we enable account sign in by default,
+ // we'll remove logging preference from here.
if (ProductionFlags.IS_METRICS_LOGGING_SUPPORTED) {
final Preference enableMetricsLogging =
findPreference(Settings.PREF_ENABLE_METRICS_LOGGING);
if (enableMetricsLogging != null) {
- final int applicationLabelRes = context.getApplicationInfo().labelRes;
- final String applicationName = res.getString(applicationLabelRes);
final String enableMetricsLoggingTitle = res.getString(
- R.string.enable_metrics_logging, applicationName);
+ R.string.enable_metrics_logging, getApplicationName());
enableMetricsLogging.setTitle(enableMetricsLoggingTitle);
}
} else {
removePreference(Settings.PREF_ENABLE_METRICS_LOGGING);
}
+ AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this);
+
setupKeypressVibrationDurationSettings();
setupKeypressSoundVolumeSettings();
refreshEnablingsOfKeypressSoundAndVibrationSettings();
diff --git a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
index f5e4d33a2..a9884ba13 100644
--- a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
@@ -19,6 +19,7 @@ package com.android.inputmethod.latin.settings;
import android.os.Bundle;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.define.ProductionFlags;
/**
@@ -29,6 +30,10 @@ public final class AppearanceSettingsFragment extends SubScreenFragment {
public void onCreate(final Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.prefs_screen_appearance);
+ if (!ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED
+ || !Settings.getInstance().getCurrent().isTablet()) {
+ removePreference(Settings.PREF_ENABLE_SPLIT_KEYBOARD);
+ }
}
@Override
@@ -38,4 +43,4 @@ public final class AppearanceSettingsFragment extends SubScreenFragment {
findPreference(Settings.PREF_CUSTOM_INPUT_STYLES));
ThemeSettingsFragment.updateKeyboardThemeSummary(findPreference(Settings.SCREEN_THEME));
}
-}
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
index 9bc398654..2174f5224 100644
--- a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
@@ -32,6 +32,7 @@ import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.support.v4.view.ViewCompat;
import android.text.TextUtils;
+import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -59,6 +60,12 @@ import java.util.ArrayList;
import java.util.TreeSet;
public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
+ private static final String TAG = CustomInputStyleSettingsFragment.class.getSimpleName();
+ private static final boolean DEBUG_SUBTYPE_ID = false;
+ // Note: We would like to turn this debug flag true in order to see what input styles are
+ // defined in a bug-report.
+ private static final boolean DEBUG_CUSTOM_INPUT_STYLES = true;
+
private RichInputMethodManager mRichImm;
private SharedPreferences mPrefs;
private SubtypeLocaleAdapter mSubtypeLocaleAdapter;
@@ -96,8 +103,7 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
}
static final class SubtypeLocaleAdapter extends ArrayAdapter<SubtypeLocaleItem> {
- private static final String TAG = SubtypeLocaleAdapter.class.getSimpleName();
- private static final boolean DEBUG_SUBTYPE_ID = false;
+ private static final String TAG_SUBTYPE = SubtypeLocaleAdapter.class.getSimpleName();
public SubtypeLocaleAdapter(final Context context) {
super(context, android.R.layout.simple_spinner_item);
@@ -110,7 +116,7 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
for (int i = 0; i < count; i++) {
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
if (DEBUG_SUBTYPE_ID) {
- android.util.Log.d(TAG, String.format("%-6s 0x%08x %11d %s",
+ Log.d(TAG_SUBTYPE, String.format("%-6s 0x%08x %11d %s",
subtype.getLocale(), subtype.hashCode(), subtype.hashCode(),
SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)));
}
@@ -445,6 +451,9 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
final String prefSubtypes =
Settings.readPrefAdditionalSubtypes(mPrefs, getResources());
+ if (DEBUG_CUSTOM_INPUT_STYLES) {
+ Log.i(TAG, "Load custom input styles: " + prefSubtypes);
+ }
setPrefSubtypes(prefSubtypes, context);
mIsAddingNewSubtype = (savedInstanceState != null)
@@ -611,6 +620,9 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
final String oldSubtypes = Settings.readPrefAdditionalSubtypes(mPrefs, getResources());
final InputMethodSubtype[] subtypes = getSubtypes();
final String prefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(subtypes);
+ if (DEBUG_CUSTOM_INPUT_STYLES) {
+ Log.i(TAG, "Save custom input styles: " + prefSubtypes);
+ }
if (prefSubtypes.equals(oldSubtypes)) {
return;
}
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 0de2d8831..83adb1c55 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -32,6 +32,7 @@ import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.latin.utils.RunInLocale;
+import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import java.util.Collections;
@@ -43,6 +44,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
private static final String TAG = Settings.class.getSimpleName();
// Settings screens
public static final String SCREEN_PREFERENCES = "screen_preferences";
+ public static final String SCREEN_ACCOUNTS = "screen_accounts";
public static final String SCREEN_APPEARANCE = "screen_appearance";
public static final String SCREEN_THEME = "screen_theme";
public static final String SCREEN_MULTILINGUAL = "screen_multilingual";
@@ -83,6 +85,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
"pref_include_other_imes_in_language_switch_list";
public static final String PREF_KEYBOARD_THEME = "pref_keyboard_theme";
public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles";
+ public static final String PREF_ENABLE_SPLIT_KEYBOARD = "pref_split_keyboard";
// TODO: consolidate key preview dismiss delay with the key preview animation parameters.
public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
"pref_key_preview_popup_dismiss_delay";
@@ -103,6 +106,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_KEY_IS_INTERNAL = "pref_key_is_internal";
public static final String PREF_ENABLE_METRICS_LOGGING = "pref_enable_metrics_logging";
+ public static final String PREF_ACCOUNT_NAME = "pref_account_name";
// This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead.
// This is being used only for the backward compatibility.
@@ -166,6 +170,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return;
}
loadSettings(mContext, mSettingsValues.mLocale, mSettingsValues.mInputAttributes);
+ StatsUtils.onLoadSettings(mSettingsValues);
} finally {
mSettingsValuesLock.unlock();
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 4fc17387f..8c4801798 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -25,6 +25,7 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.define.ProductionFlags;
import com.android.inputmethod.latin.utils.ApplicationUtils;
import com.android.inputmethod.latin.utils.FeedbackUtils;
import com.android.inputmethodcommon.InputMethodSettingsFragment;
@@ -51,6 +52,10 @@ public final class SettingsFragment extends InputMethodSettingsFragment {
final Preference multilingualOptions = findPreference(Settings.SCREEN_MULTILINGUAL);
preferenceScreen.removePreference(multilingualOptions);
}
+ if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) {
+ final Preference accountsPreference = findPreference(Settings.SCREEN_ACCOUNTS);
+ preferenceScreen.removePreference(accountsPreference);
+ }
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index d8c548d8b..99f761ca6 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -25,6 +25,7 @@ import android.util.Log;
import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.AppWorkaroundsUtils;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
@@ -40,7 +41,8 @@ import java.util.Locale;
* When you call the constructor of this class, you may want to change the current system locale by
* using {@link com.android.inputmethod.latin.utils.RunInLocale}.
*/
-public final class SettingsValues {
+// Non-final for testing via mock library.
+public class SettingsValues {
private static final String TAG = SettingsValues.class.getSimpleName();
// "floatMaxValue" and "floatNegativeInfinity" are special marker strings for
// Float.NEGATIVE_INFINITE and Float.MAX_VALUE. Currently used for auto-correction settings.
@@ -62,6 +64,7 @@ public final class SettingsValues {
public final boolean mSoundOn;
public final boolean mKeyPreviewPopupOn;
public final boolean mShowsVoiceInputKey;
+ public final String mAccountName;
public final boolean mIncludesOtherImesInLanguageSwitchList;
public final boolean mShowsLanguageSwitchKey;
public final boolean mUseContactsDict;
@@ -78,6 +81,9 @@ public final class SettingsValues {
public final int mKeyLongpressTimeout;
public final boolean mEnableMetricsLogging;
public final boolean mShouldShowUiToAcceptTypedWord;
+ // Use split layout for keyboard.
+ public final boolean mIsSplitKeyboardEnabled;
+ public final int mScreenMetrics;
// From the input box
public final InputAttributes mInputAttributes;
@@ -135,6 +141,7 @@ public final class SettingsValues {
mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res)
&& mInputAttributes.mShouldShowVoiceInputKey
&& SubtypeSwitcher.getInstance().isShortcutImeEnabled();
+ mAccountName = prefs.getString(Settings.PREF_ACCOUNT_NAME, null);
final String autoCorrectionThresholdRawValue = prefs.getString(
Settings.PREF_AUTO_CORRECTION_THRESHOLD,
res.getString(R.string.auto_correction_threshold_mode_index_modest));
@@ -153,6 +160,9 @@ public final class SettingsValues {
mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout);
mHasHardwareKeyboard = Settings.readHasHardwareKeyboard(res.getConfiguration());
mEnableMetricsLogging = prefs.getBoolean(Settings.PREF_ENABLE_METRICS_LOGGING, true);
+ mIsSplitKeyboardEnabled = prefs.getBoolean(Settings.PREF_ENABLE_SPLIT_KEYBOARD, false);
+ mScreenMetrics = res.getInteger(R.integer.config_screen_metrics);
+
mShouldShowUiToAcceptTypedWord = Settings.HAS_UI_TO_ACCEPT_TYPED_WORD
&& prefs.getBoolean(DebugSettings.PREF_SHOW_UI_TO_ACCEPT_TYPED_WORD, true);
// Compute other readable settings
@@ -164,13 +174,13 @@ public final class SettingsValues {
autoCorrectionThresholdRawValue);
mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
- mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
- Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
+ mGestureFloatingPreviewTextEnabled = !mInputAttributes.mDisableGestureFloatingPreviewText
+ && prefs.getBoolean(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
mPhraseGestureEnabled = Settings.readPhraseGestureEnabled(prefs, res);
mAutoCorrectionEnabledPerUserSettings = mAutoCorrectEnabled
&& !mInputAttributes.mInputTypeNoAutoCorrect;
mSuggestionsEnabledPerUserSettings = readSuggestionsEnabled(prefs);
- AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
+ AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(context,
prefs, mAdditionalFeaturesSettingValues);
mTextHighlightColorForAddToDictionaryIndicator = res.getColor(
R.color.text_decorator_add_to_dictionary_indicator_text_highlight_color);
@@ -211,6 +221,15 @@ public final class SettingsValues {
}
}
+ public boolean isMetricsLoggingEnabled() {
+ return mEnableMetricsLogging;
+ }
+
+ public boolean isTablet() {
+ return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_TABLET
+ || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_TABLET;
+ }
+
public boolean isApplicationSpecifiedCompletionsOn() {
return mInputAttributes.mApplicationSpecifiedCompletionOn;
}
@@ -366,6 +385,8 @@ public final class SettingsValues {
sb.append("" + mKeyPreviewPopupOn);
sb.append("\n mShowsVoiceInputKey = ");
sb.append("" + mShowsVoiceInputKey);
+ sb.append("\n mAccountName = ");
+ sb.append("" + mAccountName);
sb.append("\n mIncludesOtherImesInLanguageSwitchList = ");
sb.append("" + mIncludesOtherImesInLanguageSwitchList);
sb.append("\n mShowsLanguageSwitchKey = ");
diff --git a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
index 49d81104d..97aad3b6d 100644
--- a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
+++ b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
@@ -36,6 +36,8 @@ public final class SpacingAndPunctuations {
public final int[] mSortedWordSeparators;
public final PunctuationSuggestions mSuggestPuncList;
private final int mSentenceSeparator;
+ private final int mAbbreviationMarker;
+ private final int[] mSortedSentenceTerminators;
public final String mSentenceSeparatorAndSpace;
public final boolean mCurrentLanguageHasSpaces;
public final boolean mUsesAmericanTypography;
@@ -55,7 +57,10 @@ public final class SpacingAndPunctuations {
res.getString(R.string.symbols_word_connectors));
mSortedWordSeparators = StringUtils.toSortedCodePointArray(
res.getString(R.string.symbols_word_separators));
+ mSortedSentenceTerminators = StringUtils.toSortedCodePointArray(
+ res.getString(R.string.symbols_sentence_terminators));
mSentenceSeparator = res.getInteger(R.integer.sentence_separator);
+ mAbbreviationMarker = res.getInteger(R.integer.abbreviation_marker);
mSentenceSeparatorAndSpace = new String(new int[] {
mSentenceSeparator, Constants.CODE_SPACE }, 0, 2);
mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces);
@@ -77,8 +82,10 @@ public final class SpacingAndPunctuations {
mSortedSymbolsClusteringTogether = model.mSortedSymbolsClusteringTogether;
mSortedWordConnectors = model.mSortedWordConnectors;
mSortedWordSeparators = overrideSortedWordSeparators;
+ mSortedSentenceTerminators = model.mSortedSentenceTerminators;
mSuggestPuncList = model.mSuggestPuncList;
mSentenceSeparator = model.mSentenceSeparator;
+ mAbbreviationMarker = model.mAbbreviationMarker;
mSentenceSeparatorAndSpace = model.mSentenceSeparatorAndSpace;
mCurrentLanguageHasSpaces = model.mCurrentLanguageHasSpaces;
mUsesAmericanTypography = model.mUsesAmericanTypography;
@@ -109,6 +116,14 @@ public final class SpacingAndPunctuations {
return Arrays.binarySearch(mSortedSymbolsClusteringTogether, code) >= 0;
}
+ public boolean isSentenceTerminator(final int code) {
+ return Arrays.binarySearch(mSortedSentenceTerminators, code) >= 0;
+ }
+
+ public boolean isAbbreviationMarker(final int code) {
+ return code == mAbbreviationMarker;
+ }
+
public boolean isSentenceSeparator(final int code) {
return code == mSentenceSeparator;
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java b/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java
index ca5b395ce..240f8f89b 100644
--- a/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java
@@ -20,6 +20,7 @@ import android.app.backup.BackupManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.Resources;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
@@ -79,6 +80,16 @@ abstract class SubScreenFragment extends PreferenceFragment
return getPreferenceManager().getSharedPreferences();
}
+ /**
+ * Gets the application name to display on the UI.
+ */
+ final String getApplicationName() {
+ final Context context = getActivity();
+ final Resources res = getResources();
+ final int applicationLabelRes = context.getApplicationInfo().labelRes;
+ return res.getString(applicationLabelRes);
+ }
+
@Override
public void addPreferencesFromResource(final int preferencesResId) {
super.addPreferencesFromResource(preferencesResId);
diff --git a/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java b/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java
new file mode 100644
index 000000000..254bc6567
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.settings;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Test activity to use when testing preference fragments. <br/>
+ * Usage: <br/>
+ * Create an ActivityInstrumentationTestCase2 for this activity
+ * and call setIntent() with an intent that specifies the fragment to load in the activity.
+ * The fragment can then be obtained from this activity and used for testing/verification.
+ */
+public final class TestFragmentActivity extends Activity {
+ /**
+ * The fragment name that should be loaded when starting this activity.
+ * This must be specified when starting this activity, as this activity is only
+ * meant to test fragments from instrumentation tests.
+ */
+ public static final String EXTRA_SHOW_FRAGMENT = "show_fragment";
+
+ public Fragment mFragment;
+
+ @Override
+ protected void onCreate(final Bundle savedState) {
+ super.onCreate(savedState);
+ final Intent intent = getIntent();
+ final String fragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
+ if (fragmentName == null) {
+ throw new IllegalArgumentException("No fragment name specified for testing");
+ }
+
+ mFragment = Fragment.instantiate(this, fragmentName);
+ FragmentManager fragmentManager = getFragmentManager();
+ fragmentManager.beginTransaction().add(mFragment, fragmentName).commit();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
index 5a3fc3600..29289aed2 100644
--- a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin.settings;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.Preference;
@@ -32,12 +31,12 @@ import com.android.inputmethod.latin.settings.RadioButtonPreference.OnRadioButto
*/
public final class ThemeSettingsFragment extends SubScreenFragment
implements OnRadioButtonClickedListener {
- private String mSelectedThemeId;
+ private int mSelectedThemeId;
static class KeyboardThemePreference extends RadioButtonPreference {
- final String mThemeId;
+ final int mThemeId;
- KeyboardThemePreference(final Context context, final String name, final String id) {
+ KeyboardThemePreference(final Context context, final String name, final int id) {
super(context);
setTitle(name);
mThemeId = id;
@@ -45,14 +44,13 @@ public final class ThemeSettingsFragment extends SubScreenFragment
}
static void updateKeyboardThemeSummary(final Preference pref) {
- final Resources res = pref.getContext().getResources();
- final SharedPreferences prefs = pref.getSharedPreferences();
- final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
- final String keyboardThemeId = String.valueOf(keyboardTheme.mThemeId);
+ final Context context = pref.getContext();
+ final Resources res = context.getResources();
+ final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context);
final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names);
- final String[] keyboardThemeIds = res.getStringArray(R.array.keyboard_theme_ids);
+ final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids);
for (int index = 0; index < keyboardThemeNames.length; index++) {
- if (keyboardThemeId.equals(keyboardThemeIds[index])) {
+ if (keyboardTheme.mThemeId == keyboardThemeIds[index]) {
pref.setSummary(keyboardThemeNames[index]);
return;
}
@@ -64,18 +62,18 @@ public final class ThemeSettingsFragment extends SubScreenFragment
super.onCreate(icicle);
addPreferencesFromResource(R.xml.prefs_screen_theme);
final PreferenceScreen screen = getPreferenceScreen();
+ final Context context = getActivity();
final Resources res = getResources();
final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names);
- final String[] keyboardThemeIds = res.getStringArray(R.array.keyboard_theme_ids);
+ final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids);
for (int index = 0; index < keyboardThemeNames.length; index++) {
final KeyboardThemePreference pref = new KeyboardThemePreference(
- getActivity(), keyboardThemeNames[index], keyboardThemeIds[index]);
+ context, keyboardThemeNames[index], keyboardThemeIds[index]);
screen.addPreference(pref);
pref.setOnRadioButtonClickedListener(this);
}
- final SharedPreferences prefs = getSharedPreferences();
- final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
- mSelectedThemeId = String.valueOf(keyboardTheme.mThemeId);
+ final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context);
+ mSelectedThemeId = keyboardTheme.mThemeId;
}
@Override
@@ -106,7 +104,7 @@ public final class ThemeSettingsFragment extends SubScreenFragment
final Preference preference = screen.getPreference(index);
if (preference instanceof KeyboardThemePreference) {
final KeyboardThemePreference pref = (KeyboardThemePreference)preference;
- final boolean selected = mSelectedThemeId.equals(pref.mThemeId);
+ final boolean selected = (mSelectedThemeId == pref.mThemeId);
pref.setSelected(selected);
}
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 90398deb2..2a4e14ca7 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -16,14 +16,11 @@
package com.android.inputmethod.latin.spellcheck;
-import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.service.textservice.SpellCheckerService;
import android.text.InputType;
-import android.util.Log;
-import android.util.LruCache;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.textservice.SuggestionsInfo;
@@ -32,39 +29,21 @@ import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.ContactsBinaryDictionary;
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.DictionaryCollection;
import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.DictionaryFactory;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.DictionaryFacilitatorLruCache;
+import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
-import com.android.inputmethod.latin.UserBinaryDictionary;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
-import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.SuggestionResults;
import com.android.inputmethod.latin.WordComposer;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.Locale;
-import java.util.Map;
-import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
/**
* Service for spell checking, using LatinIME's dictionaries and mechanisms.
@@ -80,61 +59,28 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 368;
private static final String DICTIONARY_NAME_PREFIX = "spellcheck_";
- private static final int WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS = 1000;
- private static final int MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT = 5;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private final HashSet<Locale> mCachedLocales = new HashSet<>();
-
private final int MAX_NUM_OF_THREADS_READ_DICTIONARY = 2;
private final Semaphore mSemaphore = new Semaphore(MAX_NUM_OF_THREADS_READ_DICTIONARY,
true /* fair */);
// TODO: Make each spell checker session has its own session id.
private final ConcurrentLinkedQueue<Integer> mSessionIdPool = new ConcurrentLinkedQueue<>();
- private static class DictionaryFacilitatorLruCache extends
- LruCache<Locale, DictionaryFacilitator> {
- private final HashSet<Locale> mCachedLocales;
- public DictionaryFacilitatorLruCache(final HashSet<Locale> cachedLocales, int maxSize) {
- super(maxSize);
- mCachedLocales = cachedLocales;
- }
-
- @Override
- protected void entryRemoved(boolean evicted, Locale key,
- DictionaryFacilitator oldValue, DictionaryFacilitator newValue) {
- if (oldValue != null && oldValue != newValue) {
- oldValue.closeDictionaries();
- }
- if (key != null && newValue == null) {
- // Remove locale from the cache when the dictionary facilitator for the locale is
- // evicted and new facilitator is not set for the locale.
- mCachedLocales.remove(key);
- if (size() >= maxSize()) {
- Log.w(TAG, "DictionaryFacilitator for " + key.toString()
- + " has been evicted due to cache size limit."
- + " size: " + size() + ", maxSize: " + maxSize());
- }
- }
- }
- }
-
private static final int MAX_DICTIONARY_FACILITATOR_COUNT = 3;
- private final LruCache<Locale, DictionaryFacilitator> mDictionaryFacilitatorCache =
- new DictionaryFacilitatorLruCache(mCachedLocales, MAX_DICTIONARY_FACILITATOR_COUNT);
+ private final DictionaryFacilitatorLruCache mDictionaryFacilitatorCache =
+ new DictionaryFacilitatorLruCache(this /* context */, MAX_DICTIONARY_FACILITATOR_COUNT,
+ DICTIONARY_NAME_PREFIX);
private final ConcurrentHashMap<Locale, Keyboard> mKeyboardCache = new ConcurrentHashMap<>();
// The threshold for a suggestion to be considered "recommended".
private float mRecommendedThreshold;
- // Whether to use the contacts dictionary
- private boolean mUseContactsDictionary;
// TODO: make a spell checker option to block offensive words or not
private final SettingsValuesForSuggestion mSettingsValuesForSuggestion =
new SettingsValuesForSuggestion(true /* blockPotentiallyOffensive */,
true /* spaceAwareGestureEnabled */,
null /* additionalFeaturesSettingValues */);
- private final Object mDictionaryLock = new Object();
public static final String SINGLE_QUOTE = "\u0027";
public static final String APOSTROPHE = "\u2019";
@@ -176,20 +122,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
final boolean useContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
- if (useContactsDictionary != mUseContactsDictionary) {
- mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
- try {
- mUseContactsDictionary = useContactsDictionary;
- for (final Locale locale : mCachedLocales) {
- final DictionaryFacilitator dictionaryFacilitator =
- mDictionaryFacilitatorCache.get(locale);
- resetDictionariesForLocale(this /* context */,
- dictionaryFacilitator, locale, mUseContactsDictionary);
- }
- } finally {
- mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
- }
- }
+ mDictionaryFacilitatorCache.setUseContactsDictionary(useContactsDictionary);
}
@Override
@@ -222,7 +155,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
mSemaphore.acquireUninterruptibly();
try {
DictionaryFacilitator dictionaryFacilitatorForLocale =
- getDictionaryFacilitatorForLocaleLocked(locale);
+ mDictionaryFacilitatorCache.get(locale);
return dictionaryFacilitatorForLocale.isValidWord(word, false /* igroreCase */);
} finally {
mSemaphore.release();
@@ -230,14 +163,14 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
}
public SuggestionResults getSuggestionResults(final Locale locale, final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo) {
+ final NgramContext ngramContext, final ProximityInfo proximityInfo) {
Integer sessionId = null;
mSemaphore.acquireUninterruptibly();
try {
sessionId = mSessionIdPool.poll();
DictionaryFacilitator dictionaryFacilitatorForLocale =
- getDictionaryFacilitatorForLocaleLocked(locale);
- return dictionaryFacilitatorForLocale.getSuggestionResults(composer, prevWordsInfo,
+ mDictionaryFacilitatorCache.get(locale);
+ return dictionaryFacilitatorForLocale.getSuggestionResults(composer, ngramContext,
proximityInfo, mSettingsValuesForSuggestion, sessionId);
} finally {
if (sessionId != null) {
@@ -251,56 +184,18 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
mSemaphore.acquireUninterruptibly();
try {
final DictionaryFacilitator dictionaryFacilitator =
- getDictionaryFacilitatorForLocaleLocked(locale);
- return dictionaryFacilitator.hasInitializedMainDictionary();
+ mDictionaryFacilitatorCache.get(locale);
+ return dictionaryFacilitator.hasAtLeastOneInitializedMainDictionary();
} finally {
mSemaphore.release();
}
}
- private DictionaryFacilitator getDictionaryFacilitatorForLocaleLocked(final Locale locale) {
- DictionaryFacilitator dictionaryFacilitatorForLocale =
- mDictionaryFacilitatorCache.get(locale);
- if (dictionaryFacilitatorForLocale == null) {
- dictionaryFacilitatorForLocale = new DictionaryFacilitator();
- mDictionaryFacilitatorCache.put(locale, dictionaryFacilitatorForLocale);
- mCachedLocales.add(locale);
- resetDictionariesForLocale(this /* context */, dictionaryFacilitatorForLocale,
- locale, mUseContactsDictionary);
- }
- return dictionaryFacilitatorForLocale;
- }
-
- private static void resetDictionariesForLocale(final Context context,
- final DictionaryFacilitator dictionaryFacilitator, final Locale locale,
- final boolean useContactsDictionary) {
- dictionaryFacilitator.resetDictionariesWithDictNamePrefix(context, locale,
- useContactsDictionary, false /* usePersonalizedDicts */,
- false /* forceReloadMainDictionary */, null /* listener */,
- DICTIONARY_NAME_PREFIX);
- for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) {
- try {
- dictionaryFacilitator.waitForLoadingMainDictionary(
- WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
- return;
- } catch (final InterruptedException e) {
- Log.i(TAG, "Interrupted during waiting for loading main dictionary.", e);
- if (i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT - 1) {
- Log.i(TAG, "Retry", e);
- } else {
- Log.w(TAG, "Give up retrying. Retried "
- + MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT + " times.", e);
- }
- }
- }
- }
-
@Override
public boolean onUnbind(final Intent intent) {
mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
try {
mDictionaryFacilitatorCache.evictAll();
- mCachedLocales.clear();
} finally {
mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
}
@@ -334,7 +229,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(this, editorInfo);
builder.setKeyboardGeometry(
SPELLCHECKER_DUMMY_KEYBOARD_WIDTH, SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT);
- builder.setSubtype(subtype);
+ builder.setSubtype(new RichInputMethodSubtype(subtype));
builder.setIsSpellChecker(true /* isSpellChecker */);
builder.disableTouchPositionCorrectionData();
return builder.build();
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 34e01197a..8393b306c 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -25,7 +25,7 @@ import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.TextInfoCompatUtils;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
@@ -62,8 +62,8 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
final int offset = ssi.getOffsetAt(i);
final int length = ssi.getLengthAt(i);
final CharSequence subText = typedText.subSequence(offset, offset + length);
- final PrevWordsInfo prevWordsInfo =
- new PrevWordsInfo(new PrevWordsInfo.WordInfo(currentWord));
+ final NgramContext ngramContext =
+ new NgramContext(new NgramContext.WordInfo(currentWord));
currentWord = subText;
if (!subText.toString().contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
continue;
@@ -80,7 +80,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
if (TextUtils.isEmpty(splitText)) {
continue;
}
- if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString(), prevWordsInfo)
+ if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString(), ngramContext)
== null) {
continue;
}
@@ -208,10 +208,10 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
} else {
prevWord = null;
}
- final PrevWordsInfo prevWordsInfo =
- new PrevWordsInfo(new PrevWordsInfo.WordInfo(prevWord));
+ final NgramContext ngramContext =
+ new NgramContext(new NgramContext.WordInfo(prevWord));
final TextInfo textInfo = textInfos[i];
- retval[i] = onGetSuggestionsInternal(textInfo, prevWordsInfo, suggestionsLimit);
+ retval[i] = onGetSuggestionsInternal(textInfo, ngramContext, suggestionsLimit);
retval[i].setCookieAndSequence(textInfo.getCookie(), textInfo.getSequence());
}
return retval;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index d668672aa..7b6aacd15 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -31,7 +31,7 @@ import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
@@ -73,27 +73,25 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache =
new LruCache<>(MAX_CACHE_SIZE);
- // TODO: Support n-gram input
- private static String generateKey(final String query, final PrevWordsInfo prevWordsInfo) {
- if (TextUtils.isEmpty(query) || !prevWordsInfo.isValid()) {
+ private static String generateKey(final String query, final NgramContext ngramContext) {
+ if (TextUtils.isEmpty(query) || !ngramContext.isValid()) {
return query;
}
- return query + CHAR_DELIMITER + prevWordsInfo;
+ return query + CHAR_DELIMITER + ngramContext;
}
public SuggestionsParams getSuggestionsFromCache(String query,
- final PrevWordsInfo prevWordsInfo) {
- return mUnigramSuggestionsInfoCache.get(generateKey(query, prevWordsInfo));
+ final NgramContext ngramContext) {
+ return mUnigramSuggestionsInfoCache.get(generateKey(query, ngramContext));
}
- public void putSuggestionsToCache(
- final String query, final PrevWordsInfo prevWordsInfo,
+ 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, prevWordsInfo), new SuggestionsParams(suggestions, flags));
+ generateKey(query, ngramContext), new SuggestionsParams(suggestions, flags));
}
public void clearCache() {
@@ -223,12 +221,11 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
}
protected SuggestionsInfo onGetSuggestionsInternal(
- final TextInfo textInfo, final PrevWordsInfo prevWordsInfo,
- final int suggestionsLimit) {
+ final TextInfo textInfo, final NgramContext ngramContext, final int suggestionsLimit) {
try {
final String inText = textInfo.getText();
final SuggestionsParams cachedSuggestionsParams =
- mSuggestionsCache.getSuggestionsFromCache(inText, prevWordsInfo);
+ mSuggestionsCache.getSuggestionsFromCache(inText, ngramContext);
if (cachedSuggestionsParams != null) {
if (DBG) {
Log.d(TAG, "Cache hit: " + inText + ", " + cachedSuggestionsParams.mFlags);
@@ -283,7 +280,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
composer.setComposingWord(codePoints, coordinates);
// TODO: Don't gather suggestions if the limit is <= 0 unless necessary
final SuggestionResults suggestionResults = mService.getSuggestionResults(
- mLocale, composer, prevWordsInfo, proximityInfo);
+ mLocale, composer, ngramContext, proximityInfo);
final Result result = getResult(capitalizeType, mLocale, suggestionsLimit,
mService.getRecommendedThreshold(), text, suggestionResults);
isInDict = isInDictForAnyCapitalization(text, capitalizeType);
@@ -308,7 +305,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
.getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS()
: 0);
final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions);
- mSuggestionsCache.putSuggestionsToCache(text, prevWordsInfo, result.mSuggestions,
+ mSuggestionsCache.putSuggestionsToCache(text, ngramContext, result.mSuggestions,
flags);
return retval;
} catch (RuntimeException e) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
index 51c4b1ee8..9ddee8629 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
@@ -145,9 +145,8 @@ public class SentenceLevelAdapter {
int wordEnd = wordIterator.getEndOfWord(originalText, wordStart);
while (wordStart <= end && wordEnd != -1 && wordStart != -1) {
if (wordEnd >= start && wordEnd > wordStart) {
- CharSequence subSequence = originalText.subSequence(wordStart, wordEnd).toString();
- final TextInfo ti = TextInfoCompatUtils.newInstance(subSequence, 0,
- subSequence.length(), cookie, subSequence.hashCode());
+ final TextInfo ti = TextInfoCompatUtils.newInstance(originalText, wordStart,
+ wordEnd, cookie, originalText.subSequence(wordStart, wordEnd).hashCode());
wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd));
}
wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index f7b6f919d..907e3fa42 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -40,6 +40,8 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
public abstract void onSuggestionSelected(final SuggestedWordInfo info);
}
+ private boolean mIsInModalMode;
+
public MoreSuggestionsView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.moreKeysKeyboardViewStyle);
}
@@ -53,6 +55,7 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
@Override
public void setKeyboard(final Keyboard keyboard) {
super.setKeyboard(keyboard);
+ mIsInModalMode = false;
// With accessibility mode off, {@link #mAccessibilityDelegate} is set to null at the
// above {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call.
// With accessibility mode on, {@link #mAccessibilityDelegate} is set to a
@@ -74,12 +77,17 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
updateKeyDrawParams(keyHeight);
}
- public void adjustVerticalCorrectionForModalMode() {
+ public void setModalMode() {
+ mIsInModalMode = true;
// Set vertical correction to zero (Reset more keys keyboard sliding allowance
// {@link R#dimen.config_more_keys_keyboard_slide_allowance}).
mKeyDetector.setKeyboard(getKeyboard(), -getPaddingLeft(), -getPaddingTop());
}
+ public boolean isInModalMode() {
+ return mIsInModalMode;
+ }
+
@Override
protected void onKeyInput(final Key key, final int x, final int y) {
if (!(key instanceof MoreSuggestionKey)) {
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 1e8df8986..839fce051 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -365,17 +365,21 @@ final class SuggestionStripLayoutHelper {
(PunctuationSuggestions)suggestedWords, stripView);
}
+ final boolean shouldShowUiToAcceptTypedWord = Settings.getInstance().getCurrent()
+ .mShouldShowUiToAcceptTypedWord;
+ final int suggestionsCount = suggestedWords.size()
+ - (shouldShowUiToAcceptTypedWord ? /* typed word */ 1 : 0);
final int startIndexOfMoreSuggestions = setupWordViewsAndReturnStartIndexOfMoreSuggestions(
suggestedWords, mSuggestionsCountInStrip);
final TextView centerWordView = mWordViews.get(mCenterPositionInStrip);
final int stripWidth = stripView.getWidth();
final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth);
- if (suggestedWords.size() == 1 || getTextScaleX(centerWordView.getText(), centerWidth,
+ if (suggestionsCount == 1 || getTextScaleX(centerWordView.getText(), centerWidth,
centerWordView.getPaint()) < MIN_TEXT_XSCALE) {
// Layout only the most relevant suggested word at the center of the suggestion strip
// by consolidating all slots in the strip.
final int countInStrip = 1;
- mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
+ mMoreSuggestionsAvailable = (suggestionsCount > countInStrip);
layoutWord(mCenterPositionInStrip, stripWidth - mPadding);
stripView.addView(centerWordView);
setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT);
@@ -387,7 +391,7 @@ final class SuggestionStripLayoutHelper {
}
final int countInStrip = mSuggestionsCountInStrip;
- mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
+ mMoreSuggestionsAvailable = (suggestionsCount > countInStrip);
int x = 0;
for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
if (positionInStrip != 0) {
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 33745a846..e40fd8800 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -82,7 +82,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private final ArrayList<View> mDividerViews = new ArrayList<>();
Listener mListener;
- private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+ private SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance();
private int mStartIndexOfMoreSuggestions;
private final SuggestionStripLayoutHelper mLayoutHelper;
@@ -131,6 +131,10 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mImportantNoticeStrip.setVisibility(VISIBLE);
}
+ public boolean isShowingImportantNoticeStrip() {
+ return mImportantNoticeStrip.getVisibility() == VISIBLE;
+ }
+
public boolean isShowingAddToDictionaryStrip() {
return mAddToDictionaryStrip.getVisibility() == VISIBLE;
}
@@ -393,11 +397,18 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
public boolean onInterceptTouchEvent(final MotionEvent me) {
+ if (mStripVisibilityGroup.isShowingImportantNoticeStrip()) {
+ return false;
+ }
+ // Detecting sliding up finger to show {@link MoreSuggestionsView}.
if (!mMoreSuggestionsView.isShowingInParent()) {
mLastX = (int)me.getX();
mLastY = (int)me.getY();
return mMoreSuggestionsSlidingDetector.onTouchEvent(me);
}
+ if (mMoreSuggestionsView.isInModalMode()) {
+ return false;
+ }
final int action = me.getAction();
final int index = me.getActionIndex();
@@ -408,15 +419,15 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
// Decided to be in the sliding suggestion mode only when the touch point has been moved
// upward. Further {@link MotionEvent}s will be delivered to
// {@link #onTouchEvent(MotionEvent)}.
- mNeedsToTransformTouchEventToHoverEvent = AccessibilityUtils.getInstance()
- .isTouchExplorationEnabled();
+ mNeedsToTransformTouchEventToHoverEvent =
+ AccessibilityUtils.getInstance().isTouchExplorationEnabled();
mIsDispatchingHoverEventToMoreSuggestions = false;
return true;
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
// Decided to be in the modal input mode.
- mMoreSuggestionsView.adjustVerticalCorrectionForModalMode();
+ mMoreSuggestionsView.setModalMode();
}
return false;
}
@@ -429,6 +440,11 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
public boolean onTouchEvent(final MotionEvent me) {
+ if (!mMoreSuggestionsView.isShowingInParent()) {
+ // Ignore any touch event while more suggestions panel hasn't been shown.
+ // Detecting sliding up is done at {@link #onInterceptTouchEvent}.
+ return true;
+ }
// In the sliding input mode. {@link MotionEvent} should be forwarded to
// {@link MoreSuggestionsView}.
final int index = me.getActionIndex();
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 936219332..02f1c5f00 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -213,12 +213,22 @@ public final class CapsModeUtils {
char c = cs.charAt(--j);
// We found the next interesting chunk of text ; next we need to determine if it's the
- // end of a sentence. If we have a question mark or an exclamation mark, it's the end of
- // a sentence. If it's neither, the only remaining case is the period so we get the opposite
- // case out of the way.
- if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) {
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ // end of a sentence. If we have a sentence terminator (typically a question mark or an
+ // exclamation mark), then it's the end of a sentence; however, we treat the abbreviation
+ // marker specially because usually is the same char as the sentence separator (the
+ // period in most languages) and in this case we need to apply a heuristic to determine
+ // in which of these senses it's used.
+ if (spacingAndPunctuations.isSentenceTerminator(c)
+ && !spacingAndPunctuations.isAbbreviationMarker(c)) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+ | TextUtils.CAP_MODE_SENTENCES) & reqModes;
}
+ // If we reach here, we know we have whitespace before the cursor and before that there
+ // is something that either does not terminate the sentence, or a symbol preceded by the
+ // start of the text, or it's the sentence separator AND it happens to be the same code
+ // point as the abbreviation marker.
+ // If it's a symbol or something that does not terminate the sentence, then we need to
+ // return caps for MODE_CHARACTERS and MODE_WORDS, but not for MODE_SENTENCES.
if (!spacingAndPunctuations.isSentenceSeparator(c) || j <= 0) {
return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
index 34f59e8bc..7e8e55990 100644
--- a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
@@ -67,7 +67,7 @@ public class CombinedFormatUtils {
builder.append("," + BLACKLISTED_TAG + "=true");
}
builder.append("\n");
- if (wordProperty.mShortcutTargets != null) {
+ if (wordProperty.mHasShortcuts) {
for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
builder.append(" " + SHORTCUT_TAG + "=" + shortcutTarget.mWord);
builder.append(",");
@@ -75,8 +75,9 @@ public class CombinedFormatUtils {
builder.append("\n");
}
}
- if (wordProperty.mBigrams != null) {
- for (final WeightedString bigram : wordProperty.mBigrams) {
+ if (wordProperty.mHasNgrams) {
+ // TODO: Support ngram.
+ for (final WeightedString bigram : wordProperty.getBigrams()) {
builder.append(" " + BIGRAM_TAG + "=" + bigram.mWord);
builder.append(",");
builder.append(formatProbabilityInfo(bigram.mProbabilityInfo));
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 197908032..249478785 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -23,6 +23,7 @@ import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.AssetFileAddress;
import com.android.inputmethod.latin.BinaryDictionaryGetter;
import com.android.inputmethod.latin.Constants;
@@ -382,6 +383,7 @@ public class DictionaryInfoUtils {
return dictList;
}
+ @UsedForTesting
public static boolean looksValidForDictionaryInsertion(final CharSequence text,
final SpacingAndPunctuations spacingAndPunctuations) {
if (TextUtils.isEmpty(text)) return false;
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index 787e4a59d..355d00dac 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -21,33 +21,69 @@ import java.util.Locale;
import android.view.inputmethod.InputMethodSubtype;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.NgramContext;
public interface DistracterFilter {
/**
* Determine whether a word is a distracter to words in dictionaries.
*
- * @param prevWordsInfo the information of previous words.
+ * @param ngramContext the n-gram context
* @param testedWord the word that will be tested to see whether it is a distracter to words
* in dictionaries.
* @param locale the locale of word.
* @return true if testedWord is a distracter, otherwise false.
*/
- public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
+ public boolean isDistracterToWordsInDictionaries(final NgramContext ngramContext,
final String testedWord, final Locale locale);
+ @UsedForTesting
+ public int getWordHandlingType(final NgramContext ngramContext, final String testedWord,
+ final Locale locale);
+
public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes);
public void close();
+ public static final class HandlingType {
+ private final static int REQUIRE_NO_SPECIAL_HANDLINGS = 0x0;
+ private final static int SHOULD_BE_LOWER_CASED = 0x1;
+ private final static int SHOULD_BE_HANDLED_AS_OOV = 0x2;
+
+ public static int getHandlingType(final boolean shouldBeLowerCased, final boolean isOov) {
+ int wordHandlingType = HandlingType.REQUIRE_NO_SPECIAL_HANDLINGS;
+ if (shouldBeLowerCased) {
+ wordHandlingType |= HandlingType.SHOULD_BE_LOWER_CASED;
+ }
+ if (isOov) {
+ wordHandlingType |= HandlingType.SHOULD_BE_HANDLED_AS_OOV;
+ }
+ return wordHandlingType;
+ }
+
+ public static boolean shouldBeLowerCased(final int handlingType) {
+ return (handlingType & SHOULD_BE_LOWER_CASED) != 0;
+ }
+
+ public static boolean shouldBeHandledAsOov(final int handlingType) {
+ return (handlingType & SHOULD_BE_HANDLED_AS_OOV) != 0;
+ }
+ };
+
public static final DistracterFilter EMPTY_DISTRACTER_FILTER = new DistracterFilter() {
@Override
- public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
+ public boolean isDistracterToWordsInDictionaries(NgramContext ngramContext,
String testedWord, Locale locale) {
return false;
}
@Override
+ public int getWordHandlingType(final NgramContext ngramContext,
+ final String testedWord, final Locale locale) {
+ return HandlingType.REQUIRE_NO_SPECIAL_HANDLINGS;
+ }
+
+ @Override
public void close() {
}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
index 27973287d..8f0f9bb44 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
@@ -20,13 +20,14 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ConcurrentHashMap;
import android.content.Context;
import android.content.res.Resources;
import android.text.InputType;
import android.util.Log;
import android.util.LruCache;
+import android.util.Pair;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -34,7 +35,9 @@ import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.DictionaryFacilitatorLruCache;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
@@ -48,21 +51,22 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
DistracterFilterCheckingExactMatchesAndSuggestions.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final long TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS = 120;
- private static final int MAX_DISTRACTERS_CACHE_SIZE = 512;
+ private static final int MAX_DICTIONARY_FACILITATOR_CACHE_SIZE = 3;
+ private static final int MAX_DISTRACTERS_CACHE_SIZE = 1024;
private final Context mContext;
- private final Map<Locale, InputMethodSubtype> mLocaleToSubtypeMap;
- private final Map<Locale, Keyboard> mLocaleToKeyboardMap;
- private final DictionaryFacilitator mDictionaryFacilitator;
- private final LruCache<String, Boolean> mDistractersCache;
- private Keyboard mKeyboard;
+ private final ConcurrentHashMap<Locale, InputMethodSubtype> mLocaleToSubtypeCache;
+ private final ConcurrentHashMap<Locale, Keyboard> mLocaleToKeyboardCache;
+ private final DictionaryFacilitatorLruCache mDictionaryFacilitatorLruCache;
+ // The key is a pair of a locale and a word. The value indicates the word is a distracter to
+ // words of the locale.
+ private final LruCache<Pair<Locale, String>, Boolean> mDistractersCache;
private final Object mLock = new Object();
// If the score of the top suggestion exceeds this value, the tested word (e.g.,
- // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distractor to
+ // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distracter to
// words in dictionary. The greater the threshold is, the less likely the tested word would
- // become a distractor, which means the tested word will be more likely to be added to
+ // become a distracter, which means the tested word will be more likely to be added to
// the dictionary.
private static final float DISTRACTER_WORD_SCORE_THRESHOLD = 0.4f;
@@ -73,16 +77,19 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
*/
public DistracterFilterCheckingExactMatchesAndSuggestions(final Context context) {
mContext = context;
- mLocaleToSubtypeMap = new HashMap<>();
- mLocaleToKeyboardMap = new HashMap<>();
- mDictionaryFacilitator = new DictionaryFacilitator();
+ mLocaleToSubtypeCache = new ConcurrentHashMap<>();
+ mLocaleToKeyboardCache = new ConcurrentHashMap<>();
+ mDictionaryFacilitatorLruCache = new DictionaryFacilitatorLruCache(context,
+ MAX_DICTIONARY_FACILITATOR_CACHE_SIZE, "" /* dictionaryNamePrefix */);
mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
- mKeyboard = null;
}
@Override
public void close() {
- mDictionaryFacilitator.closeDictionaries();
+ mLocaleToSubtypeCache.clear();
+ mLocaleToKeyboardCache.clear();
+ mDictionaryFacilitatorLruCache.evictAll();
+ // Don't clear mDistractersCache.
}
@Override
@@ -99,29 +106,36 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
newLocaleToSubtypeMap.put(locale, subtype);
}
}
- if (mLocaleToSubtypeMap.equals(newLocaleToSubtypeMap)) {
+ if (mLocaleToSubtypeCache.equals(newLocaleToSubtypeMap)) {
// Enabled subtypes have not been changed.
return;
}
- synchronized (mLock) {
- mLocaleToSubtypeMap.clear();
- mLocaleToSubtypeMap.putAll(newLocaleToSubtypeMap);
- mLocaleToKeyboardMap.clear();
+ // Update subtype and keyboard map for locales that are in the current mapping.
+ for (final Locale locale: mLocaleToSubtypeCache.keySet()) {
+ if (newLocaleToSubtypeMap.containsKey(locale)) {
+ final InputMethodSubtype newSubtype = newLocaleToSubtypeMap.remove(locale);
+ if (newSubtype.equals(newLocaleToSubtypeMap.get(locale))) {
+ // Mapping has not been changed.
+ continue;
+ }
+ mLocaleToSubtypeCache.replace(locale, newSubtype);
+ } else {
+ mLocaleToSubtypeCache.remove(locale);
+ }
+ mLocaleToKeyboardCache.remove(locale);
}
+ // Add locales that are not in the current mapping.
+ mLocaleToSubtypeCache.putAll(newLocaleToSubtypeMap);
}
- private void loadKeyboardForLocale(final Locale newLocale) {
- final Keyboard cachedKeyboard = mLocaleToKeyboardMap.get(newLocale);
+ private Keyboard getKeyboardForLocale(final Locale locale) {
+ final Keyboard cachedKeyboard = mLocaleToKeyboardCache.get(locale);
if (cachedKeyboard != null) {
- mKeyboard = cachedKeyboard;
- return;
- }
- final InputMethodSubtype subtype;
- synchronized (mLock) {
- subtype = mLocaleToSubtypeMap.get(newLocale);
+ return cachedKeyboard;
}
+ final InputMethodSubtype subtype = mLocaleToSubtypeCache.get(locale);
if (subtype == null) {
- return;
+ return null;
}
final EditorInfo editorInfo = new EditorInfo();
editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
@@ -131,59 +145,41 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
- builder.setSubtype(subtype);
+ builder.setSubtype(new RichInputMethodSubtype(subtype));
builder.setIsSpellChecker(false /* isSpellChecker */);
final KeyboardLayoutSet layoutSet = builder.build();
- mKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
- }
-
- private void loadDictionariesForLocale(final Locale newlocale) throws InterruptedException {
- mDictionaryFacilitator.resetDictionaries(mContext, newlocale,
- false /* useContactsDict */, false /* usePersonalizedDicts */,
- false /* forceReloadMainDictionary */, null /* listener */);
- mDictionaryFacilitator.waitForLoadingMainDictionary(
- TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS, TimeUnit.SECONDS);
+ final Keyboard newKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
+ mLocaleToKeyboardCache.put(locale, newKeyboard);
+ return newKeyboard;
}
/**
* Determine whether a word is a distracter to words in dictionaries.
*
- * @param prevWordsInfo the information of previous words. Not used for now.
+ * @param ngramContext the n-gram context. Not used for now.
* @param testedWord the word that will be tested to see whether it is a distracter to words
* in dictionaries.
* @param locale the locale of word.
* @return true if testedWord is a distracter, otherwise false.
*/
@Override
- public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
+ public boolean isDistracterToWordsInDictionaries(final NgramContext ngramContext,
final String testedWord, final Locale locale) {
if (locale == null) {
return false;
}
- if (!locale.equals(mDictionaryFacilitator.getLocale())) {
- synchronized (mLock) {
- if (!mLocaleToSubtypeMap.containsKey(locale)) {
- Log.e(TAG, "Locale " + locale + " is not enabled.");
- // TODO: Investigate what we should do for disabled locales.
- return false;
- }
- loadKeyboardForLocale(locale);
- // Reset dictionaries for the locale.
- try {
- mDistractersCache.evictAll();
- loadDictionariesForLocale(locale);
- } catch (final InterruptedException e) {
- Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter",
- e);
- return false;
- }
- }
+ if (!mLocaleToSubtypeCache.containsKey(locale)) {
+ Log.e(TAG, "Locale " + locale + " is not enabled.");
+ // TODO: Investigate what we should do for disabled locales.
+ return false;
}
-
+ final DictionaryFacilitator dictionaryFacilitator =
+ mDictionaryFacilitatorLruCache.get(locale);
if (DEBUG) {
Log.d(TAG, "testedWord: " + testedWord);
}
- final Boolean isCachedDistracter = mDistractersCache.get(testedWord);
+ final Pair<Locale, String> cacheKey = new Pair<>(locale, testedWord);
+ final Boolean isCachedDistracter = mDistractersCache.get(cacheKey);
if (isCachedDistracter != null && isCachedDistracter) {
if (DEBUG) {
Log.d(TAG, "isDistracter: true (cache hit)");
@@ -192,37 +188,38 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
}
final boolean isDistracterCheckedByGetMaxFreqencyOfExactMatches =
- checkDistracterUsingMaxFreqencyOfExactMatches(testedWord);
+ checkDistracterUsingMaxFreqencyOfExactMatches(dictionaryFacilitator, testedWord);
if (isDistracterCheckedByGetMaxFreqencyOfExactMatches) {
- // Add the word to the cache.
- mDistractersCache.put(testedWord, Boolean.TRUE);
+ // Add the pair of locale and word to the cache.
+ mDistractersCache.put(cacheKey, Boolean.TRUE);
return true;
}
- final boolean isValidWord = mDictionaryFacilitator.isValidWord(testedWord,
- false /* ignoreCase */);
- if (isValidWord) {
- // Valid word is not a distractor.
+ final boolean Word = dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */);
+ if (Word) {
+ // Valid word is not a distracter.
if (DEBUG) {
Log.d(TAG, "isDistracter: false (valid word)");
}
return false;
}
+ final Keyboard keyboard = getKeyboardForLocale(locale);
final boolean isDistracterCheckedByGetSuggestion =
- checkDistracterUsingGetSuggestions(testedWord);
+ checkDistracterUsingGetSuggestions(dictionaryFacilitator, keyboard, testedWord);
if (isDistracterCheckedByGetSuggestion) {
- // Add the word to the cache.
- mDistractersCache.put(testedWord, Boolean.TRUE);
+ // Add the pair of locale and word to the cache.
+ mDistractersCache.put(cacheKey, Boolean.TRUE);
return true;
}
return false;
}
- private boolean checkDistracterUsingMaxFreqencyOfExactMatches(final String testedWord) {
+ private static boolean checkDistracterUsingMaxFreqencyOfExactMatches(
+ final DictionaryFacilitator dictionaryFacilitator, final String testedWord) {
// The tested word is a distracter when there is a word that is exact matched to the tested
// word and its probability is higher than the tested word's probability.
- final int perfectMatchFreq = mDictionaryFacilitator.getFrequency(testedWord);
- final int exactMatchFreq = mDictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord);
+ final int perfectMatchFreq = dictionaryFacilitator.getFrequency(testedWord);
+ final int exactMatchFreq = dictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord);
final boolean isDistracter = perfectMatchFreq < exactMatchFreq;
if (DEBUG) {
Log.d(TAG, "perfectMatchFreq: " + perfectMatchFreq);
@@ -232,8 +229,10 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
return isDistracter;
}
- private boolean checkDistracterUsingGetSuggestions(final String testedWord) {
- if (mKeyboard == null) {
+ private boolean checkDistracterUsingGetSuggestions(
+ final DictionaryFacilitator dictionaryFacilitator, final Keyboard keyboard,
+ final String testedWord) {
+ if (keyboard == null) {
return false;
}
final SettingsValuesForSuggestion settingsValuesForSuggestion =
@@ -246,24 +245,24 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
testedWord;
final WordComposer composer = new WordComposer();
final int[] codePoints = StringUtils.toCodePointArray(testedWord);
-
+ final int[] coordinates = keyboard.getCoordinates(codePoints);
+ composer.setComposingWord(codePoints, coordinates);
+ final SuggestionResults suggestionResults;
synchronized (mLock) {
- final int[] coordinates = mKeyboard.getCoordinates(codePoints);
- composer.setComposingWord(codePoints, coordinates);
- final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- composer, PrevWordsInfo.EMPTY_PREV_WORDS_INFO, mKeyboard.getProximityInfo(),
+ suggestionResults = dictionaryFacilitator.getSuggestionResults(
+ composer, NgramContext.EMPTY_PREV_WORDS_INFO, keyboard.getProximityInfo(),
settingsValuesForSuggestion, 0 /* sessionId */);
- if (suggestionResults.isEmpty()) {
- return false;
- }
- final SuggestedWordInfo firstSuggestion = suggestionResults.first();
- final boolean isDistractor = suggestionExceedsDistracterThreshold(
- firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
- if (DEBUG) {
- Log.d(TAG, "isDistracter: " + isDistractor);
- }
- return isDistractor;
}
+ if (suggestionResults.isEmpty()) {
+ return false;
+ }
+ final SuggestedWordInfo firstSuggestion = suggestionResults.first();
+ final boolean isDistracter = suggestionExceedsDistracterThreshold(
+ firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
+ if (DEBUG) {
+ Log.d(TAG, "isDistracter: " + isDistracter);
+ }
+ return isDistracter;
}
private static boolean suggestionExceedsDistracterThreshold(final SuggestedWordInfo suggestion,
@@ -283,4 +282,41 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
}
return false;
}
+
+ private boolean shouldBeLowerCased(final NgramContext ngramContext, final String testedWord,
+ final Locale locale) {
+ final DictionaryFacilitator dictionaryFacilitator =
+ mDictionaryFacilitatorLruCache.get(locale);
+ if (dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */)) {
+ return false;
+ }
+ final String lowerCaseTargetWord = testedWord.toLowerCase(locale);
+ if (testedWord.equals(lowerCaseTargetWord)) {
+ return false;
+ }
+ if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
+ return true;
+ }
+ if (StringUtils.getCapitalizationType(testedWord) == StringUtils.CAPITALIZE_FIRST
+ && !ngramContext.isValid()) {
+ // TODO: Check beginning-of-sentence.
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int getWordHandlingType(final NgramContext ngramContext, final String testedWord,
+ final Locale locale) {
+ // TODO: Use this method for user history dictionary.
+ if (testedWord == null|| locale == null) {
+ return HandlingType.getHandlingType(false /* shouldBeLowerCased */, false /* isOov */);
+ }
+ final boolean shouldBeLowerCased = shouldBeLowerCased(ngramContext, testedWord, locale);
+ final String caseModifiedWord =
+ shouldBeLowerCased ? testedWord.toLowerCase(locale) : testedWord;
+ final boolean isOov = !mDictionaryFacilitatorLruCache.get(locale).isValidWord(
+ caseModifiedWord, false /* ignoreCase */);
+ return HandlingType.getHandlingType(shouldBeLowerCased, isOov);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
index 4ad4ba784..df6e97028 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
@@ -22,7 +22,7 @@ import java.util.Locale;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.NgramContext;
public class DistracterFilterCheckingIsInDictionary implements DistracterFilter {
private final DistracterFilter mDistracterFilter;
@@ -35,7 +35,7 @@ public class DistracterFilterCheckingIsInDictionary implements DistracterFilter
}
@Override
- public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
+ public boolean isDistracterToWordsInDictionaries(NgramContext ngramContext,
String testedWord, Locale locale) {
if (mDictionary.isInDictionary(testedWord)) {
// This filter treats entries that are already in the dictionary as non-distracters
@@ -43,11 +43,17 @@ public class DistracterFilterCheckingIsInDictionary implements DistracterFilter
return false;
} else {
return mDistracterFilter.isDistracterToWordsInDictionaries(
- prevWordsInfo, testedWord, locale);
+ ngramContext, testedWord, locale);
}
}
@Override
+ public int getWordHandlingType(final NgramContext ngramContext, final String testedWord,
+ final Locale locale) {
+ return mDistracterFilter.getWordHandlingType(ngramContext, testedWord, locale);
+ }
+
+ @Override
public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
// Do nothing.
}
diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
index c2167a76b..ae2de44c7 100644
--- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
@@ -18,6 +18,7 @@ package com.android.inputmethod.latin.utils;
import com.android.inputmethod.dictionarypack.DictionarySettingsFragment;
import com.android.inputmethod.latin.about.AboutPreferences;
+import com.android.inputmethod.latin.settings.AccountsSettingsFragment;
import com.android.inputmethod.latin.settings.AdvancedSettingsFragment;
import com.android.inputmethod.latin.settings.AppearanceSettingsFragment;
import com.android.inputmethod.latin.settings.CorrectionSettingsFragment;
@@ -42,6 +43,7 @@ public class FragmentUtils {
sLatinImeFragments.add(DictionarySettingsFragment.class.getName());
sLatinImeFragments.add(AboutPreferences.class.getName());
sLatinImeFragments.add(PreferencesSettingsFragment.class.getName());
+ sLatinImeFragments.add(AccountsSettingsFragment.class.getName());
sLatinImeFragments.add(AppearanceSettingsFragment.class.getName());
sLatinImeFragments.add(ThemeSettingsFragment.class.getName());
sLatinImeFragments.add(MultiLingualSettingsFragment.class.getName());
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
index ea406fa75..142548b25 100644
--- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -53,7 +53,8 @@ public final class ImportantNoticeUtils {
// This utility class is not publicly instantiable.
}
- private static boolean isInSystemSetupWizard(final Context context) {
+ @UsedForTesting
+ static boolean isInSystemSetupWizard(final Context context) {
try {
final int userSetupComplete = Settings.Secure.getInt(
context.getContentResolver(), Settings_Secure_USER_SETUP_COMPLETE);
@@ -84,7 +85,8 @@ public final class ImportantNoticeUtils {
return getLastImportantNoticeVersion(context) + 1;
}
- private static boolean hasNewImportantNotice(final Context context) {
+ @UsedForTesting
+ static boolean hasNewImportantNotice(final Context context) {
final int lastVersion = getLastImportantNoticeVersion(context);
return getCurrentImportantNoticeVersion(context) > lastVersion;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
index fbce3f2fd..73aefb821 100644
--- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -18,10 +18,12 @@ package com.android.inputmethod.latin.utils;
import android.util.Log;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.DistracterFilter.HandlingType;
import java.util.ArrayList;
import java.util.List;
@@ -57,12 +59,14 @@ public final class LanguageModelParam {
public final int mTimestamp;
// Constructor for unigram. TODO: support shortcuts
+ @UsedForTesting
public LanguageModelParam(final CharSequence word, final int unigramProbability,
final int timestamp) {
this(null /* word0 */, word, unigramProbability, Dictionary.NOT_A_PROBABILITY, timestamp);
}
// Constructor for unigram and bigram.
+ @UsedForTesting
public LanguageModelParam(final CharSequence word0, final CharSequence word1,
final int unigramProbability, final int bigramProbability,
final int timestamp) {
@@ -81,12 +85,11 @@ public final class LanguageModelParam {
// Process a list of words and return a list of {@link LanguageModelParam} objects.
public static ArrayList<LanguageModelParam> createLanguageModelParamsFrom(
final List<String> tokens, final int timestamp,
- final DictionaryFacilitator dictionaryFacilitator,
- final SpacingAndPunctuations spacingAndPunctuations,
+ final SpacingAndPunctuations spacingAndPunctuations, final Locale locale,
final DistracterFilter distracterFilter) {
final ArrayList<LanguageModelParam> languageModelParams = new ArrayList<>();
final int N = tokens.size();
- PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ NgramContext ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO;
for (int i = 0; i < N; ++i) {
final String tempWord = tokens.get(i);
if (StringUtils.isEmptyStringOrWhiteSpaces(tempWord)) {
@@ -103,7 +106,7 @@ public final class LanguageModelParam {
+ tempWord + "\"");
}
// Sentence terminator found. Split.
- prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO;
continue;
}
if (DEBUG_TOKEN) {
@@ -111,64 +114,41 @@ public final class LanguageModelParam {
}
final LanguageModelParam languageModelParam =
detectWhetherVaildWordOrNotAndGetLanguageModelParam(
- prevWordsInfo, tempWord, timestamp, dictionaryFacilitator,
- distracterFilter);
+ ngramContext, tempWord, timestamp, locale, distracterFilter);
if (languageModelParam == null) {
continue;
}
languageModelParams.add(languageModelParam);
- prevWordsInfo = prevWordsInfo.getNextPrevWordsInfo(
- new PrevWordsInfo.WordInfo(tempWord));
+ ngramContext = ngramContext.getNextNgramContext(
+ new NgramContext.WordInfo(tempWord));
}
return languageModelParams;
}
private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam(
- final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
- final DictionaryFacilitator dictionaryFacilitator,
- final DistracterFilter distracterFilter) {
- final Locale locale = dictionaryFacilitator.getLocale();
+ final NgramContext ngramContext, final String targetWord, final int timestamp,
+ final Locale locale, final DistracterFilter distracterFilter) {
if (locale == null) {
return null;
}
- if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) {
- return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
- true /* isValidWord */, locale, distracterFilter);
- }
-
- final String lowerCaseTargetWord = targetWord.toLowerCase(locale);
- if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
- // Add the lower-cased word.
- return createAndGetLanguageModelParamOfWord(prevWordsInfo, lowerCaseTargetWord,
- timestamp, true /* isValidWord */, locale, distracterFilter);
+ final int wordHandlingType = distracterFilter.getWordHandlingType(ngramContext,
+ targetWord, locale);
+ final String word = HandlingType.shouldBeLowerCased(wordHandlingType) ?
+ targetWord.toLowerCase(locale) : targetWord;
+ if (distracterFilter.isDistracterToWordsInDictionaries(ngramContext, targetWord, locale)) {
+ // The word is a distracter.
+ return null;
}
-
- // Treat the word as an OOV word.
- return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
- false /* isValidWord */, locale, distracterFilter);
+ return createAndGetLanguageModelParamOfWord(ngramContext, word, timestamp,
+ !HandlingType.shouldBeHandledAsOov(wordHandlingType));
}
private static LanguageModelParam createAndGetLanguageModelParamOfWord(
- final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
- final boolean isValidWord, final Locale locale,
- final DistracterFilter distracterFilter) {
- final String word;
- if (StringUtils.getCapitalizationType(targetWord) == StringUtils.CAPITALIZE_FIRST
- && !prevWordsInfo.isValid() && !isValidWord) {
- word = targetWord.toLowerCase(locale);
- } else {
- word = targetWord;
- }
- // Check whether the word is a distracter to words in the dictionaries.
- if (distracterFilter.isDistracterToWordsInDictionaries(prevWordsInfo, word, locale)) {
- if (DEBUG) {
- Log.d(TAG, "The word (" + word + ") is a distracter. Skip this word.");
- }
- return null;
- }
+ final NgramContext ngramContext, final String word, final int timestamp,
+ final boolean isValidWord) {
final int unigramProbability = isValidWord ?
UNIGRAM_PROBABILITY_FOR_VALID_WORD : UNIGRAM_PROBABILITY_FOR_OOV_WORD;
- if (!prevWordsInfo.isValid()) {
+ if (!ngramContext.isValid()) {
if (DEBUG) {
Log.d(TAG, "--- add unigram: current("
+ (isValidWord ? "Valid" : "OOV") + ") = " + word);
@@ -176,12 +156,12 @@ public final class LanguageModelParam {
return new LanguageModelParam(word, unigramProbability, timestamp);
}
if (DEBUG) {
- Log.d(TAG, "--- add bigram: prev = " + prevWordsInfo + ", current("
+ Log.d(TAG, "--- add bigram: prev = " + ngramContext + ", current("
+ (isValidWord ? "Valid" : "OOV") + ") = " + word);
}
final int bigramProbability = isValidWord ?
BIGRAM_PROBABILITY_FOR_VALID_WORD : BIGRAM_PROBABILITY_FOR_OOV_WORD;
- return new LanguageModelParam(prevWordsInfo.mPrevWordsInfo[0].mWord, word,
+ return new LanguageModelParam(ngramContext.getNthPrevWord(1 /* n */), word,
unigramProbability, bigramProbability, timestamp);
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java
index 3cd63612c..34eeac2c2 100644
--- a/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java
@@ -16,15 +16,16 @@
package com.android.inputmethod.latin.utils;
+import java.util.Arrays;
import java.util.regex.Pattern;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.PrevWordsInfo;
-import com.android.inputmethod.latin.PrevWordsInfo.WordInfo;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.NgramContext.WordInfo;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
-public final class PrevWordsInfoUtils {
- private PrevWordsInfoUtils() {
+public final class NgramContextUtils {
+ private NgramContextUtils() {
// Intentional empty constructor for utility class.
}
@@ -43,7 +44,7 @@ public final class PrevWordsInfoUtils {
// (n = 2) "abc def|" -> beginning-of-sentence, abc
// (n = 2) "abc def |" -> beginning-of-sentence, abc
// (n = 2) "abc 'def|" -> empty. The context is different from "abc def", but we cannot
- // represent this situation using PrevWordsInfo. See TODO in the method.
+ // represent this situation using NgramContext. See TODO in the method.
// TODO: The next example's result should be "abc, def". This have to be fixed before we
// retrieve the prior context of Beginning-of-Sentence.
// (n = 2) "abc def. |" -> beginning-of-sentence, abc
@@ -51,11 +52,12 @@ public final class PrevWordsInfoUtils {
// (n = 2) "abc|" -> beginning-of-sentence
// (n = 2) "abc |" -> beginning-of-sentence
// (n = 2) "abc. def|" -> beginning-of-sentence
- public static PrevWordsInfo getPrevWordsInfoFromNthPreviousWord(final CharSequence prev,
+ public static NgramContext getNgramContextFromNthPreviousWord(final CharSequence prev,
final SpacingAndPunctuations spacingAndPunctuations, final int n) {
- if (prev == null) return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ if (prev == null) return NgramContext.EMPTY_PREV_WORDS_INFO;
final String[] w = SPACE_REGEX.split(prev);
final WordInfo[] prevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ Arrays.fill(prevWordsInfo, WordInfo.EMPTY_WORD_INFO);
for (int i = 0; i < prevWordsInfo.length; i++) {
final int focusedWordIndex = w.length - n - i;
// Referring to the word after the focused word.
@@ -66,7 +68,6 @@ public final class PrevWordsInfoUtils {
if (spacingAndPunctuations.isWordConnector(firstChar)) {
// The word following the focused word is starting with a word connector.
// TODO: Return meaningful context for this case.
- prevWordsInfo[i] = WordInfo.EMPTY_WORD_INFO;
break;
}
}
@@ -93,11 +94,10 @@ public final class PrevWordsInfoUtils {
// TODO: Return meaningful context for this case.
if (spacingAndPunctuations.isWordSeparator(lastChar)
|| spacingAndPunctuations.isWordConnector(lastChar)) {
- prevWordsInfo[i] = WordInfo.EMPTY_WORD_INFO;
break;
}
prevWordsInfo[i] = new WordInfo(focusedWord);
}
- return new PrevWordsInfo(prevWordsInfo);
+ return new NgramContext(prevWordsInfo);
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
deleted file mode 100644
index 1ca895fdb..000000000
--- a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.view.inputmethod.InputMethodSubtype;
-
-public final class SpacebarLanguageUtils {
- private SpacebarLanguageUtils() {
- // Intentional empty constructor for utility class.
- }
-
- // InputMethodSubtype's display name for spacebar text in its locale.
- // isAdditionalSubtype (T=true, F=false)
- // locale layout | Middle Full
- // ------ ------- - --------- ----------------------
- // en_US qwerty F English English (US) exception
- // en_GB qwerty F English English (UK) exception
- // es_US spanish F Español Español (EE.UU.) exception
- // fr azerty F Français Français
- // fr_CA qwerty F Français Français (Canada)
- // fr_CH swiss F Français Français (Suisse)
- // de qwertz F Deutsch Deutsch
- // de_CH swiss T Deutsch Deutsch (Schweiz)
- // zz qwerty F QWERTY QWERTY
- // fr qwertz T Français Français
- // de qwerty T Deutsch Deutsch
- // en_US azerty T English English (US)
- // zz azerty T AZERTY AZERTY
- // Get InputMethodSubtype's full display name in its locale.
- public static String getFullDisplayName(final InputMethodSubtype subtype) {
- if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
- return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
- }
- return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(subtype.getLocale());
- }
-
- // Get InputMethodSubtype's middle display name in its locale.
- public static String getMiddleDisplayName(final InputMethodSubtype subtype) {
- if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
- return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
- }
- return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(subtype.getLocale());
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index 1781924ac..bbcef990d 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -49,9 +49,9 @@ public final class StringUtils {
// This utility class is not publicly instantiable.
}
- public static int codePointCount(final String text) {
+ public static int codePointCount(final CharSequence text) {
if (TextUtils.isEmpty(text)) return 0;
- return text.codePointCount(0, text.length());
+ return Character.codePointCount(text, 0, text.length());
}
public static String newSingleCodePointString(int codePoint) {
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 351d01400..5a7f7662c 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -27,11 +27,17 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Locale;
+/**
+ * A helper class to deal with subtype locales.
+ */
+// TODO: consolidate this into RichInputMethodSubtype
public final class SubtypeLocaleUtils {
private static final String TAG = SubtypeLocaleUtils.class.getSimpleName();
@@ -52,6 +58,8 @@ public final class SubtypeLocaleUtils {
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = new HashMap<>();
// Keyboard layout to subtype name resource id map.
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>();
+ // Exceptional locale whose name should be displayed in Locale.ROOT.
+ static final HashSet<String> sExceptionalLocaleDisplayedInRootLocale = new HashSet<>();
// Exceptional locale to subtype name resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>();
// Exceptional locale to subtype name with layout resource id map.
@@ -106,6 +114,12 @@ public final class SubtypeLocaleUtils {
sKeyboardLayoutToNameIdsMap.put(key, noLanguageResId);
}
+ final String[] exceptionalLocaleInRootLocale = res.getStringArray(
+ R.array.subtype_locale_displayed_in_root_locale);
+ for (int i = 0; i < exceptionalLocaleInRootLocale.length; i++) {
+ sExceptionalLocaleDisplayedInRootLocale.add(exceptionalLocaleInRootLocale[i]);
+ }
+
final String[] exceptionalLocales = res.getStringArray(
R.array.subtype_locale_exception_keys);
for (int i = 0; i < exceptionalLocales.length; i++) {
@@ -153,10 +167,13 @@ public final class SubtypeLocaleUtils {
return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId;
}
- private static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) {
+ public static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) {
if (NO_LANGUAGE.equals(localeString)) {
return sResources.getConfiguration().locale;
}
+ if (sExceptionalLocaleDisplayedInRootLocale.contains(localeString)) {
+ return Locale.ROOT;
+ }
return LocaleUtils.constructLocaleFromString(localeString);
}
@@ -171,9 +188,15 @@ public final class SubtypeLocaleUtils {
}
public static String getSubtypeLanguageDisplayName(final String localeString) {
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
- return getSubtypeLocaleDisplayNameInternal(locale.getLanguage(), displayLocale);
+ final String languageString;
+ if (sExceptionalLocaleDisplayedInRootLocale.contains(localeString)) {
+ languageString = localeString;
+ } else {
+ final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+ languageString = locale.getLanguage();
+ }
+ return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale);
}
private static String getSubtypeLocaleDisplayNameInternal(final String localeString,
@@ -242,6 +265,7 @@ public final class SubtypeLocaleUtils {
private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype,
final Locale displayLocale) {
final String replacementString = getReplacementString(subtype, displayLocale);
+ // TODO: rework this for multi-lingual subtypes
final int nameResId = subtype.getNameResId();
final RunInLocale<String> getSubtypeName = new RunInLocale<String>() {
@Override
@@ -264,12 +288,14 @@ public final class SubtypeLocaleUtils {
getSubtypeName.runInLocale(sResources, displayLocale), displayLocale);
}
- public static boolean isNoLanguage(final InputMethodSubtype subtype) {
+ public static Locale getSubtypeLocale(final InputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
- return NO_LANGUAGE.equals(localeString);
+ return LocaleUtils.constructLocaleFromString(localeString);
}
- public static Locale getSubtypeLocale(final InputMethodSubtype subtype) {
+ // TODO: remove this. When RichInputMethodSubtype#getLocale is removed we can do away with this
+ // method at the same time.
+ public static Locale getSubtypeLocale(final RichInputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
return LocaleUtils.constructLocaleFromString(localeString);
}
@@ -283,6 +309,10 @@ public final class SubtypeLocaleUtils {
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
}
+ public static String getKeyboardLayoutSetName(final RichInputMethodSubtype subtype) {
+ return getKeyboardLayoutSetName(subtype.getRawSubtype());
+ }
+
public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) {
String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET);
if (keyboardLayoutSet == null) {
@@ -318,7 +348,7 @@ public final class SubtypeLocaleUtils {
return Arrays.binarySearch(SORTED_RTL_LANGUAGES, language) >= 0;
}
- public static boolean isRtlLanguage(final InputMethodSubtype subtype) {
+ public static boolean isRtlLanguage(final RichInputMethodSubtype subtype) {
return isRtlLanguage(getSubtypeLocale(subtype));
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
index 8cd49509f..4e2e396c2 100644
--- a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
+++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
@@ -22,7 +22,6 @@ import com.android.inputmethod.latin.define.ProductionFlags;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
-import java.util.Locale;
import java.util.TreeSet;
/**
@@ -30,22 +29,19 @@ import java.util.TreeSet;
* than its limit
*/
public final class SuggestionResults extends TreeSet<SuggestedWordInfo> {
- public final Locale mLocale;
public final ArrayList<SuggestedWordInfo> mRawSuggestions;
// TODO: Instead of a boolean , we may want to include the context of this suggestion results,
- // such as {@link PrevWordsInfo}.
+ // such as {@link NgramContext}.
public final boolean mIsBeginningOfSentence;
private final int mCapacity;
- public SuggestionResults(final Locale locale, final int capacity,
- final boolean isBeginningOfSentence) {
- this(locale, sSuggestedWordInfoComparator, capacity, isBeginningOfSentence);
+ public SuggestionResults(final int capacity, final boolean isBeginningOfSentence) {
+ this(sSuggestedWordInfoComparator, capacity, isBeginningOfSentence);
}
- private SuggestionResults(final Locale locale, final Comparator<SuggestedWordInfo> comparator,
+ private SuggestionResults(final Comparator<SuggestedWordInfo> comparator,
final int capacity, final boolean isBeginningOfSentence) {
super(comparator);
- mLocale = locale;
mCapacity = capacity;
if (ProductionFlags.INCLUDE_RAW_SUGGESTIONS) {
mRawSuggestions = new ArrayList<>();