aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java2
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java23
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java4
-rw-r--r--java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java6
-rw-r--r--java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java29
-rw-r--r--java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java14
-rw-r--r--java/src/com/android/inputmethod/compat/LooperCompatUtils.java42
-rw-r--r--java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java17
-rw-r--r--java/src/com/android/inputmethod/compat/ViewCompatUtils.java3
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ActionBatch.java21
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java11
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java6
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java113
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java109
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java99
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java17
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java32
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/WordListPreference.java5
-rw-r--r--java/src/com/android/inputmethod/event/Combiner.java25
-rw-r--r--java/src/com/android/inputmethod/event/CombinerChain.java117
-rw-r--r--java/src/com/android/inputmethod/event/DeadKeyCombiner.java27
-rw-r--r--java/src/com/android/inputmethod/event/Event.java227
-rw-r--r--java/src/com/android/inputmethod/event/EventInterpreter.java133
-rw-r--r--java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java24
-rw-r--r--java/src/com/android/inputmethod/event/InputTransaction.java84
-rw-r--r--java/src/com/android/inputmethod/event/SoftwareEventDecoder.java29
-rw-r--r--java/src/com/android/inputmethod/event/SoftwareKeyboardEventDecoder.java27
-rw-r--r--java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java509
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java175
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyDetector.java56
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java17
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java34
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java46
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java132
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardTheme.java81
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java63
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java796
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java12
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java15
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java40
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java22
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java804
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java35
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java181
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java115
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java22
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java77
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java (renamed from java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java)18
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java58
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java (renamed from java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java)36
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java (renamed from java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java)128
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureEnabler.java54
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java (renamed from java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java)9
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingParams.java58
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java (renamed from java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java)110
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionParams.java109
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java (renamed from java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java)184
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingParams.java79
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java (renamed from java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java)80
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java (renamed from java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java)63
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java9
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java285
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java103
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java491
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java9
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java11
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java68
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java90
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java56
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java1
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java97
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java3560
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java3782
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java68
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java232
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java24
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java66
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java (renamed from java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java)10
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java220
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/TypingTimeRecorder.java72
-rw-r--r--java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java80
-rw-r--r--java/src/com/android/inputmethod/latin/AssetFileAddress.java10
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java304
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java49
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java13
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java78
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java156
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java21
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java9
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryDumpBroadcastReceiver.java50
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java593
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java50
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryWriter.java109
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java709
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java894
-rw-r--r--java/src/com/android/inputmethod/latin/ImportantNoticeDialog.java78
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java297
-rw-r--r--java/src/com/android/inputmethod/latin/InputPointers.java67
-rw-r--r--java/src/com/android/inputmethod/latin/InputView.java245
-rw-r--r--java/src/com/android/inputmethod/latin/LastComposedWord.java19
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java2751
-rw-r--r--java/src/com/android/inputmethod/latin/PunctuationSuggestions.java116
-rw-r--r--java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java9
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java418
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java73
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java130
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java304
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java117
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java31
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java29
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java58
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java314
-rw-r--r--java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java19
-rw-r--r--java/src/com/android/inputmethod/latin/define/ProductionFlag.java9
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java2006
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java213
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/SpaceState.java54
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java207
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java623
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java956
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java599
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictDecoder.java231
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictEncoder.java38
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictUpdater.java54
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java89
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java492
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java251
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java916
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/MakedictLog.java47
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java32
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java91
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java52
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/SparseTable.java223
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java271
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java255
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java82
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java343
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java475
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java59
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/WeightedString.java62
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Word.java100
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/WordProperty.java164
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java193
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java4
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java190
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java55
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java (renamed from java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java)19
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java128
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java143
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java37
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java32
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java128
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java12
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettings.java168
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java152
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java128
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java307
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java116
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java10
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java20
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java17
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java9
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java4
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java2
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java27
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java2
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java241
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java259
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java (renamed from java/src/com/android/inputmethod/event/EventDecoderSpec.java)18
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java20
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java5
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java81
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java20
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java12
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java54
-rw-r--r--java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java137
-rw-r--r--java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java49
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java81
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java29
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CollectionUtils.java15
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java99
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java46
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CsvUtils.java7
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DialogUtils.java34
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java66
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java60
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FileUtils.java54
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java125
-rw-r--r--java/src/com/android/inputmethod/latin/utils/JsonUtils.java103
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java177
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java (renamed from java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java)20
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LocaleUtils.java47
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java1
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java18
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResourceUtils.java60
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RunInLocale.java22
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java58
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java22
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StatsUtils.java53
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java315
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java96
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SuggestionResults.java76
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java14
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TextRange.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java36
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java182
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java233
-rw-r--r--java/src/com/android/inputmethod/research/JsonUtils.java2
-rw-r--r--java/src/com/android/inputmethod/research/MainLogBuffer.java30
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java113
217 files changed, 17239 insertions, 19804 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 10fb9fef4..216a825e0 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -158,7 +158,7 @@ public final class AccessibilityUtils {
* @param typedWord the currently typed word
*/
public void setAutoCorrection(final SuggestedWords suggestedWords, final String typedWord) {
- if (suggestedWords != null && suggestedWords.mWillAutoCorrect) {
+ if (suggestedWords.mWillAutoCorrect) {
mAutoCorrectionWord = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
mTypedWord = typedWord;
} else {
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index 73896dfd3..0043b7844 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -29,10 +29,10 @@ import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.MainKeyboardView;
-import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.latin.R;
public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
@@ -82,7 +82,7 @@ public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateComp
private void initInternal(final InputMethodService inputMethod) {
mInputMethod = inputMethod;
mEdgeSlop = inputMethod.getResources().getDimensionPixelSize(
- R.dimen.accessibility_edge_slop);
+ R.dimen.config_accessibility_edge_slop);
}
/**
@@ -204,25 +204,14 @@ public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateComp
}
/**
- * Intercepts touch events before dispatch when touch exploration is turned on in ICS and
- * higher.
- *
- * @param event The motion event being dispatched.
- * @return {@code true} if the event is handled
- */
- public boolean dispatchTouchEvent(final MotionEvent event) {
- // To avoid accidental key presses during touch exploration, always drop
- // touch events generated by the user.
- return false;
- }
-
- /**
* Receives hover events when touch exploration is turned on in SDK versions ICS and higher.
*
* @param event The hover event.
+ * @param keyDetector The {@link KeyDetector} to determine on which key the <code>event</code>
+ * is hovering.
* @return {@code true} if the event is handled
*/
- public boolean dispatchHoverEvent(final MotionEvent event, final PointerTracker tracker) {
+ public boolean dispatchHoverEvent(final MotionEvent event, final KeyDetector keyDetector) {
if (mView == null) {
return false;
}
@@ -233,7 +222,7 @@ public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateComp
final Key key;
if (pointInView(x, y)) {
- key = tracker.getKeyOn(x, y);
+ key = keyDetector.detectHitKey(x, y);
} else {
key = null;
}
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 58624a2e6..2e6649bf2 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -58,9 +58,6 @@ public final class KeyCodeDescriptionMapper {
}
private void initInternal() {
- // Manual label substitutions for key labels with no string resource
- mKeyLabelMap.put(":-)", R.string.spoken_description_smiley);
-
// Special non-character codes defined in Keyboard
mKeyCodeMap.put(Constants.CODE_SPACE, R.string.spoken_description_space);
mKeyCodeMap.put(Constants.CODE_DELETE, R.string.spoken_description_delete);
@@ -75,6 +72,7 @@ public final class KeyCodeDescriptionMapper {
mKeyCodeMap.put(Constants.CODE_ACTION_NEXT, R.string.spoken_description_action_next);
mKeyCodeMap.put(Constants.CODE_ACTION_PREVIOUS,
R.string.spoken_description_action_previous);
+ mKeyCodeMap.put(Constants.CODE_EMOJI, R.string.spoken_description_emoji);
}
/**
diff --git a/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java b/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java
index 7e9e2e37b..6e43cc9a7 100644
--- a/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java
+++ b/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java
@@ -23,10 +23,10 @@ import android.os.Build.VERSION_CODES;
* A class to encapsulate work-arounds specific to particular apps.
*/
public class AppWorkaroundsUtils {
- private PackageInfo mPackageInfo; // May be null
- private boolean mIsBrokenByRecorrection = false;
+ private final PackageInfo mPackageInfo; // May be null
+ private final boolean mIsBrokenByRecorrection;
- public void setPackageInfo(final PackageInfo packageInfo) {
+ public AppWorkaroundsUtils(final PackageInfo packageInfo) {
mPackageInfo = packageInfo;
mIsBrokenByRecorrection = AppWorkaroundsHelper.evaluateIsBrokenByRecorrection(
packageInfo);
diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
index 14ee654f3..81df17127 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
@@ -17,11 +17,12 @@
package com.android.inputmethod.compat;
import android.inputmethodservice.InputMethodService;
+import com.android.inputmethod.latin.define.ProductionFlag;
import java.lang.reflect.Method;
public final class InputMethodServiceCompatUtils {
- // Note that InputMethodService.enableHardwareAcceleration() has been introduced
+ // Note that {@link InputMethodService#enableHardwareAcceleration} has been introduced
// in API level 17 (Build.VERSION_CODES.JELLY_BEAN_MR1).
private static final Method METHOD_enableHardwareAcceleration =
CompatUtils.getMethod(InputMethodService.class, "enableHardwareAcceleration");
@@ -34,4 +35,30 @@ public final class InputMethodServiceCompatUtils {
return (Boolean)CompatUtils.invoke(ims, false /* defaultValue */,
METHOD_enableHardwareAcceleration);
}
+
+ public static void setCursorAnchorMonitorMode(final InputMethodService ims, final int mode) {
+ if (ProductionFlag.USES_CURSOR_ANCHOR_MONITOR) {
+ ExperimentalAPIUtils.setCursorAnchorMonitorMode(ims, mode);
+ }
+ }
+
+ /*
+ * For unreleased APIs. ProGuard will strip this class entirely, unless used explicitly.
+ */
+ private static final class ExperimentalAPIUtils {
+ // Note that {@link InputMethodManager#setCursorAnchorMonitorMode} is not yet available as
+ // an official API as of API level 19 (Build.VERSION_CODES.KITKAT).
+ private static final Method METHOD_setCursorAnchorMonitorMode = CompatUtils.getMethod(
+ InputMethodService.class, "setCursorAnchorMonitorMode", int.class);
+
+ private ExperimentalAPIUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static void setCursorAnchorMonitorMode(final InputMethodService ims,
+ final int mode) {
+ CompatUtils.invoke(ims, null /* defaultValue */,
+ METHOD_setCursorAnchorMonitorMode, mode);
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
index b119d6c82..4ea7fb888 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
@@ -19,7 +19,10 @@ package com.android.inputmethod.compat;
import android.os.Build;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.latin.Constants;
+
import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
public final class InputMethodSubtypeCompatUtils {
private static final String TAG = InputMethodSubtypeCompatUtils.class.getSimpleName();
@@ -37,6 +40,12 @@ public final class InputMethodSubtypeCompatUtils {
}
}
}
+
+ // Note that {@link InputMethodSubtype#isAsciiCapable()} has been introduced in API level 19
+ // (Build.VERSION_CODE.KITKAT).
+ private static final Method METHOD_isAsciiCapable = CompatUtils.getMethod(
+ InputMethodSubtype.class, "isAsciiCapable");
+
private InputMethodSubtypeCompatUtils() {
// This utility class is not publicly instantiable.
}
@@ -53,4 +62,9 @@ public final class InputMethodSubtypeCompatUtils {
nameId, iconId, locale, mode, extraValue, isAuxiliary,
overridesImplicitlyEnabledSubtype, id);
}
+
+ public static boolean isAsciiCapable(final InputMethodSubtype subtype) {
+ return (Boolean)CompatUtils.invoke(subtype, false, METHOD_isAsciiCapable)
+ || subtype.containsExtraValueKey(Constants.Subtype.ExtraValue.ASCII_CAPABLE);
+ }
}
diff --git a/java/src/com/android/inputmethod/compat/LooperCompatUtils.java b/java/src/com/android/inputmethod/compat/LooperCompatUtils.java
new file mode 100644
index 000000000..d647dbbd3
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/LooperCompatUtils.java
@@ -0,0 +1,42 @@
+/*
+ * 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 android.os.Looper;
+
+import java.lang.reflect.Method;
+
+/**
+ * Helper to call Looper#quitSafely, which was introduced in API
+ * level 18 (Build.VERSION_CODES.JELLY_BEAN_MR2).
+ *
+ * In unit tests, we create lots of instances of LatinIME, which means we need to clean up
+ * some Loopers lest we leak file descriptors. In normal use on a device though, this is never
+ * necessary (although it does not hurt).
+ */
+public final class LooperCompatUtils {
+ private static final Method METHOD_quitSafely = CompatUtils.getMethod(
+ Looper.class, "quitSafely");
+
+ public static void quitSafely(final Looper looper) {
+ if (null != METHOD_quitSafely) {
+ CompatUtils.invoke(looper, null /* default return value */, METHOD_quitSafely);
+ } else {
+ looper.quit();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 55282c583..60f7e2def 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -25,6 +25,7 @@ import android.text.style.SuggestionSpan;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
import com.android.inputmethod.latin.utils.CollectionUtils;
@@ -66,30 +67,30 @@ public final class SuggestionSpanUtils {
}
public static CharSequence getTextWithSuggestionSpan(final Context context,
- final String pickedWord, final SuggestedWords suggestedWords,
- final boolean dictionaryAvailable) {
- if (!dictionaryAvailable || TextUtils.isEmpty(pickedWord) || suggestedWords.isEmpty()
- || suggestedWords.mIsPrediction || suggestedWords.mIsPunctuationSuggestions) {
+ final String pickedWord, final SuggestedWords suggestedWords) {
+ if (TextUtils.isEmpty(pickedWord) || suggestedWords.isEmpty()
+ || suggestedWords.mIsPrediction || suggestedWords.isPunctuationSuggestions()) {
return pickedWord;
}
- final Spannable spannable = new SpannableString(pickedWord);
final ArrayList<String> suggestionsList = CollectionUtils.newArrayList();
for (int i = 0; i < suggestedWords.size(); ++i) {
if (suggestionsList.size() >= SuggestionSpan.SUGGESTIONS_MAX_SIZE) {
break;
}
+ final SuggestedWordInfo info = suggestedWords.getInfo(i);
+ if (info.mKind == SuggestedWordInfo.KIND_PREDICTION) {
+ continue;
+ }
final String word = suggestedWords.getWord(i);
if (!TextUtils.equals(pickedWord, word)) {
suggestionsList.add(word.toString());
}
}
-
- // TODO: We should avoid adding suggestion span candidates that came from the bigram
- // prediction.
final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null /* locale */,
suggestionsList.toArray(new String[suggestionsList.size()]), 0 /* flags */,
SuggestionSpanPickedNotificationReceiver.class);
+ final Spannable spannable = new SpannableString(pickedWord);
spannable.setSpan(suggestionSpan, 0, pickedWord.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
}
diff --git a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
index a8fab8855..dec739d39 100644
--- a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
@@ -20,6 +20,9 @@ import android.view.View;
import java.lang.reflect.Method;
+// TODO: Use {@link android.support.v4.view.ViewCompat} instead of this utility class.
+// Currently {@link #getPaddingEnd(View)} and {@link #setPaddingRelative(View,int,int,int,int)}
+// are missing from android-support-v4 static library in KitKat SDK.
public final class ViewCompatUtils {
// Note that View.LAYOUT_DIRECTION_LTR and View.LAYOUT_DIRECTION_RTL have been introduced in
// API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1).
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index d5e638e7e..706bdea8e 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -117,16 +117,11 @@ public final class ActionBatch {
final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
mWordList.mId, mWordList.mVersion);
final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
- final DownloadManager manager =
- (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+ final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
if (MetadataDbHelper.STATUS_DOWNLOADING == status) {
// The word list is still downloading. Cancel the download and revert the
// word list status to "available".
- if (null != manager) {
- // DownloadManager is disabled (or not installed?). We can't cancel - there
- // is nothing we can do. We still need to mark the entry as 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) {
// Should never happen
@@ -136,9 +131,6 @@ public final class ActionBatch {
// Download it.
DebugLogUtils.l("Upgrade word list, downloading", mWordList.mRemoteFilename);
- // TODO: if DownloadManager is disabled or not installed, download by ourselves
- if (null == manager) return;
-
// This is an upgraded word list: we should download it.
// Adding a disambiguator to circumvent a bug in older versions of DownloadManager.
// DownloadManager also stupidly cuts the extension to replace with its own that it
@@ -293,13 +285,8 @@ public final class ActionBatch {
}
// The word list is still downloading. Cancel the download and revert the
// word list status to "available".
- final DownloadManager manager =
- (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
- if (null != manager) {
- // If we can't cancel the download because DownloadManager is not available,
- // we still need to mark the entry as available.
- manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
- }
+ final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
+ manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java b/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
index 7c27e6d51..3d0e29ed0 100644
--- a/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
+++ b/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
@@ -23,7 +23,7 @@ public final class CommonPreferences {
private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs";
public static SharedPreferences getCommonPreferences(final Context context) {
- return context.getSharedPreferences(COMMON_PREFERENCES_NAME, Context.MODE_WORLD_READABLE);
+ return context.getSharedPreferences(COMMON_PREFERENCES_NAME, 0);
}
public static void enable(final SharedPreferences pref, final String id) {
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
index 88b5032e3..2623eff56 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
@@ -100,32 +100,29 @@ public class DictionaryDownloadProgressBar extends ProgressBar {
@Override
protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
mIsCurrentlyAttachedToWindow = false;
updateReporterThreadRunningStatusAccordingToVisibility();
}
private class UpdaterThread extends Thread {
private final static int REPORT_PERIOD = 150; // how often to report progress, in ms
- final DownloadManager mDownloadManager;
+ final DownloadManagerWrapper mDownloadManagerWrapper;
final int mId;
public UpdaterThread(final Context context, final int id) {
super();
- mDownloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+ mDownloadManagerWrapper = new DownloadManagerWrapper(context);
mId = id;
}
@Override
public void run() {
try {
- // It's almost impossible that mDownloadManager is null (it would mean it has been
- // disabled between pressing the 'install' button and displaying the progress
- // bar), but just in case.
- if (null == mDownloadManager) return;
final UpdateHelper updateHelper = new UpdateHelper();
final Query query = new Query().setFilterById(mId);
int lastProgress = 0;
setIndeterminate(true);
while (!isInterrupted()) {
- final Cursor cursor = mDownloadManager.query(query);
+ final Cursor cursor = mDownloadManagerWrapper.query(query);
if (null == cursor) {
// Can't contact DownloadManager: this should never happen.
return;
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
index 1d9b9991e..80def701d 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
@@ -350,7 +350,8 @@ public final class DictionaryProvider extends ContentProvider {
clientId);
if (null == results) {
return Collections.<WordListInfo>emptyList();
- } else {
+ }
+ try {
final HashMap<String, WordListInfo> dicts = new HashMap<String, WordListInfo>();
final int idIndex = results.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN);
final int localeIndex = results.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
@@ -416,8 +417,9 @@ public final class DictionaryProvider extends ContentProvider {
}
} while (results.moveToNext());
}
- results.close();
return Collections.unmodifiableCollection(dicts.values());
+ } finally {
+ results.close();
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index 7bbd041e7..dae2f22a4 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -283,59 +283,70 @@ public final class DictionarySettingsFragment extends PreferenceFragment
final ArrayList<Preference> result = new ArrayList<Preference>();
result.add(createErrorMessage(activity, R.string.cannot_connect_to_dict_service));
return result;
- } else if (!cursor.moveToFirst()) {
- final ArrayList<Preference> result = new ArrayList<Preference>();
- result.add(createErrorMessage(activity, R.string.no_dictionaries_available));
- cursor.close();
- return result;
- } else {
- final String systemLocaleString = Locale.getDefault().toString();
- final TreeMap<String, WordListPreference> prefMap =
- new TreeMap<String, WordListPreference>();
- final int idIndex = cursor.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN);
- final int versionIndex = cursor.getColumnIndex(MetadataDbHelper.VERSION_COLUMN);
- final int localeIndex = cursor.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
- final int descriptionIndex = cursor.getColumnIndex(MetadataDbHelper.DESCRIPTION_COLUMN);
- final int statusIndex = cursor.getColumnIndex(MetadataDbHelper.STATUS_COLUMN);
- final int filesizeIndex = cursor.getColumnIndex(MetadataDbHelper.FILESIZE_COLUMN);
- do {
- final String wordlistId = cursor.getString(idIndex);
- final int version = cursor.getInt(versionIndex);
- final String localeString = cursor.getString(localeIndex);
- final Locale locale = new Locale(localeString);
- final String description = cursor.getString(descriptionIndex);
- final int status = cursor.getInt(statusIndex);
- final int matchLevel = LocaleUtils.getMatchLevel(systemLocaleString, localeString);
- final String matchLevelString = LocaleUtils.getMatchLevelSortedString(matchLevel);
- final int filesize = cursor.getInt(filesizeIndex);
- // The key is sorted in lexicographic order, according to the match level, then
- // the description.
- final String key = matchLevelString + "." + description + "." + wordlistId;
- final WordListPreference existingPref = prefMap.get(key);
- if (null == existingPref || existingPref.hasPriorityOver(status)) {
- final WordListPreference oldPreference = mCurrentPreferenceMap.get(key);
- final WordListPreference pref;
- if (null != oldPreference
- && oldPreference.mVersion == version
- && oldPreference.mLocale.equals(locale)) {
- // If the old preference has all the new attributes, reuse it. We test
- // for version and locale because although attributes other than status
- // need to be the same, others have been tested through the key of the
- // map. Also, status may differ so we don't want to use #equals() here.
- pref = oldPreference;
- pref.setStatus(status);
- } else {
- // Otherwise, discard it and create a new one instead.
- pref = new WordListPreference(activity, mDictionaryListInterfaceState,
- mClientId, wordlistId, version, locale, description, status,
- filesize);
+ }
+ try {
+ if (!cursor.moveToFirst()) {
+ final ArrayList<Preference> result = new ArrayList<Preference>();
+ result.add(createErrorMessage(activity, R.string.no_dictionaries_available));
+ return result;
+ } else {
+ final String systemLocaleString = Locale.getDefault().toString();
+ final TreeMap<String, WordListPreference> prefMap =
+ new TreeMap<String, WordListPreference>();
+ final int idIndex = cursor.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN);
+ final int versionIndex = cursor.getColumnIndex(MetadataDbHelper.VERSION_COLUMN);
+ final int localeIndex = cursor.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
+ final int descriptionIndex =
+ cursor.getColumnIndex(MetadataDbHelper.DESCRIPTION_COLUMN);
+ final int statusIndex = cursor.getColumnIndex(MetadataDbHelper.STATUS_COLUMN);
+ final int filesizeIndex = cursor.getColumnIndex(MetadataDbHelper.FILESIZE_COLUMN);
+ do {
+ final String wordlistId = cursor.getString(idIndex);
+ final int version = cursor.getInt(versionIndex);
+ final String localeString = cursor.getString(localeIndex);
+ final Locale locale = new Locale(localeString);
+ final String description = cursor.getString(descriptionIndex);
+ final int status = cursor.getInt(statusIndex);
+ final int matchLevel =
+ LocaleUtils.getMatchLevel(systemLocaleString, localeString);
+ final String matchLevelString =
+ LocaleUtils.getMatchLevelSortedString(matchLevel);
+ final int filesize = cursor.getInt(filesizeIndex);
+ // The key is sorted in lexicographic order, according to the match level, then
+ // the description.
+ final String key = matchLevelString + "." + description + "." + wordlistId;
+ final WordListPreference existingPref = prefMap.get(key);
+ if (null == existingPref || existingPref.hasPriorityOver(status)) {
+ final WordListPreference oldPreference = mCurrentPreferenceMap.get(key);
+ final WordListPreference pref;
+ if (null != oldPreference
+ && oldPreference.mVersion == version
+ && oldPreference.hasStatus(status)
+ && oldPreference.mLocale.equals(locale)) {
+ // If the old preference has all the new attributes, reuse it. Ideally,
+ // we should reuse the old pref even if its status is different and call
+ // setStatus here, but setStatus calls Preference#setSummary() which
+ // needs to be done on the UI thread and we're not on the UI thread
+ // here. We could do all this work on the UI thread, but in this case
+ // it's probably lighter to stay on a background thread and throw this
+ // old preference out.
+ pref = oldPreference;
+ } else {
+ // Otherwise, discard it and create a new one instead.
+ // TODO: when the status is different from the old one, we need to
+ // animate the old one out before animating the new one in.
+ pref = new WordListPreference(activity, mDictionaryListInterfaceState,
+ mClientId, wordlistId, version, locale, description, status,
+ filesize);
+ }
+ prefMap.put(key, pref);
}
- prefMap.put(key, pref);
- }
- } while (cursor.moveToNext());
+ } while (cursor.moveToNext());
+ mCurrentPreferenceMap = prefMap;
+ return prefMap.values();
+ }
+ } finally {
cursor.close();
- mCurrentPreferenceMap = prefMap;
- return prefMap.values();
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java b/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java
new file mode 100644
index 000000000..75cc7d4cb
--- /dev/null
+++ b/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import android.app.DownloadManager;
+import android.app.DownloadManager.Query;
+import android.app.DownloadManager.Request;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+
+/**
+ * A class to help with calling DownloadManager methods.
+ *
+ * Mostly, the problem here is that most methods from DownloadManager may throw SQL exceptions if
+ * they can't open the database on disk. We want to avoid crashing in these cases but can't do
+ * much more, so this class insulates the callers from these. SQLiteException also inherit from
+ * RuntimeException so they are unchecked :(
+ * While we're at it, we also insulate callers from the cases where DownloadManager is disabled,
+ * and getSystemService returns null.
+ */
+public class DownloadManagerWrapper {
+ private final static String TAG = DownloadManagerWrapper.class.getSimpleName();
+ private final DownloadManager mDownloadManager;
+
+ public DownloadManagerWrapper(final Context context) {
+ this((DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE));
+ }
+
+ private DownloadManagerWrapper(final DownloadManager downloadManager) {
+ mDownloadManager = downloadManager;
+ }
+
+ public void remove(final long... ids) {
+ try {
+ if (null != mDownloadManager) {
+ mDownloadManager.remove(ids);
+ }
+ } catch (SQLiteException e) {
+ // We couldn't remove the file from DownloadManager. Apparently, the database can't
+ // be opened. It may be a problem with file system corruption. In any case, there is
+ // not much we can do apart from avoiding crashing.
+ Log.e(TAG, "Can't remove files with ID " + ids + " from download manager", e);
+ } catch (IllegalArgumentException e) {
+ // Not sure how this can happen, but it could be another case where the provider
+ // is disabled. Or it could be a bug in older versions of the framework.
+ Log.e(TAG, "Can't find the content URL for DownloadManager?", e);
+ }
+ }
+
+ public ParcelFileDescriptor openDownloadedFile(final long fileId) throws FileNotFoundException {
+ try {
+ if (null != mDownloadManager) {
+ return mDownloadManager.openDownloadedFile(fileId);
+ }
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Can't open downloaded file with ID " + fileId, e);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Can't find the content URL for DownloadManager?", e);
+ }
+ // We come here if mDownloadManager is null or if an exception was thrown.
+ throw new FileNotFoundException();
+ }
+
+ public Cursor query(final Query query) {
+ try {
+ if (null != mDownloadManager) {
+ return mDownloadManager.query(query);
+ }
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Can't query the download manager", e);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Can't find the content URL for DownloadManager?", e);
+ }
+ // We come here if mDownloadManager is null or if an exception was thrown.
+ return null;
+ }
+
+ public long enqueue(final Request request) {
+ try {
+ if (null != mDownloadManager) {
+ return mDownloadManager.enqueue(request);
+ }
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Can't enqueue a request with the download manager", e);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Can't find the content URL for DownloadManager?", e);
+ }
+ return 0;
+ }
+}
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
index ff5aba6d8..4a8fa51ee 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -45,10 +45,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// This is the first released version of the database that implements CLIENTID. It is
// used to identify the versions for upgrades. This should never change going forward.
private static final int METADATA_DATABASE_VERSION_WITH_CLIENTID = 6;
- // This is the current database version. It should be updated when the database schema
- // gets updated. It is passed to the framework constructor of SQLiteOpenHelper, so
- // that's what the framework uses to track our database version.
- private static final int METADATA_DATABASE_VERSION = 6;
+ // The current database version.
+ private static final int CURRENT_METADATA_DATABASE_VERSION = 7;
private final static long NOT_A_DOWNLOAD_ID = -1;
@@ -169,7 +167,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
private MetadataDbHelper(final Context context, final String clientId) {
super(context,
METADATA_DATABASE_NAME_STEM + (TextUtils.isEmpty(clientId) ? "" : "." + clientId),
- null, METADATA_DATABASE_VERSION);
+ null, CURRENT_METADATA_DATABASE_VERSION);
mContext = context;
mClientId = clientId;
}
@@ -219,22 +217,45 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
/**
* 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.
+ * Version 6 and above has a DB named METADATA_DATABASE_NAME_STEM containing a
+ * table CLIENT_TABLE_NAME, and for each client a table called METADATA_TABLE_STEM + "." + the
+ * name of the client and contains a table METADATA_TABLE_NAME.
+ * For schemas, see the above create statements. The schemas have never changed so far.
+ *
+ * This method is called by the framework. See {@link SQLiteOpenHelper#onUpgrade}
+ * @param db The database we are upgrading
+ * @param oldVersion The old database version (the one on the disk)
+ * @param newVersion The new database version as supplied to the constructor of SQLiteOpenHelper
*/
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
if (METADATA_DATABASE_INITIAL_VERSION == oldVersion
- && METADATA_DATABASE_VERSION_WITH_CLIENTID == newVersion) {
+ && METADATA_DATABASE_VERSION_WITH_CLIENTID <= newVersion
+ && CURRENT_METADATA_DATABASE_VERSION >= newVersion) {
// Upgrade from version METADATA_DATABASE_INITIAL_VERSION to version
// METADATA_DATABASE_VERSION_WITH_CLIENT_ID
+ // Only the default database should contain the client table, so we test for mClientId.
if (TextUtils.isEmpty(mClientId)) {
- // Only the default database should contain the client table.
- // Anyway in version 3 only the default table existed so the emptyness
+ // Anyway in version 3 only the default table existed so the emptiness
// test should always be true, but better check to be sure.
createClientTable(db);
}
+ } else if (METADATA_DATABASE_VERSION_WITH_CLIENTID < newVersion
+ && CURRENT_METADATA_DATABASE_VERSION >= newVersion) {
+ // Here we drop the client table, so that all clients send us their information again.
+ // The client table contains the URL to hit to update the available dictionaries list,
+ // but the info about the dictionaries themselves is stored in the table called
+ // METADATA_TABLE_NAME and we want to keep it, so we only drop the client table.
+ db.execSQL("DROP TABLE IF EXISTS " + CLIENT_TABLE_NAME);
+ // Only the default database should contain the client table, so we test for mClientId.
+ if (TextUtils.isEmpty(mClientId)) {
+ createClientTable(db);
+ }
} else {
- // Version 3 was the earliest version, so we should never come here. If we do, we
- // have no idea what this database is, so we'd better wipe it off.
+ // If we're not in the above case, either we are upgrading from an earlier versionCode
+ // and we should wipe the database, or we are handling a version we never heard about
+ // (can only be a bug) so it's safer to wipe the database.
db.execSQL("DROP TABLE IF EXISTS " + METADATA_TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + CLIENT_TABLE_NAME);
onCreate(db);
@@ -533,12 +554,17 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
PENDINGID_COLUMN + "= ?",
new String[] { Long.toString(id) },
null, null, null);
- // There should never be more than one result. If because of some bug there are, returning
- // only one result is the right thing to do, because we couldn't handle several anyway
- // and we should still handle one.
- final ContentValues result = getFirstLineAsContentValues(cursor);
- cursor.close();
- return result;
+ if (null == cursor) {
+ return null;
+ }
+ try {
+ // There should never be more than one result. If because of some bug there are,
+ // returning only one result is the right thing to do, because we couldn't handle
+ // several anyway and we should still handle one.
+ return getFirstLineAsContentValues(cursor);
+ } finally {
+ cursor.close();
+ }
}
/**
@@ -559,11 +585,16 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
new String[] { id, Integer.toString(STATUS_INSTALLED),
Integer.toString(STATUS_DELETING) },
null, null, null);
- // There should only be one result, but if there are several, we can't tell which
- // is the best, so we just return the first one.
- final ContentValues result = getFirstLineAsContentValues(cursor);
- cursor.close();
- return result;
+ if (null == cursor) {
+ return null;
+ }
+ try {
+ // There should only be one result, but if there are several, we can't tell which
+ // is the best, so we just return the first one.
+ return getFirstLineAsContentValues(cursor);
+ } finally {
+ cursor.close();
+ }
}
/**
@@ -622,10 +653,15 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
METADATA_TABLE_COLUMNS,
WORDLISTID_COLUMN + "= ? AND " + VERSION_COLUMN + "= ?",
new String[] { id, Integer.toString(version) }, null, null, null);
- // This is a lookup by primary key, so there can't be more than one result.
- final ContentValues result = getFirstLineAsContentValues(cursor);
- cursor.close();
- return result;
+ if (null == cursor) {
+ return null;
+ }
+ try {
+ // This is a lookup by primary key, so there can't be more than one result.
+ return getFirstLineAsContentValues(cursor);
+ } finally {
+ cursor.close();
+ }
}
/**
@@ -641,10 +677,15 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
METADATA_TABLE_COLUMNS,
WORDLISTID_COLUMN + "= ?",
new String[] { id }, null, null, VERSION_COLUMN + " DESC", "1");
- // This is a lookup by primary key, so there can't be more than one result.
- final ContentValues result = getFirstLineAsContentValues(cursor);
- cursor.close();
- return result;
+ if (null == cursor) {
+ return null;
+ }
+ try {
+ // This is a lookup by primary key, so there can't be more than one result.
+ return getFirstLineAsContentValues(cursor);
+ } finally {
+ cursor.close();
+ }
}
/**
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
index a0147b6d6..5c2289911 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
@@ -44,8 +44,7 @@ public class MetadataHandler {
*/
private static List<WordListMetadata> makeMetadataObject(final Cursor results) {
final ArrayList<WordListMetadata> buildingMetadata = new ArrayList<WordListMetadata>();
-
- if (results.moveToFirst()) {
+ if (null != results && results.moveToFirst()) {
final int localeColumn = results.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
final int typeColumn = results.getColumnIndex(MetadataDbHelper.TYPE_COLUMN);
final int descriptionColumn =
@@ -61,7 +60,6 @@ public class MetadataHandler {
final int versionIndex = results.getColumnIndex(MetadataDbHelper.VERSION_COLUMN);
final int formatVersionIndex =
results.getColumnIndex(MetadataDbHelper.FORMATVERSION_COLUMN);
-
do {
buildingMetadata.add(new WordListMetadata(results.getString(idIndex),
results.getInt(typeColumn),
@@ -75,8 +73,6 @@ public class MetadataHandler {
results.getInt(formatVersionIndex),
0, results.getString(localeColumn)));
} while (results.moveToNext());
-
- results.close();
}
return Collections.unmodifiableList(buildingMetadata);
}
@@ -92,9 +88,14 @@ public class MetadataHandler {
// If clientId is null, we get a cursor on the default database (see
// MetadataDbHelper#getInstance() for more on this)
final Cursor results = MetadataDbHelper.queryCurrentMetadata(context, clientId);
- final List<WordListMetadata> resultList = makeMetadataObject(results);
- results.close();
- return resultList;
+ // If null, we should return makeMetadataObject(null), so we go through.
+ try {
+ return makeMetadataObject(results);
+ } finally {
+ if (null != results) {
+ results.close();
+ }
+ }
}
/**
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index 0e7c3bb7e..dcff490db 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -249,13 +249,7 @@ public final class UpdateHandler {
metadataRequest.setVisibleInDownloadsUi(
res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI));
- final DownloadManager manager =
- (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
- if (null == manager) {
- // Download manager is not installed or disabled.
- // TODO: fall back to self-managed download?
- return;
- }
+ final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
cancelUpdateWithDownloadManager(context, metadataUri, manager);
final long downloadId;
synchronized (sSharedIdProtector) {
@@ -278,10 +272,10 @@ public final class UpdateHandler {
*
* @param context the context to open the database on
* @param metadataUri the URI to cancel
- * @param manager an instance of DownloadManager
+ * @param manager an wrapped instance of DownloadManager
*/
private static void cancelUpdateWithDownloadManager(final Context context,
- final String metadataUri, final DownloadManager manager) {
+ final String metadataUri, final DownloadManagerWrapper manager) {
synchronized (sSharedIdProtector) {
final long metadataDownloadId =
MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri);
@@ -306,10 +300,9 @@ public final class UpdateHandler {
* @param clientId the ID of the client we want to cancel the update of
*/
public static void cancelUpdate(final Context context, final String clientId) {
- final DownloadManager manager =
- (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+ final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId);
- if (null != manager) cancelUpdateWithDownloadManager(context, metadataUri, manager);
+ cancelUpdateWithDownloadManager(context, metadataUri, manager);
}
/**
@@ -323,15 +316,15 @@ public final class UpdateHandler {
* download request id, which is not known before submitting the request to the download
* manager. Hence, it only updates the relevant line.
*
- * @param manager the download manager service to register the request with.
+ * @param manager a wrapped download manager service to register the request with.
* @param request the request to register.
* @param db the metadata database.
* @param id the id of the word list.
* @param version the version of the word list.
* @return the download id returned by the download manager.
*/
- public static long registerDownloadRequest(final DownloadManager manager, final Request request,
- final SQLiteDatabase db, final String id, final int version) {
+ public static long registerDownloadRequest(final DownloadManagerWrapper manager,
+ final Request request, final SQLiteDatabase db, final String id, final int version) {
DebugLogUtils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version);
final long downloadId;
synchronized (sSharedIdProtector) {
@@ -345,8 +338,8 @@ public final class UpdateHandler {
/**
* Retrieve information about a specific download from DownloadManager.
*/
- private static CompletedDownloadInfo getCompletedDownloadInfo(final DownloadManager manager,
- final long downloadId) {
+ private static CompletedDownloadInfo getCompletedDownloadInfo(
+ final DownloadManagerWrapper manager, final long downloadId) {
final Query query = new Query().setFilterById(downloadId);
final Cursor cursor = manager.query(query);
@@ -425,8 +418,7 @@ public final class UpdateHandler {
DebugLogUtils.l("DownloadFinished with id", fileId);
if (NOT_AN_ID == fileId) return; // Spurious wake-up: ignore
- final DownloadManager manager =
- (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+ final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
final CompletedDownloadInfo downloadInfo = getCompletedDownloadInfo(manager, fileId);
final ArrayList<DownloadRecord> recordList =
@@ -517,7 +509,7 @@ public final class UpdateHandler {
}
private static boolean handleDownloadedFile(final Context context,
- final DownloadRecord downloadRecord, final DownloadManager manager,
+ final DownloadRecord downloadRecord, final DownloadManagerWrapper manager,
final long fileId) {
try {
// {@link handleWordList(Context,InputStream,ContentValues)}.
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
index ba1fce1a8..aea16af0d 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
@@ -98,6 +98,10 @@ public final class WordListPreference extends Preference {
setSummary(getSummary(status));
}
+ public boolean hasStatus(final int status) {
+ return status == mStatus;
+ }
+
@Override
public View onCreateView(final ViewGroup parent) {
final View orphanedView = mInterfaceState.findFirstOrphanedView();
@@ -217,6 +221,7 @@ public final class WordListPreference extends Preference {
progressBar.setIds(mClientId, mWordlistId);
progressBar.setMax(mFilesize);
final boolean showProgressBar = (MetadataDbHelper.STATUS_DOWNLOADING == mStatus);
+ setSummary(getSummary(mStatus));
status.setVisibility(showProgressBar ? View.INVISIBLE : View.VISIBLE);
progressBar.setVisibility(showProgressBar ? View.VISIBLE : View.INVISIBLE);
diff --git a/java/src/com/android/inputmethod/event/Combiner.java b/java/src/com/android/inputmethod/event/Combiner.java
index ab6b70c04..8b808c6b3 100644
--- a/java/src/com/android/inputmethod/event/Combiner.java
+++ b/java/src/com/android/inputmethod/event/Combiner.java
@@ -16,14 +16,33 @@
package com.android.inputmethod.event;
+import java.util.ArrayList;
+
/**
- * A generic interface for combiners.
+ * A generic interface for combiners. Combiners are objects that transform chains of input events
+ * into committable strings and manage feedback to show to the user on the combining state.
*/
public interface Combiner {
/**
- * Combine an event with the existing state and return the new event.
+ * Process an event, possibly combining it with the existing state and return the new event.
+ *
+ * If this event does not result in any new event getting passed down the chain, this method
+ * returns null. It may also modify the previous event list if appropriate.
+ *
+ * @param previousEvents the previous events in this composition.
* @param event the event to combine with the existing state.
* @return the resulting event.
*/
- Event combine(Event event);
+ Event processEvent(ArrayList<Event> previousEvents, Event event);
+
+ /**
+ * Get the feedback that should be shown to the user for the current state of this combiner.
+ * @return A CharSequence representing the feedback to show users. It may include styles.
+ */
+ CharSequence getCombiningStateFeedback();
+
+ /**
+ * Reset the state of this combiner, for example when the cursor was moved.
+ */
+ void reset();
}
diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java
new file mode 100644
index 000000000..8b59dc52a
--- /dev/null
+++ b/java/src/com/android/inputmethod/event/CombinerChain.java
@@ -0,0 +1,117 @@
+/*
+ * 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.event;
+
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.util.ArrayList;
+
+/**
+ * This class implements the logic chain between receiving events and generating code points.
+ *
+ * Event sources are multiple. It may be a hardware keyboard, a D-PAD, a software keyboard,
+ * or any exotic input source.
+ * This class will orchestrate the composing chain that starts with an event as its input. Each
+ * composer will be given turns one after the other.
+ * The output is composed of two sequences of code points: the first, representing the already
+ * finished combining part, will be shown normally as the composing string, while the second is
+ * feedback on the composing state and will typically be shown with different styling such as
+ * a colored background.
+ */
+public class CombinerChain {
+ // The already combined text, as described above
+ private StringBuilder mCombinedText;
+ // The feedback on the composing state, as described above
+ private SpannableStringBuilder mStateFeedback;
+ private final ArrayList<Combiner> mCombiners;
+
+ /**
+ * Create an combiner chain.
+ *
+ * The combiner chain takes events as inputs and outputs code points and combining state.
+ * For example, if the input language is Japanese, the combining chain will typically perform
+ * kana conversion.
+ *
+ * @param combinerList A list of combiners to be applied in order.
+ */
+ public CombinerChain(final Combiner... combinerList) {
+ mCombiners = CollectionUtils.newArrayList();
+ // The dead key combiner is always active, and always first
+ mCombiners.add(new DeadKeyCombiner());
+ mCombinedText = new StringBuilder();
+ mStateFeedback = new SpannableStringBuilder();
+ }
+
+ public void reset() {
+ mCombinedText.setLength(0);
+ mStateFeedback.clear();
+ for (final Combiner c : mCombiners) {
+ c.reset();
+ }
+ }
+
+ /**
+ * Pass a new event through the whole chain.
+ * @param previousEvents the list of previous events in this composition
+ * @param newEvent the new event to process
+ */
+ public void processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {
+ final ArrayList<Event> modifiablePreviousEvents = new ArrayList<Event>(previousEvents);
+ Event event = newEvent;
+ for (final Combiner combiner : mCombiners) {
+ // A combiner can never return more than one event; it can return several
+ // code points, but they should be encapsulated within one event.
+ event = combiner.processEvent(modifiablePreviousEvents, event);
+ if (null == event) {
+ // Combiners return null if they eat the event.
+ break;
+ }
+ }
+ if (null != event) {
+ // TODO: figure out the generic way of doing this
+ if (Constants.CODE_DELETE == event.mKeyCode) {
+ final int length = mCombinedText.length();
+ if (length > 0) {
+ final int lastCodePoint = mCombinedText.codePointBefore(length);
+ mCombinedText.delete(length - Character.charCount(lastCodePoint), length);
+ }
+ } else {
+ final CharSequence textToCommit = event.getTextToCommit();
+ if (!TextUtils.isEmpty(textToCommit)) {
+ mCombinedText.append(textToCommit);
+ }
+ }
+ }
+ mStateFeedback.clear();
+ for (int i = mCombiners.size() - 1; i >= 0; --i) {
+ mStateFeedback.append(mCombiners.get(i).getCombiningStateFeedback());
+ }
+ }
+
+ /**
+ * Get the char sequence that should be displayed as the composing word. It may include
+ * styling spans.
+ */
+ public CharSequence getComposingWordWithCombiningFeedback() {
+ final SpannableStringBuilder s = new SpannableStringBuilder(mCombinedText);
+ return s.append(mStateFeedback);
+ }
+}
diff --git a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
index 52987d571..bef4d8594 100644
--- a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
+++ b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
@@ -21,14 +21,17 @@ import android.view.KeyCharacterMap;
import com.android.inputmethod.latin.Constants;
+import java.util.ArrayList;
+
/**
* A combiner that handles dead keys.
*/
public class DeadKeyCombiner implements Combiner {
+ // TODO: make this a list of events instead
final StringBuilder mDeadSequence = new StringBuilder();
@Override
- public Event combine(final Event event) {
+ public Event processEvent(final ArrayList<Event> previousEvents, final Event event) {
if (null == event) return null; // Just in case some combiner is broken
if (TextUtils.isEmpty(mDeadSequence)) {
if (event.isDead()) {
@@ -43,19 +46,33 @@ public class DeadKeyCombiner implements Combiner {
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 committable
+ // 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.
- return Event.createCommittableEvent(deadCodePoint,
- Constants.CODE_SPACE == event.mCodePoint ? null : event /* next */);
+ // 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 */);
} else {
// We could combine the characters.
- return Event.createCommittableEvent(resultingCodePoint, null /* next */);
+ return Event.createHardwareKeypressEvent(resultingCodePoint, event.mKeyCode,
+ null /* next */, false /* isKeyRepeat */);
}
}
}
+ @Override
+ public void reset() {
+ mDeadSequence.setLength(0);
+ }
+
+ @Override
+ public CharSequence getCombiningStateFeedback() {
+ return mDeadSequence;
+ }
}
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index 1f3320eb7..4a9163c8e 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -16,6 +16,10 @@
package com.android.inputmethod.event;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.utils.StringUtils;
+
/**
* Class representing a generic input event as handled by Latin IME.
*
@@ -32,62 +36,227 @@ public class Event {
// Should the types below be represented by separate classes instead? It would be cleaner
// but probably a bit too much
// An event we don't handle in Latin IME, for example pressing Ctrl on a hardware keyboard.
- final public static int EVENT_NOT_HANDLED = 0;
- // A character that is already final, for example pressing an alphabetic character on a
- // hardware qwerty keyboard.
- final public static int EVENT_COMMITTABLE = 1;
- // A dead key, which means a character that should combine with what is coming next. Examples
- // include the "^" character on an azerty keyboard which combines with "e" to make "ê", or
- // AltGr+' on a dvorak international keyboard which combines with "e" to make "é". This is
- // true regardless of the language or combining mode, and should be seen as a property of the
- // key - a dead key followed by another key with which it can combine should be regarded as if
- // the keyboard actually had such a key.
- final public static int EVENT_DEAD = 2;
+ final public static int EVENT_TYPE_NOT_HANDLED = 0;
+ // A key press that is part of input, for example pressing an alphabetic character on a
+ // hardware qwerty keyboard. It may be part of a sequence that will be re-interpreted later
+ // through combination.
+ final public static int EVENT_TYPE_INPUT_KEYPRESS = 1;
// A toggle event is triggered by a key that affects the previous character. An example would
// be a numeric key on a 10-key keyboard, which would toggle between 1 - a - b - c with
// repeated presses.
- final public static int EVENT_TOGGLE = 3;
+ final public static int EVENT_TYPE_TOGGLE = 2;
// A mode event instructs the combiner to change modes. The canonical example would be the
// hankaku/zenkaku key on a Japanese keyboard, or even the caps lock key on a qwerty keyboard
// if handled at the combiner level.
- final public static int EVENT_MODE_KEY = 4;
+ final public static int EVENT_TYPE_MODE_KEY = 3;
+ // An event corresponding to a gesture.
+ final public static int EVENT_TYPE_GESTURE = 4;
+ // An event corresponding to the manual pick of a suggestion.
+ final public static int EVENT_TYPE_SUGGESTION_PICKED = 5;
+ // An event corresponding to a string generated by some software process.
+ final public static int EVENT_TYPE_SOFTWARE_GENERATED_STRING = 6;
+
+ // 0 is a valid code point, so we use -1 here.
+ final public static int NOT_A_CODE_POINT = -1;
+ // -1 is a valid key code, so we use 0 here.
+ final public static int NOT_A_KEY_CODE = 0;
- final private static int NOT_A_CODE_POINT = 0;
+ final private static int FLAG_NONE = 0;
+ // This event is a dead character, usually input by a dead key. Examples include dead-acute
+ // or dead-abovering.
+ final private static int FLAG_DEAD = 0x1;
+ // This event is coming from a key repeat, software or hardware.
+ final private static int FLAG_REPEAT = 0x2;
- final private int mType; // The type of event - one of the constants above
+ final private int mEventType; // The type of event - one of the constants above
// The code point associated with the event, if relevant. This is a unicode code point, and
// has nothing to do with other representations of the key. It is only relevant if this event
- // is the right type: COMMITTABLE or DEAD or TOGGLE, but for a mode key like hankaku/zenkaku or
- // ctrl, there is no code point associated so this should be NOT_A_CODE_POINT to avoid
- // unintentional use of its value when it's not relevant.
+ // is of KEYPRESS type, but for a mode key like hankaku/zenkaku or ctrl, there is no code point
+ // associated so this should be NOT_A_CODE_POINT to avoid unintentional use of its value when
+ // it's not relevant.
final public int mCodePoint;
+
+ // If applicable, this contains the string that should be input.
+ final public CharSequence mText;
+
+ // The key code associated with the event, if relevant. This is relevant whenever this event
+ // has been triggered by a key press, but not for a gesture for example. This has conceptually
+ // no link to the code point, although keys that enter a straight code point may often set
+ // this to be equal to mCodePoint for convenience. If this is not a key, this must contain
+ // NOT_A_KEY_CODE.
+ final public int mKeyCode;
+
+ // Coordinates of the touch event, if relevant. If useful, we may want to replace this with
+ // a MotionEvent or something in the future. This is only relevant when the keypress is from
+ // a software keyboard obviously, unless there are touch-sensitive hardware keyboards in the
+ // future or some other awesome sauce.
+ final public int mX;
+ final public int mY;
+
+ // Some flags that can't go into the key code. It's a bit field of FLAG_*
+ final private int mFlags;
+
+ // If this is of type EVENT_TYPE_SUGGESTION_PICKED, this must not be null (and must be null in
+ // other cases).
+ final public SuggestedWordInfo mSuggestedWordInfo;
+
// The next event, if any. Null if there is no next event yet.
final public Event mNextEvent;
// This method is private - to create a new event, use one of the create* utility methods.
- private Event(final int type, final int codePoint, final Event next) {
- mType = type;
+ private Event(final int type, final CharSequence text, final int codePoint, final int keyCode,
+ final int x, final int y, final SuggestedWordInfo suggestedWordInfo, final int flags,
+ final Event next) {
+ mEventType = type;
+ mText = text;
mCodePoint = codePoint;
+ mKeyCode = keyCode;
+ mX = x;
+ mY = y;
+ mSuggestedWordInfo = suggestedWordInfo;
+ mFlags = flags;
mNextEvent = next;
+ // Sanity checks
+ // mSuggestedWordInfo is non-null if and only if the type is SUGGESTION_PICKED
+ if (EVENT_TYPE_SUGGESTION_PICKED == mEventType) {
+ if (null == mSuggestedWordInfo) {
+ throw new RuntimeException("Wrong event: SUGGESTION_PICKED event must have a "
+ + "non-null SuggestedWordInfo");
+ }
+ } else {
+ if (null != mSuggestedWordInfo) {
+ throw new RuntimeException("Wrong event: only SUGGESTION_PICKED events may have " +
+ "a non-null SuggestedWordInfo");
+ }
+ }
}
- public static Event createDeadEvent(final int codePoint, final Event next) {
- return new Event(EVENT_DEAD, codePoint, next);
+ public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode,
+ final int x, final int y, final boolean isKeyRepeat) {
+ return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, x, y,
+ null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, null);
}
- public static Event createCommittableEvent(final int codePoint, final Event next) {
- return new Event(EVENT_COMMITTABLE, codePoint, next);
+ public static Event createHardwareKeypressEvent(final int codePoint, final int keyCode,
+ final Event next, final boolean isKeyRepeat) {
+ return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
+ Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
+ null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, next);
}
- public static Event createNotHandledEvent() {
- return new Event(EVENT_NOT_HANDLED, NOT_A_CODE_POINT, null);
+ // This creates an input event for a dead character. @see {@link #FLAG_DEAD}
+ 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,
+ Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
+ null /* suggestedWordInfo */, FLAG_DEAD, next);
+ }
+
+ /**
+ * Create an input event with nothing but a code point. This is the most basic possible input
+ * event; it contains no information on many things the IME requires to function correctly,
+ * so avoid using it unless really nothing is known about this input.
+ * @param codePoint the code point.
+ * @return an event for this code point.
+ */
+ public static Event createEventForCodePointFromUnknownSource(final int codePoint) {
+ // TODO: should we have a different type of event for this? After all, it's not a key press.
+ return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+ null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
+ }
+
+ /**
+ * Creates an input event with a code point and x, y coordinates. This is typically used when
+ * resuming a previously-typed word, when the coordinates are still known.
+ * @param codePoint the code point to input.
+ * @param x the X coordinate.
+ * @param y the Y coordinate.
+ * @return an event for this code point and coordinates.
+ */
+ public static Event createEventForCodePointFromAlreadyTypedText(final int codePoint,
+ final int x, final int y) {
+ // TODO: should we have a different type of event for this? After all, it's not a key press.
+ return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE,
+ x, y, null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
+ }
+
+ /**
+ * Creates an input event representing the manual pick of a suggestion.
+ * @return an event for this suggestion pick.
+ */
+ public static Event createSuggestionPickedEvent(final SuggestedWordInfo suggestedWordInfo) {
+ return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord,
+ NOT_A_CODE_POINT, NOT_A_KEY_CODE,
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
+ suggestedWordInfo, FLAG_NONE, null /* next */);
+ }
+
+ /**
+ * Creates an input event with a CharSequence. This is used by some software processes whose
+ * output is a string, possibly with styling. Examples include press on a multi-character key,
+ * or combination that outputs a string.
+ * @param text the CharSequence associated with this event.
+ * @param keyCode the key code, or NOT_A_KEYCODE if not applicable.
+ * @return an event for this text.
+ */
+ public static Event createSoftwareTextEvent(final CharSequence text, final int keyCode) {
+ return new Event(EVENT_TYPE_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+ null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
}
- public boolean isCommittable() {
- return EVENT_COMMITTABLE == mType;
+ /**
+ * Creates an input event representing the manual pick of a punctuation suggestion.
+ * @return an event for this suggestion pick.
+ */
+ public static Event createPunctuationSuggestionPickedEvent(
+ final SuggestedWordInfo suggestedWordInfo) {
+ final int primaryCode = suggestedWordInfo.mWord.charAt(0);
+ return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, primaryCode,
+ NOT_A_KEY_CODE, Constants.SUGGESTION_STRIP_COORDINATE,
+ Constants.SUGGESTION_STRIP_COORDINATE, suggestedWordInfo, FLAG_NONE,
+ null /* next */);
}
+ public static Event createNotHandledEvent() {
+ return new Event(EVENT_TYPE_NOT_HANDLED, null /* text */, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+ null /* suggestedWordInfo */, FLAG_NONE, null);
+ }
+
+ // Returns whether this event is for a dead character. @see {@link #FLAG_DEAD}
public boolean isDead() {
- return EVENT_DEAD == mType;
+ return 0 != (FLAG_DEAD & mFlags);
+ }
+
+ public boolean isKeyRepeat() {
+ return 0 != (FLAG_REPEAT & mFlags);
+ }
+
+ // Returns whether this is a fake key press from the suggestion strip. This happens with
+ // punctuation signs selected from the suggestion strip.
+ public boolean isSuggestionStripPress() {
+ return EVENT_TYPE_SUGGESTION_PICKED == mEventType;
+ }
+
+ public boolean isHandled() {
+ return EVENT_TYPE_NOT_HANDLED != mEventType;
+ }
+
+ public CharSequence getTextToCommit() {
+ switch (mEventType) {
+ case EVENT_TYPE_MODE_KEY:
+ case EVENT_TYPE_NOT_HANDLED:
+ case EVENT_TYPE_TOGGLE:
+ return "";
+ case EVENT_TYPE_INPUT_KEYPRESS:
+ return StringUtils.newSingleCodePointString(mCodePoint);
+ case EVENT_TYPE_GESTURE:
+ case EVENT_TYPE_SOFTWARE_GENERATED_STRING:
+ case EVENT_TYPE_SUGGESTION_PICKED:
+ return mText;
+ }
+ throw new RuntimeException("Unknown event type: " + mEventType);
}
}
diff --git a/java/src/com/android/inputmethod/event/EventInterpreter.java b/java/src/com/android/inputmethod/event/EventInterpreter.java
deleted file mode 100644
index 726b9206b..000000000
--- a/java/src/com/android/inputmethod/event/EventInterpreter.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2012 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.event;
-
-import android.util.SparseArray;
-import android.view.KeyEvent;
-
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
-import java.util.ArrayList;
-
-/**
- * This class implements the logic between receiving events and generating code points.
- *
- * Event sources are multiple. It may be a hardware keyboard, a D-PAD, a software keyboard,
- * or any exotic input source.
- * This class will orchestrate the decoding chain that starts with an event and ends up with
- * a stream of code points + decoding state.
- */
-public class EventInterpreter {
- // TODO: Implement an object pool for events, as we'll create a lot of them
- // TODO: Create a combiner
- // TODO: Create an object type to represent input material + visual feedback + decoding state
- // TODO: Create an interface to call back to Latin IME through the above object
-
- final EventDecoderSpec mDecoderSpec;
- final SparseArray<HardwareEventDecoder> mHardwareEventDecoders;
- final SoftwareEventDecoder mSoftwareEventDecoder;
- final LatinIME mLatinIme;
- final ArrayList<Combiner> mCombiners;
-
- /**
- * Create a default interpreter.
- *
- * This creates a default interpreter that does nothing. A default interpreter should normally
- * only be used for fallback purposes, when we really don't know what we want to do with input.
- *
- * @param latinIme a reference to the ime.
- */
- public EventInterpreter(final LatinIME latinIme) {
- this(null, latinIme);
- }
-
- /**
- * Create an event interpreter according to a specification.
- *
- * The specification contains information about what to do with events. Typically, it will
- * contain information about the type of keyboards - for example, if hardware keyboard(s) is/are
- * attached, their type will be included here so that the decoder knows what to do with each
- * keypress (a 10-key keyboard is not handled like a qwerty-ish keyboard).
- * It also contains information for combining characters. For example, if the input language
- * is Japanese, the specification will typically request kana conversion.
- * Also note that the specification can be null. This means that we need to create a default
- * interpreter that does no specific combining, and assumes the most common cases.
- *
- * @param specification the specification for event interpretation. null for default.
- * @param latinIme a reference to the ime.
- */
- public EventInterpreter(final EventDecoderSpec specification, final LatinIME latinIme) {
- mDecoderSpec = null != specification ? specification : new EventDecoderSpec();
- // For both, we expect to have only one decoder in almost all cases, hence the default
- // capacity of 1.
- mHardwareEventDecoders = new SparseArray<HardwareEventDecoder>(1);
- mSoftwareEventDecoder = new SoftwareKeyboardEventDecoder();
- mCombiners = CollectionUtils.newArrayList();
- mCombiners.add(new DeadKeyCombiner());
- mLatinIme = latinIme;
- }
-
- // Helper method to decode a hardware key event into a generic event, and execute any
- // necessary action.
- public boolean onHardwareKeyEvent(final KeyEvent hardwareKeyEvent) {
- final Event decodedEvent = getHardwareKeyEventDecoder(hardwareKeyEvent.getDeviceId())
- .decodeHardwareKey(hardwareKeyEvent);
- return onEvent(decodedEvent);
- }
-
- public boolean onSoftwareEvent() {
- final Event decodedEvent = getSoftwareEventDecoder().decodeSoftwareEvent();
- return onEvent(decodedEvent);
- }
-
- private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
- final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId);
- if (null != decoder) return decoder;
- // TODO: create the decoder according to the specification
- final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId);
- mHardwareEventDecoders.put(deviceId, newDecoder);
- return newDecoder;
- }
-
- private SoftwareEventDecoder getSoftwareEventDecoder() {
- // Within the context of Latin IME, since we never present several software interfaces
- // at the time, we should never need multiple software event decoders at a time.
- return mSoftwareEventDecoder;
- }
-
- private boolean onEvent(final Event event) {
- Event currentlyProcessingEvent = event;
- boolean processed = false;
- for (int i = 0; i < mCombiners.size(); ++i) {
- currentlyProcessingEvent = mCombiners.get(i).combine(event);
- }
- while (null != currentlyProcessingEvent) {
- if (currentlyProcessingEvent.isCommittable()) {
- mLatinIme.onCodeInput(currentlyProcessingEvent.mCodePoint,
- Constants.EXTERNAL_KEYBOARD_COORDINATE,
- Constants.EXTERNAL_KEYBOARD_COORDINATE);
- processed = true;
- } else if (event.isDead()) {
- processed = true;
- }
- currentlyProcessingEvent = currentlyProcessingEvent.mNextEvent;
- }
- return processed;
- }
-}
diff --git a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
index 720d07433..05ba99923 100644
--- a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
+++ b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
@@ -46,28 +46,36 @@ public class HardwareKeyboardEventDecoder implements HardwareEventDecoder {
// do not necessarily map to a unicode character. This represents a physical key, like
// the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock.
final int keyCode = keyEvent.getKeyCode();
+ final boolean isKeyRepeat = (0 != keyEvent.getRepeatCount());
if (KeyEvent.KEYCODE_DEL == keyCode) {
- return Event.createCommittableEvent(Constants.CODE_DELETE, null /* next */);
+ return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT, Constants.CODE_DELETE,
+ null /* next */, isKeyRepeat);
}
if (keyEvent.isPrintingKey() || KeyEvent.KEYCODE_SPACE == keyCode
|| KeyEvent.KEYCODE_ENTER == keyCode) {
if (0 != (codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT)) {
// A dead key.
return Event.createDeadEvent(
- codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT_MASK, null /* next */);
+ codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT_MASK, keyCode,
+ null /* next */);
}
if (KeyEvent.KEYCODE_ENTER == keyCode) {
// The Enter key. If the Shift key is not being pressed, this should send a
// CODE_ENTER to trigger the action if any, or a carriage return otherwise. If the
// Shift key is being pressed, this should send a CODE_SHIFT_ENTER and let
// Latin IME decide what to do with it.
- return Event.createCommittableEvent(keyEvent.isShiftPressed()
- ? Constants.CODE_SHIFT_ENTER : Constants.CODE_ENTER,
- null /* next */);
+ if (keyEvent.isShiftPressed()) {
+ return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT,
+ Constants.CODE_SHIFT_ENTER, null /* next */, isKeyRepeat);
+ } else {
+ return Event.createHardwareKeypressEvent(Constants.CODE_ENTER, keyCode,
+ null /* next */, isKeyRepeat);
+ }
}
- // If not Enter, then we have a committable character. This should be committed
- // right away, taking into account the current state.
- return Event.createCommittableEvent(codePointAndFlags, null /* next */);
+ // If not Enter, then this is just a regular keypress event for a normal character
+ // that can be committed right away, taking into account the current state.
+ return Event.createHardwareKeypressEvent(keyCode, codePointAndFlags, null /* next */,
+ isKeyRepeat);
}
return Event.createNotHandledEvent();
}
diff --git a/java/src/com/android/inputmethod/event/InputTransaction.java b/java/src/com/android/inputmethod/event/InputTransaction.java
new file mode 100644
index 000000000..4fe9b403e
--- /dev/null
+++ b/java/src/com/android/inputmethod/event/InputTransaction.java
@@ -0,0 +1,84 @@
+/*
+ * 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.event;
+
+import com.android.inputmethod.latin.settings.SettingsValues;
+
+/**
+ * An object encapsulating a single transaction for input.
+ */
+public class InputTransaction {
+ // UPDATE_LATER is stronger than UPDATE_NOW. The reason for this is, if we have to update later,
+ // it's because something will change that we can't evaluate now, which means that even if we
+ // re-evaluate now we'll have to do it again later. The only case where that wouldn't apply
+ // would be if we needed to update now to find out the new state right away, but then we
+ // can't do it with this deferred mechanism anyway.
+ public static final int SHIFT_NO_UPDATE = 0;
+ public static final int SHIFT_UPDATE_NOW = 1;
+ public static final int SHIFT_UPDATE_LATER = 2;
+
+ // Initial conditions
+ public final SettingsValues mSettingsValues;
+ public final Event mEvent;
+ public final long mTimestamp;
+ public final int mSpaceState;
+ public final int mShiftState;
+
+ // Outputs
+ private int mRequiredShiftUpdate = SHIFT_NO_UPDATE;
+ private boolean mRequiresUpdateSuggestions = false;
+
+ public InputTransaction(final SettingsValues settingsValues, final Event event,
+ final long timestamp, final int spaceState, final int shiftState) {
+ mSettingsValues = settingsValues;
+ mEvent = event;
+ mTimestamp = timestamp;
+ mSpaceState = spaceState;
+ mShiftState = shiftState;
+ }
+
+ /**
+ * Indicate that this transaction requires some type of shift update.
+ * @param updateType What type of shift update this requires.
+ */
+ public void requireShiftUpdate(final int updateType) {
+ mRequiredShiftUpdate = Math.max(mRequiredShiftUpdate, updateType);
+ }
+
+ /**
+ * Gets what type of shift update this transaction requires.
+ * @return The shift update type.
+ */
+ public int getRequiredShiftUpdate() {
+ return mRequiredShiftUpdate;
+ }
+
+ /**
+ * Indicate that this transaction requires updating the suggestions.
+ */
+ public void setRequiresUpdateSuggestions() {
+ mRequiresUpdateSuggestions = true;
+ }
+
+ /**
+ * Find out whether this transaction requires updating the suggestions.
+ * @return Whether this transaction requires updating the suggestions.
+ */
+ public boolean requiresUpdateSuggestions() {
+ return mRequiresUpdateSuggestions;
+ }
+}
diff --git a/java/src/com/android/inputmethod/event/SoftwareEventDecoder.java b/java/src/com/android/inputmethod/event/SoftwareEventDecoder.java
deleted file mode 100644
index d81ee0b37..000000000
--- a/java/src/com/android/inputmethod/event/SoftwareEventDecoder.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2012 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.event;
-
-/**
- * An event decoder for events out of a software keyboard.
- *
- * This defines the interface for an event decoder that supports events out of a software keyboard.
- * This differs significantly from hardware keyboard event decoders in several respects. First,
- * a software keyboard does not have a scancode/layout system; the keypresses that insert
- * characters output unicode characters directly.
- */
-public interface SoftwareEventDecoder extends EventDecoder {
- public Event decodeSoftwareEvent();
-}
diff --git a/java/src/com/android/inputmethod/event/SoftwareKeyboardEventDecoder.java b/java/src/com/android/inputmethod/event/SoftwareKeyboardEventDecoder.java
deleted file mode 100644
index de91567c7..000000000
--- a/java/src/com/android/inputmethod/event/SoftwareKeyboardEventDecoder.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2012 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.event;
-
-/**
- * A decoder for events from software keyboard, like the ones displayed by Latin IME.
- */
-public class SoftwareKeyboardEventDecoder implements SoftwareEventDecoder {
- @Override
- public Event decodeSoftwareEvent() {
- return null;
- }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
index e23131a30..d56a3cf25 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
@@ -31,17 +31,17 @@ public class EmojiCategoryPageIndicatorView extends LinearLayout {
private int mCurrentCategoryPageId = 0;
private float mOffset = 0.0f;
- public EmojiCategoryPageIndicatorView(Context context) {
+ public EmojiCategoryPageIndicatorView(final Context context) {
this(context, null /* attrs */);
}
- public EmojiCategoryPageIndicatorView(Context context, AttributeSet attrs) {
+ public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs) {
super(context, attrs);
mPaint.setColor(context.getResources().getColor(
R.color.emoji_category_page_id_view_foreground));
}
- public void setCategoryPageId(int size, int id, float offset) {
+ public void setCategoryPageId(final int size, final int id, final float offset) {
mCategoryPageSize = size;
mCurrentCategoryPageId = id;
mOffset = offset;
@@ -49,7 +49,7 @@ public class EmojiCategoryPageIndicatorView extends LinearLayout {
}
@Override
- protected void onDraw(Canvas canvas) {
+ protected void onDraw(final Canvas canvas) {
if (mCategoryPageSize <= 1) {
// If the category is not set yet or contains only one category,
// just clear and return.
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
index f12373503..ad996971f 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
@@ -23,16 +23,18 @@ import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.Build;
+import android.os.CountDownTimer;
import android.preference.PreferenceManager;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
-import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
+import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -44,8 +46,10 @@ import android.widget.TabHost.OnTabChangeListener;
import android.widget.TextView;
import com.android.inputmethod.keyboard.internal.DynamicGridKeyboard;
-import com.android.inputmethod.keyboard.internal.ScrollKeyboardView;
-import com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier;
+import com.android.inputmethod.keyboard.internal.EmojiLayoutParams;
+import com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView;
+import com.android.inputmethod.keyboard.internal.KeyDrawParams;
+import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
@@ -58,6 +62,7 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
/**
* View class to implement Emoji palettes.
@@ -71,17 +76,19 @@ import java.util.concurrent.ConcurrentHashMap;
* Because of the above reasons, this class doesn't extend {@link KeyboardView}.
*/
public final class EmojiPalettesView extends LinearLayout implements OnTabChangeListener,
- ViewPager.OnPageChangeListener, View.OnClickListener,
- ScrollKeyboardView.OnKeyClickListener {
- private static final String TAG = EmojiPalettesView.class.getSimpleName();
+ ViewPager.OnPageChangeListener, View.OnClickListener, View.OnTouchListener,
+ EmojiPageKeyboardView.OnKeyEventListener {
+ static final String TAG = EmojiPalettesView.class.getSimpleName();
private static final boolean DEBUG_PAGER = false;
private final int mKeyBackgroundId;
private final int mEmojiFunctionalKeyBackgroundId;
- private final KeyboardLayoutSet mLayoutSet;
private final ColorStateList mTabLabelColor;
private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener;
private EmojiPalettesAdapter mEmojiPalettesAdapter;
+ private final EmojiLayoutParams mEmojiLayoutParams;
+ private TextView mAlphabetKeyLeft;
+ private TextView mAlphabetKeyRight;
private TabHost mTabHost;
private ViewPager mEmojiPager;
private int mCurrentPagerPosition = 0;
@@ -116,7 +123,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
"places",
"symbols",
"emoticons" };
- private static final int[] sCategoryIcon = new int[] {
+ private static final int[] sCategoryIcon = {
R.drawable.ic_emoji_recent_light,
R.drawable.ic_emoji_people_light,
R.drawable.ic_emoji_objects_light,
@@ -126,6 +133,14 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
0 };
private static final String[] sCategoryLabel =
{ null, null, null, null, null, null, ":-)" };
+ private static final int[] sAccessibilityDescriptionResourceIdsForCategories = {
+ R.string.spoken_descrption_emoji_category_recents,
+ R.string.spoken_descrption_emoji_category_people,
+ R.string.spoken_descrption_emoji_category_objects,
+ R.string.spoken_descrption_emoji_category_nature,
+ R.string.spoken_descrption_emoji_category_places,
+ R.string.spoken_descrption_emoji_category_symbols,
+ R.string.spoken_descrption_emoji_category_emoticons };
private static final int[] sCategoryElementId = {
KeyboardId.ELEMENT_EMOJI_RECENTS,
KeyboardId.ELEMENT_EMOJI_CATEGORY1,
@@ -135,6 +150,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
KeyboardId.ELEMENT_EMOJI_CATEGORY5,
KeyboardId.ELEMENT_EMOJI_CATEGORY6 };
private final SharedPreferences mPrefs;
+ private final Resources mRes;
private final int mMaxPageKeyCount;
private final KeyboardLayoutSet mLayoutSet;
private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
@@ -149,7 +165,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
public EmojiCategory(final SharedPreferences prefs, final Resources res,
final KeyboardLayoutSet layoutSet) {
mPrefs = prefs;
- mMaxPageKeyCount = res.getInteger(R.integer.emoji_keyboard_max_key_count);
+ mRes = res;
+ mMaxPageKeyCount = res.getInteger(R.integer.config_emoji_keyboard_max_page_key_count);
mLayoutSet = layoutSet;
for (int i = 0; i < sCategoryName.length; ++i) {
mCategoryNameToIdMap.put(sCategoryName[i], i);
@@ -174,7 +191,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
.loadRecentKeys(mCategoryKeyboardMap.values());
}
- private void addShownCategoryId(int categoryId) {
+ private void addShownCategoryId(final int categoryId) {
// Load a keyboard of categoryId
getKeyboard(categoryId, 0 /* cagetoryPageId */);
final CategoryProperties properties =
@@ -182,23 +199,27 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
mShownCategories.add(properties);
}
- public String getCategoryName(int categoryId, int categoryPageId) {
+ public String getCategoryName(final int categoryId, final int categoryPageId) {
return sCategoryName[categoryId] + "-" + categoryPageId;
}
- public int getCategoryId(String name) {
+ public int getCategoryId(final String name) {
final String[] strings = name.split("-");
return mCategoryNameToIdMap.get(strings[0]);
}
- public int getCategoryIcon(int categoryId) {
+ public int getCategoryIcon(final int categoryId) {
return sCategoryIcon[categoryId];
}
- public String getCategoryLabel(int categoryId) {
+ public String getCategoryLabel(final int categoryId) {
return sCategoryLabel[categoryId];
}
+ public String getAccessibilityDescription(final int categoryId) {
+ return mRes.getString(sAccessibilityDescriptionResourceIdsForCategories[categoryId]);
+ }
+
public ArrayList<CategoryProperties> getShownCategories() {
return mShownCategories;
}
@@ -211,7 +232,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return getCategoryPageSize(mCurrentCategoryId);
}
- public int getCategoryPageSize(int categoryId) {
+ public int getCategoryPageSize(final int categoryId) {
for (final CategoryProperties prop : mShownCategories) {
if (prop.mCategoryId == categoryId) {
return prop.mPageCount;
@@ -222,12 +243,12 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return 0;
}
- public void setCurrentCategoryId(int categoryId) {
+ public void setCurrentCategoryId(final int categoryId) {
mCurrentCategoryId = categoryId;
Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId);
}
- public void setCurrentCategoryPageId(int id) {
+ public void setCurrentCategoryPageId(final int id) {
mCurrentCategoryPageId = id;
}
@@ -244,7 +265,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return mCurrentCategoryId == CATEGORY_ID_RECENTS;
}
- public int getTabIdFromCategoryId(int categoryId) {
+ public int getTabIdFromCategoryId(final int categoryId) {
for (int i = 0; i < mShownCategories.size(); ++i) {
if (mShownCategories.get(i).mCategoryId == categoryId) {
return i;
@@ -255,7 +276,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
// Returns the view pager's page position for the categoryId
- public int getPageIdFromCategoryId(int categoryId) {
+ public int getPageIdFromCategoryId(final int categoryId) {
final int lastSavedCategoryPageId =
Settings.readLastTypedEmojiCategoryPageId(mPrefs, categoryId);
int sum = 0;
@@ -274,7 +295,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return getTabIdFromCategoryId(CATEGORY_ID_RECENTS);
}
- private int getCategoryPageCount(int categoryId) {
+ private int getCategoryPageCount(final int categoryId) {
final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
return (keyboard.getKeys().length - 1) / mMaxPageKeyCount + 1;
}
@@ -283,9 +304,9 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
// position. The category page id is numbered in each category. And the view page position
// is the position of the current shown page in the view pager which contains all pages of
// all categories.
- public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(int position) {
+ public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(final int position) {
int sum = 0;
- for (CategoryProperties properties : mShownCategories) {
+ for (final CategoryProperties properties : mShownCategories) {
final int temp = sum;
sum += properties.mPageCount;
if (sum > position) {
@@ -296,7 +317,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
// Returns a keyboard from the view pager's page position.
- public DynamicGridKeyboard getKeyboardFromPagePosition(int position) {
+ public DynamicGridKeyboard getKeyboardFromPagePosition(final int position) {
final Pair<Integer, Integer> categoryAndId =
getCategoryIdAndPageIdFromPagePosition(position);
if (categoryAndId != null) {
@@ -305,39 +326,41 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return null;
}
- public DynamicGridKeyboard getKeyboard(int categoryId, int id) {
- synchronized(mCategoryKeyboardMap) {
- final long key = (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
- final DynamicGridKeyboard kbd;
- if (!mCategoryKeyboardMap.containsKey(key)) {
- if (categoryId != CATEGORY_ID_RECENTS) {
- final Keyboard keyboard =
- mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
- final Key[][] sortedKeys = sortKeys(keyboard.getKeys(), mMaxPageKeyCount);
- for (int i = 0; i < sortedKeys.length; ++i) {
- final DynamicGridKeyboard tempKbd = new DynamicGridKeyboard(mPrefs,
- mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
- mMaxPageKeyCount, categoryId, i /* categoryPageId */);
- for (Key emojiKey : sortedKeys[i]) {
- if (emojiKey == null) {
- break;
- }
- tempKbd.addKeyLast(emojiKey);
- }
- mCategoryKeyboardMap.put((((long) categoryId)
- << Constants.MAX_INT_BIT_COUNT) | i, tempKbd);
+ private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) {
+ return (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
+ }
+
+ public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) {
+ synchronized (mCategoryKeyboardMap) {
+ final Long categotyKeyboardMapKey = getCategoryKeyboardMapKey(categoryId, id);
+ if (mCategoryKeyboardMap.containsKey(categotyKeyboardMapKey)) {
+ return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
+ }
+
+ if (categoryId == CATEGORY_ID_RECENTS) {
+ final DynamicGridKeyboard kbd = new DynamicGridKeyboard(mPrefs,
+ mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+ mMaxPageKeyCount, categoryId);
+ mCategoryKeyboardMap.put(categotyKeyboardMapKey, kbd);
+ return kbd;
+ }
+
+ final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
+ final Key[][] sortedKeys = sortKeysIntoPages(keyboard.getKeys(), mMaxPageKeyCount);
+ for (int pageId = 0; pageId < sortedKeys.length; ++pageId) {
+ final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs,
+ mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+ mMaxPageKeyCount, categoryId);
+ for (final Key emojiKey : sortedKeys[pageId]) {
+ if (emojiKey == null) {
+ break;
}
- kbd = mCategoryKeyboardMap.get(key);
- } else {
- kbd = new DynamicGridKeyboard(mPrefs,
- mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
- mMaxPageKeyCount, categoryId, 0 /* categoryPageId */);
- mCategoryKeyboardMap.put(key, kbd);
+ tempKeyboard.addKeyLast(emojiKey);
}
- } else {
- kbd = mCategoryKeyboardMap.get(key);
+ mCategoryKeyboardMap.put(
+ getCategoryKeyboardMapKey(categoryId, pageId), tempKeyboard);
}
- return kbd;
+ return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
}
}
@@ -349,29 +372,31 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return sum;
}
- private Key[][] sortKeys(Key[] inKeys, int maxPageCount) {
- Key[] keys = Arrays.copyOf(inKeys, inKeys.length);
- Arrays.sort(keys, 0, keys.length, new Comparator<Key>() {
- @Override
- public int compare(Key lhs, Key rhs) {
- final Rect lHitBox = lhs.getHitBox();
- final Rect rHitBox = rhs.getHitBox();
- if (lHitBox.top < rHitBox.top) {
- return -1;
- } else if (lHitBox.top > rHitBox.top) {
- return 1;
- }
- if (lHitBox.left < rHitBox.left) {
- return -1;
- } else if (lHitBox.left > rHitBox.left) {
- return 1;
- }
- if (lhs.getCode() == rhs.getCode()) {
- return 0;
- }
- return lhs.getCode() < rhs.getCode() ? -1 : 1;
+ private static Comparator<Key> EMOJI_KEY_COMPARATOR = new Comparator<Key>() {
+ @Override
+ public int compare(final Key lhs, final Key rhs) {
+ final Rect lHitBox = lhs.getHitBox();
+ final Rect rHitBox = rhs.getHitBox();
+ if (lHitBox.top < rHitBox.top) {
+ return -1;
+ } else if (lHitBox.top > rHitBox.top) {
+ return 1;
+ }
+ if (lHitBox.left < rHitBox.left) {
+ return -1;
+ } else if (lHitBox.left > rHitBox.left) {
+ return 1;
}
- });
+ if (lhs.getCode() == rhs.getCode()) {
+ return 0;
+ }
+ return lhs.getCode() < rhs.getCode() ? -1 : 1;
+ }
+ };
+
+ private static Key[][] sortKeysIntoPages(final Key[] inKeys, final int maxPageCount) {
+ final Key[] keys = Arrays.copyOf(inKeys, inKeys.length);
+ Arrays.sort(keys, 0, keys.length, EMOJI_KEY_COMPARATOR);
final int pageCount = (keys.length - 1) / maxPageCount + 1;
final Key[][] retval = new Key[pageCount][maxPageCount];
for (int i = 0; i < keys.length; ++i) {
@@ -404,12 +429,12 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
context, null /* editorInfo */);
final Resources res = context.getResources();
- final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
+ mEmojiLayoutParams = new EmojiLayoutParams(res);
builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype());
builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res),
- emojiLp.mEmojiKeyboardHeight);
- builder.setOptions(false, false, false /* lanuageSwitchKeyEnabled */);
- mLayoutSet = builder.build();
+ mEmojiLayoutParams.mEmojiKeyboardHeight);
+ builder.setOptions(false /* shortcutImeEnabled */, false /* showsVoiceInputKey */,
+ false /* languageSwitchKeyEnabled */);
mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context),
context.getResources(), builder.build());
mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener(context);
@@ -423,7 +448,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
final int width = ResourceUtils.getDefaultKeyboardWidth(res)
+ getPaddingLeft() + getPaddingRight();
final int height = ResourceUtils.getDefaultKeyboardHeight(res)
- + res.getDimensionPixelSize(R.dimen.suggestions_strip_height)
+ + res.getDimensionPixelSize(R.dimen.config_suggestions_strip_height)
+ getPaddingTop() + getPaddingBottom();
setMeasuredDimension(width, height);
}
@@ -436,12 +461,14 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate(
R.layout.emoji_keyboard_tab_icon, null);
iconView.setImageResource(mEmojiCategory.getCategoryIcon(categoryId));
+ iconView.setContentDescription(mEmojiCategory.getAccessibilityDescription(categoryId));
tspec.setIndicator(iconView);
}
if (mEmojiCategory.getCategoryLabel(categoryId) != null) {
final TextView textView = (TextView)LayoutInflater.from(getContext()).inflate(
R.layout.emoji_keyboard_tab_label, null);
textView.setText(mEmojiCategory.getCategoryLabel(categoryId));
+ textView.setContentDescription(mEmojiCategory.getAccessibilityDescription(categoryId));
textView.setTextColor(mTabLabelColor);
tspec.setIndicator(textView);
}
@@ -458,42 +485,60 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
mTabHost.setOnTabChangedListener(this);
mTabHost.getTabWidget().setStripEnabled(true);
- mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, mLayoutSet, this);
+ mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, this);
mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager);
mEmojiPager.setAdapter(mEmojiPalettesAdapter);
mEmojiPager.setOnPageChangeListener(this);
mEmojiPager.setOffscreenPageLimit(0);
- mEmojiPager.setPersistentDrawingCache(ViewPager.PERSISTENT_NO_CACHE);
- final Resources res = getResources();
- final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
- emojiLp.setPagerProperties(mEmojiPager);
+ mEmojiPager.setPersistentDrawingCache(PERSISTENT_NO_CACHE);
+ mEmojiLayoutParams.setPagerProperties(mEmojiPager);
mEmojiCategoryPageIndicatorView =
(EmojiCategoryPageIndicatorView)findViewById(R.id.emoji_category_page_id_view);
- emojiLp.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView);
+ mEmojiLayoutParams.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView);
setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */);
final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar);
- emojiLp.setActionBarProperties(actionBar);
+ mEmojiLayoutParams.setActionBarProperties(actionBar);
+ // deleteKey depends only on OnTouchListener.
final ImageView deleteKey = (ImageView)findViewById(R.id.emoji_keyboard_delete);
deleteKey.setTag(Constants.CODE_DELETE);
deleteKey.setOnTouchListener(mDeleteKeyOnTouchListener);
- final ImageView alphabetKey = (ImageView)findViewById(R.id.emoji_keyboard_alphabet);
- alphabetKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
- alphabetKey.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL);
- alphabetKey.setOnClickListener(this);
+
+ // {@link #mAlphabetKeyLeft}, {@link #mAlphabetKeyRight, and spaceKey depend on
+ // {@link View.OnClickListener} as well as {@link View.OnTouchListener}.
+ // {@link View.OnTouchListener} is used as the trigger of key-press, while
+ // {@link View.OnClickListener} is used as the trigger of key-release which does not occur
+ // if the event is canceled by moving off the finger from the view.
+ // The text on alphabet keys are set at
+ // {@link #startEmojiPalettes(String,int,float,Typeface)}.
+ mAlphabetKeyLeft = (TextView)findViewById(R.id.emoji_keyboard_alphabet_left);
+ mAlphabetKeyLeft.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
+ mAlphabetKeyLeft.setTag(Constants.CODE_ALPHA_FROM_EMOJI);
+ mAlphabetKeyLeft.setOnTouchListener(this);
+ mAlphabetKeyLeft.setOnClickListener(this);
+ mAlphabetKeyRight = (TextView)findViewById(R.id.emoji_keyboard_alphabet_right);
+ mAlphabetKeyRight.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
+ mAlphabetKeyRight.setTag(Constants.CODE_ALPHA_FROM_EMOJI);
+ mAlphabetKeyRight.setOnTouchListener(this);
+ mAlphabetKeyRight.setOnClickListener(this);
final ImageView spaceKey = (ImageView)findViewById(R.id.emoji_keyboard_space);
spaceKey.setBackgroundResource(mKeyBackgroundId);
spaceKey.setTag(Constants.CODE_SPACE);
+ spaceKey.setOnTouchListener(this);
spaceKey.setOnClickListener(this);
- emojiLp.setKeyProperties(spaceKey);
- final ImageView alphabetKey2 = (ImageView)findViewById(R.id.emoji_keyboard_alphabet2);
- alphabetKey2.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
- alphabetKey2.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL);
- alphabetKey2.setOnClickListener(this);
+ mEmojiLayoutParams.setKeyProperties(spaceKey);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(final MotionEvent ev) {
+ // Add here to the stack trace to nail down the {@link IllegalArgumentException} exception
+ // in MotionEvent that sporadically happens.
+ // TODO: Remove this override method once the issue has been addressed.
+ return super.dispatchTouchEvent(ev);
}
@Override
@@ -503,7 +548,6 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
updateEmojiCategoryPageIdView();
}
-
@Override
public void onPageSelected(final int position) {
final Pair<Integer, Integer> newPos =
@@ -522,6 +566,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
@Override
public void onPageScrolled(final int position, final float positionOffset,
final int positionOffsetPixels) {
+ mEmojiPalettesAdapter.onPageScrolled();
final Pair<Integer, Integer> newPos =
mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position);
final int newCategoryId = newPos.first;
@@ -541,41 +586,100 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
}
+ /**
+ * Called from {@link EmojiPageKeyboardView} through {@link android.view.View.OnTouchListener}
+ * interface to handle touch events from View-based elements such as the space bar.
+ * Note that this method is used only for observing {@link MotionEvent#ACTION_DOWN} to trigger
+ * {@link KeyboardActionListener#onPressKey}. {@link KeyboardActionListener#onReleaseKey} will
+ * be covered by {@link #onClick} as long as the event is not canceled.
+ */
@Override
- public void onClick(final View v) {
- if (v.getTag() instanceof Integer) {
- final int code = (Integer)v.getTag();
- registerCode(code);
+ public boolean onTouch(final View v, final MotionEvent event) {
+ if (event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ final Object tag = v.getTag();
+ if (!(tag instanceof Integer)) {
+ return false;
+ }
+ final int code = (Integer) tag;
+ mKeyboardActionListener.onPressKey(
+ code, 0 /* repeatCount */, true /* isSinglePointer */);
+ // It's important to return false here. Otherwise, {@link #onClick} and touch-down visual
+ // feedback stop working.
+ return false;
+ }
+
+ /**
+ * Called from {@link EmojiPageKeyboardView} through {@link android.view.View.OnClickListener}
+ * interface to handle non-canceled touch-up events from View-based elements such as the space
+ * bar.
+ */
+ @Override
+ public void onClick(View v) {
+ final Object tag = v.getTag();
+ if (!(tag instanceof Integer)) {
return;
}
+ final int code = (Integer) tag;
+ mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE,
+ false /* isKeyRepeat */);
+ mKeyboardActionListener.onReleaseKey(code, false /* withSliding */);
}
- private void registerCode(final int code) {
+ /**
+ * Called from {@link EmojiPageKeyboardView} through
+ * {@link com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView.OnKeyEventListener}
+ * interface to handle touch events from non-View-based elements such as Emoji buttons.
+ */
+ @Override
+ public void onPressKey(final Key key) {
+ final int code = key.getCode();
mKeyboardActionListener.onPressKey(code, 0 /* repeatCount */, true /* isSinglePointer */);
- mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE);
- mKeyboardActionListener.onReleaseKey(code, false /* withSliding */);
}
+ /**
+ * Called from {@link EmojiPageKeyboardView} through
+ * {@link com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView.OnKeyEventListener}
+ * interface to handle touch events from non-View-based elements such as Emoji buttons.
+ */
@Override
- public void onKeyClick(final Key key) {
+ public void onReleaseKey(final Key key) {
mEmojiPalettesAdapter.addRecentKey(key);
mEmojiCategory.saveLastTypedCategoryPage();
final int code = key.getCode();
if (code == Constants.CODE_OUTPUT_TEXT) {
mKeyboardActionListener.onTextInput(key.getOutputText());
- return;
+ } else {
+ mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE,
+ false /* isKeyRepeat */);
}
- registerCode(code);
+ mKeyboardActionListener.onReleaseKey(code, false /* withSliding */);
}
public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
- // TODO:
+ if (!enabled) return;
+ // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
+ setLayerType(LAYER_TYPE_HARDWARE, null);
}
- public void startEmojiPalettes() {
+ private static void setupAlphabetKey(final TextView alphabetKey, final String label,
+ final KeyDrawParams params) {
+ alphabetKey.setText(label);
+ alphabetKey.setTextColor(params.mTextColor);
+ alphabetKey.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mLabelSize);
+ alphabetKey.setTypeface(params.mTypeface);
+ }
+
+ public void startEmojiPalettes(final String switchToAlphaLabel,
+ final KeyVisualAttributes keyVisualAttr) {
if (DEBUG_PAGER) {
Log.d(TAG, "allocate emoji palettes memory " + mCurrentPagerPosition);
}
+ final KeyDrawParams params = new KeyDrawParams();
+ params.updateParams(mEmojiLayoutParams.getActionBarHeight(), keyVisualAttr);
+ setupAlphabetKey(mAlphabetKeyLeft, switchToAlphaLabel, params);
+ setupAlphabetKey(mAlphabetKeyRight, switchToAlphaLabel, params);
mEmojiPager.setAdapter(mEmojiPalettesAdapter);
mEmojiPager.setCurrentItem(mCurrentPagerPosition);
}
@@ -628,16 +732,15 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
private static class EmojiPalettesAdapter extends PagerAdapter {
- private final ScrollKeyboardView.OnKeyClickListener mListener;
+ private final EmojiPageKeyboardView.OnKeyEventListener mListener;
private final DynamicGridKeyboard mRecentsKeyboard;
- private final SparseArray<ScrollKeyboardView> mActiveKeyboardViews =
+ private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews =
CollectionUtils.newSparseArray();
private final EmojiCategory mEmojiCategory;
private int mActivePosition = 0;
public EmojiPalettesAdapter(final EmojiCategory emojiCategory,
- final KeyboardLayoutSet layoutSet,
- final ScrollKeyboardView.OnKeyClickListener listener) {
+ final EmojiPageKeyboardView.OnKeyEventListener listener) {
mEmojiCategory = emojiCategory;
mListener = listener;
mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0);
@@ -665,17 +768,28 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
}
+ public void onPageScrolled() {
+ // Make sure the delayed key-down event (highlight effect and haptic feedback) will be
+ // canceled.
+ final EmojiPageKeyboardView currentKeyboardView =
+ mActiveKeyboardViews.get(mActivePosition);
+ if (currentKeyboardView != null) {
+ currentKeyboardView.releaseCurrentKey();
+ }
+ }
+
@Override
public int getCount() {
return mEmojiCategory.getTotalPageCountOfAllCategories();
}
@Override
- public void setPrimaryItem(final View container, final int position, final Object object) {
+ public void setPrimaryItem(final ViewGroup container, final int position,
+ final Object object) {
if (mActivePosition == position) {
return;
}
- final ScrollKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
+ final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
if (oldKeyboardView != null) {
oldKeyboardView.releaseCurrentKey();
oldKeyboardView.deallocateMemory();
@@ -688,7 +802,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
if (DEBUG_PAGER) {
Log.d(TAG, "instantiate item: " + position);
}
- final ScrollKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
+ final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
if (oldKeyboardView != null) {
oldKeyboardView.deallocateMemory();
// This may be redundant but wanted to be safer..
@@ -697,18 +811,13 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
final Keyboard keyboard =
mEmojiCategory.getKeyboardFromPagePosition(position);
final LayoutInflater inflater = LayoutInflater.from(container.getContext());
- final View view = inflater.inflate(
+ final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate(
R.layout.emoji_keyboard_page, container, false /* attachToRoot */);
- final ScrollKeyboardView keyboardView = (ScrollKeyboardView)view.findViewById(
- R.id.emoji_keyboard_page);
keyboardView.setKeyboard(keyboard);
- keyboardView.setOnKeyClickListener(mListener);
- final ScrollViewWithNotifier scrollView = (ScrollViewWithNotifier)view.findViewById(
- R.id.emoji_keyboard_scroller);
- keyboardView.setScrollView(scrollView);
- container.addView(view);
+ keyboardView.setOnKeyEventListener(mListener);
+ container.addView(keyboardView);
mActiveKeyboardViews.put(position, keyboardView);
- return view;
+ return keyboardView;
}
@Override
@@ -722,7 +831,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
if (DEBUG_PAGER) {
Log.d(TAG, "destroy item: " + position + ", " + object.getClass().getSimpleName());
}
- final ScrollKeyboardView keyboardView = mActiveKeyboardViews.get(position);
+ final EmojiPageKeyboardView keyboardView = mActiveKeyboardViews.get(position);
if (keyboardView != null) {
keyboardView.deallocateMemory();
mActiveKeyboardViews.remove(position);
@@ -735,9 +844,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
}
- // TODO: Do the same things done in PointerTracker
private static class DeleteKeyOnTouchListener implements OnTouchListener {
- private static final long MAX_REPEAT_COUNT_TIME = 30 * DateUtils.SECOND_IN_MILLIS;
+ private static final long MAX_REPEAT_COUNT_TIME = TimeUnit.SECONDS.toMillis(30);
private final int mDeleteKeyPressedBackgroundColor;
private final long mKeyRepeatStartTimeout;
private final long mKeyRepeatInterval;
@@ -748,80 +856,117 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
res.getColor(R.color.emoji_key_pressed_background_color);
mKeyRepeatStartTimeout = res.getInteger(R.integer.config_key_repeat_start_timeout);
mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
+ mTimer = new CountDownTimer(MAX_REPEAT_COUNT_TIME, mKeyRepeatInterval) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ final long elapsed = MAX_REPEAT_COUNT_TIME - millisUntilFinished;
+ if (elapsed < mKeyRepeatStartTimeout) {
+ return;
+ }
+ onKeyRepeat();
+ }
+ @Override
+ public void onFinish() {
+ onKeyRepeat();
+ }
+ };
}
+ /** Key-repeat state. */
+ private static final int KEY_REPEAT_STATE_INITIALIZED = 0;
+ // The key is touched but auto key-repeat is not started yet.
+ private static final int KEY_REPEAT_STATE_KEY_DOWN = 1;
+ // At least one key-repeat event has already been triggered and the key is not released.
+ private static final int KEY_REPEAT_STATE_KEY_REPEAT = 2;
+
private KeyboardActionListener mKeyboardActionListener =
KeyboardActionListener.EMPTY_LISTENER;
- private DummyRepeatKeyRepeatTimer mTimer;
- private synchronized void startRepeat() {
- if (mTimer != null) {
- abortRepeat();
- }
- mTimer = new DummyRepeatKeyRepeatTimer();
- mTimer.start();
- }
+ // TODO: Do the same things done in PointerTracker
+ private final CountDownTimer mTimer;
+ private int mState = KEY_REPEAT_STATE_INITIALIZED;
+ private int mRepeatCount = 0;
- private synchronized void abortRepeat() {
- mTimer.abort();
- mTimer = null;
+ public void setKeyboardActionListener(final KeyboardActionListener listener) {
+ mKeyboardActionListener = listener;
}
- // TODO: Remove
- // This function is mimicking the repeat code in PointerTracker.
- // Specifically referring to PointerTracker#startRepeatKey and PointerTracker#onKeyRepeat.
- private class DummyRepeatKeyRepeatTimer extends Thread {
- public boolean mAborted = false;
-
- @Override
- public void run() {
- int repeatCount = 1;
- int timeCount = 0;
- while (timeCount < MAX_REPEAT_COUNT_TIME && !mAborted) {
- if (timeCount > mKeyRepeatStartTimeout) {
- pressDelete(repeatCount);
- }
- timeCount += mKeyRepeatInterval;
- ++repeatCount;
- try {
- Thread.sleep(mKeyRepeatInterval);
- } catch (InterruptedException e) {
- }
+ @Override
+ public boolean onTouch(final View v, final MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ onTouchDown(v);
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ final float x = event.getX();
+ final float y = event.getY();
+ if (x < 0.0f || v.getWidth() < x || y < 0.0f || v.getHeight() < y) {
+ // Stop generating key events once the finger moves away from the view area.
+ onTouchCanceled(v);
}
+ return true;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ onTouchUp(v);
+ return true;
}
-
- public void abort() {
- mAborted = true;
- }
+ return false;
}
- public void pressDelete(int repeatCount) {
+ private void handleKeyDown() {
mKeyboardActionListener.onPressKey(
- Constants.CODE_DELETE, repeatCount, true /* isSinglePointer */);
- mKeyboardActionListener.onCodeInput(
- Constants.CODE_DELETE, NOT_A_COORDINATE, NOT_A_COORDINATE);
+ Constants.CODE_DELETE, mRepeatCount, true /* isSinglePointer */);
+ }
+
+ private void handleKeyUp() {
+ mKeyboardActionListener.onCodeInput(Constants.CODE_DELETE,
+ NOT_A_COORDINATE, NOT_A_COORDINATE, false /* isKeyRepeat */);
mKeyboardActionListener.onReleaseKey(
Constants.CODE_DELETE, false /* withSliding */);
+ ++mRepeatCount;
}
- public void setKeyboardActionListener(KeyboardActionListener listener) {
- mKeyboardActionListener = listener;
+ private void onTouchDown(final View v) {
+ mTimer.cancel();
+ mRepeatCount = 0;
+ handleKeyDown();
+ v.setBackgroundColor(mDeleteKeyPressedBackgroundColor);
+ mState = KEY_REPEAT_STATE_KEY_DOWN;
+ mTimer.start();
}
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch(event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- v.setBackgroundColor(mDeleteKeyPressedBackgroundColor);
- pressDelete(0 /* repeatCount */);
- startRepeat();
- return true;
- case MotionEvent.ACTION_UP:
- v.setBackgroundColor(0);
- abortRepeat();
- return true;
+ private void onTouchUp(final View v) {
+ mTimer.cancel();
+ if (mState == KEY_REPEAT_STATE_KEY_DOWN) {
+ handleKeyUp();
+ }
+ v.setBackgroundColor(Color.TRANSPARENT);
+ mState = KEY_REPEAT_STATE_INITIALIZED;
+ }
+
+ private void onTouchCanceled(final View v) {
+ mTimer.cancel();
+ v.setBackgroundColor(Color.TRANSPARENT);
+ mState = KEY_REPEAT_STATE_INITIALIZED;
+ }
+
+ // Called by {@link #mTimer} in the UI thread as an auto key-repeat signal.
+ private void onKeyRepeat() {
+ switch (mState) {
+ case KEY_REPEAT_STATE_INITIALIZED:
+ // Basically this should not happen.
+ break;
+ case KEY_REPEAT_STATE_KEY_DOWN:
+ // Do not call {@link #handleKeyDown} here because it has already been called
+ // in {@link #onTouchDown}.
+ handleKeyUp();
+ mState = KEY_REPEAT_STATE_KEY_REPEAT;
+ break;
+ case KEY_REPEAT_STATE_KEY_REPEAT:
+ handleKeyDown();
+ handleKeyUp();
+ break;
}
- return false;
}
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index f7ec9509d..816a94300 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -22,14 +22,11 @@ import static com.android.inputmethod.latin.Constants.CODE_SHIFT;
import static com.android.inputmethod.latin.Constants.CODE_SWITCH_ALPHA_SYMBOL;
import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
-import android.util.Log;
-import android.util.Xml;
import com.android.inputmethod.keyboard.internal.KeyDrawParams;
import com.android.inputmethod.keyboard.internal.KeySpecParser;
@@ -43,9 +40,6 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.StringUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.util.Arrays;
import java.util.Locale;
@@ -53,8 +47,6 @@ import java.util.Locale;
* Class for describing the position and characteristics of a single key in the keyboard.
*/
public class Key implements Comparable<Key> {
- private static final String TAG = Key.class.getSimpleName();
-
/**
* The key code (unicode or custom code) that this key generates.
*/
@@ -84,10 +76,16 @@ public class Key implements Comparable<Key> {
private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800;
private static final int LABEL_FLAGS_WITH_ICON_LEFT = 0x1000;
private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000;
+ // The bit to calculate the ratio of key label width against key width. If autoXScale bit is on
+ // and autoYScale bit is off, the key label may be shrunk only for X-direction.
+ // If both autoXScale and autoYScale bits are on, the key label text size may be auto scaled.
private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
- private static final int LABEL_FLAGS_PRESERVE_CASE = 0x8000;
- private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x10000;
- private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x20000;
+ private static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000;
+ private static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE
+ | LABEL_FLAGS_AUTO_Y_SCALE;
+ private static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000;
+ private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000;
+ private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000;
private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000;
private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000;
@@ -139,8 +137,6 @@ public class Key implements Comparable<Key> {
private final OptionalAttributes mOptionalAttributes;
- private static final int DEFAULT_TEXT_COLOR = 0xFFFFFFFF;
-
private static final class OptionalAttributes {
/** Text to output when pressed. This can be multiple characters, like ".com" */
public final String mOutputText;
@@ -185,22 +181,15 @@ public class Key implements Comparable<Key> {
private boolean mEnabled = true;
/**
- * This constructor is being used only for keys in more keys keyboard.
- */
- public Key(final KeyboardParams params, final MoreKeySpec moreKeySpec, final int x, final int y,
- final int width, final int height, final int labelFlags) {
- this(params, moreKeySpec.mLabel, null, moreKeySpec.mIconId, moreKeySpec.mCode,
- moreKeySpec.mOutputText, x, y, width, height, labelFlags, BACKGROUND_TYPE_NORMAL);
- }
-
- /**
- * This constructor is being used only for key in popup suggestions pane.
+ * Constructor for a key on <code>MoreKeyKeyboard</code>, on <code>MoreSuggestions</code>,
+ * and in a <GridRows/>.
*/
- public Key(final KeyboardParams params, final String label, final String hintLabel,
- final int iconId, final int code, final String outputText, final int x, final int y,
- final int width, final int height, final int labelFlags, final int backgroundType) {
- mHeight = height - params.mVerticalGap;
- mWidth = width - params.mHorizontalGap;
+ public Key(final String label, final int iconId, final int code, final String outputText,
+ 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;
mHintLabel = hintLabel;
mLabelFlags = labelFlags;
mBackgroundType = backgroundType;
@@ -215,7 +204,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 + params.mHorizontalGap / 2;
+ mX = x + horizontalGap / 2;
mY = y;
mHitBox.set(x, y, x + width + 1, y + height);
mKeyVisualAttributes = null;
@@ -224,25 +213,22 @@ public class Key implements Comparable<Key> {
}
/**
- * Create a key with the given top-left coordinate and extract its attributes from the XML
- * parser.
- * @param res resources associated with the caller's context
+ * Create a key with the given top-left coordinate and extract its attributes from a key
+ * specification string, Key attribute array, key style, and etc.
+ *
+ * @param keySpec the key specification.
+ * @param keyAttr the Key XML attributes array.
+ * @param keyStyle the {@link KeyStyle} of this key.
* @param params the keyboard building parameters.
* @param row the row that this key belongs to. row's x-coordinate will be the right edge of
* this key.
- * @param parser the XML parser containing the attributes for this key
- * @throws XmlPullParserException
*/
- public Key(final Resources res, final KeyboardParams params, final KeyboardRow row,
- final XmlPullParser parser) throws XmlPullParserException {
+ public Key(final String keySpec, final TypedArray keyAttr, final KeyStyle style,
+ final KeyboardParams params, final KeyboardRow row) {
final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
final int rowHeight = row.getRowHeight();
mHeight = rowHeight - params.mVerticalGap;
- final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard_Key);
-
- final KeyStyle style = params.mKeyStyles.getKeyStyle(keyAttr, parser);
final float keyXPos = row.getKeyX(keyAttr);
final float keyWidth = row.getKeyWidth(keyAttr, keyXPos);
final int keyYPos = row.getKeyY();
@@ -264,12 +250,6 @@ public class Key implements Comparable<Key> {
R.styleable.Keyboard_Key_visualInsetsLeft, baseWidth, baseWidth, 0));
final int visualInsetsRight = Math.round(keyAttr.getFraction(
R.styleable.Keyboard_Key_visualInsetsRight, baseWidth, baseWidth, 0));
- mIconId = KeySpecParser.getIconId(style.getString(keyAttr,
- R.styleable.Keyboard_Key_keyIcon));
- final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
- R.styleable.Keyboard_Key_keyIconDisabled));
- final int previewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
- R.styleable.Keyboard_Key_keyIconPreview));
mLabelFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
| row.getDefaultKeyLabelFlags();
@@ -281,19 +261,19 @@ public class Key implements Comparable<Key> {
int moreKeysColumn = style.getInt(keyAttr,
R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn);
int value;
- if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
+ if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
moreKeysColumn = value & MORE_KEYS_COLUMN_MASK;
}
- if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
+ if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK);
}
- if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
+ if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
moreKeysColumn |= MORE_KEYS_FLAGS_HAS_LABELS;
}
- if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
+ if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
}
- if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
+ if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
moreKeysColumn |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY;
}
mMoreKeysColumnAndFlags = moreKeysColumn;
@@ -305,21 +285,25 @@ public class Key implements Comparable<Key> {
additionalMoreKeys = style.getStringArray(keyAttr,
R.styleable.Keyboard_Key_additionalMoreKeys);
}
- moreKeys = KeySpecParser.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys);
+ moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys);
if (moreKeys != null) {
actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
mMoreKeys = new MoreKeySpec[moreKeys.length];
for (int i = 0; i < moreKeys.length; i++) {
- mMoreKeys[i] = new MoreKeySpec(
- moreKeys[i], needsToUpperCase, locale, params.mCodesSet);
+ mMoreKeys[i] = new MoreKeySpec(moreKeys[i], needsToUpperCase, locale);
}
} else {
mMoreKeys = null;
}
mActionFlags = actionFlags;
- final int code = KeySpecParser.parseCode(style.getString(keyAttr,
- R.styleable.Keyboard_Key_code), params.mCodesSet, CODE_UNSPECIFIED);
+ mIconId = KeySpecParser.getIconId(keySpec);
+ final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
+ R.styleable.Keyboard_Key_keyIconDisabled));
+ final int previewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
+ R.styleable.Keyboard_Key_keyIconPreview));
+
+ final int code = KeySpecParser.getCode(keySpec);
if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) {
mLabel = params.mId.mCustomActionLabel;
} else if (code >= Character.MIN_SUPPLEMENTARY_CODE_POINT) {
@@ -328,25 +312,24 @@ public class Key implements Comparable<Key> {
// code point nor as a surrogate pair.
mLabel = new StringBuilder().appendCodePoint(code).toString();
} else {
- mLabel = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr,
- R.styleable.Keyboard_Key_keyLabel), needsToUpperCase, locale);
+ mLabel = StringUtils.toUpperCaseOfStringForLocale(
+ KeySpecParser.getLabel(keySpec), needsToUpperCase, locale);
}
if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) {
mHintLabel = null;
} else {
- mHintLabel = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr,
+ mHintLabel = StringUtils.toUpperCaseOfStringForLocale(style.getString(keyAttr,
R.styleable.Keyboard_Key_keyHintLabel), needsToUpperCase, locale);
}
- String outputText = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr,
- R.styleable.Keyboard_Key_keyOutputText), needsToUpperCase, locale);
+ String outputText = StringUtils.toUpperCaseOfStringForLocale(
+ KeySpecParser.getOutputText(keySpec), needsToUpperCase, locale);
// Choose the first letter of the label as primary code if not specified.
if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText)
&& !TextUtils.isEmpty(mLabel)) {
if (StringUtils.codePointCount(mLabel) == 1) {
// Use the first letter of the hint label if shiftedLetterActivated flag is
// specified.
- if (hasShiftedLetterHint() && isShiftedLetterActivated()
- && !TextUtils.isEmpty(mHintLabel)) {
+ if (hasShiftedLetterHint() && isShiftedLetterActivated()) {
mCode = mHintLabel.codePointAt(0);
} else {
mCode = mLabel.codePointAt(0);
@@ -365,24 +348,20 @@ public class Key implements Comparable<Key> {
mCode = CODE_OUTPUT_TEXT;
}
} else {
- mCode = KeySpecParser.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale);
+ mCode = StringUtils.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale);
}
- final int altCode = KeySpecParser.toUpperCaseOfCodeForLocale(
- KeySpecParser.parseCode(style.getString(keyAttr,
- R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED),
- needsToUpperCase, locale);
+ final int altCodeInAttr = KeySpecParser.parseCode(
+ style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), CODE_UNSPECIFIED);
+ final int altCode = StringUtils.toUpperCaseOfCodeForLocale(
+ altCodeInAttr, needsToUpperCase, locale);
mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode,
disabledIconId, previewIconId, visualInsetsLeft, visualInsetsRight);
mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
- keyAttr.recycle();
mHashCode = computeHashCode(this);
- if (hasShiftedLetterHint() && TextUtils.isEmpty(mHintLabel)) {
- Log.w(TAG, "hasShiftedLetterHint specified without keyHintLabel: " + this);
- }
}
/**
- * Copy constructor.
+ * Copy constructor for DynamicGridKeyboard.GridKey.
*
* @param key the original key.
*/
@@ -604,22 +583,7 @@ public class Key implements Comparable<Key> {
}
public final int selectTextColor(final KeyDrawParams params) {
- if (isShiftedLetterActivated()) {
- return params.mTextInactivatedColor;
- }
- if (params.mTextColorStateList == null) {
- return DEFAULT_TEXT_COLOR;
- }
- final int[] state;
- // TODO: Hack!!!!!!!! Consider having a new attribute for the functional text labels.
- // Currently, we distinguish "input key" from "functional key" by checking the
- // length of the label( > 1) and "functional" attributes (= true).
- if (mLabel != null && mLabel.length() > 1) {
- state = getCurrentDrawableState();
- } else {
- state = KEY_STATE_NORMAL;
- }
- return params.mTextColorStateList.getColorForState(state, DEFAULT_TEXT_COLOR);
+ return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor;
}
public final int selectHintTextSize(final KeyDrawParams params) {
@@ -687,7 +651,8 @@ public class Key implements Comparable<Key> {
}
public final boolean hasShiftedLetterHint() {
- return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0;
+ return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0
+ && !TextUtils.isEmpty(mHintLabel);
}
public final boolean hasHintLabel() {
@@ -702,12 +667,17 @@ public class Key implements Comparable<Key> {
return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0;
}
- public final boolean needsXScale() {
+ public final boolean needsAutoXScale() {
return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
}
- public final boolean isShiftedLetterActivated() {
- return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0;
+ public final boolean needsAutoScale() {
+ return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE;
+ }
+
+ private final boolean isShiftedLetterActivated() {
+ return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0
+ && !TextUtils.isEmpty(mHintLabel);
}
public final int getMoreKeysColumn() {
@@ -746,10 +716,14 @@ public class Key implements Comparable<Key> {
return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED;
}
+ public int getIconId() {
+ return mIconId;
+ }
+
public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
final OptionalAttributes attrs = mOptionalAttributes;
final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED;
- final int iconId = mEnabled ? mIconId : disabledIconId;
+ final int iconId = mEnabled ? getIconId() : disabledIconId;
final Drawable icon = iconSet.getIconDrawable(iconId);
if (icon != null) {
icon.setAlpha(alpha);
@@ -761,7 +735,7 @@ public class Key implements Comparable<Key> {
final OptionalAttributes attrs = mOptionalAttributes;
final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED;
return previewIconId != ICON_UNDEFINED
- ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId);
+ ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(getIconId());
}
public int getWidth() {
@@ -928,9 +902,9 @@ public class Key implements Comparable<Key> {
}
public static class Spacer extends Key {
- public Spacer(final Resources res, final KeyboardParams params, final KeyboardRow row,
- final XmlPullParser parser) throws XmlPullParserException {
- super(res, params, row, parser);
+ public Spacer(final TypedArray keyAttr, final KeyStyle keyStyle,
+ final KeyboardParams params, final KeyboardRow row) {
+ super(null /* keySpec */, keyAttr, keyStyle, params, row);
}
/**
@@ -938,8 +912,9 @@ public class Key implements Comparable<Key> {
*/
protected Spacer(final KeyboardParams params, final int x, final int y, final int width,
final int height) {
- super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED,
- null, x, y, width, height, 0, BACKGROUND_TYPE_EMPTY);
+ super(null /* label */, ICON_UNDEFINED, CODE_UNSPECIFIED, null /* outputText */,
+ null /* hintLabel */, 0 /* labelFlags */, BACKGROUND_TYPE_EMPTY, x, y, width,
+ height, params.mHorizontalGap, params.mVerticalGap);
}
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index befb6fa92..87368d4ef 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -16,9 +16,9 @@
package com.android.inputmethod.keyboard;
-import com.android.inputmethod.latin.Constants;
-
-
+/**
+ * This class handles key detection.
+ */
public class KeyDetector {
private final int mKeyHysteresisDistanceSquared;
private final int mKeyHysteresisDistanceForSlidingModifierSquared;
@@ -27,31 +27,27 @@ public class KeyDetector {
private int mCorrectionX;
private int mCorrectionY;
- /**
- * This class handles key detection.
- *
- * @param keyHysteresisDistance if the pointer movement distance is smaller than this, the
- * movement will not be handled as meaningful movement. The unit is pixel.
- */
- public KeyDetector(float keyHysteresisDistance) {
- this(keyHysteresisDistance, keyHysteresisDistance);
+ public KeyDetector() {
+ this(0.0f /* keyHysteresisDistance */, 0.0f /* keyHysteresisDistanceForSlidingModifier */);
}
/**
- * This class handles key detection.
+ * Key detection object constructor with key hysteresis distances.
*
* @param keyHysteresisDistance if the pointer movement distance is smaller than this, the
* movement will not be handled as meaningful movement. The unit is pixel.
* @param keyHysteresisDistanceForSlidingModifier the same parameter for sliding input that
* starts from a modifier key such as shift and symbols key.
*/
- public KeyDetector(float keyHysteresisDistance, float keyHysteresisDistanceForSlidingModifier) {
+ public KeyDetector(final float keyHysteresisDistance,
+ final float keyHysteresisDistanceForSlidingModifier) {
mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
mKeyHysteresisDistanceForSlidingModifierSquared = (int)(
keyHysteresisDistanceForSlidingModifier * keyHysteresisDistanceForSlidingModifier);
}
- public void setKeyboard(Keyboard keyboard, float correctionX, float correctionY) {
+ public void setKeyboard(final Keyboard keyboard, final float correctionX,
+ final float correctionY) {
if (keyboard == null) {
throw new NullPointerException();
}
@@ -60,28 +56,25 @@ public class KeyDetector {
mKeyboard = keyboard;
}
- public int getKeyHysteresisDistanceSquared(boolean isSlidingFromModifier) {
+ public int getKeyHysteresisDistanceSquared(final boolean isSlidingFromModifier) {
return isSlidingFromModifier
? mKeyHysteresisDistanceForSlidingModifierSquared : mKeyHysteresisDistanceSquared;
}
- public int getTouchX(int x) {
+ public int getTouchX(final int x) {
return x + mCorrectionX;
}
// TODO: Remove vertical correction.
- public int getTouchY(int y) {
+ public int getTouchY(final int y) {
return y + mCorrectionY;
}
public Keyboard getKeyboard() {
- if (mKeyboard == null) {
- throw new IllegalStateException("keyboard isn't set");
- }
return mKeyboard;
}
- public boolean alwaysAllowsSlidingInput() {
+ public boolean alwaysAllowsKeySelectionByDraggingFinger() {
return false;
}
@@ -92,7 +85,10 @@ public class KeyDetector {
* @param y The y-coordinate of a touch point
* @return the key that the touch point hits.
*/
- public Key detectHitKey(int x, int y) {
+ public Key detectHitKey(final int x, final int y) {
+ if (mKeyboard == null) {
+ return null;
+ }
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
@@ -117,20 +113,4 @@ public class KeyDetector {
}
return primaryKey;
}
-
- public static String printableCode(Key key) {
- return key != null ? Constants.printableCode(key.getCode()) : "none";
- }
-
- public static String printableCodes(int[] codes) {
- final StringBuilder sb = new StringBuilder();
- boolean addDelimiter = false;
- for (final int code : codes) {
- if (code == Constants.NOT_A_CODE) break;
- if (addDelimiter) sb.append(", ");
- sb.append(Constants.printableCode(code));
- addDelimiter = true;
- }
- return "[" + sb + "]";
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index bc1383aff..4fd3bac2f 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -23,6 +23,7 @@ import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
/**
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
@@ -217,4 +218,20 @@ public class Keyboard {
final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));
return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
}
+
+ public int[] getCoordinates(final int[] codePoints) {
+ final int length = codePoints.length;
+ final int[] coordinates = CoordinateUtils.newCoordinateArray(length);
+ for (int i = 0; i < length; ++i) {
+ final Key key = getKey(codePoints[i]);
+ if (null != key) {
+ CoordinateUtils.setXYInArray(coordinates, i,
+ key.getX() + key.getWidth() / 2, key.getY() + key.getHeight() / 2);
+ } else {
+ CoordinateUtils.setXYInArray(coordinates, i,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+ }
+ }
+ return coordinates;
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index dc760e685..c565866b7 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -53,8 +53,10 @@ public interface KeyboardActionListener {
* {@link PointerTracker} or so, the value should be
* {@link Constants#NOT_A_COORDINATE}.If it's called on insertion from the
* suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
+ * @param isKeyRepeat true if this is a key repeat, false otherwise
*/
- public void onCodeInput(int primaryCode, int x, int y);
+ // TODO: change this to send an Event object instead
+ public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat);
/**
* Sends a string of characters to the listener.
@@ -107,7 +109,7 @@ public interface KeyboardActionListener {
@Override
public void onReleaseKey(int primaryCode, boolean withSliding) {}
@Override
- public void onCodeInput(int primaryCode, int x, int y) {}
+ public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat) {}
@Override
public void onTextInput(String text) {}
@Override
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 736f13ed6..02beb3f11 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -70,8 +70,7 @@ public final class KeyboardId {
public final int mElementId;
private final EditorInfo mEditorInfo;
public final boolean mClobberSettingsKey;
- public final boolean mShortcutKeyEnabled;
- public final boolean mShortcutKeyOnSymbols;
+ public final boolean mSupportsSwitchingToShortcutIme;
public final boolean mLanguageSwitchKeyEnabled;
public final String mCustomActionLabel;
public final boolean mHasShortcutKey;
@@ -87,17 +86,11 @@ public final class KeyboardId {
mElementId = elementId;
mEditorInfo = params.mEditorInfo;
mClobberSettingsKey = params.mNoSettingsKey;
- mShortcutKeyEnabled = params.mVoiceKeyEnabled;
- mShortcutKeyOnSymbols = mShortcutKeyEnabled && !params.mVoiceKeyOnMain;
+ mSupportsSwitchingToShortcutIme = params.mSupportsSwitchingToShortcutIme;
mLanguageSwitchKeyEnabled = params.mLanguageSwitchKeyEnabled;
mCustomActionLabel = (mEditorInfo.actionLabel != null)
? mEditorInfo.actionLabel.toString() : null;
- final boolean alphabetMayHaveShortcutKey = isAlphabetKeyboard(elementId)
- && !mShortcutKeyOnSymbols;
- final boolean symbolsMayHaveShortcutKey = (elementId == KeyboardId.ELEMENT_SYMBOLS)
- && mShortcutKeyOnSymbols;
- mHasShortcutKey = mShortcutKeyEnabled
- && (alphabetMayHaveShortcutKey || symbolsMayHaveShortcutKey);
+ mHasShortcutKey = mSupportsSwitchingToShortcutIme && params.mShowsVoiceInputKey;
mHashCode = computeHashCode(this);
}
@@ -110,8 +103,8 @@ public final class KeyboardId {
id.mHeight,
id.passwordInput(),
id.mClobberSettingsKey,
- id.mShortcutKeyEnabled,
- id.mShortcutKeyOnSymbols,
+ id.mSupportsSwitchingToShortcutIme,
+ id.mHasShortcutKey,
id.mLanguageSwitchKeyEnabled,
id.isMultiLine(),
id.imeAction(),
@@ -131,8 +124,8 @@ public final class KeyboardId {
&& other.mHeight == mHeight
&& other.passwordInput() == passwordInput()
&& other.mClobberSettingsKey == mClobberSettingsKey
- && other.mShortcutKeyEnabled == mShortcutKeyEnabled
- && other.mShortcutKeyOnSymbols == mShortcutKeyOnSymbols
+ && other.mSupportsSwitchingToShortcutIme == mSupportsSwitchingToShortcutIme
+ && other.mHasShortcutKey == mHasShortcutKey
&& other.mLanguageSwitchKeyEnabled == mLanguageSwitchKeyEnabled
&& other.isMultiLine() == isMultiLine()
&& other.imeAction() == imeAction()
@@ -186,21 +179,20 @@ 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%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,
modeName(mMode),
- imeAction(),
- (navigateNext() ? "navigateNext" : ""),
- (navigatePrevious() ? "navigatePrevious" : ""),
+ actionName(imeAction()),
+ (navigateNext() ? " navigateNext" : ""),
+ (navigatePrevious() ? " navigatePrevious" : ""),
(mClobberSettingsKey ? " clobberSettingsKey" : ""),
(passwordInput() ? " passwordInput" : ""),
- (mShortcutKeyEnabled ? " shortcutKeyEnabled" : ""),
- (mShortcutKeyOnSymbols ? " shortcutKeyOnSymbols" : ""),
+ (mSupportsSwitchingToShortcutIme ? " supportsSwitchingToShortcutIme" : ""),
(mHasShortcutKey ? " hasShortcutKey" : ""),
(mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
- (isMultiLine() ? "isMultiLine" : "")
+ (isMultiLine() ? " isMultiLine" : "")
);
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 1eccdf341..cde5091c4 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -20,7 +20,6 @@ import static com.android.inputmethod.latin.Constants.ImeOption.FORCE_ASCII;
import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE;
import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT;
import static com.android.inputmethod.latin.Constants.ImeOption.NO_SETTINGS_KEY;
-import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.ASCII_CAPABLE;
import android.content.Context;
import android.content.res.Resources;
@@ -34,6 +33,7 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.keyboard.internal.KeysCache;
@@ -105,10 +105,10 @@ public final class KeyboardLayoutSet {
int mMode;
EditorInfo mEditorInfo;
boolean mDisableTouchPositionCorrectionDataForTest;
- boolean mVoiceKeyEnabled;
- // TODO: Remove mVoiceKeyOnMain when it's certainly confirmed that we no longer show
- // the voice input key on the symbol layout
- boolean mVoiceKeyOnMain;
+ boolean mIsPasswordField;
+ boolean mSupportsSwitchingToShortcutIme;
+ boolean mShowsVoiceInputKey;
+ boolean mNoMicrophoneKey;
boolean mNoSettingsKey;
boolean mLanguageSwitchKeyEnabled;
InputMethodSubtype mSubtype;
@@ -221,16 +221,24 @@ public final class KeyboardLayoutSet {
private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo();
- public Builder(final Context context, final EditorInfo editorInfo) {
+ public Builder(final Context context, final EditorInfo ei) {
mContext = context;
mPackageName = context.getPackageName();
mResources = context.getResources();
final Params params = mParams;
+ final EditorInfo editorInfo = (ei != null) ? ei : EMPTY_EDITOR_INFO;
params.mMode = getKeyboardMode(editorInfo);
- params.mEditorInfo = (editorInfo != null) ? editorInfo : EMPTY_EDITOR_INFO;
+ params.mEditorInfo = editorInfo;
+ params.mIsPasswordField = InputTypeUtils.isPasswordInputType(editorInfo.inputType);
+ @SuppressWarnings("deprecation")
+ final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
+ null, NO_MICROPHONE_COMPAT, editorInfo);
+ params.mNoMicrophoneKey = InputAttributes.inPrivateImeOptions(
+ mPackageName, NO_MICROPHONE, editorInfo)
+ || deprecatedNoMicrophone;
params.mNoSettingsKey = InputAttributes.inPrivateImeOptions(
- mPackageName, NO_SETTINGS_KEY, params.mEditorInfo);
+ mPackageName, NO_SETTINGS_KEY, editorInfo);
}
public Builder setKeyboardGeometry(final int keyboardWidth, final int keyboardHeight) {
@@ -240,7 +248,7 @@ public final class KeyboardLayoutSet {
}
public Builder setSubtype(final InputMethodSubtype subtype) {
- final boolean asciiCapable = subtype.containsExtraValueKey(ASCII_CAPABLE);
+ final boolean asciiCapable = InputMethodSubtypeCompatUtils.isAsciiCapable(subtype);
@SuppressWarnings("deprecation")
final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions(
mPackageName, FORCE_ASCII, mParams.mEditorInfo);
@@ -261,18 +269,11 @@ public final class KeyboardLayoutSet {
return this;
}
- // TODO: Remove mVoiceKeyOnMain when it's certainly confirmed that we no longer show
- // the voice input key on the symbol layout
- public Builder setOptions(final boolean voiceKeyEnabled, final boolean voiceKeyOnMain,
- final boolean languageSwitchKeyEnabled) {
- @SuppressWarnings("deprecation")
- final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
- null, NO_MICROPHONE_COMPAT, mParams.mEditorInfo);
- final boolean noMicrophone = InputAttributes.inPrivateImeOptions(
- mPackageName, NO_MICROPHONE, mParams.mEditorInfo)
- || deprecatedNoMicrophone;
- mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone;
- mParams.mVoiceKeyOnMain = voiceKeyOnMain;
+ public Builder setOptions(final boolean isShortcutImeEnabled,
+ final boolean showsVoiceInputKey, final boolean languageSwitchKeyEnabled) {
+ mParams.mSupportsSwitchingToShortcutIme =
+ isShortcutImeEnabled && !mParams.mNoMicrophoneKey && !mParams.mIsPasswordField;
+ mParams.mShowsVoiceInputKey = showsVoiceInputKey;
mParams.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled;
return this;
}
@@ -368,9 +369,6 @@ public final class KeyboardLayoutSet {
}
private static int getKeyboardMode(final EditorInfo editorInfo) {
- if (editorInfo == null)
- return KeyboardId.MODE_TEXT;
-
final int inputType = editorInfo.inputType;
final int variation = inputType & InputType.TYPE_MASK_VARIATION;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 5abc9ab38..dcf7f7472 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -30,6 +30,7 @@ import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
import com.android.inputmethod.keyboard.internal.KeyboardState;
+import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
import com.android.inputmethod.latin.InputView;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger;
@@ -37,35 +38,12 @@ 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.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.utils.ResourceUtils;
public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
- static final class KeyboardTheme {
- public final int mThemeId;
- public final int mStyleId;
-
- // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
- // in values/style.xml.
- public KeyboardTheme(final int themeId, final int styleId) {
- mThemeId = themeId;
- mStyleId = styleId;
- }
- }
-
- public static final int THEME_INDEX_ICS = 0;
- public static final int THEME_INDEX_GB = 1;
- public static final int THEME_INDEX_KLP = 2;
- public static final int THEME_INDEX_DEFAULT = THEME_INDEX_KLP;
- public static final KeyboardTheme[] KEYBOARD_THEMES = {
- new KeyboardTheme(THEME_INDEX_ICS, R.style.KeyboardTheme_ICS),
- new KeyboardTheme(THEME_INDEX_GB, R.style.KeyboardTheme_GB),
- new KeyboardTheme(THEME_INDEX_KLP, R.style.KeyboardTheme_KLP),
- };
-
private SubtypeSwitcher mSubtypeSwitcher;
private SharedPreferences mPrefs;
@@ -74,18 +52,20 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
private MainKeyboardView mKeyboardView;
private EmojiPalettesView mEmojiPalettesView;
private LatinIME mLatinIME;
- private Resources mResources;
private boolean mIsHardwareAcceleratedDrawingEnabled;
private KeyboardState mState;
private KeyboardLayoutSet mKeyboardLayoutSet;
+ // TODO: The following {@link KeyboardTextsSet} should be in {@link KeyboardLayoutSet}.
+ private final KeyboardTextsSet mKeyboardTextsSet = new KeyboardTextsSet();
+ private SettingsValues mCurrentSettingsValues;
/** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
* what user actually typed. */
private boolean mIsAutoCorrectionActive;
- private KeyboardTheme mKeyboardTheme = KEYBOARD_THEMES[THEME_INDEX_DEFAULT];
+ private KeyboardTheme mKeyboardTheme = KeyboardTheme.getDefaultKeyboardTheme();
private Context mThemeContext;
private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
@@ -105,7 +85,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
private void initInternal(final LatinIME latinIme, final SharedPreferences prefs) {
mLatinIME = latinIme;
- mResources = latinIme.getResources();
mPrefs = prefs;
mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mState = new KeyboardState(this);
@@ -115,25 +94,12 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
public void updateKeyboardTheme() {
final boolean themeUpdated = updateKeyboardThemeAndContextThemeWrapper(
- mLatinIME, getKeyboardTheme(mLatinIME, mPrefs));
+ mLatinIME, KeyboardTheme.getKeyboardTheme(mPrefs));
if (themeUpdated && mKeyboardView != null) {
mLatinIME.setInputView(onCreateInputView(mIsHardwareAcceleratedDrawingEnabled));
}
}
- private static KeyboardTheme getKeyboardTheme(final Context context,
- final SharedPreferences prefs) {
- final Resources res = context.getResources();
- final int index = Settings.readKeyboardThemeIndex(prefs, res);
- if (index >= 0 && index < KEYBOARD_THEMES.length) {
- return KEYBOARD_THEMES[index];
- }
- final int defaultThemeIndex = Settings.resetAndGetDefaultKeyboardThemeIndex(prefs, res);
- Log.w(TAG, "Illegal keyboard theme in preference: " + index + ", default to "
- + defaultThemeIndex);
- return KEYBOARD_THEMES[defaultThemeIndex];
- }
-
private boolean updateKeyboardThemeAndContextThemeWrapper(final Context context,
final KeyboardTheme keyboardTheme) {
if (mThemeContext == null || mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) {
@@ -145,7 +111,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
return false;
}
- public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues) {
+ public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues,
+ final int currentAutoCapsState, final int currentRecapitalizeState) {
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
mThemeContext, editorInfo);
final Resources res = mThemeContext.getResources();
@@ -154,12 +121,14 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
builder.setOptions(
- settingsValues.isVoiceKeyEnabled(editorInfo),
- true /* always show a voice key on the main keyboard */,
+ mSubtypeSwitcher.isShortcutImeEnabled(),
+ settingsValues.mShowsVoiceInputKey,
settingsValues.isLanguageSwitchKeyEnabled());
mKeyboardLayoutSet = builder.build();
+ mCurrentSettingsValues = settingsValues;
try {
- mState.onLoadKeyboard();
+ mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState);
+ mKeyboardTextsSet.setLocale(mSubtypeSwitcher.getCurrentSubtypeLocale(), mThemeContext);
} catch (KeyboardLayoutSetException e) {
Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
@@ -187,18 +156,25 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
final MainKeyboardView keyboardView = mKeyboardView;
final Keyboard oldKeyboard = keyboardView.getKeyboard();
keyboardView.setKeyboard(keyboard);
- mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
+ mCurrentInputView.setKeyboardTopPadding(keyboard.mTopPadding);
keyboardView.setKeyPreviewPopupEnabled(
- Settings.readKeyPreviewPopupEnabled(mPrefs, mResources),
- Settings.readKeyPreviewPopupDismissDelay(mPrefs, mResources));
+ mCurrentSettingsValues.mKeyPreviewPopupOn,
+ mCurrentSettingsValues.mKeyPreviewPopupDismissDelay);
+ keyboardView.setKeyPreviewAnimationParams(
+ mCurrentSettingsValues.mKeyPreviewShowUpStartScale,
+ mCurrentSettingsValues.mKeyPreviewShowUpDuration,
+ mCurrentSettingsValues.mKeyPreviewDismissEndScale,
+ mCurrentSettingsValues.mKeyPreviewDismissDuration);
keyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
keyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
final boolean subtypeChanged = (oldKeyboard == null)
|| !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
- final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage(
- keyboard.mId.mLocale);
- keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage,
- RichInputMethodManager.getInstance().hasMultipleEnabledIMEsOrSubtypes(true));
+ final int languageOnSpacebarFormatType = mSubtypeSwitcher.getLanguageOnSpacebarFormatType(
+ keyboard.mId.mSubtype);
+ final boolean hasMultipleEnabledIMEsOrSubtypes = RichInputMethodManager.getInstance()
+ .hasMultipleEnabledIMEsOrSubtypes(true /* shouldIncludeAuxiliarySubtypes */);
+ keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, languageOnSpacebarFormatType,
+ hasMultipleEnabledIMEsOrSubtypes);
}
public Keyboard getKeyboard() {
@@ -208,30 +184,26 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
return null;
}
- /**
- * Update keyboard shift state triggered by connected EditText status change.
- */
- public void updateShiftState() {
- mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(),
- mLatinIME.getCurrentRecapitalizeState());
- }
-
// TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
// when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal().
- public void resetKeyboardStateToAlphabet() {
- mState.onResetKeyboardStateToAlphabet();
+ public void resetKeyboardStateToAlphabet(final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
+ mState.onResetKeyboardStateToAlphabet(currentAutoCapsState, currentRecapitalizeState);
}
- public void onPressKey(final int code, final boolean isSinglePointer) {
- mState.onPressKey(code, isSinglePointer, mLatinIME.getCurrentAutoCapsState());
+ public void onPressKey(final int code, final boolean isSinglePointer,
+ final int currentAutoCapsState, final int currentRecapitalizeState) {
+ mState.onPressKey(code, isSinglePointer, currentAutoCapsState, currentRecapitalizeState);
}
- public void onReleaseKey(final int code, final boolean withSliding) {
- mState.onReleaseKey(code, withSliding);
+ public void onReleaseKey(final int code, final boolean withSliding,
+ final int currentAutoCapsState, final int currentRecapitalizeState) {
+ mState.onReleaseKey(code, withSliding, currentAutoCapsState, currentRecapitalizeState);
}
- public void onFinishSlidingInput() {
- mState.onFinishSlidingInput();
+ public void onFinishSlidingInput(final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
+ mState.onFinishSlidingInput(currentAutoCapsState, currentRecapitalizeState);
}
// Implements {@link KeyboardState.SwitchActions}.
@@ -280,7 +252,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
@Override
public void setEmojiKeyboard() {
mMainKeyboardFrame.setVisibility(View.GONE);
- mEmojiPalettesView.startEmojiPalettes();
+ mEmojiPalettesView.startEmojiPalettes(
+ mKeyboardTextsSet.getText(KeyboardTextsSet.SWITCH_TO_ALPHA_KEY_LABEL),
+ mKeyboardView.getKeyVisualAttribute());
mEmojiPalettesView.setVisibility(View.VISIBLE);
}
@@ -290,11 +264,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
}
- // Implements {@link KeyboardState.SwitchActions}.
- @Override
- public void requestUpdatingShiftState() {
- mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(),
- mLatinIME.getCurrentRecapitalizeState());
+ // Future method for requesting an updating to the shift state.
+ public void requestUpdatingShiftState(final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
+ mState.onUpdateShiftState(currentAutoCapsState, currentRecapitalizeState);
}
// Implements {@link KeyboardState.SwitchActions}.
@@ -325,12 +298,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) {
- mState.onCodeInput(code, mLatinIME.getCurrentAutoCapsState());
- }
-
- private boolean isShowingMainKeyboard() {
- return null != mKeyboardView && mKeyboardView.isShown();
+ public void onCodeInput(final int code, final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
+ mState.onCodeInput(code, currentAutoCapsState, currentRecapitalizeState);
}
public boolean isShowingEmojiPalettes() {
@@ -365,10 +335,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
}
- public boolean isShowingMainKeyboardOrEmojiPalettes() {
- return isShowingMainKeyboard() || isShowingEmojiPalettes();
- }
-
public View onCreateInputView(final boolean isHardwareAcceleratedDrawingEnabled) {
if (mKeyboardView != null) {
mKeyboardView.closing();
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
new file mode 100644
index 000000000..4db72ad4d
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
@@ -0,0 +1,81 @@
+/*
+ * 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.keyboard;
+
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.settings.Settings;
+
+public final class KeyboardTheme {
+ private static final String TAG = KeyboardTheme.class.getSimpleName();
+
+ public static final int THEME_ID_ICS = 0;
+ public static final int THEME_ID_KLP = 2;
+ private static final int DEFAULT_THEME_ID = THEME_ID_KLP;
+
+ private static final KeyboardTheme[] KEYBOARD_THEMES = {
+ new KeyboardTheme(THEME_ID_ICS, R.style.KeyboardTheme_ICS),
+ new KeyboardTheme(THEME_ID_KLP, R.style.KeyboardTheme_KLP),
+ };
+
+ public final int mThemeId;
+ public final int mStyleId;
+
+ // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
+ // in values/style.xml.
+ public KeyboardTheme(final int themeId, final int styleId) {
+ mThemeId = themeId;
+ mStyleId = styleId;
+ }
+
+ private static KeyboardTheme searchKeyboardTheme(final int themeId) {
+ // TODO: This search algorithm isn't optimal if there are many themes.
+ for (final KeyboardTheme theme : KEYBOARD_THEMES) {
+ if (theme.mThemeId == themeId) {
+ return theme;
+ }
+ }
+ return null;
+ }
+
+ public static KeyboardTheme getDefaultKeyboardTheme() {
+ return searchKeyboardTheme(DEFAULT_THEME_ID);
+ }
+
+ public static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs) {
+ final String themeIdString = prefs.getString(Settings.PREF_KEYBOARD_LAYOUT, null);
+ if (themeIdString == null) {
+ return getDefaultKeyboardTheme();
+ }
+ try {
+ final int themeId = Integer.parseInt(themeIdString);
+ final KeyboardTheme theme = searchKeyboardTheme(themeId);
+ if (theme != null) {
+ return theme;
+ }
+ Log.w(TAG, "Unknown keyboard theme in preference: " + themeIdString);
+ } catch (final NumberFormatException e) {
+ Log.w(TAG, "Illegal keyboard theme in preference: " + themeIdString);
+ }
+ // Reset preference to default value.
+ final String defaultThemeIdString = Integer.toString(DEFAULT_THEME_ID);
+ prefs.edit().putString(Settings.PREF_KEYBOARD_LAYOUT, defaultThemeIdString).apply();
+ return getDefaultKeyboardTheme();
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 5578713a0..18e51d392 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -113,9 +113,6 @@ public class KeyboardView extends View {
private final Canvas mOffscreenCanvas = new Canvas();
private final Paint mPaint = new Paint();
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
- private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
- private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
-
public KeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
@@ -149,6 +146,10 @@ public class KeyboardView extends View {
mPaint.setAntiAlias(true);
}
+ public KeyVisualAttributes getKeyVisualAttribute() {
+ return mKeyVisualAttributes;
+ }
+
private static void blendAlpha(final Paint paint, final int alpha) {
final int color = paint.getColor();
paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE,
@@ -322,7 +323,7 @@ public class KeyboardView extends View {
params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
if (!key.isSpacer()) {
- onDrawKeyBackground(key, canvas);
+ onDrawKeyBackground(key, canvas, mKeyBackground);
}
onDrawKeyTopVisuals(key, canvas, paint, params);
@@ -330,14 +331,14 @@ public class KeyboardView extends View {
}
// Draw key background.
- protected void onDrawKeyBackground(final Key key, final Canvas canvas) {
+ protected void onDrawKeyBackground(final Key key, final Canvas canvas,
+ final Drawable background) {
final Rect padding = mKeyBackgroundPadding;
final int bgWidth = key.getDrawWidth() + padding.left + padding.right;
final int bgHeight = key.getHeight() + padding.top + padding.bottom;
final int bgX = -padding.left;
final int bgY = -padding.top;
final int[] drawableState = key.getCurrentDrawableState();
- final Drawable background = mKeyBackground;
background.setState(drawableState);
final Rect bounds = background.getBounds();
if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
@@ -370,10 +371,8 @@ public class KeyboardView extends View {
if (label != null) {
paint.setTypeface(key.selectTypeface(params));
paint.setTextSize(key.selectTextSize(params));
- final float labelCharHeight = TypefaceUtils.getCharHeight(
- KEY_LABEL_REFERENCE_CHAR, paint);
- final float labelCharWidth = TypefaceUtils.getCharWidth(
- KEY_LABEL_REFERENCE_CHAR, paint);
+ final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
+ final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
// Vertical label text alignment.
final float baseline = centerY + labelCharHeight / 2.0f;
@@ -391,12 +390,12 @@ public class KeyboardView extends View {
positionX = centerX - labelCharWidth * 7.0f / 4.0f;
paint.setTextAlign(Align.LEFT);
} else if (key.hasLabelWithIconLeft() && icon != null) {
- labelWidth = TypefaceUtils.getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+ labelWidth = TypefaceUtils.getStringWidth(label, paint) + icon.getIntrinsicWidth()
+ LABEL_ICON_MARGIN * keyWidth;
positionX = centerX + labelWidth / 2.0f;
paint.setTextAlign(Align.RIGHT);
} else if (key.hasLabelWithIconRight() && icon != null) {
- labelWidth = TypefaceUtils.getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+ labelWidth = TypefaceUtils.getStringWidth(label, paint) + icon.getIntrinsicWidth()
+ LABEL_ICON_MARGIN * keyWidth;
positionX = centerX - labelWidth / 2.0f;
paint.setTextAlign(Align.LEFT);
@@ -404,9 +403,15 @@ public class KeyboardView extends View {
positionX = centerX;
paint.setTextAlign(Align.CENTER);
}
- if (key.needsXScale()) {
- paint.setTextScaleX(Math.min(1.0f,
- (keyWidth * MAX_LABEL_RATIO) / TypefaceUtils.getLabelWidth(label, paint)));
+ if (key.needsAutoXScale()) {
+ final float ratio = Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) /
+ TypefaceUtils.getStringWidth(label, paint));
+ if (key.needsAutoScale()) {
+ final float autoSize = paint.getTextSize() * ratio;
+ paint.setTextSize(autoSize);
+ } else {
+ paint.setTextScaleX(ratio);
+ }
}
paint.setColor(key.selectTextColor(params));
@@ -451,36 +456,35 @@ public class KeyboardView extends View {
// TODO: Should add a way to specify type face for hint letters
paint.setTypeface(Typeface.DEFAULT_BOLD);
blendAlpha(paint, params.mAnimAlpha);
+ final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
+ final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
+ final KeyVisualAttributes visualAttr = key.getVisualAttributes();
+ final float adjustmentY = (visualAttr == null) ? 0.0f
+ : visualAttr.mHintLabelVerticalAdjustment * labelCharHeight;
final float hintX, hintY;
if (key.hasHintLabel()) {
// The hint label is placed just right of the key label. Used mainly on
// "phone number" layout.
// TODO: Generalize the following calculations.
- hintX = positionX
- + TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2.0f;
- hintY = centerY
- + TypefaceUtils.getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
+ hintX = positionX + labelCharWidth * 2.0f;
+ hintY = centerY + labelCharHeight / 2.0f;
paint.setTextAlign(Align.LEFT);
} else if (key.hasShiftedLetterHint()) {
// The hint label is placed at top-right corner of the key. Used mainly on tablet.
- hintX = keyWidth - mKeyShiftedLetterHintPadding
- - TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
+ hintX = keyWidth - mKeyShiftedLetterHintPadding - labelCharWidth / 2.0f;
paint.getFontMetrics(mFontMetrics);
hintY = -mFontMetrics.top;
paint.setTextAlign(Align.CENTER);
} else { // key.hasHintLetter()
// The hint letter is placed at top-right corner of the key. Used mainly on phone.
- final float keyNumericHintLabelReferenceCharWidth =
- TypefaceUtils.getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint);
- final float keyHintLabelStringWidth =
- TypefaceUtils.getStringWidth(hintLabel, paint);
+ final float hintDigitWidth = TypefaceUtils.getReferenceDigitWidth(paint);
+ final float hintLabelWidth = TypefaceUtils.getStringWidth(hintLabel, paint);
hintX = keyWidth - mKeyHintLetterPadding
- - Math.max(keyNumericHintLabelReferenceCharWidth, keyHintLabelStringWidth)
- / 2.0f;
+ - Math.max(hintDigitWidth, hintLabelWidth) / 2.0f;
hintY = -paint.ascent();
paint.setTextAlign(Align.CENTER);
}
- canvas.drawText(hintLabel, 0, hintLabel.length(), hintX, hintY, paint);
+ canvas.drawText(hintLabel, 0, hintLabel.length(), hintX, hintY + adjustmentY, paint);
if (LatinImeLogger.sVISUALDEBUG) {
final Paint line = new Paint();
@@ -530,7 +534,7 @@ public class KeyboardView extends View {
paint.setColor(params.mHintLabelColor);
paint.setTextAlign(Align.CENTER);
final float hintX = keyWidth - mKeyHintLetterPadding
- - TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
+ - TypefaceUtils.getReferenceCharWidth(paint) / 2.0f;
final float hintY = keyHeight - mKeyPopupHintLetterPadding;
canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
@@ -582,6 +586,7 @@ public class KeyboardView extends View {
paint.setTypeface(mKeyDrawParams.mTypeface);
paint.setTextSize(mKeyDrawParams.mLabelSize);
} else {
+ paint.setColor(key.selectTextColor(mKeyDrawParams));
paint.setTypeface(key.selectTypeface(mKeyDrawParams));
paint.setTextSize(key.selectTextSize(mKeyDrawParams));
}
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 13db47004..1cafd4162 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -28,18 +28,12 @@ import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
-import android.os.Message;
-import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.SparseArray;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodSubtype;
import android.widget.TextView;
@@ -47,15 +41,17 @@ import android.widget.TextView;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
import com.android.inputmethod.annotations.ExternallyReferenced;
-import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
-import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
-import com.android.inputmethod.keyboard.internal.GestureFloatingPreviewText;
-import com.android.inputmethod.keyboard.internal.GestureTrailsPreview;
+import com.android.inputmethod.keyboard.internal.DrawingHandler;
+import com.android.inputmethod.keyboard.internal.DrawingPreviewPlacerView;
+import com.android.inputmethod.keyboard.internal.GestureFloatingTextDrawingPreview;
+import com.android.inputmethod.keyboard.internal.GestureTrailsDrawingPreview;
import com.android.inputmethod.keyboard.internal.KeyDrawParams;
+import com.android.inputmethod.keyboard.internal.KeyPreviewChoreographer;
import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
+import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
-import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
-import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview;
+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.LatinImeLogger;
import com.android.inputmethod.latin.R;
@@ -64,11 +60,9 @@ import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.DebugSettings;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
-import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import com.android.inputmethod.latin.utils.SpacebarLanguageUtils;
import com.android.inputmethod.latin.utils.TypefaceUtils;
import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
-import com.android.inputmethod.latin.utils.ViewLayoutUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.util.WeakHashMap;
@@ -78,9 +72,10 @@ import java.util.WeakHashMap;
*
* @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled
* @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon
- * @attr ref R.styleable#MainKeyboardView_spacebarTextRatio
- * @attr ref R.styleable#MainKeyboardView_spacebarTextColor
- * @attr ref R.styleable#MainKeyboardView_spacebarTextShadowColor
+ * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextRatio
+ * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextColor
+ * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowColor
+ * @attr ref R.styleable#MainKeyboardView_spacebarBackground
* @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
* @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator
* @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator
@@ -88,7 +83,7 @@ import java.util.WeakHashMap;
* @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance
* @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime
* @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance
- * @attr ref R.styleable#MainKeyboardView_slidingKeyInputEnable
+ * @attr ref R.styleable#MainKeyboardView_keySelectionByDraggingFinger
* @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout
* @attr ref R.styleable#MainKeyboardView_keyRepeatInterval
* @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
@@ -114,26 +109,27 @@ import java.util.WeakHashMap;
* @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold
* @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
*/
-public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
- PointerTracker.DrawingProxy, MoreKeysPanel.Controller {
+public final class MainKeyboardView extends KeyboardView implements PointerTracker.DrawingProxy,
+ MoreKeysPanel.Controller, DrawingHandler.Callbacks, TimerHandler.Callbacks {
private static final String TAG = MainKeyboardView.class.getSimpleName();
/** Listener for {@link KeyboardActionListener}. */
private KeyboardActionListener mKeyboardActionListener;
- /* Space key and its icons */
+ /* Space key and its icon and background. */
private Key mSpaceKey;
- private Drawable mSpaceIcon;
+ private Drawable mSpacebarIcon;
+ private final Drawable mSpacebarBackground;
// Stuff to draw language name on spacebar.
private final int mLanguageOnSpacebarFinalAlpha;
private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
- private boolean mNeedsToDisplayLanguage;
+ private int mLanguageOnSpacebarFormatType;
private boolean mHasMultipleEnabledIMEsOrSubtypes;
private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE;
- private final float mSpacebarTextRatio;
- private float mSpacebarTextSize;
- private final int mSpacebarTextColor;
- private final int mSpacebarTextShadowColor;
+ private final float mLanguageOnSpacebarTextRatio;
+ private float mLanguageOnSpacebarTextSize;
+ private final int mLanguageOnSpacebarTextColor;
+ private final int mLanguageOnSpacebarTextShadowColor;
// The minimum x-scale to fit the language name on spacebar.
private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f;
// Stuff to draw auto correction LED on spacebar.
@@ -143,25 +139,21 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private static final int SPACE_LED_LENGTH_PERCENT = 80;
// Stuff to draw altCodeWhileTyping keys.
- private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
- private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
+ private final ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
+ private final ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE;
- // Preview placer view
- private final PreviewPlacerView mPreviewPlacerView;
+ // Drawing preview placer view
+ private final DrawingPreviewPlacerView mDrawingPreviewPlacerView;
private final int[] mOriginCoords = CoordinateUtils.newInstance();
- private final GestureFloatingPreviewText mGestureFloatingPreviewText;
- private final GestureTrailsPreview mGestureTrailsPreview;
- private final SlidingKeyInputPreview mSlidingKeyInputPreview;
+ private final GestureFloatingTextDrawingPreview mGestureFloatingTextDrawingPreview;
+ private final GestureTrailsDrawingPreview mGestureTrailsDrawingPreview;
+ private final SlidingKeyInputDrawingPreview mSlidingKeyInputDrawingPreview;
// Key preview
- private final int mKeyPreviewLayoutId;
- private final int mKeyPreviewOffset;
- private final int mKeyPreviewHeight;
- private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
- private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
- private boolean mShowKeyPreviewPopup = true;
- private int mKeyPreviewLingerTimeout;
+ private static final boolean FADE_OUT_KEY_TOP_LETTER_WHEN_KEY_IS_PRESSED = false;
+ private final KeyPreviewDrawParams mKeyPreviewDrawParams;
+ private final KeyPreviewChoreographer mKeyPreviewChoreographer;
// More keys keyboard
private final Paint mBackgroundDimAlphaPaint = new Paint();
@@ -178,244 +170,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
// TODO: Make this parameter customizable by user via settings.
private int mGestureFloatingPreviewTextLingerTimeout;
- private KeyDetector mKeyDetector;
+ private final KeyDetector mKeyDetector;
private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
- private final KeyTimerHandler mKeyTimerHandler;
+ private final TimerHandler mKeyTimerHandler;
private final int mLanguageOnSpacebarHorizontalMargin;
- private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView>
- implements TimerProxy {
- private static final int MSG_TYPING_STATE_EXPIRED = 0;
- private static final int MSG_REPEAT_KEY = 1;
- private static final int MSG_LONGPRESS_KEY = 2;
- private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 3;
- private static final int MSG_UPDATE_BATCH_INPUT = 4;
-
- private final int mIgnoreAltCodeKeyTimeout;
- private final int mGestureRecognitionUpdateTime;
-
- public KeyTimerHandler(final MainKeyboardView outerInstance,
- final TypedArray mainKeyboardViewAttr) {
- super(outerInstance);
-
- mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
- mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0);
- }
-
- @Override
- public void handleMessage(final Message msg) {
- final MainKeyboardView keyboardView = getOuterInstance();
- if (keyboardView == null) {
- return;
- }
- final PointerTracker tracker = (PointerTracker) msg.obj;
- switch (msg.what) {
- case MSG_TYPING_STATE_EXPIRED:
- startWhileTypingFadeinAnimation(keyboardView);
- break;
- case MSG_REPEAT_KEY:
- tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
- break;
- case MSG_LONGPRESS_KEY:
- keyboardView.onLongPress(tracker);
- break;
- case MSG_UPDATE_BATCH_INPUT:
- tracker.updateBatchInputByTimer(SystemClock.uptimeMillis());
- startUpdateBatchInputTimer(tracker);
- break;
- }
- }
-
- @Override
- public void startKeyRepeatTimer(final PointerTracker tracker, final int repeatCount,
- final int delay) {
- final Key key = tracker.getKey();
- if (key == null || delay == 0) {
- return;
- }
- sendMessageDelayed(
- obtainMessage(MSG_REPEAT_KEY, key.getCode(), repeatCount, tracker), delay);
- }
-
- public void cancelKeyRepeatTimer() {
- removeMessages(MSG_REPEAT_KEY);
- }
-
- // TODO: Suppress layout changes in key repeat mode
- public boolean isInKeyRepeat() {
- return hasMessages(MSG_REPEAT_KEY);
- }
-
- @Override
- public void startLongPressTimer(final PointerTracker tracker, final int delay) {
- cancelLongPressTimer();
- if (delay <= 0) return;
- sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
- }
-
- @Override
- public void cancelLongPressTimer() {
- removeMessages(MSG_LONGPRESS_KEY);
- }
-
- private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel,
- final ObjectAnimator animatorToStart) {
- if (animatorToCancel == null || animatorToStart == null) {
- // TODO: Stop using null as a no-operation animator.
- return;
- }
- float startFraction = 0.0f;
- if (animatorToCancel.isStarted()) {
- animatorToCancel.cancel();
- startFraction = 1.0f - animatorToCancel.getAnimatedFraction();
- }
- final long startTime = (long)(animatorToStart.getDuration() * startFraction);
- animatorToStart.start();
- animatorToStart.setCurrentPlayTime(startTime);
- }
-
- private static void startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView) {
- cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator,
- keyboardView.mAltCodeKeyWhileTypingFadeinAnimator);
- }
-
- private static void startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView) {
- cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator,
- keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator);
- }
-
- @Override
- public void startTypingStateTimer(final Key typedKey) {
- if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) {
- return;
- }
-
- final boolean isTyping = isTypingState();
- removeMessages(MSG_TYPING_STATE_EXPIRED);
- final MainKeyboardView keyboardView = getOuterInstance();
-
- // When user hits the space or the enter key, just cancel the while-typing timer.
- final int typedCode = typedKey.getCode();
- if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) {
- if (isTyping) {
- startWhileTypingFadeinAnimation(keyboardView);
- }
- return;
- }
-
- sendMessageDelayed(
- obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
- if (isTyping) {
- return;
- }
- startWhileTypingFadeoutAnimation(keyboardView);
- }
-
- @Override
- public boolean isTypingState() {
- return hasMessages(MSG_TYPING_STATE_EXPIRED);
- }
-
- @Override
- public void startDoubleTapShiftKeyTimer() {
- sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP_SHIFT_KEY),
- ViewConfiguration.getDoubleTapTimeout());
- }
-
- @Override
- public void cancelDoubleTapShiftKeyTimer() {
- removeMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
- }
-
- @Override
- public boolean isInDoubleTapShiftKeyTimeout() {
- return hasMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
- }
-
- @Override
- public void cancelKeyTimers() {
- cancelKeyRepeatTimer();
- cancelLongPressTimer();
- }
-
- @Override
- public void startUpdateBatchInputTimer(final PointerTracker tracker) {
- if (mGestureRecognitionUpdateTime <= 0) {
- return;
- }
- removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
- sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker),
- mGestureRecognitionUpdateTime);
- }
-
- @Override
- public void cancelUpdateBatchInputTimer(final PointerTracker tracker) {
- removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
- }
-
- @Override
- public void cancelAllUpdateBatchInputTimers() {
- removeMessages(MSG_UPDATE_BATCH_INPUT);
- }
-
- public void cancelAllMessages() {
- cancelKeyTimers();
- cancelAllUpdateBatchInputTimers();
- }
- }
-
- private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
-
- public static class DrawingHandler extends StaticInnerHandlerWrapper<MainKeyboardView> {
- private static final int MSG_DISMISS_KEY_PREVIEW = 0;
- private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
-
- public DrawingHandler(final MainKeyboardView outerInstance) {
- super(outerInstance);
- }
-
- @Override
- public void handleMessage(final Message msg) {
- final MainKeyboardView mainKeyboardView = getOuterInstance();
- if (mainKeyboardView == null) return;
- final PointerTracker tracker = (PointerTracker) msg.obj;
- switch (msg.what) {
- case MSG_DISMISS_KEY_PREVIEW:
- final TextView previewText = mainKeyboardView.mKeyPreviewTexts.get(
- tracker.mPointerId);
- if (previewText != null) {
- previewText.setVisibility(INVISIBLE);
- }
- break;
- case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
- mainKeyboardView.showGestureFloatingPreviewText(SuggestedWords.EMPTY);
- break;
- }
- }
-
- public void dismissKeyPreview(final long delay, final PointerTracker tracker) {
- sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
- }
-
- public void cancelDismissKeyPreview(final PointerTracker tracker) {
- removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
- }
-
- private void cancelAllDismissKeyPreviews() {
- removeMessages(MSG_DISMISS_KEY_PREVIEW);
- }
-
- public void dismissGestureFloatingPreviewText(final long delay) {
- sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
- }
-
- public void cancelAllMessages() {
- cancelAllDismissKeyPreviews();
- }
- }
+ private final DrawingHandler mDrawingHandler =
+ new DrawingHandler(this);
public MainKeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.mainKeyboardViewStyle);
@@ -424,7 +186,26 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
- PointerTracker.init(getResources());
+ mDrawingPreviewPlacerView = new DrawingPreviewPlacerView(context, attrs);
+
+ final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
+ attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
+ final int ignoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
+ final int gestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0);
+ mKeyTimerHandler = new TimerHandler(
+ this, ignoreAltCodeKeyTimeout, gestureRecognitionUpdateTime);
+
+ final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
+ R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f);
+ final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
+ R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f);
+ mKeyDetector = new KeyDetector(
+ keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
+
+ PointerTracker.init(mainKeyboardViewAttr, mKeyTimerHandler, this /* DrawingProxy */);
+
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final boolean forceNonDistinctMultitouch = prefs.getBoolean(
DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false);
@@ -434,24 +215,22 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null
: new NonDistinctMultitouchHelper();
- mPreviewPlacerView = new PreviewPlacerView(context, attrs);
-
- final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
- attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
final int backgroundDimAlpha = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_backgroundDimAlpha, 0);
mBackgroundDimAlphaPaint.setColor(Color.BLACK);
mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha);
+ mSpacebarBackground = mainKeyboardViewAttr.getDrawable(
+ R.styleable.MainKeyboardView_spacebarBackground);
mAutoCorrectionSpacebarLedEnabled = mainKeyboardViewAttr.getBoolean(
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
mAutoCorrectionSpacebarLedIcon = mainKeyboardViewAttr.getDrawable(
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
- mSpacebarTextRatio = mainKeyboardViewAttr.getFraction(
- R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
- mSpacebarTextColor = mainKeyboardViewAttr.getColor(
- R.styleable.MainKeyboardView_spacebarTextColor, 0);
- mSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
- R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
+ mLanguageOnSpacebarTextRatio = mainKeyboardViewAttr.getFraction(
+ R.styleable.MainKeyboardView_languageOnSpacebarTextRatio, 1, 1, 1.0f);
+ mLanguageOnSpacebarTextColor = mainKeyboardViewAttr.getColor(
+ R.styleable.MainKeyboardView_languageOnSpacebarTextColor, 0);
+ mLanguageOnSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
+ R.styleable.MainKeyboardView_languageOnSpacebarTextShadowColor, 0);
mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha,
Constants.Color.ALPHA_OPAQUE);
@@ -462,24 +241,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId(
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
- final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f);
- final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f);
- mKeyDetector = new KeyDetector(
- keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
- mKeyTimerHandler = new KeyTimerHandler(this, mainKeyboardViewAttr);
- mKeyPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
- R.styleable.MainKeyboardView_keyPreviewOffset, 0);
- mKeyPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
- R.styleable.MainKeyboardView_keyPreviewHeight, 0);
- mKeyPreviewLingerTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0);
- mKeyPreviewLayoutId = mainKeyboardViewAttr.getResourceId(
- R.styleable.MainKeyboardView_keyPreviewLayout, 0);
- if (mKeyPreviewLayoutId == 0) {
- mShowKeyPreviewPopup = false;
- }
+ mKeyPreviewDrawParams = new KeyPreviewDrawParams(mainKeyboardViewAttr);
+ mKeyPreviewChoreographer = new KeyPreviewChoreographer(mKeyPreviewDrawParams);
+
final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId(
R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0);
mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean(
@@ -487,19 +251,18 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
- PointerTracker.setParameters(mainKeyboardViewAttr);
- mGestureFloatingPreviewText = new GestureFloatingPreviewText(
- mPreviewPlacerView, mainKeyboardViewAttr);
- mPreviewPlacerView.addPreview(mGestureFloatingPreviewText);
+ mGestureFloatingTextDrawingPreview = new GestureFloatingTextDrawingPreview(
+ mDrawingPreviewPlacerView, mainKeyboardViewAttr);
+ mDrawingPreviewPlacerView.addPreview(mGestureFloatingTextDrawingPreview);
- mGestureTrailsPreview = new GestureTrailsPreview(
- mPreviewPlacerView, mainKeyboardViewAttr);
- mPreviewPlacerView.addPreview(mGestureTrailsPreview);
+ mGestureTrailsDrawingPreview = new GestureTrailsDrawingPreview(
+ mDrawingPreviewPlacerView, mainKeyboardViewAttr);
+ mDrawingPreviewPlacerView.addPreview(mGestureTrailsDrawingPreview);
- mSlidingKeyInputPreview = new SlidingKeyInputPreview(
- mPreviewPlacerView, mainKeyboardViewAttr);
- mPreviewPlacerView.addPreview(mSlidingKeyInputPreview);
+ mSlidingKeyInputDrawingPreview = new SlidingKeyInputDrawingPreview(
+ mDrawingPreviewPlacerView, mainKeyboardViewAttr);
+ mDrawingPreviewPlacerView.addPreview(mSlidingKeyInputDrawingPreview);
mainKeyboardViewAttr.recycle();
mMoreKeysKeyboardContainer = LayoutInflater.from(getContext())
@@ -513,14 +276,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
- mLanguageOnSpacebarHorizontalMargin =
- (int) getResources().getDimension(R.dimen.language_on_spacebar_horizontal_margin);
+ mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension(
+ R.dimen.config_language_on_spacebar_horizontal_margin);
}
@Override
public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
super.setHardwareAcceleratedDrawingEnabled(enabled);
- mPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled);
+ mDrawingPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled);
}
private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
@@ -536,6 +299,35 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
return animator;
}
+ private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel,
+ final ObjectAnimator animatorToStart) {
+ if (animatorToCancel == null || animatorToStart == null) {
+ // TODO: Stop using null as a no-operation animator.
+ return;
+ }
+ float startFraction = 0.0f;
+ if (animatorToCancel.isStarted()) {
+ animatorToCancel.cancel();
+ startFraction = 1.0f - animatorToCancel.getAnimatedFraction();
+ }
+ final long startTime = (long)(animatorToStart.getDuration() * startFraction);
+ animatorToStart.start();
+ animatorToStart.setCurrentPlayTime(startTime);
+ }
+
+ // Implements {@link TimerHander.Callbacks} method.
+ @Override
+ public void startWhileTypingFadeinAnimation() {
+ cancelAndStartAnimators(
+ mAltCodeKeyWhileTypingFadeoutAnimator, mAltCodeKeyWhileTypingFadeinAnimator);
+ }
+
+ @Override
+ public void startWhileTypingFadeoutAnimation() {
+ cancelAndStartAnimators(
+ mAltCodeKeyWhileTypingFadeinAnimator, mAltCodeKeyWhileTypingFadeoutAnimator);
+ }
+
@ExternallyReferenced
public int getLanguageOnSpacebarAnimAlpha() {
return mLanguageOnSpacebarAnimAlpha;
@@ -573,28 +365,16 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
PointerTracker.setKeyboardActionListener(listener);
}
- /**
- * Returns the {@link KeyboardActionListener} object.
- * @return the listener attached to this keyboard
- */
- @Override
- public KeyboardActionListener getKeyboardActionListener() {
- return mKeyboardActionListener;
- }
-
- @Override
- public KeyDetector getKeyDetector() {
- return mKeyDetector;
- }
-
- @Override
- public DrawingProxy getDrawingProxy() {
- return this;
+ // TODO: We should reconsider which coordinate system should be used to represent keyboard
+ // event.
+ public int getKeyX(final int x) {
+ return Constants.isValidCoordinate(x) ? mKeyDetector.getTouchX(x) : x;
}
- @Override
- public TimerProxy getTimerProxy() {
- return mKeyTimerHandler;
+ // TODO: We should reconsider which coordinate system should be used to represent keyboard
+ // event.
+ public int getKeyY(final int y) {
+ return Constants.isValidCoordinate(y) ? mKeyDetector.getTouchY(y) : y;
}
/**
@@ -607,7 +387,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
public void setKeyboard(final Keyboard keyboard) {
// Remove any pending messages, except dismissing preview and key repeat.
- mKeyTimerHandler.cancelLongPressTimer();
+ mKeyTimerHandler.cancelLongPressTimers();
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
@@ -615,10 +395,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mMoreKeysKeyboardCache.clear();
mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
- mSpaceIcon = (mSpaceKey != null)
+ mSpacebarIcon = (mSpaceKey != null)
? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null;
final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
- mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
+ mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio;
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
final int orientation = getContext().getResources().getConfiguration().orientation;
ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation);
@@ -637,26 +417,21 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
* @see #isKeyPreviewPopupEnabled()
*/
public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
- mShowKeyPreviewPopup = previewEnabled;
- mKeyPreviewLingerTimeout = delay;
+ mKeyPreviewDrawParams.setPopupEnabled(previewEnabled, delay);
+ }
+
+ public void setKeyPreviewAnimationParams(final float showUpStartScale, final int showUpDuration,
+ final float dismissEndScale, final int dismissDuration) {
+ mKeyPreviewDrawParams.setAnimationParams(
+ showUpStartScale, showUpDuration, dismissEndScale, dismissDuration);
}
private void locatePreviewPlacerView() {
- if (mPreviewPlacerView.getParent() != null) {
- return;
- }
- final int width = getWidth();
- final int height = getHeight();
- if (width == 0 || height == 0) {
- // In transient state.
- return;
- }
getLocationInWindow(mOriginCoords);
- final DisplayMetrics dm = getResources().getDisplayMetrics();
- if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) {
- // In transient state.
- return;
- }
+ mDrawingPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, getWidth(), getHeight());
+ }
+
+ private void installPreviewPlacerView() {
final View rootView = getRootView();
if (rootView == null) {
Log.w(TAG, "Cannot find root view");
@@ -665,11 +440,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
// Note: It'd be very weird if we get null by android.R.id.content.
if (windowContentView == null) {
- Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
- } else {
- windowContentView.addView(mPreviewPlacerView);
- mPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height);
+ Log.w(TAG, "Cannot find android.R.id.content view to add DrawingPreviewPlacerView");
+ return;
}
+ windowContentView.addView(mDrawingPreviewPlacerView);
}
/**
@@ -678,80 +452,18 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
* @see #setKeyPreviewPopupEnabled(boolean, int)
*/
public boolean isKeyPreviewPopupEnabled() {
- return mShowKeyPreviewPopup;
- }
-
- private void addKeyPreview(final TextView keyPreview) {
- locatePreviewPlacerView();
- mPreviewPlacerView.addView(
- keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
- }
-
- private TextView getKeyPreviewText(final int pointerId) {
- TextView previewText = mKeyPreviewTexts.get(pointerId);
- if (previewText != null) {
- return previewText;
- }
- final Context context = getContext();
- if (mKeyPreviewLayoutId != 0) {
- previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
- } else {
- previewText = new TextView(context);
- }
- mKeyPreviewTexts.put(pointerId, previewText);
- return previewText;
+ return mKeyPreviewDrawParams.isPopupEnabled();
}
- private void dismissAllKeyPreviews() {
- final int pointerCount = mKeyPreviewTexts.size();
- for (int id = 0; id < pointerCount; id++) {
- final TextView previewText = mKeyPreviewTexts.get(id);
- if (previewText != null) {
- previewText.setVisibility(INVISIBLE);
- }
- }
+ // Implements {@link DrawingHandler.Callbacks} method.
+ @Override
+ public void dismissAllKeyPreviews() {
+ mKeyPreviewChoreographer.dismissAllKeyPreviews();
PointerTracker.setReleasedKeyGraphicsToAllKeys();
}
- // Background state set
- private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
- { // STATE_MIDDLE
- EMPTY_STATE_SET,
- { R.attr.state_has_morekeys }
- },
- { // STATE_LEFT
- { R.attr.state_left_edge },
- { R.attr.state_left_edge, R.attr.state_has_morekeys }
- },
- { // STATE_RIGHT
- { R.attr.state_right_edge },
- { R.attr.state_right_edge, R.attr.state_has_morekeys }
- }
- };
- private static final int STATE_MIDDLE = 0;
- private static final int STATE_LEFT = 1;
- private static final int STATE_RIGHT = 2;
- private static final int STATE_NORMAL = 0;
- private static final int STATE_HAS_MOREKEYS = 1;
-
@Override
- public void showKeyPreview(final PointerTracker tracker) {
- final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
- final Keyboard keyboard = getKeyboard();
- if (!mShowKeyPreviewPopup) {
- previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap;
- return;
- }
-
- final TextView previewText = getKeyPreviewText(tracker.mPointerId);
- // If the key preview has no parent view yet, add it to the ViewGroup which can place
- // key preview absolutely in SoftInputWindow.
- if (previewText.getParent() == null) {
- addKeyPreview(previewText);
- }
-
- mDrawingHandler.cancelDismissKeyPreview(tracker);
- final Key key = tracker.getKey();
+ public void showKeyPreview(final Key key) {
// If key is invalid or IME is already closed, we must not show key preview.
// Trying to show key preview while root window is closed causes
// WindowManager.BadTokenException.
@@ -759,97 +471,66 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
return;
}
- final KeyDrawParams drawParams = mKeyDrawParams;
- previewText.setTextColor(drawParams.mPreviewTextColor);
- final Drawable background = previewText.getBackground();
- final String label = key.getPreviewLabel();
- // What we show as preview should match what we show on a key top in onDraw().
- if (label != null) {
- // TODO Should take care of temporaryShiftLabel here.
- previewText.setCompoundDrawables(null, null, null, null);
- previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- key.selectPreviewTextSize(drawParams));
- previewText.setTypeface(key.selectPreviewTypeface(drawParams));
- previewText.setText(label);
- } else {
- previewText.setCompoundDrawables(null, null, null,
- key.getPreviewIcon(keyboard.mIconsSet));
- previewText.setText(null);
+ final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
+ final Keyboard keyboard = getKeyboard();
+ if (!previewParams.isPopupEnabled()) {
+ previewParams.setVisibleOffset(-keyboard.mVerticalGap);
+ return;
}
- previewText.measure(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- final int keyDrawWidth = key.getDrawWidth();
- final int previewWidth = previewText.getMeasuredWidth();
- final int previewHeight = mKeyPreviewHeight;
- // The width and height of visible part of the key preview background. The content marker
- // of the background 9-patch have to cover the visible part of the background.
- previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
- - previewText.getPaddingRight();
- previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
- - previewText.getPaddingBottom();
- // The distance between the top edge of the parent key and the bottom of the visible part
- // of the key preview background.
- previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom();
+ locatePreviewPlacerView();
+ final TextView previewTextView = mKeyPreviewChoreographer.getKeyPreviewTextView(
+ key, mDrawingPreviewPlacerView);
getLocationInWindow(mOriginCoords);
- // The key preview is horizontally aligned with the center of the visible part of the
- // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
- // the left/right background is used if such background is specified.
- final int statePosition;
- int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
- + CoordinateUtils.x(mOriginCoords);
- if (previewX < 0) {
- previewX = 0;
- statePosition = STATE_LEFT;
- } else if (previewX > getWidth() - previewWidth) {
- previewX = getWidth() - previewWidth;
- statePosition = STATE_RIGHT;
- } else {
- statePosition = STATE_MIDDLE;
- }
- // The key preview is placed vertically above the top edge of the parent key with an
- // arbitrary offset.
- final int previewY = key.getY() - previewHeight + mKeyPreviewOffset
- + CoordinateUtils.y(mOriginCoords);
-
- if (background != null) {
- final int hasMoreKeys = (key.getMoreKeys() != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
- background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
- }
- ViewLayoutUtils.placeViewAt(
- previewText, previewX, previewY, previewWidth, previewHeight);
- previewText.setVisibility(VISIBLE);
+ mKeyPreviewChoreographer.placeKeyPreview(key, previewTextView, keyboard.mIconsSet,
+ mKeyDrawParams, getWidth(), mOriginCoords);
+ mKeyPreviewChoreographer.showKeyPreview(key, previewTextView, isHardwareAccelerated());
}
+ // Implements {@link TimerHandler.Callbacks} method.
@Override
- public void dismissKeyPreview(final PointerTracker tracker) {
- mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker);
+ public void dismissKeyPreviewWithoutDelay(final Key key) {
+ mKeyPreviewChoreographer.dismissKeyPreview(key, false /* withAnimation */);
+ // To redraw key top letter.
+ invalidateKey(key);
+ }
+
+ @Override
+ public void dismissKeyPreview(final Key key) {
+ if (!isHardwareAccelerated()) {
+ // TODO: Implement preference option to control key preview method and duration.
+ mDrawingHandler.dismissKeyPreview(mKeyPreviewDrawParams.getLingerTimeout(), key);
+ return;
+ }
+ mKeyPreviewChoreographer.dismissKeyPreview(key, true /* withAnimation */);
}
public void setSlidingKeyInputPreviewEnabled(final boolean enabled) {
- mSlidingKeyInputPreview.setPreviewEnabled(enabled);
+ mSlidingKeyInputDrawingPreview.setPreviewEnabled(enabled);
}
@Override
public void showSlidingKeyInputPreview(final PointerTracker tracker) {
locatePreviewPlacerView();
- mSlidingKeyInputPreview.setPreviewPosition(tracker);
+ mSlidingKeyInputDrawingPreview.setPreviewPosition(tracker);
}
@Override
public void dismissSlidingKeyInputPreview() {
- mSlidingKeyInputPreview.dismissSlidingKeyInputPreview();
+ mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview();
}
private void setGesturePreviewMode(final boolean isGestureTrailEnabled,
final boolean isGestureFloatingPreviewTextEnabled) {
- mGestureFloatingPreviewText.setPreviewEnabled(isGestureFloatingPreviewTextEnabled);
- mGestureTrailsPreview.setPreviewEnabled(isGestureTrailEnabled);
+ mGestureFloatingTextDrawingPreview.setPreviewEnabled(isGestureFloatingPreviewTextEnabled);
+ mGestureTrailsDrawingPreview.setPreviewEnabled(isGestureTrailEnabled);
}
+ // Implements {@link DrawingHandler.Callbacks} method.
+ @Override
public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
locatePreviewPlacerView();
- mGestureFloatingPreviewText.setSuggetedWords(suggestedWords);
+ mGestureFloatingTextDrawingPreview.setSuggetedWords(suggestedWords);
}
public void dismissGestureFloatingPreviewText() {
@@ -862,9 +543,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final boolean showsFloatingPreviewText) {
locatePreviewPlacerView();
if (showsFloatingPreviewText) {
- mGestureFloatingPreviewText.setPreviewPosition(tracker);
+ mGestureFloatingTextDrawingPreview.setPreviewPosition(tracker);
}
- mGestureTrailsPreview.setPreviewPosition(tracker);
+ mGestureTrailsDrawingPreview.setPreviewPosition(tracker);
}
// Note that this method is called from a non-UI thread.
@@ -883,6 +564,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ installPreviewPlacerView();
// Notify the ResearchLogger (development only diagnostics) that the keyboard view has
// been attached. This is needed to properly show the splash screen, which requires that
// the window token of the KeyboardView be non-null.
@@ -894,7 +576,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mPreviewPlacerView.removeAllViews();
+ mDrawingPreviewPlacerView.removeAllViews();
// Notify the ResearchLogger (development only diagnostics) that the keyboard view has
// been detached. This is needed to invalidate the reference of {@link MainKeyboardView}
// to null.
@@ -922,11 +604,13 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
return moreKeysKeyboardView;
}
+ // Implements {@link TimerHandler.Callbacks} method.
/**
* Called when a key is long pressed.
* @param tracker the pointer tracker which pressed the parent key
*/
- private void onLongPress(final PointerTracker tracker) {
+ @Override
+ public void onLongPress(final PointerTracker tracker) {
if (isShowingMoreKeysPanel()) {
return;
}
@@ -942,8 +626,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final int moreKeyCode = key.getMoreKeys()[0].mCode;
tracker.onLongPressed();
listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */);
- listener.onCodeInput(moreKeyCode,
- Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+ listener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE,
+ Constants.NOT_A_COORDINATE, false /* isKeyRepeat */);
listener.onReleaseKey(moreKeyCode, false /* withSliding */);
return;
}
@@ -979,26 +663,24 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
// aligned with the bottom edge of the visible part of the key preview.
// {@code mPreviewVisibleOffset} has been set appropriately in
// {@link KeyboardView#showKeyPreview(PointerTracker)}.
- final int pointY = key.getY() + mKeyPreviewDrawParams.mPreviewVisibleOffset;
+ final int pointY = key.getY() + mKeyPreviewDrawParams.getVisibleOffset();
moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener);
tracker.onShowMoreKeysPanel(moreKeysPanel);
+ // TODO: Implement zoom in animation of more keys panel.
+ dismissKeyPreviewWithoutDelay(key);
}
- public boolean isInSlidingKeyInput() {
+ public boolean isInDraggingFinger() {
if (isShowingMoreKeysPanel()) {
return true;
}
- return PointerTracker.isAnyInSlidingKeyInput();
+ return PointerTracker.isAnyInDraggingFinger();
}
@Override
public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
locatePreviewPlacerView();
- // TODO: Remove this check
- if (panel.isShowingInParent()) {
- panel.dismissMoreKeysPanel();
- }
- mPreviewPlacerView.addView(panel.getContainerView());
+ panel.showInParent(mDrawingPreviewPlacerView);
mMoreKeysPanel = panel;
dimEntireKeyboard(true /* dimmed */);
}
@@ -1008,15 +690,15 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
@Override
- public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {
+ public void onCancelMoreKeysPanel() {
PointerTracker.dismissAllMoreKeysPanels();
}
@Override
- public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {
+ public void onDismissMoreKeysPanel() {
dimEntireKeyboard(false /* dimmed */);
if (isShowingMoreKeysPanel()) {
- mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView());
+ mMoreKeysPanel.removeFromParent();
mMoreKeysPanel = null;
}
}
@@ -1034,14 +716,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
@Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
- return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event);
- }
- return super.dispatchTouchEvent(event);
- }
-
- @Override
public boolean onTouchEvent(final MotionEvent me) {
if (getKeyboard() == null) {
return false;
@@ -1049,10 +723,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (mNonDistinctMultitouchHelper != null) {
if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) {
// Key repeating timer will be canceled if 2 or more keys are in action.
- mKeyTimerHandler.cancelKeyRepeatTimer();
+ mKeyTimerHandler.cancelKeyRepeatTimers();
}
// Non distinct multitouch screen support
- mNonDistinctMultitouchHelper.processMotionEvent(me, this);
+ mNonDistinctMultitouchHelper.processMotionEvent(me, mKeyDetector);
return true;
}
return processMotionEvent(me);
@@ -1069,8 +743,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final int index = me.getActionIndex();
final int id = me.getPointerId(index);
- final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
- tracker.processMotionEvent(me, this);
+ final PointerTracker tracker = PointerTracker.getPointerTracker(id);
+ // When a more keys panel is showing, we should ignore other fingers' single touch events
+ // other than the finger that is showing the more keys panel.
+ if (isShowingMoreKeysPanel() && !tracker.isShowingMoreKeysPanel()
+ && PointerTracker.getActivePointerTrackerCount() == 1) {
+ return true;
+ }
+ tracker.processMotionEvent(me, mKeyDetector);
return true;
}
@@ -1099,8 +779,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
public boolean dispatchHoverEvent(final MotionEvent event) {
if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
- final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
- return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
+ return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(
+ event, mKeyDetector);
}
// Reflection doesn't support calling superclass methods.
@@ -1121,14 +801,16 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged,
- final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) {
- mNeedsToDisplayLanguage = needsToDisplayLanguage;
+ final int languageOnSpacebarFormatType,
+ final boolean hasMultipleEnabledIMEsOrSubtypes) {
+ mLanguageOnSpacebarFormatType = languageOnSpacebarFormatType;
mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
if (animator == null) {
- mNeedsToDisplayLanguage = false;
+ mLanguageOnSpacebarFormatType = LanguageOnSpacebarHelper.FORMAT_TYPE_NONE;
} else {
- if (subtypeChanged && needsToDisplayLanguage) {
+ if (subtypeChanged
+ && languageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) {
setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE);
if (animator.isStarted()) {
animator.cancel();
@@ -1169,12 +851,30 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
}
+ // Draw key background.
+ @Override
+ protected void onDrawKeyBackground(final Key key, final Canvas canvas,
+ final Drawable background) {
+ if (key.getCode() == Constants.CODE_SPACE) {
+ super.onDrawKeyBackground(key, canvas, mSpacebarBackground);
+ return;
+ }
+ super.onDrawKeyBackground(key, canvas, background);
+ }
+
@Override
protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
final KeyDrawParams params) {
if (key.altCodeWhileTyping() && key.isEnabled()) {
params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
}
+ // Don't draw key top letter when key preview is showing.
+ if (FADE_OUT_KEY_TOP_LETTER_WHEN_KEY_IS_PRESSED
+ && mKeyPreviewChoreographer.isShowingKeyPreview(key)) {
+ // TODO: Fade out animation for the key top letter, and fade in animation for the key
+ // background color when the user presses the key.
+ return;
+ }
final int code = key.getCode();
if (code == Constants.CODE_SPACE) {
drawSpacebar(key, canvas, paint);
@@ -1193,7 +893,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2;
paint.setTextScaleX(1.0f);
- final float textWidth = TypefaceUtils.getLabelWidth(text, paint);
+ final float textWidth = TypefaceUtils.getStringWidth(text, paint);
if (textWidth < width) {
return true;
}
@@ -1204,29 +904,25 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
paint.setTextScaleX(scaleX);
- return TypefaceUtils.getLabelWidth(text, paint) < maxTextWidth;
+ return TypefaceUtils.getStringWidth(text, paint) < maxTextWidth;
}
// Layout language name on spacebar.
private String layoutLanguageOnSpacebar(final Paint paint,
final InputMethodSubtype subtype, final int width) {
-
// Choose appropriate language name to fit into the width.
- final String fullText = SubtypeLocaleUtils.getFullDisplayName(subtype);
- if (fitsTextIntoWidth(width, fullText, paint)) {
- return fullText;
+ if (mLanguageOnSpacebarFormatType == LanguageOnSpacebarHelper.FORMAT_TYPE_FULL_LOCALE) {
+ final String fullText = SpacebarLanguageUtils.getFullDisplayName(subtype);
+ if (fitsTextIntoWidth(width, fullText, paint)) {
+ return fullText;
+ }
}
- final String middleText = SubtypeLocaleUtils.getMiddleDisplayName(subtype);
+ final String middleText = SpacebarLanguageUtils.getMiddleDisplayName(subtype);
if (fitsTextIntoWidth(width, middleText, paint)) {
return middleText;
}
- final String shortText = SubtypeLocaleUtils.getShortDisplayName(subtype);
- if (fitsTextIntoWidth(width, shortText, paint)) {
- return shortText;
- }
-
return "";
}
@@ -1235,20 +931,20 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final int height = key.getHeight();
// If input language are explicitly selected.
- if (mNeedsToDisplayLanguage) {
+ if (mLanguageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) {
paint.setTextAlign(Align.CENTER);
paint.setTypeface(Typeface.DEFAULT);
- paint.setTextSize(mSpacebarTextSize);
+ paint.setTextSize(mLanguageOnSpacebarTextSize);
final InputMethodSubtype subtype = getKeyboard().mId.mSubtype;
final String language = layoutLanguageOnSpacebar(paint, subtype, width);
// Draw language text with shadow
final float descent = paint.descent();
final float textHeight = -paint.ascent() + descent;
final float baseline = height / 2 + textHeight / 2;
- paint.setColor(mSpacebarTextShadowColor);
+ paint.setColor(mLanguageOnSpacebarTextShadowColor);
paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
canvas.drawText(language, width / 2, baseline - descent - 1, paint);
- paint.setColor(mSpacebarTextColor);
+ paint.setColor(mLanguageOnSpacebarTextColor);
paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
canvas.drawText(language, width / 2, baseline - descent, paint);
}
@@ -1260,18 +956,18 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
int x = (width - iconWidth) / 2;
int y = height - iconHeight;
drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight);
- } else if (mSpaceIcon != null) {
- final int iconWidth = mSpaceIcon.getIntrinsicWidth();
- final int iconHeight = mSpaceIcon.getIntrinsicHeight();
+ } else if (mSpacebarIcon != null) {
+ final int iconWidth = mSpacebarIcon.getIntrinsicWidth();
+ final int iconHeight = mSpacebarIcon.getIntrinsicHeight();
int x = (width - iconWidth) / 2;
int y = height - iconHeight;
- drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight);
+ drawIcon(canvas, mSpacebarIcon, x, y, iconWidth, iconHeight);
}
}
@Override
public void deallocateMemory() {
super.deallocateMemory();
- mGestureTrailsPreview.deallocateMemory();
+ mDrawingPreviewPlacerView.deallocateMemory();
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index 6b76e2461..abff202b7 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
@@ -21,25 +21,29 @@ public final class MoreKeysDetector extends KeyDetector {
private final int mSlideAllowanceSquareTop;
public MoreKeysDetector(float slideAllowance) {
- super(/* keyHysteresisDistance */0);
+ super();
mSlideAllowanceSquare = (int)(slideAllowance * slideAllowance);
// Top slide allowance is slightly longer (sqrt(2) times) than other edges.
mSlideAllowanceSquareTop = mSlideAllowanceSquare * 2;
}
@Override
- public boolean alwaysAllowsSlidingInput() {
+ public boolean alwaysAllowsKeySelectionByDraggingFinger() {
return true;
}
@Override
- public Key detectHitKey(int x, int y) {
+ public Key detectHitKey(final int x, final int y) {
+ final Keyboard keyboard = getKeyboard();
+ if (keyboard == null) {
+ return null;
+ }
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
Key nearestKey = null;
int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
- for (final Key key : getKeyboard().getKeys()) {
+ for (final Key key : keyboard.getKeys()) {
final int dist = key.squaredDistanceToEdge(touchX, touchY);
if (dist < nearestDist) {
nearestKey = key;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 8256d4623..a72f79137 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -223,7 +223,7 @@ public final class MoreKeysKeyboard extends Keyboard {
}
public int getDefaultKeyCoordX() {
- return mLeftKeys * mColumnWidth;
+ return mLeftKeys * mColumnWidth + mLeftPadding;
}
public int getX(final int n, final int row) {
@@ -285,7 +285,7 @@ public final class MoreKeysKeyboard extends Keyboard {
// {@link MoreKeysKeyboardParams#setParameters(int,int,int,int,int,int,boolean,int)}.
final boolean singleMoreKeyWithPreview = parentKeyboardView.isKeyPreviewPopupEnabled()
&& !parentKey.noKeyPreview() && moreKeys.length == 1
- && keyPreviewDrawParams.mPreviewVisibleWidth > 0;
+ && keyPreviewDrawParams.getVisibleWidth() > 0;
if (singleMoreKeyWithPreview) {
// Use pre-computed width and height if this more keys keyboard has only one key to
// mitigate visual flicker between key preview and more keys keyboard.
@@ -294,11 +294,11 @@ public final class MoreKeysKeyboard extends Keyboard {
// left/right/top paddings. The bottom paddings of both backgrounds don't need to
// be considered because the vertical positions of both backgrounds were already
// adjusted with their bottom paddings deducted.
- width = keyPreviewDrawParams.mPreviewVisibleWidth;
- height = keyPreviewDrawParams.mPreviewVisibleHeight + mParams.mVerticalGap;
+ width = keyPreviewDrawParams.getVisibleWidth();
+ height = keyPreviewDrawParams.getVisibleHeight() + mParams.mVerticalGap;
} else {
final float padding = context.getResources().getDimension(
- R.dimen.more_keys_keyboard_key_horizontal_padding)
+ R.dimen.config_more_keys_keyboard_key_horizontal_padding)
+ (parentKey.hasLabelsInMoreKeys()
? mParams.mDefaultKeyWidth * LABEL_PADDING_RATIO : 0.0f);
width = getMaxKeyWidth(parentKey, mParams.mDefaultKeyWidth, padding,
@@ -327,7 +327,7 @@ public final class MoreKeysKeyboard extends Keyboard {
// If the label is single letter, minKeyWidth is enough to hold the label.
if (label != null && StringUtils.codePointCount(label) > 1) {
maxWidth = Math.max(maxWidth,
- (int)(TypefaceUtils.getLabelWidth(label, paint) + padding));
+ (int)(TypefaceUtils.getStringWidth(label, paint) + padding));
}
}
return maxWidth;
@@ -343,8 +343,7 @@ public final class MoreKeysKeyboard extends Keyboard {
final int row = n / params.mNumColumns;
final int x = params.getX(n, row);
final int y = params.getY(row);
- final Key key = new Key(params, moreKeySpec, x, y,
- params.mDefaultKeyWidth, params.mDefaultRowHeight, moreKeyFlags);
+ final Key key = moreKeySpec.buildKey(x, y, moreKeyFlags, params);
params.markAsEdgeKey(key, row);
params.onAddKey(key);
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 973128d36..65242dd76 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -21,6 +21,7 @@ import android.content.res.Resources;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
@@ -52,7 +53,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
final Resources res = context.getResources();
mKeyDetector = new MoreKeysDetector(
- res.getDimension(R.dimen.more_keys_keyboard_slide_allowance));
+ res.getDimension(R.dimen.config_more_keys_keyboard_slide_allowance));
}
@Override
@@ -81,11 +82,13 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
mListener = listener;
final View container = getContainerView();
// The coordinates of panel's left-top corner in parentView's coordinate system.
- final int x = pointX - getDefaultCoordX() - container.getPaddingLeft();
- final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom();
+ // We need to consider background drawable paddings.
+ final int x = pointX - getDefaultCoordX() - container.getPaddingLeft() - getPaddingLeft();
+ final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom()
+ + getPaddingBottom();
parentView.getLocationInWindow(mCoordinates);
- // Ensure the horizontal position of the panel does not extend past the screen edges.
+ // Ensure the horizontal position of the panel does not extend past the parentView edges.
final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth();
final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates);
final int panelY = y + CoordinateUtils.y(mCoordinates);
@@ -119,7 +122,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
onMoveKeyInternal(x, y, pointerId);
if (hasOldKey && mCurrentKey == null) {
// If the pointer has moved too far away from any target then cancel the panel.
- mController.onCancelMoreKeysPanel(this);
+ mController.onCancelMoreKeysPanel();
}
}
@@ -139,7 +142,12 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
if (code == Constants.CODE_OUTPUT_TEXT) {
mListener.onTextInput(mCurrentKey.getOutputText());
} else if (code != Constants.CODE_UNSPECIFIED) {
- mListener.onCodeInput(code, x, y);
+ if (getKeyboard().hasProximityCharsCorrection(code)) {
+ mListener.onCodeInput(code, x, y, false /* isKeyRepeat */);
+ } else {
+ mListener.onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+ false /* isKeyRepeat */);
+ }
}
}
@@ -177,7 +185,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
if (!isShowingInParent()) {
return;
}
- mController.onDismissMoreKeysPanel(this);
+ mController.onDismissMoreKeysPanel();
}
@Override
@@ -214,12 +222,26 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
return true;
}
- @Override
- public View getContainerView() {
+ private View getContainerView() {
return (View)getParent();
}
@Override
+ public void showInParent(final ViewGroup parentView) {
+ removeFromParent();
+ parentView.addView(getContainerView());
+ }
+
+ @Override
+ public void removeFromParent() {
+ final View containerView = getContainerView();
+ final ViewGroup currentParent = (ViewGroup)containerView.getParent();
+ if (currentParent != null) {
+ currentParent.removeView(containerView);
+ }
+ }
+
+ @Override
public boolean isShowingInParent() {
return (getContainerView().getParent() != null);
}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
index 886c6286f..7bddd09f6 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.keyboard;
import android.view.View;
+import android.view.ViewGroup;
public interface MoreKeysPanel {
public interface Controller {
@@ -28,24 +29,22 @@ public interface MoreKeysPanel {
/**
* Remove the current {@link MoreKeysPanel} from the target view.
- * @param panel the panel to be dismissed.
*/
- public void onDismissMoreKeysPanel(final MoreKeysPanel panel);
+ public void onDismissMoreKeysPanel();
/**
* Instructs the parent to cancel the panel (e.g., when entering a different input mode).
- * @param panel the panel to be canceled.
*/
- public void onCancelMoreKeysPanel(final MoreKeysPanel panel);
+ public void onCancelMoreKeysPanel();
}
public static final Controller EMPTY_CONTROLLER = new Controller() {
@Override
public void onShowMoreKeysPanel(final MoreKeysPanel panel) {}
@Override
- public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {}
+ public void onDismissMoreKeysPanel() {}
@Override
- public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {}
+ public void onCancelMoreKeysPanel() {}
};
/**
@@ -119,9 +118,16 @@ public interface MoreKeysPanel {
public int translateY(int y);
/**
- * Return the view containing the more keys panel.
+ * Show this {@link MoreKeysPanel} in the parent view.
+ *
+ * @param parentView the {@link ViewGroup} that hosts this {@link MoreKeysPanel}.
+ */
+ public void showInParent(ViewGroup parentView);
+
+ /**
+ * Remove this {@link MoreKeysPanel} from the parent view.
*/
- public View getContainerView();
+ public void removeFromParent();
/**
* Return whether the panel is currently being shown.
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 52f190e77..4777166ea 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -19,16 +19,18 @@ package com.android.inputmethod.keyboard;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.SystemClock;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
-import com.android.inputmethod.accessibility.AccessibilityUtils;
-import com.android.inputmethod.keyboard.internal.GestureStroke;
-import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokeParams;
-import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints;
-import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints.GestureStrokePreviewParams;
+import com.android.inputmethod.keyboard.internal.BatchInputArbiter;
+import com.android.inputmethod.keyboard.internal.BatchInputArbiter.BatchInputArbiterListener;
+import com.android.inputmethod.keyboard.internal.BogusMoveEventDetector;
+import com.android.inputmethod.keyboard.internal.GestureEnabler;
+import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingParams;
+import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingPoints;
+import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionParams;
import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
+import com.android.inputmethod.keyboard.internal.TypingTimeRecorder;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LatinImeLogger;
@@ -42,50 +44,18 @@ import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
-public final class PointerTracker implements PointerTrackerQueue.Element {
+public final class PointerTracker implements PointerTrackerQueue.Element,
+ BatchInputArbiterListener {
private static final String TAG = PointerTracker.class.getSimpleName();
private static final boolean DEBUG_EVENT = false;
private static final boolean DEBUG_MOVE_EVENT = false;
private static final boolean DEBUG_LISTENER = false;
private static boolean DEBUG_MODE = LatinImeLogger.sDBG || DEBUG_EVENT;
- /** True if {@link PointerTracker}s should handle gesture events. */
- private static boolean sShouldHandleGesture = false;
- private static boolean sMainDictionaryAvailable = false;
- private static boolean sGestureHandlingEnabledByInputField = false;
- private static boolean sGestureHandlingEnabledByUser = false;
-
- public interface KeyEventHandler {
- /**
- * Get KeyDetector object that is used for this PointerTracker.
- * @return the KeyDetector object that is used for this PointerTracker
- */
- public KeyDetector getKeyDetector();
-
- /**
- * Get KeyboardActionListener object that is used to register key code and so on.
- * @return the KeyboardActionListner for this PointerTracke
- */
- public KeyboardActionListener getKeyboardActionListener();
-
- /**
- * Get DrawingProxy object that is used for this PointerTracker.
- * @return the DrawingProxy object that is used for this PointerTracker
- */
- public DrawingProxy getDrawingProxy();
-
- /**
- * Get TimerProxy object that handles key repeat and long press timer event for this
- * PointerTracker.
- * @return the TimerProxy object that handles key repeat and long press timer event.
- */
- public TimerProxy getTimerProxy();
- }
-
public interface DrawingProxy {
public void invalidateKey(Key key);
- public void showKeyPreview(PointerTracker tracker);
- public void dismissKeyPreview(PointerTracker tracker);
+ public void showKeyPreview(Key key);
+ public void dismissKeyPreview(Key key);
public void showSlidingKeyInputPreview(PointerTracker tracker);
public void dismissSlidingKeyInputPreview();
public void showGestureTrail(PointerTracker tracker, boolean showsFloatingPreviewText);
@@ -94,13 +64,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
public interface TimerProxy {
public void startTypingStateTimer(Key typedKey);
public boolean isTypingState();
- public void startKeyRepeatTimer(PointerTracker tracker, int repeatCount, int delay);
- public void startLongPressTimer(PointerTracker tracker, int delay);
- public void cancelLongPressTimer();
+ public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay);
+ public void startLongPressTimerOf(PointerTracker tracker, int delay);
+ public void cancelLongPressTimerOf(PointerTracker tracker);
+ public void cancelLongPressShiftKeyTimers();
+ public void cancelKeyTimersOf(PointerTracker tracker);
public void startDoubleTapShiftKeyTimer();
public void cancelDoubleTapShiftKeyTimer();
public boolean isInDoubleTapShiftKeyTimeout();
- public void cancelKeyTimers();
public void startUpdateBatchInputTimer(PointerTracker tracker);
public void cancelUpdateBatchInputTimer(PointerTracker tracker);
public void cancelAllUpdateBatchInputTimers();
@@ -111,11 +82,15 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
@Override
public boolean isTypingState() { return false; }
@Override
- public void startKeyRepeatTimer(PointerTracker tracker, int repeatCount, int delay) {}
+ public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay) {}
+ @Override
+ public void startLongPressTimerOf(PointerTracker tracker, int delay) {}
@Override
- public void startLongPressTimer(PointerTracker tracker, int delay) {}
+ public void cancelLongPressTimerOf(PointerTracker tracker) {}
@Override
- public void cancelLongPressTimer() {}
+ public void cancelLongPressShiftKeyTimers() {}
+ @Override
+ public void cancelKeyTimersOf(PointerTracker tracker) {}
@Override
public void startDoubleTapShiftKeyTimer() {}
@Override
@@ -123,8 +98,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
@Override
public boolean isInDoubleTapShiftKeyTimeout() { return false; }
@Override
- public void cancelKeyTimers() {}
- @Override
public void startUpdateBatchInputTimer(PointerTracker tracker) {}
@Override
public void cancelUpdateBatchInputTimer(PointerTracker tracker) {}
@@ -134,7 +107,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
static final class PointerTrackerParams {
- public final boolean mSlidingKeyInputEnabled;
+ public final boolean mKeySelectionByDraggingFinger;
public final int mTouchNoiseThresholdTime;
public final int mTouchNoiseThresholdDistance;
public final int mSuppressKeyPreviewAfterBatchInputDuration;
@@ -142,21 +115,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
public final int mKeyRepeatInterval;
public final int mLongPressShiftLockTimeout;
- public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
-
- private PointerTrackerParams() {
- mSlidingKeyInputEnabled = false;
- mTouchNoiseThresholdTime = 0;
- mTouchNoiseThresholdDistance = 0;
- mSuppressKeyPreviewAfterBatchInputDuration = 0;
- mKeyRepeatStartTimeout = 0;
- mKeyRepeatInterval = 0;
- mLongPressShiftLockTimeout = 0;
- }
-
public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
- mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
- R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
+ mKeySelectionByDraggingFinger = mainKeyboardViewAttr.getBoolean(
+ R.styleable.MainKeyboardView_keySelectionByDraggingFinger, false);
mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimensionPixelSize(
@@ -172,149 +133,36 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
+ private static GestureEnabler sGestureEnabler = new GestureEnabler();
+
// Parameters for pointer handling.
private static PointerTrackerParams sParams;
- private static GestureStrokeParams sGestureStrokeParams;
- private static GestureStrokePreviewParams sGesturePreviewParams;
+ private static GestureStrokeRecognitionParams sGestureStrokeRecognitionParams;
+ private static GestureStrokeDrawingParams sGestureStrokeDrawingParams;
private static boolean sNeedsPhantomSuddenMoveEventHack;
// Move this threshold to resource.
// TODO: Device specific parameter would be better for device specific hack?
private static final float PHANTOM_SUDDEN_MOVE_THRESHOLD = 0.25f; // in keyWidth
- // This hack is applied to certain classes of tablets.
- // See {@link #needsProximateBogusDownMoveUpEventHack(Resources)}.
- private static boolean sNeedsProximateBogusDownMoveUpEventHack;
private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
private static final PointerTrackerQueue sPointerTrackerQueue = new PointerTrackerQueue();
public final int mPointerId;
- private DrawingProxy mDrawingProxy;
- private TimerProxy mTimerProxy;
- private KeyDetector mKeyDetector;
- private KeyboardActionListener mListener = KeyboardActionListener.EMPTY_LISTENER;
+ private static DrawingProxy sDrawingProxy;
+ private static TimerProxy sTimerProxy;
+ private static KeyboardActionListener sListener = KeyboardActionListener.EMPTY_LISTENER;
+ // The {@link KeyDetector} is set whenever the down event is processed. Also this is updated
+ // when new {@link Keyboard} is set by {@link #setKeyDetector(KeyDetector)}.
+ private KeyDetector mKeyDetector = new KeyDetector();
private Keyboard mKeyboard;
- private int mPhantonSuddenMoveThreshold;
+ private int mPhantomSuddenMoveThreshold;
private final BogusMoveEventDetector mBogusMoveEventDetector = new BogusMoveEventDetector();
private boolean mIsDetectingGesture = false; // per PointerTracker.
private static boolean sInGesture = false;
- private static long sGestureFirstDownTime;
- private static TimeRecorder sTimeRecorder;
- private static final InputPointers sAggregratedPointers = new InputPointers(
- GestureStroke.DEFAULT_CAPACITY);
- private static int sLastRecognitionPointSize = 0; // synchronized using sAggregratedPointers
- private static long sLastRecognitionTime = 0; // synchronized using sAggregratedPointers
-
- static final class BogusMoveEventDetector {
- // Move these thresholds to resource.
- // These thresholds' unit is a diagonal length of a key.
- private static final float BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD = 0.53f;
- private static final float BOGUS_MOVE_RADIUS_THRESHOLD = 1.14f;
-
- private int mAccumulatedDistanceThreshold;
- private int mRadiusThreshold;
-
- // Accumulated distance from actual and artificial down keys.
- /* package */ int mAccumulatedDistanceFromDownKey;
- private int mActualDownX;
- private int mActualDownY;
-
- public void setKeyboardGeometry(final int keyWidth, final int keyHeight) {
- final float keyDiagonal = (float)Math.hypot(keyWidth, keyHeight);
- mAccumulatedDistanceThreshold = (int)(
- keyDiagonal * BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD);
- mRadiusThreshold = (int)(keyDiagonal * BOGUS_MOVE_RADIUS_THRESHOLD);
- }
-
- public void onActualDownEvent(final int x, final int y) {
- mActualDownX = x;
- mActualDownY = y;
- }
-
- public void onDownKey() {
- mAccumulatedDistanceFromDownKey = 0;
- }
-
- public void onMoveKey(final int distance) {
- mAccumulatedDistanceFromDownKey += distance;
- }
-
- public boolean hasTraveledLongDistance(final int x, final int y) {
- final int dx = Math.abs(x - mActualDownX);
- final int dy = Math.abs(y - mActualDownY);
- // A bogus move event should be a horizontal movement. A vertical movement might be
- // a sloppy typing and should be ignored.
- return dx >= dy && mAccumulatedDistanceFromDownKey >= mAccumulatedDistanceThreshold;
- }
-
- /* package */ int getDistanceFromDownEvent(final int x, final int y) {
- return getDistance(x, y, mActualDownX, mActualDownY);
- }
-
- public boolean isCloseToActualDownEvent(final int x, final int y) {
- return getDistanceFromDownEvent(x, y) < mRadiusThreshold;
- }
- }
-
- static final class TimeRecorder {
- private final int mSuppressKeyPreviewAfterBatchInputDuration;
- private final int mStaticTimeThresholdAfterFastTyping; // msec
- private long mLastTypingTime;
- private long mLastLetterTypingTime;
- private long mLastBatchInputTime;
-
- public TimeRecorder(final PointerTrackerParams pointerTrackerParams,
- final GestureStrokeParams gestureStrokeParams) {
- mSuppressKeyPreviewAfterBatchInputDuration =
- pointerTrackerParams.mSuppressKeyPreviewAfterBatchInputDuration;
- mStaticTimeThresholdAfterFastTyping =
- gestureStrokeParams.mStaticTimeThresholdAfterFastTyping;
- }
-
- public boolean isInFastTyping(final long eventTime) {
- final long elapsedTimeSinceLastLetterTyping = eventTime - mLastLetterTypingTime;
- return elapsedTimeSinceLastLetterTyping < mStaticTimeThresholdAfterFastTyping;
- }
-
- private boolean wasLastInputTyping() {
- return mLastTypingTime >= mLastBatchInputTime;
- }
-
- public void onCodeInput(final int code, final long eventTime) {
- // Record the letter typing time when
- // 1. Letter keys are typed successively without any batch input in between.
- // 2. A letter key is typed within the threshold time since the last any key typing.
- // 3. A non-letter key is typed within the threshold time since the last letter key
- // typing.
- if (Character.isLetter(code)) {
- if (wasLastInputTyping()
- || eventTime - mLastTypingTime < mStaticTimeThresholdAfterFastTyping) {
- mLastLetterTypingTime = eventTime;
- }
- } else {
- if (eventTime - mLastLetterTypingTime < mStaticTimeThresholdAfterFastTyping) {
- // This non-letter typing should be treated as a part of fast typing.
- mLastLetterTypingTime = eventTime;
- }
- }
- mLastTypingTime = eventTime;
- }
-
- public void onEndBatchInput(final long eventTime) {
- mLastBatchInputTime = eventTime;
- }
-
- public long getLastLetterTypingTime() {
- return mLastLetterTypingTime;
- }
-
- public boolean needsToSuppressKeyPreviewPopup(final long eventTime) {
- return !wasLastInputTyping()
- && eventTime - mLastBatchInputTime < mSuppressKeyPreviewAfterBatchInputDuration;
- }
- }
+ private static TypingTimeRecorder sTypingTimeRecorder;
// The position and time at which first down event occurred.
private long mDownTime;
@@ -341,92 +189,63 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private MoreKeysPanel mMoreKeysPanel;
private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3;
- // true if this pointer is in a sliding key input.
- boolean mIsInSlidingKeyInput;
- // true if this pointer is in a sliding key input from a modifier key,
+ // true if this pointer is in the dragging finger mode.
+ boolean mIsInDraggingFinger;
+ // true if this pointer is sliding from a modifier key and in the sliding key input mode,
// so that further modifier keys should be ignored.
- boolean mIsInSlidingKeyInputFromModifier;
+ boolean mIsInSlidingKeyInput;
// if not a NOT_A_CODE, the key of this code is repeating
private int mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
- // true if a sliding key input is allowed.
- private boolean mIsAllowedSlidingKeyInput;
-
- private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints;
-
- private static final int SMALL_TABLET_SMALLEST_WIDTH = 600; // dp
- private static final int LARGE_TABLET_SMALLEST_WIDTH = 768; // dp
-
- private static boolean needsProximateBogusDownMoveUpEventHack(final Resources res) {
- // The proximate bogus down move up event hack is needed for a device such like,
- // 1) is large tablet, or 2) is small tablet and the screen density is less than hdpi.
- // Though it seems odd to use screen density as criteria of the quality of the touch
- // screen, the small table that has a less density screen than hdpi most likely has been
- // made with the touch screen that needs the hack.
- final int sw = res.getConfiguration().smallestScreenWidthDp;
- final boolean isLargeTablet = (sw >= LARGE_TABLET_SMALLEST_WIDTH);
- final boolean isSmallTablet =
- (sw >= SMALL_TABLET_SMALLEST_WIDTH && sw < LARGE_TABLET_SMALLEST_WIDTH);
- final int densityDpi = res.getDisplayMetrics().densityDpi;
- final boolean hasLowDensityScreen = (densityDpi < DisplayMetrics.DENSITY_HIGH);
- final boolean needsTheHack = isLargeTablet || (isSmallTablet && hasLowDensityScreen);
- if (DEBUG_MODE) {
- Log.d(TAG, "needsProximateBogusDownMoveUpEventHack=" + needsTheHack
- + " smallestScreenWidthDp=" + sw + " densityDpi=" + densityDpi);
- }
- return needsTheHack;
- }
+ // true if dragging finger is allowed.
+ private boolean mIsAllowedDraggingFinger;
- public static void init(final Resources res) {
- sNeedsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
- ResourceUtils.getDeviceOverrideValue(
- res, R.array.phantom_sudden_move_event_device_list));
- sNeedsProximateBogusDownMoveUpEventHack = needsProximateBogusDownMoveUpEventHack(res);
- sParams = PointerTrackerParams.DEFAULT;
- sGestureStrokeParams = GestureStrokeParams.DEFAULT;
- sGesturePreviewParams = GestureStrokePreviewParams.DEFAULT;
- sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
- }
+ private final BatchInputArbiter mBatchInputArbiter;
+ private final GestureStrokeDrawingPoints mGestureStrokeDrawingPoints;
- public static void setParameters(final TypedArray mainKeyboardViewAttr) {
+ // TODO: Add PointerTrackerFactory singleton and move some class static methods into it.
+ public static void init(final TypedArray mainKeyboardViewAttr, final TimerProxy timerProxy,
+ final DrawingProxy drawingProxy) {
sParams = new PointerTrackerParams(mainKeyboardViewAttr);
- sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr);
- sGesturePreviewParams = new GestureStrokePreviewParams(mainKeyboardViewAttr);
- sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
- }
+ sGestureStrokeRecognitionParams = new GestureStrokeRecognitionParams(mainKeyboardViewAttr);
+ sGestureStrokeDrawingParams = new GestureStrokeDrawingParams(mainKeyboardViewAttr);
+ sTypingTimeRecorder = new TypingTimeRecorder(
+ sGestureStrokeRecognitionParams.mStaticTimeThresholdAfterFastTyping,
+ sParams.mSuppressKeyPreviewAfterBatchInputDuration);
+
+ final Resources res = mainKeyboardViewAttr.getResources();
+ sNeedsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
+ ResourceUtils.getDeviceOverrideValue(res,
+ R.array.phantom_sudden_move_event_device_list, Boolean.FALSE.toString()));
+ BogusMoveEventDetector.init(res);
- private static void updateGestureHandlingMode() {
- sShouldHandleGesture = sMainDictionaryAvailable
- && sGestureHandlingEnabledByInputField
- && sGestureHandlingEnabledByUser
- && !AccessibilityUtils.getInstance().isTouchExplorationEnabled();
+ sTimerProxy = timerProxy;
+ sDrawingProxy = drawingProxy;
}
// Note that this method is called from a non-UI thread.
public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
- sMainDictionaryAvailable = mainDictionaryAvailable;
- updateGestureHandlingMode();
+ sGestureEnabler.setMainDictionaryAvailability(mainDictionaryAvailable);
}
public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
- sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser;
- updateGestureHandlingMode();
+ sGestureEnabler.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
}
- public static PointerTracker getPointerTracker(final int id, final KeyEventHandler handler) {
+ public static PointerTracker getPointerTracker(final int id) {
final ArrayList<PointerTracker> trackers = sTrackers;
// Create pointer trackers until we can get 'id+1'-th tracker, if needed.
for (int i = trackers.size(); i <= id; i++) {
- final PointerTracker tracker = new PointerTracker(i, handler);
+ final PointerTracker tracker = new PointerTracker(i);
trackers.add(tracker);
}
return trackers.get(id);
}
- public static boolean isAnyInSlidingKeyInput() {
- return sPointerTrackerQueue.isAnyInSlidingKeyInput();
+ public static boolean isAnyInDraggingFinger() {
+ return sPointerTrackerQueue.isAnyInDraggingFinger();
}
public static void cancelAllPointerTrackers() {
@@ -434,31 +253,27 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
public static void setKeyboardActionListener(final KeyboardActionListener listener) {
- final int trackersSize = sTrackers.size();
- for (int i = 0; i < trackersSize; ++i) {
- final PointerTracker tracker = sTrackers.get(i);
- tracker.mListener = listener;
- }
+ sListener = listener;
}
public static void setKeyDetector(final KeyDetector keyDetector) {
+ final Keyboard keyboard = keyDetector.getKeyboard();
+ if (keyboard == null) {
+ return;
+ }
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
tracker.setKeyDetectorInner(keyDetector);
- // Mark that keyboard layout has been changed.
- tracker.mKeyboardLayoutHasBeenChanged = true;
}
- final Keyboard keyboard = keyDetector.getKeyboard();
- sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput();
- updateGestureHandlingMode();
+ sGestureEnabler.setPasswordMode(keyboard.mId.passwordInput());
}
public static void setReleasedKeyGraphicsToAllKeys() {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
- tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
+ tracker.setReleasedKeyGraphics(tracker.getKey());
}
}
@@ -466,28 +281,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
- if (tracker.isShowingMoreKeysPanel()) {
- tracker.mMoreKeysPanel.dismissMoreKeysPanel();
- tracker.mMoreKeysPanel = null;
- }
+ tracker.dismissMoreKeysPanel();
}
}
- private PointerTracker(final int id, final KeyEventHandler handler) {
- if (handler == null) {
- throw new NullPointerException();
- }
+ private PointerTracker(final int id) {
mPointerId = id;
- mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(
- id, sGestureStrokeParams, sGesturePreviewParams);
- setKeyEventHandler(handler);
- }
-
- private void setKeyEventHandler(final KeyEventHandler handler) {
- setKeyDetectorInner(handler.getKeyDetector());
- mListener = handler.getKeyboardActionListener();
- mDrawingProxy = handler.getDrawingProxy();
- mTimerProxy = handler.getTimerProxy();
+ mBatchInputArbiter = new BatchInputArbiter(id, sGestureStrokeRecognitionParams);
+ mGestureStrokeDrawingPoints = new GestureStrokeDrawingPoints(sGestureStrokeDrawingParams);
}
// Returns true if keyboard has been changed by this callback.
@@ -500,10 +301,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) {
return false;
}
- final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
+ final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onPress : %s%s%s%s", mPointerId,
- KeyDetector.printableCode(key),
+ (key == null ? "none" : Constants.printableCode(key.getCode())),
ignoreModifierKey ? " ignoreModifier" : "",
key.isEnabled() ? "" : " disabled",
repeatCount > 0 ? " repeatCount=" + repeatCount : ""));
@@ -512,21 +313,21 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return false;
}
if (key.isEnabled()) {
- mListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1);
+ sListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1);
final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
mKeyboardLayoutHasBeenChanged = false;
- mTimerProxy.startTypingStateTimer(key);
+ sTimerProxy.startTypingStateTimer(key);
return keyboardLayoutHasBeenChanged;
}
return false;
}
// Note that we need primaryCode argument because the keyboard may in shifted state and the
- // primaryCode is different from {@link Key#mCode}.
+ // primaryCode is different from {@link Key#mKeyCode}.
private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
- final int y, final long eventTime) {
- final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
- final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
+ final int y, final long eventTime, final boolean isKeyRepeat) {
+ final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
+ final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState();
final int code = altersCode ? key.getAltCode() : primaryCode;
if (DEBUG_LISTENER) {
final String output = code == Constants.CODE_OUTPUT_TEXT
@@ -544,24 +345,29 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
// Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
if (key.isEnabled() || altersCode) {
- sTimeRecorder.onCodeInput(code, eventTime);
+ sTypingTimeRecorder.onCodeInput(code, eventTime);
if (code == Constants.CODE_OUTPUT_TEXT) {
- mListener.onTextInput(key.getOutputText());
+ sListener.onTextInput(key.getOutputText());
} else if (code != Constants.CODE_UNSPECIFIED) {
- mListener.onCodeInput(code, x, y);
+ if (mKeyboard.hasProximityCharsCorrection(code)) {
+ sListener.onCodeInput(code, x, y, isKeyRepeat);
+ } else {
+ sListener.onCodeInput(code,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, isKeyRepeat);
+ }
}
}
}
// Note that we need primaryCode argument because the keyboard may be in shifted state and the
- // primaryCode is different from {@link Key#mCode}.
+ // primaryCode is different from {@link Key#mKeyCode}.
private void callListenerOnRelease(final Key key, final int primaryCode,
final boolean withSliding) {
// See the comment at {@link #callListenerOnPressAndCheckKeyboardLayoutChange(Key}}.
if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) {
return;
}
- final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
+ final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId,
Constants.printableCode(primaryCode),
@@ -576,7 +382,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return;
}
if (key.isEnabled()) {
- mListener.onReleaseKey(primaryCode, withSliding);
+ sListener.onReleaseKey(primaryCode, withSliding);
}
}
@@ -584,7 +390,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onFinishSlidingInput", mPointerId));
}
- mListener.onFinishSlidingInput();
+ sListener.onFinishSlidingInput();
}
private void callListenerOnCancelInput() {
@@ -594,33 +400,34 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.pointerTracker_callListenerOnCancelInput();
}
- mListener.onCancelInput();
+ sListener.onCancelInput();
}
private void setKeyDetectorInner(final KeyDetector keyDetector) {
final Keyboard keyboard = keyDetector.getKeyboard();
+ if (keyboard == null) {
+ return;
+ }
if (keyDetector == mKeyDetector && keyboard == mKeyboard) {
return;
}
mKeyDetector = keyDetector;
- mKeyboard = keyDetector.getKeyboard();
+ mKeyboard = keyboard;
+ // Mark that keyboard layout has been changed.
+ mKeyboardLayoutHasBeenChanged = true;
final int keyWidth = mKeyboard.mMostCommonKeyWidth;
final int keyHeight = mKeyboard.mMostCommonKeyHeight;
- mGestureStrokeWithPreviewPoints.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight);
- final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
- if (newKey != mCurrentKey) {
- if (mDrawingProxy != null) {
- setReleasedKeyGraphics(mCurrentKey);
- }
- // Keep {@link #mCurrentKey} that comes from previous keyboard.
- }
- mPhantonSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD);
+ mBatchInputArbiter.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight);
+ // Keep {@link #mCurrentKey} that comes from previous keyboard. The key preview of
+ // {@link #mCurrentKey} will be dismissed by {@setReleasedKeyGraphics(Key)} via
+ // {@link onMoveEventInternal(int,int,long)} or {@link #onUpEventInternal(int,int,long)}.
+ mPhantomSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD);
mBogusMoveEventDetector.setKeyboardGeometry(keyWidth, keyHeight);
}
@Override
- public boolean isInSlidingKeyInput() {
- return mIsInSlidingKeyInput;
+ public boolean isInDraggingFinger() {
+ return mIsInDraggingFinger;
}
public Key getKey() {
@@ -637,7 +444,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
private void setReleasedKeyGraphics(final Key key) {
- mDrawingProxy.dismissKeyPreview(this);
+ sDrawingProxy.dismissKeyPreview(key);
if (key == null) {
return;
}
@@ -668,8 +475,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
private static boolean needsToSuppressKeyPreviewPopup(final long eventTime) {
- if (!sShouldHandleGesture) return false;
- return sTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime);
+ if (!sGestureEnabler.shouldHandleGesture()) return false;
+ return sTypingTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime);
}
private void setPressedKeyGraphics(final Key key, final long eventTime) {
@@ -678,14 +485,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
// Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
- final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
+ final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState();
final boolean needsToUpdateGraphics = key.isEnabled() || altersCode;
if (!needsToUpdateGraphics) {
return;
}
if (!key.noKeyPreview() && !sInGesture && !needsToSuppressKeyPreviewPopup(eventTime)) {
- mDrawingProxy.showKeyPreview(this);
+ sDrawingProxy.showKeyPreview(key);
}
updatePressKeyGraphics(key);
@@ -697,7 +504,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) {
+ if (altersCode) {
final int altCode = key.getAltCode();
final Key altKey = mKeyboard.getKey(altCode);
if (altKey != null) {
@@ -711,18 +518,18 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void updateReleaseKeyGraphics(final Key key) {
+ private static void updateReleaseKeyGraphics(final Key key) {
key.onReleased();
- mDrawingProxy.invalidateKey(key);
+ sDrawingProxy.invalidateKey(key);
}
- private void updatePressKeyGraphics(final Key key) {
+ private static void updatePressKeyGraphics(final Key key) {
key.onPressed();
- mDrawingProxy.invalidateKey(key);
+ sDrawingProxy.invalidateKey(key);
}
- public GestureStrokeWithPreviewPoints getGestureStrokeWithPreviewPoints() {
- return mGestureStrokeWithPreviewPoints;
+ public GestureStrokeDrawingPoints getGestureStrokeDrawingPoints() {
+ return mGestureStrokeDrawingPoints;
}
public void getLastCoordinates(final int[] outCoords) {
@@ -744,7 +551,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
}
- static int getDistance(final int x1, final int y1, final int x2, final int y2) {
+ private static int getDistance(final int x1, final int y1, final int x2, final int y2) {
return (int)Math.hypot(x1 - x2, y1 - y2);
}
@@ -766,7 +573,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return newKey;
}
- private static int getActivePointerTrackerCount() {
+ /* package */ static int getActivePointerTrackerCount() {
return sPointerTrackerQueue.size();
}
@@ -774,91 +581,59 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return sPointerTrackerQueue.getOldestElement() == this;
}
- private void mayStartBatchInput(final Key key) {
- if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) {
- return;
- }
- if (key == null || !Character.isLetter(key.getCode())) {
- return;
- }
+ // Implements {@link BatchInputArbiterListener}.
+ @Override
+ public void onStartBatchInput() {
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId));
}
- sInGesture = true;
- synchronized (sAggregratedPointers) {
- sAggregratedPointers.reset();
- sLastRecognitionPointSize = 0;
- sLastRecognitionTime = 0;
- mListener.onStartBatchInput();
- dismissAllMoreKeysPanels();
- }
- mTimerProxy.cancelLongPressTimer();
- // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
- mDrawingProxy.showGestureTrail(
- this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
- }
-
- public void updateBatchInputByTimer(final long eventTime) {
- final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
- mGestureStrokeWithPreviewPoints.duplicateLastPointWith(gestureTime);
- updateBatchInput(eventTime);
+ sListener.onStartBatchInput();
+ dismissAllMoreKeysPanels();
+ sTimerProxy.cancelLongPressTimerOf(this);
}
- private void mayUpdateBatchInput(final long eventTime, final Key key) {
- if (key != null) {
- updateBatchInput(eventTime);
- }
+ private void showGestureTrail() {
if (mIsTrackingForActionDisabled) {
return;
}
// A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
- mDrawingProxy.showGestureTrail(
+ sDrawingProxy.showGestureTrail(
this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
}
- private void updateBatchInput(final long eventTime) {
- synchronized (sAggregratedPointers) {
- final GestureStroke stroke = mGestureStrokeWithPreviewPoints;
- stroke.appendIncrementalBatchPoints(sAggregratedPointers);
- final int size = sAggregratedPointers.getPointerSize();
- if (size > sLastRecognitionPointSize
- && stroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) {
- if (DEBUG_LISTENER) {
- Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId,
- size));
- }
- mTimerProxy.startUpdateBatchInputTimer(this);
- mListener.onUpdateBatchInput(sAggregratedPointers);
- // The listener may change the size of the pointers (when auto-committing
- // for example), so we need to get the size from the pointers again.
- sLastRecognitionPointSize = sAggregratedPointers.getPointerSize();
- sLastRecognitionTime = eventTime;
- }
- }
+ public void updateBatchInputByTimer(final long syntheticMoveEventTime) {
+ mBatchInputArbiter.updateBatchInputByTimer(syntheticMoveEventTime, this);
}
- private void mayEndBatchInput(final long eventTime) {
- synchronized (sAggregratedPointers) {
- mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers);
- if (getActivePointerTrackerCount() == 1) {
- sInGesture = false;
- sTimeRecorder.onEndBatchInput(eventTime);
- mTimerProxy.cancelAllUpdateBatchInputTimers();
- if (!mIsTrackingForActionDisabled) {
- if (DEBUG_LISTENER) {
- Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d",
- mPointerId, sAggregratedPointers.getPointerSize()));
- }
- mListener.onEndBatchInput(sAggregratedPointers);
- }
- }
+ // Implements {@link BatchInputArbiterListener}.
+ @Override
+ public void onUpdateBatchInput(final InputPointers aggregatedPointers, final long eventTime) {
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId,
+ aggregatedPointers.getPointerSize()));
}
+ sListener.onUpdateBatchInput(aggregatedPointers);
+ }
+
+ // Implements {@link BatchInputArbiterListener}.
+ @Override
+ public void onStartUpdateBatchInputTimer() {
+ sTimerProxy.startUpdateBatchInputTimer(this);
+ }
+
+ // Implements {@link BatchInputArbiterListener}.
+ @Override
+ public void onEndBatchInput(final InputPointers aggregatedPointers, final long eventTime) {
+ sTypingTimeRecorder.onEndBatchInput(eventTime);
+ sTimerProxy.cancelAllUpdateBatchInputTimers();
if (mIsTrackingForActionDisabled) {
return;
}
- // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
- mDrawingProxy.showGestureTrail(
- this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d",
+ mPointerId, aggregatedPointers.getPointerSize()));
+ }
+ sListener.onEndBatchInput(aggregatedPointers);
}
private void cancelBatchInput() {
@@ -871,19 +646,26 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onCancelBatchInput", mPointerId));
}
- mListener.onCancelBatchInput();
+ sListener.onCancelBatchInput();
}
- public void processMotionEvent(final MotionEvent me, final KeyEventHandler handler) {
+ public void processMotionEvent(final MotionEvent me, final KeyDetector keyDetector) {
final int action = me.getActionMasked();
final long eventTime = me.getEventTime();
if (action == MotionEvent.ACTION_MOVE) {
+ // When this pointer is the only active pointer and is showing a more keys panel,
+ // we should ignore other pointers' motion event.
+ final boolean shouldIgnoreOtherPointers =
+ isShowingMoreKeysPanel() && getActivePointerTrackerCount() == 1;
final int pointerCount = me.getPointerCount();
for (int index = 0; index < pointerCount; index++) {
final int id = me.getPointerId(index);
- final PointerTracker tracker = getPointerTracker(id, handler);
+ if (shouldIgnoreOtherPointers && id != mPointerId) {
+ continue;
+ }
final int x = (int)me.getX(index);
final int y = (int)me.getY(index);
+ final PointerTracker tracker = getPointerTracker(id);
tracker.onMoveEvent(x, y, eventTime, me);
}
return;
@@ -894,7 +676,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
- onDownEvent(x, y, eventTime, handler);
+ onDownEvent(x, y, eventTime, keyDetector);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
@@ -907,11 +689,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
private void onDownEvent(final int x, final int y, final long eventTime,
- final KeyEventHandler handler) {
+ final KeyDetector keyDetector) {
if (DEBUG_EVENT) {
printTouchEvent("onDownEvent:", x, y, eventTime);
}
- setKeyEventHandler(handler);
+ setKeyDetectorInner(keyDetector);
// Naive up-to-down noise filter.
final long deltaT = eventTime - mUpTime;
if (deltaT < sParams.mTouchNoiseThresholdTime) {
@@ -938,7 +720,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
sPointerTrackerQueue.add(this);
onDownEventInternal(x, y, eventTime);
- if (!sShouldHandleGesture) {
+ if (!sGestureEnabler.shouldHandleGesture()) {
return;
}
// A gesture should start only from a non-modifier key. Note that the gesture detection is
@@ -946,28 +728,36 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard()
&& key != null && !key.isModifier();
if (mIsDetectingGesture) {
- if (getActivePointerTrackerCount() == 1) {
- sGestureFirstDownTime = eventTime;
- }
- mGestureStrokeWithPreviewPoints.onDownEvent(x, y, eventTime, sGestureFirstDownTime,
- sTimeRecorder.getLastLetterTypingTime());
+ mBatchInputArbiter.addDownEventPoint(x, y, eventTime,
+ sTypingTimeRecorder.getLastLetterTypingTime(), getActivePointerTrackerCount());
+ mGestureStrokeDrawingPoints.onDownEvent(
+ x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime));
}
}
- private boolean isShowingMoreKeysPanel() {
+ /* package */ boolean isShowingMoreKeysPanel() {
return (mMoreKeysPanel != null);
}
+ private void dismissMoreKeysPanel() {
+ if (isShowingMoreKeysPanel()) {
+ mMoreKeysPanel.dismissMoreKeysPanel();
+ mMoreKeysPanel = null;
+ }
+ }
+
private void onDownEventInternal(final int x, final int y, final long eventTime) {
Key key = onDownKey(x, y, eventTime);
- // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
- // from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
- mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled
+ // Key selection by dragging finger is allowed when 1) key selection by dragging finger is
+ // enabled by configuration, 2) this pointer starts dragging from modifier key, or 3) this
+ // pointer's KeyDetector always allows key selection by dragging finger, such as
+ // {@link MoreKeysKeyboard}.
+ mIsAllowedDraggingFinger = sParams.mKeySelectionByDraggingFinger
|| (key != null && key.isModifier())
- || mKeyDetector.alwaysAllowsSlidingInput();
+ || mKeyDetector.alwaysAllowsKeySelectionByDraggingFinger();
mKeyboardLayoutHasBeenChanged = false;
mIsTrackingForActionDisabled = false;
- resetSlidingKeyInput();
+ resetKeySelectionByDraggingFinger();
if (key != null) {
// This onPress call may have changed keyboard layout. Those cases are detected at
// {@link #setKeyboard}. In those cases, we should update key according to the new
@@ -982,43 +772,47 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void startSlidingKeyInput(final Key key) {
- if (!mIsInSlidingKeyInput) {
- mIsInSlidingKeyInputFromModifier = key.isModifier();
+ private void startKeySelectionByDraggingFinger(final Key key) {
+ if (!mIsInDraggingFinger) {
+ mIsInSlidingKeyInput = key.isModifier();
}
- mIsInSlidingKeyInput = true;
+ mIsInDraggingFinger = true;
}
- private void resetSlidingKeyInput() {
+ private void resetKeySelectionByDraggingFinger() {
+ mIsInDraggingFinger = false;
mIsInSlidingKeyInput = false;
- mIsInSlidingKeyInputFromModifier = false;
- mDrawingProxy.dismissSlidingKeyInputPreview();
+ sDrawingProxy.dismissSlidingKeyInputPreview();
}
private void onGestureMoveEvent(final int x, final int y, final long eventTime,
final boolean isMajorEvent, final Key key) {
- final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
- if (mIsDetectingGesture) {
- final int beforeLength = mGestureStrokeWithPreviewPoints.getLength();
- final boolean onValidArea = mGestureStrokeWithPreviewPoints.addPointOnKeyboard(
- x, y, gestureTime, isMajorEvent);
- if (mGestureStrokeWithPreviewPoints.getLength() > beforeLength) {
- mTimerProxy.startUpdateBatchInputTimer(this);
- }
- // If the move event goes out from valid batch input area, cancel batch input.
- if (!onValidArea) {
- cancelBatchInput();
- return;
- }
- // If the MoreKeysPanel is showing then do not attempt to enter gesture mode. However,
- // the gestured touch points are still being recorded in case the panel is dismissed.
- if (isShowingMoreKeysPanel()) {
- return;
- }
- mayStartBatchInput(key);
- if (sInGesture) {
- mayUpdateBatchInput(eventTime, key);
+ if (!mIsDetectingGesture) {
+ return;
+ }
+ final boolean onValidArea = mBatchInputArbiter.addMoveEventPoint(
+ x, y, eventTime, isMajorEvent, this);
+ // If the move event goes out from valid batch input area, cancel batch input.
+ if (!onValidArea) {
+ cancelBatchInput();
+ return;
+ }
+ mGestureStrokeDrawingPoints.onMoveEvent(
+ x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime));
+ // If the MoreKeysPanel is showing then do not attempt to enter gesture mode. However,
+ // the gestured touch points are still being recorded in case the panel is dismissed.
+ if (isShowingMoreKeysPanel()) {
+ return;
+ }
+ if (!sInGesture && key != null && Character.isLetter(key.getCode())
+ && mBatchInputArbiter.mayStartBatchInput(this)) {
+ sInGesture = true;
+ }
+ if (sInGesture) {
+ if (key != null) {
+ mBatchInputArbiter.updateBatchInput(eventTime, this);
}
+ showGestureTrail();
}
}
@@ -1030,7 +824,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return;
}
- if (sShouldHandleGesture && me != null) {
+ if (sGestureEnabler.shouldHandleGesture() && me != null) {
// Add historical points to gesture path.
final int pointerIndex = me.findPointerIndex(mPointerId);
final int historicalSize = me.getHistorySize();
@@ -1048,15 +842,15 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final int translatedY = mMoreKeysPanel.translateY(y);
mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime);
onMoveKey(x, y);
- if (mIsInSlidingKeyInputFromModifier) {
- mDrawingProxy.showSlidingKeyInputPreview(this);
+ if (mIsInSlidingKeyInput) {
+ sDrawingProxy.showSlidingKeyInputPreview(this);
}
return;
}
onMoveEventInternal(x, y, eventTime);
}
- private void processSlidingKeyInput(final Key newKey, final int x, final int y,
+ private void processDraggingFingerInToNewKey(final Key newKey, final int x, final int y,
final long eventTime) {
// This onPress call may have changed keyboard layout. Those cases are detected
// at {@link #setKeyboard}. In those cases, we should update key according
@@ -1110,35 +904,35 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
onDownEventInternal(x, y, eventTime);
}
- private void processSildeOutFromOldKey(final Key oldKey) {
+ private void processDraggingFingerOutFromOldKey(final Key oldKey) {
setReleasedKeyGraphics(oldKey);
callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */);
- startSlidingKeyInput(oldKey);
- mTimerProxy.cancelKeyTimers();
+ startKeySelectionByDraggingFinger(oldKey);
+ sTimerProxy.cancelKeyTimersOf(this);
}
- private void slideFromOldKeyToNewKey(final Key key, final int x, final int y,
+ private void dragFingerFromOldKeyToNewKey(final Key key, final int x, final int y,
final long eventTime, final Key oldKey, final int lastX, final int lastY) {
// The pointer has been slid in to the new key from the previous key, we must call
// onRelease() first to notify that the previous key has been released, then call
// onPress() to notify that the new key is being pressed.
- processSildeOutFromOldKey(oldKey);
+ processDraggingFingerOutFromOldKey(oldKey);
startRepeatKey(key);
- if (mIsAllowedSlidingKeyInput) {
- processSlidingKeyInput(key, x, y, eventTime);
+ if (mIsAllowedDraggingFinger) {
+ processDraggingFingerInToNewKey(key, x, y, eventTime);
}
// HACK: On some devices, quick successive touches may be reported as a sudden move by
// touch panel firmware. This hack detects such cases and translates the move event to
// successive up and down events.
// TODO: Should find a way to balance gesture detection and this hack.
else if (sNeedsPhantomSuddenMoveEventHack
- && getDistance(x, y, lastX, lastY) >= mPhantonSuddenMoveThreshold) {
+ && getDistance(x, y, lastX, lastY) >= mPhantomSuddenMoveThreshold) {
processPhantomSuddenMoveHack(key, x, y, eventTime, oldKey, lastX, lastY);
}
// HACK: On some devices, quick successive proximate touches may be reported as a bogus
// down-move-up event by touch panel firmware. This hack detects such cases and breaks
// these events into separate up and down events.
- else if (sNeedsProximateBogusDownMoveUpEventHack && sTimeRecorder.isInFastTyping(eventTime)
+ else if (sTypingTimeRecorder.isInFastTyping(eventTime)
&& mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) {
processProximateBogusDownMoveUpEventHack(key, x, y, eventTime, oldKey, lastX, lastY);
}
@@ -1163,11 +957,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void slideOutFromOldKey(final Key oldKey, final int x, final int y) {
+ private void dragFingerOutFromOldKey(final Key oldKey, final int x, final int y) {
// The pointer has been slid out from the previous key, we must call onRelease() to
// notify that the previous key has been released.
- processSildeOutFromOldKey(oldKey);
- if (mIsAllowedSlidingKeyInput) {
+ processDraggingFingerOutFromOldKey(oldKey);
+ if (mIsAllowedDraggingFinger) {
onMoveToNewKey(null, x, y);
} else {
if (!mIsDetectingGesture) {
@@ -1182,7 +976,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final Key oldKey = mCurrentKey;
final Key newKey = onMoveKey(x, y);
- if (sShouldHandleGesture) {
+ if (sGestureEnabler.shouldHandleGesture()) {
// Register move event on gesture tracker.
onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey);
if (sInGesture) {
@@ -1194,19 +988,19 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (newKey != null) {
if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) {
- slideFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY);
+ dragFingerFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY);
} else if (oldKey == null) {
// The pointer has been slid in to the new key, but the finger was not on any keys.
// In this case, we must call onPress() to notify that the new key is being pressed.
- processSlidingKeyInput(newKey, x, y, eventTime);
+ processDraggingFingerInToNewKey(newKey, x, y, eventTime);
}
} else { // newKey == null
if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) {
- slideOutFromOldKey(oldKey, x, y);
+ dragFingerOutFromOldKey(oldKey, x, y);
}
}
- if (mIsInSlidingKeyInputFromModifier) {
- mDrawingProxy.showSlidingKeyInputPreview(this);
+ if (mIsInSlidingKeyInput) {
+ sDrawingProxy.showSlidingKeyInputPreview(this);
}
}
@@ -1215,7 +1009,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
printTouchEvent("onUpEvent :", x, y, eventTime);
}
- mTimerProxy.cancelUpdateBatchInputTimer(this);
+ sTimerProxy.cancelUpdateBatchInputTimer(this);
if (!sInGesture) {
if (mCurrentKey != null && mCurrentKey.isModifier()) {
// Before processing an up event of modifier key, all pointers already being
@@ -1237,18 +1031,15 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (DEBUG_EVENT) {
printTouchEvent("onPhntEvent:", mLastX, mLastY, eventTime);
}
- if (isShowingMoreKeysPanel()) {
- return;
- }
onUpEventInternal(mLastX, mLastY, eventTime);
cancelTrackingForAction();
}
private void onUpEventInternal(final int x, final int y, final long eventTime) {
- mTimerProxy.cancelKeyTimers();
+ sTimerProxy.cancelKeyTimersOf(this);
+ final boolean isInDraggingFinger = mIsInDraggingFinger;
final boolean isInSlidingKeyInput = mIsInSlidingKeyInput;
- final boolean isInSlidingKeyInputFromModifier = mIsInSlidingKeyInputFromModifier;
- resetSlidingKeyInput();
+ resetKeySelectionByDraggingFinger();
mIsDetectingGesture = false;
final Key currentKey = mCurrentKey;
mCurrentKey = null;
@@ -1272,7 +1063,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (currentKey != null) {
callListenerOnRelease(currentKey, currentKey.getCode(), true /* withSliding */);
}
- mayEndBatchInput(eventTime);
+ if (mBatchInputArbiter.mayEndBatchInput(
+ eventTime, getActivePointerTrackerCount(), this)) {
+ sInGesture = false;
+ }
+ showGestureTrail();
return;
}
@@ -1280,11 +1075,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return;
}
if (currentKey != null && currentKey.isRepeatable()
- && (currentKey.getCode() == currentRepeatingKeyCode) && !isInSlidingKeyInput) {
+ && (currentKey.getCode() == currentRepeatingKeyCode) && !isInDraggingFinger) {
return;
}
detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime);
- if (isInSlidingKeyInputFromModifier) {
+ if (isInSlidingKeyInput) {
callListenerOnFinishSlidingInput();
}
}
@@ -1306,7 +1101,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
public void onLongPressed() {
- resetSlidingKeyInput();
+ resetKeySelectionByDraggingFinger();
cancelTrackingForAction();
setReleasedKeyGraphics(mCurrentKey);
sPointerTrackerQueue.remove(this);
@@ -1324,9 +1119,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
private void onCancelEventInternal() {
- mTimerProxy.cancelKeyTimers();
+ sTimerProxy.cancelKeyTimersOf(this);
setReleasedKeyGraphics(mCurrentKey);
- resetSlidingKeyInput();
+ resetKeySelectionByDraggingFinger();
if (isShowingMoreKeysPanel()) {
mMoreKeysPanel.dismissMoreKeysPanel();
mMoreKeysPanel = null;
@@ -1335,9 +1130,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
final Key newKey) {
- if (mKeyDetector == null) {
- throw new NullPointerException("keyboard and/or key detector not set");
- }
final Key curKey = mCurrentKey;
if (newKey == curKey) {
return false;
@@ -1347,7 +1139,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
// Here curKey points to the different key from newKey.
final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared(
- mIsInSlidingKeyInputFromModifier);
+ mIsInSlidingKeyInput);
final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y);
if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) {
if (DEBUG_MODE) {
@@ -1358,14 +1150,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
return true;
}
- if (sNeedsProximateBogusDownMoveUpEventHack && !mIsAllowedSlidingKeyInput
- && sTimeRecorder.isInFastTyping(eventTime)
+ if (!mIsAllowedDraggingFinger && sTypingTimeRecorder.isInFastTyping(eventTime)
&& mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) {
if (DEBUG_MODE) {
final float keyDiagonal = (float)Math.hypot(
mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
final float lengthFromDownRatio =
- mBogusMoveEventDetector.mAccumulatedDistanceFromDownKey / keyDiagonal;
+ mBogusMoveEventDetector.getAccumulatedDistanceFromDownKey() / keyDiagonal;
Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:"
+ " %.2f key diagonal from virtual down point",
mPointerId, lengthFromDownRatio));
@@ -1376,30 +1167,34 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
private void startLongPressTimer(final Key key) {
+ // Note that we need to cancel all active long press shift key timers if any whenever we
+ // start a new long press timer for both non-shift and shift keys.
+ sTimerProxy.cancelLongPressShiftKeyTimers();
if (sInGesture) return;
if (key == null) return;
if (!key.isLongPressEnabled()) return;
// Caveat: Please note that isLongPressEnabled() can be true even if the current key
- // doesn't have its more keys. (e.g. spacebar, globe key)
+ // doesn't have its more keys. (e.g. spacebar, globe key) If we are in the dragging finger
+ // mode, we will disable long press timer of such key.
// We always need to start the long press timer if the key has its more keys regardless of
- // whether or not we are in the sliding input mode.
- if (mIsInSlidingKeyInput && key.getMoreKeys() == null) return;
- final int delay;
- switch (key.getCode()) {
- case Constants.CODE_SHIFT:
- delay = sParams.mLongPressShiftLockTimeout;
- break;
- default:
- final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout;
- if (mIsInSlidingKeyInputFromModifier) {
- // We use longer timeout for sliding finger input started from the modifier key.
- delay = longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
- } else {
- delay = longpressTimeout;
- }
- break;
+ // whether or not we are in the dragging finger mode.
+ if (mIsInDraggingFinger && key.getMoreKeys() == null) return;
+
+ final int delay = getLongPressTimeout(key.getCode());
+ if (delay <= 0) return;
+ sTimerProxy.startLongPressTimerOf(this, delay);
+ }
+
+ private int getLongPressTimeout(final int code) {
+ if (code == Constants.CODE_SHIFT) {
+ return sParams.mLongPressShiftLockTimeout;
+ }
+ final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout;
+ if (mIsInSlidingKeyInput) {
+ // We use longer timeout for sliding finger input started from the modifier key.
+ return longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
}
- mTimerProxy.startLongPressTimer(this, delay);
+ return longpressTimeout;
}
private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) {
@@ -1409,7 +1204,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
final int code = key.getCode();
- callListenerOnCodeInput(key, code, x, y, eventTime);
+ callListenerOnCodeInput(key, code, x, y, eventTime, false /* isKeyRepeat */);
callListenerOnRelease(key, code, false /* withSliding */);
}
@@ -1417,10 +1212,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (sInGesture) return;
if (key == null) return;
if (!key.isRepeatable()) return;
- // Don't start key repeat when we are in sliding input mode.
- if (mIsInSlidingKeyInput) return;
+ // Don't start key repeat when we are in the dragging finger mode.
+ if (mIsInDraggingFinger) return;
final int startRepeatCount = 1;
- mTimerProxy.startKeyRepeatTimer(this, startRepeatCount, sParams.mKeyRepeatStartTimeout);
+ startKeyRepeatTimer(startRepeatCount);
}
public void onKeyRepeat(final int code, final int repeatCount) {
@@ -1432,15 +1227,22 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
mCurrentRepeatingKeyCode = code;
mIsDetectingGesture = false;
final int nextRepeatCount = repeatCount + 1;
- mTimerProxy.startKeyRepeatTimer(this, nextRepeatCount, sParams.mKeyRepeatInterval);
+ startKeyRepeatTimer(nextRepeatCount);
callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount);
- callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis());
+ callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis(),
+ true /* isKeyRepeat */);
+ }
+
+ private void startKeyRepeatTimer(final int repeatCount) {
+ final int delay =
+ (repeatCount == 1) ? sParams.mKeyRepeatStartTimeout : sParams.mKeyRepeatInterval;
+ sTimerProxy.startKeyRepeatTimerOf(this, repeatCount, delay);
}
private void printTouchEvent(final String title, final int x, final int y,
final long eventTime) {
final Key key = mKeyDetector.detectHitKey(x, y);
- final String code = KeyDetector.printableCode(key);
+ final String code = (key == null ? "none" : Constants.printableCode(key.getCode()));
Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId,
(mIsTrackingForActionDisabled ? "-" : " "), title, x, y, eventTime, code));
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
index b814fc162..3a72aed0d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
@@ -22,36 +22,47 @@ import android.view.View;
import com.android.inputmethod.keyboard.PointerTracker;
/**
- * Abstract base class for previews that are drawn on PreviewPlacerView, e.g.,
- * GestureFloatingPrevewText, GestureTrail, and SlidingKeyInputPreview.
+ * Abstract base class for previews that are drawn on DrawingPreviewPlacerView, e.g.,
+ * GestureFloatingTextDrawingPreview, GestureTrailsDrawingPreview, and
+ * SlidingKeyInputDrawingPreview.
*/
public abstract class AbstractDrawingPreview {
private final View mDrawingView;
private boolean mPreviewEnabled;
+ private boolean mHasValidGeometry;
protected AbstractDrawingPreview(final View drawingView) {
mDrawingView = drawingView;
}
- public final View getDrawingView() {
+ protected final View getDrawingView() {
return mDrawingView;
}
- public final void setPreviewEnabled(final boolean enabled) {
- mPreviewEnabled = enabled;
+ protected final boolean isPreviewEnabled() {
+ return mPreviewEnabled && mHasValidGeometry;
}
- public boolean isPreviewEnabled() {
- return mPreviewEnabled;
+ public final void setPreviewEnabled(final boolean enabled) {
+ mPreviewEnabled = enabled;
}
- public void setKeyboardGeometry(final int[] originCoords, final int width, final int height) {
- // Default implementation is empty.
+ /**
+ * Set {@link MainKeyboardView} geometry and position in the {@link SoftInputWindow}.
+ * The class that is overriding this method must call this super implementation.
+ *
+ * @param originCoords the top-left coordinates of the {@link MainKeyboardView} in
+ * {@link SoftInputWindow} coordinate-system. This is unused but has a point in an
+ * extended class, such as {@link GestureTrailsDrawingPreview}.
+ * @param width the width of {@link MainKeyboardView}.
+ * @param height the height of {@link MainKeyboardView}.
+ */
+ public void setKeyboardViewGeometry(final int[] originCoords, final int width,
+ final int height) {
+ mHasValidGeometry = (width > 0 && height > 0);
}
- public void onDetachFromWindow() {
- // Default implementation is empty.
- }
+ public abstract void onDeallocateMemory();
/**
* Draws the preview
diff --git a/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java b/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java
new file mode 100644
index 000000000..cd9875955
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.InputPointers;
+
+/**
+ * This class arbitrates batch input.
+ * An instance of this class holds a {@link GestureStrokeRecognitionPoints}.
+ * And it arbitrates multiple strokes gestured by multiple fingers and aggregates those gesture
+ * points into one batch input.
+ */
+public class BatchInputArbiter {
+ public interface BatchInputArbiterListener {
+ public void onStartBatchInput();
+ public void onUpdateBatchInput(
+ final InputPointers aggregatedPointers, final long moveEventTime);
+ public void onStartUpdateBatchInputTimer();
+ public void onEndBatchInput(final InputPointers aggregatedPointers, final long upEventTime);
+ }
+
+ // The starting time of the first stroke of a gesture input.
+ private static long sGestureFirstDownTime;
+ // The {@link InputPointers} that includes all events of a gesture input.
+ private static final InputPointers sAggregatedPointers = new InputPointers(
+ Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
+ private static int sLastRecognitionPointSize = 0; // synchronized using sAggregatedPointers
+ private static long sLastRecognitionTime = 0; // synchronized using sAggregatedPointers
+
+ private final GestureStrokeRecognitionPoints mRecognitionPoints;
+
+ public BatchInputArbiter(final int pointerId, final GestureStrokeRecognitionParams params) {
+ mRecognitionPoints = new GestureStrokeRecognitionPoints(pointerId, params);
+ }
+
+ public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
+ mRecognitionPoints.setKeyboardGeometry(keyWidth, keyboardHeight);
+ }
+
+ /**
+ * Calculate elapsed time since the first gesture down.
+ * @param eventTime the time of this event.
+ * @return the elapsed time in millisecond from the first gesture down.
+ */
+ public int getElapsedTimeSinceFirstDown(final long eventTime) {
+ return (int)(eventTime - sGestureFirstDownTime);
+ }
+
+ /**
+ * Add a down event point.
+ * @param x the x-coordinate of this down event.
+ * @param y the y-coordinate of this down event.
+ * @param downEventTime the time of this down event.
+ * @param lastLetterTypingTime the last typing input time.
+ * @param activePointerCount the number of active pointers when this pointer down event occurs.
+ */
+ public void addDownEventPoint(final int x, final int y, final long downEventTime,
+ final long lastLetterTypingTime, final int activePointerCount) {
+ if (activePointerCount == 1) {
+ sGestureFirstDownTime = downEventTime;
+ }
+ final int elapsedTimeSinceFirstDown = getElapsedTimeSinceFirstDown(downEventTime);
+ final int elapsedTimeSinceLastTyping = (int)(downEventTime - lastLetterTypingTime);
+ mRecognitionPoints.addDownEventPoint(
+ x, y, elapsedTimeSinceFirstDown, elapsedTimeSinceLastTyping);
+ }
+
+ /**
+ * Add a move event point.
+ * @param x the x-coordinate of this move event.
+ * @param y the y-coordinate of this move event.
+ * @param moveEventTime the time of this move event.
+ * @param isMajorEvent false if this is a historical move event.
+ * @param listener {@link BatchInputArbiterListener#onStartUpdateBatchInputTimer()} of this
+ * <code>listener</code> may be called if enough move points have been added.
+ * @return true if this move event occurs on the valid gesture area.
+ */
+ public boolean addMoveEventPoint(final int x, final int y, final long moveEventTime,
+ final boolean isMajorEvent, final BatchInputArbiterListener listener) {
+ final int beforeLength = mRecognitionPoints.getLength();
+ final boolean onValidArea = mRecognitionPoints.addEventPoint(
+ x, y, getElapsedTimeSinceFirstDown(moveEventTime), isMajorEvent);
+ if (mRecognitionPoints.getLength() > beforeLength) {
+ listener.onStartUpdateBatchInputTimer();
+ }
+ return onValidArea;
+ }
+
+ /**
+ * Determine whether the batch input has started or not.
+ * @param listener {@link BatchInputArbiterListener#onStartBatchInput()} of this
+ * <code>listener</code> will be called when the batch input has started successfully.
+ * @return true if the batch input has started successfully.
+ */
+ public boolean mayStartBatchInput(final BatchInputArbiterListener listener) {
+ if (!mRecognitionPoints.isStartOfAGesture()) {
+ return false;
+ }
+ synchronized (sAggregatedPointers) {
+ sAggregatedPointers.reset();
+ sLastRecognitionPointSize = 0;
+ sLastRecognitionTime = 0;
+ listener.onStartBatchInput();
+ }
+ return true;
+ }
+
+ /**
+ * Add synthetic move event point. After adding the point,
+ * {@link #updateBatchInput(long,BatchInputArbiterListener)} will be called internally.
+ * @param syntheticMoveEventTime the synthetic move event time.
+ * @param listener the listener to be passed to
+ * {@link #updateBatchInput(long,BatchInputArbiterListener)}.
+ */
+ public void updateBatchInputByTimer(final long syntheticMoveEventTime,
+ final BatchInputArbiterListener listener) {
+ mRecognitionPoints.duplicateLastPointWith(
+ getElapsedTimeSinceFirstDown(syntheticMoveEventTime));
+ updateBatchInput(syntheticMoveEventTime, listener);
+ }
+
+ /**
+ * Determine whether we have enough gesture points to lookup dictionary.
+ * @param moveEventTime the time of this move event.
+ * @param listener {@link BatchInputArbiterListener#onUpdateBatchInput(InputPointers,long)} of
+ * this <code>listener</code> will be called when enough event points we have. Also
+ * {@link BatchInputArbiterListener#onStartUpdateBatchInputTimer()} will be called to have
+ * possible future synthetic move event.
+ */
+ public void updateBatchInput(final long moveEventTime,
+ final BatchInputArbiterListener listener) {
+ synchronized (sAggregatedPointers) {
+ mRecognitionPoints.appendIncrementalBatchPoints(sAggregatedPointers);
+ final int size = sAggregatedPointers.getPointerSize();
+ if (size > sLastRecognitionPointSize && mRecognitionPoints.hasRecognitionTimePast(
+ moveEventTime, sLastRecognitionTime)) {
+ listener.onUpdateBatchInput(sAggregatedPointers, moveEventTime);
+ listener.onStartUpdateBatchInputTimer();
+ // The listener may change the size of the pointers (when auto-committing
+ // for example), so we need to get the size from the pointers again.
+ sLastRecognitionPointSize = sAggregatedPointers.getPointerSize();
+ sLastRecognitionTime = moveEventTime;
+ }
+ }
+ }
+
+ /**
+ * Determine whether the batch input has ended successfully or continues.
+ * @param upEventTime the time of this up event.
+ * @param activePointerCount the number of active pointers when this pointer up event occurs.
+ * @param listener {@link BatchInputArbiterListener#onEndBatchInput(InputPointers,long)} of this
+ * <code>listener</code> will be called when the batch input has started successfully.
+ * @return true if the batch input has ended successfully.
+ */
+ public boolean mayEndBatchInput(final long upEventTime, final int activePointerCount,
+ final BatchInputArbiterListener listener) {
+ synchronized (sAggregatedPointers) {
+ mRecognitionPoints.appendAllBatchPoints(sAggregatedPointers);
+ if (activePointerCount == 1) {
+ listener.onEndBatchInput(sAggregatedPointers, upEventTime);
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java b/java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java
new file mode 100644
index 000000000..e0589fc97
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+
+// This hack is applied to certain classes of tablets.
+public final class BogusMoveEventDetector {
+ private static final String TAG = BogusMoveEventDetector.class.getSimpleName();
+ private static final boolean DEBUG_MODE = LatinImeLogger.sDBG;
+
+ // Move these thresholds to resource.
+ // These thresholds' unit is a diagonal length of a key.
+ private static final float BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD = 0.53f;
+ private static final float BOGUS_MOVE_RADIUS_THRESHOLD = 1.14f;
+
+ private static boolean sNeedsProximateBogusDownMoveUpEventHack;
+
+ public static void init(final Resources res) {
+ // The proximate bogus down move up event hack is needed for a device such like,
+ // 1) is large tablet, or 2) is small tablet and the screen density is less than hdpi.
+ // Though it seems odd to use screen density as criteria of the quality of the touch
+ // screen, the small table that has a less density screen than hdpi most likely has been
+ // made with the touch screen that needs the hack.
+ final int screenMetrics = res.getInteger(R.integer.config_screen_metrics);
+ final boolean isLargeTablet = (screenMetrics == Constants.SCREEN_METRICS_LARGE_TABLET);
+ final boolean isSmallTablet = (screenMetrics == Constants.SCREEN_METRICS_SMALL_TABLET);
+ final int densityDpi = res.getDisplayMetrics().densityDpi;
+ final boolean hasLowDensityScreen = (densityDpi < DisplayMetrics.DENSITY_HIGH);
+ final boolean needsTheHack = isLargeTablet || (isSmallTablet && hasLowDensityScreen);
+ if (DEBUG_MODE) {
+ final int sw = res.getConfiguration().smallestScreenWidthDp;
+ Log.d(TAG, "needsProximateBogusDownMoveUpEventHack=" + needsTheHack
+ + " smallestScreenWidthDp=" + sw + " densityDpi=" + densityDpi
+ + " screenMetrics=" + screenMetrics);
+ }
+ sNeedsProximateBogusDownMoveUpEventHack = needsTheHack;
+ }
+
+ private int mAccumulatedDistanceThreshold;
+ private int mRadiusThreshold;
+
+ // Accumulated distance from actual and artificial down keys.
+ /* package */ int mAccumulatedDistanceFromDownKey;
+ private int mActualDownX;
+ private int mActualDownY;
+
+ public void setKeyboardGeometry(final int keyWidth, final int keyHeight) {
+ final float keyDiagonal = (float)Math.hypot(keyWidth, keyHeight);
+ mAccumulatedDistanceThreshold = (int)(
+ keyDiagonal * BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD);
+ mRadiusThreshold = (int)(keyDiagonal * BOGUS_MOVE_RADIUS_THRESHOLD);
+ }
+
+ public void onActualDownEvent(final int x, final int y) {
+ mActualDownX = x;
+ mActualDownY = y;
+ }
+
+ public void onDownKey() {
+ mAccumulatedDistanceFromDownKey = 0;
+ }
+
+ public void onMoveKey(final int distance) {
+ mAccumulatedDistanceFromDownKey += distance;
+ }
+
+ public boolean hasTraveledLongDistance(final int x, final int y) {
+ if (!sNeedsProximateBogusDownMoveUpEventHack) {
+ return false;
+ }
+ final int dx = Math.abs(x - mActualDownX);
+ final int dy = Math.abs(y - mActualDownY);
+ // A bogus move event should be a horizontal movement. A vertical movement might be
+ // a sloppy typing and should be ignored.
+ return dx >= dy && mAccumulatedDistanceFromDownKey >= mAccumulatedDistanceThreshold;
+ }
+
+ public int getAccumulatedDistanceFromDownKey() {
+ return mAccumulatedDistanceFromDownKey;
+ }
+
+ public int getDistanceFromDownEvent(final int x, final int y) {
+ return getDistance(x, y, mActualDownX, mActualDownY);
+ }
+
+ private static int getDistance(final int x1, final int y1, final int x2, final int y2) {
+ return (int)Math.hypot(x1 - x2, y1 - y2);
+ }
+
+ public boolean isCloseToActualDownEvent(final int x, final int y) {
+ return sNeedsProximateBogusDownMoveUpEventHack
+ && getDistanceFromDownEvent(x, y) < mRadiusThreshold;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
index 4ccecb2f0..dce7fc57e 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.StringUtils;
import android.text.TextUtils;
@@ -29,15 +30,16 @@ import android.text.TextUtils;
* marker. An output text may consist of multiple code points separated by comma.
* The format of the codesArray element should be:
* <pre>
- * codePointInHex[,codePoint2InHex]*(|outputTextCodePointInHex[,outputTextCodePoint2InHex]*)?
+ * label1[,label2]*(|outputText1[,outputText2]*(|minSupportSdkVersion)?)?
* </pre>
*/
// TODO: Write unit tests for this class.
public final class CodesArrayParser {
// Constants for parsing.
- private static final char COMMA = ',';
- private static final String VERTICAL_BAR_STRING = "\\|";
- private static final String COMMA_STRING = ",";
+ private static final char COMMA = Constants.CODE_COMMA;
+ private static final String COMMA_REGEX = StringUtils.newSingleCodePointString(COMMA);
+ private static final String VERTICAL_BAR_REGEX = // "\\|"
+ new String(new char[] { Constants.CODE_BACKSLASH, Constants.CODE_VERTICAL_BAR });
private static final int BASE_HEX = 16;
private CodesArrayParser() {
@@ -45,7 +47,7 @@ public final class CodesArrayParser {
}
private static String getLabelSpec(final String codesArraySpec) {
- final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1);
+ final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1);
if (strs.length <= 1) {
return codesArraySpec;
}
@@ -55,7 +57,7 @@ public final class CodesArrayParser {
public static String parseLabel(final String codesArraySpec) {
final String labelSpec = getLabelSpec(codesArraySpec);
final StringBuilder sb = new StringBuilder();
- for (final String codeInHex : labelSpec.split(COMMA_STRING)) {
+ for (final String codeInHex : labelSpec.split(COMMA_REGEX)) {
final int codePoint = Integer.parseInt(codeInHex, BASE_HEX);
sb.appendCodePoint(codePoint);
}
@@ -63,17 +65,15 @@ public final class CodesArrayParser {
}
private static String getCodeSpec(final String codesArraySpec) {
- final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1);
+ final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1);
if (strs.length <= 1) {
return codesArraySpec;
}
return TextUtils.isEmpty(strs[1]) ? strs[0] : strs[1];
}
- // codesArraySpec consists of:
- // <label>|<code0>,<code1>,...|<minSupportSdkVersion>
public static int getMinSupportSdkVersion(final String codesArraySpec) {
- final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1);
+ final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1);
if (strs.length <= 2) {
return 0;
}
@@ -98,7 +98,7 @@ public final class CodesArrayParser {
return null;
}
final StringBuilder sb = new StringBuilder();
- for (final String codeInHex : codeSpec.split(COMMA_STRING)) {
+ for (final String codeInHex : codeSpec.split(COMMA_REGEX)) {
final int codePoint = Integer.parseInt(codeInHex, BASE_HEX);
sb.appendCodePoint(codePoint);
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
new file mode 100644
index 000000000..df82becae
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.os.Message;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.internal.DrawingHandler.Callbacks;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
+
+// TODO: Separate this class into KeyPreviewHandler and BatchInputPreviewHandler or so.
+public class DrawingHandler extends LeakGuardHandlerWrapper<Callbacks> {
+ public interface Callbacks {
+ public void dismissKeyPreviewWithoutDelay(Key key);
+ public void dismissAllKeyPreviews();
+ public void showGestureFloatingPreviewText(SuggestedWords suggestedWords);
+ }
+
+ private static final int MSG_DISMISS_KEY_PREVIEW = 0;
+ private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
+
+ public DrawingHandler(final Callbacks ownerInstance) {
+ super(ownerInstance);
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ final Callbacks callbacks = getOwnerInstance();
+ if (callbacks == null) {
+ return;
+ }
+ switch (msg.what) {
+ case MSG_DISMISS_KEY_PREVIEW:
+ callbacks.dismissKeyPreviewWithoutDelay((Key)msg.obj);
+ break;
+ case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
+ callbacks.showGestureFloatingPreviewText(SuggestedWords.EMPTY);
+ break;
+ }
+ }
+
+ public void dismissKeyPreview(final long delay, final Key key) {
+ sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, key), delay);
+ }
+
+ private void cancelAllDismissKeyPreviews() {
+ removeMessages(MSG_DISMISS_KEY_PREVIEW);
+ final Callbacks callbacks = getOwnerInstance();
+ if (callbacks == null) {
+ return;
+ }
+ callbacks.dismissAllKeyPreviews();
+ }
+
+ public void dismissGestureFloatingPreviewText(final long delay) {
+ sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
+ }
+
+ public void cancelAllMessages() {
+ cancelAllDismissKeyPreviews();
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java
index 4c8607da8..fdc2458d4 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java
@@ -29,12 +29,12 @@ import com.android.inputmethod.latin.utils.CoordinateUtils;
import java.util.ArrayList;
-public final class PreviewPlacerView extends RelativeLayout {
+public final class DrawingPreviewPlacerView extends RelativeLayout {
private final int[] mKeyboardViewOrigin = CoordinateUtils.newInstance();
private final ArrayList<AbstractDrawingPreview> mPreviews = CollectionUtils.newArrayList();
- public PreviewPlacerView(final Context context, final AttributeSet attrs) {
+ public DrawingPreviewPlacerView(final Context context, final AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
}
@@ -55,20 +55,24 @@ public final class PreviewPlacerView extends RelativeLayout {
CoordinateUtils.copy(mKeyboardViewOrigin, originCoords);
final int count = mPreviews.size();
for (int i = 0; i < count; i++) {
- mPreviews.get(i).setKeyboardGeometry(originCoords, width, height);
+ mPreviews.get(i).setKeyboardViewGeometry(originCoords, width, height);
}
}
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
+ public void deallocateMemory() {
final int count = mPreviews.size();
for (int i = 0; i < count; i++) {
- mPreviews.get(i).onDetachFromWindow();
+ mPreviews.get(i).onDeallocateMemory();
}
}
@Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ deallocateMemory();
+ }
+
+ @Override
public void onDraw(final Canvas canvas) {
super.onDraw(canvas);
final int originX = CoordinateUtils.x(mKeyboardViewOrigin);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
index 3133e54be..e2fd39017 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -25,7 +25,7 @@ import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.JsonUtils;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -53,7 +53,7 @@ public class DynamicGridKeyboard extends Keyboard {
private Key[] mCachedGridKeys;
public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard,
- final int maxKeyCount, final int categoryId, final int categoryPageId) {
+ final int maxKeyCount, final int categoryId) {
super(templateKeyboard);
final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0);
final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1);
@@ -124,7 +124,7 @@ public class DynamicGridKeyboard extends Keyboard {
final int keyY0 = getKeyY0(index);
final int keyX1 = getKeyX1(index);
final int keyY1 = getKeyY1(index);
- gridKey.updateCorrdinates(keyX0, keyY0, keyX1, keyY1);
+ gridKey.updateCoordinates(keyX0, keyY0, keyX1, keyY1);
index++;
}
}
@@ -139,36 +139,48 @@ public class DynamicGridKeyboard extends Keyboard {
keys.add(key.getCode());
}
}
- final String jsonStr = StringUtils.listToJsonStr(keys);
+ final String jsonStr = JsonUtils.listToJsonStr(keys);
Settings.writeEmojiRecentKeys(mPrefs, jsonStr);
}
- private static Key getKey(final Collection<DynamicGridKeyboard> keyboards, final Object o) {
+ private static Key getKeyByCode(final Collection<DynamicGridKeyboard> keyboards,
+ final int code) {
+ for (final DynamicGridKeyboard keyboard : keyboards) {
+ final Key key = keyboard.getKey(code);
+ if (key != null) {
+ return key;
+ }
+ }
+ return null;
+ }
+
+ private static Key getKeyByOutputText(final Collection<DynamicGridKeyboard> keyboards,
+ final String outputText) {
for (final DynamicGridKeyboard kbd : keyboards) {
- if (o instanceof Integer) {
- final int code = (Integer) o;
- final Key key = kbd.getKey(code);
- if (key != null) {
- return key;
- }
- } else if (o instanceof String) {
- final String outputText = (String) o;
- final Key key = kbd.getKeyFromOutputText(outputText);
- if (key != null) {
- return key;
- }
- } else {
- Log.w(TAG, "Invalid object: " + o);
+ final Key key = kbd.getKeyFromOutputText(outputText);
+ if (key != null) {
+ return key;
}
}
return null;
}
- public void loadRecentKeys(Collection<DynamicGridKeyboard> keyboards) {
+ public void loadRecentKeys(final Collection<DynamicGridKeyboard> keyboards) {
final String str = Settings.readEmojiRecentKeys(mPrefs);
- final List<Object> keys = StringUtils.jsonStrToList(str);
+ final List<Object> keys = JsonUtils.jsonStrToList(str);
for (final Object o : keys) {
- addKeyLast(getKey(keyboards, o));
+ final Key key;
+ if (o instanceof Integer) {
+ final int code = (Integer)o;
+ key = getKeyByCode(keyboards, code);
+ } else if (o instanceof String) {
+ final String outputText = (String)o;
+ key = getKeyByOutputText(keyboards, outputText);
+ } else {
+ Log.w(TAG, "Invalid object: " + o);
+ continue;
+ }
+ addKeyLast(key);
}
}
@@ -217,7 +229,7 @@ public class DynamicGridKeyboard extends Keyboard {
super(originalKey);
}
- public void updateCorrdinates(final int x0, final int y0, final int x1, final int y1) {
+ public void updateCoordinates(final int x0, final int y0, final int x1, final int y1) {
mCurrentX = x0;
mCurrentY = y0;
getHitBox().set(x0, y0, x1, y1);
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java
index 967448c28..d57ea5a94 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.ResourceUtils;
@@ -37,22 +37,22 @@ public class EmojiLayoutParams {
private final int mBottomPadding;
private final int mTopPadding;
- public EmojiLayoutParams(Resources res) {
+ public EmojiLayoutParams(final Resources res) {
final int defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
final int defaultKeyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
- mKeyVerticalGap = (int) res.getFraction(R.fraction.key_bottom_gap_holo,
- (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
- mBottomPadding = (int) res.getFraction(R.fraction.keyboard_bottom_padding_holo,
- (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
- mTopPadding = (int) res.getFraction(R.fraction.keyboard_top_padding_holo,
- (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
- mKeyHorizontalGap = (int) (res.getFraction(R.fraction.key_horizontal_gap_holo,
+ mKeyVerticalGap = (int) res.getFraction(R.fraction.config_key_vertical_gap_holo,
+ defaultKeyboardHeight, defaultKeyboardHeight);
+ mBottomPadding = (int) res.getFraction(R.fraction.config_keyboard_bottom_padding_holo,
+ defaultKeyboardHeight, defaultKeyboardHeight);
+ mTopPadding = (int) res.getFraction(R.fraction.config_keyboard_top_padding_holo,
+ defaultKeyboardHeight, defaultKeyboardHeight);
+ mKeyHorizontalGap = (int) (res.getFraction(R.fraction.config_key_horizontal_gap_holo,
defaultKeyboardWidth, defaultKeyboardWidth));
mEmojiCategoryPageIdViewHeight =
- (int) (res.getDimension(R.dimen.emoji_category_page_id_height));
+ (int) (res.getDimension(R.dimen.config_emoji_category_page_id_height));
final int baseheight = defaultKeyboardHeight - mBottomPadding - mTopPadding
+ mKeyVerticalGap;
- mEmojiActionBarHeight = ((int) baseheight) / DEFAULT_KEYBOARD_ROWS
+ mEmojiActionBarHeight = baseheight / DEFAULT_KEYBOARD_ROWS
- (mKeyVerticalGap - mBottomPadding) / 2;
mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight
- mEmojiCategoryPageIdViewHeight;
@@ -60,26 +60,30 @@ public class EmojiLayoutParams {
mEmojiKeyboardHeight = mEmojiPagerHeight - mEmojiPagerBottomMargin - 1;
}
- public void setPagerProperties(ViewPager vp) {
+ public void setPagerProperties(final ViewPager vp) {
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) vp.getLayoutParams();
lp.height = mEmojiKeyboardHeight;
lp.bottomMargin = mEmojiPagerBottomMargin;
vp.setLayoutParams(lp);
}
- public void setCategoryPageIdViewProperties(LinearLayout ll) {
+ public void setCategoryPageIdViewProperties(final LinearLayout ll) {
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
lp.height = mEmojiCategoryPageIdViewHeight;
ll.setLayoutParams(lp);
}
- public void setActionBarProperties(LinearLayout ll) {
+ public int getActionBarHeight() {
+ return mEmojiActionBarHeight - mBottomPadding;
+ }
+
+ public void setActionBarProperties(final LinearLayout ll) {
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
- lp.height = mEmojiActionBarHeight - mBottomPadding;
+ lp.height = getActionBarHeight();
ll.setLayoutParams(lp);
}
- public void setKeyProperties(ImageView ib) {
+ public void setKeyProperties(final ImageView ib) {
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ib.getLayoutParams();
lp.leftMargin = mKeyHorizontalGap / 2;
lp.rightMargin = mKeyHorizontalGap / 2;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java
index 9cf68d43d..e175a051e 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java
@@ -17,101 +17,57 @@
package com.android.inputmethod.keyboard.internal;
import android.content.Context;
+import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
-import android.widget.ScrollView;
-import android.widget.Scroller;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.latin.R;
/**
- * This is an extended {@link KeyboardView} class that hosts a vertical scroll keyboard.
+ * This is an extended {@link KeyboardView} class that hosts an emoji page keyboard.
* Multi-touch unsupported. No {@link PointerTracker}s. No gesture support.
- * TODO: Vertical scroll capability should be removed from this class because it's no longer used.
*/
// TODO: Implement key popup preview.
-public final class ScrollKeyboardView extends KeyboardView implements
- ScrollViewWithNotifier.ScrollListener, GestureDetector.OnGestureListener {
- private static final boolean PAGINATION = false;
+public final class EmojiPageKeyboardView extends KeyboardView implements
+ GestureDetector.OnGestureListener {
+ private static final long KEY_PRESS_DELAY_TIME = 250; // msec
+ private static final long KEY_RELEASE_DELAY_TIME = 30; // msec
- public interface OnKeyClickListener {
- public void onKeyClick(Key key);
+ public interface OnKeyEventListener {
+ public void onPressKey(Key key);
+ public void onReleaseKey(Key key);
}
- private static final OnKeyClickListener EMPTY_LISTENER = new OnKeyClickListener() {
+ private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() {
@Override
- public void onKeyClick(final Key key) {}
+ public void onPressKey(final Key key) {}
+ @Override
+ public void onReleaseKey(final Key key) {}
};
- private OnKeyClickListener mListener = EMPTY_LISTENER;
- private final KeyDetector mKeyDetector = new KeyDetector(0.0f /*keyHysteresisDistance */);
+ private OnKeyEventListener mListener = EMPTY_LISTENER;
+ private final KeyDetector mKeyDetector = new KeyDetector();
private final GestureDetector mGestureDetector;
- private final Scroller mScroller;
- private ScrollViewWithNotifier mScrollView;
-
- public ScrollKeyboardView(final Context context, final AttributeSet attrs) {
+ public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
- public ScrollKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
+ public EmojiPageKeyboardView(final Context context, final AttributeSet attrs,
+ final int defStyle) {
super(context, attrs, defStyle);
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */);
- mScroller = new Scroller(context);
- }
-
- public void setScrollView(final ScrollViewWithNotifier scrollView) {
- mScrollView = scrollView;
- scrollView.setScrollListener(this);
- }
-
- private final Runnable mScrollTask = new Runnable() {
- @Override
- public void run() {
- final Scroller scroller = mScroller;
- final ScrollView scrollView = mScrollView;
- scroller.computeScrollOffset();
- scrollView.scrollTo(0, scroller.getCurrY());
- if (!scroller.isFinished()) {
- scrollView.post(this);
- }
- }
- };
-
- // {@link ScrollViewWithNotified#ScrollListener} methods.
- @Override
- public void notifyScrollChanged(final int scrollX, final int scrollY, final int oldX,
- final int oldY) {
- if (PAGINATION) {
- mScroller.forceFinished(true /* finished */);
- mScrollView.removeCallbacks(mScrollTask);
- final int currentTop = mScrollView.getScrollY();
- final int pageHeight = getKeyboard().mBaseHeight;
- final int lastPageNo = currentTop / pageHeight;
- final int lastPageTop = lastPageNo * pageHeight;
- final int nextPageNo = lastPageNo + 1;
- final int nextPageTop = Math.min(nextPageNo * pageHeight, getHeight() - pageHeight);
- final int scrollTo = (currentTop - lastPageTop) < (nextPageTop - currentTop)
- ? lastPageTop : nextPageTop;
- final int deltaY = scrollTo - currentTop;
- mScroller.startScroll(0, currentTop, 0, deltaY, 300);
- mScrollView.post(mScrollTask);
- }
- }
-
- @Override
- public void notifyOverScrolled(final int scrollX, final int scrollY, final boolean clampedX,
- final boolean clampedY) {
- releaseCurrentKey();
+ mHandler = new Handler();
}
- public void setOnKeyClickListener(final OnKeyClickListener listener) {
+ public void setOnKeyEventListener(final OnKeyEventListener listener) {
mListener = listener;
}
@@ -139,8 +95,10 @@ public final class ScrollKeyboardView extends KeyboardView implements
return true;
}
- // {@link GestureDetector#OnGestureListener} methods.
+ // {@link GestureEnabler#OnGestureListener} methods.
private Key mCurrentKey;
+ private Runnable mPendingKeyDown;
+ private final Handler mHandler;
private Key getKey(final MotionEvent e) {
final int index = e.getActionIndex();
@@ -150,6 +108,8 @@ public final class ScrollKeyboardView extends KeyboardView implements
}
public void releaseCurrentKey() {
+ mHandler.removeCallbacks(mPendingKeyDown);
+ mPendingKeyDown = null;
final Key currentKey = mCurrentKey;
if (currentKey == null) {
return;
@@ -167,9 +127,17 @@ public final class ScrollKeyboardView extends KeyboardView implements
if (key == null) {
return false;
}
- // TODO: May call {@link KeyboardActionListener#onPressKey(int,int,boolean)}.
- key.onPressed();
- invalidateKey(key);
+ // Do not trigger key-down effect right now in case this is actually a fling action.
+ mPendingKeyDown = new Runnable() {
+ @Override
+ public void run() {
+ mPendingKeyDown = null;
+ key.onPressed();
+ invalidateKey(key);
+ mListener.onPressKey(key);
+ }
+ };
+ mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME);
return false;
}
@@ -181,14 +149,28 @@ public final class ScrollKeyboardView extends KeyboardView implements
@Override
public boolean onSingleTapUp(final MotionEvent e) {
final Key key = getKey(e);
+ final Runnable pendingKeyDown = mPendingKeyDown;
+ final Key currentKey = mCurrentKey;
releaseCurrentKey();
if (key == null) {
return false;
}
- // TODO: May call {@link KeyboardActionListener#onReleaseKey(int,boolean)}.
- key.onReleased();
- invalidateKey(key);
- mListener.onKeyClick(key);
+ if (key == currentKey && pendingKeyDown != null) {
+ pendingKeyDown.run();
+ // Trigger key-release event a little later so that a user can see visual feedback.
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ key.onReleased();
+ invalidateKey(key);
+ mListener.onReleaseKey(key);
+ }
+ }, KEY_RELEASE_DELAY_TIME);
+ } else {
+ key.onReleased();
+ invalidateKey(key);
+ mListener.onReleaseKey(key);
+ }
return true;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureEnabler.java b/java/src/com/android/inputmethod/keyboard/internal/GestureEnabler.java
new file mode 100644
index 000000000..7d14ae924
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureEnabler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.accessibility.AccessibilityUtils;
+
+public final class GestureEnabler {
+ /** True if we should handle gesture events. */
+ private boolean mShouldHandleGesture;
+ private boolean mMainDictionaryAvailable;
+ private boolean mGestureHandlingEnabledByInputField;
+ private boolean mGestureHandlingEnabledByUser;
+
+ private void updateGestureHandlingMode() {
+ mShouldHandleGesture = mMainDictionaryAvailable
+ && mGestureHandlingEnabledByInputField
+ && mGestureHandlingEnabledByUser
+ && !AccessibilityUtils.getInstance().isTouchExplorationEnabled();
+ }
+
+ // Note that this method is called from a non-UI thread.
+ public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
+ mMainDictionaryAvailable = mainDictionaryAvailable;
+ updateGestureHandlingMode();
+ }
+
+ public void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
+ mGestureHandlingEnabledByUser = gestureHandlingEnabledByUser;
+ updateGestureHandlingMode();
+ }
+
+ public void setPasswordMode(final boolean passwordMode) {
+ mGestureHandlingEnabledByInputField = !passwordMode;
+ updateGestureHandlingMode();
+ }
+
+ public boolean shouldHandleGesture() {
+ return mShouldHandleGesture;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java
index c6dd9e100..2fa703083 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java
@@ -42,7 +42,7 @@ import com.android.inputmethod.latin.utils.CoordinateUtils;
* @attr ref R.styleable#KeyboardView_gestureFloatingPreviewVerticalPadding
* @attr ref R.styleable#KeyboardView_gestureFloatingPreviewRoundRadius
*/
-public class GestureFloatingPreviewText extends AbstractDrawingPreview {
+public class GestureFloatingTextDrawingPreview extends AbstractDrawingPreview {
protected static final class GesturePreviewTextParams {
public final int mGesturePreviewTextOffset;
public final int mGesturePreviewTextHeight;
@@ -100,11 +100,16 @@ public class GestureFloatingPreviewText extends AbstractDrawingPreview {
private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
private final int[] mLastPointerCoords = CoordinateUtils.newInstance();
- public GestureFloatingPreviewText(final View drawingView, final TypedArray typedArray) {
+ public GestureFloatingTextDrawingPreview(final View drawingView, final TypedArray typedArray) {
super(drawingView);
mParams = new GesturePreviewTextParams(typedArray);
}
+ @Override
+ public void onDeallocateMemory() {
+ // Nothing to do here.
+ }
+
public void setSuggetedWords(final SuggestedWords suggestedWords) {
if (!isPreviewEnabled()) {
return;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingParams.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingParams.java
new file mode 100644
index 000000000..478639d2d
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingParams.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.TypedArray;
+
+import com.android.inputmethod.latin.R;
+
+/**
+ * This class holds parameters to control how a gesture stroke is sampled and drawn on the screen.
+ *
+ * @attr ref R.styleable#MainKeyboardView_gestureTrailMinSamplingDistance
+ * @attr ref R.styleable#MainKeyboardView_gestureTrailMaxInterpolationAngularThreshold
+ * @attr ref R.styleable#MainKeyboardView_gestureTrailMaxInterpolationDistanceThreshold
+ * @attr ref R.styleable#MainKeyboardView_gestureTrailMaxInterpolationSegments
+ */
+public final class GestureStrokeDrawingParams {
+ public final double mMinSamplingDistance; // in pixel
+ public final double mMaxInterpolationAngularThreshold; // in radian
+ public final double mMaxInterpolationDistanceThreshold; // in pixel
+ public final int mMaxInterpolationSegments;
+
+ private static final float DEFAULT_MIN_SAMPLING_DISTANCE = 0.0f; // dp
+ private static final int DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD = 15; // in degree
+ private static final float DEFAULT_MAX_INTERPOLATION_DISTANCE_THRESHOLD = 0.0f; // dp
+ private static final int DEFAULT_MAX_INTERPOLATION_SEGMENTS = 4;
+
+ public GestureStrokeDrawingParams(final TypedArray mainKeyboardViewAttr) {
+ mMinSamplingDistance = mainKeyboardViewAttr.getDimension(
+ R.styleable.MainKeyboardView_gestureTrailMinSamplingDistance,
+ DEFAULT_MIN_SAMPLING_DISTANCE);
+ final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable
+ .MainKeyboardView_gestureTrailMaxInterpolationAngularThreshold, 0);
+ mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0)
+ ? Math.toRadians(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD)
+ : Math.toRadians(interpolationAngularDegree);
+ mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable
+ .MainKeyboardView_gestureTrailMaxInterpolationDistanceThreshold,
+ DEFAULT_MAX_INTERPOLATION_DISTANCE_THRESHOLD);
+ mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger(
+ R.styleable.MainKeyboardView_gestureTrailMaxInterpolationSegments,
+ DEFAULT_MAX_INTERPOLATION_SEGMENTS);
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java
index ecc67dd44..7d09e9d2f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java
@@ -16,19 +16,19 @@
package com.android.inputmethod.keyboard.internal;
-import android.content.res.TypedArray;
-
-import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.ResizableIntArray;
-public final class GestureStrokeWithPreviewPoints extends GestureStroke {
+/**
+ * This class holds drawing points to represent a gesture stroke on the screen.
+ */
+public final class GestureStrokeDrawingPoints {
public static final int PREVIEW_CAPACITY = 256;
private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
- private final GestureStrokePreviewParams mPreviewParams;
+ private final GestureStrokeDrawingParams mDrawingParams;
private int mStrokeId;
private int mLastPreviewSize;
@@ -39,56 +39,11 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
private int mLastY;
private double mDistanceFromLastSample;
- public static final class GestureStrokePreviewParams {
- public final double mMinSamplingDistance; // in pixel
- public final double mMaxInterpolationAngularThreshold; // in radian
- public final double mMaxInterpolationDistanceThreshold; // in pixel
- public final int mMaxInterpolationSegments;
-
- public static final GestureStrokePreviewParams DEFAULT = new GestureStrokePreviewParams();
-
- private static final int DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD = 15; // in degree
-
- private GestureStrokePreviewParams() {
- mMinSamplingDistance = 0.0d;
- mMaxInterpolationAngularThreshold =
- degreeToRadian(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD);
- mMaxInterpolationDistanceThreshold = mMinSamplingDistance;
- mMaxInterpolationSegments = 4;
- }
-
- private static double degreeToRadian(final int degree) {
- return degree / 180.0d * Math.PI;
- }
-
- public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) {
- mMinSamplingDistance = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_gestureTrailMinSamplingDistance,
- (float)DEFAULT.mMinSamplingDistance);
- final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable
- .MainKeyboardView_gestureTrailMaxInterpolationAngularThreshold, 0);
- mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0)
- ? DEFAULT.mMaxInterpolationAngularThreshold
- : degreeToRadian(interpolationAngularDegree);
- mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable
- .MainKeyboardView_gestureTrailMaxInterpolationDistanceThreshold,
- (float)DEFAULT.mMaxInterpolationDistanceThreshold);
- mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger(
- R.styleable.MainKeyboardView_gestureTrailMaxInterpolationSegments,
- DEFAULT.mMaxInterpolationSegments);
- }
- }
-
- public GestureStrokeWithPreviewPoints(final int pointerId,
- final GestureStrokeParams strokeParams,
- final GestureStrokePreviewParams previewParams) {
- super(pointerId, strokeParams);
- mPreviewParams = previewParams;
+ public GestureStrokeDrawingPoints(final GestureStrokeDrawingParams drawingParams) {
+ mDrawingParams = drawingParams;
}
- @Override
- protected void reset() {
- super.reset();
+ private void reset() {
mStrokeId++;
mLastPreviewSize = 0;
mLastInterpolatedPreviewIndex = 0;
@@ -101,28 +56,29 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
return mStrokeId;
}
+ public void onDownEvent(final int x, final int y, final int elapsedTimeSinceFirstDown) {
+ reset();
+ onMoveEvent(x, y, elapsedTimeSinceFirstDown);
+ }
+
private boolean needsSampling(final int x, final int y) {
mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY);
mLastX = x;
mLastY = y;
final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0);
- if (mDistanceFromLastSample >= mPreviewParams.mMinSamplingDistance || isDownEvent) {
+ if (mDistanceFromLastSample >= mDrawingParams.mMinSamplingDistance || isDownEvent) {
mDistanceFromLastSample = 0.0d;
return true;
}
return false;
}
- @Override
- public boolean addPointOnKeyboard(final int x, final int y, final int time,
- final boolean isMajorEvent) {
+ public void onMoveEvent(final int x, final int y, final int elapsedTimeSinceFirstDown) {
if (needsSampling(x, y)) {
- mPreviewEventTimes.add(time);
+ mPreviewEventTimes.add(elapsedTimeSinceFirstDown);
mPreviewXCoordinates.add(x);
mPreviewYCoordinates.add(y);
}
- return super.addPointOnKeyboard(x, y, time, isMajorEvent);
-
}
/**
@@ -132,7 +88,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
* @param xCoords the x-coordinates array of gesture trail to be drawn.
* @param yCoords the y-coordinates array of gesture trail to be drawn.
* @param types the point types array of gesture trail. This is valid only when
- * {@link GestureTrail#DEBUG_SHOW_POINTS} is true.
+ * {@link GestureTrailDrawingPoints#DEBUG_SHOW_POINTS} is true.
*/
public void appendPreviewStroke(final ResizableIntArray eventTimes,
final ResizableIntArray xCoords, final ResizableIntArray yCoords,
@@ -144,8 +100,8 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length);
xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length);
yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
- if (GestureTrail.DEBUG_SHOW_POINTS) {
- types.fill(GestureTrail.POINT_TYPE_SAMPLED, types.getLength(), length);
+ if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) {
+ types.fill(GestureTrailDrawingPoints.POINT_TYPE_SAMPLED, types.getLength(), length);
}
mLastPreviewSize = mPreviewEventTimes.getLength();
}
@@ -162,7 +118,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
* @param xCoords the x-coordinates array of gesture trail to be drawn.
* @param yCoords the y-coordinates array of gesture trail to be drawn.
* @param types the point types array of gesture trail. This is valid only when
- * {@link GestureTrail#DEBUG_SHOW_POINTS} is true.
+ * {@link GestureTrailDrawingPoints#DEBUG_SHOW_POINTS} is true.
* @return the start index of the last interpolated segment of input arrays.
*/
public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex,
@@ -188,12 +144,12 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X);
final double deltaAngle = Math.abs(angularDiff(m2, m1));
final int segmentsByAngle = (int)Math.ceil(
- deltaAngle / mPreviewParams.mMaxInterpolationAngularThreshold);
+ deltaAngle / mDrawingParams.mMaxInterpolationAngularThreshold);
final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X,
mInterpolator.mP1Y - mInterpolator.mP2Y);
final int segmentsByDistance = (int)Math.ceil(deltaDistance
- / mPreviewParams.mMaxInterpolationDistanceThreshold);
- final int segments = Math.min(mPreviewParams.mMaxInterpolationSegments,
+ / mDrawingParams.mMaxInterpolationDistanceThreshold);
+ final int segments = Math.min(mDrawingParams.mMaxInterpolationSegments,
Math.max(segmentsByAngle, segmentsByDistance));
final int t1 = eventTimes.get(d1);
final int dt = pt[p2] - pt[p1];
@@ -201,19 +157,19 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
for (int i = 1; i < segments; i++) {
final float t = i / (float)segments;
mInterpolator.interpolate(t);
- eventTimes.add(d1, (int)(dt * t) + t1);
- xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
- yCoords.add(d1, (int)mInterpolator.mInterpolatedY);
- if (GestureTrail.DEBUG_SHOW_POINTS) {
- types.add(d1, GestureTrail.POINT_TYPE_INTERPOLATED);
+ eventTimes.addAt(d1, (int)(dt * t) + t1);
+ xCoords.addAt(d1, (int)mInterpolator.mInterpolatedX);
+ yCoords.addAt(d1, (int)mInterpolator.mInterpolatedY);
+ if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) {
+ types.addAt(d1, GestureTrailDrawingPoints.POINT_TYPE_INTERPOLATED);
}
d1++;
}
- eventTimes.add(d1, pt[p2]);
- xCoords.add(d1, px[p2]);
- yCoords.add(d1, py[p2]);
- if (GestureTrail.DEBUG_SHOW_POINTS) {
- types.add(d1, GestureTrail.POINT_TYPE_SAMPLED);
+ eventTimes.addAt(d1, pt[p2]);
+ xCoords.addAt(d1, px[p2]);
+ yCoords.addAt(d1, py[p2]);
+ if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) {
+ types.addAt(d1, GestureTrailDrawingPoints.POINT_TYPE_SAMPLED);
}
}
return lastInterpolatedDrawIndex;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionParams.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionParams.java
new file mode 100644
index 000000000..07b14514c
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionParams.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.TypedArray;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+
+/**
+ * This class holds parameters to control how a gesture stroke is sampled and recognized.
+ * This class also has parameters to distinguish gesture input events from fast typing events.
+ *
+ * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping
+ * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo
+ * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance
+ * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime
+ * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold
+ */
+public final class GestureStrokeRecognitionParams {
+ // Static threshold for gesture after fast typing
+ public final int mStaticTimeThresholdAfterFastTyping; // msec
+ // Static threshold for starting gesture detection
+ public final float mDetectFastMoveSpeedThreshold; // keyWidth/sec
+ // Dynamic threshold for gesture after fast typing
+ public final int mDynamicThresholdDecayDuration; // msec
+ // Time based threshold values
+ public final int mDynamicTimeThresholdFrom; // msec
+ public final int mDynamicTimeThresholdTo; // msec
+ // Distance based threshold values
+ public final float mDynamicDistanceThresholdFrom; // keyWidth
+ public final float mDynamicDistanceThresholdTo; // keyWidth
+ // Parameters for gesture sampling
+ public final float mSamplingMinimumDistance; // keyWidth
+ // Parameters for gesture recognition
+ public final int mRecognitionMinimumTime; // msec
+ public final float mRecognitionSpeedThreshold; // keyWidth/sec
+
+ // Default GestureStrokeRecognitionPoints parameters.
+ public static final GestureStrokeRecognitionParams DEFAULT =
+ new GestureStrokeRecognitionParams();
+
+ private GestureStrokeRecognitionParams() {
+ // These parameter values are default and intended for testing.
+ mStaticTimeThresholdAfterFastTyping = 350; // msec
+ mDetectFastMoveSpeedThreshold = 1.5f; // keyWidth/sec
+ mDynamicThresholdDecayDuration = 450; // msec
+ mDynamicTimeThresholdFrom = 300; // msec
+ mDynamicTimeThresholdTo = 20; // msec
+ mDynamicDistanceThresholdFrom = 6.0f; // keyWidth
+ mDynamicDistanceThresholdTo = 0.35f; // keyWidth
+ // The following parameters' change will affect the result of regression test.
+ mSamplingMinimumDistance = 1.0f / 6.0f; // keyWidth
+ mRecognitionMinimumTime = 100; // msec
+ mRecognitionSpeedThreshold = 5.5f; // keyWidth/sec
+ }
+
+ public GestureStrokeRecognitionParams(final TypedArray mainKeyboardViewAttr) {
+ mStaticTimeThresholdAfterFastTyping = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping,
+ DEFAULT.mStaticTimeThresholdAfterFastTyping);
+ mDetectFastMoveSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr,
+ R.styleable.MainKeyboardView_gestureDetectFastMoveSpeedThreshold,
+ DEFAULT.mDetectFastMoveSpeedThreshold);
+ mDynamicThresholdDecayDuration = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureDynamicThresholdDecayDuration,
+ DEFAULT.mDynamicThresholdDecayDuration);
+ mDynamicTimeThresholdFrom = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureDynamicTimeThresholdFrom,
+ DEFAULT.mDynamicTimeThresholdFrom);
+ mDynamicTimeThresholdTo = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureDynamicTimeThresholdTo,
+ DEFAULT.mDynamicTimeThresholdTo);
+ mDynamicDistanceThresholdFrom = ResourceUtils.getFraction(mainKeyboardViewAttr,
+ R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdFrom,
+ DEFAULT.mDynamicDistanceThresholdFrom);
+ mDynamicDistanceThresholdTo = ResourceUtils.getFraction(mainKeyboardViewAttr,
+ R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdTo,
+ DEFAULT.mDynamicDistanceThresholdTo);
+ mSamplingMinimumDistance = ResourceUtils.getFraction(mainKeyboardViewAttr,
+ R.styleable.MainKeyboardView_gestureSamplingMinimumDistance,
+ DEFAULT.mSamplingMinimumDistance);
+ mRecognitionMinimumTime = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureRecognitionMinimumTime,
+ DEFAULT.mRecognitionMinimumTime);
+ mRecognitionSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr,
+ R.styleable.MainKeyboardView_gestureRecognitionSpeedThreshold,
+ DEFAULT.mRecognitionSpeedThreshold);
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java
index f29ade861..e49e538aa 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java
@@ -16,16 +16,18 @@
package com.android.inputmethod.keyboard.internal;
-import android.content.res.TypedArray;
import android.util.Log;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
-import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.ResizableIntArray;
-import com.android.inputmethod.latin.utils.ResourceUtils;
-public class GestureStroke {
- private static final String TAG = GestureStroke.class.getSimpleName();
+/**
+ * This class holds event points to recognize a gesture stroke.
+ * TODO: Should be package private class.
+ */
+public final class GestureStrokeRecognitionPoints {
+ private static final String TAG = GestureStrokeRecognitionPoints.class.getSimpleName();
private static final boolean DEBUG = false;
private static final boolean DEBUG_SPEED = false;
@@ -33,14 +35,15 @@ public class GestureStroke {
// Proportional to the keyboard height.
public static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f;
- public static final int DEFAULT_CAPACITY = 128;
-
private final int mPointerId;
- private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
- private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
- private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
+ private final ResizableIntArray mEventTimes = new ResizableIntArray(
+ Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
+ private final ResizableIntArray mXCoordinates = new ResizableIntArray(
+ Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
+ private final ResizableIntArray mYCoordinates = new ResizableIntArray(
+ Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
- private final GestureStrokeParams mParams;
+ private final GestureStrokeRecognitionParams mRecognitionParams;
private int mKeyWidth; // pixel
private int mMinYCoordinate; // pixel
@@ -64,145 +67,85 @@ public class GestureStroke {
private int mIncrementalRecognitionSize;
private int mLastIncrementalBatchSize;
- public static final class GestureStrokeParams {
- // Static threshold for gesture after fast typing
- public final int mStaticTimeThresholdAfterFastTyping; // msec
- // Static threshold for starting gesture detection
- public final float mDetectFastMoveSpeedThreshold; // keyWidth/sec
- // Dynamic threshold for gesture after fast typing
- public final int mDynamicThresholdDecayDuration; // msec
- // Time based threshold values
- public final int mDynamicTimeThresholdFrom; // msec
- public final int mDynamicTimeThresholdTo; // msec
- // Distance based threshold values
- public final float mDynamicDistanceThresholdFrom; // keyWidth
- public final float mDynamicDistanceThresholdTo; // keyWidth
- // Parameters for gesture sampling
- public final float mSamplingMinimumDistance; // keyWidth
- // Parameters for gesture recognition
- public final int mRecognitionMinimumTime; // msec
- public final float mRecognitionSpeedThreshold; // keyWidth/sec
-
- // Default GestureStroke parameters.
- public static final GestureStrokeParams DEFAULT = new GestureStrokeParams();
-
- private GestureStrokeParams() {
- // These parameter values are default and intended for testing.
- mStaticTimeThresholdAfterFastTyping = 350; // msec
- mDetectFastMoveSpeedThreshold = 1.5f; // keyWidth / sec
- mDynamicThresholdDecayDuration = 450; // msec
- mDynamicTimeThresholdFrom = 300; // msec
- mDynamicTimeThresholdTo = 20; // msec
- mDynamicDistanceThresholdFrom = 6.0f; // keyWidth
- mDynamicDistanceThresholdTo = 0.35f; // keyWidth
- // The following parameters' change will affect the result of regression test.
- mSamplingMinimumDistance = 1.0f / 6.0f; // keyWidth
- mRecognitionMinimumTime = 100; // msec
- mRecognitionSpeedThreshold = 5.5f; // keyWidth / sec
- }
-
- public GestureStrokeParams(final TypedArray mainKeyboardViewAttr) {
- mStaticTimeThresholdAfterFastTyping = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping,
- DEFAULT.mStaticTimeThresholdAfterFastTyping);
- mDetectFastMoveSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr,
- R.styleable.MainKeyboardView_gestureDetectFastMoveSpeedThreshold,
- DEFAULT.mDetectFastMoveSpeedThreshold);
- mDynamicThresholdDecayDuration = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureDynamicThresholdDecayDuration,
- DEFAULT.mDynamicThresholdDecayDuration);
- mDynamicTimeThresholdFrom = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureDynamicTimeThresholdFrom,
- DEFAULT.mDynamicTimeThresholdFrom);
- mDynamicTimeThresholdTo = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureDynamicTimeThresholdTo,
- DEFAULT.mDynamicTimeThresholdTo);
- mDynamicDistanceThresholdFrom = ResourceUtils.getFraction(mainKeyboardViewAttr,
- R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdFrom,
- DEFAULT.mDynamicDistanceThresholdFrom);
- mDynamicDistanceThresholdTo = ResourceUtils.getFraction(mainKeyboardViewAttr,
- R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdTo,
- DEFAULT.mDynamicDistanceThresholdTo);
- mSamplingMinimumDistance = ResourceUtils.getFraction(mainKeyboardViewAttr,
- R.styleable.MainKeyboardView_gestureSamplingMinimumDistance,
- DEFAULT.mSamplingMinimumDistance);
- mRecognitionMinimumTime = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureRecognitionMinimumTime,
- DEFAULT.mRecognitionMinimumTime);
- mRecognitionSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr,
- R.styleable.MainKeyboardView_gestureRecognitionSpeedThreshold,
- DEFAULT.mRecognitionSpeedThreshold);
- }
- }
-
private static final int MSEC_PER_SEC = 1000;
- public GestureStroke(final int pointerId, final GestureStrokeParams params) {
+ // TODO: Make this package private
+ public GestureStrokeRecognitionPoints(final int pointerId,
+ final GestureStrokeRecognitionParams recognitionParams) {
mPointerId = pointerId;
- mParams = params;
+ mRecognitionParams = recognitionParams;
}
+ // TODO: Make this package private
public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
mKeyWidth = keyWidth;
mMinYCoordinate = -(int)(keyboardHeight * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
mMaxYCoordinate = keyboardHeight;
// TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
- mDetectFastMoveSpeedThreshold = (int)(keyWidth * mParams.mDetectFastMoveSpeedThreshold);
- mGestureDynamicDistanceThresholdFrom =
- (int)(keyWidth * mParams.mDynamicDistanceThresholdFrom);
- mGestureDynamicDistanceThresholdTo = (int)(keyWidth * mParams.mDynamicDistanceThresholdTo);
- mGestureSamplingMinimumDistance = (int)(keyWidth * mParams.mSamplingMinimumDistance);
- mGestureRecognitionSpeedThreshold = (int)(keyWidth * mParams.mRecognitionSpeedThreshold);
+ mDetectFastMoveSpeedThreshold = (int)(
+ keyWidth * mRecognitionParams.mDetectFastMoveSpeedThreshold);
+ mGestureDynamicDistanceThresholdFrom = (int)(
+ keyWidth * mRecognitionParams.mDynamicDistanceThresholdFrom);
+ mGestureDynamicDistanceThresholdTo = (int)(
+ keyWidth * mRecognitionParams.mDynamicDistanceThresholdTo);
+ mGestureSamplingMinimumDistance = (int)(
+ keyWidth * mRecognitionParams.mSamplingMinimumDistance);
+ mGestureRecognitionSpeedThreshold = (int)(
+ keyWidth * mRecognitionParams.mRecognitionSpeedThreshold);
if (DEBUG) {
Log.d(TAG, String.format(
"[%d] setKeyboardGeometry: keyWidth=%3d tT=%3d >> %3d tD=%3d >> %3d",
mPointerId, keyWidth,
- mParams.mDynamicTimeThresholdFrom,
- mParams.mDynamicTimeThresholdTo,
+ mRecognitionParams.mDynamicTimeThresholdFrom,
+ mRecognitionParams.mDynamicTimeThresholdTo,
mGestureDynamicDistanceThresholdFrom,
mGestureDynamicDistanceThresholdTo));
}
}
+ // TODO: Make this package private
public int getLength() {
return mEventTimes.getLength();
}
- public void onDownEvent(final int x, final int y, final long downTime,
- final long gestureFirstDownTime, final long lastTypingTime) {
+ // TODO: Make this package private
+ public void addDownEventPoint(final int x, final int y, final int elapsedTimeSinceFirstDown,
+ final int elapsedTimeSinceLastTyping) {
reset();
- final long elapsedTimeAfterTyping = downTime - lastTypingTime;
- if (elapsedTimeAfterTyping < mParams.mStaticTimeThresholdAfterFastTyping) {
+ if (elapsedTimeSinceLastTyping < mRecognitionParams.mStaticTimeThresholdAfterFastTyping) {
mAfterFastTyping = true;
}
if (DEBUG) {
Log.d(TAG, String.format("[%d] onDownEvent: dT=%3d%s", mPointerId,
- elapsedTimeAfterTyping, mAfterFastTyping ? " afterFastTyping" : ""));
+ elapsedTimeSinceLastTyping, mAfterFastTyping ? " afterFastTyping" : ""));
}
- final int elapsedTimeFromFirstDown = (int)(downTime - gestureFirstDownTime);
- addPointOnKeyboard(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */);
+ // Call {@link #addEventPoint(int,int,int,boolean)} to record this down event point as a
+ // major event point.
+ addEventPoint(x, y, elapsedTimeSinceFirstDown, true /* isMajorEvent */);
}
private int getGestureDynamicDistanceThreshold(final int deltaTime) {
- if (!mAfterFastTyping || deltaTime >= mParams.mDynamicThresholdDecayDuration) {
+ if (!mAfterFastTyping || deltaTime >= mRecognitionParams.mDynamicThresholdDecayDuration) {
return mGestureDynamicDistanceThresholdTo;
}
final int decayedThreshold =
(mGestureDynamicDistanceThresholdFrom - mGestureDynamicDistanceThresholdTo)
- * deltaTime / mParams.mDynamicThresholdDecayDuration;
+ * deltaTime / mRecognitionParams.mDynamicThresholdDecayDuration;
return mGestureDynamicDistanceThresholdFrom - decayedThreshold;
}
private int getGestureDynamicTimeThreshold(final int deltaTime) {
- if (!mAfterFastTyping || deltaTime >= mParams.mDynamicThresholdDecayDuration) {
- return mParams.mDynamicTimeThresholdTo;
+ if (!mAfterFastTyping || deltaTime >= mRecognitionParams.mDynamicThresholdDecayDuration) {
+ return mRecognitionParams.mDynamicTimeThresholdTo;
}
final int decayedThreshold =
- (mParams.mDynamicTimeThresholdFrom - mParams.mDynamicTimeThresholdTo)
- * deltaTime / mParams.mDynamicThresholdDecayDuration;
- return mParams.mDynamicTimeThresholdFrom - decayedThreshold;
+ (mRecognitionParams.mDynamicTimeThresholdFrom
+ - mRecognitionParams.mDynamicTimeThresholdTo)
+ * deltaTime / mRecognitionParams.mDynamicThresholdDecayDuration;
+ return mRecognitionParams.mDynamicTimeThresholdFrom - decayedThreshold;
}
+ // TODO: Make this package private
public final boolean isStartOfAGesture() {
if (!hasDetectedFastMove()) {
return false;
@@ -233,6 +176,7 @@ public class GestureStroke {
return isStartOfAGesture;
}
+ // TODO: Make this package private
public void duplicateLastPointWith(final int time) {
final int lastIndex = getLength() - 1;
if (lastIndex >= 0) {
@@ -248,7 +192,7 @@ public class GestureStroke {
}
}
- protected void reset() {
+ private void reset() {
mIncrementalRecognitionSize = 0;
mLastIncrementalBatchSize = 0;
mEventTimes.setLength(0);
@@ -316,19 +260,20 @@ public class GestureStroke {
}
/**
- * Add a touch event as a gesture point. Returns true if the touch event is on the valid
- * gesture area.
- * @param x the x-coordinate of the touch event
- * @param y the y-coordinate of the touch event
+ * Add an event point to this gesture stroke recognition points. Returns true if the event
+ * point is on the valid gesture area.
+ * @param x the x-coordinate of the event point
+ * @param y the y-coordinate of the event point
* @param time the elapsed time in millisecond from the first gesture down
* @param isMajorEvent false if this is a historical move event
- * @return true if the touch event is on the valid gesture area
+ * @return true if the event point is on the valid gesture area
*/
- public boolean addPointOnKeyboard(final int x, final int y, final int time,
+ // TODO: Make this package private
+ public boolean addEventPoint(final int x, final int y, final int time,
final boolean isMajorEvent) {
final int size = getLength();
if (size <= 0) {
- // Down event
+ // The first event of this stroke (a.k.a. down event).
appendPoint(x, y, time);
updateMajorEvent(x, y, time);
} else {
@@ -357,15 +302,18 @@ public class GestureStroke {
}
}
+ // TODO: Make this package private
public final boolean hasRecognitionTimePast(
final long currentTime, final long lastRecognitionTime) {
- return currentTime > lastRecognitionTime + mParams.mRecognitionMinimumTime;
+ return currentTime > lastRecognitionTime + mRecognitionParams.mRecognitionMinimumTime;
}
+ // TODO: Make this package private
public final void appendAllBatchPoints(final InputPointers out) {
appendBatchPoints(out, getLength());
}
+ // TODO: Make this package private
public final void appendIncrementalBatchPoints(final InputPointers out) {
appendBatchPoints(out, mIncrementalRecognitionSize);
}
@@ -381,10 +329,6 @@ public class GestureStroke {
}
private static int getDistance(final int x1, final int y1, final int x2, final int y2) {
- final int dx = x1 - x2;
- final int dy = y1 - y2;
- // Note that, in recent versions of Android, FloatMath is actually slower than
- // java.lang.Math due to the way the JIT optimizes java.lang.Math.
- return (int)Math.sqrt(dx * dx + dy * dy);
+ return (int)Math.hypot(x1 - x2, y1 - y2);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingParams.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingParams.java
new file mode 100644
index 000000000..088f03aa6
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingParams.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.TypedArray;
+
+import com.android.inputmethod.latin.R;
+
+/**
+ * This class holds parameters to control how a gesture trail is drawn and animated on the screen.
+ *
+ * On the other hand, {@link GestureStrokeDrawingParams} class controls how each gesture stroke is
+ * sampled and interpolated. This class controls how those gesture strokes are displayed as a
+ * gesture trail and animated on the screen.
+ *
+ * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutStartDelay
+ * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutDuration
+ * @attr ref R.styleable#MainKeyboardView_gestureTrailUpdateInterval
+ * @attr ref R.styleable#MainKeyboardView_gestureTrailColor
+ * @attr ref R.styleable#MainKeyboardView_gestureTrailWidth
+ */
+final class GestureTrailDrawingParams {
+ private static final int FADEOUT_START_DELAY_FOR_DEBUG = 2000; // millisecond
+ private static final int FADEOUT_DURATION_FOR_DEBUG = 200; // millisecond
+
+ public final int mTrailColor;
+ public final float mTrailStartWidth;
+ public final float mTrailEndWidth;
+ public final float mTrailBodyRatio;
+ public boolean mTrailShadowEnabled;
+ public final float mTrailShadowRatio;
+ public final int mFadeoutStartDelay;
+ public final int mFadeoutDuration;
+ public final int mUpdateInterval;
+
+ public final int mTrailLingerDuration;
+
+ public GestureTrailDrawingParams(final TypedArray mainKeyboardViewAttr) {
+ mTrailColor = mainKeyboardViewAttr.getColor(
+ R.styleable.MainKeyboardView_gestureTrailColor, 0);
+ mTrailStartWidth = mainKeyboardViewAttr.getDimension(
+ R.styleable.MainKeyboardView_gestureTrailStartWidth, 0.0f);
+ mTrailEndWidth = mainKeyboardViewAttr.getDimension(
+ R.styleable.MainKeyboardView_gestureTrailEndWidth, 0.0f);
+ final int PERCENTAGE_INT = 100;
+ mTrailBodyRatio = (float)mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureTrailBodyRatio, PERCENTAGE_INT)
+ / (float)PERCENTAGE_INT;
+ final int trailShadowRatioInt = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0);
+ mTrailShadowEnabled = (trailShadowRatioInt > 0);
+ mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT;
+ mFadeoutStartDelay = GestureTrailDrawingPoints.DEBUG_SHOW_POINTS
+ ? FADEOUT_START_DELAY_FOR_DEBUG
+ : mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0);
+ mFadeoutDuration = GestureTrailDrawingPoints.DEBUG_SHOW_POINTS
+ ? FADEOUT_DURATION_FOR_DEBUG
+ : mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0);
+ mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
+ mUpdateInterval = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0);
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java
index aca667919..bf4c4da10 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java
@@ -16,7 +16,6 @@
package com.android.inputmethod.keyboard.internal;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -25,24 +24,22 @@ import android.graphics.Rect;
import android.os.SystemClock;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.ResizableIntArray;
-/*
- * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutStartDelay
- * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutDuration
- * @attr ref R.styleable#MainKeyboardView_gestureTrailUpdateInterval
- * @attr ref R.styleable#MainKeyboardView_gestureTrailColor
- * @attr ref R.styleable#MainKeyboardView_gestureTrailWidth
+/**
+ * This class holds drawing points to represent a gesture trail. The gesture trail may contain
+ * multiple non-contiguous gesture strokes and will be animated asynchronously from gesture input.
+ *
+ * On the other hand, {@link GestureStrokeDrawingPoints} class holds drawing points of each gesture
+ * stroke. This class holds drawing points of those gesture strokes to draw as a gesture trail.
+ * Drawing points in this class will be asynchronously removed when fading out animation goes.
*/
-final class GestureTrail {
+final class GestureTrailDrawingPoints {
public static final boolean DEBUG_SHOW_POINTS = false;
public static final int POINT_TYPE_SAMPLED = 1;
public static final int POINT_TYPE_INTERPOLATED = 2;
- private static final int FADEOUT_START_DELAY_FOR_DEBUG = 2000; // millisecond
- private static final int FADEOUT_DURATION_FOR_DEBUG = 200; // millisecond
- private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
+ private static final int DEFAULT_CAPACITY = GestureStrokeDrawingPoints.PREVIEW_CAPACITY;
// These three {@link ResizableIntArray}s should be synchronized by {@link #mEventTimes}.
private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
@@ -56,46 +53,6 @@ final class GestureTrail {
private int mTrailStartIndex;
private int mLastInterpolatedDrawIndex;
- static final class Params {
- public final int mTrailColor;
- public final float mTrailStartWidth;
- public final float mTrailEndWidth;
- public final float mTrailBodyRatio;
- public boolean mTrailShadowEnabled;
- public final float mTrailShadowRatio;
- public final int mFadeoutStartDelay;
- public final int mFadeoutDuration;
- public final int mUpdateInterval;
-
- public final int mTrailLingerDuration;
-
- public Params(final TypedArray mainKeyboardViewAttr) {
- mTrailColor = mainKeyboardViewAttr.getColor(
- R.styleable.MainKeyboardView_gestureTrailColor, 0);
- mTrailStartWidth = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_gestureTrailStartWidth, 0.0f);
- mTrailEndWidth = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_gestureTrailEndWidth, 0.0f);
- final int PERCENTAGE_INT = 100;
- mTrailBodyRatio = (float)mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureTrailBodyRatio, PERCENTAGE_INT)
- / (float)PERCENTAGE_INT;
- final int trailShadowRatioInt = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0);
- mTrailShadowEnabled = (trailShadowRatioInt > 0);
- mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT;
- mFadeoutStartDelay = DEBUG_SHOW_POINTS ? FADEOUT_START_DELAY_FOR_DEBUG
- : mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0);
- mFadeoutDuration = DEBUG_SHOW_POINTS ? FADEOUT_DURATION_FOR_DEBUG
- : mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0);
- mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
- mUpdateInterval = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0);
- }
- }
-
// Use this value as imaginary zero because x-coordinates may be zero.
private static final int DOWN_EVENT_MARKER = -128;
@@ -112,13 +69,13 @@ final class GestureTrail {
? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark;
}
- public void addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
+ public void addStroke(final GestureStrokeDrawingPoints stroke, final long downTime) {
synchronized (mEventTimes) {
addStrokeLocked(stroke, downTime);
}
}
- private void addStrokeLocked(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
+ private void addStrokeLocked(final GestureStrokeDrawingPoints stroke, final long downTime) {
final int trailSize = mEventTimes.getLength();
stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates, mPointTypes);
if (mEventTimes.getLength() == trailSize) {
@@ -126,13 +83,14 @@ final class GestureTrail {
}
final int[] eventTimes = mEventTimes.getPrimitiveArray();
final int strokeId = stroke.getGestureStrokeId();
- // Because interpolation algorithm in {@link GestureStrokeWithPreviewPoints} can't determine
+ // Because interpolation algorithm in {@link GestureStrokeDrawingPoints} can't determine
// the interpolated points in the last segment of gesture stroke, it may need recalculation
// of interpolation when new segments are added to the stroke.
// {@link #mLastInterpolatedDrawIndex} holds the start index of the last segment. It may
// be updated by the interpolation
- // {@link GestureStrokeWithPreviewPoints#interpolatePreviewStroke}
- // or by animation {@link #drawGestureTrail(Canvas,Paint,Rect,Params)} below.
+ // {@link GestureStrokeDrawingPoints#interpolatePreviewStroke}
+ // or by animation {@link #drawGestureTrail(Canvas,Paint,Rect,GestureTrailDrawingParams)}
+ // below.
final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId)
? mLastInterpolatedDrawIndex : trailSize;
mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment(
@@ -161,7 +119,7 @@ final class GestureTrail {
* @param params gesture trail display parameters
* @return the width of a gesture trail
*/
- private static int getAlpha(final int elapsedTime, final Params params) {
+ private static int getAlpha(final int elapsedTime, final GestureTrailDrawingParams params) {
if (elapsedTime < params.mFadeoutStartDelay) {
return Constants.Color.ALPHA_OPAQUE;
}
@@ -180,7 +138,7 @@ final class GestureTrail {
* @param params gesture trail display parameters
* @return the width of a gesture trail
*/
- private static float getWidth(final int elapsedTime, final Params params) {
+ private static float getWidth(final int elapsedTime, final GestureTrailDrawingParams params) {
final float deltaWidth = params.mTrailStartWidth - params.mTrailEndWidth;
return params.mTrailStartWidth - (deltaWidth * elapsedTime) / params.mTrailLingerDuration;
}
@@ -197,14 +155,14 @@ final class GestureTrail {
* @return true if some gesture trails remain to be drawn
*/
public boolean drawGestureTrail(final Canvas canvas, final Paint paint,
- final Rect outBoundsRect, final Params params) {
+ final Rect outBoundsRect, final GestureTrailDrawingParams params) {
synchronized (mEventTimes) {
return drawGestureTrailLocked(canvas, paint, outBoundsRect, params);
}
}
private boolean drawGestureTrailLocked(final Canvas canvas, final Paint paint,
- final Rect outBoundsRect, final Params params) {
+ final Rect outBoundsRect, final GestureTrailDrawingParams params) {
// Initialize bounds rectangle.
outBoundsRect.setEmpty();
final int trailSize = mEventTimes.getLength();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java
index 19e995548..d8b00c707 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java
@@ -29,16 +29,16 @@ import android.util.SparseArray;
import android.view.View;
import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.keyboard.internal.GestureTrail.Params;
import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
/**
- * Draw gesture trail preview graphics during gesture.
+ * Draw preview graphics of multiple gesture trails during gesture input.
*/
-public final class GestureTrailsPreview extends AbstractDrawingPreview {
- private final SparseArray<GestureTrail> mGestureTrails = CollectionUtils.newSparseArray();
- private final Params mGestureTrailParams;
+public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview {
+ private final SparseArray<GestureTrailDrawingPoints> mGestureTrails =
+ CollectionUtils.newSparseArray();
+ private final GestureTrailDrawingParams mDrawingParams;
private final Paint mGesturePaint;
private int mOffscreenWidth;
private int mOffscreenHeight;
@@ -52,21 +52,23 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview {
private final DrawingHandler mDrawingHandler;
private static final class DrawingHandler
- extends StaticInnerHandlerWrapper<GestureTrailsPreview> {
+ extends LeakGuardHandlerWrapper<GestureTrailsDrawingPreview> {
private static final int MSG_UPDATE_GESTURE_TRAIL = 0;
- private final Params mGestureTrailParams;
+ private final GestureTrailDrawingParams mDrawingParams;
- public DrawingHandler(final GestureTrailsPreview outerInstance,
- final Params gestureTrailParams) {
- super(outerInstance);
- mGestureTrailParams = gestureTrailParams;
+ public DrawingHandler(final GestureTrailsDrawingPreview ownerInstance,
+ final GestureTrailDrawingParams drawingParams) {
+ super(ownerInstance);
+ mDrawingParams = drawingParams;
}
@Override
public void handleMessage(final Message msg) {
- final GestureTrailsPreview preview = getOuterInstance();
- if (preview == null) return;
+ final GestureTrailsDrawingPreview preview = getOwnerInstance();
+ if (preview == null) {
+ return;
+ }
switch (msg.what) {
case MSG_UPDATE_GESTURE_TRAIL:
preview.getDrawingView().invalidate();
@@ -77,14 +79,15 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview {
public void postUpdateGestureTrailPreview() {
removeMessages(MSG_UPDATE_GESTURE_TRAIL);
sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_TRAIL),
- mGestureTrailParams.mUpdateInterval);
+ mDrawingParams.mUpdateInterval);
}
}
- public GestureTrailsPreview(final View drawingView, final TypedArray mainKeyboardViewAttr) {
+ public GestureTrailsDrawingPreview(final View drawingView,
+ final TypedArray mainKeyboardViewAttr) {
super(drawingView);
- mGestureTrailParams = new Params(mainKeyboardViewAttr);
- mDrawingHandler = new DrawingHandler(this, mGestureTrailParams);
+ mDrawingParams = new GestureTrailDrawingParams(mainKeyboardViewAttr);
+ mDrawingHandler = new DrawingHandler(this, mDrawingParams);
final Paint gesturePaint = new Paint();
gesturePaint.setAntiAlias(true);
gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
@@ -92,19 +95,17 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview {
}
@Override
- public void setKeyboardGeometry(final int[] originCoords, final int width, final int height) {
- mOffscreenOffsetY = (int)(
- height * GestureStroke.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
+ public void setKeyboardViewGeometry(final int[] originCoords, final int width,
+ final int height) {
+ super.setKeyboardViewGeometry(originCoords, width, height);
+ mOffscreenOffsetY = (int)(height
+ * GestureStrokeRecognitionPoints.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
mOffscreenWidth = width;
mOffscreenHeight = mOffscreenOffsetY + height;
}
@Override
- public void onDetachFromWindow() {
- freeOffscreenBuffer();
- }
-
- public void deallocateMemory() {
+ public void onDeallocateMemory() {
freeOffscreenBuffer();
}
@@ -144,9 +145,9 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview {
// Trails count == fingers count that have ever been active.
final int trailsCount = mGestureTrails.size();
for (int index = 0; index < trailsCount; index++) {
- final GestureTrail trail = mGestureTrails.valueAt(index);
+ final GestureTrailDrawingPoints trail = mGestureTrails.valueAt(index);
needsUpdatingGestureTrail |= trail.drawGestureTrail(offscreenCanvas, paint,
- mGestureTrailBoundsRect, mGestureTrailParams);
+ mGestureTrailBoundsRect, mDrawingParams);
// {@link #mGestureTrailBoundsRect} has bounding box of the trail.
dirtyRect.union(mGestureTrailBoundsRect);
}
@@ -189,15 +190,15 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview {
if (!isPreviewEnabled()) {
return;
}
- GestureTrail trail;
+ GestureTrailDrawingPoints trail;
synchronized (mGestureTrails) {
trail = mGestureTrails.get(tracker.mPointerId);
if (trail == null) {
- trail = new GestureTrail();
+ trail = new GestureTrailDrawingPoints();
mGestureTrails.put(tracker.mPointerId, trail);
}
}
- trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime());
+ trail.addStroke(tracker.getGestureStrokeDrawingPoints(), tracker.getDownTime());
// TODO: Should narrow the invalidate region.
getDrawingView().invalidate();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
index b528b692e..1716fa049 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
@@ -16,7 +16,6 @@
package com.android.inputmethod.keyboard.internal;
-import android.content.res.ColorStateList;
import android.graphics.Typeface;
import com.android.inputmethod.latin.utils.ResourceUtils;
@@ -33,7 +32,7 @@ public final class KeyDrawParams {
public int mHintLabelSize;
public int mPreviewTextSize;
- public ColorStateList mTextColorStateList;
+ public int mTextColor;
public int mTextInactivatedColor;
public int mTextShadowColor;
public int mHintLetterColor;
@@ -58,7 +57,7 @@ public final class KeyDrawParams {
mHintLabelSize = copyFrom.mHintLabelSize;
mPreviewTextSize = copyFrom.mPreviewTextSize;
- mTextColorStateList = copyFrom.mTextColorStateList;
+ mTextColor = copyFrom.mTextColor;
mTextInactivatedColor = copyFrom.mTextInactivatedColor;
mTextShadowColor = copyFrom.mTextShadowColor;
mHintLetterColor = copyFrom.mHintLetterColor;
@@ -90,8 +89,8 @@ public final class KeyDrawParams {
attr.mShiftedLetterHintRatio, mShiftedLetterHintSize);
mHintLabelSize = selectTextSize(keyHeight, attr.mHintLabelRatio, mHintLabelSize);
mPreviewTextSize = selectTextSize(keyHeight, attr.mPreviewTextRatio, mPreviewTextSize);
- mTextColorStateList =
- attr.mTextColorStateList != null ? attr.mTextColorStateList : mTextColorStateList;
+
+ mTextColor = selectColor(attr.mTextColor, mTextColor);
mTextInactivatedColor = selectColor(attr.mTextInactivatedColor, mTextInactivatedColor);
mTextShadowColor = selectColor(attr.mTextShadowColor, mTextShadowColor);
mHintLetterColor = selectColor(attr.mHintLetterColor, mHintLetterColor);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
new file mode 100644
index 000000000..625d1f0a4
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
@@ -0,0 +1,285 @@
+/*
+ * 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.keyboard.internal;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.TextView;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.utils.ViewLayoutUtils;
+
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * This class controls pop up key previews. This class decides:
+ * - what kind of key previews should be shown.
+ * - where key previews should be placed.
+ * - how key previews should be shown and dismissed.
+ */
+public final class KeyPreviewChoreographer {
+ // Free {@link TextView} pool that can be used for key preview.
+ private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = CollectionUtils.newArrayDeque();
+ // Map from {@link Key} to {@link TextView} that is currently being displayed as key preview.
+ private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = CollectionUtils.newHashMap();
+
+ private final KeyPreviewDrawParams mParams;
+
+ public KeyPreviewChoreographer(final KeyPreviewDrawParams params) {
+ mParams = params;
+ }
+
+ public TextView getKeyPreviewTextView(final Key key, final ViewGroup placerView) {
+ TextView previewTextView = mShowingKeyPreviewTextViews.remove(key);
+ if (previewTextView != null) {
+ return previewTextView;
+ }
+ previewTextView = mFreeKeyPreviewTextViews.poll();
+ if (previewTextView != null) {
+ return previewTextView;
+ }
+ final Context context = placerView.getContext();
+ if (mParams.mLayoutId != 0) {
+ previewTextView = (TextView)LayoutInflater.from(context)
+ .inflate(mParams.mLayoutId, null);
+ } else {
+ previewTextView = new TextView(context);
+ }
+ placerView.addView(previewTextView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0));
+ return previewTextView;
+ }
+
+ public boolean isShowingKeyPreview(final Key key) {
+ return mShowingKeyPreviewTextViews.containsKey(key);
+ }
+
+ public void dismissAllKeyPreviews() {
+ for (final Key key : new HashSet<Key>(mShowingKeyPreviewTextViews.keySet())) {
+ dismissKeyPreview(key, false /* withAnimation */);
+ }
+ }
+
+ public void dismissKeyPreview(final Key key, final boolean withAnimation) {
+ if (key == null) {
+ return;
+ }
+ final TextView previewTextView = mShowingKeyPreviewTextViews.get(key);
+ if (previewTextView == null) {
+ return;
+ }
+ final Object tag = previewTextView.getTag();
+ if (withAnimation) {
+ if (tag instanceof KeyPreviewAnimations) {
+ final KeyPreviewAnimations animation = (KeyPreviewAnimations)tag;
+ animation.startDismiss();
+ return;
+ }
+ }
+ // Dismiss preview without animation.
+ mShowingKeyPreviewTextViews.remove(key);
+ if (tag instanceof Animator) {
+ ((Animator)tag).cancel();
+ }
+ previewTextView.setTag(null);
+ previewTextView.setVisibility(View.INVISIBLE);
+ mFreeKeyPreviewTextViews.add(previewTextView);
+ }
+
+ // Background state set
+ private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
+ { // STATE_MIDDLE
+ {},
+ { R.attr.state_has_morekeys }
+ },
+ { // STATE_LEFT
+ { R.attr.state_left_edge },
+ { R.attr.state_left_edge, R.attr.state_has_morekeys }
+ },
+ { // STATE_RIGHT
+ { R.attr.state_right_edge },
+ { R.attr.state_right_edge, R.attr.state_has_morekeys }
+ }
+ };
+ private static final int STATE_MIDDLE = 0;
+ private static final int STATE_LEFT = 1;
+ private static final int STATE_RIGHT = 2;
+ private static final int STATE_NORMAL = 0;
+ private static final int STATE_HAS_MOREKEYS = 1;
+
+ public void placeKeyPreview(final Key key, final TextView previewTextView,
+ final KeyboardIconsSet iconsSet, final KeyDrawParams drawParams,
+ final int keyboardViewWidth, final int[] originCoords) {
+ previewTextView.setTextColor(drawParams.mPreviewTextColor);
+ final Drawable background = previewTextView.getBackground();
+ final String label = key.getPreviewLabel();
+ // What we show as preview should match what we show on a key top in onDraw().
+ if (label != null) {
+ // TODO Should take care of temporaryShiftLabel here.
+ previewTextView.setCompoundDrawables(null, null, null, null);
+ previewTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ key.selectPreviewTextSize(drawParams));
+ previewTextView.setTypeface(key.selectPreviewTypeface(drawParams));
+ previewTextView.setText(label);
+ } else {
+ previewTextView.setCompoundDrawables(null, null, null, key.getPreviewIcon(iconsSet));
+ previewTextView.setText(null);
+ }
+
+ previewTextView.measure(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ mParams.setGeometry(previewTextView);
+ final int previewWidth = previewTextView.getMeasuredWidth();
+ final int previewHeight = mParams.mPreviewHeight;
+ final int keyDrawWidth = key.getDrawWidth();
+ // The key preview is horizontally aligned with the center of the visible part of the
+ // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
+ // the left/right background is used if such background is specified.
+ final int statePosition;
+ int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
+ + CoordinateUtils.x(originCoords);
+ if (previewX < 0) {
+ previewX = 0;
+ statePosition = STATE_LEFT;
+ } else if (previewX > keyboardViewWidth - previewWidth) {
+ previewX = keyboardViewWidth - previewWidth;
+ statePosition = STATE_RIGHT;
+ } else {
+ statePosition = STATE_MIDDLE;
+ }
+ // The key preview is placed vertically above the top edge of the parent key with an
+ // arbitrary offset.
+ final int previewY = key.getY() - previewHeight + mParams.mPreviewOffset
+ + CoordinateUtils.y(originCoords);
+
+ if (background != null) {
+ final int hasMoreKeys = (key.getMoreKeys() != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
+ background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
+ }
+ ViewLayoutUtils.placeViewAt(
+ previewTextView, previewX, previewY, previewWidth, previewHeight);
+ previewTextView.setPivotX(previewWidth / 2.0f);
+ previewTextView.setPivotY(previewHeight);
+ }
+
+ public void showKeyPreview(final Key key, final TextView previewTextView,
+ final boolean withAnimation) {
+ if (!withAnimation) {
+ previewTextView.setVisibility(View.VISIBLE);
+ mShowingKeyPreviewTextViews.put(key, previewTextView);
+ return;
+ }
+
+ // Show preview with animation.
+ final Animator showUpAnimation = createShowUpAniation(key, previewTextView);
+ final Animator dismissAnimation = createDismissAnimation(key, previewTextView);
+ final KeyPreviewAnimations animation = new KeyPreviewAnimations(
+ showUpAnimation, dismissAnimation);
+ previewTextView.setTag(animation);
+ animation.startShowUp();
+ }
+
+ private static final float KEY_PREVIEW_SHOW_UP_END_SCALE = 1.0f;
+ private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR =
+ new AccelerateInterpolator();
+ private static final DecelerateInterpolator DECELERATE_INTERPOLATOR =
+ new DecelerateInterpolator();
+
+ private Animator createShowUpAniation(final Key key, final TextView previewTextView) {
+ // TODO: Optimization for no scale animation and no duration.
+ final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
+ previewTextView, View.SCALE_X, mParams.getShowUpStartScale(),
+ KEY_PREVIEW_SHOW_UP_END_SCALE);
+ final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
+ previewTextView, View.SCALE_Y, mParams.getShowUpStartScale(),
+ KEY_PREVIEW_SHOW_UP_END_SCALE);
+ final AnimatorSet showUpAnimation = new AnimatorSet();
+ showUpAnimation.play(scaleXAnimation).with(scaleYAnimation);
+ showUpAnimation.setDuration(mParams.getShowUpDuration());
+ showUpAnimation.setInterpolator(DECELERATE_INTERPOLATOR);
+ showUpAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(final Animator animation) {
+ showKeyPreview(key, previewTextView, false /* withAnimation */);
+ }
+ });
+ return showUpAnimation;
+ }
+
+ private Animator createDismissAnimation(final Key key, final TextView previewTextView) {
+ // TODO: Optimization for no scale animation and no duration.
+ final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
+ previewTextView, View.SCALE_X, mParams.getDismissEndScale());
+ final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
+ previewTextView, View.SCALE_Y, mParams.getDismissEndScale());
+ final AnimatorSet dismissAnimation = new AnimatorSet();
+ dismissAnimation.play(scaleXAnimation).with(scaleYAnimation);
+ final int dismissDuration = Math.min(
+ mParams.getDismissDuration(), mParams.getLingerTimeout());
+ dismissAnimation.setDuration(dismissDuration);
+ dismissAnimation.setInterpolator(ACCELERATE_INTERPOLATOR);
+ dismissAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(final Animator animation) {
+ dismissKeyPreview(key, false /* withAnimation */);
+ }
+ });
+ return dismissAnimation;
+ }
+
+ private static class KeyPreviewAnimations extends AnimatorListenerAdapter {
+ private final Animator mShowUpAnimation;
+ private final Animator mDismissAnimation;
+
+ public KeyPreviewAnimations(final Animator showUpAnimation,
+ final Animator dismissAnimation) {
+ mShowUpAnimation = showUpAnimation;
+ mDismissAnimation = dismissAnimation;
+ }
+
+ public void startShowUp() {
+ mShowUpAnimation.start();
+ }
+
+ public void startDismiss() {
+ if (mShowUpAnimation.isRunning()) {
+ mShowUpAnimation.addListener(this);
+ return;
+ }
+ mDismissAnimation.start();
+ }
+
+ @Override
+ public void onAnimationEnd(final Animator animation) {
+ mDismissAnimation.start();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
index 609d1a57f..37e5c889d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
@@ -16,7 +16,23 @@
package com.android.inputmethod.keyboard.internal;
+import android.content.res.TypedArray;
+import android.view.View;
+
+import com.android.inputmethod.latin.R;
+
public final class KeyPreviewDrawParams {
+ // XML attributes of {@link MainKeyboardView}.
+ public final int mLayoutId;
+ public final int mPreviewOffset;
+ public final int mPreviewHeight;
+ private int mShowUpDuration;
+ private int mDismissDuration;
+ private float mShowUpStartScale;
+ private float mDismissEndScale;
+ private int mLingerTimeout;
+ private boolean mShowPopup = true;
+
// The graphical geometry of the key preview.
// <-width->
// +-------+ ^
@@ -34,11 +50,92 @@ public final class KeyPreviewDrawParams {
// paddings. To align the more keys keyboard panel's visible part with the visible part of
// the background, we need to record the width and height of key preview that don't include
// invisible paddings.
- public int mPreviewVisibleWidth;
- public int mPreviewVisibleHeight;
+ private int mVisibleWidth;
+ private int mVisibleHeight;
// The key preview may have an arbitrary offset and its background that may have a bottom
// padding. To align the more keys keyboard and the key preview we also need to record the
// offset between the top edge of parent key and the bottom of the visible part of key
// preview background.
- public int mPreviewVisibleOffset;
+ private int mVisibleOffset;
+
+ public KeyPreviewDrawParams(final TypedArray mainKeyboardViewAttr) {
+ mPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
+ R.styleable.MainKeyboardView_keyPreviewOffset, 0);
+ mPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
+ R.styleable.MainKeyboardView_keyPreviewHeight, 0);
+ mLingerTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0);
+ mLayoutId = mainKeyboardViewAttr.getResourceId(
+ R.styleable.MainKeyboardView_keyPreviewLayout, 0);
+ if (mLayoutId == 0) {
+ mShowPopup = false;
+ }
+ }
+
+ public void setVisibleOffset(final int previewVisibleOffset) {
+ mVisibleOffset = previewVisibleOffset;
+ }
+
+ public int getVisibleOffset() {
+ return mVisibleOffset;
+ }
+
+ public void setGeometry(final View previewTextView) {
+ final int previewWidth = previewTextView.getMeasuredWidth();
+ final int previewHeight = mPreviewHeight;
+ // The width and height of visible part of the key preview background. The content marker
+ // of the background 9-patch have to cover the visible part of the background.
+ mVisibleWidth = previewWidth - previewTextView.getPaddingLeft()
+ - previewTextView.getPaddingRight();
+ mVisibleHeight = previewHeight - previewTextView.getPaddingTop()
+ - previewTextView.getPaddingBottom();
+ // The distance between the top edge of the parent key and the bottom of the visible part
+ // of the key preview background.
+ setVisibleOffset(mPreviewOffset - previewTextView.getPaddingBottom());
+ }
+
+ public int getVisibleWidth() {
+ return mVisibleWidth;
+ }
+
+ public int getVisibleHeight() {
+ return mVisibleHeight;
+ }
+
+ public void setPopupEnabled(final boolean enabled, final int lingerTimeout) {
+ mShowPopup = enabled;
+ mLingerTimeout = lingerTimeout;
+ }
+
+ public boolean isPopupEnabled() {
+ return mShowPopup;
+ }
+
+ public int getLingerTimeout() {
+ return mLingerTimeout;
+ }
+
+ public void setAnimationParams(final float showUpStartScale, final int showUpDuration,
+ final float dismissEndScale, final int dismissDuration) {
+ mShowUpStartScale = showUpStartScale;
+ mShowUpDuration = showUpDuration;
+ mDismissEndScale = dismissEndScale;
+ mDismissDuration = dismissDuration;
+ }
+
+ public float getShowUpStartScale() {
+ return mShowUpStartScale;
+ }
+
+ public int getShowUpDuration() {
+ return mShowUpDuration;
+ }
+
+ public float getDismissEndScale() {
+ return mDismissEndScale;
+ }
+
+ public int getDismissDuration() {
+ return mDismissDuration;
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 22f5b3dd1..48ba8e051 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -19,114 +19,54 @@ package com.android.inputmethod.keyboard.internal;
import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT;
import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
-import android.text.TextUtils;
-
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.StringUtils;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-
/**
- * The string parser of more keys specification.
- * The specification is comma separated texts each of which represents one "more key".
- * The specification might have label or string resource reference in it. These references are
- * expanded before parsing comma.
- * - Label reference should be a string representation of label (!text/label_name)
- * - String resource reference should be a string representation of resource (!text/resource_name)
- * Each "more key" specification is one of the following:
- * - Label optionally followed by keyOutputText or code (keyLabel|keyOutputText).
- * - Icon followed by keyOutputText or code (!icon/icon_name|!code/code_name)
- * - Icon should be a string representation of icon (!icon/icon_name).
- * - Code should be a code point presented by hexadecimal string prefixed with "0x", or a string
- * representation of code (!code/code_name).
+ * The string parser of the key specification.
+ *
+ * Each key specification is one of the following:
+ * - Label optionally followed by keyOutputText (keyLabel|keyOutputText).
+ * - Label optionally followed by code point (keyLabel|!code/code_name).
+ * - Icon followed by keyOutputText (!icon/icon_name|keyOutputText).
+ * - Icon followed by code point (!icon/icon_name|!code/code_name).
+ * Label and keyOutputText are one of the following:
+ * - Literal string.
+ * - Label reference represented by (!text/label_name), see {@link KeyboardTextsSet}.
+ * - String resource reference represented by (!text/resource_name), see {@link KeyboardTextsSet}.
+ * Icon is represented by (!icon/icon_name), see {@link KeyboardIconsSet}.
+ * Code is one of the following:
+ * - Code point presented by hexadecimal string prefixed with "0x"
+ * - Code reference represented by (!code/code_name), see {@link KeyboardCodesSet}.
* Special character, comma ',' backslash '\', and bar '|' can be escaped by '\' character.
- * Note that the '\' is also parsed by XML parser and CSV parser as well.
- * See {@link KeyboardIconsSet} about icon_name.
+ * Note that the '\' is also parsed by XML parser and {@link MoreKeySpec#splitKeySpecs(String)}
+ * as well.
*/
+// TODO: Rename to KeySpec and make this class to the key specification object.
public final class KeySpecParser {
- private static final boolean DEBUG = LatinImeLogger.sDBG;
-
- private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
-
// Constants for parsing.
- private static final char COMMA = ',';
- private static final char BACKSLASH = '\\';
- private static final char VERTICAL_BAR = '|';
- private static final String PREFIX_TEXT = "!text/";
- static final String PREFIX_ICON = "!icon/";
- private static final String PREFIX_CODE = "!code/";
+ private static final char BACKSLASH = Constants.CODE_BACKSLASH;
+ private static final char VERTICAL_BAR = Constants.CODE_VERTICAL_BAR;
private static final String PREFIX_HEX = "0x";
- private static final String ADDITIONAL_MORE_KEY_MARKER = "%";
private KeySpecParser() {
// Intentional empty constructor for utility class.
}
- /**
- * Split the text containing multiple key specifications separated by commas into an array of
- * key specifications.
- * A key specification can contain a character escaped by the backslash character, including a
- * comma character.
- * Note that an empty key specification will be eliminated from the result array.
- *
- * @param text the text containing multiple key specifications.
- * @return an array of key specification text. Null if the specified <code>text</code> is empty
- * or has no key specifications.
- */
- public static String[] splitKeySpecs(final String text) {
- final int size = text.length();
- if (size == 0) {
- return null;
- }
- // Optimization for one-letter key specification.
- if (size == 1) {
- return text.charAt(0) == COMMA ? null : new String[] { text };
- }
+ private static boolean hasIcon(final String keySpec) {
+ return keySpec.startsWith(KeyboardIconsSet.PREFIX_ICON);
+ }
- ArrayList<String> list = null;
- int start = 0;
- // The characters in question in this loop are COMMA and BACKSLASH. These characters never
- // match any high or low surrogate character. So it is OK to iterate through with char
- // index.
- for (int pos = 0; pos < size; pos++) {
- final char c = text.charAt(pos);
- if (c == COMMA) {
- // Skip empty entry.
- if (pos - start > 0) {
- if (list == null) {
- list = CollectionUtils.newArrayList();
- }
- list.add(text.substring(start, pos));
- }
- // Skip comma
- start = pos + 1;
- } else if (c == BACKSLASH) {
- // Skip escape character and escaped character.
- pos++;
- }
- }
- final String remain = (size - start > 0) ? text.substring(start) : null;
- if (list == null) {
- return remain != null ? new String[] { remain } : null;
+ private static boolean hasCode(final String keySpec, final int labelEnd) {
+ if (labelEnd <= 0 || labelEnd + 1 >= keySpec.length()) {
+ return false;
}
- if (remain != null) {
- list.add(remain);
+ if (keySpec.startsWith(KeyboardCodesSet.PREFIX_CODE, labelEnd + 1)) {
+ return true;
}
- return list.toArray(new String[list.size()]);
- }
-
- private static boolean hasIcon(final String moreKeySpec) {
- return moreKeySpec.startsWith(PREFIX_ICON);
- }
-
- private static boolean hasCode(final String moreKeySpec) {
- final int end = indexOfLabelEnd(moreKeySpec, 0);
- if (end > 0 && end + 1 < moreKeySpec.length() && moreKeySpec.startsWith(
- PREFIX_CODE, end + 1)) {
+ // This is a workaround to have a key that has a supplementary code point. We can't put a
+ // string in resource as a XML entity of a supplementary code point or a surrogate pair.
+ if (keySpec.startsWith(PREFIX_HEX, labelEnd + 1)) {
return true;
}
return false;
@@ -151,17 +91,21 @@ public final class KeySpecParser {
return sb.toString();
}
- private static int indexOfLabelEnd(final String moreKeySpec, final int start) {
- if (moreKeySpec.indexOf(BACKSLASH, start) < 0) {
- final int end = moreKeySpec.indexOf(VERTICAL_BAR, start);
- if (end == 0) {
- throw new KeySpecParserError(VERTICAL_BAR + " at " + start + ": " + moreKeySpec);
+ private static int indexOfLabelEnd(final String keySpec) {
+ final int length = keySpec.length();
+ if (keySpec.indexOf(BACKSLASH) < 0) {
+ final int labelEnd = keySpec.indexOf(VERTICAL_BAR);
+ if (labelEnd == 0) {
+ if (length == 1) {
+ // Treat a sole vertical bar as a special case of key label.
+ return -1;
+ }
+ throw new KeySpecParserError("Empty label");
}
- return end;
+ return labelEnd;
}
- final int length = moreKeySpec.length();
- for (int pos = start; pos < length; pos++) {
- final char c = moreKeySpec.charAt(pos);
+ for (int pos = 0; pos < length; pos++) {
+ final char c = keySpec.charAt(pos);
if (c == BACKSLASH && pos + 1 < length) {
// Skip escape char
pos++;
@@ -172,63 +116,85 @@ public final class KeySpecParser {
return -1;
}
- public static String getLabel(final String moreKeySpec) {
- if (hasIcon(moreKeySpec)) {
+ private static String getBeforeLabelEnd(final String keySpec, final int labelEnd) {
+ return (labelEnd < 0) ? keySpec : keySpec.substring(0, labelEnd);
+ }
+
+ private static String getAfterLabelEnd(final String keySpec, final int labelEnd) {
+ return keySpec.substring(labelEnd + /* VERTICAL_BAR */1);
+ }
+
+ private static void checkDoubleLabelEnd(final String keySpec, final int labelEnd) {
+ if (indexOfLabelEnd(getAfterLabelEnd(keySpec, labelEnd)) < 0) {
+ return;
+ }
+ throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec);
+ }
+
+ public static String getLabel(final String keySpec) {
+ if (keySpec == null) {
+ // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
+ return null;
+ }
+ if (hasIcon(keySpec)) {
return null;
}
- final int end = indexOfLabelEnd(moreKeySpec, 0);
- final String label = (end > 0) ? parseEscape(moreKeySpec.substring(0, end))
- : parseEscape(moreKeySpec);
- if (TextUtils.isEmpty(label)) {
- throw new KeySpecParserError("Empty label: " + moreKeySpec);
+ final int labelEnd = indexOfLabelEnd(keySpec);
+ final String label = parseEscape(getBeforeLabelEnd(keySpec, labelEnd));
+ if (label.isEmpty()) {
+ throw new KeySpecParserError("Empty label: " + keySpec);
}
return label;
}
- private static String getOutputTextInternal(final String moreKeySpec) {
- final int end = indexOfLabelEnd(moreKeySpec, 0);
- if (end <= 0) {
+ private static String getOutputTextInternal(final String keySpec, final int labelEnd) {
+ if (labelEnd <= 0) {
return null;
}
- if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
- throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec);
- }
- return parseEscape(moreKeySpec.substring(end + /* VERTICAL_BAR */1));
+ checkDoubleLabelEnd(keySpec, labelEnd);
+ return parseEscape(getAfterLabelEnd(keySpec, labelEnd));
}
- static String getOutputText(final String moreKeySpec) {
- if (hasCode(moreKeySpec)) {
+ public static String getOutputText(final String keySpec) {
+ if (keySpec == null) {
+ // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
+ return null;
+ }
+ final int labelEnd = indexOfLabelEnd(keySpec);
+ if (hasCode(keySpec, labelEnd)) {
return null;
}
- final String outputText = getOutputTextInternal(moreKeySpec);
+ final String outputText = getOutputTextInternal(keySpec, labelEnd);
if (outputText != null) {
if (StringUtils.codePointCount(outputText) == 1) {
// If output text is one code point, it should be treated as a code.
// See {@link #getCode(Resources, String)}.
return null;
}
- if (!TextUtils.isEmpty(outputText)) {
- return outputText;
+ if (outputText.isEmpty()) {
+ throw new KeySpecParserError("Empty outputText: " + keySpec);
}
- throw new KeySpecParserError("Empty outputText: " + moreKeySpec);
+ return outputText;
}
- final String label = getLabel(moreKeySpec);
+ final String label = getLabel(keySpec);
if (label == null) {
- throw new KeySpecParserError("Empty label: " + moreKeySpec);
+ throw new KeySpecParserError("Empty label: " + keySpec);
}
// Code is automatically generated for one letter label. See {@link getCode()}.
return (StringUtils.codePointCount(label) == 1) ? null : label;
}
- static int getCode(final String moreKeySpec, final KeyboardCodesSet codesSet) {
- if (hasCode(moreKeySpec)) {
- final int end = indexOfLabelEnd(moreKeySpec, 0);
- if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
- throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec);
- }
- return parseCode(moreKeySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED);
+ public static int getCode(final String keySpec) {
+ if (keySpec == null) {
+ // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
+ return CODE_UNSPECIFIED;
+ }
+ final int labelEnd = indexOfLabelEnd(keySpec);
+ if (hasCode(keySpec, labelEnd)) {
+ checkDoubleLabelEnd(keySpec, labelEnd);
+ return parseCode(getAfterLabelEnd(keySpec, labelEnd), CODE_UNSPECIFIED);
}
- final String outputText = getOutputTextInternal(moreKeySpec);
+ final String outputText = getOutputTextInternal(keySpec, labelEnd);
if (outputText != null) {
// If output text is one code point, it should be treated as a code.
// See {@link #getOutputText(String)}.
@@ -237,138 +203,41 @@ public final class KeySpecParser {
}
return CODE_OUTPUT_TEXT;
}
- final String label = getLabel(moreKeySpec);
- // Code is automatically generated for one letter label.
- if (StringUtils.codePointCount(label) == 1) {
- return label.codePointAt(0);
- }
- return CODE_OUTPUT_TEXT;
- }
-
- public static int parseCode(final String text, final KeyboardCodesSet codesSet,
- final int defCode) {
- if (text == null) return defCode;
- if (text.startsWith(PREFIX_CODE)) {
- return codesSet.getCode(text.substring(PREFIX_CODE.length()));
- } else if (text.startsWith(PREFIX_HEX)) {
- return Integer.parseInt(text.substring(PREFIX_HEX.length()), 16);
- } else {
- return Integer.parseInt(text);
- }
- }
-
- public static int getIconId(final String moreKeySpec) {
- if (moreKeySpec != null && hasIcon(moreKeySpec)) {
- final int end = moreKeySpec.indexOf(VERTICAL_BAR, PREFIX_ICON.length());
- final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length())
- : moreKeySpec.substring(PREFIX_ICON.length(), end);
- return KeyboardIconsSet.getIconId(name);
- }
- return KeyboardIconsSet.ICON_UNDEFINED;
- }
-
- private static <T> ArrayList<T> arrayAsList(final T[] array, final int start, final int end) {
- if (array == null) {
- throw new NullPointerException();
- }
- if (start < 0 || start > end || end > array.length) {
- throw new IllegalArgumentException();
- }
-
- final ArrayList<T> list = CollectionUtils.newArrayList(end - start);
- for (int i = start; i < end; i++) {
- list.add(array[i]);
+ final String label = getLabel(keySpec);
+ if (label == null) {
+ throw new KeySpecParserError("Empty label: " + keySpec);
}
- return list;
+ // Code is automatically generated for one letter label.
+ return (StringUtils.codePointCount(label) == 1) ? label.codePointAt(0) : CODE_OUTPUT_TEXT;
}
- private static final String[] EMPTY_STRING_ARRAY = new String[0];
-
- private static String[] filterOutEmptyString(final String[] array) {
- if (array == null) {
- return EMPTY_STRING_ARRAY;
+ public static int parseCode(final String text, final int defaultCode) {
+ if (text == null) {
+ return defaultCode;
}
- ArrayList<String> out = null;
- for (int i = 0; i < array.length; i++) {
- final String entry = array[i];
- if (TextUtils.isEmpty(entry)) {
- if (out == null) {
- out = arrayAsList(array, 0, i);
- }
- } else if (out != null) {
- out.add(entry);
- }
+ if (text.startsWith(KeyboardCodesSet.PREFIX_CODE)) {
+ return KeyboardCodesSet.getCode(text.substring(KeyboardCodesSet.PREFIX_CODE.length()));
}
- if (out == null) {
- return array;
+ // This is a workaround to have a key that has a supplementary code point. We can't put a
+ // string in resource as a XML entity of a supplementary code point or a surrogate pair.
+ if (text.startsWith(PREFIX_HEX)) {
+ return Integer.parseInt(text.substring(PREFIX_HEX.length()), 16);
}
- return out.toArray(new String[out.size()]);
+ return defaultCode;
}
- public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs,
- final String[] additionalMoreKeySpecs) {
- final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
- final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
- final int moreKeysCount = moreKeys.length;
- final int additionalCount = additionalMoreKeys.length;
- ArrayList<String> out = null;
- int additionalIndex = 0;
- for (int moreKeyIndex = 0; moreKeyIndex < moreKeysCount; moreKeyIndex++) {
- final String moreKeySpec = moreKeys[moreKeyIndex];
- if (moreKeySpec.equals(ADDITIONAL_MORE_KEY_MARKER)) {
- if (additionalIndex < additionalCount) {
- // Replace '%' marker with additional more key specification.
- final String additionalMoreKey = additionalMoreKeys[additionalIndex];
- if (out != null) {
- out.add(additionalMoreKey);
- } else {
- moreKeys[moreKeyIndex] = additionalMoreKey;
- }
- additionalIndex++;
- } else {
- // Filter out excessive '%' marker.
- if (out == null) {
- out = arrayAsList(moreKeys, 0, moreKeyIndex);
- }
- }
- } else {
- if (out != null) {
- out.add(moreKeySpec);
- }
- }
+ public static int getIconId(final String keySpec) {
+ if (keySpec == null) {
+ // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
+ return KeyboardIconsSet.ICON_UNDEFINED;
}
- if (additionalCount > 0 && additionalIndex == 0) {
- // No '%' marker is found in more keys.
- // Insert all additional more keys to the head of more keys.
- if (DEBUG && out != null) {
- throw new RuntimeException("Internal logic error:"
- + " moreKeys=" + Arrays.toString(moreKeys)
- + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
- }
- out = arrayAsList(additionalMoreKeys, additionalIndex, additionalCount);
- for (int i = 0; i < moreKeysCount; i++) {
- out.add(moreKeys[i]);
- }
- } else if (additionalIndex < additionalCount) {
- // The number of '%' markers are less than additional more keys.
- // Append remained additional more keys to the tail of more keys.
- if (DEBUG && out != null) {
- throw new RuntimeException("Internal logic error:"
- + " moreKeys=" + Arrays.toString(moreKeys)
- + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
- }
- out = arrayAsList(moreKeys, 0, moreKeysCount);
- for (int i = additionalIndex; i < additionalCount; i++) {
- out.add(additionalMoreKeys[additionalIndex]);
- }
- }
- if (out == null && moreKeysCount > 0) {
- return moreKeys;
- } else if (out != null && out.size() > 0) {
- return out.toArray(new String[out.size()]);
- } else {
- return null;
+ if (!hasIcon(keySpec)) {
+ return KeyboardIconsSet.ICON_UNDEFINED;
}
+ final int labelEnd = indexOfLabelEnd(keySpec);
+ final String iconName = getBeforeLabelEnd(keySpec, labelEnd)
+ .substring(KeyboardIconsSet.PREFIX_ICON.length());
+ return KeyboardIconsSet.getIconId(iconName);
}
@SuppressWarnings("serial")
@@ -377,122 +246,4 @@ public final class KeySpecParser {
super(message);
}
}
-
- public static String resolveTextReference(final String rawText,
- final KeyboardTextsSet textsSet) {
- int level = 0;
- String text = rawText;
- StringBuilder sb;
- do {
- level++;
- if (level >= MAX_STRING_REFERENCE_INDIRECTION) {
- throw new RuntimeException("too many @string/resource indirection: " + text);
- }
-
- final int prefixLen = PREFIX_TEXT.length();
- final int size = text.length();
- if (size < prefixLen) {
- return text;
- }
-
- sb = null;
- for (int pos = 0; pos < size; pos++) {
- final char c = text.charAt(pos);
- if (text.startsWith(PREFIX_TEXT, pos) && textsSet != null) {
- 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(textsSet.getText(name));
- pos = end - 1;
- } else if (c == BACKSLASH) {
- if (sb != null) {
- // Append both escape character and escaped character.
- sb.append(text.substring(pos, Math.min(pos + 2, size)));
- }
- pos++;
- } else if (sb != null) {
- sb.append(c);
- }
- }
-
- if (sb != null) {
- text = sb.toString();
- }
- } while (sb != null);
- return text;
- }
-
- private static int searchTextNameEnd(final String text, final int start) {
- final int size = text.length();
- for (int pos = start; pos < size; pos++) {
- final char c = text.charAt(pos);
- // Label name should be consisted of [a-zA-Z_0-9].
- if ((c >= 'a' && c <= 'z') || c == '_' || (c >= '0' && c <= '9')) {
- continue;
- }
- return pos;
- }
- return size;
- }
-
- public static int getIntValue(final String[] moreKeys, final String key,
- final int defaultValue) {
- if (moreKeys == null) {
- return defaultValue;
- }
- final int keyLen = key.length();
- boolean foundValue = false;
- int value = defaultValue;
- for (int i = 0; i < moreKeys.length; i++) {
- final String moreKeySpec = moreKeys[i];
- if (moreKeySpec == null || !moreKeySpec.startsWith(key)) {
- continue;
- }
- moreKeys[i] = null;
- try {
- if (!foundValue) {
- value = Integer.parseInt(moreKeySpec.substring(keyLen));
- foundValue = true;
- }
- } catch (NumberFormatException e) {
- throw new RuntimeException(
- "integer should follow after " + key + ": " + moreKeySpec);
- }
- }
- return value;
- }
-
- public static boolean getBooleanValue(final String[] moreKeys, final String key) {
- if (moreKeys == null) {
- return false;
- }
- boolean value = false;
- for (int i = 0; i < moreKeys.length; i++) {
- final String moreKeySpec = moreKeys[i];
- if (moreKeySpec == null || !moreKeySpec.equals(key)) {
- continue;
- }
- moreKeys[i] = null;
- value = true;
- }
- return value;
- }
-
- public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase,
- final Locale locale) {
- if (!Constants.isLetterCode(code) || !needsToUpperCase) return code;
- final String text = new String(new int[] { code } , 0, 1);
- final String casedText = KeySpecParser.toUpperCaseOfStringForLocale(
- text, needsToUpperCase, locale);
- return StringUtils.codePointCount(casedText) == 1
- ? casedText.codePointAt(0) : CODE_UNSPECIFIED;
- }
-
- public static String toUpperCaseOfStringForLocale(final String text,
- final boolean needsToUpperCase, final Locale locale) {
- if (text == null || !needsToUpperCase) return text;
- return text.toUpperCase(locale);
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
index e6a674334..7941ddd41 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
@@ -32,15 +32,15 @@ public abstract class KeyStyle {
protected String parseString(final TypedArray a, final int index) {
if (a.hasValue(index)) {
- return KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
+ return mTextsSet.resolveTextReference(a.getString(index));
}
return null;
}
protected String[] parseStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) {
- final String text = KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
- return KeySpecParser.splitKeySpecs(text);
+ final String text = mTextsSet.resolveTextReference(a.getString(index));
+ return MoreKeySpec.splitKeySpecs(text);
}
return null;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index 05d855e31..700c9b07c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -27,6 +27,7 @@ import com.android.inputmethod.latin.utils.XmlParseUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.util.Arrays;
import java.util.HashMap;
public final class KeyStylesSet {
@@ -90,7 +91,8 @@ public final class KeyStylesSet {
}
final Object value = mStyleAttributes.get(index);
if (value != null) {
- return (String[])value;
+ final String[] array = (String[])value;
+ return Arrays.copyOf(array, array.length);
}
final KeyStyle parentStyle = mStyles.get(mParentStyleName);
return parentStyle.getStringArray(a, index);
@@ -133,15 +135,12 @@ public final class KeyStylesSet {
public void readKeyAttributes(final TypedArray keyAttr) {
// TODO: Currently not all Key attributes can be declared as style.
- readString(keyAttr, R.styleable.Keyboard_Key_code);
readString(keyAttr, R.styleable.Keyboard_Key_altCode);
- readString(keyAttr, R.styleable.Keyboard_Key_keyLabel);
- readString(keyAttr, R.styleable.Keyboard_Key_keyOutputText);
+ readString(keyAttr, R.styleable.Keyboard_Key_keySpec);
readString(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
readStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
readStringArray(keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys);
readFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags);
- readString(keyAttr, R.styleable.Keyboard_Key_keyIcon);
readString(keyAttr, R.styleable.Keyboard_Key_keyIconDisabled);
readString(keyAttr, R.styleable.Keyboard_Key_keyIconPreview);
readInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
index 8bdad364c..df386fce4 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
@@ -16,7 +16,6 @@
package com.android.inputmethod.keyboard.internal;
-import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.util.SparseIntArray;
@@ -38,7 +37,7 @@ public final class KeyVisualAttributes {
public final float mHintLabelRatio;
public final float mPreviewTextRatio;
- public final ColorStateList mTextColorStateList;
+ public final int mTextColor;
public final int mTextInactivatedColor;
public final int mTextShadowColor;
public final int mHintLetterColor;
@@ -47,6 +46,8 @@ public final class KeyVisualAttributes {
public final int mShiftedLetterHintActivatedColor;
public final int mPreviewTextColor;
+ public final float mHintLabelVerticalAdjustment;
+
private static final int[] VISUAL_ATTRIBUTE_IDS = {
R.styleable.Keyboard_Key_keyTypeface,
R.styleable.Keyboard_Key_keyLetterSize,
@@ -65,6 +66,7 @@ public final class KeyVisualAttributes {
R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor,
R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor,
R.styleable.Keyboard_Key_keyPreviewTextColor,
+ R.styleable.Keyboard_Key_keyHintLabelVerticalAdjustment,
};
private static final SparseIntArray sVisualAttributeIds = new SparseIntArray();
private static final int ATTR_DEFINED = 1;
@@ -116,7 +118,7 @@ public final class KeyVisualAttributes {
mPreviewTextRatio = ResourceUtils.getFraction(keyAttr,
R.styleable.Keyboard_Key_keyPreviewTextRatio);
- mTextColorStateList = keyAttr.getColorStateList(R.styleable.Keyboard_Key_keyTextColor);
+ mTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextColor, 0);
mTextInactivatedColor = keyAttr.getColor(
R.styleable.Keyboard_Key_keyTextInactivatedColor, 0);
mTextShadowColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextShadowColor, 0);
@@ -127,5 +129,8 @@ public final class KeyVisualAttributes {
mShiftedLetterHintActivatedColor = keyAttr.getColor(
R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor, 0);
mPreviewTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyPreviewTextColor, 0);
+
+ mHintLabelVerticalAdjustment = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyHintLabelVerticalAdjustment, 0.0f);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index c1ae65695..dfe0df04c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -21,6 +21,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Build;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
@@ -33,17 +34,16 @@ import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.ResourceUtils;
-import com.android.inputmethod.latin.utils.RunInLocale;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.latin.utils.XmlParseUtils;
+import com.android.inputmethod.latin.utils.XmlParseUtils.ParseException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Arrays;
-import java.util.Locale;
/**
* Keyboard Building helper.
@@ -276,20 +276,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0);
params.mIconsSet.loadIcons(keyboardAttr);
- final String language = params.mId.mLocale.getLanguage();
- params.mCodesSet.setLanguage(language);
- params.mTextsSet.setLanguage(language);
- final RunInLocale<Void> job = new RunInLocale<Void>() {
- @Override
- protected Void job(final Resources res) {
- params.mTextsSet.loadStringResources(mContext);
- return null;
- }
- };
- // Null means the current system locale.
- final Locale locale = SubtypeLocaleUtils.isNoLanguage(params.mId.mSubtype)
- ? null : params.mId.mLocale;
- job.runInLocale(mResources, locale);
+ params.mTextsSet.setLocale(params.mId.mLocale, mContext);
final int resourceId = keyboardAttr.getResourceId(
R.styleable.Keyboard_touchPositionCorrectionData, 0);
@@ -456,11 +443,15 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
if (Build.VERSION.SDK_INT < supportedMinSdkVersion) {
continue;
}
+ final int labelFlags = row.getDefaultKeyLabelFlags();
+ final int backgroundType = row.getDefaultBackgroundType();
final int x = (int)row.getKeyX(null);
final int y = row.getKeyY();
- final Key key = new Key(mParams, label, null /* hintLabel */, 0 /* iconId */,
- code, outputText, x, y, (int)keyWidth, (int)row.getRowHeight(),
- row.getDefaultKeyLabelFlags(), row.getDefaultBackgroundType());
+ final int width = (int)keyWidth;
+ final int height = row.getRowHeight();
+ final Key key = new Key(label, KeyboardIconsSet.ICON_UNDEFINED, code, outputText,
+ null /* hintLabel */, labelFlags, backgroundType, x, y, width, height,
+ mParams.mHorizontalGap, mParams.mVerticalGap);
endKey(key);
row.advanceXPos(keyWidth);
}
@@ -477,7 +468,15 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY);
return;
}
- final Key key = new Key(mResources, mParams, row, parser);
+ final TypedArray keyAttr = mResources.obtainAttributes(
+ Xml.asAttributeSet(parser), R.styleable.Keyboard_Key);
+ final KeyStyle keyStyle = mParams.mKeyStyles.getKeyStyle(keyAttr, parser);
+ final String keySpec = keyStyle.getString(keyAttr, R.styleable.Keyboard_Key_keySpec);
+ if (TextUtils.isEmpty(keySpec)) {
+ throw new ParseException("Empty keySpec", parser);
+ }
+ final Key key = new Key(keySpec, keyAttr, keyStyle, mParams, row);
+ keyAttr.recycle();
if (DEBUG) {
startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY, (key.isEnabled() ? "" : " disabled"),
key, Arrays.toString(key.getMoreKeys()));
@@ -493,7 +492,11 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER);
return;
}
- final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser);
+ final TypedArray keyAttr = mResources.obtainAttributes(
+ Xml.asAttributeSet(parser), R.styleable.Keyboard_Key);
+ final KeyStyle keyStyle = mParams.mKeyStyles.getKeyStyle(keyAttr, parser);
+ final Key spacer = new Key.Spacer(keyAttr, keyStyle, mParams, row);
+ keyAttr.recycle();
if (DEBUG) startEndTag("<%s />", TAG_SPACER);
XmlParseUtils.checkEndTag(TAG_SPACER, parser);
endKey(spacer);
@@ -649,10 +652,9 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
final boolean clobberSettingsKeyMatched = matchBoolean(caseAttr,
R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
- final boolean shortcutKeyEnabledMatched = matchBoolean(caseAttr,
- R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
- final boolean shortcutKeyOnSymbolsMatched = matchBoolean(caseAttr,
- R.styleable.Keyboard_Case_shortcutKeyOnSymbols, id.mShortcutKeyOnSymbols);
+ final boolean supportsSwitchingToShortcutImeMatched = matchBoolean(caseAttr,
+ R.styleable.Keyboard_Case_supportsSwitchingToShortcutIme,
+ id.mSupportsSwitchingToShortcutIme);
final boolean hasShortcutKeyMatched = matchBoolean(caseAttr,
R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
final boolean languageSwitchKeyEnabledMatched = matchBoolean(caseAttr,
@@ -671,13 +673,12 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
final boolean selected = keyboardLayoutSetMatched && keyboardLayoutSetElementMatched
&& modeMatched && navigateNextMatched && navigatePreviousMatched
&& passwordInputMatched && clobberSettingsKeyMatched
- && shortcutKeyEnabledMatched && shortcutKeyOnSymbolsMatched
- && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
- && isMultiLineMatched && imeActionMatched && localeCodeMatched
- && languageCodeMatched && countryCodeMatched;
+ && supportsSwitchingToShortcutImeMatched && hasShortcutKeyMatched
+ && languageSwitchKeyEnabledMatched && isMultiLineMatched && imeActionMatched
+ && localeCodeMatched && languageCodeMatched && countryCodeMatched;
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", TAG_CASE,
textAttr(caseAttr.getString(
R.styleable.Keyboard_Case_keyboardLayoutSet), "keyboardLayoutSet"),
textAttr(caseAttr.getString(
@@ -694,10 +695,9 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
"clobberSettingsKey"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_passwordInput,
"passwordInput"),
- booleanAttr(caseAttr, R.styleable.Keyboard_Case_shortcutKeyEnabled,
- "shortcutKeyEnabled"),
- booleanAttr(caseAttr, R.styleable.Keyboard_Case_shortcutKeyOnSymbols,
- "shortcutKeyOnSymbols"),
+ booleanAttr(
+ caseAttr, R.styleable.Keyboard_Case_supportsSwitchingToShortcutIme,
+ "supportsSwitchingToShortcutIme"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_hasShortcutKey,
"hasShortcutKey"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index dc815e57d..06da5719b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -22,20 +22,18 @@ import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.HashMap;
public final class KeyboardCodesSet {
- private static final HashMap<String, int[]> sLanguageToCodesMap = CollectionUtils.newHashMap();
- private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
+ public static final String PREFIX_CODE = "!code/";
- private int[] mCodes = DEFAULT;
+ private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
- public void setLanguage(final String language) {
- final int[] codes = sLanguageToCodesMap.get(language);
- mCodes = (codes != null) ? codes : DEFAULT;
+ private KeyboardCodesSet() {
+ // This utility class is not publicly instantiable.
}
- public int getCode(final String name) {
+ public static int getCode(final String name) {
Integer id = sNameToIdMap.get(name);
if (id == null) throw new RuntimeException("Unknown key code: " + name);
- return mCodes[id];
+ return DEFAULT[id];
}
private static final String[] ID_TO_NAME = {
@@ -54,27 +52,10 @@ public final class KeyboardCodesSet {
"key_shift_enter",
"key_language_switch",
"key_emoji",
+ "key_alpha_from_emoji",
"key_unspecified",
- "key_left_parenthesis",
- "key_right_parenthesis",
- "key_less_than",
- "key_greater_than",
- "key_left_square_bracket",
- "key_right_square_bracket",
- "key_left_curly_bracket",
- "key_right_curly_bracket",
};
- private static final int CODE_LEFT_PARENTHESIS = '(';
- private static final int CODE_RIGHT_PARENTHESIS = ')';
- private static final int CODE_LESS_THAN_SIGN = '<';
- private static final int CODE_GREATER_THAN_SIGN = '>';
- private static final int CODE_LEFT_SQUARE_BRACKET = '[';
- private static final int CODE_RIGHT_SQUARE_BRACKET = ']';
- private static final int CODE_LEFT_CURLY_BRACKET = '{';
- private static final int CODE_RIGHT_CURLY_BRACKET = '}';
-
- // This array should be aligned with the array RTL below.
private static final int[] DEFAULT = {
Constants.CODE_TAB,
Constants.CODE_ENTER,
@@ -91,68 +72,13 @@ public final class KeyboardCodesSet {
Constants.CODE_SHIFT_ENTER,
Constants.CODE_LANGUAGE_SWITCH,
Constants.CODE_EMOJI,
+ Constants.CODE_ALPHA_FROM_EMOJI,
Constants.CODE_UNSPECIFIED,
- CODE_LEFT_PARENTHESIS,
- CODE_RIGHT_PARENTHESIS,
- CODE_LESS_THAN_SIGN,
- CODE_GREATER_THAN_SIGN,
- CODE_LEFT_SQUARE_BRACKET,
- CODE_RIGHT_SQUARE_BRACKET,
- CODE_LEFT_CURLY_BRACKET,
- CODE_RIGHT_CURLY_BRACKET,
- };
-
- private static final int[] RTL = {
- DEFAULT[0],
- DEFAULT[1],
- DEFAULT[2],
- DEFAULT[3],
- DEFAULT[4],
- DEFAULT[5],
- DEFAULT[6],
- DEFAULT[7],
- DEFAULT[8],
- DEFAULT[9],
- DEFAULT[10],
- DEFAULT[11],
- DEFAULT[12],
- DEFAULT[13],
- DEFAULT[14],
- DEFAULT[15],
- CODE_RIGHT_PARENTHESIS,
- CODE_LEFT_PARENTHESIS,
- CODE_GREATER_THAN_SIGN,
- CODE_LESS_THAN_SIGN,
- CODE_RIGHT_SQUARE_BRACKET,
- CODE_LEFT_SQUARE_BRACKET,
- CODE_RIGHT_CURLY_BRACKET,
- CODE_LEFT_CURLY_BRACKET,
- };
-
- private static final String LANGUAGE_DEFAULT = "DEFAULT";
- private static final String LANGUAGE_ARABIC = "ar";
- private static final String LANGUAGE_PERSIAN = "fa";
- private static final String LANGUAGE_HEBREW = "iw";
-
- private static final Object[] LANGUAGE_AND_CODES = {
- LANGUAGE_DEFAULT, DEFAULT,
- LANGUAGE_ARABIC, RTL,
- LANGUAGE_PERSIAN, RTL,
- LANGUAGE_HEBREW, RTL,
};
static {
- if (DEFAULT.length != RTL.length || DEFAULT.length != ID_TO_NAME.length) {
- throw new RuntimeException("Internal inconsistency");
- }
for (int i = 0; i < ID_TO_NAME.length; i++) {
sNameToIdMap.put(ID_TO_NAME[i], i);
}
-
- for (int i = 0; i < LANGUAGE_AND_CODES.length; i += 2) {
- final String language = (String)LANGUAGE_AND_CODES[i];
- final int[] codes = (int[])LANGUAGE_AND_CODES[i + 1];
- sLanguageToCodesMap.put(language, codes);
- }
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 336db186e..6c9b5adc3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -30,33 +30,51 @@ import java.util.HashMap;
public final class KeyboardIconsSet {
private static final String TAG = KeyboardIconsSet.class.getSimpleName();
+ public static final String PREFIX_ICON = "!icon/";
public static final int ICON_UNDEFINED = 0;
private static final int ATTR_UNDEFINED = 0;
+ private static final String NAME_UNDEFINED = "undefined";
+ public static final String NAME_SHIFT_KEY = "shift_key";
+ public static final String NAME_SHIFT_KEY_SHIFTED = "shift_key_shifted";
+ public static final String NAME_DELETE_KEY = "delete_key";
+ public static final String NAME_SETTINGS_KEY = "settings_key";
+ public static final String NAME_SPACE_KEY = "space_key";
+ public static final String NAME_SPACE_KEY_FOR_NUMBER_LAYOUT = "space_key_for_number_layout";
+ public static final String NAME_ENTER_KEY = "enter_key";
+ public static final String NAME_SEARCH_KEY = "search_key";
+ public static final String NAME_TAB_KEY = "tab_key";
+ public static final String NANE_TAB_KEY_PREVIEW = "tab_key_preview";
+ public static final String NAME_SHORTCUT_KEY = "shortcut_key";
+ public static final String NAME_SHORTCUT_KEY_DISABLED = "shortcut_key_disabled";
+ public static final String NAME_LANGUAGE_SWITCH_KEY = "language_switch_key";
+ public static final String NAME_ZWNJ_KEY = "zwnj_key";
+ public static final String NAME_ZWJ_KEY = "zwj_key";
+ public static final String NAME_EMOJI_KEY = "emoji_key";
+
private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
// Icon name to icon id map.
private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
private static final Object[] NAMES_AND_ATTR_IDS = {
- "undefined", ATTR_UNDEFINED,
- "shift_key", R.styleable.Keyboard_iconShiftKey,
- "delete_key", R.styleable.Keyboard_iconDeleteKey,
- "settings_key", R.styleable.Keyboard_iconSettingsKey,
- "space_key", R.styleable.Keyboard_iconSpaceKey,
- "enter_key", R.styleable.Keyboard_iconEnterKey,
- "search_key", R.styleable.Keyboard_iconSearchKey,
- "tab_key", R.styleable.Keyboard_iconTabKey,
- "shortcut_key", R.styleable.Keyboard_iconShortcutKey,
- "shortcut_for_label", R.styleable.Keyboard_iconShortcutForLabel,
- "space_key_for_number_layout", R.styleable.Keyboard_iconSpaceKeyForNumberLayout,
- "shift_key_shifted", R.styleable.Keyboard_iconShiftKeyShifted,
- "shortcut_key_disabled", R.styleable.Keyboard_iconShortcutKeyDisabled,
- "tab_key_preview", R.styleable.Keyboard_iconTabKeyPreview,
- "language_switch_key", R.styleable.Keyboard_iconLanguageSwitchKey,
- "zwnj_key", R.styleable.Keyboard_iconZwnjKey,
- "zwj_key", R.styleable.Keyboard_iconZwjKey,
- "emoji_key", R.styleable.Keyboard_iconEmojiKey,
+ NAME_UNDEFINED, ATTR_UNDEFINED,
+ NAME_SHIFT_KEY, R.styleable.Keyboard_iconShiftKey,
+ NAME_DELETE_KEY, R.styleable.Keyboard_iconDeleteKey,
+ NAME_SETTINGS_KEY, R.styleable.Keyboard_iconSettingsKey,
+ NAME_SPACE_KEY, R.styleable.Keyboard_iconSpaceKey,
+ NAME_ENTER_KEY, R.styleable.Keyboard_iconEnterKey,
+ NAME_SEARCH_KEY, R.styleable.Keyboard_iconSearchKey,
+ NAME_TAB_KEY, R.styleable.Keyboard_iconTabKey,
+ NAME_SHORTCUT_KEY, R.styleable.Keyboard_iconShortcutKey,
+ NAME_SPACE_KEY_FOR_NUMBER_LAYOUT, R.styleable.Keyboard_iconSpaceKeyForNumberLayout,
+ NAME_SHIFT_KEY_SHIFTED, R.styleable.Keyboard_iconShiftKeyShifted,
+ NAME_SHORTCUT_KEY_DISABLED, R.styleable.Keyboard_iconShortcutKeyDisabled,
+ NANE_TAB_KEY_PREVIEW, R.styleable.Keyboard_iconTabKeyPreview,
+ NAME_LANGUAGE_SWITCH_KEY, R.styleable.Keyboard_iconLanguageSwitchKey,
+ NAME_ZWNJ_KEY, R.styleable.Keyboard_iconZwnjKey,
+ NAME_ZWJ_KEY, R.styleable.Keyboard_iconZwjKey,
+ NAME_EMOJI_KEY, R.styleable.Keyboard_iconEmojiKey,
};
private static int NUM_ICONS = NAMES_AND_ATTR_IDS.length / 2;
@@ -102,7 +120,7 @@ public final class KeyboardIconsSet {
return isValidIconId(iconId) ? ICON_NAMES[iconId] : "unknown<" + iconId + ">";
}
- static int getIconId(final String name) {
+ public static int getIconId(final String name) {
Integer iconId = sNameToIdsMap.get(name);
if (iconId != null) {
return iconId;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
index d32bb7581..153391eed 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -62,7 +62,6 @@ public class KeyboardParams {
public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
- public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index dd98c1703..b98ced97c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -27,10 +27,10 @@ import com.android.inputmethod.latin.utils.RecapitalizeStatus;
*
* This class contains all keyboard state transition logic.
*
- * The input events are {@link #onLoadKeyboard()}, {@link #onSaveKeyboardState()},
- * {@link #onPressKey(int,boolean,int)}, {@link #onReleaseKey(int,boolean)},
- * {@link #onCodeInput(int,int)}, {@link #onFinishSlidingInput()},
- * {@link #onUpdateShiftState(int,int)}, {@link #onResetKeyboardStateToAlphabet()}.
+ * 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 #onUpdateShiftState(int,int)}, {@link #onResetKeyboardStateToAlphabet(int,int)}.
*
* The actions are {@link SwitchActions}'s methods.
*/
@@ -52,7 +52,8 @@ public final class KeyboardState {
/**
* Request to call back {@link KeyboardState#onUpdateShiftState(int, int)}.
*/
- public void requestUpdatingShiftState();
+ public void requestUpdatingShiftState(final int currentAutoCapsState,
+ final int currentRecapitalizeState);
public void startDoubleTapShiftKeyTimer();
public boolean isInDoubleTapShiftKeyTimeout();
@@ -117,7 +118,8 @@ public final class KeyboardState {
mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
}
- public void onLoadKeyboard() {
+ public void onLoadKeyboard(final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
if (DEBUG_EVENT) {
Log.d(TAG, "onLoadKeyboard: " + this);
}
@@ -127,7 +129,7 @@ public final class KeyboardState {
mPrevSymbolsKeyboardWasShifted = false;
mShiftKeyState.onRelease();
mSymbolKeyState.onRelease();
- onRestoreKeyboardState();
+ onRestoreKeyboardState(currentAutoCapsState, currentRecapitalizeState);
}
private static final int UNSHIFT = 0;
@@ -153,13 +155,14 @@ public final class KeyboardState {
}
}
- private void onRestoreKeyboardState() {
+ private void onRestoreKeyboardState(final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
final SavedKeyboardState state = mSavedKeyboardState;
if (DEBUG_EVENT) {
Log.d(TAG, "onRestoreKeyboardState: saved=" + state + " " + this);
}
if (!state.mIsValid || state.mIsAlphabetMode) {
- setAlphabetKeyboard();
+ setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState);
} else if (state.mIsEmojiMode) {
setEmojiKeyboard();
} else {
@@ -237,7 +240,8 @@ public final class KeyboardState {
mAlphabetShiftState.setShiftLocked(shiftLocked);
}
- private void toggleAlphabetAndSymbols() {
+ private void toggleAlphabetAndSymbols(final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
if (DEBUG_ACTION) {
Log.d(TAG, "toggleAlphabetAndSymbols: " + this);
}
@@ -251,7 +255,7 @@ public final class KeyboardState {
mPrevSymbolsKeyboardWasShifted = false;
} else {
mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
- setAlphabetKeyboard();
+ setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState);
if (mPrevMainKeyboardWasShiftLocked) {
setShiftLocked(true);
}
@@ -261,14 +265,15 @@ public final class KeyboardState {
// TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
// when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal().
- private void resetKeyboardStateToAlphabet() {
+ private void resetKeyboardStateToAlphabet(final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
if (DEBUG_ACTION) {
Log.d(TAG, "resetKeyboardStateToAlphabet: " + this);
}
if (mIsAlphabetMode) return;
mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
- setAlphabetKeyboard();
+ setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState);
if (mPrevMainKeyboardWasShiftLocked) {
setShiftLocked(true);
}
@@ -283,7 +288,8 @@ public final class KeyboardState {
}
}
- private void setAlphabetKeyboard() {
+ private void setAlphabetKeyboard(final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
if (DEBUG_ACTION) {
Log.d(TAG, "setAlphabetKeyboard");
}
@@ -294,7 +300,7 @@ public final class KeyboardState {
mIsSymbolShifted = false;
mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
mSwitchState = SWITCH_STATE_ALPHA;
- mSwitchActions.requestUpdatingShiftState();
+ mSwitchActions.requestUpdatingShiftState(currentAutoCapsState, currentRecapitalizeState);
}
private void setSymbolsKeyboard() {
@@ -304,6 +310,7 @@ public final class KeyboardState {
mSwitchActions.setSymbolsKeyboard();
mIsAlphabetMode = false;
mIsSymbolShifted = false;
+ mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
// Reset alphabet shift state.
mAlphabetShiftState.setShiftLocked(false);
mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
@@ -316,6 +323,7 @@ public final class KeyboardState {
mSwitchActions.setSymbolsShiftedKeyboard();
mIsAlphabetMode = false;
mIsSymbolShifted = true;
+ mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
// Reset alphabet shift state.
mAlphabetShiftState.setShiftLocked(false);
mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
@@ -327,16 +335,18 @@ public final class KeyboardState {
}
mIsAlphabetMode = false;
mIsEmojiMode = true;
+ mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
// Remember caps lock mode and reset alphabet shift state.
mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked();
mAlphabetShiftState.setShiftLocked(false);
mSwitchActions.setEmojiKeyboard();
}
- public void onPressKey(final int code, final boolean isSinglePointer, final int autoCaps) {
+ public void onPressKey(final int code, final boolean isSinglePointer,
+ final int currentAutoCapsState, final int currentRecapitalizeState) {
if (DEBUG_EVENT) {
- Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code)
- + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this);
+ Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code) + " single="
+ + isSinglePointer + " autoCaps=" + currentAutoCapsState + " " + this);
}
if (code != Constants.CODE_SHIFT) {
// Because the double tap shift key timer is to detect two consecutive shift key press,
@@ -348,7 +358,7 @@ public final class KeyboardState {
} else if (code == Constants.CODE_CAPSLOCK) {
// Nothing to do here. See {@link #onReleaseKey(int,boolean)}.
} else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
- onPressSymbol();
+ onPressSymbol(currentAutoCapsState, currentRecapitalizeState);
} else {
mShiftKeyState.onOtherKeyPressed();
mSymbolKeyState.onOtherKeyPressed();
@@ -360,7 +370,8 @@ public final class KeyboardState {
// As for #3, please note that it's required to check even when the auto caps mode is
// off because, for example, we may be in the #1 state within the manual temporary
// shifted mode.
- if (!isSinglePointer && mIsAlphabetMode && autoCaps != TextUtils.CAP_MODE_CHARACTERS) {
+ if (!isSinglePointer && mIsAlphabetMode
+ && currentAutoCapsState != TextUtils.CAP_MODE_CHARACTERS) {
final boolean needsToResetAutoCaps = mAlphabetShiftState.isAutomaticShifted()
|| (mAlphabetShiftState.isManualShifted() && mShiftKeyState.isReleasing());
if (needsToResetAutoCaps) {
@@ -370,31 +381,34 @@ public final class KeyboardState {
}
}
- public void onReleaseKey(final int code, final boolean withSliding) {
+ public void onReleaseKey(final int code, final boolean withSliding,
+ final int currentAutoCapsState, final int currentRecapitalizeState) {
if (DEBUG_EVENT) {
Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code)
+ " sliding=" + withSliding + " " + this);
}
if (code == Constants.CODE_SHIFT) {
- onReleaseShift(withSliding);
+ onReleaseShift(withSliding, currentAutoCapsState, currentRecapitalizeState);
} else if (code == Constants.CODE_CAPSLOCK) {
setShiftLocked(!mAlphabetShiftState.isShiftLocked());
} else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
- onReleaseSymbol(withSliding);
+ onReleaseSymbol(withSliding, currentAutoCapsState, currentRecapitalizeState);
}
}
- private void onPressSymbol() {
- toggleAlphabetAndSymbols();
+ private void onPressSymbol(final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
+ toggleAlphabetAndSymbols(currentAutoCapsState, currentRecapitalizeState);
mSymbolKeyState.onPress();
mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
}
- private void onReleaseSymbol(final boolean withSliding) {
+ private void onReleaseSymbol(final boolean withSliding, final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
if (mSymbolKeyState.isChording()) {
// Switch back to the previous keyboard mode if the user chords the mode change key and
// another key, then releases the mode change key.
- toggleAlphabetAndSymbols();
+ toggleAlphabetAndSymbols(currentAutoCapsState, currentRecapitalizeState);
} else if (!withSliding) {
// If the mode change key is being released without sliding, we should forget the
// previous symbols keyboard shift state and simply switch back to symbols layout
@@ -415,11 +429,12 @@ public final class KeyboardState {
// TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
// when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal().
- public void onResetKeyboardStateToAlphabet() {
+ public void onResetKeyboardStateToAlphabet(final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
if (DEBUG_EVENT) {
Log.d(TAG, "onResetKeyboardStateToAlphabet: " + this);
}
- resetKeyboardStateToAlphabet();
+ resetKeyboardStateToAlphabet(currentAutoCapsState, currentRecapitalizeState);
}
private void updateShiftStateForRecapitalize(final int recapitalizeMode) {
@@ -510,7 +525,8 @@ public final class KeyboardState {
}
}
- private void onReleaseShift(final boolean withSliding) {
+ private void onReleaseShift(final boolean withSliding, final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
// We are recapitalizing. We should match the keyboard state to the recapitalize
// state in priority.
@@ -533,7 +549,8 @@ public final class KeyboardState {
// After chording input, automatic shift state may have been changed depending on
// what characters were input.
mShiftKeyState.onRelease();
- mSwitchActions.requestUpdatingShiftState();
+ mSwitchActions.requestUpdatingShiftState(currentAutoCapsState,
+ currentRecapitalizeState);
return;
} else if (mAlphabetShiftState.isShiftLockShifted() && withSliding) {
// In shift locked state, shift has been pressed and slid out to other key.
@@ -570,20 +587,21 @@ public final class KeyboardState {
mShiftKeyState.onRelease();
}
- public void onFinishSlidingInput() {
+ public void onFinishSlidingInput(final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
if (DEBUG_EVENT) {
Log.d(TAG, "onFinishSlidingInput: " + this);
}
// Switch back to the previous keyboard mode if the user cancels sliding input.
switch (mSwitchState) {
case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
- toggleAlphabetAndSymbols();
+ toggleAlphabetAndSymbols(currentAutoCapsState, currentRecapitalizeState);
break;
case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
toggleShiftInSymbols();
break;
case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT:
- setAlphabetKeyboard();
+ setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState);
break;
}
}
@@ -592,10 +610,11 @@ public final class KeyboardState {
return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
}
- public void onCodeInput(final int code, final int autoCaps) {
+ public void onCodeInput(final int code, final int currentAutoCapsState,
+ final int currentRecapitalizeState) {
if (DEBUG_EVENT) {
Log.d(TAG, "onCodeInput: code=" + Constants.printableCode(code)
- + " autoCaps=" + autoCaps + " " + this);
+ + " autoCaps=" + currentAutoCapsState + " " + this);
}
switch (mSwitchState) {
@@ -631,7 +650,7 @@ public final class KeyboardState {
// Switch back to alpha keyboard mode if user types one or more non-space/enter
// characters followed by a space/enter.
if (isSpaceOrEnter(code)) {
- toggleAlphabetAndSymbols();
+ toggleAlphabetAndSymbols(currentAutoCapsState, currentRecapitalizeState);
mPrevSymbolsKeyboardWasShifted = false;
}
break;
@@ -639,9 +658,11 @@ public final class KeyboardState {
// If the code is a letter, update keyboard shift state.
if (Constants.isLetterCode(code)) {
- updateAlphabetShiftState(autoCaps, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE);
+ updateAlphabetShiftState(currentAutoCapsState, currentRecapitalizeState);
} else if (code == Constants.CODE_EMOJI) {
setEmojiKeyboard();
+ } else if (code == Constants.CODE_ALPHA_FROM_EMOJI) {
+ setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index c2a01b5e8..0047aa4a1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -18,80 +18,126 @@ package com.android.inputmethod.keyboard.internal;
import android.content.Context;
import android.content.res.Resources;
+import android.text.TextUtils;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.RunInLocale;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.HashMap;
+import java.util.Locale;
-/**
- * !!!!! DO NOT EDIT THIS FILE !!!!!
- *
- * This file is generated by tools/make-keyboard-text. The base template file is
- * tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
- *
- * This file must be updated when any text resources in keyboard layout files have been changed.
- * These text resources are referred as "!text/<resource_name>" in keyboard XML definitions,
- * and should be defined in
- * tools/make-keyboard-text/res/values-<locale>/donottranslate-more-keys.xml
- *
- * To update this file, please run the following commands.
- * $ cd $ANDROID_BUILD_TOP
- * $ mmm packages/inputmethods/LatinIME/tools/make-keyboard-text
- * $ make-keyboard-text -java packages/inputmethods/LatinIME/java/src
- *
- * The updated source file will be generated to the following path (this file).
- * packages/inputmethods/LatinIME/java/src/com/android/inputmethod/keyboard/internal/
- * KeyboardTextsSet.java
- */
public final class KeyboardTextsSet {
- // Language to texts map.
- private static final HashMap<String, String[]> sLocaleToTextsMap = CollectionUtils.newHashMap();
- private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
+ public static final String PREFIX_TEXT = "!text/";
+ public static final String SWITCH_TO_ALPHA_KEY_LABEL = "keylabel_to_alpha";
- private String[] mTexts;
+ private static final char BACKSLASH = Constants.CODE_BACKSLASH;
+ private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
+
+ private String[] mTextsTable;
// Resource name to text map.
private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
- public void setLanguage(final String language) {
- mTexts = sLocaleToTextsMap.get(language);
- if (mTexts == null) {
- mTexts = LANGUAGE_DEFAULT;
- }
- }
-
- public void loadStringResources(final Context context) {
+ public void setLocale(final Locale locale, final Context context) {
+ mTextsTable = KeyboardTextsTable.getTextsTable(locale);
+ final Resources res = context.getResources();
final int referenceId = context.getApplicationInfo().labelRes;
- loadStringResourcesInternal(context, RESOURCE_NAMES, referenceId);
+ 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);
}
@UsedForTesting
- void loadStringResourcesInternal(final Context context, final String[] resourceNames,
- final int referenceId) {
- final Resources res = context.getResources();
- final String packageName = res.getResourcePackageName(referenceId);
+ void loadStringResourcesInternal(final Resources res, final String[] resourceNames,
+ final String resourcePackageName) {
for (final String resName : resourceNames) {
- final int resId = res.getIdentifier(resName, "string", packageName);
+ final int resId = res.getIdentifier(resName, "string", resourcePackageName);
mResourceNameToTextsMap.put(resName, res.getString(resId));
}
}
public String getText(final String name) {
- String text = mResourceNameToTextsMap.get(name);
- if (text != null) {
- return text;
+ final String text = mResourceNameToTextsMap.get(name);
+ return (text != null) ? text : KeyboardTextsTable.getText(name, mTextsTable);
+ }
+
+ private static int searchTextNameEnd(final String text, final int start) {
+ final int size = text.length();
+ for (int pos = start; pos < size; pos++) {
+ final char c = text.charAt(pos);
+ // Label name should be consisted of [a-zA-Z_0-9].
+ if ((c >= 'a' && c <= 'z') || c == '_' || (c >= '0' && c <= '9')) {
+ continue;
+ }
+ return pos;
}
- final Integer id = sNameToIdsMap.get(name);
- if (id == null) throw new RuntimeException("Unknown label: " + name);
- text = (id < mTexts.length) ? mTexts[id] : null;
- return (text == null) ? LANGUAGE_DEFAULT[id] : text;
+ return size;
}
- private static final String[] RESOURCE_NAMES = {
- // These texts' name should be aligned with the @string/<name> in values/strings.xml.
+ // TODO: Resolve text reference when creating {@link KeyboardTextsTable} class.
+ public String resolveTextReference(final String rawText) {
+ if (TextUtils.isEmpty(rawText)) {
+ return null;
+ }
+ int level = 0;
+ String text = rawText;
+ StringBuilder sb;
+ do {
+ level++;
+ if (level >= MAX_STRING_REFERENCE_INDIRECTION) {
+ throw new RuntimeException("Too many " + PREFIX_TEXT + "name indirection: " + text);
+ }
+
+ final int prefixLen = PREFIX_TEXT.length();
+ final int size = text.length();
+ if (size < prefixLen) {
+ break;
+ }
+
+ sb = null;
+ for (int pos = 0; pos < size; pos++) {
+ final char c = text.charAt(pos);
+ if (text.startsWith(PREFIX_TEXT, pos)) {
+ 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;
+ } else if (c == BACKSLASH) {
+ if (sb != null) {
+ // Append both escape character and escaped character.
+ sb.append(text.substring(pos, Math.min(pos + 2, size)));
+ }
+ pos++;
+ } else if (sb != null) {
+ sb.append(c);
+ }
+ }
+
+ if (sb != null) {
+ text = sb.toString();
+ }
+ } while (sb != null);
+ 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_search_key",
"label_send_key",
"label_next_key",
"label_done_key",
@@ -100,3422 +146,4 @@ public final class KeyboardTextsSet {
"label_pause_key",
"label_wait_key",
};
-
- private static final String[] NAMES = {
- /* 0 */ "more_keys_for_a",
- /* 1 */ "more_keys_for_e",
- /* 2 */ "more_keys_for_i",
- /* 3 */ "more_keys_for_o",
- /* 4 */ "more_keys_for_u",
- /* 5 */ "more_keys_for_s",
- /* 6 */ "more_keys_for_n",
- /* 7 */ "more_keys_for_c",
- /* 8 */ "more_keys_for_y",
- /* 9 */ "more_keys_for_d",
- /* 10 */ "more_keys_for_r",
- /* 11 */ "more_keys_for_t",
- /* 12 */ "more_keys_for_z",
- /* 13 */ "more_keys_for_k",
- /* 14 */ "more_keys_for_l",
- /* 15 */ "more_keys_for_g",
- /* 16 */ "more_keys_for_v",
- /* 17 */ "more_keys_for_h",
- /* 18 */ "more_keys_for_j",
- /* 19 */ "more_keys_for_w",
- /* 20 */ "keylabel_for_nordic_row1_11",
- /* 21 */ "keylabel_for_nordic_row2_10",
- /* 22 */ "keylabel_for_nordic_row2_11",
- /* 23 */ "more_keys_for_nordic_row2_10",
- /* 24 */ "more_keys_for_nordic_row2_11",
- /* 25 */ "keylabel_for_east_slavic_row1_9",
- /* 26 */ "keylabel_for_east_slavic_row1_12",
- /* 27 */ "keylabel_for_east_slavic_row2_1",
- /* 28 */ "keylabel_for_east_slavic_row2_11",
- /* 29 */ "keylabel_for_east_slavic_row3_5",
- /* 30 */ "more_keys_for_cyrillic_u",
- /* 31 */ "more_keys_for_cyrillic_ka",
- /* 32 */ "more_keys_for_cyrillic_en",
- /* 33 */ "more_keys_for_cyrillic_ghe",
- /* 34 */ "more_keys_for_east_slavic_row2_1",
- /* 35 */ "more_keys_for_cyrillic_a",
- /* 36 */ "more_keys_for_cyrillic_o",
- /* 37 */ "more_keys_for_cyrillic_soft_sign",
- /* 38 */ "more_keys_for_east_slavic_row2_11",
- /* 39 */ "keylabel_for_south_slavic_row1_6",
- /* 40 */ "keylabel_for_south_slavic_row2_11",
- /* 41 */ "keylabel_for_south_slavic_row3_1",
- /* 42 */ "keylabel_for_south_slavic_row3_8",
- /* 43 */ "more_keys_for_cyrillic_ie",
- /* 44 */ "more_keys_for_cyrillic_i",
- /* 45 */ "label_to_alpha_key",
- /* 46 */ "single_quotes",
- /* 47 */ "double_quotes",
- /* 48 */ "single_angle_quotes",
- /* 49 */ "double_angle_quotes",
- /* 50 */ "more_keys_for_currency_dollar",
- /* 51 */ "keylabel_for_currency",
- /* 52 */ "more_keys_for_currency",
- /* 53 */ "more_keys_for_punctuation",
- /* 54 */ "more_keys_for_star",
- /* 55 */ "more_keys_for_bullet",
- /* 56 */ "more_keys_for_plus",
- /* 57 */ "more_keys_for_left_parenthesis",
- /* 58 */ "more_keys_for_right_parenthesis",
- /* 59 */ "more_keys_for_less_than",
- /* 60 */ "more_keys_for_greater_than",
- /* 61 */ "more_keys_for_arabic_diacritics",
- /* 62 */ "keyhintlabel_for_arabic_diacritics",
- /* 63 */ "keylabel_for_symbols_1",
- /* 64 */ "keylabel_for_symbols_2",
- /* 65 */ "keylabel_for_symbols_3",
- /* 66 */ "keylabel_for_symbols_4",
- /* 67 */ "keylabel_for_symbols_5",
- /* 68 */ "keylabel_for_symbols_6",
- /* 69 */ "keylabel_for_symbols_7",
- /* 70 */ "keylabel_for_symbols_8",
- /* 71 */ "keylabel_for_symbols_9",
- /* 72 */ "keylabel_for_symbols_0",
- /* 73 */ "label_to_symbol_key",
- /* 74 */ "label_to_symbol_with_microphone_key",
- /* 75 */ "additional_more_keys_for_symbols_1",
- /* 76 */ "additional_more_keys_for_symbols_2",
- /* 77 */ "additional_more_keys_for_symbols_3",
- /* 78 */ "additional_more_keys_for_symbols_4",
- /* 79 */ "additional_more_keys_for_symbols_5",
- /* 80 */ "additional_more_keys_for_symbols_6",
- /* 81 */ "additional_more_keys_for_symbols_7",
- /* 82 */ "additional_more_keys_for_symbols_8",
- /* 83 */ "additional_more_keys_for_symbols_9",
- /* 84 */ "additional_more_keys_for_symbols_0",
- /* 85 */ "more_keys_for_symbols_1",
- /* 86 */ "more_keys_for_symbols_2",
- /* 87 */ "more_keys_for_symbols_3",
- /* 88 */ "more_keys_for_symbols_4",
- /* 89 */ "more_keys_for_symbols_5",
- /* 90 */ "more_keys_for_symbols_6",
- /* 91 */ "more_keys_for_symbols_7",
- /* 92 */ "more_keys_for_symbols_8",
- /* 93 */ "more_keys_for_symbols_9",
- /* 94 */ "more_keys_for_symbols_0",
- /* 95 */ "keylabel_for_comma",
- /* 96 */ "more_keys_for_comma",
- /* 97 */ "keylabel_for_symbols_question",
- /* 98 */ "keylabel_for_symbols_semicolon",
- /* 99 */ "keylabel_for_symbols_percent",
- /* 100 */ "more_keys_for_symbols_exclamation",
- /* 101 */ "more_keys_for_symbols_question",
- /* 102 */ "more_keys_for_symbols_semicolon",
- /* 103 */ "more_keys_for_symbols_percent",
- /* 104 */ "keylabel_for_tablet_comma",
- /* 105 */ "keyhintlabel_for_tablet_comma",
- /* 106 */ "more_keys_for_tablet_comma",
- /* 107 */ "keyhintlabel_for_period",
- /* 108 */ "more_keys_for_period",
- /* 109 */ "keylabel_for_apostrophe",
- /* 110 */ "keyhintlabel_for_apostrophe",
- /* 111 */ "more_keys_for_apostrophe",
- /* 112 */ "more_keys_for_q",
- /* 113 */ "more_keys_for_x",
- /* 114 */ "keylabel_for_q",
- /* 115 */ "keylabel_for_w",
- /* 116 */ "keylabel_for_y",
- /* 117 */ "keylabel_for_x",
- /* 118 */ "keylabel_for_spanish_row2_10",
- /* 119 */ "more_keys_for_am_pm",
- /* 120 */ "settings_as_more_key",
- /* 121 */ "shortcut_as_more_key",
- /* 122 */ "action_next_as_more_key",
- /* 123 */ "action_previous_as_more_key",
- /* 124 */ "label_to_more_symbol_key",
- /* 125 */ "label_to_more_symbol_for_tablet_key",
- /* 126 */ "label_tab_key",
- /* 127 */ "label_to_phone_numeric_key",
- /* 128 */ "label_to_phone_symbols_key",
- /* 129 */ "label_time_am",
- /* 130 */ "label_time_pm",
- /* 131 */ "keylabel_for_popular_domain",
- /* 132 */ "more_keys_for_popular_domain",
- /* 133 */ "more_keys_for_smiley",
- /* 134 */ "single_laqm_raqm",
- /* 135 */ "single_laqm_raqm_rtl",
- /* 136 */ "single_raqm_laqm",
- /* 137 */ "double_laqm_raqm",
- /* 138 */ "double_laqm_raqm_rtl",
- /* 139 */ "double_raqm_laqm",
- /* 140 */ "single_lqm_rqm",
- /* 141 */ "single_9qm_lqm",
- /* 142 */ "single_9qm_rqm",
- /* 143 */ "double_lqm_rqm",
- /* 144 */ "double_9qm_lqm",
- /* 145 */ "double_9qm_rqm",
- /* 146 */ "more_keys_for_single_quote",
- /* 147 */ "more_keys_for_double_quote",
- /* 148 */ "more_keys_for_tablet_double_quote",
- /* 149 */ "emoji_key_as_more_key",
- };
-
- private static final String EMPTY = "";
-
- /* Default texts */
- private static final String[] LANGUAGE_DEFAULT = {
- /* 0~ */
- EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
- EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
- EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
- EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- /* 45 */ "ABC",
- /* 46 */ "!text/single_lqm_rqm",
- /* 47 */ "!text/double_lqm_rqm",
- /* 48 */ "!text/single_laqm_raqm",
- /* 49 */ "!text/double_laqm_raqm",
- // U+00A2: "¢" CENT SIGN
- // U+00A3: "£" POUND SIGN
- // U+20AC: "€" EURO SIGN
- // U+00A5: "¥" YEN SIGN
- // U+20B1: "₱" PESO SIGN
- /* 50 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
- /* 51 */ "$",
- /* 52 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
- /* 53 */ "!fixedColumnOrder!8,;,/,(,),#,!,\\,,?,&,\\%,+,\",-,:,',@",
- // U+2020: "†" DAGGER
- // U+2021: "‡" DOUBLE DAGGER
- // U+2605: "★" BLACK STAR
- /* 54 */ "\u2020,\u2021,\u2605",
- // U+266A: "♪" EIGHTH NOTE
- // U+2665: "♥" BLACK HEART SUIT
- // U+2660: "♠" BLACK SPADE SUIT
- // U+2666: "♦" BLACK DIAMOND SUIT
- // U+2663: "♣" BLACK CLUB SUIT
- /* 55 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
- // U+00B1: "±" PLUS-MINUS SIGN
- /* 56 */ "\u00B1",
- // The all letters need to be mirrored are found at
- // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
- /* 57 */ "!fixedColumnOrder!3,<,{,[",
- /* 58 */ "!fixedColumnOrder!3,>,},]",
- // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
- // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- // U+2264: "≤" LESS-THAN OR EQUAL TO
- // U+2265: "≥" GREATER-THAN EQUAL TO
- // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
- // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- /* 59 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
- /* 60 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
- /* 61 */ EMPTY,
- /* 62 */ EMPTY,
- /* 63 */ "1",
- /* 64 */ "2",
- /* 65 */ "3",
- /* 66 */ "4",
- /* 67 */ "5",
- /* 68 */ "6",
- /* 69 */ "7",
- /* 70 */ "8",
- /* 71 */ "9",
- /* 72 */ "0",
- // Label for "switch to symbols" key.
- /* 73 */ "?123",
- // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
- // part because it'll be appended by the code.
- /* 74 */ "123",
- /* 75~ */
- EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
- /* ~84 */
- // U+00B9: "¹" SUPERSCRIPT ONE
- // U+00BD: "½" VULGAR FRACTION ONE HALF
- // U+2153: "⅓" VULGAR FRACTION ONE THIRD
- // U+00BC: "¼" VULGAR FRACTION ONE QUARTER
- // U+215B: "⅛" VULGAR FRACTION ONE EIGHTH
- /* 85 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
- // U+00B2: "²" SUPERSCRIPT TWO
- // U+2154: "⅔" VULGAR FRACTION TWO THIRDS
- /* 86 */ "\u00B2,\u2154",
- // U+00B3: "³" SUPERSCRIPT THREE
- // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS
- // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS
- /* 87 */ "\u00B3,\u00BE,\u215C",
- // U+2074: "⁴" SUPERSCRIPT FOUR
- /* 88 */ "\u2074",
- // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS
- /* 89 */ "\u215D",
- /* 90 */ EMPTY,
- // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
- /* 91 */ "\u215E",
- /* 92 */ EMPTY,
- /* 93 */ EMPTY,
- // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N
- // U+2205: "∅" EMPTY SET
- /* 94 */ "\u207F,\u2205",
- /* 95 */ ",",
- /* 96 */ EMPTY,
- /* 97 */ "?",
- /* 98 */ ";",
- /* 99 */ "%",
- // U+00A1: "¡" INVERTED EXCLAMATION MARK
- /* 100 */ "\u00A1",
- // U+00BF: "¿" INVERTED QUESTION MARK
- /* 101 */ "\u00BF",
- /* 102 */ EMPTY,
- // U+2030: "‰" PER MILLE SIGN
- /* 103 */ "\u2030",
- /* 104 */ ",",
- /* 105~ */
- EMPTY, EMPTY, EMPTY,
- /* ~107 */
- // U+2026: "…" HORIZONTAL ELLIPSIS
- /* 108 */ "\u2026",
- /* 109 */ "\'",
- /* 110 */ "\"",
- /* 111 */ "\"",
- /* 112 */ EMPTY,
- /* 113 */ EMPTY,
- /* 114 */ "q",
- /* 115 */ "w",
- /* 116 */ "y",
- /* 117 */ "x",
- /* 118 */ EMPTY,
- /* 119 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
- /* 120 */ "!icon/settings_key|!code/key_settings",
- /* 121 */ "!icon/shortcut_key|!code/key_shortcut",
- /* 122 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
- /* 123 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
- // Label for "switch to more symbol" modifier key. Must be short to fit on key!
- /* 124 */ "= \\ <",
- // Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key!
- /* 125 */ "~ [ <",
- // Label for "Tab" key. Must be short to fit on key!
- /* 126 */ "Tab",
- // Label for "switch to phone numeric" key. Must be short to fit on key!
- /* 127 */ "123",
- // Label for "switch to phone symbols" key. Must be short to fit on key!
- // U+FF0A: "*" FULLWIDTH ASTERISK
- // U+FF03: "#" FULLWIDTH NUMBER SIGN
- /* 128 */ "\uFF0A\uFF03",
- // Key label for "ante meridiem"
- /* 129 */ "AM",
- // Key label for "post meridiem"
- /* 130 */ "PM",
- /* 131 */ ".com",
- // popular web domains for the locale - most popular, displayed on the keyboard
- /* 132 */ "!hasLabels!,.net,.org,.gov,.edu",
- /* 133 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
- // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
- // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
- // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- // The following characters don't need BIDI mirroring.
- // U+2018: "‘" LEFT SINGLE QUOTATION MARK
- // U+2019: "’" RIGHT SINGLE QUOTATION MARK
- // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
- // U+201C: "“" LEFT DOUBLE QUOTATION MARK
- // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
- // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
- // Abbreviations are:
- // laqm: LEFT-POINTING ANGLE QUOTATION MARK
- // raqm: RIGHT-POINTING ANGLE QUOTATION MARK
- // rtl: Right-To-Left script order
- // lqm: LEFT QUOTATION MARK
- // rqm: RIGHT QUOTATION MARK
- // 9qm: LOW-9 QUOTATION MARK
- // The following each quotation mark pair consist of
- // <opening quotation mark>, <closing quotation mark>
- // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
- /* 134 */ "\u2039,\u203A",
- /* 135 */ "\u2039|\u203A,\u203A|\u2039",
- /* 136 */ "\u203A,\u2039",
- /* 137 */ "\u00AB,\u00BB",
- /* 138 */ "\u00AB|\u00BB,\u00BB|\u00AB",
- /* 139 */ "\u00BB,\u00AB",
- // The following each quotation mark triplet consists of
- // <another quotation mark>, <opening quotation mark>, <closing quotation mark>
- // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
- /* 140 */ "\u201A,\u2018,\u2019",
- /* 141 */ "\u2019,\u201A,\u2018",
- /* 142 */ "\u2018,\u201A,\u2019",
- /* 143 */ "\u201E,\u201C,\u201D",
- /* 144 */ "\u201D,\u201E,\u201C",
- /* 145 */ "\u201C,\u201E,\u201D",
- /* 146 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
- /* 147 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
- /* 148 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
- /* 149 */ "!icon/emoji_key|!code/key_emoji",
- };
-
- /* Language af: Afrikaans */
- private static final String[] LANGUAGE_af = {
- // This is the same as Dutch except more keys of y and demoting vowels with diaeresis.
- // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
- // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* 0 */ "\u00E1,\u00E2,\u00E4,\u00E0,\u00E6,\u00E3,\u00E5,\u0101",
- // 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
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* 1 */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
- // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
- // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- // U+0133: "ij" LATIN SMALL LIGATURE IJ
- /* 2 */ "\u00ED,\u00EC,\u00EF,\u00EE,\u012F,\u012B,\u0133",
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* 3 */ "\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
- /* 4 */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
- /* 5 */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\u00F1,\u0144",
- /* 7 */ null,
- // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
- // U+0133: "ij" LATIN SMALL LIGATURE IJ
- /* 8 */ "\u00FD,\u0133",
- };
-
- /* Language ar: Arabic */
- private static final String[] LANGUAGE_ar = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+0623: "ا" ARABIC LETTER ALEF
- // U+200C: ZERO WIDTH NON-JOINER
- // U+0628: "ب" ARABIC LETTER BEH
- // U+062C: "پ" ARABIC LETTER PEH
- /* 45 */ "\u0623\u200C\u0628\u200C\u062C",
- /* 46 */ null,
- /* 47 */ null,
- /* 48 */ "!text/single_laqm_raqm_rtl",
- /* 49 */ "!text/double_laqm_raqm_rtl",
- /* 50~ */
- null, null, null,
- /* ~52 */
- // U+061F: "؟" ARABIC QUESTION MARK
- // U+060C: "،" ARABIC COMMA
- // U+061B: "؛" ARABIC SEMICOLON
- /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
- // U+2605: "★" BLACK STAR
- // U+066D: "٭" ARABIC FIVE POINTED STAR
- /* 54 */ "\u2605,\u066D",
- // U+266A: "♪" EIGHTH NOTE
- /* 55 */ "\u266A",
- /* 56 */ null,
- // The all letters need to be mirrored are found at
- // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
- // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
- // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
- /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
- /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
- // U+2264: "≤" LESS-THAN OR EQUAL TO
- // U+2265: "≥" GREATER-THAN EQUAL TO
- // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
- // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
- // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
- /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
- // U+0655: "ٕ" ARABIC HAMZA BELOW
- // U+0654: "ٔ" ARABIC HAMZA ABOVE
- // U+0652: "ْ" ARABIC SUKUN
- // U+064D: "ٍ" ARABIC KASRATAN
- // U+064C: "ٌ" ARABIC DAMMATAN
- // U+064B: "ً" ARABIC FATHATAN
- // U+0651: "ّ" ARABIC SHADDA
- // U+0656: "ٖ" ARABIC SUBSCRIPT ALEF
- // U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF
- // U+0653: "ٓ" ARABIC MADDAH ABOVE
- // U+0650: "ِ" ARABIC KASRA
- // U+064F: "ُ" ARABIC DAMMA
- // U+064E: "َ" ARABIC FATHA
- // U+0640: "ـ" ARABIC TATWEEL
- // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
- // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
- /* 61 */ "!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",
- /* 62 */ "\u0651",
- // U+0661: "١" ARABIC-INDIC DIGIT ONE
- /* 63 */ "\u0661",
- // U+0662: "٢" ARABIC-INDIC DIGIT TWO
- /* 64 */ "\u0662",
- // U+0663: "٣" ARABIC-INDIC DIGIT THREE
- /* 65 */ "\u0663",
- // U+0664: "٤" ARABIC-INDIC DIGIT FOUR
- /* 66 */ "\u0664",
- // U+0665: "٥" ARABIC-INDIC DIGIT FIVE
- /* 67 */ "\u0665",
- // U+0666: "٦" ARABIC-INDIC DIGIT SIX
- /* 68 */ "\u0666",
- // U+0667: "٧" ARABIC-INDIC DIGIT SEVEN
- /* 69 */ "\u0667",
- // U+0668: "٨" ARABIC-INDIC DIGIT EIGHT
- /* 70 */ "\u0668",
- // U+0669: "٩" ARABIC-INDIC DIGIT NINE
- /* 71 */ "\u0669",
- // U+0660: "٠" ARABIC-INDIC DIGIT ZERO
- /* 72 */ "\u0660",
- // Label for "switch to symbols" key.
- // U+061F: "؟" ARABIC QUESTION MARK
- /* 73 */ "\u0663\u0662\u0661\u061F",
- // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
- // part because it'll be appended by the code.
- /* 74 */ "\u0663\u0662\u0661",
- /* 75 */ "1",
- /* 76 */ "2",
- /* 77 */ "3",
- /* 78 */ "4",
- /* 79 */ "5",
- /* 80 */ "6",
- /* 81 */ "7",
- /* 82 */ "8",
- /* 83 */ "9",
- // U+066B: "٫" ARABIC DECIMAL SEPARATOR
- // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
- /* 84 */ "0,\u066B,\u066C",
- /* 85~ */
- null, null, null, null, null, null, null, null, null, null,
- /* ~94 */
- // U+060C: "،" ARABIC COMMA
- /* 95 */ "\u060C",
- /* 96 */ "\\,",
- /* 97 */ "\u061F",
- /* 98 */ "\u061B",
- // U+066A: "٪" ARABIC PERCENT SIGN
- /* 99 */ "\u066A",
- /* 100 */ null,
- /* 101 */ "?",
- /* 102 */ ";",
- // U+2030: "‰" PER MILLE SIGN
- /* 103 */ "\\%,\u2030",
- /* 104~ */
- null, null, null, null, null,
- /* ~108 */
- // U+060C: "،" ARABIC COMMA
- // U+061B: "؛" ARABIC SEMICOLON
- // U+061F: "؟" ARABIC QUESTION MARK
- /* 109 */ "\u060C",
- /* 110 */ "\u061F",
- /* 111 */ "\u061F,\u061B,!,:,-,/,\',\"",
- };
-
- /* Language az: Azerbaijani */
- private static final String[] LANGUAGE_az = {
- // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- /* 0 */ "\u00E2",
- // U+0259: "ə" LATIN SMALL LETTER SCHWA
- /* 1 */ "\u0259",
- // 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
- /* 2 */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
- // 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
- /* 3 */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\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
- /* 4 */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
- // 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
- /* 5 */ "\u015F,\u00DF,\u015B,\u0161",
- /* 6 */ 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
- /* 7 */ "\u00E7,\u0107,\u010D",
- /* 8~ */
- null, null, null, null, null, null, null,
- /* ~14 */
- // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
- /* 15 */ "\u011F",
- };
-
- /* Language be: Belarusian */
- private static final String[] LANGUAGE_be = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null,
- /* ~24 */
- // U+045E: "ў" CYRILLIC SMALL LETTER SHORT U
- /* 25 */ "\u045E",
- // U+0451: "ё" CYRILLIC SMALL LETTER IO
- /* 26 */ "\u0451",
- // U+044B: "ы" CYRILLIC SMALL LETTER YERU
- /* 27 */ "\u044B",
- // U+044D: "э" CYRILLIC SMALL LETTER E
- /* 28 */ "\u044D",
- // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
- /* 29 */ "\u0456",
- /* 30~ */
- null, null, null, null, null, null, null,
- /* ~36 */
- // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 37 */ "\u044A",
- /* 38~ */
- null, null, null, null, null,
- /* ~42 */
- // U+0451: "ё" CYRILLIC SMALL LETTER IO
- /* 43 */ "\u0451",
- /* 44 */ null,
- // Label for "switch to alphabetic" key.
- // U+0410: "А" CYRILLIC CAPITAL LETTER A
- // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
- // U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- };
-
- /* Language bg: Bulgarian */
- private static final String[] LANGUAGE_bg = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+0410: "А" CYRILLIC CAPITAL LETTER A
- // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
- // U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ null,
- // single_quotes of Bulgarian is default single_quotes_right_left.
- /* 47 */ "!text/double_9qm_lqm",
- };
-
- /* Language ca: Catalan */
- private static final String[] LANGUAGE_ca = {
- // 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+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- // U+00AA: "ª" FEMININE ORDINAL INDICATOR
- /* 0 */ "\u00E0,\u00E1,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA",
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // 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
- /* 1 */ "\u00E8,\u00E9,\u00EB,\u00EA,\u0119,\u0117,\u0113",
- // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
- // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- /* 2 */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
- // 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+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- // U+00BA: "º" MASCULINE ORDINAL INDICATOR
- /* 3 */ "\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
- /* 4 */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* 5 */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\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
- /* 7 */ "\u00E7,\u0107,\u010D",
- /* 8~ */
- null, null, null, null, null, null,
- /* ~13 */
- // U+00B7: "·" MIDDLE DOT
- // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
- /* 14 */ "l\u00B7l,\u0142",
- /* 15~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
- /* ~52 */
- // U+00B7: "·" MIDDLE DOT
- /* 53 */ "!fixedColumnOrder!9,;,/,(,),#,\u00B7,!,\\,,?,&,\\%,+,\",-,:,',@",
- /* 54~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null,
- /* ~107 */
- /* 108 */ "?,\u00B7",
- /* 109~ */
- null, null, null, null, null, null, null, null, null,
- /* ~117 */
- // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- /* 118 */ "\u00E7",
- };
-
- /* Language cs: Czech */
- private static final String[] LANGUAGE_cs = {
- // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* 0 */ "\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
- // 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
- /* 1 */ "\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
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- /* 2 */ "\u00ED,\u00EE,\u00EF,\u00EC,\u012F,\u012B",
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* 3 */ "\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
- /* 4 */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B",
- // U+0161: "š" LATIN SMALL LETTER S WITH CARON
- // U+00DF: "ß" LATIN SMALL LETTER SHARP S
- // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
- /* 5 */ "\u0161,\u00DF,\u015B",
- // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\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
- /* 7 */ "\u010D,\u00E7,\u0107",
- // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
- // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
- /* 8 */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* 9 */ "\u010F",
- // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
- /* 10 */ "\u0159",
- // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
- /* 11 */ "\u0165",
- // 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
- /* 12 */ "\u017E,\u017A,\u017C",
- /* 13~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
- };
-
- /* Language da: Danish */
- private static final String[] LANGUAGE_da = {
- // 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
- /* 0 */ "\u00E1,\u00E4,\u00E0,\u00E2,\u00E3,\u0101",
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- /* 1 */ "\u00E9,\u00EB",
- // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
- // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
- /* 2 */ "\u00ED,\u00EF",
- // 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
- /* 3 */ "\u00F3,\u00F4,\u00F2,\u00F5,\u0153,\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
- /* 4 */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
- // U+00DF: "ß" LATIN SMALL LETTER SHARP S
- // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
- // U+0161: "š" LATIN SMALL LETTER S WITH CARON
- /* 5 */ "\u00DF,\u015B,\u0161",
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\u00F1,\u0144",
- /* 7 */ null,
- // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
- // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
- /* 8 */ "\u00FD,\u00FF",
- // U+00F0: "ð" LATIN SMALL LETTER ETH
- /* 9 */ "\u00F0",
- /* 10~ */
- null, null, null, null,
- /* ~13 */
- // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
- /* 14 */ "\u0142",
- /* 15~ */
- null, null, null, null, null,
- /* ~19 */
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- /* 20 */ "\u00E5",
- // U+00E6: "æ" LATIN SMALL LETTER AE
- /* 21 */ "\u00E6",
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- /* 22 */ "\u00F8",
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- /* 23 */ "\u00E4",
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- /* 24 */ "\u00F6",
- /* 25~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
- };
-
- /* Language de: German */
- private static final String[] LANGUAGE_de = {
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
- // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* 0 */ "\u00E4,\u00E2,\u00E0,\u00E1,\u00E6,\u00E3,\u00E5,\u0101",
- // 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
- /* 1 */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0117",
- /* 2 */ null,
- // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* 3 */ "\u00F6,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u00F8,\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
- /* 4 */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
- // U+00DF: "ß" LATIN SMALL LETTER SHARP S
- // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
- // U+0161: "š" LATIN SMALL LETTER S WITH CARON
- /* 5 */ "\u00DF,\u015B,\u0161",
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\u00F1,\u0144",
- /* 7~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
- };
-
- /* Language el: Greek */
- private static final String[] LANGUAGE_el = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+0391: "Α" GREEK CAPITAL LETTER ALPHA
- // U+0392: "Β" GREEK CAPITAL LETTER BETA
- // U+0393: "Γ" GREEK CAPITAL LETTER GAMMA
- /* 45 */ "\u0391\u0392\u0393",
- };
-
- /* Language en: English */
- private static final String[] LANGUAGE_en = {
- // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* 0 */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
- // 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
- /* 1 */ "\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
- /* 2 */ "\u00EE,\u00EF,\u00ED,\u012B,\u00EC",
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- /* 3 */ "\u00F4,\u00F6,\u00F2,\u00F3,\u0153,\u00F8,\u014D,\u00F5",
- // 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+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* 4 */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B",
- // U+00DF: "ß" LATIN SMALL LETTER SHARP S
- /* 5 */ "\u00DF",
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* 6 */ "\u00F1",
- // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- /* 7 */ "\u00E7",
- };
-
- /* Language eo: Esperanto */
- private static final String[] LANGUAGE_eo = {
- // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
- // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
- // U+00AA: "ª" FEMININE ORDINAL INDICATOR
- /* 0 */ "\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101,\u0103,\u0105,\u00AA",
- // 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
- /* 1 */ "\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
- // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- // U+0131: "ı" LATIN SMALL LETTER DOTLESS I
- // U+0133: "ij" LATIN SMALL LIGATURE IJ
- /* 2 */ "\u00ED,\u00EE,\u00EF,\u0129,\u00EC,\u012F,\u012B,\u0131,\u0133",
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
- // U+00BA: "º" MASCULINE ORDINAL INDICATOR
- /* 3 */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D,\u0151,\u00BA",
- // 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
- // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE
- // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
- // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
- // U+00B5: "µ" MICRO SIGN
- /* 4 */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B,\u0169,\u0171,\u0173,\u00B5",
- // 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
- /* 5 */ "\u00DF,\u0161,\u015B,\u0219,\u015F",
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
- // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
- // U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
- // U+014B: "ŋ" LATIN SMALL LETTER ENG
- /* 6 */ "\u00F1,\u0144,\u0146,\u0148,\u0149,\u014B",
- // 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
- /* 7 */ "\u0107,\u010D,\u00E7,\u010B",
- // 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
- /* 8 */ "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
- /* 9 */ "\u00F0,\u010F,\u0111",
- // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
- // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
- // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
- /* 10 */ "\u0159,\u0155,\u0157",
- // 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
- // U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE
- /* 11 */ "\u0165,\u021B,\u0163,\u0167",
- // 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
- /* 12 */ "\u017A,\u017C,\u017E",
- // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
- // U+0138: "ĸ" LATIN SMALL LETTER KRA
- /* 13 */ "\u0137,\u0138",
- // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
- // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
- // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
- // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT
- // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
- /* 14 */ "\u013A,\u013C,\u013E,\u0140,\u0142",
- // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
- // U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE
- // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
- /* 15 */ "\u011F,\u0121,\u0123",
- // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
- /* 16 */ "w,\u0175",
- // U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX
- // U+0127: "ħ" LATIN SMALL LETTER H WITH STROKE
- /* 17 */ "\u0125,\u0127",
- /* 18 */ null,
- // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
- /* 19 */ "w,\u0175",
- /* 20~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null,
- /* ~111 */
- /* 112 */ "q",
- /* 113 */ "x",
- // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
- /* 114 */ "\u015D",
- // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
- /* 115 */ "\u011D",
- // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
- /* 116 */ "\u016D",
- // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
- /* 117 */ "\u0109",
- // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
- /* 118 */ "\u0135",
- };
-
- /* Language es: Spanish */
- private static final String[] LANGUAGE_es = {
- // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
- // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- // U+00AA: "ª" FEMININE ORDINAL INDICATOR
- /* 0 */ "\u00E1,\u00E0,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA",
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // 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
- /* 1 */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
- // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
- // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- /* 2 */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- // U+00BA: "º" MASCULINE ORDINAL INDICATOR
- /* 3 */ "\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
- /* 4 */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* 5 */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\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
- /* 7 */ "\u00E7,\u0107,\u010D",
- /* 8~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~52 */
- // U+00A1: "¡" INVERTED EXCLAMATION MARK
- // U+00BF: "¿" INVERTED QUESTION MARK
- /* 53 */ "!fixedColumnOrder!4,;,!,\\,,?,:,\u00A1,@,\u00BF",
- /* 54~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
- /* ~105 */
- // U+00A1: "¡" INVERTED EXCLAMATION MARK
- /* 106 */ "!,\u00A1",
- /* 107 */ null,
- // U+00BF: "¿" INVERTED QUESTION MARK
- /* 108 */ "?,\u00BF",
- /* 109 */ "\"",
- /* 110 */ "\'",
- /* 111 */ "\'",
- /* 112~ */
- null, null, null, null, null, null,
- /* ~117 */
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* 118 */ "\u00F1",
- };
-
- /* Language et: Estonian */
- private static final String[] LANGUAGE_et = {
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- // 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+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
- /* 0 */ "\u00E4,\u0101,\u00E0,\u00E1,\u00E2,\u00E3,\u00E5,\u00E6,\u0105",
- // 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
- /* 1 */ "\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
- // 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+0131: "ı" LATIN SMALL LETTER DOTLESS I
- /* 2 */ "\u012B,\u00EC,\u012F,\u00ED,\u00EE,\u00EF,\u0131",
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- /* 3 */ "\u00F6,\u00F5,\u00F2,\u00F3,\u00F4,\u0153,\u0151,\u00F8",
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
- // 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+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
- // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
- /* 4 */ "\u00FC,\u016B,\u0173,\u00F9,\u00FA,\u00FB,\u016F,\u0171",
- // 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
- /* 5 */ "\u0161,\u00DF,\u015B,\u015F",
- // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\u0146,\u00F1,\u0144,\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
- /* 7 */ "\u010D,\u00E7,\u0107",
- // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
- // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
- /* 8 */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* 9 */ "\u010F",
- // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
- // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
- // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
- /* 10 */ "\u0157,\u0159,\u0155",
- // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
- // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
- /* 11 */ "\u0163,\u0165",
- // 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
- /* 12 */ "\u017E,\u017C,\u017A",
- // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
- /* 13 */ "\u0137",
- // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
- // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
- // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
- // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
- /* 14 */ "\u013C,\u0142,\u013A,\u013E",
- // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
- // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
- /* 15 */ "\u0123,\u011F",
- /* 16~ */
- null, null, null, null,
- /* ~19 */
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- /* 20 */ "\u00FC",
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- /* 21 */ "\u00F6",
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- /* 22 */ "\u00E4",
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- /* 23 */ "\u00F5",
- /* 24~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- };
-
- /* Language fa: Persian */
- private static final String[] LANGUAGE_fa = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+0627: "ا" ARABIC LETTER ALEF
- // U+200C: ZERO WIDTH NON-JOINER
- // U+0628: "ب" ARABIC LETTER BEH
- // U+067E: "پ" ARABIC LETTER PEH
- /* 45 */ "\u0627\u200C\u0628\u200C\u067E",
- /* 46 */ null,
- /* 47 */ null,
- /* 48 */ "!text/single_laqm_raqm_rtl",
- /* 49 */ "!text/double_laqm_raqm_rtl",
- /* 50 */ null,
- // U+FDFC: "﷼" RIAL SIGN
- /* 51 */ "\uFDFC",
- /* 52 */ null,
- // U+061F: "؟" ARABIC QUESTION MARK
- // U+060C: "،" ARABIC COMMA
- // U+061B: "؛" ARABIC SEMICOLON
- /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
- // U+2605: "★" BLACK STAR
- // U+066D: "٭" ARABIC FIVE POINTED STAR
- /* 54 */ "\u2605,\u066D",
- // U+266A: "♪" EIGHTH NOTE
- /* 55 */ "\u266A",
- /* 56 */ null,
- // The all letters need to be mirrored are found at
- // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
- // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
- // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
- /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
- /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
- // U+2264: "≤" LESS-THAN OR EQUAL TO
- // U+2265: "≥" GREATER-THAN EQUAL TO
- // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
- // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
- // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
- /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
- // U+0655: "ٕ" ARABIC HAMZA BELOW
- // U+0652: "ْ" ARABIC SUKUN
- // U+0651: "ّ" ARABIC SHADDA
- // U+064C: "ٌ" ARABIC DAMMATAN
- // U+064D: "ٍ" ARABIC KASRATAN
- // U+064B: "ً" ARABIC FATHATAN
- // U+0654: "ٔ" ARABIC HAMZA ABOVE
- // U+0656: "ٖ" ARABIC SUBSCRIPT ALEF
- // U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF
- // U+0653: "ٓ" ARABIC MADDAH ABOVE
- // U+064F: "ُ" ARABIC DAMMA
- // U+0650: "ِ" ARABIC KASRA
- // U+064E: "َ" ARABIC FATHA
- // U+0640: "ـ" ARABIC TATWEEL
- // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
- // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
- /* 61 */ "!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",
- /* 62 */ "\u064B",
- // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE
- /* 63 */ "\u06F1",
- // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO
- /* 64 */ "\u06F2",
- // U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE
- /* 65 */ "\u06F3",
- // U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR
- /* 66 */ "\u06F4",
- // U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE
- /* 67 */ "\u06F5",
- // U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX
- /* 68 */ "\u06F6",
- // U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN
- /* 69 */ "\u06F7",
- // U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT
- /* 70 */ "\u06F8",
- // U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE
- /* 71 */ "\u06F9",
- // U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO
- /* 72 */ "\u06F0",
- // Label for "switch to symbols" key.
- // U+061F: "؟" ARABIC QUESTION MARK
- /* 73 */ "\u06F3\u06F2\u06F1\u061F",
- // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
- // part because it'll be appended by the code.
- /* 74 */ "\u06F3\u06F2\u06F1",
- /* 75 */ "1",
- /* 76 */ "2",
- /* 77 */ "3",
- /* 78 */ "4",
- /* 79 */ "5",
- /* 80 */ "6",
- /* 81 */ "7",
- /* 82 */ "8",
- /* 83 */ "9",
- // U+066B: "٫" ARABIC DECIMAL SEPARATOR
- // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
- /* 84 */ "0,\u066B,\u066C",
- /* 85~ */
- null, null, null, null, null, null, null, null, null, null,
- /* ~94 */
- // U+060C: "،" ARABIC COMMA
- /* 95 */ "\u060C",
- /* 96 */ "\\,",
- /* 97 */ "\u061F",
- /* 98 */ "\u061B",
- // U+066A: "٪" ARABIC PERCENT SIGN
- /* 99 */ "\u066A",
- /* 100 */ null,
- /* 101 */ "?",
- /* 102 */ ";",
- // U+2030: "‰" PER MILLE SIGN
- /* 103 */ "\\%,\u2030",
- // U+060C: "،" ARABIC COMMA
- // U+061B: "؛" ARABIC SEMICOLON
- // U+061F: "؟" ARABIC QUESTION MARK
- // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
- // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- /* 104 */ "\u060C",
- /* 105 */ "!",
- /* 106 */ "!,\\,",
- /* 107 */ "\u061F",
- /* 108 */ "\u061F,?",
- /* 109 */ "\u060C",
- /* 110 */ "\u061F",
- /* 111 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
- };
-
- /* Language fi: Finnish */
- private static final String[] LANGUAGE_fi = {
- // 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
- /* 0 */ "\u00E6,\u00E0,\u00E1,\u00E2,\u00E3,\u0101",
- /* 1 */ null,
- /* 2 */ null,
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* 3 */ "\u00F8,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u014D",
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- /* 4 */ "\u00FC",
- // U+0161: "š" LATIN SMALL LETTER S WITH CARON
- // U+00DF: "ß" LATIN SMALL LETTER SHARP S
- // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
- /* 5 */ "\u0161,\u00DF,\u015B",
- /* 6~ */
- null, null, null, null, null, null,
- /* ~11 */
- // 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
- /* 12 */ "\u017E,\u017A,\u017C",
- /* 13~ */
- null, null, null, null, null, null, null,
- /* ~19 */
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- /* 20 */ "\u00E5",
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- /* 21 */ "\u00F6",
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- /* 22 */ "\u00E4",
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- /* 23 */ "\u00F8",
- // U+00E6: "æ" LATIN SMALL LETTER AE
- /* 24 */ "\u00E6",
- };
-
- /* Language fr: French */
- private static final String[] LANGUAGE_fr = {
- // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
- // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- // U+00AA: "ª" FEMININE ORDINAL INDICATOR
- /* 0 */ "\u00E0,\u00E2,%,\u00E6,\u00E1,\u00E4,\u00E3,\u00E5,\u0101,\u00AA",
- // 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
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* 1 */ "\u00E9,\u00E8,\u00EA,\u00EB,%,\u0119,\u0117,\u0113",
- // 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
- /* 2 */ "\u00EE,%,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // 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
- // U+00BA: "º" MASCULINE ORDINAL INDICATOR
- /* 3 */ "\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
- /* 4 */ "\u00F9,\u00FB,%,\u00FC,\u00FA,\u016B",
- /* 5 */ null,
- /* 6 */ 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
- /* 7 */ "\u00E7,\u0107,\u010D",
- // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
- /* 8 */ "%,\u00FF",
- };
-
- /* Language hi: Hindi */
- private static final String[] LANGUAGE_hi = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+0915: "क" DEVANAGARI LETTER KA
- // U+0916: "ख" DEVANAGARI LETTER KHA
- // U+0917: "ग" DEVANAGARI LETTER GA
- /* 45 */ "\u0915\u0916\u0917",
- /* 46~ */
- null, null, null, null, null,
- /* ~50 */
- // U+20B9: "₹" INDIAN RUPEE SIGN
- /* 51 */ "\u20B9",
- /* 52~ */
- null, null, null, null, null, null, null, null, null, null, null,
- /* ~62 */
- // U+0967: "१" DEVANAGARI DIGIT ONE
- /* 63 */ "\u0967",
- // U+0968: "२" DEVANAGARI DIGIT TWO
- /* 64 */ "\u0968",
- // U+0969: "३" DEVANAGARI DIGIT THREE
- /* 65 */ "\u0969",
- // U+096A: "४" DEVANAGARI DIGIT FOUR
- /* 66 */ "\u096A",
- // U+096B: "५" DEVANAGARI DIGIT FIVE
- /* 67 */ "\u096B",
- // U+096C: "६" DEVANAGARI DIGIT SIX
- /* 68 */ "\u096C",
- // U+096D: "७" DEVANAGARI DIGIT SEVEN
- /* 69 */ "\u096D",
- // U+096E: "८" DEVANAGARI DIGIT EIGHT
- /* 70 */ "\u096E",
- // U+096F: "९" DEVANAGARI DIGIT NINE
- /* 71 */ "\u096F",
- // U+0966: "०" DEVANAGARI DIGIT ZERO
- /* 72 */ "\u0966",
- // Label for "switch to symbols" key.
- /* 73 */ "?\u0967\u0968\u0969",
- // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
- // part because it'll be appended by the code.
- /* 74 */ "\u0967\u0968\u0969",
- /* 75 */ "1",
- /* 76 */ "2",
- /* 77 */ "3",
- /* 78 */ "4",
- /* 79 */ "5",
- /* 80 */ "6",
- /* 81 */ "7",
- /* 82 */ "8",
- /* 83 */ "9",
- /* 84 */ "0",
- };
-
- /* Language hr: Croatian */
- private static final String[] LANGUAGE_hr = {
- /* 0~ */
- null, null, null, null, null,
- /* ~4 */
- // U+0161: "š" LATIN SMALL LETTER S WITH CARON
- // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
- // U+00DF: "ß" LATIN SMALL LETTER SHARP S
- /* 5 */ "\u0161,\u015B,\u00DF",
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\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
- /* 7 */ "\u010D,\u0107,\u00E7",
- /* 8 */ null,
- // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
- /* 9 */ "\u0111",
- /* 10 */ null,
- /* 11 */ null,
- // 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
- /* 12 */ "\u017E,\u017A,\u017C",
- /* 13~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
- };
-
- /* Language hu: Hungarian */
- private static final String[] LANGUAGE_hu = {
- // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* 0 */ "\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
- // 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
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* 1 */ "\u00E9,\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
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- /* 2 */ "\u00ED,\u00EE,\u00EF,\u00EC,\u012F,\u012B",
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE 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+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* 3 */ "\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
- /* 4 */ "\u00FA,\u00FC,\u0171,\u00FB,\u00F9,\u016B",
- /* 5~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
- };
-
- /* Language hy: Armenian */
- private static final String[] LANGUAGE_hy = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
- /* ~52 */
- // U+058A: "֊" ARMENIAN HYPHEN
- // U+055C: "՜" ARMENIAN EXCLAMATION MARK
- // U+055D: "՝" ARMENIAN COMMA
- // U+055E: "՞" ARMENIAN QUESTION MARK
- // U+0559: "ՙ" ARMENIAN MODIFIER LETTER LEFT HALF RING
- // U+055A: "՚" ARMENIAN APOSTROPHE
- // U+055B: "՛" ARMENIAN EMPHASIS MARK
- // U+055F: "՟" ARMENIAN ABBREVIATION MARK
- /* 53 */ "!fixedColumnOrder!8,!,?,\\,,.,\u058A,\u055C,\u055D,\u055E,:,;,@,\u0559,\u055A,\u055B,\u055F",
- /* 54~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null,
- /* ~99 */
- // U+055C: "՜" ARMENIAN EXCLAMATION MARK
- // U+00A1: "¡" INVERTED EXCLAMATION MARK
- /* 100 */ "\u055C,\u00A1",
- // U+055E: "՞" ARMENIAN QUESTION MARK
- // U+00BF: "¿" INVERTED QUESTION MARK
- /* 101 */ "\u055E,\u00BF",
- };
-
- /* Language is: Icelandic */
- private static final String[] LANGUAGE_is = {
- // 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+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
- /* 0 */ "\u00E1,\u00E4,\u00E6,\u00E5,\u00E0,\u00E2,\u00E3,\u0101",
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // 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
- /* 1 */ "\u00E9,\u00EB,\u00E8,\u00EA,\u0119,\u0117,\u0113",
- // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
- // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
- // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- /* 2 */ "\u00ED,\u00EF,\u00EE,\u00EC,\u012F,\u012B",
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* 3 */ "\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
- /* 4 */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
- /* 5~ */
- null, null, null,
- /* ~7 */
- // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
- // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
- /* 8 */ "\u00FD,\u00FF",
- // U+00F0: "ð" LATIN SMALL LETTER ETH
- /* 9 */ "\u00F0",
- /* 10 */ null,
- // U+00FE: "þ" LATIN SMALL LETTER THORN
- /* 11 */ "\u00FE",
- /* 12~ */
- null, null, null, null, null, null, null, null,
- /* ~19 */
- // U+00F0: "ð" LATIN SMALL LETTER ETH
- /* 20 */ "\u00F0",
- // U+00E6: "æ" LATIN SMALL LETTER AE
- /* 21 */ "\u00E6",
- // U+00FE: "þ" LATIN SMALL LETTER THORN
- /* 22 */ "\u00FE",
- /* 23~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- };
-
- /* Language it: Italian */
- private static final String[] LANGUAGE_it = {
- // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- // U+00AA: "ª" FEMININE ORDINAL INDICATOR
- /* 0 */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101,\u00AA",
- // 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+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* 1 */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0119,\u0117,\u0113",
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // 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+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- /* 2 */ "\u00EC,\u00ED,\u00EE,\u00EF,\u012F,\u012B",
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- // U+00BA: "º" MASCULINE ORDINAL INDICATOR
- /* 3 */ "\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
- /* 4 */ "\u00F9,\u00FA,\u00FB,\u00FC,\u016B",
- };
-
- /* Language iw: Hebrew */
- private static final String[] LANGUAGE_iw = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+05D0: "א" HEBREW LETTER ALEF
- // U+05D1: "ב" HEBREW LETTER BET
- // U+05D2: "ג" HEBREW LETTER GIMEL
- /* 45 */ "\u05D0\u05D1\u05D2",
- // The following characters don't need BIDI mirroring.
- // U+2018: "‘" LEFT SINGLE QUOTATION MARK
- // U+2019: "’" RIGHT SINGLE QUOTATION MARK
- // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
- // U+201C: "“" LEFT DOUBLE QUOTATION MARK
- // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
- // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
- /* 46 */ "\u2018,\u2019,\u201A",
- /* 47 */ "\u201C,\u201D,\u201E",
- /* 48 */ "!text/single_laqm_raqm_rtl",
- /* 49 */ "!text/double_laqm_raqm_rtl",
- /* 50 */ null,
- // U+20AA: "₪" NEW SHEQEL SIGN
- /* 51 */ "\u20AA",
- /* 52 */ null,
- /* 53 */ "!fixedColumnOrder!8,;,/,(|),)|(,#,!,\\,,?,&,\\%,+,\",-,:,',@",
- // U+2605: "★" BLACK STAR
- /* 54 */ "\u2605",
- /* 55 */ null,
- // U+00B1: "±" PLUS-MINUS SIGN
- // U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
- /* 56 */ "\u00B1,\uFB29",
- // The all letters need to be mirrored are found at
- // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
- /* 57 */ "!fixedColumnOrder!3,<|>,{|},[|]",
- /* 58 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
- // U+2264: "≤" LESS-THAN OR EQUAL TO
- // U+2265: "≥" GREATER-THAN EQUAL TO
- // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
- // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
- // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
- /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
- /* 61~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~104 */
- /* 105 */ "!",
- /* 106 */ "!",
- /* 107 */ "?",
- /* 108 */ "?",
- };
-
- /* Language ka: Georgian */
- private static final String[] LANGUAGE_ka = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+10D0: "ა" GEORGIAN LETTER AN
- // U+10D1: "ბ" GEORGIAN LETTER BAN
- // U+10D2: "გ" GEORGIAN LETTER GAN
- /* 45 */ "\u10D0\u10D1\u10D2",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- };
-
- /* Language kk: Kazakh */
- private static final String[] LANGUAGE_kk = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null,
- /* ~24 */
- // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
- /* 25 */ "\u0449",
- // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 26 */ "\u044A",
- // U+044B: "ы" CYRILLIC SMALL LETTER YERU
- /* 27 */ "\u044B",
- // U+044D: "э" CYRILLIC SMALL LETTER E
- /* 28 */ "\u044D",
- // U+0438: "и" CYRILLIC SMALL LETTER I
- /* 29 */ "\u0438",
- // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
- // U+04B1: "ұ" CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE
- /* 30 */ "\u04AF,\u04B1",
- // U+049B: "қ" CYRILLIC SMALL LETTER KA WITH DESCENDER
- /* 31 */ "\u049B",
- // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER
- /* 32 */ "\u04A3",
- // U+0493: "ғ" CYRILLIC SMALL LETTER GHE WITH STROKE
- /* 33 */ "\u0493",
- // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
- /* 34 */ "\u0456",
- // U+04D9: "ә" CYRILLIC SMALL LETTER SCHWA
- /* 35 */ "\u04D9",
- // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
- /* 36 */ "\u04E9",
- // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 37 */ "\u044A",
- // U+04BB: "һ" CYRILLIC SMALL LETTER SHHA
- /* 38 */ "\u04BB",
- /* 39~ */
- null, null, null, null,
- /* ~42 */
- // U+0451: "ё" CYRILLIC SMALL LETTER IO
- /* 43 */ "\u0451",
- /* 44 */ null,
- // Label for "switch to alphabetic" key.
- // U+0410: "А" CYRILLIC CAPITAL LETTER A
- // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
- // U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- };
-
- /* Language km: Khmer */
- private static final String[] LANGUAGE_km = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+1780: "ក" KHMER LETTER KA
- // U+1781: "ខ" KHMER LETTER KHA
- // U+1782: "គ" KHMER LETTER KO
- /* 45 */ "\u1780\u1781\u1782",
- /* 46~ */
- null, null, null, null,
- /* ~49 */
- // U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL
- /* 50 */ "\u17DB,\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
- };
-
- /* Language ky: Kirghiz */
- private static final String[] LANGUAGE_ky = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null,
- /* ~24 */
- // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
- /* 25 */ "\u0449",
- // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 26 */ "\u044A",
- // U+044B: "ы" CYRILLIC SMALL LETTER YERU
- /* 27 */ "\u044B",
- // U+044D: "э" CYRILLIC SMALL LETTER E
- /* 28 */ "\u044D",
- // U+0438: "и" CYRILLIC SMALL LETTER I
- /* 29 */ "\u0438",
- // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
- /* 30 */ "\u04AF",
- /* 31 */ null,
- // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER
- /* 32 */ "\u04A3",
- /* 33~ */
- null, null, null,
- /* ~35 */
- // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
- /* 36 */ "\u04E9",
- // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 37 */ "\u044A",
- /* 38~ */
- null, null, null, null, null,
- /* ~42 */
- // U+0451: "ё" CYRILLIC SMALL LETTER IO
- /* 43 */ "\u0451",
- /* 44 */ null,
- // Label for "switch to alphabetic" key.
- // U+0410: "А" CYRILLIC CAPITAL LETTER A
- // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
- // U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- };
-
- /* Language lo: Lao */
- private static final String[] LANGUAGE_lo = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+0E81: "ກ" LAO LETTER KO
- // U+0E82: "ຂ" LAO LETTER KHO SUNG
- // U+0E84: "ຄ" LAO LETTER KHO TAM
- /* 45 */ "\u0E81\u0E82\u0E84",
- /* 46~ */
- null, null, null, null, null,
- /* ~50 */
- // U+20AD: "₭" KIP SIGN
- /* 51 */ "\u20AD",
- };
-
- /* Language lt: Lithuanian */
- private static final String[] LANGUAGE_lt = {
- // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- // 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+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+00E6: "æ" LATIN SMALL LETTER AE
- /* 0 */ "\u0105,\u00E4,\u0101,\u00E0,\u00E1,\u00E2,\u00E3,\u00E5,\u00E6",
- // 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
- /* 1 */ "\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
- // 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+0131: "ı" LATIN SMALL LETTER DOTLESS I
- /* 2 */ "\u012F,\u012B,\u00EC,\u00ED,\u00EE,\u00EF,\u0131",
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- /* 3 */ "\u00F6,\u00F5,\u00F2,\u00F3,\u00F4,\u0153,\u0151,\u00F8",
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- // 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+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
- // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
- /* 4 */ "\u016B,\u0173,\u00FC,\u016B,\u00F9,\u00FA,\u00FB,\u016F,\u0171",
- // 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
- /* 5 */ "\u0161,\u00DF,\u015B,\u015F",
- // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\u0146,\u00F1,\u0144,\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
- /* 7 */ "\u010D,\u00E7,\u0107",
- // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
- // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
- /* 8 */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* 9 */ "\u010F",
- // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
- // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
- // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
- /* 10 */ "\u0157,\u0159,\u0155",
- // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
- // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
- /* 11 */ "\u0163,\u0165",
- // 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
- /* 12 */ "\u017E,\u017C,\u017A",
- // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
- /* 13 */ "\u0137",
- // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
- // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
- // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
- // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
- /* 14 */ "\u013C,\u0142,\u013A,\u013E",
- // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
- // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
- /* 15 */ "\u0123,\u011F",
- /* 16~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- };
-
- /* Language lv: Latvian */
- private static final String[] LANGUAGE_lv = {
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
- /* 0 */ "\u0101,\u00E0,\u00E1,\u00E2,\u00E3,\u00E4,\u00E5,\u00E6,\u0105",
- // 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
- /* 1 */ "\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
- // 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+0131: "ı" LATIN SMALL LETTER DOTLESS I
- /* 2 */ "\u012B,\u012F,\u00EC,\u00ED,\u00EE,\u00EF,\u0131",
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- /* 3 */ "\u00F2,\u00F3,\u00F4,\u00F5,\u00F6,\u0153,\u0151,\u00F8",
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
- // 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+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
- // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
- /* 4 */ "\u016B,\u0173,\u00F9,\u00FA,\u00FB,\u00FC,\u016F,\u0171",
- // 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
- /* 5 */ "\u0161,\u00DF,\u015B,\u015F",
- // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\u0146,\u00F1,\u0144,\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
- /* 7 */ "\u010D,\u00E7,\u0107",
- // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
- // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
- /* 8 */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* 9 */ "\u010F",
- // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
- // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
- // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
- /* 10 */ "\u0157,\u0159,\u0155",
- // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
- // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
- /* 11 */ "\u0163,\u0165",
- // 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
- /* 12 */ "\u017E,\u017C,\u017A",
- // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
- /* 13 */ "\u0137",
- // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
- // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
- // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
- // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
- /* 14 */ "\u013C,\u0142,\u013A,\u013E",
- // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
- // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
- /* 15 */ "\u0123,\u011F",
- /* 16~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- };
-
- /* Language mk: Macedonian */
- private static final String[] LANGUAGE_mk = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null,
- /* ~38 */
- // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE
- /* 39 */ "\u0455",
- // U+045C: "ќ" CYRILLIC SMALL LETTER KJE
- /* 40 */ "\u045C",
- // U+0437: "з" CYRILLIC SMALL LETTER ZE
- /* 41 */ "\u0437",
- // U+0453: "ѓ" CYRILLIC SMALL LETTER GJE
- /* 42 */ "\u0453",
- // U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE
- /* 43 */ "\u0450",
- // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
- /* 44 */ "\u045D",
- // Label for "switch to alphabetic" key.
- // U+0410: "А" CYRILLIC CAPITAL LETTER A
- // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
- // U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- };
-
- /* Language mn: Mongolian */
- private static final String[] LANGUAGE_mn = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+0410: "А" CYRILLIC CAPITAL LETTER A
- // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
- // U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46~ */
- null, null, null, null, null,
- /* ~50 */
- // U+20AE: "₮" TUGRIK SIGN
- /* 51 */ "\u20AE",
- };
-
- /* Language nb: Norwegian Bokmål */
- private static final String[] LANGUAGE_nb = {
- // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // 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
- /* 0 */ "\u00E0,\u00E4,\u00E1,\u00E2,\u00E3,\u0101",
- // 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
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* 1 */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
- /* 2 */ null,
- // 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
- /* 3 */ "\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
- /* 4 */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
- /* 5~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~19 */
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- /* 20 */ "\u00E5",
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- /* 21 */ "\u00F8",
- // U+00E6: "æ" LATIN SMALL LETTER AE
- /* 22 */ "\u00E6",
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- /* 23 */ "\u00F6",
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- /* 24 */ "\u00E4",
- /* 25~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
- };
-
- /* Language ne: Nepali */
- private static final String[] LANGUAGE_ne = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+0915: "क" DEVANAGARI LETTER KA
- // U+0916: "ख" DEVANAGARI LETTER KHA
- // U+0917: "ग" DEVANAGARI LETTER GA
- /* 45 */ "\u0915\u0916\u0917",
- /* 46~ */
- null, null, null, null, null,
- /* ~50 */
- // U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
- /* 51 */ "\u0930\u0941.",
- /* 52~ */
- null, null, null, null, null, null, null, null, null, null, null,
- /* ~62 */
- // U+0967: "१" DEVANAGARI DIGIT ONE
- /* 63 */ "\u0967",
- // U+0968: "२" DEVANAGARI DIGIT TWO
- /* 64 */ "\u0968",
- // U+0969: "३" DEVANAGARI DIGIT THREE
- /* 65 */ "\u0969",
- // U+096A: "४" DEVANAGARI DIGIT FOUR
- /* 66 */ "\u096A",
- // U+096B: "५" DEVANAGARI DIGIT FIVE
- /* 67 */ "\u096B",
- // U+096C: "६" DEVANAGARI DIGIT SIX
- /* 68 */ "\u096C",
- // U+096D: "७" DEVANAGARI DIGIT SEVEN
- /* 69 */ "\u096D",
- // U+096E: "८" DEVANAGARI DIGIT EIGHT
- /* 70 */ "\u096E",
- // U+096F: "९" DEVANAGARI DIGIT NINE
- /* 71 */ "\u096F",
- // U+0966: "०" DEVANAGARI DIGIT ZERO
- /* 72 */ "\u0966",
- // Label for "switch to symbols" key.
- /* 73 */ "?\u0967\u0968\u0969",
- // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
- // part because it'll be appended by the code.
- /* 74 */ "\u0967\u0968\u0969",
- /* 75 */ "1",
- /* 76 */ "2",
- /* 77 */ "3",
- /* 78 */ "4",
- /* 79 */ "5",
- /* 80 */ "6",
- /* 81 */ "7",
- /* 82 */ "8",
- /* 83 */ "9",
- /* 84 */ "0",
- };
-
- /* Language nl: Dutch */
- private static final String[] LANGUAGE_nl = {
- // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* 0 */ "\u00E1,\u00E4,\u00E2,\u00E0,\u00E6,\u00E3,\u00E5,\u0101",
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // 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
- /* 1 */ "\u00E9,\u00EB,\u00EA,\u00E8,\u0119,\u0117,\u0113",
- // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
- // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- // U+0133: "ij" LATIN SMALL LIGATURE IJ
- /* 2 */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B,\u0133",
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* 3 */ "\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
- /* 4 */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
- /* 5 */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\u00F1,\u0144",
- /* 7 */ null,
- // U+0133: "ij" LATIN SMALL LIGATURE IJ
- /* 8 */ "\u0133",
- /* 9~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
- };
-
- /* Language pl: Polish */
- private static final String[] LANGUAGE_pl = {
- // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
- // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* 0 */ "\u0105,\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- // 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+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* 1 */ "\u0119,\u00E8,\u00E9,\u00EA,\u00EB,\u0117,\u0113",
- /* 2 */ null,
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* 3 */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- /* 4 */ null,
- // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
- // U+00DF: "ß" LATIN SMALL LETTER SHARP S
- // U+0161: "š" LATIN SMALL LETTER S WITH CARON
- /* 5 */ "\u015B,\u00DF,\u0161",
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* 6 */ "\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
- /* 7 */ "\u0107,\u00E7,\u010D",
- /* 8~ */
- null, null, null, null,
- /* ~11 */
- // 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
- /* 12 */ "\u017C,\u017A,\u017E",
- /* 13 */ null,
- // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
- /* 14 */ "\u0142",
- /* 15~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
- };
-
- /* Language pt: Portuguese */
- private static final String[] LANGUAGE_pt = {
- // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
- // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00AA: "ª" FEMININE ORDINAL INDICATOR
- /* 0 */ "\u00E1,\u00E3,\u00E0,\u00E2,\u00E4,\u00E5,\u00E6,\u00AA",
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // 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
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- /* 1 */ "\u00E9,\u00EA,\u00E8,\u0119,\u0117,\u0113,\u00EB",
- // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
- // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- /* 2 */ "\u00ED,\u00EE,\u00EC,\u00EF,\u012F,\u012B",
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- // U+00BA: "º" MASCULINE ORDINAL INDICATOR
- /* 3 */ "\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
- /* 4 */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* 5 */ null,
- /* 6 */ 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
- /* 7 */ "\u00E7,\u010D,\u0107",
- };
-
- /* Language rm: Raeto-Romance */
- private static final String[] LANGUAGE_rm = {
- /* 0~ */
- null, null, null,
- /* ~2 */
- // 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+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- /* 3 */ "\u00F2,\u00F3,\u00F6,\u00F4,\u00F5,\u0153,\u00F8",
- };
-
- /* Language ro: Romanian */
- private static final String[] LANGUAGE_ro = {
- // 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
- /* 0 */ "\u00E2,\u00E3,\u0103,\u00E0,\u00E1,\u00E4,\u00E6,\u00E5,\u0101",
- /* 1 */ 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
- // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- /* 2 */ "\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
- /* 3 */ null,
- /* 4 */ null,
- // 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
- /* 5 */ "\u0219,\u00DF,\u015B,\u0161",
- /* 6~ */
- null, null, null, null, null,
- /* ~10 */
- // U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW
- /* 11 */ "\u021B",
- /* 12~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
- };
-
- /* Language ru: Russian */
- private static final String[] LANGUAGE_ru = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null,
- /* ~24 */
- // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
- /* 25 */ "\u0449",
- // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 26 */ "\u044A",
- // U+044B: "ы" CYRILLIC SMALL LETTER YERU
- /* 27 */ "\u044B",
- // U+044D: "э" CYRILLIC SMALL LETTER E
- /* 28 */ "\u044D",
- // U+0438: "и" CYRILLIC SMALL LETTER I
- /* 29 */ "\u0438",
- /* 30~ */
- null, null, null, null, null, null, null,
- /* ~36 */
- // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 37 */ "\u044A",
- /* 38~ */
- null, null, null, null, null,
- /* ~42 */
- // U+0451: "ё" CYRILLIC SMALL LETTER IO
- /* 43 */ "\u0451",
- /* 44 */ null,
- // Label for "switch to alphabetic" key.
- // U+0410: "А" CYRILLIC CAPITAL LETTER A
- // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
- // U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- };
-
- /* Language sk: Slovak */
- private static final String[] LANGUAGE_sk = {
- // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- // 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+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
- /* 0 */ "\u00E1,\u00E4,\u0101,\u00E0,\u00E2,\u00E3,\u00E5,\u00E6,\u0105",
- // 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
- /* 1 */ "\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
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
- // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
- // U+0131: "ı" LATIN SMALL LETTER DOTLESS I
- /* 2 */ "\u00ED,\u012B,\u012F,\u00EC,\u00EE,\u00EF,\u0131",
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- /* 3 */ "\u00F4,\u00F3,\u00F6,\u00F2,\u00F5,\u0153,\u0151,\u00F8",
- // 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
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
- /* 4 */ "\u00FA,\u016F,\u00FC,\u016B,\u0173,\u00F9,\u00FB,\u0171",
- // 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
- /* 5 */ "\u0161,\u00DF,\u015B,\u015F",
- // 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
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\u0148,\u0146,\u00F1,\u0144,\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
- /* 7 */ "\u010D,\u00E7,\u0107",
- // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
- // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
- /* 8 */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* 9 */ "\u010F",
- // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
- // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
- // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
- /* 10 */ "\u0155,\u0159,\u0157",
- // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
- // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
- /* 11 */ "\u0165,\u0163",
- // 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
- /* 12 */ "\u017E,\u017C,\u017A",
- // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
- /* 13 */ "\u0137",
- // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
- // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
- // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
- // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
- /* 14 */ "\u013E,\u013A,\u013C,\u0142",
- // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
- // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
- /* 15 */ "\u0123,\u011F",
- /* 16~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
- };
-
- /* Language sl: Slovenian */
- private static final String[] LANGUAGE_sl = {
- /* 0~ */
- null, null, null, null, null,
- /* ~4 */
- // U+0161: "š" LATIN SMALL LETTER S WITH CARON
- /* 5 */ "\u0161",
- /* 6 */ null,
- // U+010D: "č" LATIN SMALL LETTER C WITH CARON
- // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
- /* 7 */ "\u010D,\u0107",
- /* 8 */ null,
- // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
- /* 9 */ "\u0111",
- /* 10 */ null,
- /* 11 */ null,
- // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
- /* 12 */ "\u017E",
- /* 13~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
- };
-
- /* Language sr: Serbian */
- private static final String[] LANGUAGE_sr = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null,
- /* ~38 */
- // TODO: Move these to sr-Latn once we can handle IETF language tag with script name specified.
- // BEGIN: More keys definitions for Serbian (Latin)
- // U+0161: "š" LATIN SMALL LETTER S WITH CARON
- // U+00DF: "ß" LATIN SMALL LETTER SHARP S
- // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
- // <string name="more_keys_for_s">&#x0161;,&#x00DF;,&#x015B;</string>
- // U+010D: "č" LATIN SMALL LETTER C WITH CARON
- // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
- // <string name="more_keys_for_c">&#x010D;,&#x00E7;,&#x0107;</string>
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- // <string name="more_keys_for_d">&#x010F;</string>
- // 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
- // <string name="more_keys_for_z">&#x017E;,&#x017A;,&#x017C;</string>
- // END: More keys definitions for Serbian (Latin)
- // BEGIN: More keys definitions for Serbian (Cyrillic)
- // U+0437: "з" CYRILLIC SMALL LETTER ZE
- /* 39 */ "\u0437",
- // U+045B: "ћ" CYRILLIC SMALL LETTER TSHE
- /* 40 */ "\u045B",
- // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE
- /* 41 */ "\u0455",
- // U+0452: "ђ" CYRILLIC SMALL LETTER DJE
- /* 42 */ "\u0452",
- // U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE
- /* 43 */ "\u0450",
- // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
- /* 44 */ "\u045D",
- // END: More keys definitions for Serbian (Cyrillic)
- // Label for "switch to alphabetic" key.
- // U+0410: "А" CYRILLIC CAPITAL LETTER A
- // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
- // U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
- };
-
- /* Language sv: Swedish */
- private static final String[] LANGUAGE_sv = {
- // 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
- /* 0 */ "\u00E1,\u00E0,\u00E2,\u0105,\u00E3",
- // 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
- /* 1 */ "\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
- /* 2 */ "\u00ED,\u00EC,\u00EE,\u00EF",
- // 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
- /* 3 */ "\u00F3,\u00F2,\u00F4,\u00F5,\u014D",
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* 4 */ "\u00FC,\u00FA,\u00F9,\u00FB,\u016B",
- // 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
- /* 5 */ "\u015B,\u0161,\u015F,\u00DF",
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
- /* 6 */ "\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
- /* 7 */ "\u00E7,\u0107,\u010D",
- // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
- // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- /* 8 */ "\u00FD,\u00FF,\u00FC",
- // U+00F0: "ð" LATIN SMALL LETTER ETH
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* 9 */ "\u00F0,\u010F",
- // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
- /* 10 */ "\u0159",
- // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
- // U+00FE: "þ" LATIN SMALL LETTER THORN
- /* 11 */ "\u0165,\u00FE",
- // 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
- /* 12 */ "\u017A,\u017E,\u017C",
- /* 13 */ null,
- // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
- /* 14 */ "\u0142",
- /* 15~ */
- null, null, null, null, null,
- /* ~19 */
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- /* 20 */ "\u00E5",
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- /* 21 */ "\u00F6",
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- /* 22 */ "\u00E4",
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- /* 23 */ "\u00F8,\u0153",
- // U+00E6: "æ" LATIN SMALL LETTER AE
- /* 24 */ "\u00E6",
- /* 25~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
- /* ~47 */
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
- };
-
- /* Language sw: Swahili */
- private static final String[] LANGUAGE_sw = {
- // This is the same as English except more_keys_for_g.
- // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* 0 */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
- // 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
- /* 1 */ "\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
- /* 2 */ "\u00EE,\u00EF,\u00ED,\u012B,\u00EC",
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- /* 3 */ "\u00F4,\u00F6,\u00F2,\u00F3,\u0153,\u00F8,\u014D,\u00F5",
- // 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+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* 4 */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B",
- // U+00DF: "ß" LATIN SMALL LETTER SHARP S
- /* 5 */ "\u00DF",
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* 6 */ "\u00F1",
- // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- /* 7 */ "\u00E7",
- /* 8~ */
- null, null, null, null, null, null, null,
- /* ~14 */
- /* 15 */ "g\'",
- };
-
- /* Language th: Thai */
- private static final String[] LANGUAGE_th = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+0E01: "ก" THAI CHARACTER KO KAI
- // U+0E02: "ข" THAI CHARACTER KHO KHAI
- // U+0E04: "ค" THAI CHARACTER KHO KHWAI
- /* 45 */ "\u0E01\u0E02\u0E04",
- /* 46~ */
- null, null, null, null, null,
- /* ~50 */
- // U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
- /* 51 */ "\u0E3F",
- };
-
- /* Language tl: Tagalog */
- private static final String[] LANGUAGE_tl = {
- // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
- // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
- // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- // U+00AA: "ª" FEMININE ORDINAL INDICATOR
- /* 0 */ "\u00E1,\u00E0,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA",
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // 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
- /* 1 */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
- // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
- // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
- // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
- // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- /* 2 */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- // U+00BA: "º" MASCULINE ORDINAL INDICATOR
- /* 3 */ "\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
- /* 4 */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* 5 */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* 6 */ "\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
- /* 7 */ "\u00E7,\u0107,\u010D",
- };
-
- /* Language tr: Turkish */
- private static final String[] LANGUAGE_tr = {
- // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- /* 0 */ "\u00E2",
- /* 1 */ 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
- /* 2 */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
- // 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
- /* 3 */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\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
- /* 4 */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
- // 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
- /* 5 */ "\u015F,\u00DF,\u015B,\u0161",
- /* 6 */ 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
- /* 7 */ "\u00E7,\u0107,\u010D",
- /* 8~ */
- null, null, null, null, null, null, null,
- /* ~14 */
- // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
- /* 15 */ "\u011F",
- };
-
- /* Language uk: Ukrainian */
- private static final String[] LANGUAGE_uk = {
- /* 0~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null,
- /* ~24 */
- // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
- /* 25 */ "\u0449",
- // U+0457: "ї" CYRILLIC SMALL LETTER YI
- /* 26 */ "\u0457",
- // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
- /* 27 */ "\u0456",
- // U+0454: "є" CYRILLIC SMALL LETTER UKRAINIAN IE
- /* 28 */ "\u0454",
- // U+0438: "и" CYRILLIC SMALL LETTER I
- /* 29 */ "\u0438",
- /* 30~ */
- null, null, null,
- /* ~32 */
- // U+0491: "ґ" CYRILLIC SMALL LETTER GHE WITH UPTURN
- /* 33 */ "\u0491",
- // U+0457: "ї" CYRILLIC SMALL LETTER YI
- /* 34 */ "\u0457",
- /* 35 */ null,
- /* 36 */ null,
- // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 37 */ "\u044A",
- /* 38~ */
- null, null, null, null, null, null, null,
- /* ~44 */
- // Label for "switch to alphabetic" key.
- // U+0410: "А" CYRILLIC CAPITAL LETTER A
- // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
- // U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48~ */
- null, null, null,
- /* ~50 */
- // U+20B4: "₴" HRYVNIA SIGN
- /* 51 */ "\u20B4",
- };
-
- /* Language vi: Vietnamese */
- private static final String[] LANGUAGE_vi = {
- // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
- // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
- // U+1EA3: "ả" LATIN SMALL LETTER A WITH HOOK ABOVE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+1EA1: "ạ" LATIN SMALL LETTER A WITH DOT BELOW
- // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
- // U+1EB1: "ằ" LATIN SMALL LETTER A WITH BREVE AND GRAVE
- // U+1EAF: "ắ" LATIN SMALL LETTER A WITH BREVE AND ACUTE
- // U+1EB3: "ẳ" LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE
- // U+1EB5: "ẵ" LATIN SMALL LETTER A WITH BREVE AND TILDE
- // U+1EB7: "ặ" LATIN SMALL LETTER A WITH BREVE AND DOT BELOW
- // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- // U+1EA7: "ầ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE
- // U+1EA5: "ấ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
- // U+1EA9: "ẩ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
- // U+1EAB: "ẫ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE
- // U+1EAD: "ậ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW
- /* 0 */ "\u00E0,\u00E1,\u1EA3,\u00E3,\u1EA1,\u0103,\u1EB1,\u1EAF,\u1EB3,\u1EB5,\u1EB7,\u00E2,\u1EA7,\u1EA5,\u1EA9,\u1EAB,\u1EAD",
- // 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
- /* 1 */ "\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
- /* 2 */ "\u00EC,\u00ED,\u1EC9,\u0129,\u1ECB",
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+1ECF: "ỏ" LATIN SMALL LETTER O WITH HOOK ABOVE
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+1ECD: "ọ" LATIN SMALL LETTER O WITH DOT BELOW
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+1ED3: "ồ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE
- // U+1ED1: "ố" LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE
- // U+1ED5: "ổ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
- // U+1ED7: "ỗ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE
- // U+1ED9: "ộ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW
- // U+01A1: "ơ" LATIN SMALL LETTER O WITH HORN
- // U+1EDD: "ờ" LATIN SMALL LETTER O WITH HORN AND GRAVE
- // U+1EDB: "ớ" LATIN SMALL LETTER O WITH HORN AND ACUTE
- // U+1EDF: "ở" LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE
- // U+1EE1: "ỡ" LATIN SMALL LETTER O WITH HORN AND TILDE
- // U+1EE3: "ợ" LATIN SMALL LETTER O WITH HORN AND DOT BELOW
- /* 3 */ "\u00F2,\u00F3,\u1ECF,\u00F5,\u1ECD,\u00F4,\u1ED3,\u1ED1,\u1ED5,\u1ED7,\u1ED9,\u01A1,\u1EDD,\u1EDB,\u1EDF,\u1EE1,\u1EE3",
- // 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
- // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE
- // U+1EE5: "ụ" LATIN SMALL LETTER U WITH DOT BELOW
- // U+01B0: "ư" LATIN SMALL LETTER U WITH HORN
- // U+1EEB: "ừ" LATIN SMALL LETTER U WITH HORN AND GRAVE
- // U+1EE9: "ứ" LATIN SMALL LETTER U WITH HORN AND ACUTE
- // U+1EED: "ử" LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE
- // U+1EEF: "ữ" LATIN SMALL LETTER U WITH HORN AND TILDE
- // U+1EF1: "ự" LATIN SMALL LETTER U WITH HORN AND DOT BELOW
- /* 4 */ "\u00F9,\u00FA,\u1EE7,\u0169,\u1EE5,\u01B0,\u1EEB,\u1EE9,\u1EED,\u1EEF,\u1EF1",
- /* 5~ */
- null, null, null,
- /* ~7 */
- // U+1EF3: "ỳ" LATIN SMALL LETTER Y WITH GRAVE
- // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
- // U+1EF7: "ỷ" LATIN SMALL LETTER Y WITH HOOK ABOVE
- // U+1EF9: "ỹ" LATIN SMALL LETTER Y WITH TILDE
- // U+1EF5: "ỵ" LATIN SMALL LETTER Y WITH DOT BELOW
- /* 8 */ "\u1EF3,\u00FD,\u1EF7,\u1EF9,\u1EF5",
- // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
- /* 9 */ "\u0111",
- /* 10~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null,
- /* ~50 */
- // U+20AB: "₫" DONG SIGN
- /* 51 */ "\u20AB",
- };
-
- /* Language zu: Zulu */
- private static final String[] LANGUAGE_zu = {
- // This is the same as English
- // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* 0 */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
- // 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
- /* 1 */ "\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
- /* 2 */ "\u00EE,\u00EF,\u00ED,\u012B,\u00EC",
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- /* 3 */ "\u00F4,\u00F6,\u00F2,\u00F3,\u0153,\u00F8,\u014D,\u00F5",
- // 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+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* 4 */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B",
- // U+00DF: "ß" LATIN SMALL LETTER SHARP S
- /* 5 */ "\u00DF",
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* 6 */ "\u00F1",
- // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- /* 7 */ "\u00E7",
- };
-
- /* Language zz: Alphabet */
- private static final String[] LANGUAGE_zz = {
- // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
- // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
- // U+00E6: "æ" LATIN SMALL LETTER AE
- // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
- // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
- // U+00AA: "ª" FEMININE ORDINAL INDICATOR
- /* 0 */ "\u00E0,\u00E1,\u00E2,\u00E3,\u00E4,\u00E5,\u00E6,\u00E3,\u00E5,\u0101,\u0103,\u0105,\u00AA",
- // 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
- /* 1 */ "\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
- // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
- // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE
- // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
- // U+012D: "ĭ" LATIN SMALL LETTER I WITH BREVE
- // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
- // U+0131: "ı" LATIN SMALL LETTER DOTLESS I
- // U+0133: "ij" LATIN SMALL LIGATURE IJ
- /* 2 */ "\u00EC,\u00ED,\u00EE,\u00EF,\u0129,\u012B,\u012D,\u012F,\u0131,\u0133",
- // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
- // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
- // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
- // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
- // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- // U+014F: "ŏ" LATIN SMALL LETTER O WITH BREVE
- // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
- // U+0153: "œ" LATIN SMALL LIGATURE OE
- // U+00BA: "º" MASCULINE ORDINAL INDICATOR
- /* 3 */ "\u00F2,\u00F3,\u00F4,\u00F5,\u00F6,\u00F8,\u014D,\u014F,\u0151,\u0153,\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+0169: "ũ" LATIN SMALL LETTER U WITH TILDE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
- // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
- // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
- // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
- /* 4 */ "\u00F9,\u00FA,\u00FB,\u00FC,\u0169,\u016B,\u016D,\u016F,\u0171,\u0173",
- // U+00DF: "ß" LATIN SMALL LETTER SHARP S
- // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
- // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
- // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
- // U+0161: "š" LATIN SMALL LETTER S WITH CARON
- // U+017F: "ſ" LATIN SMALL LETTER LONG S
- /* 5 */ "\u00DF,\u015B,\u015D,\u015F,\u0161,\u017F",
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
- // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
- // U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
- // U+014B: "ŋ" LATIN SMALL LETTER ENG
- /* 6 */ "\u00F1,\u0144,\u0146,\u0148,\u0149,\u014B",
- // 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
- /* 7 */ "\u00E7,\u0107,\u0109,\u010B,\u010D",
- // 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
- /* 8 */ "\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
- /* 9 */ "\u010F,\u0111,\u00F0",
- // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
- // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
- // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
- /* 10 */ "\u0155,\u0157,\u0159",
- // U+00FE: "þ" LATIN SMALL LETTER THORN
- // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
- // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
- // U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE
- /* 11 */ "\u00FE,\u0163,\u0165,\u0167",
- // 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
- /* 12 */ "\u017A,\u017C,\u017E",
- // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
- // U+0138: "ĸ" LATIN SMALL LETTER KRA
- /* 13 */ "\u0137,\u0138",
- // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
- // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
- // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
- // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT
- // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
- /* 14 */ "\u013A,\u013C,\u013E,\u0140,\u0142",
- // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
- // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
- // U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE
- // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
- /* 15 */ "\u011D,\u011F,\u0121,\u0123",
- /* 16 */ null,
- // U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX
- /* 17 */ "\u0125",
- // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
- /* 18 */ "\u0135",
- // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
- /* 19 */ "\u0175",
- };
-
- private static final Object[] LANGUAGES_AND_TEXTS = {
- "DEFAULT", LANGUAGE_DEFAULT, /* default */
- "af", LANGUAGE_af, /* Afrikaans */
- "ar", LANGUAGE_ar, /* Arabic */
- "az", LANGUAGE_az, /* Azerbaijani */
- "be", LANGUAGE_be, /* Belarusian */
- "bg", LANGUAGE_bg, /* Bulgarian */
- "ca", LANGUAGE_ca, /* Catalan */
- "cs", LANGUAGE_cs, /* Czech */
- "da", LANGUAGE_da, /* Danish */
- "de", LANGUAGE_de, /* German */
- "el", LANGUAGE_el, /* Greek */
- "en", LANGUAGE_en, /* English */
- "eo", LANGUAGE_eo, /* Esperanto */
- "es", LANGUAGE_es, /* Spanish */
- "et", LANGUAGE_et, /* Estonian */
- "fa", LANGUAGE_fa, /* Persian */
- "fi", LANGUAGE_fi, /* Finnish */
- "fr", LANGUAGE_fr, /* French */
- "hi", LANGUAGE_hi, /* Hindi */
- "hr", LANGUAGE_hr, /* Croatian */
- "hu", LANGUAGE_hu, /* Hungarian */
- "hy", LANGUAGE_hy, /* Armenian */
- "is", LANGUAGE_is, /* Icelandic */
- "it", LANGUAGE_it, /* Italian */
- "iw", LANGUAGE_iw, /* Hebrew */
- "ka", LANGUAGE_ka, /* Georgian */
- "kk", LANGUAGE_kk, /* Kazakh */
- "km", LANGUAGE_km, /* Khmer */
- "ky", LANGUAGE_ky, /* Kirghiz */
- "lo", LANGUAGE_lo, /* Lao */
- "lt", LANGUAGE_lt, /* Lithuanian */
- "lv", LANGUAGE_lv, /* Latvian */
- "mk", LANGUAGE_mk, /* Macedonian */
- "mn", LANGUAGE_mn, /* Mongolian */
- "nb", LANGUAGE_nb, /* Norwegian Bokmål */
- "ne", LANGUAGE_ne, /* Nepali */
- "nl", LANGUAGE_nl, /* Dutch */
- "pl", LANGUAGE_pl, /* Polish */
- "pt", LANGUAGE_pt, /* Portuguese */
- "rm", LANGUAGE_rm, /* Raeto-Romance */
- "ro", LANGUAGE_ro, /* Romanian */
- "ru", LANGUAGE_ru, /* Russian */
- "sk", LANGUAGE_sk, /* Slovak */
- "sl", LANGUAGE_sl, /* Slovenian */
- "sr", LANGUAGE_sr, /* Serbian */
- "sv", LANGUAGE_sv, /* Swedish */
- "sw", LANGUAGE_sw, /* Swahili */
- "th", LANGUAGE_th, /* Thai */
- "tl", LANGUAGE_tl, /* Tagalog */
- "tr", LANGUAGE_tr, /* Turkish */
- "uk", LANGUAGE_uk, /* Ukrainian */
- "vi", LANGUAGE_vi, /* Vietnamese */
- "zu", LANGUAGE_zu, /* Zulu */
- "zz", LANGUAGE_zz, /* Alphabet */
- };
-
- static {
- int id = 0;
- for (final String name : NAMES) {
- sNameToIdsMap.put(name, id++);
- }
-
- for (int i = 0; i < LANGUAGES_AND_TEXTS.length; i += 2) {
- final String language = (String)LANGUAGES_AND_TEXTS[i];
- final String[] texts = (String[])LANGUAGES_AND_TEXTS[i + 1];
- sLocaleToTextsMap.put(language, texts);
- }
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
new file mode 100644
index 000000000..14fa76744
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
@@ -0,0 +1,3782 @@
+/*
+ * 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.keyboard.internal;
+
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+/**
+ * !!!!! DO NOT EDIT THIS FILE !!!!!
+ *
+ * This file is generated by tools/make-keyboard-text. The base template file is
+ * tools/make-keyboard-text/res/src/com/android/inputmethod/keyboard/internal/
+ * KeyboardTextsTable.tmpl
+ *
+ * This file must be updated when any text resources in keyboard layout files have been changed.
+ * These text resources are referred as "!text/<resource_name>" in keyboard XML definitions,
+ * and should be defined in
+ * tools/make-keyboard-text/res/values-<locale>/donottranslate-more-keys.xml
+ *
+ * To update this file, please run the following commands.
+ * $ cd $ANDROID_BUILD_TOP
+ * $ mmm packages/inputmethods/LatinIME/tools/make-keyboard-text
+ * $ make-keyboard-text -java packages/inputmethods/LatinIME/java
+ *
+ * The updated source file will be generated to the following path (this file).
+ * packages/inputmethods/LatinIME/java/src/com/android/inputmethod/keyboard/internal/
+ * KeyboardTextsTable.java
+ */
+public final class KeyboardTextsTable {
+ // Name to index map.
+ private static final HashMap<String, Integer> sNameToIndexesMap = CollectionUtils.newHashMap();
+ // Locale to texts table map.
+ private static final HashMap<String, String[]> sLocaleToTextsTableMap =
+ CollectionUtils.newHashMap();
+ // TODO: Remove this variable after debugging.
+ // Texts table to locale maps.
+ private static final HashMap<String[], String> sTextsTableToLocaleMap =
+ CollectionUtils.newHashMap();
+
+ public static String getText(final String name, final String[] textsTable) {
+ final Integer indexObj = sNameToIndexesMap.get(name);
+ if (indexObj == null) {
+ throw new RuntimeException("Unknown text name=" + name + " locale="
+ + sTextsTableToLocaleMap.get(textsTable));
+ }
+ final int index = indexObj;
+ final String text = (index < textsTable.length) ? textsTable[index] : null;
+ if (text != null) {
+ return text;
+ }
+ // Sanity check.
+ if (index >= 0 && index < TEXTS_DEFAULT.length) {
+ return TEXTS_DEFAULT[index];
+ }
+ // Throw exception for debugging purpose.
+ throw new RuntimeException("Illegal index=" + index + " for name=" + name
+ + " locale=" + sTextsTableToLocaleMap.get(textsTable));
+ }
+
+ public static String[] getTextsTable(final Locale locale) {
+ final String localeKey = locale.toString();
+ if (sLocaleToTextsTableMap.containsKey(localeKey)) {
+ return sLocaleToTextsTableMap.get(localeKey);
+ }
+ final String languageKey = locale.getLanguage();
+ if (sLocaleToTextsTableMap.containsKey(languageKey)) {
+ return sLocaleToTextsTableMap.get(languageKey);
+ }
+ return TEXTS_DEFAULT;
+ }
+
+ private static final String[] NAMES = {
+ // /* index:histogram */ "name",
+ /* 0:32 */ "morekeys_a",
+ /* 1:32 */ "morekeys_o",
+ /* 2:30 */ "morekeys_u",
+ /* 3:29 */ "morekeys_e",
+ /* 4:28 */ "morekeys_i",
+ /* 5:23 */ "morekeys_c",
+ /* 6:23 */ "double_quotes",
+ /* 7:22 */ "morekeys_n",
+ /* 8:22 */ "single_quotes",
+ /* 9:21 */ "keylabel_to_alpha",
+ /* 10:20 */ "morekeys_s",
+ /* 11:14 */ "morekeys_y",
+ /* 12:13 */ "morekeys_d",
+ /* 13:12 */ "morekeys_z",
+ /* 14:10 */ "morekeys_t",
+ /* 15:10 */ "morekeys_l",
+ /* 16: 9 */ "morekeys_g",
+ /* 17: 9 */ "single_angle_quotes",
+ /* 18: 9 */ "double_angle_quotes",
+ /* 19: 9 */ "keyspec_currency",
+ /* 20: 8 */ "morekeys_r",
+ /* 21: 6 */ "morekeys_k",
+ /* 22: 6 */ "morekeys_cyrillic_ie",
+ /* 23: 5 */ "keyspec_nordic_row1_11",
+ /* 24: 5 */ "keyspec_nordic_row2_10",
+ /* 25: 5 */ "keyspec_nordic_row2_11",
+ /* 26: 5 */ "morekeys_nordic_row2_10",
+ /* 27: 5 */ "keyspec_east_slavic_row1_9",
+ /* 28: 5 */ "keyspec_east_slavic_row2_2",
+ /* 29: 5 */ "keyspec_east_slavic_row2_11",
+ /* 30: 5 */ "keyspec_east_slavic_row3_5",
+ /* 31: 5 */ "morekeys_cyrillic_soft_sign",
+ /* 32: 4 */ "morekeys_nordic_row2_11",
+ /* 33: 4 */ "morekeys_punctuation",
+ /* 34: 4 */ "keyspec_symbols_1",
+ /* 35: 4 */ "keyspec_symbols_2",
+ /* 36: 4 */ "keyspec_symbols_3",
+ /* 37: 4 */ "keyspec_symbols_4",
+ /* 38: 4 */ "keyspec_symbols_5",
+ /* 39: 4 */ "keyspec_symbols_6",
+ /* 40: 4 */ "keyspec_symbols_7",
+ /* 41: 4 */ "keyspec_symbols_8",
+ /* 42: 4 */ "keyspec_symbols_9",
+ /* 43: 4 */ "keyspec_symbols_0",
+ /* 44: 4 */ "keylabel_to_symbol",
+ /* 45: 4 */ "additional_morekeys_symbols_1",
+ /* 46: 4 */ "additional_morekeys_symbols_2",
+ /* 47: 4 */ "additional_morekeys_symbols_3",
+ /* 48: 4 */ "additional_morekeys_symbols_4",
+ /* 49: 4 */ "additional_morekeys_symbols_5",
+ /* 50: 4 */ "additional_morekeys_symbols_6",
+ /* 51: 4 */ "additional_morekeys_symbols_7",
+ /* 52: 4 */ "additional_morekeys_symbols_8",
+ /* 53: 4 */ "additional_morekeys_symbols_9",
+ /* 54: 4 */ "additional_morekeys_symbols_0",
+ /* 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 */ "morekeys_tablet_comma",
+ /* 78: 3 */ "keyhintlabel_period",
+ /* 79: 3 */ "morekeys_tablet_period",
+ /* 80: 3 */ "morekeys_question",
+ /* 81: 2 */ "morekeys_h",
+ /* 82: 2 */ "morekeys_w",
+ /* 83: 2 */ "morekeys_east_slavic_row2_2",
+ /* 84: 2 */ "morekeys_cyrillic_u",
+ /* 85: 2 */ "morekeys_cyrillic_en",
+ /* 86: 2 */ "morekeys_cyrillic_ghe",
+ /* 87: 2 */ "morekeys_cyrillic_o",
+ /* 88: 2 */ "morekeys_cyrillic_i",
+ /* 89: 2 */ "keyspec_south_slavic_row1_6",
+ /* 90: 2 */ "keyspec_south_slavic_row2_11",
+ /* 91: 2 */ "keyspec_south_slavic_row3_1",
+ /* 92: 2 */ "keyspec_south_slavic_row3_8",
+ /* 93: 2 */ "morekeys_tablet_punctuation",
+ /* 94: 2 */ "keyspec_spanish_row2_10",
+ /* 95: 2 */ "morekeys_bullet",
+ /* 96: 2 */ "morekeys_left_parenthesis",
+ /* 97: 2 */ "morekeys_right_parenthesis",
+ /* 98: 2 */ "morekeys_arabic_diacritics",
+ /* 99: 2 */ "keyspec_comma",
+ /* 100: 2 */ "keyhintlabel_tablet_comma",
+ /* 101: 2 */ "keyspec_period",
+ /* 102: 2 */ "morekeys_period",
+ /* 103: 2 */ "keyspec_tablet_period",
+ /* 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",
+ /* 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_key",
+ };
+
+ private static final String EMPTY = "";
+
+ /* Default texts */
+ private static final String[] TEXTS_DEFAULT = {
+ /* morekeys_a ~ */
+ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+ /* ~ morekeys_c */
+ /* double_quotes */ "!text/double_lqm_rqm",
+ /* morekeys_n */ EMPTY,
+ /* single_quotes */ "!text/single_lqm_rqm",
+ // Label for "switch to alphabetic" key.
+ /* keylabel_to_alpha */ "ABC",
+ /* morekeys_s ~ */
+ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+ /* ~ morekeys_g */
+ /* single_angle_quotes */ "!text/single_laqm_raqm",
+ /* double_angle_quotes */ "!text/double_laqm_raqm",
+ /* keyspec_currency */ "$",
+ /* morekeys_r ~ */
+ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+ /* ~ morekeys_nordic_row2_11 */
+ /* morekeys_punctuation */ "!autoColumnOrder!8,\\,,?,!,#,!text/keyspec_right_parenthesis,!text/keyspec_left_parenthesis,/,;,',@,:,-,\",+,\\%,&",
+ /* keyspec_symbols_1 */ "1",
+ /* keyspec_symbols_2 */ "2",
+ /* keyspec_symbols_3 */ "3",
+ /* keyspec_symbols_4 */ "4",
+ /* keyspec_symbols_5 */ "5",
+ /* keyspec_symbols_6 */ "6",
+ /* keyspec_symbols_7 */ "7",
+ /* keyspec_symbols_8 */ "8",
+ /* keyspec_symbols_9 */ "9",
+ /* keyspec_symbols_0 */ "0",
+ // Label for "switch to symbols" key.
+ /* keylabel_to_symbol */ "?123",
+ /* additional_morekeys_symbols_1 ~ */
+ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+ /* ~ additional_morekeys_symbols_0 */
+ /* keyspec_tablet_comma */ ",",
+ /* keyspec_swiss_row1_11 ~ */
+ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+ /* ~ morekeys_swiss_row2_11 */
+ // U+2020: "†" DAGGER
+ // U+2021: "‡" DOUBLE DAGGER
+ // U+2605: "★" BLACK STAR
+ /* morekeys_star */ "\u2020,\u2021,\u2605",
+ // The all letters need to be mirrored are found at
+ // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
+ // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ // U+2264: "≤" LESS-THAN OR EQUAL TO
+ // U+2265: "≥" GREATER-THAN EQUAL TO
+ // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ /* keyspec_left_parenthesis */ "(",
+ /* keyspec_right_parenthesis */ ")",
+ /* keyspec_left_square_bracket */ "[",
+ /* keyspec_right_square_bracket */ "]",
+ /* keyspec_left_curly_bracket */ "{",
+ /* keyspec_right_curly_bracket */ "}",
+ /* keyspec_less_than */ "<",
+ /* keyspec_greater_than */ ">",
+ /* keyspec_less_than_equal */ "\u2264",
+ /* keyspec_greater_than_equal */ "\u2265",
+ /* keyspec_left_double_angle_quote */ "\u00AB",
+ /* keyspec_right_double_angle_quote */ "\u00BB",
+ /* keyspec_left_single_angle_quote */ "\u2039",
+ /* keyspec_right_single_angle_quote */ "\u203A",
+ /* morekeys_tablet_comma */ EMPTY,
+ /* keyhintlabel_period */ EMPTY,
+ /* morekeys_tablet_period */ "!text/morekeys_tablet_punctuation",
+ // U+00BF: "¿" INVERTED QUESTION MARK
+ /* morekeys_question */ "\u00BF",
+ /* morekeys_h ~ */
+ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+ /* ~ keyspec_south_slavic_row3_8 */
+ /* morekeys_tablet_punctuation */ "!autoColumnOrder!7,\\,,',#,!text/keyspec_right_parenthesis,!text/keyspec_left_parenthesis,/,;,@,:,-,\",+,\\%,&",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* keyspec_spanish_row2_10 */ "\u00F1",
+ // U+266A: "♪" EIGHTH NOTE
+ // U+2665: "♥" BLACK HEART SUIT
+ // U+2660: "♠" BLACK SPADE SUIT
+ // U+2666: "♦" BLACK DIAMOND SUIT
+ // U+2663: "♣" BLACK CLUB SUIT
+ /* 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,
+ // Comma key
+ /* keyspec_comma */ ",",
+ /* keyhintlabel_tablet_comma */ EMPTY,
+ // Period key
+ /* keyspec_period */ ".",
+ /* morekeys_period */ "!text/morekeys_punctuation",
+ /* keyspec_tablet_period */ ".",
+ /* keyhintlabel_tablet_period */ EMPTY,
+ /* keyspec_symbols_question */ "?",
+ /* keyspec_symbols_semicolon */ ";",
+ /* keyspec_symbols_percent */ "%",
+ /* morekeys_symbols_semicolon */ EMPTY,
+ // U+2030: "‰" PER MILLE SIGN
+ /* morekeys_symbols_percent */ "\u2030",
+ /* morekeys_v ~ */
+ EMPTY, EMPTY, EMPTY, EMPTY,
+ /* ~ morekeys_x */
+ /* keyspec_q */ "q",
+ /* keyspec_w */ "w",
+ /* keyspec_y */ "y",
+ /* keyspec_x */ "x",
+ /* morekeys_east_slavic_row2_11 ~ */
+ EMPTY, EMPTY, EMPTY,
+ /* ~ morekeys_cyrillic_a */
+ // U+00A2: "¢" CENT SIGN
+ // U+00A3: "£" POUND SIGN
+ // U+20AC: "€" EURO SIGN
+ // U+00A5: "¥" YEN SIGN
+ // U+20B1: "₱" PESO SIGN
+ /* morekeys_currency_dollar */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
+ // U+00B1: "±" PLUS-MINUS SIGN
+ /* morekeys_plus */ "\u00B1",
+ /* morekeys_less_than */ "!fixedColumnOrder!3,!text/keyspec_left_single_angle_quote,!text/keyspec_less_than_equal,!text/keyspec_left_double_angle_quote",
+ /* morekeys_greater_than */ "!fixedColumnOrder!3,!text/keyspec_right_single_angle_quote,!text/keyspec_greater_than_equal,!text/keyspec_right_double_angle_quote",
+ // U+00A1: "¡" INVERTED EXCLAMATION MARK
+ /* morekeys_exclamation */ "\u00A1",
+ /* morekeys_currency */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
+ // U+00B9: "¹" SUPERSCRIPT ONE
+ // U+00BD: "½" VULGAR FRACTION ONE HALF
+ // U+2153: "⅓" VULGAR FRACTION ONE THIRD
+ // U+00BC: "¼" VULGAR FRACTION ONE QUARTER
+ // U+215B: "⅛" VULGAR FRACTION ONE EIGHTH
+ /* morekeys_symbols_1 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
+ // U+00B2: "²" SUPERSCRIPT TWO
+ // U+2154: "⅔" VULGAR FRACTION TWO THIRDS
+ /* morekeys_symbols_2 */ "\u00B2,\u2154",
+ // U+00B3: "³" SUPERSCRIPT THREE
+ // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS
+ // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS
+ /* morekeys_symbols_3 */ "\u00B3,\u00BE,\u215C",
+ // U+2074: "⁴" SUPERSCRIPT FOUR
+ /* morekeys_symbols_4 */ "\u2074",
+ // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS
+ /* morekeys_symbols_5 */ "\u215D",
+ /* morekeys_symbols_6 */ EMPTY,
+ // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
+ /* morekeys_symbols_7 */ "\u215E",
+ /* morekeys_symbols_8 */ EMPTY,
+ /* morekeys_symbols_9 */ EMPTY,
+ // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N
+ // U+2205: "∅" EMPTY SET
+ /* morekeys_symbols_0 */ "\u207F,\u2205",
+ /* morekeys_am_pm */ "!fixedColumnOrder!2,!hasLabels!,!text/keylabel_time_am,!text/keylabel_time_pm",
+ /* keyspec_settings */ "!icon/settings_key|!code/key_settings",
+ /* keyspec_shortcut */ "!icon/shortcut_key|!code/key_shortcut",
+ /* keyspec_action_next */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
+ /* keyspec_action_previous */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
+ // Label for "switch to more symbol" modifier key ("= \ <"). Must be short to fit on key!
+ /* keylabel_to_more_symbol */ "= \\\\ <",
+ // Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key!
+ /* keylabel_tablet_to_more_symbol */ "~ [ <",
+ // Label for "switch to phone numeric" key. Must be short to fit on key!
+ /* keylabel_to_phone_numeric */ "123",
+ // Label for "switch to phone symbols" key. Must be short to fit on key!
+ // U+FF0A: "*" FULLWIDTH ASTERISK
+ // U+FF03: "#" FULLWIDTH NUMBER SIGN
+ /* keylabel_to_phone_symbols */ "\uFF0A\uFF03",
+ // Key label for "ante meridiem"
+ /* keylabel_time_am */ "AM",
+ // Key label for "post meridiem"
+ /* keylabel_time_pm */ "PM",
+ /* keyspec_popular_domain */ ".com",
+ // popular web domains for the locale - most popular, displayed on the keyboard
+ /* morekeys_popular_domain */ "!hasLabels!,.net,.org,.gov,.edu",
+ /* keyspecs_left_parenthesis_more_keys */ "!text/keyspec_less_than,!text/keyspec_left_curly_bracket,!text/keyspec_left_square_bracket",
+ /* keyspecs_right_parenthesis_more_keys */ "!text/keyspec_greater_than,!text/keyspec_right_curly_bracket,!text/keyspec_right_square_bracket",
+ // The following characters don't need BIDI mirroring.
+ // U+2018: "‘" LEFT SINGLE QUOTATION MARK
+ // U+2019: "’" RIGHT SINGLE QUOTATION MARK
+ // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+ // U+201C: "“" LEFT DOUBLE QUOTATION MARK
+ // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+ // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+ // Abbreviations are:
+ // laqm: LEFT-POINTING ANGLE QUOTATION MARK
+ // raqm: RIGHT-POINTING ANGLE QUOTATION MARK
+ // lqm: LEFT QUOTATION MARK
+ // rqm: RIGHT QUOTATION MARK
+ // 9qm: LOW-9 QUOTATION MARK
+ // The following each quotation mark pair consist of
+ // <opening quotation mark>, <closing quotation mark>
+ // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
+ /* single_laqm_raqm */ "!text/keyspec_left_single_angle_quote,!text/keyspec_right_single_angle_quote",
+ /* single_raqm_laqm */ "!text/keyspec_right_single_angle_quote,!text/keyspec_left_single_angle_quote",
+ /* double_laqm_raqm */ "!text/keyspec_left_double_angle_quote,!text/keyspec_right_double_angle_quote",
+ /* double_raqm_laqm */ "!text/keyspec_right_double_angle_quote,!text/keyspec_left_double_angle_quote",
+ // The following each quotation mark triplet consists of
+ // <another quotation mark>, <opening quotation mark>, <closing quotation mark>
+ // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
+ /* single_lqm_rqm */ "\u201A,\u2018,\u2019",
+ /* single_9qm_lqm */ "\u2019,\u201A,\u2018",
+ /* single_9qm_rqm */ "\u2018,\u201A,\u2019",
+ /* single_rqm_9qm */ "\u2018,\u2019,\u201A",
+ /* double_lqm_rqm */ "\u201E,\u201C,\u201D",
+ /* double_9qm_lqm */ "\u201D,\u201E,\u201C",
+ /* double_9qm_rqm */ "\u201C,\u201E,\u201D",
+ /* double_rqm_9qm */ "\u201C,\u201D,\u201E",
+ /* morekeys_single_quote */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
+ /* morekeys_double_quote */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
+ /* morekeys_tablet_double_quote */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
+ /* keyspec_emoji_key */ "!icon/emoji_key|!code/key_emoji",
+ };
+
+ /* Locale af: Afrikaans */
+ private static final String[] TEXTS_af = {
+ // This is the same as Dutch except more keys of y and demoting vowels with diaeresis.
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ /* morekeys_a */ "\u00E1,\u00E2,\u00E4,\u00E0,\u00E6,\u00E3,\u00E5,\u0101",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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",
+ // 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
+ // 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+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ // 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_s */
+ // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+ // U+0133: "ij" LATIN SMALL LIGATURE IJ
+ /* morekeys_y */ "\u00FD,\u0133",
+ };
+
+ /* Locale ar: Arabic */
+ private static final String[] TEXTS_ar = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // Label for "switch to alphabetic" key.
+ // U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
+ // U+200C: ZERO WIDTH NON-JOINER
+ // U+0628: "ب" ARABIC LETTER BEH
+ // U+062C: "ج" ARABIC LETTER JEEM
+ /* keylabel_to_alpha */ "\u0623\u200C\u0628\u200C\u062C",
+ /* morekeys_s ~ */
+ 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_punctuation */
+ // U+0661: "١" ARABIC-INDIC DIGIT ONE
+ /* keyspec_symbols_1 */ "\u0661",
+ // U+0662: "٢" ARABIC-INDIC DIGIT TWO
+ /* keyspec_symbols_2 */ "\u0662",
+ // U+0663: "٣" ARABIC-INDIC DIGIT THREE
+ /* keyspec_symbols_3 */ "\u0663",
+ // U+0664: "٤" ARABIC-INDIC DIGIT FOUR
+ /* keyspec_symbols_4 */ "\u0664",
+ // U+0665: "٥" ARABIC-INDIC DIGIT FIVE
+ /* keyspec_symbols_5 */ "\u0665",
+ // U+0666: "٦" ARABIC-INDIC DIGIT SIX
+ /* keyspec_symbols_6 */ "\u0666",
+ // U+0667: "٧" ARABIC-INDIC DIGIT SEVEN
+ /* keyspec_symbols_7 */ "\u0667",
+ // U+0668: "٨" ARABIC-INDIC DIGIT EIGHT
+ /* keyspec_symbols_8 */ "\u0668",
+ // U+0669: "٩" ARABIC-INDIC DIGIT NINE
+ /* keyspec_symbols_9 */ "\u0669",
+ // U+0660: "٠" ARABIC-INDIC DIGIT ZERO
+ /* keyspec_symbols_0 */ "\u0660",
+ // Label for "switch to symbols" key.
+ // U+061F: "؟" ARABIC QUESTION MARK
+ /* keylabel_to_symbol */ "\u0663\u0662\u0661\u061F",
+ /* additional_morekeys_symbols_1 */ "1",
+ /* additional_morekeys_symbols_2 */ "2",
+ /* additional_morekeys_symbols_3 */ "3",
+ /* additional_morekeys_symbols_4 */ "4",
+ /* additional_morekeys_symbols_5 */ "5",
+ /* additional_morekeys_symbols_6 */ "6",
+ /* additional_morekeys_symbols_7 */ "7",
+ /* additional_morekeys_symbols_8 */ "8",
+ /* additional_morekeys_symbols_9 */ "9",
+ // U+066B: "٫" ARABIC DECIMAL SEPARATOR
+ // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
+ /* additional_morekeys_symbols_0 */ "0,\u066B,\u066C",
+ // U+061F: "؟" ARABIC QUESTION MARK
+ // U+060C: "،" ARABIC COMMA
+ // U+061B: "؛" ARABIC SEMICOLON
+ /* keyspec_tablet_comma */ "\u060C",
+ /* keyspec_swiss_row1_11 ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_swiss_row2_11 */
+ // U+2605: "★" BLACK STAR
+ // U+066D: "٭" ARABIC FIVE POINTED STAR
+ /* morekeys_star */ "\u2605,\u066D",
+ // U+2264: "≤" LESS-THAN OR EQUAL TO
+ // U+2265: "≥" GREATER-THAN EQUAL TO
+ // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ /* keyspec_left_parenthesis */ "(|)",
+ /* keyspec_right_parenthesis */ ")|(",
+ /* keyspec_left_square_bracket */ "[|]",
+ /* keyspec_right_square_bracket */ "]|[",
+ /* keyspec_left_curly_bracket */ "{|}",
+ /* keyspec_right_curly_bracket */ "}|{",
+ /* keyspec_less_than */ "<|>",
+ /* keyspec_greater_than */ ">|<",
+ /* keyspec_less_than_equal */ "\u2264|\u2265",
+ /* keyspec_greater_than_equal */ "\u2265|\u2264",
+ /* keyspec_left_double_angle_quote */ "\u00AB|\u00BB",
+ /* keyspec_right_double_angle_quote */ "\u00BB|\u00AB",
+ /* keyspec_left_single_angle_quote */ "\u2039|\u203A",
+ /* keyspec_right_single_angle_quote */ "\u203A|\u2039",
+ /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\",\'",
+ // U+0651: "ّ" ARABIC SHADDA
+ /* keyhintlabel_period */ "\u0651",
+ /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics",
+ // U+00BF: "¿" INVERTED QUESTION MARK
+ /* morekeys_question */ "?,\u00BF",
+ /* morekeys_h ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ keyspec_spanish_row2_10 */
+ // U+266A: "♪" EIGHTH NOTE
+ /* morekeys_bullet */ "\u266A",
+ // The all letters need to be mirrored are found at
+ // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
+ // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
+ // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
+ /* morekeys_left_parenthesis */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,!text/keyspecs_left_parenthesis_more_keys",
+ /* morekeys_right_parenthesis */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,!text/keyspecs_right_parenthesis_more_keys",
+ // U+0655: "ٕ" ARABIC HAMZA BELOW
+ // U+0654: "ٔ" ARABIC HAMZA ABOVE
+ // U+0652: "ْ" ARABIC SUKUN
+ // U+064D: "ٍ" ARABIC KASRATAN
+ // U+064C: "ٌ" ARABIC DAMMATAN
+ // U+064B: "ً" ARABIC FATHATAN
+ // U+0651: "ّ" ARABIC SHADDA
+ // U+0656: "ٖ" ARABIC SUBSCRIPT ALEF
+ // U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF
+ // U+0653: "ٓ" ARABIC MADDAH ABOVE
+ // U+0650: "ِ" ARABIC KASRA
+ // U+064F: "ُ" ARABIC DAMMA
+ // U+064E: "َ" ARABIC FATHA
+ // U+0640: "ـ" ARABIC TATWEEL
+ // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
+ // 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",
+ // U+060C: "،" ARABIC COMMA
+ /* keyspec_comma */ "\u060C",
+ /* 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",
+ // U+066A: "٪" ARABIC PERCENT SIGN
+ /* keyspec_symbols_percent */ "\u066A",
+ /* morekeys_symbols_semicolon */ ";",
+ // U+2030: "‰" PER MILLE SIGN
+ /* morekeys_symbols_percent */ "\\%,\u2030",
+ };
+
+ /* Locale az_AZ: Azerbaijani (Azerbaijan) */
+ private static final String[] TEXTS_az_AZ = {
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ /* morekeys_a */ "\u00E2",
+ // 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+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",
+ // 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
+ // 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+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,
+ /* ~ keylabel_to_alpha */
+ // 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",
+ /* morekeys_y ~ */
+ null, null, null, null, null,
+ /* ~ morekeys_l */
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ /* morekeys_g */ "\u011F",
+ };
+
+ /* Locale be_BY: Belarusian (Belarus) */
+ private static final String[] TEXTS_be_BY = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_c */
+ /* double_quotes */ "!text/double_9qm_lqm",
+ /* morekeys_n */ null,
+ /* single_quotes */ "!text/single_9qm_lqm",
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_k */
+ // U+0451: "ё" CYRILLIC SMALL LETTER IO
+ /* morekeys_cyrillic_ie */ "\u0451",
+ /* keyspec_nordic_row1_11 ~ */
+ null, null, null, null,
+ /* ~ morekeys_nordic_row2_10 */
+ // U+045E: "ў" CYRILLIC SMALL LETTER SHORT U
+ /* keyspec_east_slavic_row1_9 */ "\u045E",
+ // U+044B: "ы" CYRILLIC SMALL LETTER YERU
+ /* keyspec_east_slavic_row2_2 */ "\u044B",
+ // U+044D: "э" CYRILLIC SMALL LETTER E
+ /* keyspec_east_slavic_row2_11 */ "\u044D",
+ // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+ /* keyspec_east_slavic_row3_5 */ "\u0456",
+ // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
+ /* morekeys_cyrillic_soft_sign */ "\u044A",
+ };
+
+ /* Locale bg: Bulgarian */
+ private static final String[] TEXTS_bg = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_c */
+ // single_quotes of Bulgarian is default single_quotes_right_left.
+ /* double_quotes */ "!text/double_9qm_lqm",
+ /* morekeys_n */ null,
+ /* single_quotes */ null,
+ // 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",
+ };
+
+ /* Locale ca: Catalan */
+ private static final String[] TEXTS_ca = {
+ // 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+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* morekeys_a */ "\u00E0,\u00E1,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA",
+ // 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+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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",
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // 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 */ "\u00E8,\u00E9,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // 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+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,
+ /* ~ morekeys_t */
+ // U+00B7: "·" MIDDLE DOT
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ /* morekeys_l */ "l\u00B7l,\u0142",
+ /* morekeys_g ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null,
+ /* ~ morekeys_nordic_row2_11 */
+ // U+00B7: "·" MIDDLE DOT
+ /* morekeys_punctuation */ "!autoColumnOrder!9,\\,,?,!,\u00B7,#,),(,/,;,',@,:,-,\",+,\\%,&",
+ /* keyspec_symbols_1 ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, 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
+ /* keyspec_spanish_row2_10 */ "\u00E7",
+ };
+
+ /* Locale cs: Czech */
+ private static final String[] TEXTS_cs = {
+ // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ /* morekeys_a */ "\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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",
+ // 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
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // 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+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",
+ /* keylabel_to_alpha */ null,
+ // 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",
+ // 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+0165: "ť" LATIN SMALL LETTER T WITH CARON
+ /* morekeys_t */ "\u0165",
+ /* morekeys_l */ null,
+ /* morekeys_g */ null,
+ /* single_angle_quotes */ "!text/single_raqm_laqm",
+ /* double_angle_quotes */ "!text/double_raqm_laqm",
+ /* keyspec_currency */ null,
+ // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+ /* morekeys_r */ "\u0159",
+ };
+
+ /* Locale da: Danish */
+ private static final String[] TEXTS_da = {
+ // 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",
+ // 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",
+ // 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",
+ // 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",
+ /* keylabel_to_alpha */ null,
+ // 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",
+ // 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
+ /* morekeys_d */ "\u00F0",
+ /* morekeys_z */ null,
+ /* morekeys_t */ null,
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ /* morekeys_l */ "\u0142",
+ /* morekeys_g */ null,
+ /* single_angle_quotes */ "!text/single_raqm_laqm",
+ /* double_angle_quotes */ "!text/double_raqm_laqm",
+ /* keyspec_currency ~ */
+ null, null, null, null,
+ /* ~ morekeys_cyrillic_ie */
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ /* keyspec_nordic_row1_11 */ "\u00E5",
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ /* keyspec_nordic_row2_10 */ "\u00E6",
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ /* keyspec_nordic_row2_11 */ "\u00F8",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ /* morekeys_nordic_row2_10 */ "\u00E4",
+ /* keyspec_east_slavic_row1_9 ~ */
+ null, null, null, null, null,
+ /* ~ morekeys_cyrillic_soft_sign */
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ /* morekeys_nordic_row2_11 */ "\u00F6",
+ };
+
+ /* Locale de: German */
+ private static final String[] TEXTS_de = {
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ /* morekeys_a */ "\u00E4,%,\u00E2,\u00E0,\u00E1,\u00E6,\u00E3,\u00E5,\u0101",
+ // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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+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",
+ // 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",
+ /* keylabel_to_alpha */ null,
+ // 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",
+ /* morekeys_y ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_g */
+ /* single_angle_quotes */ "!text/single_raqm_laqm",
+ /* double_angle_quotes */ "!text/double_raqm_laqm",
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, 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 */
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ /* keyspec_swiss_row1_11 */ "\u00FC",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ /* keyspec_swiss_row2_10 */ "\u00F6",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ /* keyspec_swiss_row2_11 */ "\u00E4",
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ /* morekeys_swiss_row1_11 */ "\u00E8",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ /* morekeys_swiss_row2_10 */ "\u00E9",
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ /* morekeys_swiss_row2_11 */ "\u00E0",
+ };
+
+ /* Locale el: Greek */
+ private static final String[] TEXTS_el = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // Label for "switch to alphabetic" key.
+ // U+0391: "Α" GREEK CAPITAL LETTER ALPHA
+ // U+0392: "Β" GREEK CAPITAL LETTER BETA
+ // U+0393: "Γ" GREEK CAPITAL LETTER GAMMA
+ /* keylabel_to_alpha */ "\u0391\u0392\u0393",
+ };
+
+ /* Locale en: English */
+ private static final String[] TEXTS_en = {
+ // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // 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+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // 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 */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B",
+ // 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+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,
+ /* keylabel_to_alpha */ null,
+ // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+ /* morekeys_s */ "\u00DF",
+ };
+
+ /* Locale eo: Esperanto */
+ private static final String[] TEXTS_eo = {
+ // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* morekeys_a */ "\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101,\u0103,\u0105,\u00AA",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+ // 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+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
+ // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE
+ // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+ // U+00B5: "µ" MICRO SIGN
+ /* morekeys_u */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B,\u0169,\u0171,\u0173,\u00B5",
+ // 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
+ // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+ // 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
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // 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,
+ /* keylabel_to_alpha */ 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",
+ // 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+0165: "ť" LATIN SMALL LETTER T WITH CARON
+ // U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW
+ // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+ // U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE
+ /* morekeys_t */ "\u0165,\u021B,\u0163,\u0167",
+ // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+ // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+ // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
+ // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ /* morekeys_l */ "\u013A,\u013C,\u013E,\u0140,\u0142",
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ // U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE
+ // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+ /* morekeys_g */ "\u011F,\u0121,\u0123",
+ /* single_angle_quotes ~ */
+ null, null, null,
+ /* ~ keyspec_currency */
+ // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+ // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
+ // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+ /* morekeys_r */ "\u0159,\u0155,\u0157",
+ // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
+ // U+0138: "ĸ" LATIN SMALL LETTER KRA
+ /* morekeys_k */ "\u0137,\u0138",
+ /* morekeys_cyrillic_ie ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, 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
+ /* morekeys_h */ "\u0125,\u0127",
+ // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
+ /* morekeys_w */ "w,\u0175",
+ /* morekeys_east_slavic_row2_2 ~ */
+ null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_tablet_punctuation */
+ // 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, null,
+ /* ~ morekeys_symbols_percent */
+ // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
+ /* morekeys_v */ "w,\u0175",
+ /* morekeys_j */ null,
+ /* morekeys_q */ "q",
+ /* morekeys_x */ "x",
+ // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
+ /* keyspec_q */ "\u015D",
+ // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
+ /* keyspec_w */ "\u011D",
+ // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
+ /* keyspec_y */ "\u016D",
+ // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
+ /* keyspec_x */ "\u0109",
+ };
+
+ /* Locale es: Spanish */
+ private static final String[] TEXTS_es = {
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* morekeys_a */ "\u00E1,\u00E0,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // 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,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // 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+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, 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
+ /* morekeys_punctuation */ "!autoColumnOrder!9,\\,,?,!,#,),(,/,;,\u00A1,',@,:,-,\",+,\\%,&,\u00BF",
+ };
+
+ /* Locale et_EE: Estonian (Estonia) */
+ private static final String[] TEXTS_et_EE = {
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // 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+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ /* morekeys_a */ "\u00E4,\u0101,\u00E0,\u00E1,\u00E2,\u00E3,\u00E5,\u00E6,\u0105",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+ // 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+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+ // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ /* morekeys_u */ "\u00FC,\u016B,\u0173,\u00F9,\u00FA,\u00FB,\u016F,\u0171",
+ // 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
+ // 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+0131: "ı" LATIN SMALL LETTER DOTLESS I
+ /* morekeys_i */ "\u012B,\u00EC,\u012F,\u00ED,\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+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",
+ /* keylabel_to_alpha */ null,
+ // 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",
+ // 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+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+ // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+ /* morekeys_t */ "\u0163,\u0165",
+ // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+ // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
+ /* morekeys_l */ "\u013C,\u0142,\u013A,\u013E",
+ // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ /* morekeys_g */ "\u0123,\u011F",
+ /* single_angle_quotes ~ */
+ null, null, null,
+ /* ~ keyspec_currency */
+ // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+ // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+ // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
+ /* morekeys_r */ "\u0157,\u0159,\u0155",
+ // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
+ /* morekeys_k */ "\u0137",
+ /* morekeys_cyrillic_ie */ null,
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ /* keyspec_nordic_row1_11 */ "\u00FC",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ /* keyspec_nordic_row2_10 */ "\u00F6",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ /* keyspec_nordic_row2_11 */ "\u00E4",
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ /* morekeys_nordic_row2_10 */ "\u00F5",
+ };
+
+ /* Locale eu_ES: Basque (Spain) */
+ private static final String[] TEXTS_eu_ES = {
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* morekeys_a */ "\u00E1,\u00E0,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // 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,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // 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+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, null, null,
+ /* ~ single_quotes */
+ // Label for "switch to alphabetic" key.
+ // U+0627: "ا" ARABIC LETTER ALEF
+ // U+200C: ZERO WIDTH NON-JOINER
+ // U+0628: "ب" ARABIC LETTER BEH
+ // U+067E: "پ" ARABIC LETTER PEH
+ /* keylabel_to_alpha */ "\u0627\u200C\u0628\u200C\u067E",
+ /* morekeys_s ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ double_angle_quotes */
+ // U+FDFC: "﷼" RIAL SIGN
+ /* keyspec_currency */ "\uFDFC",
+ /* morekeys_r ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_punctuation */
+ // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE
+ /* keyspec_symbols_1 */ "\u06F1",
+ // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO
+ /* keyspec_symbols_2 */ "\u06F2",
+ // U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE
+ /* keyspec_symbols_3 */ "\u06F3",
+ // U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR
+ /* keyspec_symbols_4 */ "\u06F4",
+ // U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE
+ /* keyspec_symbols_5 */ "\u06F5",
+ // U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX
+ /* keyspec_symbols_6 */ "\u06F6",
+ // U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN
+ /* keyspec_symbols_7 */ "\u06F7",
+ // U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT
+ /* keyspec_symbols_8 */ "\u06F8",
+ // U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE
+ /* keyspec_symbols_9 */ "\u06F9",
+ // U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO
+ /* keyspec_symbols_0 */ "\u06F0",
+ // Label for "switch to symbols" key.
+ // U+061F: "؟" ARABIC QUESTION MARK
+ /* keylabel_to_symbol */ "\u06F3\u06F2\u06F1\u061F",
+ /* additional_morekeys_symbols_1 */ "1",
+ /* additional_morekeys_symbols_2 */ "2",
+ /* additional_morekeys_symbols_3 */ "3",
+ /* additional_morekeys_symbols_4 */ "4",
+ /* additional_morekeys_symbols_5 */ "5",
+ /* additional_morekeys_symbols_6 */ "6",
+ /* additional_morekeys_symbols_7 */ "7",
+ /* additional_morekeys_symbols_8 */ "8",
+ /* additional_morekeys_symbols_9 */ "9",
+ // U+066B: "٫" ARABIC DECIMAL SEPARATOR
+ // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
+ /* additional_morekeys_symbols_0 */ "0,\u066B,\u066C",
+ // U+060C: "،" ARABIC COMMA
+ // U+061B: "؛" ARABIC SEMICOLON
+ // U+061F: "؟" ARABIC QUESTION MARK
+ // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ /* keyspec_tablet_comma */ "\u060C",
+ /* keyspec_swiss_row1_11 ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_swiss_row2_11 */
+ // U+2605: "★" BLACK STAR
+ // U+066D: "٭" ARABIC FIVE POINTED STAR
+ /* morekeys_star */ "\u2605,\u066D",
+ /* keyspec_left_parenthesis */ "(|)",
+ /* keyspec_right_parenthesis */ ")|(",
+ /* keyspec_left_square_bracket */ "[|]",
+ /* keyspec_right_square_bracket */ "]|[",
+ /* keyspec_left_curly_bracket */ "{|}",
+ /* keyspec_right_curly_bracket */ "}|{",
+ /* keyspec_less_than */ "<|>",
+ /* keyspec_greater_than */ ">|<",
+ /* keyspec_less_than_equal */ "\u2264|\u2265",
+ /* keyspec_greater_than_equal */ "\u2265|\u2264",
+ /* keyspec_left_double_angle_quote */ "\u00AB|\u00BB",
+ /* keyspec_right_double_angle_quote */ "\u00BB|\u00AB",
+ /* keyspec_left_single_angle_quote */ "\u2039|\u203A",
+ /* keyspec_right_single_angle_quote */ "\u203A|\u2039",
+ /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,!text/keyspec_left_double_angle_quote,!text/keyspec_right_double_angle_quote",
+ // U+064B: "ً" ARABIC FATHATAN
+ /* keyhintlabel_period */ "\u064B",
+ /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics",
+ // U+00BF: "¿" INVERTED QUESTION MARK
+ /* morekeys_question */ "?,\u00BF",
+ /* morekeys_h ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ keyspec_spanish_row2_10 */
+ // U+266A: "♪" EIGHTH NOTE
+ /* morekeys_bullet */ "\u266A",
+ // The all letters need to be mirrored are found at
+ // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
+ // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
+ // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
+ /* morekeys_left_parenthesis */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,!text/keyspecs_left_parenthesis_more_keys",
+ /* morekeys_right_parenthesis */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,!text/keyspecs_right_parenthesis_more_keys",
+ // U+0655: "ٕ" ARABIC HAMZA BELOW
+ // U+0652: "ْ" ARABIC SUKUN
+ // U+0651: "ّ" ARABIC SHADDA
+ // U+064C: "ٌ" ARABIC DAMMATAN
+ // U+064D: "ٍ" ARABIC KASRATAN
+ // U+064B: "ً" ARABIC FATHATAN
+ // U+0654: "ٔ" ARABIC HAMZA ABOVE
+ // U+0656: "ٖ" ARABIC SUBSCRIPT ALEF
+ // U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF
+ // U+0653: "ٓ" ARABIC MADDAH ABOVE
+ // U+064F: "ُ" ARABIC DAMMA
+ // U+0650: "ِ" ARABIC KASRA
+ // U+064E: "َ" ARABIC FATHA
+ // U+0640: "ـ" ARABIC TATWEEL
+ // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
+ // 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",
+ // U+060C: "،" ARABIC COMMA
+ /* keyspec_comma */ "\u060C",
+ /* 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",
+ // U+066A: "٪" ARABIC PERCENT SIGN
+ /* keyspec_symbols_percent */ "\u066A",
+ /* 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,
+ /* ~ morekeys_plus */
+ // U+2264: "≤" LESS-THAN OR EQUAL TO
+ // U+2265: "≥" GREATER-THAN EQUAL TO
+ // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ /* morekeys_less_than */ "!fixedColumnOrder!3,!text/keyspec_left_single_angle_quote,!text/keyspec_less_than_equal,!text/keyspec_less_than",
+ /* morekeys_greater_than */ "!fixedColumnOrder!3,!text/keyspec_right_single_angle_quote,!text/keyspec_greater_than_equal,!text/keyspec_greater_than",
+ };
+
+ /* Locale fi: Finnish */
+ private static final String[] TEXTS_fi = {
+ // 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",
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // 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+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",
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ /* morekeys_u */ "\u00FC",
+ /* morekeys_e ~ */
+ null, null, null, null, null, null, null,
+ /* ~ keylabel_to_alpha */
+ // 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",
+ /* morekeys_y */ null,
+ /* morekeys_d */ null,
+ // 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, null,
+ /* ~ morekeys_cyrillic_ie */
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ /* keyspec_nordic_row1_11 */ "\u00E5",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ /* keyspec_nordic_row2_10 */ "\u00F6",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ /* keyspec_nordic_row2_11 */ "\u00E4",
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ /* morekeys_nordic_row2_10 */ "\u00F8",
+ /* keyspec_east_slavic_row1_9 ~ */
+ null, null, null, null, null,
+ /* ~ morekeys_cyrillic_soft_sign */
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ /* morekeys_nordic_row2_11 */ "\u00E6",
+ };
+
+ /* Locale fr: French */
+ private static final String[] TEXTS_fr = {
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* morekeys_a */ "\u00E0,\u00E2,%,\u00E6,\u00E1,\u00E4,\u00E3,\u00E5,\u0101,\u00AA",
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // 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
+ // 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",
+ // 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
+ // 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+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 */ "\u00EE,%,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ // 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,
+ /* ~ morekeys_s */
+ // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
+ /* morekeys_y */ "%,\u00FF",
+ /* morekeys_d ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, 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 */
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ /* keyspec_swiss_row1_11 */ "\u00E8",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ /* keyspec_swiss_row2_10 */ "\u00E9",
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ /* keyspec_swiss_row2_11 */ "\u00E0",
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ /* morekeys_swiss_row1_11 */ "\u00FC",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ /* morekeys_swiss_row2_10 */ "\u00F6",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ /* morekeys_swiss_row2_11 */ "\u00E4",
+ };
+
+ /* Locale gl_ES: Gallegan (Spain) */
+ private static final String[] TEXTS_gl_ES = {
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* morekeys_a */ "\u00E1,\u00E0,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // 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,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // 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+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, null, null,
+ /* ~ single_quotes */
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ double_angle_quotes */
+ // U+20B9: "₹" INDIAN RUPEE SIGN
+ /* keyspec_currency */ "\u20B9",
+ /* morekeys_r ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_punctuation */
+ // U+0967: "१" DEVANAGARI DIGIT ONE
+ /* keyspec_symbols_1 */ "\u0967",
+ // U+0968: "२" DEVANAGARI DIGIT TWO
+ /* keyspec_symbols_2 */ "\u0968",
+ // U+0969: "३" DEVANAGARI DIGIT THREE
+ /* keyspec_symbols_3 */ "\u0969",
+ // U+096A: "४" DEVANAGARI DIGIT FOUR
+ /* keyspec_symbols_4 */ "\u096A",
+ // U+096B: "५" DEVANAGARI DIGIT FIVE
+ /* keyspec_symbols_5 */ "\u096B",
+ // U+096C: "६" DEVANAGARI DIGIT SIX
+ /* keyspec_symbols_6 */ "\u096C",
+ // U+096D: "७" DEVANAGARI DIGIT SEVEN
+ /* keyspec_symbols_7 */ "\u096D",
+ // U+096E: "८" DEVANAGARI DIGIT EIGHT
+ /* keyspec_symbols_8 */ "\u096E",
+ // U+096F: "९" DEVANAGARI DIGIT NINE
+ /* keyspec_symbols_9 */ "\u096F",
+ // U+0966: "०" DEVANAGARI DIGIT ZERO
+ /* keyspec_symbols_0 */ "\u0966",
+ // Label for "switch to symbols" key.
+ /* keylabel_to_symbol */ "?\u0967\u0968\u0969",
+ /* additional_morekeys_symbols_1 */ "1",
+ /* additional_morekeys_symbols_2 */ "2",
+ /* additional_morekeys_symbols_3 */ "3",
+ /* additional_morekeys_symbols_4 */ "4",
+ /* additional_morekeys_symbols_5 */ "5",
+ /* additional_morekeys_symbols_6 */ "6",
+ /* additional_morekeys_symbols_7 */ "7",
+ /* additional_morekeys_symbols_8 */ "8",
+ /* additional_morekeys_symbols_9 */ "9",
+ /* additional_morekeys_symbols_0 */ "0",
+ };
+
+ /* Locale hr: Croatian */
+ private static final String[] TEXTS_hr = {
+ /* morekeys_a ~ */
+ null, null, null, null, null,
+ /* ~ morekeys_i */
+ // 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",
+ /* keylabel_to_alpha */ null,
+ // 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",
+ /* 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",
+ /* morekeys_t ~ */
+ null, null, null,
+ /* ~ morekeys_g */
+ /* single_angle_quotes */ "!text/single_raqm_laqm",
+ /* double_angle_quotes */ "!text/double_raqm_laqm",
+ };
+
+ /* Locale hu: Hungarian */
+ private static final String[] TEXTS_hu = {
+ // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ /* morekeys_a */ "\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE 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+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",
+ // 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
+ // 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+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // 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+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+ /* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u00EC,\u012F,\u012B",
+ /* morekeys_c */ null,
+ /* double_quotes */ "!text/double_9qm_rqm",
+ /* morekeys_n */ null,
+ /* single_quotes */ "!text/single_9qm_rqm",
+ /* keylabel_to_alpha ~ */
+ null, null, null, null, null, null, null, null,
+ /* ~ morekeys_g */
+ /* single_angle_quotes */ "!text/single_raqm_laqm",
+ /* double_angle_quotes */ "!text/double_raqm_laqm",
+ };
+
+ /* Locale hy_AM: Armenian (Armenia) */
+ private static final String[] TEXTS_hy_AM = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // 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_s ~ */
+ 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
+ // U+055A: "՚" ARMENIAN APOSTROPHE
+ // U+0559: "ՙ" ARMENIAN MODIFIER LETTER LEFT HALF RING
+ // U+055D: "՝" ARMENIAN COMMA
+ // U+055B: "՛" ARMENIAN EMPHASIS MARK
+ // U+058A: "֊" ARMENIAN HYPHEN
+ // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ // U+055F: "՟" ARMENIAN ABBREVIATION MARK
+ /* morekeys_punctuation */ "!autoColumnOrder!8,\\,,\u055E,\u055C,.,\u055A,\u0559,?,!,\u055D,\u055B,\u058A,\u00BB,\u00AB,\u055F,;,:",
+ /* keyspec_symbols_1 ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null,
+ /* ~ additional_morekeys_symbols_0 */
+ // U+058F: "֏" ARMENIAN DRAM SIGN
+ // TODO: Enable this when we have glyph for the following letter
+ // <string name="keyspec_currency">&#x058F;</string>
+ //
+ // U+055D: "՝" ARMENIAN COMMA
+ /* keyspec_tablet_comma */ "\u055D",
+ /* 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,
+ /* ~ keyhintlabel_period */
+ /* morekeys_tablet_period */ "!text/morekeys_punctuation",
+ // 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, 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,
+ /* ~ morekeys_greater_than */
+ // U+055C: "՜" ARMENIAN EXCLAMATION MARK
+ // U+00A1: "¡" INVERTED EXCLAMATION MARK
+ /* morekeys_exclamation */ "\u055C,\u00A1",
+ };
+
+ /* Locale is: Icelandic */
+ private static final String[] TEXTS_is = {
+ // 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+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,\u00E6,\u00E5,\u00E0,\u00E2,\u00E3,\u0101",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // 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,\u00EB,\u00E8,\u00EA,\u0119,\u0117,\u0113",
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // 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_c */ null,
+ /* double_quotes */ "!text/double_9qm_lqm",
+ /* morekeys_n */ null,
+ /* single_quotes */ "!text/single_9qm_lqm",
+ /* keylabel_to_alpha */ null,
+ /* morekeys_s */ 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
+ /* morekeys_d */ "\u00F0",
+ /* morekeys_z */ null,
+ // U+00FE: "þ" LATIN SMALL LETTER THORN
+ /* morekeys_t */ "\u00FE",
+ };
+
+ /* Locale it: Italian */
+ private static final String[] TEXTS_it = {
+ // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101,\u00AA",
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // 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",
+ // 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+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+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // 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+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 ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, 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 */
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ /* keyspec_swiss_row1_11 */ "\u00FC",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ /* keyspec_swiss_row2_10 */ "\u00F6",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ /* keyspec_swiss_row2_11 */ "\u00E4",
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ /* morekeys_swiss_row1_11 */ "\u00E8",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ /* morekeys_swiss_row2_10 */ "\u00E9",
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ /* morekeys_swiss_row2_11 */ "\u00E0",
+ };
+
+ /* Locale iw: Hebrew */
+ private static final String[] TEXTS_iw = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_c */
+ /* double_quotes */ "!text/double_rqm_9qm",
+ /* morekeys_n */ null,
+ /* single_quotes */ "!text/single_rqm_9qm",
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ double_angle_quotes */
+ // U+20AA: "₪" NEW SHEQEL SIGN
+ /* keyspec_currency */ "\u20AA",
+ /* 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, null, null, null, null, null, null,
+ /* ~ morekeys_swiss_row2_11 */
+ // U+2605: "★" BLACK STAR
+ /* morekeys_star */ "\u2605",
+ // The all letters need to be mirrored are found at
+ // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
+ // U+2264: "≤" LESS-THAN OR EQUAL TO
+ // U+2265: "≥" GREATER-THAN EQUAL TO
+ // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ /* keyspec_left_parenthesis */ "(|)",
+ /* keyspec_right_parenthesis */ ")|(",
+ /* keyspec_left_square_bracket */ "[|]",
+ /* keyspec_right_square_bracket */ "]|[",
+ /* keyspec_left_curly_bracket */ "{|}",
+ /* keyspec_right_curly_bracket */ "}|{",
+ /* keyspec_less_than */ "<|>",
+ /* keyspec_greater_than */ ">|<",
+ /* keyspec_less_than_equal */ "\u2264|\u2265",
+ /* keyspec_greater_than_equal */ "\u2265|\u2264",
+ /* keyspec_left_double_angle_quote */ "\u00AB|\u00BB",
+ /* keyspec_right_double_angle_quote */ "\u00BB|\u00AB",
+ /* keyspec_left_single_angle_quote */ "\u2039|\u203A",
+ /* keyspec_right_single_angle_quote */ "\u203A|\u2039",
+ /* morekeys_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,
+ /* ~ morekeys_currency_dollar */
+ // U+00B1: "±" PLUS-MINUS SIGN
+ // U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
+ /* morekeys_plus */ "\u00B1,\uFB29",
+ };
+
+ /* Locale ka_GE: Georgian (Georgia) */
+ private static final String[] TEXTS_ka_GE = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_c */
+ /* double_quotes */ "!text/double_9qm_lqm",
+ /* morekeys_n */ null,
+ /* single_quotes */ "!text/single_9qm_lqm",
+ // 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",
+ };
+
+ /* Locale kk: Kazakh */
+ private static final String[] TEXTS_kk = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_k */
+ // U+0451: "ё" CYRILLIC SMALL LETTER IO
+ /* morekeys_cyrillic_ie */ "\u0451",
+ /* keyspec_nordic_row1_11 ~ */
+ null, null, null, null,
+ /* ~ morekeys_nordic_row2_10 */
+ // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
+ /* keyspec_east_slavic_row1_9 */ "\u0449",
+ // U+044B: "ы" CYRILLIC SMALL LETTER YERU
+ /* keyspec_east_slavic_row2_2 */ "\u044B",
+ // U+044D: "э" CYRILLIC SMALL LETTER E
+ /* keyspec_east_slavic_row2_11 */ "\u044D",
+ // U+0438: "и" CYRILLIC SMALL LETTER I
+ /* keyspec_east_slavic_row3_5 */ "\u0438",
+ // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
+ /* morekeys_cyrillic_soft_sign */ "\u044A",
+ /* morekeys_nordic_row2_11 ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, 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",
+ // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
+ // U+04B1: "ұ" CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE
+ /* morekeys_cyrillic_u */ "\u04AF,\u04B1",
+ // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER
+ /* morekeys_cyrillic_en */ "\u04A3",
+ // U+0493: "ғ" CYRILLIC SMALL LETTER GHE WITH STROKE
+ /* morekeys_cyrillic_ghe */ "\u0493",
+ // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
+ /* 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,
+ /* ~ keyspec_x */
+ // U+04BB: "һ" CYRILLIC SMALL LETTER SHHA
+ /* morekeys_east_slavic_row2_11 */ "\u04BB",
+ // U+049B: "қ" CYRILLIC SMALL LETTER KA WITH DESCENDER
+ /* morekeys_cyrillic_ka */ "\u049B",
+ // U+04D9: "ә" CYRILLIC SMALL LETTER SCHWA
+ /* morekeys_cyrillic_a */ "\u04D9",
+ };
+
+ /* Locale km_KH: Khmer (Cambodia) */
+ private static final String[] TEXTS_km_KH = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, 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",
+ };
+
+ /* Locale ky: Kirghiz */
+ private static final String[] TEXTS_ky = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_k */
+ // U+0451: "ё" CYRILLIC SMALL LETTER IO
+ /* morekeys_cyrillic_ie */ "\u0451",
+ /* keyspec_nordic_row1_11 ~ */
+ null, null, null, null,
+ /* ~ morekeys_nordic_row2_10 */
+ // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
+ /* keyspec_east_slavic_row1_9 */ "\u0449",
+ // U+044B: "ы" CYRILLIC SMALL LETTER YERU
+ /* keyspec_east_slavic_row2_2 */ "\u044B",
+ // U+044D: "э" CYRILLIC SMALL LETTER E
+ /* keyspec_east_slavic_row2_11 */ "\u044D",
+ // U+0438: "и" CYRILLIC SMALL LETTER I
+ /* keyspec_east_slavic_row3_5 */ "\u0438",
+ // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
+ /* morekeys_cyrillic_soft_sign */ "\u044A",
+ /* morekeys_nordic_row2_11 ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, 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",
+ // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER
+ /* morekeys_cyrillic_en */ "\u04A3",
+ /* morekeys_cyrillic_ghe */ null,
+ // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
+ /* morekeys_cyrillic_o */ "\u04E9",
+ };
+
+ /* Locale lo_LA: Lao (Laos) */
+ private static final String[] TEXTS_lo_LA = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ double_angle_quotes */
+ // U+20AD: "₭" KIP SIGN
+ /* keyspec_currency */ "\u20AD",
+ };
+
+ /* Locale lt: Lithuanian */
+ private static final String[] TEXTS_lt = {
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // 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+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ /* morekeys_a */ "\u0105,\u00E4,\u0101,\u00E0,\u00E1,\u00E2,\u00E3,\u00E5,\u00E6",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ // 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+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+ // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ /* morekeys_u */ "\u016B,\u0173,\u00FC,\u016B,\u00F9,\u00FA,\u00FB,\u016F,\u0171",
+ // 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
+ // 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+0131: "ı" LATIN SMALL LETTER DOTLESS I
+ /* morekeys_i */ "\u012F,\u012B,\u00EC,\u00ED,\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+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",
+ /* keylabel_to_alpha */ null,
+ // 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",
+ // 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+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+ // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+ /* morekeys_t */ "\u0163,\u0165",
+ // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+ // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
+ /* morekeys_l */ "\u013C,\u0142,\u013A,\u013E",
+ // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ /* morekeys_g */ "\u0123,\u011F",
+ /* single_angle_quotes ~ */
+ null, null, null,
+ /* ~ keyspec_currency */
+ // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+ // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+ // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
+ /* morekeys_r */ "\u0157,\u0159,\u0155",
+ // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
+ /* morekeys_k */ "\u0137",
+ };
+
+ /* Locale lv: Latvian */
+ private static final String[] TEXTS_lv = {
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ /* morekeys_a */ "\u0101,\u00E0,\u00E1,\u00E2,\u00E3,\u00E4,\u00E5,\u00E6,\u0105",
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+ // 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+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+ // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ /* morekeys_u */ "\u016B,\u0173,\u00F9,\u00FA,\u00FB,\u00FC,\u016F,\u0171",
+ // 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
+ // 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+0131: "ı" LATIN SMALL LETTER DOTLESS I
+ /* morekeys_i */ "\u012B,\u012F,\u00EC,\u00ED,\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+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",
+ /* keylabel_to_alpha */ null,
+ // 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",
+ // 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+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+ // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+ /* morekeys_t */ "\u0163,\u0165",
+ // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+ // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
+ /* morekeys_l */ "\u013C,\u0142,\u013A,\u013E",
+ // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ /* morekeys_g */ "\u0123,\u011F",
+ /* single_angle_quotes ~ */
+ null, null, null,
+ /* ~ keyspec_currency */
+ // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+ // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+ // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
+ /* morekeys_r */ "\u0157,\u0159,\u0155",
+ // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
+ /* morekeys_k */ "\u0137",
+ };
+
+ /* Locale mk: Macedonian */
+ private static final String[] TEXTS_mk = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_c */
+ /* double_quotes */ "!text/double_9qm_lqm",
+ /* morekeys_n */ null,
+ /* single_quotes */ "!text/single_9qm_lqm",
+ // 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_s ~ */
+ null, 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",
+ /* keyspec_nordic_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, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, 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",
+ // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE
+ /* keyspec_south_slavic_row1_6 */ "\u0455",
+ // U+045C: "ќ" CYRILLIC SMALL LETTER KJE
+ /* keyspec_south_slavic_row2_11 */ "\u045C",
+ // U+0437: "з" CYRILLIC SMALL LETTER ZE
+ /* keyspec_south_slavic_row3_1 */ "\u0437",
+ // U+0453: "ѓ" CYRILLIC SMALL LETTER GJE
+ /* keyspec_south_slavic_row3_8 */ "\u0453",
+ };
+
+ /* Locale mn_MN: Mongolian (Mongolia) */
+ private static final String[] TEXTS_mn_MN = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ double_angle_quotes */
+ // U+20AE: "₮" TUGRIK SIGN
+ /* keyspec_currency */ "\u20AE",
+ };
+
+ /* Locale my_MM: Burmese (Myanmar) */
+ private static final String[] TEXTS_my_MM = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // 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_s ~ */
+ 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,.,?,!,#,),(,/,;,...,',@,:,-,\",+,\\%,&",
+ /* keyspec_symbols_1 ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null,
+ /* ~ additional_morekeys_symbols_0 */
+ // U+104A: "၊" MYANMAR SIGN LITTLE SECTION
+ // U+104B: "။" MYANMAR SIGN SECTION
+ /* keyspec_tablet_comma */ "\u104A",
+ /* 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,
+ /* ~ keyspec_right_single_angle_quote */
+ /* 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, null,
+ /* ~ keyhintlabel_tablet_comma */
+ /* keyspec_period */ "\u104B",
+ /* morekeys_period */ null,
+ /* keyspec_tablet_period */ "\u104B",
+ };
+
+ /* Locale nb: Norwegian Bokmål */
+ private static final String[] TEXTS_nb = {
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // 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",
+ // 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",
+ // 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
+ // 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,
+ /* double_quotes */ "!text/double_9qm_rqm",
+ /* morekeys_n */ null,
+ /* single_quotes */ "!text/single_9qm_rqm",
+ /* keylabel_to_alpha ~ */
+ null, null, 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",
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ /* keyspec_nordic_row2_10 */ "\u00F8",
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ /* keyspec_nordic_row2_11 */ "\u00E6",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ /* morekeys_nordic_row2_10 */ "\u00F6",
+ /* keyspec_east_slavic_row1_9 ~ */
+ null, null, null, null, null,
+ /* ~ morekeys_cyrillic_soft_sign */
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ /* morekeys_nordic_row2_11 */ "\u00E4",
+ };
+
+ /* Locale ne_NP: Nepali (Nepal) */
+ private static final String[] TEXTS_ne_NP = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ double_angle_quotes */
+ // U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
+ /* keyspec_currency */ "\u0930\u0941.",
+ /* morekeys_r ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_punctuation */
+ // U+0967: "१" DEVANAGARI DIGIT ONE
+ /* keyspec_symbols_1 */ "\u0967",
+ // U+0968: "२" DEVANAGARI DIGIT TWO
+ /* keyspec_symbols_2 */ "\u0968",
+ // U+0969: "३" DEVANAGARI DIGIT THREE
+ /* keyspec_symbols_3 */ "\u0969",
+ // U+096A: "४" DEVANAGARI DIGIT FOUR
+ /* keyspec_symbols_4 */ "\u096A",
+ // U+096B: "५" DEVANAGARI DIGIT FIVE
+ /* keyspec_symbols_5 */ "\u096B",
+ // U+096C: "६" DEVANAGARI DIGIT SIX
+ /* keyspec_symbols_6 */ "\u096C",
+ // U+096D: "७" DEVANAGARI DIGIT SEVEN
+ /* keyspec_symbols_7 */ "\u096D",
+ // U+096E: "८" DEVANAGARI DIGIT EIGHT
+ /* keyspec_symbols_8 */ "\u096E",
+ // U+096F: "९" DEVANAGARI DIGIT NINE
+ /* keyspec_symbols_9 */ "\u096F",
+ // U+0966: "०" DEVANAGARI DIGIT ZERO
+ /* keyspec_symbols_0 */ "\u0966",
+ // Label for "switch to symbols" key.
+ /* keylabel_to_symbol */ "?\u0967\u0968\u0969",
+ /* additional_morekeys_symbols_1 */ "1",
+ /* additional_morekeys_symbols_2 */ "2",
+ /* additional_morekeys_symbols_3 */ "3",
+ /* additional_morekeys_symbols_4 */ "4",
+ /* additional_morekeys_symbols_5 */ "5",
+ /* additional_morekeys_symbols_6 */ "6",
+ /* additional_morekeys_symbols_7 */ "7",
+ /* additional_morekeys_symbols_8 */ "8",
+ /* additional_morekeys_symbols_9 */ "9",
+ /* additional_morekeys_symbols_0 */ "0",
+ };
+
+ /* Locale nl: Dutch */
+ private static final String[] TEXTS_nl = {
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ /* morekeys_a */ "\u00E1,\u00E4,\u00E2,\u00E0,\u00E6,\u00E3,\u00E5,\u0101",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // 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,\u00EB,\u00EA,\u00E8,\u0119,\u0117,\u0113",
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ // 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",
+ /* keylabel_to_alpha */ null,
+ /* morekeys_s */ null,
+ // U+0133: "ij" LATIN SMALL LIGATURE IJ
+ /* morekeys_y */ "\u0133",
+ };
+
+ /* Locale pl: Polish */
+ private static final String[] TEXTS_pl = {
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ /* morekeys_a */ "\u0105,\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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,
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // 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+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,
+ // 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",
+ /* keylabel_to_alpha */ null,
+ // 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",
+ /* morekeys_y */ null,
+ /* morekeys_d */ 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_t */ null,
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ /* morekeys_l */ "\u0142",
+ };
+
+ /* Locale pt: Portuguese */
+ private static final String[] TEXTS_pt = {
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* morekeys_a */ "\u00E1,\u00E3,\u00E0,\u00E2,\u00E4,\u00E5,\u00E6,\u00AA",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // 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",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // 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
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ /* morekeys_e */ "\u00E9,\u00EA,\u00E8,\u0119,\u0117,\u0113,\u00EB",
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+ /* morekeys_i */ "\u00ED,\u00EE,\u00EC,\u00EF,\u012F,\u012B",
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ /* morekeys_c */ "\u00E7,\u010D,\u0107",
+ };
+
+ /* Locale rm: Raeto-Romance */
+ private static final String[] TEXTS_rm = {
+ /* morekeys_a */ null,
+ // 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+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ /* morekeys_o */ "\u00F2,\u00F3,\u00F6,\u00F4,\u00F5,\u0153,\u00F8",
+ };
+
+ /* Locale ro: Romanian */
+ private static final String[] TEXTS_ro = {
+ // 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_o ~ */
+ null, null, null,
+ /* ~ morekeys_e */
+ // 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 */ "\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ /* morekeys_c */ null,
+ /* double_quotes */ "!text/double_9qm_rqm",
+ /* morekeys_n */ null,
+ /* single_quotes */ "!text/single_9qm_rqm",
+ /* keylabel_to_alpha */ null,
+ // 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",
+ /* morekeys_y ~ */
+ null, null, null,
+ /* ~ morekeys_z */
+ // U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW
+ /* morekeys_t */ "\u021B",
+ };
+
+ /* Locale ru: Russian */
+ private static final String[] TEXTS_ru = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_c */
+ /* double_quotes */ "!text/double_9qm_lqm",
+ /* morekeys_n */ null,
+ /* single_quotes */ "!text/single_9qm_lqm",
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_k */
+ // U+0451: "ё" CYRILLIC SMALL LETTER IO
+ /* morekeys_cyrillic_ie */ "\u0451",
+ /* keyspec_nordic_row1_11 ~ */
+ null, null, null, null,
+ /* ~ morekeys_nordic_row2_10 */
+ // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
+ /* keyspec_east_slavic_row1_9 */ "\u0449",
+ // U+044B: "ы" CYRILLIC SMALL LETTER YERU
+ /* keyspec_east_slavic_row2_2 */ "\u044B",
+ // U+044D: "э" CYRILLIC SMALL LETTER E
+ /* keyspec_east_slavic_row2_11 */ "\u044D",
+ // U+0438: "и" CYRILLIC SMALL LETTER I
+ /* keyspec_east_slavic_row3_5 */ "\u0438",
+ // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
+ /* morekeys_cyrillic_soft_sign */ "\u044A",
+ };
+
+ /* Locale sk: Slovak */
+ private static final String[] TEXTS_sk = {
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // 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+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ /* morekeys_a */ "\u00E1,\u00E4,\u0101,\u00E0,\u00E2,\u00E3,\u00E5,\u00E6,\u0105",
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ /* morekeys_u */ "\u00FA,\u016F,\u00FC,\u016B,\u0173,\u00F9,\u00FB,\u0171",
+ // 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
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // 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",
+ /* keylabel_to_alpha */ null,
+ // 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",
+ // 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+0165: "ť" LATIN SMALL LETTER T WITH CARON
+ // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+ /* morekeys_t */ "\u0165,\u0163",
+ // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
+ // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+ // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ /* morekeys_l */ "\u013E,\u013A,\u013C,\u0142",
+ // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ /* morekeys_g */ "\u0123,\u011F",
+ /* single_angle_quotes */ "!text/single_raqm_laqm",
+ /* double_angle_quotes */ "!text/double_raqm_laqm",
+ /* keyspec_currency */ null,
+ // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
+ // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+ // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+ /* morekeys_r */ "\u0155,\u0159,\u0157",
+ // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
+ /* morekeys_k */ "\u0137",
+ };
+
+ /* Locale sl: Slovenian */
+ private static final String[] TEXTS_sl = {
+ /* morekeys_a ~ */
+ null, null, null, null, null,
+ /* ~ morekeys_i */
+ // 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",
+ /* keylabel_to_alpha */ null,
+ // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+ /* morekeys_s */ "\u0161",
+ /* 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",
+ /* morekeys_t ~ */
+ null, null, null,
+ /* ~ morekeys_g */
+ /* single_angle_quotes */ "!text/single_raqm_laqm",
+ /* double_angle_quotes */ "!text/double_raqm_laqm",
+ };
+
+ /* Locale sr: Serbian */
+ private static final String[] TEXTS_sr = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_c */
+ /* double_quotes */ "!text/double_9qm_lqm",
+ /* morekeys_n */ null,
+ /* single_quotes */ "!text/single_9qm_lqm",
+ // END: More keys definitions for Serbian (Cyrillic)
+ // 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_s ~ */
+ null, null, null, null, null, null, null,
+ /* ~ morekeys_g */
+ /* single_angle_quotes */ "!text/single_raqm_laqm",
+ /* double_angle_quotes */ "!text/double_raqm_laqm",
+ /* keyspec_currency ~ */
+ null, null, null,
+ /* ~ morekeys_k */
+ // U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE
+ /* morekeys_cyrillic_ie */ "\u0450",
+ /* keyspec_nordic_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, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, 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",
+ // TODO: Move these to sr-Latn once we can handle IETF language tag with script name specified.
+ // BEGIN: More keys definitions for Serbian (Latin)
+ // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+ // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+ // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+ // <string name="morekeys_s">&#x0161;,&#x00DF;,&#x015B;</string>
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ // <string name="morekeys_c">&#x010D;,&#x00E7;,&#x0107;</string>
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ // <string name="morekeys_d">&#x010F;</string>
+ // 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
+ // <string name="morekeys_z">&#x017E;,&#x017A;,&#x017C;</string>
+ // END: More keys definitions for Serbian (Latin)
+ // BEGIN: More keys definitions for Serbian (Cyrillic)
+ // U+0437: "з" CYRILLIC SMALL LETTER ZE
+ /* keyspec_south_slavic_row1_6 */ "\u0437",
+ // U+045B: "ћ" CYRILLIC SMALL LETTER TSHE
+ /* keyspec_south_slavic_row2_11 */ "\u045B",
+ // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE
+ /* keyspec_south_slavic_row3_1 */ "\u0455",
+ // U+0452: "ђ" CYRILLIC SMALL LETTER DJE
+ /* keyspec_south_slavic_row3_8 */ "\u0452",
+ };
+
+ /* Locale sv: Swedish */
+ private static final String[] TEXTS_sv = {
+ // 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",
+ // 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",
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // 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 */ "\u00FC,\u00FA,\u00F9,\u00FB,\u016B",
+ // 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+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,
+ /* keylabel_to_alpha */ 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",
+ // 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+0165: "ť" LATIN SMALL LETTER T WITH CARON
+ // U+00FE: "þ" LATIN SMALL LETTER THORN
+ /* morekeys_t */ "\u0165,\u00FE",
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ /* morekeys_l */ "\u0142",
+ /* morekeys_g */ null,
+ /* single_angle_quotes */ "!text/single_raqm_laqm",
+ /* double_angle_quotes */ "!text/double_raqm_laqm",
+ /* keyspec_currency */ null,
+ // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+ /* morekeys_r */ "\u0159",
+ /* morekeys_k */ null,
+ /* morekeys_cyrillic_ie */ null,
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ /* keyspec_nordic_row1_11 */ "\u00E5",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ /* keyspec_nordic_row2_10 */ "\u00F6",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ /* keyspec_nordic_row2_11 */ "\u00E4",
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ /* morekeys_nordic_row2_10 */ "\u00F8,\u0153",
+ /* keyspec_east_slavic_row1_9 ~ */
+ null, null, null, null, null,
+ /* ~ morekeys_cyrillic_soft_sign */
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ /* morekeys_nordic_row2_11 */ "\u00E6",
+ };
+
+ /* Locale sw: Swahili */
+ private static final String[] TEXTS_sw = {
+ // This is the same as English except morekeys_g.
+ // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // 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+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // 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 */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B",
+ // 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+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,
+ /* keylabel_to_alpha */ null,
+ // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+ /* morekeys_s */ "\u00DF",
+ /* morekeys_y ~ */
+ null, null, null, null, null,
+ /* ~ morekeys_l */
+ /* morekeys_g */ "g\'",
+ };
+
+ /* Locale th: Thai */
+ private static final String[] TEXTS_th = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ double_angle_quotes */
+ // U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
+ /* keyspec_currency */ "\u0E3F",
+ };
+
+ /* Locale tl: Tagalog */
+ private static final String[] TEXTS_tl = {
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* morekeys_a */ "\u00E1,\u00E0,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // 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",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // 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,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // 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+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+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+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",
+ /* 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
+ // 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+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,
+ /* ~ keylabel_to_alpha */
+ // 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",
+ /* morekeys_y ~ */
+ null, null, null, null, null,
+ /* ~ morekeys_l */
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ /* morekeys_g */ "\u011F",
+ };
+
+ /* Locale uk: Ukrainian */
+ private static final String[] TEXTS_uk = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_c */
+ /* double_quotes */ "!text/double_9qm_lqm",
+ /* morekeys_n */ null,
+ /* single_quotes */ "!text/single_9qm_lqm",
+ // 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_s ~ */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ double_angle_quotes */
+ // U+20B4: "₴" HRYVNIA SIGN
+ /* keyspec_currency */ "\u20B4",
+ /* morekeys_r ~ */
+ null, null, null, null, null, null, null,
+ /* ~ morekeys_nordic_row2_10 */
+ // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
+ /* keyspec_east_slavic_row1_9 */ "\u0449",
+ // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+ /* keyspec_east_slavic_row2_2 */ "\u0456",
+ // U+0454: "є" CYRILLIC SMALL LETTER UKRAINIAN IE
+ /* keyspec_east_slavic_row2_11 */ "\u0454",
+ // U+0438: "и" CYRILLIC SMALL LETTER I
+ /* keyspec_east_slavic_row3_5 */ "\u0438",
+ // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
+ /* morekeys_cyrillic_soft_sign */ "\u044A",
+ /* morekeys_nordic_row2_11 ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, 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",
+ /* morekeys_cyrillic_u */ null,
+ /* morekeys_cyrillic_en */ null,
+ // U+0491: "ґ" CYRILLIC SMALL LETTER GHE WITH UPTURN
+ /* morekeys_cyrillic_ghe */ "\u0491",
+ };
+
+ /* Locale vi: Vietnamese */
+ private static final String[] TEXTS_vi = {
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+1EA3: "ả" LATIN SMALL LETTER A WITH HOOK ABOVE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+1EA1: "ạ" LATIN SMALL LETTER A WITH DOT BELOW
+ // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
+ // U+1EB1: "ằ" LATIN SMALL LETTER A WITH BREVE AND GRAVE
+ // U+1EAF: "ắ" LATIN SMALL LETTER A WITH BREVE AND ACUTE
+ // U+1EB3: "ẳ" LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE
+ // U+1EB5: "ẵ" LATIN SMALL LETTER A WITH BREVE AND TILDE
+ // U+1EB7: "ặ" LATIN SMALL LETTER A WITH BREVE AND DOT BELOW
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+1EA7: "ầ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE
+ // U+1EA5: "ấ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
+ // U+1EA9: "ẩ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+ // U+1EAB: "ẫ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE
+ // U+1EAD: "ậ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+ /* morekeys_a */ "\u00E0,\u00E1,\u1EA3,\u00E3,\u1EA1,\u0103,\u1EB1,\u1EAF,\u1EB3,\u1EB5,\u1EB7,\u00E2,\u1EA7,\u1EA5,\u1EA9,\u1EAB,\u1EAD",
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+1ECF: "ỏ" LATIN SMALL LETTER O WITH HOOK ABOVE
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+1ECD: "ọ" LATIN SMALL LETTER O WITH DOT BELOW
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+1ED3: "ồ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE
+ // U+1ED1: "ố" LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE
+ // U+1ED5: "ổ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+ // U+1ED7: "ỗ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE
+ // U+1ED9: "ộ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+ // U+01A1: "ơ" LATIN SMALL LETTER O WITH HORN
+ // U+1EDD: "ờ" LATIN SMALL LETTER O WITH HORN AND GRAVE
+ // U+1EDB: "ớ" LATIN SMALL LETTER O WITH HORN AND ACUTE
+ // U+1EDF: "ở" LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE
+ // 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+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+1EE7: "ủ" LATIN SMALL LETTER U WITH HOOK ABOVE
+ // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE
+ // U+1EE5: "ụ" LATIN SMALL LETTER U WITH DOT BELOW
+ // U+01B0: "ư" LATIN SMALL LETTER U WITH HORN
+ // U+1EEB: "ừ" LATIN SMALL LETTER U WITH HORN AND GRAVE
+ // U+1EE9: "ứ" LATIN SMALL LETTER U WITH HORN AND ACUTE
+ // U+1EED: "ử" LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE
+ // U+1EEF: "ữ" LATIN SMALL LETTER U WITH HORN AND TILDE
+ // U+1EF1: "ự" LATIN SMALL LETTER U WITH HORN AND DOT BELOW
+ /* morekeys_u */ "\u00F9,\u00FA,\u1EE7,\u0169,\u1EE5,\u01B0,\u1EEB,\u1EE9,\u1EED,\u1EEF,\u1EF1",
+ // 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 ~ */
+ null, null, null, null, null, null,
+ /* ~ morekeys_s */
+ // U+1EF3: "ỳ" LATIN SMALL LETTER Y WITH GRAVE
+ // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+ // U+1EF7: "ỷ" LATIN SMALL LETTER Y WITH HOOK ABOVE
+ // U+1EF9: "ỹ" LATIN SMALL LETTER Y WITH TILDE
+ // U+1EF5: "ỵ" LATIN SMALL LETTER Y WITH DOT BELOW
+ /* morekeys_y */ "\u1EF3,\u00FD,\u1EF7,\u1EF9,\u1EF5",
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ /* morekeys_d */ "\u0111",
+ /* morekeys_z ~ */
+ null, null, null, null, null, null,
+ /* ~ double_angle_quotes */
+ // U+20AB: "₫" DONG SIGN
+ /* keyspec_currency */ "\u20AB",
+ };
+
+ /* Locale zu: Zulu */
+ private static final String[] TEXTS_zu = {
+ // This is the same as English
+ // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // 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+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // 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 */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B",
+ // 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+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,
+ /* keylabel_to_alpha */ null,
+ // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+ /* morekeys_s */ "\u00DF",
+ };
+
+ /* Locale zz: Alphabet */
+ private static final String[] TEXTS_zz = {
+ // 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+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E3,\u00E4,\u00E5,\u00E6,\u0101,\u0103,\u0105,\u00AA",
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+ // U+014F: "ŏ" LATIN SMALL LETTER O WITH BREVE
+ // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
+ // 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+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+0169: "ũ" LATIN SMALL LETTER U WITH TILDE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
+ // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+ // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+ /* morekeys_u */ "\u00F9,\u00FA,\u00FB,\u00FC,\u0169,\u016B,\u016D,\u016F,\u0171,\u0173",
+ // 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
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE
+ // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+ // U+012D: "ĭ" LATIN SMALL LETTER I WITH BREVE
+ // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ // 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
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // 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,
+ /* keylabel_to_alpha */ null,
+ // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+ // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+ // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
+ // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+ // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+ // U+017F: "ſ" LATIN SMALL LETTER LONG S
+ /* morekeys_s */ "\u00DF,\u015B,\u015D,\u015F,\u0161,\u017F",
+ // 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+00FE: "þ" LATIN SMALL LETTER THORN
+ // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+ // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+ // U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE
+ /* morekeys_t */ "\u00FE,\u0163,\u0165,\u0167",
+ // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+ // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+ // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
+ // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ /* morekeys_l */ "\u013A,\u013C,\u013E,\u0140,\u0142",
+ // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ // U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE
+ // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+ /* morekeys_g */ "\u011D,\u011F,\u0121,\u0123",
+ /* single_angle_quotes ~ */
+ null, null, null,
+ /* ~ keyspec_currency */
+ // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
+ // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+ // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+ /* morekeys_r */ "\u0155,\u0157,\u0159",
+ // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
+ // U+0138: "ĸ" LATIN SMALL LETTER KRA
+ /* morekeys_k */ "\u0137,\u0138",
+ /* morekeys_cyrillic_ie ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, 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",
+ // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
+ /* 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,
+ /* ~ morekeys_v */
+ // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
+ /* morekeys_j */ "\u0135",
+ };
+
+ private static final Object[] LOCALES_AND_TEXTS = {
+ // "locale", TEXT_ARRAY, /* numberOfNonNullText/lengthOf_TEXT_ARRAY localeName */
+ "DEFAULT", TEXTS_DEFAULT, /* 168/168 DEFAULT */
+ "af" , TEXTS_af, /* 7/ 12 Afrikaans */
+ "ar" , TEXTS_ar, /* 55/110 Arabic */
+ "az_AZ" , TEXTS_az_AZ, /* 8/ 17 Azerbaijani (Azerbaijan) */
+ "be_BY" , TEXTS_be_BY, /* 9/ 32 Belarusian (Belarus) */
+ "bg" , TEXTS_bg, /* 2/ 10 Bulgarian */
+ "ca" , TEXTS_ca, /* 11/ 95 Catalan */
+ "cs" , TEXTS_cs, /* 17/ 21 Czech */
+ "da" , TEXTS_da, /* 19/ 33 Danish */
+ "de" , TEXTS_de, /* 16/ 62 German */
+ "el" , TEXTS_el, /* 1/ 10 Greek */
+ "en" , TEXTS_en, /* 8/ 11 English */
+ "eo" , TEXTS_eo, /* 26/118 Esperanto */
+ "es" , TEXTS_es, /* 8/ 34 Spanish */
+ "et_EE" , TEXTS_et_EE, /* 22/ 27 Estonian (Estonia) */
+ "eu_ES" , TEXTS_eu_ES, /* 7/ 8 Basque (Spain) */
+ "fa" , TEXTS_fa, /* 58/125 Persian */
+ "fi" , TEXTS_fi, /* 10/ 33 Finnish */
+ "fr" , TEXTS_fr, /* 13/ 62 French */
+ "gl_ES" , TEXTS_gl_ES, /* 7/ 8 Gallegan (Spain) */
+ "hi" , TEXTS_hi, /* 23/ 55 Hindi */
+ "hr" , TEXTS_hr, /* 9/ 19 Croatian */
+ "hu" , TEXTS_hu, /* 9/ 19 Hungarian */
+ "hy_AM" , TEXTS_hy_AM, /* 8/126 Armenian (Armenia) */
+ "is" , TEXTS_is, /* 10/ 15 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) */
+ "ky" , TEXTS_ky, /* 10/ 88 Kirghiz */
+ "lo_LA" , TEXTS_lo_LA, /* 2/ 20 Lao (Laos) */
+ "lt" , TEXTS_lt, /* 18/ 22 Lithuanian */
+ "lv" , TEXTS_lv, /* 18/ 22 Latvian */
+ "mk" , TEXTS_mk, /* 9/ 93 Macedonian */
+ "mn_MN" , TEXTS_mn_MN, /* 2/ 20 Mongolian (Mongolia) */
+ "my_MM" , TEXTS_my_MM, /* 8/104 Burmese (Myanmar) */
+ "nb" , TEXTS_nb, /* 11/ 33 Norwegian Bokmål */
+ "ne_NP" , TEXTS_ne_NP, /* 23/ 55 Nepali (Nepal) */
+ "nl" , TEXTS_nl, /* 9/ 12 Dutch */
+ "pl" , TEXTS_pl, /* 10/ 16 Polish */
+ "pt" , TEXTS_pt, /* 6/ 6 Portuguese */
+ "rm" , TEXTS_rm, /* 1/ 2 Raeto-Romance */
+ "ro" , TEXTS_ro, /* 6/ 15 Romanian */
+ "ru" , TEXTS_ru, /* 9/ 32 Russian */
+ "sk" , TEXTS_sk, /* 20/ 22 Slovak */
+ "sl" , TEXTS_sl, /* 8/ 19 Slovenian */
+ "sr" , TEXTS_sr, /* 11/ 93 Serbian */
+ "sv" , TEXTS_sv, /* 21/ 33 Swedish */
+ "sw" , TEXTS_sw, /* 9/ 17 Swahili */
+ "th" , TEXTS_th, /* 2/ 20 Thai */
+ "tl" , TEXTS_tl, /* 7/ 8 Tagalog */
+ "tr" , TEXTS_tr, /* 7/ 17 Turkish */
+ "uk" , TEXTS_uk, /* 11/ 87 Ukrainian */
+ "vi" , TEXTS_vi, /* 8/ 20 Vietnamese */
+ "zu" , TEXTS_zu, /* 8/ 11 Zulu */
+ "zz" , TEXTS_zz, /* 19/112 Alphabet */
+ };
+
+ static {
+ for (int index = 0; index < NAMES.length; index++) {
+ sNameToIndexesMap.put(NAMES[index], index);
+ }
+
+ for (int i = 0; i < LOCALES_AND_TEXTS.length; i += 2) {
+ final String locale = (String)LOCALES_AND_TEXTS[i];
+ final String[] textsTable = (String[])LOCALES_AND_TEXTS[i + 1];
+ sLocaleToTextsTableMap.put(locale, textsTable);
+ sTextsTableToLocaleMap.put(textsTable, locale);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java b/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java
new file mode 100644
index 000000000..6400a2440
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java
@@ -0,0 +1,68 @@
+/*
+ * 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.keyboard.internal;
+
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class determines that the language name on the spacebar should be displayed in what format.
+ */
+public final class LanguageOnSpacebarHelper {
+ public static final int FORMAT_TYPE_NONE = 0;
+ public static final int FORMAT_TYPE_LANGUAGE_ONLY = 1;
+ public static final int FORMAT_TYPE_FULL_LOCALE = 2;
+
+ private List<InputMethodSubtype> mEnabledSubtypes = Collections.emptyList();
+ private boolean mIsSystemLanguageSameAsInputLanguage;
+
+ public int getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype) {
+ if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
+ return FORMAT_TYPE_FULL_LOCALE;
+ }
+ // Only this subtype is enabled and equals to the system locale.
+ if (mEnabledSubtypes.size() < 2 && mIsSystemLanguageSameAsInputLanguage) {
+ return FORMAT_TYPE_NONE;
+ }
+ final String keyboardLanguage = SubtypeLocaleUtils.getSubtypeLocale(subtype).getLanguage();
+ final String keyboardLayout = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
+ int sameLanguageAndLayoutCount = 0;
+ for (final InputMethodSubtype ims : mEnabledSubtypes) {
+ final String language = SubtypeLocaleUtils.getSubtypeLocale(ims).getLanguage();
+ if (keyboardLanguage.equals(language) && keyboardLayout.equals(
+ SubtypeLocaleUtils.getKeyboardLayoutSetName(ims))) {
+ sameLanguageAndLayoutCount++;
+ }
+ }
+ // Display full locale name only when there are multiple subtypes that have the same
+ // locale and keyboard layout. Otherwise displaying language name is enough.
+ return sameLanguageAndLayoutCount > 1 ? FORMAT_TYPE_FULL_LOCALE
+ : FORMAT_TYPE_LANGUAGE_ONLY;
+ }
+
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
+ mEnabledSubtypes = enabledSubtypes;
+ }
+
+ public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
+ mIsSystemLanguageSameAsInputLanguage = isSame;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
index 110936f8f..56ef4767f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -18,23 +18,42 @@ package com.android.inputmethod.keyboard.internal;
import android.text.TextUtils;
+import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Locale;
+/**
+ * The more key specification object. The more keys are an array of {@link MoreKeySpec}.
+ *
+ * The more keys specification is comma separated "key specification" each of which represents one
+ * "more key".
+ * The key specification might have label or string resource reference in it. These references are
+ * expanded before parsing comma.
+ * Special character, comma ',' backslash '\' can be escaped by '\' character.
+ * Note that the '\' is also parsed by XML parser and {@link MoreKeySpec#splitKeySpecs(String)}
+ * as well.
+ */
+// TODO: Should extend the key specification object.
public final class MoreKeySpec {
public final int mCode;
public final String mLabel;
public final String mOutputText;
public final int mIconId;
- public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, final Locale locale,
- final KeyboardCodesSet codesSet) {
- mLabel = KeySpecParser.toUpperCaseOfStringForLocale(
+ public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, final Locale locale) {
+ if (TextUtils.isEmpty(moreKeySpec)) {
+ throw new KeySpecParser.KeySpecParserError("Empty more key spec");
+ }
+ mLabel = StringUtils.toUpperCaseOfStringForLocale(
KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale);
- final int code = KeySpecParser.toUpperCaseOfCodeForLocale(
- KeySpecParser.getCode(moreKeySpec, codesSet), needsToUpperCase, locale);
+ final int code = StringUtils.toUpperCaseOfCodeForLocale(
+ KeySpecParser.getCode(moreKeySpec), needsToUpperCase, locale);
if (code == Constants.CODE_UNSPECIFIED) {
// Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
// upper case representation ("SS").
@@ -42,12 +61,19 @@ public final class MoreKeySpec {
mOutputText = mLabel;
} else {
mCode = code;
- mOutputText = KeySpecParser.toUpperCaseOfStringForLocale(
+ mOutputText = StringUtils.toUpperCaseOfStringForLocale(
KeySpecParser.getOutputText(moreKeySpec), needsToUpperCase, locale);
}
mIconId = KeySpecParser.getIconId(moreKeySpec);
}
+ public Key buildKey(final int x, final int y, final int labelFlags,
+ final KeyboardParams params) {
+ return new Key(mLabel, mIconId, mCode, mOutputText, null /* hintLabel */, labelFlags,
+ Key.BACKGROUND_TYPE_NORMAL, x, y, params.mDefaultKeyWidth, params.mDefaultRowHeight,
+ params.mHorizontalGap, params.mVerticalGap);
+ }
+
@Override
public int hashCode() {
int hashCode = 1;
@@ -74,7 +100,7 @@ public final class MoreKeySpec {
@Override
public String toString() {
final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel
- : KeySpecParser.PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId));
+ : KeyboardIconsSet.PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId));
final String output = (mCode == Constants.CODE_OUTPUT_TEXT ? mOutputText
: Constants.printableCode(mCode));
if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
@@ -83,4 +109,196 @@ public final class MoreKeySpec {
return label + "|" + output;
}
}
+
+ private static final boolean DEBUG = LatinImeLogger.sDBG;
+ // Constants for parsing.
+ private static final char COMMA = Constants.CODE_COMMA;
+ private static final char BACKSLASH = Constants.CODE_BACKSLASH;
+ private static final String ADDITIONAL_MORE_KEY_MARKER =
+ StringUtils.newSingleCodePointString(Constants.CODE_PERCENT);
+
+ /**
+ * Split the text containing multiple key specifications separated by commas into an array of
+ * key specifications.
+ * A key specification can contain a character escaped by the backslash character, including a
+ * comma character.
+ * Note that an empty key specification will be eliminated from the result array.
+ *
+ * @param text the text containing multiple key specifications.
+ * @return an array of key specification text. Null if the specified <code>text</code> is empty
+ * or has no key specifications.
+ */
+ public static String[] splitKeySpecs(final String text) {
+ if (TextUtils.isEmpty(text)) {
+ return null;
+ }
+ final int size = text.length();
+ // Optimization for one-letter key specification.
+ if (size == 1) {
+ return text.charAt(0) == COMMA ? null : new String[] { text };
+ }
+
+ ArrayList<String> list = null;
+ int start = 0;
+ // The characters in question in this loop are COMMA and BACKSLASH. These characters never
+ // match any high or low surrogate character. So it is OK to iterate through with char
+ // index.
+ for (int pos = 0; pos < size; pos++) {
+ final char c = text.charAt(pos);
+ if (c == COMMA) {
+ // Skip empty entry.
+ if (pos - start > 0) {
+ if (list == null) {
+ list = CollectionUtils.newArrayList();
+ }
+ list.add(text.substring(start, pos));
+ }
+ // Skip comma
+ start = pos + 1;
+ } else if (c == BACKSLASH) {
+ // Skip escape character and escaped character.
+ pos++;
+ }
+ }
+ final String remain = (size - start > 0) ? text.substring(start) : null;
+ if (list == null) {
+ return remain != null ? new String[] { remain } : null;
+ }
+ if (remain != null) {
+ list.add(remain);
+ }
+ return list.toArray(new String[list.size()]);
+ }
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ private static String[] filterOutEmptyString(final String[] array) {
+ if (array == null) {
+ return EMPTY_STRING_ARRAY;
+ }
+ ArrayList<String> out = null;
+ for (int i = 0; i < array.length; i++) {
+ final String entry = array[i];
+ if (TextUtils.isEmpty(entry)) {
+ if (out == null) {
+ out = CollectionUtils.arrayAsList(array, 0, i);
+ }
+ } else if (out != null) {
+ out.add(entry);
+ }
+ }
+ if (out == null) {
+ return array;
+ }
+ return out.toArray(new String[out.size()]);
+ }
+
+ public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs,
+ final String[] additionalMoreKeySpecs) {
+ final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
+ final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
+ final int moreKeysCount = moreKeys.length;
+ final int additionalCount = additionalMoreKeys.length;
+ ArrayList<String> out = null;
+ int additionalIndex = 0;
+ for (int moreKeyIndex = 0; moreKeyIndex < moreKeysCount; moreKeyIndex++) {
+ final String moreKeySpec = moreKeys[moreKeyIndex];
+ if (moreKeySpec.equals(ADDITIONAL_MORE_KEY_MARKER)) {
+ if (additionalIndex < additionalCount) {
+ // Replace '%' marker with additional more key specification.
+ final String additionalMoreKey = additionalMoreKeys[additionalIndex];
+ if (out != null) {
+ out.add(additionalMoreKey);
+ } else {
+ moreKeys[moreKeyIndex] = additionalMoreKey;
+ }
+ additionalIndex++;
+ } else {
+ // Filter out excessive '%' marker.
+ if (out == null) {
+ out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeyIndex);
+ }
+ }
+ } else {
+ if (out != null) {
+ out.add(moreKeySpec);
+ }
+ }
+ }
+ if (additionalCount > 0 && additionalIndex == 0) {
+ // No '%' marker is found in more keys.
+ // Insert all additional more keys to the head of more keys.
+ if (DEBUG && out != null) {
+ throw new RuntimeException("Internal logic error:"
+ + " moreKeys=" + Arrays.toString(moreKeys)
+ + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
+ }
+ out = CollectionUtils.arrayAsList(additionalMoreKeys, additionalIndex, additionalCount);
+ for (int i = 0; i < moreKeysCount; i++) {
+ out.add(moreKeys[i]);
+ }
+ } else if (additionalIndex < additionalCount) {
+ // The number of '%' markers are less than additional more keys.
+ // Append remained additional more keys to the tail of more keys.
+ if (DEBUG && out != null) {
+ throw new RuntimeException("Internal logic error:"
+ + " moreKeys=" + Arrays.toString(moreKeys)
+ + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
+ }
+ out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeysCount);
+ for (int i = additionalIndex; i < additionalCount; i++) {
+ out.add(additionalMoreKeys[additionalIndex]);
+ }
+ }
+ if (out == null && moreKeysCount > 0) {
+ return moreKeys;
+ } else if (out != null && out.size() > 0) {
+ return out.toArray(new String[out.size()]);
+ } else {
+ return null;
+ }
+ }
+
+ public static int getIntValue(final String[] moreKeys, final String key,
+ final int defaultValue) {
+ if (moreKeys == null) {
+ return defaultValue;
+ }
+ final int keyLen = key.length();
+ boolean foundValue = false;
+ int value = defaultValue;
+ for (int i = 0; i < moreKeys.length; i++) {
+ final String moreKeySpec = moreKeys[i];
+ if (moreKeySpec == null || !moreKeySpec.startsWith(key)) {
+ continue;
+ }
+ moreKeys[i] = null;
+ try {
+ if (!foundValue) {
+ value = Integer.parseInt(moreKeySpec.substring(keyLen));
+ foundValue = true;
+ }
+ } catch (NumberFormatException e) {
+ throw new RuntimeException(
+ "integer should follow after " + key + ": " + moreKeySpec);
+ }
+ }
+ return value;
+ }
+
+ public static boolean getBooleanValue(final String[] moreKeys, final String key) {
+ if (moreKeys == null) {
+ return false;
+ }
+ boolean value = false;
+ for (int i = 0; i < moreKeys.length; i++) {
+ final String moreKeySpec = moreKeys[i];
+ if (moreKeySpec == null || !moreKeySpec.equals(key)) {
+ continue;
+ }
+ moreKeys[i] = null;
+ value = true;
+ }
+ return value;
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
index a0935b985..3a9aa81a3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
@@ -20,18 +20,19 @@ import android.util.Log;
import android.view.MotionEvent;
import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler;
import com.android.inputmethod.latin.utils.CoordinateUtils;
public final class NonDistinctMultitouchHelper {
private static final String TAG = NonDistinctMultitouchHelper.class.getSimpleName();
+ private static final int MAIN_POINTER_TRACKER_ID = 0;
private int mOldPointerCount = 1;
private Key mOldKey;
private int[] mLastCoords = CoordinateUtils.newInstance();
- public void processMotionEvent(final MotionEvent me, final KeyEventHandler keyEventHandler) {
+ public void processMotionEvent(final MotionEvent me, final KeyDetector keyDetector) {
final int pointerCount = me.getPointerCount();
final int oldPointerCount = mOldPointerCount;
mOldPointerCount = pointerCount;
@@ -41,8 +42,9 @@ public final class NonDistinctMultitouchHelper {
return;
}
- // Use only main (id=0) pointer tracker.
- final PointerTracker mainTracker = PointerTracker.getPointerTracker(0, keyEventHandler);
+ // Use only main pointer tracker.
+ final PointerTracker mainTracker = PointerTracker.getPointerTracker(
+ MAIN_POINTER_TRACKER_ID);
final int action = me.getActionMasked();
final int index = me.getActionIndex();
final long eventTime = me.getEventTime();
@@ -51,12 +53,12 @@ public final class NonDistinctMultitouchHelper {
// In single-touch.
if (oldPointerCount == 1 && pointerCount == 1) {
if (me.getPointerId(index) == mainTracker.mPointerId) {
- mainTracker.processMotionEvent(me, keyEventHandler);
+ mainTracker.processMotionEvent(me, keyDetector);
return;
}
// Inject a copied event.
injectMotionEvent(action, me.getX(index), me.getY(index), downTime, eventTime,
- mainTracker, keyEventHandler);
+ mainTracker, keyDetector);
return;
}
@@ -70,7 +72,7 @@ public final class NonDistinctMultitouchHelper {
mOldKey = mainTracker.getKeyOn(x, y);
// Inject an artifact up event for the old key.
injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime,
- mainTracker, keyEventHandler);
+ mainTracker, keyDetector);
return;
}
@@ -85,11 +87,11 @@ public final class NonDistinctMultitouchHelper {
// Inject an artifact down event for the new key.
// An artifact up event for the new key will usually be injected as a single-touch.
injectMotionEvent(MotionEvent.ACTION_DOWN, x, y, downTime, eventTime,
- mainTracker, keyEventHandler);
+ mainTracker, keyDetector);
if (action == MotionEvent.ACTION_UP) {
// Inject an artifact up event for the new key also.
injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime,
- mainTracker, keyEventHandler);
+ mainTracker, keyDetector);
}
}
return;
@@ -101,11 +103,11 @@ public final class NonDistinctMultitouchHelper {
private static void injectMotionEvent(final int action, final float x, final float y,
final long downTime, final long eventTime, final PointerTracker tracker,
- final KeyEventHandler handler) {
+ final KeyDetector keyDetector) {
final MotionEvent me = MotionEvent.obtain(
downTime, eventTime, action, x, y, 0 /* metaState */);
try {
- tracker.processMotionEvent(me, handler);
+ tracker.processMotionEvent(me, keyDetector);
} finally {
me.recycle();
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 7ee45e8f6..5ac34188c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -28,7 +28,7 @@ public final class PointerTrackerQueue {
public interface Element {
public boolean isModifier();
- public boolean isInSlidingKeyInput();
+ public boolean isInDraggingFinger();
public void onPhantomUpEvent(long eventTime);
public void cancelTrackingForAction();
}
@@ -193,13 +193,13 @@ public final class PointerTrackerQueue {
}
}
- public boolean isAnyInSlidingKeyInput() {
+ public boolean isAnyInDraggingFinger() {
synchronized (mExpandableArrayOfActivePointers) {
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
final int arraySize = mArraySize;
for (int index = 0; index < arraySize; index++) {
final Element element = expandableArray.get(index);
- if (element.isInSlidingKeyInput()) {
+ if (element.isInDraggingFinger()) {
return true;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java b/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java
deleted file mode 100644
index d1ccdc7b5..000000000
--- a/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.keyboard.internal;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ScrollView;
-
-/**
- * This is an extended {@link ScrollView} that can notify
- * {@link ScrollView#onScrollChanged(int,int,int,int} and
- * {@link ScrollView#onOverScrolled(int,int,int,int)} to a content view.
- */
-public class ScrollViewWithNotifier extends ScrollView {
- private ScrollListener mScrollListener = EMPTY_LISTER;
-
- public interface ScrollListener {
- public void notifyScrollChanged(int scrollX, int scrollY, int oldX, int oldY);
- public void notifyOverScrolled(int scrollX, int scrollY, boolean clampedX,
- boolean clampedY);
- }
-
- private static final ScrollListener EMPTY_LISTER = new ScrollListener() {
- @Override
- public void notifyScrollChanged(int scrollX, int scrollY, int oldX, int oldY) {}
- @Override
- public void notifyOverScrolled(int scrollX, int scrollY, boolean clampedX,
- boolean clampedY) {}
- };
-
- public ScrollViewWithNotifier(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onScrollChanged(final int scrollX, final int scrollY, final int oldX,
- final int oldY) {
- super.onScrollChanged(scrollX, scrollY, oldX, oldY);
- mScrollListener.notifyScrollChanged(scrollX, scrollY, oldX, oldY);
- }
-
- @Override
- protected void onOverScrolled(final int scrollX, final int scrollY, final boolean clampedX,
- final boolean clampedY) {
- super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
- mScrollListener.notifyOverScrolled(scrollX, scrollY, clampedX, clampedY);
- }
-
- public void setScrollListener(final ScrollListener listener) {
- mScrollListener = listener;
- }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java
index 2787ebfb9..76cb89160 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java
@@ -29,7 +29,7 @@ import com.android.inputmethod.latin.utils.CoordinateUtils;
/**
* Draw rubber band preview graphics during sliding key input.
*/
-public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
+public final class SlidingKeyInputDrawingPreview extends AbstractDrawingPreview {
private final float mPreviewBodyRadius;
private boolean mShowsSlidingKeyInputPreview;
@@ -40,7 +40,8 @@ public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
private final RoundedLine mRoundedLine = new RoundedLine();
private final Paint mPaint = new Paint();
- public SlidingKeyInputPreview(final View drawingView, final TypedArray mainKeyboardViewAttr) {
+ public SlidingKeyInputDrawingPreview(final View drawingView,
+ final TypedArray mainKeyboardViewAttr) {
super(drawingView);
final int previewColor = mainKeyboardViewAttr.getColor(
R.styleable.MainKeyboardView_slidingKeyInputPreviewColor, 0);
@@ -61,6 +62,11 @@ public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
mPaint.setColor(previewColor);
}
+ @Override
+ public void onDeallocateMemory() {
+ // Nothing to do here.
+ }
+
public void dismissSlidingKeyInputPreview() {
mShowsSlidingKeyInputPreview = false;
getDrawingView().invalidate();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java
new file mode 100644
index 000000000..ec7b9b024
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.os.Message;
+import android.os.SystemClock;
+import android.view.ViewConfiguration;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.keyboard.internal.TimerHandler.Callbacks;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
+
+// TODO: Separate this class into KeyTimerHandler and BatchInputTimerHandler or so.
+public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> implements TimerProxy {
+ public interface Callbacks {
+ public void startWhileTypingFadeinAnimation();
+ public void startWhileTypingFadeoutAnimation();
+ public void onLongPress(PointerTracker tracker);
+ }
+
+ private static final int MSG_TYPING_STATE_EXPIRED = 0;
+ private static final int MSG_REPEAT_KEY = 1;
+ private static final int MSG_LONGPRESS_KEY = 2;
+ private static final int MSG_LONGPRESS_SHIFT_KEY = 3;
+ private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 4;
+ private static final int MSG_UPDATE_BATCH_INPUT = 5;
+
+ private final int mIgnoreAltCodeKeyTimeout;
+ private final int mGestureRecognitionUpdateTime;
+
+ public TimerHandler(final Callbacks ownerInstance, final int ignoreAltCodeKeyTimeout,
+ final int gestureRecognitionUpdateTime) {
+ super(ownerInstance);
+ mIgnoreAltCodeKeyTimeout = ignoreAltCodeKeyTimeout;
+ mGestureRecognitionUpdateTime = gestureRecognitionUpdateTime;
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ final Callbacks callbacks = getOwnerInstance();
+ if (callbacks == null) {
+ return;
+ }
+ final PointerTracker tracker = (PointerTracker) msg.obj;
+ switch (msg.what) {
+ case MSG_TYPING_STATE_EXPIRED:
+ callbacks.startWhileTypingFadeinAnimation();
+ break;
+ case MSG_REPEAT_KEY:
+ tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
+ break;
+ case MSG_LONGPRESS_KEY:
+ case MSG_LONGPRESS_SHIFT_KEY:
+ cancelLongPressTimers();
+ callbacks.onLongPress(tracker);
+ break;
+ case MSG_UPDATE_BATCH_INPUT:
+ tracker.updateBatchInputByTimer(SystemClock.uptimeMillis());
+ startUpdateBatchInputTimer(tracker);
+ break;
+ }
+ }
+
+ @Override
+ public void startKeyRepeatTimerOf(final PointerTracker tracker, final int repeatCount,
+ final int delay) {
+ final Key key = tracker.getKey();
+ if (key == null || delay == 0) {
+ return;
+ }
+ sendMessageDelayed(
+ obtainMessage(MSG_REPEAT_KEY, key.getCode(), repeatCount, tracker), delay);
+ }
+
+ private void cancelKeyRepeatTimerOf(final PointerTracker tracker) {
+ removeMessages(MSG_REPEAT_KEY, tracker);
+ }
+
+ public void cancelKeyRepeatTimers() {
+ removeMessages(MSG_REPEAT_KEY);
+ }
+
+ // TODO: Suppress layout changes in key repeat mode
+ public boolean isInKeyRepeat() {
+ return hasMessages(MSG_REPEAT_KEY);
+ }
+
+ @Override
+ public void startLongPressTimerOf(final PointerTracker tracker, final int delay) {
+ final Key key = tracker.getKey();
+ if (key == null) {
+ return;
+ }
+ // Use a separate message id for long pressing shift key, because long press shift key
+ // timers should be canceled when other key is pressed.
+ final int messageId = (key.getCode() == Constants.CODE_SHIFT)
+ ? MSG_LONGPRESS_SHIFT_KEY : MSG_LONGPRESS_KEY;
+ sendMessageDelayed(obtainMessage(messageId, tracker), delay);
+ }
+
+ @Override
+ public void cancelLongPressTimerOf(final PointerTracker tracker) {
+ removeMessages(MSG_LONGPRESS_KEY, tracker);
+ removeMessages(MSG_LONGPRESS_SHIFT_KEY, tracker);
+ }
+
+ @Override
+ public void cancelLongPressShiftKeyTimers() {
+ removeMessages(MSG_LONGPRESS_SHIFT_KEY);
+ }
+
+ public void cancelLongPressTimers() {
+ removeMessages(MSG_LONGPRESS_KEY);
+ removeMessages(MSG_LONGPRESS_SHIFT_KEY);
+ }
+
+ @Override
+ public void startTypingStateTimer(final Key typedKey) {
+ if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) {
+ return;
+ }
+
+ final boolean isTyping = isTypingState();
+ removeMessages(MSG_TYPING_STATE_EXPIRED);
+ final Callbacks callbacks = getOwnerInstance();
+ if (callbacks == null) {
+ return;
+ }
+
+ // When user hits the space or the enter key, just cancel the while-typing timer.
+ final int typedCode = typedKey.getCode();
+ if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) {
+ if (isTyping) {
+ callbacks.startWhileTypingFadeinAnimation();
+ }
+ return;
+ }
+
+ sendMessageDelayed(
+ obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
+ if (isTyping) {
+ return;
+ }
+ callbacks.startWhileTypingFadeoutAnimation();
+ }
+
+ @Override
+ public boolean isTypingState() {
+ return hasMessages(MSG_TYPING_STATE_EXPIRED);
+ }
+
+ @Override
+ public void startDoubleTapShiftKeyTimer() {
+ sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP_SHIFT_KEY),
+ ViewConfiguration.getDoubleTapTimeout());
+ }
+
+ @Override
+ public void cancelDoubleTapShiftKeyTimer() {
+ removeMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
+ }
+
+ @Override
+ public boolean isInDoubleTapShiftKeyTimeout() {
+ return hasMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
+ }
+
+ @Override
+ public void cancelKeyTimersOf(final PointerTracker tracker) {
+ cancelKeyRepeatTimerOf(tracker);
+ cancelLongPressTimerOf(tracker);
+ }
+
+ public void cancelAllKeyTimers() {
+ cancelKeyRepeatTimers();
+ cancelLongPressTimers();
+ }
+
+ @Override
+ public void startUpdateBatchInputTimer(final PointerTracker tracker) {
+ if (mGestureRecognitionUpdateTime <= 0) {
+ return;
+ }
+ removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
+ sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker),
+ mGestureRecognitionUpdateTime);
+ }
+
+ @Override
+ public void cancelUpdateBatchInputTimer(final PointerTracker tracker) {
+ removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
+ }
+
+ @Override
+ public void cancelAllUpdateBatchInputTimers() {
+ removeMessages(MSG_UPDATE_BATCH_INPUT);
+ }
+
+ public void cancelAllMessages() {
+ cancelAllKeyTimers();
+ cancelAllUpdateBatchInputTimers();
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TypingTimeRecorder.java b/java/src/com/android/inputmethod/keyboard/internal/TypingTimeRecorder.java
new file mode 100644
index 000000000..9593f715c
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/TypingTimeRecorder.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+public final class TypingTimeRecorder {
+ private final int mStaticTimeThresholdAfterFastTyping; // msec
+ private final int mSuppressKeyPreviewAfterBatchInputDuration;
+ private long mLastTypingTime;
+ private long mLastLetterTypingTime;
+ private long mLastBatchInputTime;
+
+ public TypingTimeRecorder(final int staticTimeThresholdAfterFastTyping,
+ final int suppressKeyPreviewAfterBatchInputDuration) {
+ mStaticTimeThresholdAfterFastTyping = staticTimeThresholdAfterFastTyping;
+ mSuppressKeyPreviewAfterBatchInputDuration = suppressKeyPreviewAfterBatchInputDuration;
+ }
+
+ public boolean isInFastTyping(final long eventTime) {
+ final long elapsedTimeSinceLastLetterTyping = eventTime - mLastLetterTypingTime;
+ return elapsedTimeSinceLastLetterTyping < mStaticTimeThresholdAfterFastTyping;
+ }
+
+ private boolean wasLastInputTyping() {
+ return mLastTypingTime >= mLastBatchInputTime;
+ }
+
+ public void onCodeInput(final int code, final long eventTime) {
+ // Record the letter typing time when
+ // 1. Letter keys are typed successively without any batch input in between.
+ // 2. A letter key is typed within the threshold time since the last any key typing.
+ // 3. A non-letter key is typed within the threshold time since the last letter key typing.
+ if (Character.isLetter(code)) {
+ if (wasLastInputTyping()
+ || eventTime - mLastTypingTime < mStaticTimeThresholdAfterFastTyping) {
+ mLastLetterTypingTime = eventTime;
+ }
+ } else {
+ if (eventTime - mLastLetterTypingTime < mStaticTimeThresholdAfterFastTyping) {
+ // This non-letter typing should be treated as a part of fast typing.
+ mLastLetterTypingTime = eventTime;
+ }
+ }
+ mLastTypingTime = eventTime;
+ }
+
+ public void onEndBatchInput(final long eventTime) {
+ mLastBatchInputTime = eventTime;
+ }
+
+ public long getLastLetterTypingTime() {
+ return mLastLetterTypingTime;
+ }
+
+ public boolean needsToSuppressKeyPreviewPopup(final long eventTime) {
+ return !wasLastInputTyping()
+ && eventTime - mLastBatchInputTime < mSuppressKeyPreviewAfterBatchInputDuration;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
deleted file mode 100644
index 463d09344..000000000
--- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-import android.util.Log;
-
-import com.android.inputmethod.latin.makedict.DictEncoder;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-
-// TODO: Quit extending Dictionary after implementing dynamic binary dictionary.
-abstract public class AbstractDictionaryWriter extends Dictionary {
- /** Used for Log actions from this class */
- private static final String TAG = AbstractDictionaryWriter.class.getSimpleName();
-
- private final Context mContext;
-
- public AbstractDictionaryWriter(final Context context, final String dictType) {
- super(dictType);
- mContext = context;
- }
-
- abstract public void clear();
-
- /**
- * Add a unigram with an optional shortcut to the dictionary.
- * @param word The word to add.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this unigram.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
- * if shortcutTarget is null.
- * @param isNotAWord true if this is not a word, i.e. shortcut only.
- */
- abstract public void addUnigramWord(final String word, final String shortcutTarget,
- final int frequency, final int shortcutFreq, final boolean isNotAWord);
-
- // TODO: Remove lastModifiedTime after making binary dictionary support forgetting curve.
- abstract public void addBigramWords(final String word0, final String word1,
- final int frequency, final boolean isValid,
- final long lastModifiedTime);
-
- abstract public void removeBigramWords(final String word0, final String word1);
-
- abstract protected void writeDictionary(final DictEncoder dictEncoder,
- final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException;
-
- public void write(final String fileName, final Map<String, String> attributeMap) {
- final String tempFileName = fileName + ".temp";
- final File file = new File(mContext.getFilesDir(), fileName);
- final File tempFile = new File(mContext.getFilesDir(), tempFileName);
- try {
- final DictEncoder dictEncoder = new Ver3DictEncoder(tempFile);
- writeDictionary(dictEncoder, attributeMap);
- tempFile.renameTo(file);
- } catch (IOException e) {
- Log.e(TAG, "IO exception while writing file", e);
- } catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported format", e);
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
index 875192554..fd6c24dfe 100644
--- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.utils.FileUtils;
+
import java.io.File;
/**
@@ -52,4 +54,12 @@ public final class AssetFileAddress {
if (!f.isFile()) return null;
return new AssetFileAddress(filename, offset, length);
}
+
+ public boolean pointsToPhysicalFile() {
+ return 0 == mOffset;
+ }
+
+ public void deleteUnderlyingFile() {
+ FileUtils.deleteRecursively(new File(mFilename));
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index fd296988e..88174ba83 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -17,21 +17,30 @@
package com.android.inputmethod.latin;
import android.text.TextUtils;
+import android.util.Log;
import android.util.SparseArray;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.makedict.WordProperty;
import com.android.inputmethod.latin.settings.NativeSuggestOptions;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.FileUtils;
import com.android.inputmethod.latin.utils.JniUtils;
+import com.android.inputmethod.latin.utils.LanguageModelParam;
import com.android.inputmethod.latin.utils.StringUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.Locale;
-import java.util.Map;
/**
* Implements a static, compacted, binary dictionary of standard words.
@@ -57,17 +66,40 @@ public final class BinaryDictionary extends Dictionary {
@UsedForTesting
public static final String MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT";
+ public static final int NOT_A_VALID_TIMESTAMP = -1;
+
+ // Format to get unigram flags from native side via getWordPropertyNative().
+ private static final int FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT = 4;
+ private static final int FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX = 0;
+ private static final int FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX = 1;
+ private static final int FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX = 2;
+ private static final int FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX = 3;
+
+ // Format to get probability and historical info from native side via getWordPropertyNative().
+ public static final int FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT = 4;
+ public static final int FORMAT_WORD_PROPERTY_PROBABILITY_INDEX = 0;
+ public static final int FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX = 1;
+ public static final int FORMAT_WORD_PROPERTY_LEVEL_INDEX = 2;
+ public static final int FORMAT_WORD_PROPERTY_COUNT_INDEX = 3;
+
+ public static final String DICT_FILE_NAME_SUFFIX_FOR_MIGRATION = ".migrate";
+
private long mNativeDict;
private final Locale mLocale;
private final long mDictSize;
private final String mDictFilePath;
+ private final boolean mIsUpdatable;
+ private boolean mHasUpdated;
+
private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
+ private final int[] mOutputSuggestionCount = new int[1];
private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS];
private final int[] mSpaceIndices = new int[MAX_RESULTS];
private final int[] mOutputScores = new int[MAX_RESULTS];
private final int[] mOutputTypes = new int[MAX_RESULTS];
// Only one result is ever used
private final int[] mOutputAutoCommitFirstWordConfidence = new int[1];
+ private final float[] mInputOutputLanguageWeight = new float[1];
private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
@@ -107,6 +139,8 @@ public final class BinaryDictionary extends Dictionary {
mLocale = locale;
mDictSize = length;
mDictFilePath = filename;
+ mIsUpdatable = isUpdatable;
+ mHasUpdated = false;
mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
loadDictionary(filename, offset, length, isUpdatable);
}
@@ -115,92 +149,144 @@ public final class BinaryDictionary extends Dictionary {
JniUtils.loadNativeLibrary();
}
- private static native boolean createEmptyDictFileNative(String filePath, long dictVersion,
- String[] attributeKeyStringArray, String[] attributeValueStringArray);
private static native long openNative(String sourceDir, long dictOffset, long dictSize,
boolean isUpdatable);
+ private static native void getHeaderInfoNative(long dict, int[] outHeaderSize,
+ int[] outFormatVersion, ArrayList<int[]> outAttributeKeys,
+ ArrayList<int[]> outAttributeValues);
private static native void flushNative(long dict, String filePath);
private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
private static native void flushWithGCNative(long dict, String filePath);
private static native void closeNative(long dict);
+ private static native int getFormatVersionNative(long dict);
private static native int getProbabilityNative(long dict, int[] word);
private static native int getBigramProbabilityNative(long dict, int[] word0, int[] word1);
- private static native int getSuggestionsNative(long dict, long proximityInfo,
+ private static native void getWordPropertyNative(long dict, int[] word,
+ int[] outCodePoints, boolean[] outFlags, int[] outProbabilityInfo,
+ ArrayList<int[]> outBigramTargets, ArrayList<int[]> outBigramProbabilityInfo,
+ ArrayList<int[]> outShortcutTargets, ArrayList<Integer> outShortcutProbabilities);
+ private static native int getNextWordNative(long dict, int token, int[] outCodePoints);
+ private static native void getSuggestionsNative(long dict, long proximityInfo,
long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
- int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint,
- int[] suggestOptions, int[] prevWordCodePointArray,
- int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes,
- int[] outputAutoCommitFirstWordConfidence);
- private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
- private static native int editDistanceNative(int[] before, int[] after);
- private static native void addUnigramWordNative(long dict, int[] word, int probability);
+ int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions,
+ int[] prevWordCodePointArray, int[] outputSuggestionCount, int[] outputCodePoints,
+ int[] outputScores, int[] outputIndices, int[] outputTypes,
+ int[] outputAutoCommitFirstWordConfidence, float[] inOutLanguageWeight);
+ private static native void addUnigramWordNative(long dict, int[] word, int probability,
+ int[] shortcutTarget, int shortcutProbability, boolean isNotAWord,
+ boolean isBlacklisted, int timestamp);
private static native void addBigramWordsNative(long dict, int[] word0, int[] word1,
- int probability);
+ int probability, int timestamp);
private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
+ private static native int addMultipleDictionaryEntriesNative(long dict,
+ LanguageModelParam[] languageModelParams, int startIndex);
private static native int calculateProbabilityNative(long dict, int unigramProbability,
int bigramProbability);
private static native String getPropertyNative(long dict, String query);
-
- @UsedForTesting
- public static boolean createEmptyDictFile(final String filePath, final long dictVersion,
- final Map<String, String> attributeMap) {
- final String[] keyArray = new String[attributeMap.size()];
- final String[] valueArray = new String[attributeMap.size()];
- int index = 0;
- for (final String key : attributeMap.keySet()) {
- keyArray[index] = key;
- valueArray[index] = attributeMap.get(key);
- index++;
- }
- return createEmptyDictFileNative(filePath, dictVersion, keyArray, valueArray);
- }
+ private static native boolean isCorruptedNative(long dict);
// TODO: Move native dict into session
private final void loadDictionary(final String path, final long startOffset,
final long length, final boolean isUpdatable) {
+ mHasUpdated = false;
mNativeDict = openNative(path, startOffset, length, isUpdatable);
}
+ // TODO: Check isCorrupted() for main dictionaries.
+ public boolean isCorrupted() {
+ if (!isValidDictionary()) {
+ return false;
+ }
+ if (!isCorruptedNative(mNativeDict)) {
+ return false;
+ }
+ // TODO: Record the corruption.
+ Log.e(TAG, "BinaryDictionary (" + mDictFilePath + ") is corrupted.");
+ Log.e(TAG, "locale: " + mLocale);
+ Log.e(TAG, "dict size: " + mDictSize);
+ Log.e(TAG, "updatable: " + mIsUpdatable);
+ return true;
+ }
+
+ public DictionaryHeader getHeader() throws UnsupportedFormatException {
+ if (mNativeDict == 0) {
+ return null;
+ }
+ final int[] outHeaderSize = new int[1];
+ final int[] outFormatVersion = new int[1];
+ final ArrayList<int[]> outAttributeKeys = CollectionUtils.newArrayList();
+ final ArrayList<int[]> outAttributeValues = CollectionUtils.newArrayList();
+ getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys,
+ outAttributeValues);
+ final HashMap<String, String> attributes = new HashMap<String, String>();
+ for (int i = 0; i < outAttributeKeys.size(); i++) {
+ final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray(
+ outAttributeKeys.get(i));
+ final String attributeValue = StringUtils.getStringFromNullTerminatedCodePointArray(
+ outAttributeValues.get(i));
+ attributes.put(attributeKey, attributeValue);
+ }
+ final boolean hasHistoricalInfo = DictionaryHeader.ATTRIBUTE_VALUE_TRUE.equals(
+ attributes.get(DictionaryHeader.HAS_HISTORICAL_INFO_KEY));
+ return new DictionaryHeader(outHeaderSize[0], new DictionaryOptions(attributes),
+ new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo));
+ }
+
+
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final float[] inOutLanguageWeight) {
return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, 0 /* sessionId */);
+ additionalFeaturesOptions, 0 /* sessionId */, inOutLanguageWeight);
}
@Override
public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final int sessionId) {
- if (!isValidDictionary()) return null;
+ final int sessionId, final float[] inOutLanguageWeight) {
+ if (!isValidDictionary()) {
+ return null;
+ }
Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
// TODO: toLowerCase in the native code
final int[] prevWordCodePointArray = (null == prevWord)
? null : StringUtils.toCodePointArray(prevWord);
- final int composerSize = composer.size();
-
+ final InputPointers inputPointers = composer.getInputPointers();
final boolean isGesture = composer.isBatchMode();
- if (composerSize <= 1 || !isGesture) {
- if (composerSize > MAX_WORD_LENGTH - 1) return null;
- for (int i = 0; i < composerSize; i++) {
- mInputCodePoints[i] = composer.getCodeAt(i);
+ final int inputSize;
+ if (!isGesture) {
+ inputSize = composer.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
+ mInputCodePoints);
+ if (inputSize < 0) {
+ return null;
}
+ } else {
+ inputSize = inputPointers.getPointerSize();
}
- final InputPointers ips = composer.getInputPointers();
- final int inputSize = isGesture ? ips.getPointerSize() : composerSize;
mNativeSuggestOptions.setIsGesture(isGesture);
mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions);
+ if (inOutLanguageWeight != null) {
+ mInputOutputLanguageWeight[0] = inOutLanguageWeight[0];
+ } else {
+ mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT;
+ }
// proximityInfo and/or prevWordForBigrams may not be null.
- final int count = getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
- getTraverseSession(sessionId).getSession(), ips.getXCoordinates(),
- ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints,
- inputSize, 0 /* commitPoint */, mNativeSuggestOptions.getOptions(),
- prevWordCodePointArray, mOutputCodePoints, mOutputScores, mSpaceIndices,
- mOutputTypes, mOutputAutoCommitFirstWordConfidence);
+ getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
+ getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(),
+ inputPointers.getYCoordinates(), inputPointers.getTimes(),
+ inputPointers.getPointerIds(), mInputCodePoints, inputSize,
+ mNativeSuggestOptions.getOptions(), prevWordCodePointArray, mOutputSuggestionCount,
+ mOutputCodePoints, mOutputScores, mSpaceIndices, mOutputTypes,
+ mOutputAutoCommitFirstWordConfidence, mInputOutputLanguageWeight);
+ if (inOutLanguageWeight != null) {
+ inOutLanguageWeight[0] = mInputOutputLanguageWeight[0];
+ }
+ final int count = mOutputSuggestionCount[0];
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
for (int j = 0; j < count; ++j) {
final int start = j * MAX_WORD_LENGTH;
@@ -235,18 +321,8 @@ public final class BinaryDictionary extends Dictionary {
return mNativeDict != 0;
}
- public static float calcNormalizedScore(final String before, final String after,
- final int score) {
- return calcNormalizedScoreNative(StringUtils.toCodePointArray(before),
- StringUtils.toCodePointArray(after), score);
- }
-
- public static int editDistance(final String before, final String after) {
- if (before == null || after == null) {
- throw new IllegalArgumentException();
- }
- return editDistanceNative(StringUtils.toCodePointArray(before),
- StringUtils.toCodePointArray(after));
+ public int getFormatVersion() {
+ return getFormatVersionNative(mNativeDict);
}
@Override
@@ -274,23 +350,77 @@ public final class BinaryDictionary extends Dictionary {
return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1);
}
- // Add a unigram entry to binary dictionary in native code.
- public void addUnigramWord(final String word, final int probability) {
+ public WordProperty getWordProperty(final String word) {
+ if (TextUtils.isEmpty(word)) {
+ return null;
+ }
+ final int[] codePoints = StringUtils.toCodePointArray(word);
+ final int[] outCodePoints = new int[MAX_WORD_LENGTH];
+ final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT];
+ final int[] outProbabilityInfo =
+ new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT];
+ final ArrayList<int[]> outBigramTargets = CollectionUtils.newArrayList();
+ final ArrayList<int[]> outBigramProbabilityInfo = CollectionUtils.newArrayList();
+ final ArrayList<int[]> outShortcutTargets = CollectionUtils.newArrayList();
+ final ArrayList<Integer> outShortcutProbabilities = CollectionUtils.newArrayList();
+ getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo,
+ outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
+ outShortcutProbabilities);
+ return new WordProperty(codePoints,
+ outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX],
+ outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX],
+ outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX],
+ outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX], outProbabilityInfo,
+ outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
+ outShortcutProbabilities);
+ }
+
+ public static class GetNextWordPropertyResult {
+ public WordProperty mWordProperty;
+ public int mNextToken;
+
+ public GetNextWordPropertyResult(final WordProperty wordPreperty, final int nextToken) {
+ mWordProperty = wordPreperty;
+ mNextToken = nextToken;
+ }
+ }
+
+ /**
+ * Method to iterate all words in the dictionary for makedict.
+ * If token is 0, this method newly starts iterating the dictionary.
+ */
+ public GetNextWordPropertyResult getNextWordProperty(final int token) {
+ final int[] codePoints = new int[MAX_WORD_LENGTH];
+ final int nextToken = getNextWordNative(mNativeDict, token, codePoints);
+ final String word = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
+ return new GetNextWordPropertyResult(getWordProperty(word), nextToken);
+ }
+
+ // Add a unigram entry to binary dictionary with unigram attributes in native code.
+ public void addUnigramWord(final String word, final int probability,
+ final String shortcutTarget, final int shortcutProbability, final boolean isNotAWord,
+ final boolean isBlacklisted, final int timestamp) {
if (TextUtils.isEmpty(word)) {
return;
}
final int[] codePoints = StringUtils.toCodePointArray(word);
- addUnigramWordNative(mNativeDict, codePoints, probability);
+ final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
+ StringUtils.toCodePointArray(shortcutTarget) : null;
+ addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
+ shortcutProbability, isNotAWord, isBlacklisted, timestamp);
+ mHasUpdated = true;
}
- // Add a bigram entry to binary dictionary in native code.
- public void addBigramWords(final String word0, final String word1, final int probability) {
+ // Add a bigram entry to binary dictionary with timestamp in native code.
+ public void addBigramWords(final String word0, final String word1, final int probability,
+ final int timestamp) {
if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
return;
}
final int[] codePoints0 = StringUtils.toCodePointArray(word0);
final int[] codePoints1 = StringUtils.toCodePointArray(word1);
- addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability);
+ addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp);
+ mHasUpdated = true;
}
// Remove a bigram entry form binary dictionary in native code.
@@ -301,19 +431,41 @@ public final class BinaryDictionary extends Dictionary {
final int[] codePoints0 = StringUtils.toCodePointArray(word0);
final int[] codePoints1 = StringUtils.toCodePointArray(word1);
removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
+ mHasUpdated = true;
+ }
+
+ public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
+ if (!isValidDictionary()) return;
+ int processedParamCount = 0;
+ while (processedParamCount < languageModelParams.length) {
+ if (needsToRunGC(true /* mindsBlockByGC */)) {
+ flushWithGC();
+ }
+ processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict,
+ languageModelParams, processedParamCount);
+ mHasUpdated = true;
+ if (processedParamCount <= 0) {
+ return;
+ }
+ }
}
private void reopen() {
close();
final File dictFile = new File(mDictFilePath);
- mNativeDict = openNative(dictFile.getAbsolutePath(), 0 /* startOffset */,
- dictFile.length(), true /* isUpdatable */);
+ // WARNING: Because we pass 0 as the offset and file.length() as the length, this can
+ // only be called for actual files. Right now it's only called by the flush() family of
+ // functions, which require an updatable dictionary, so it's okay. But beware.
+ loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
+ dictFile.length(), mIsUpdatable);
}
public void flush() {
if (!isValidDictionary()) return;
- flushNative(mNativeDict, mDictFilePath);
- reopen();
+ if (mHasUpdated) {
+ flushNative(mNativeDict, mDictFilePath);
+ reopen();
+ }
}
public void flushWithGC() {
@@ -333,6 +485,24 @@ public final class BinaryDictionary extends Dictionary {
return needsToRunGCNative(mNativeDict, mindsBlockByGC);
}
+ public boolean migrateTo(final int newFormatVersion) {
+ if (!isValidDictionary()) {
+ return false;
+ }
+ final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION;
+ // TODO: Implement migrateNative(tmpDictFilePath, newFormatVersion).
+ close();
+ final File dictFile = new File(mDictFilePath);
+ final File tmpDictFile = new File(tmpDictFilePath);
+ FileUtils.deleteRecursively(dictFile);
+ if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) {
+ return false;
+ }
+ loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
+ dictFile.length(), mIsUpdatable);
+ return true;
+ }
+
@UsedForTesting
public int calculateProbability(final int unigramProbability, final int bigramProbability) {
if (!isValidDictionary()) return NOT_A_PROBABILITY;
@@ -340,7 +510,7 @@ public final class BinaryDictionary extends Dictionary {
}
@UsedForTesting
- public String getPropertyForTests(String query) {
+ public String getPropertyForTest(final String query) {
if (!isValidDictionary()) return "";
return getPropertyNative(mNativeDict, query);
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 722a82961..e428b1d54 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -98,7 +98,7 @@ public final class BinaryDictionaryFileDumper {
* This creates a URI builder able to build a URI pointing to the dictionary
* pack content provider for a specific dictionary id.
*/
- private static Uri.Builder getProviderUriBuilder(final String path) {
+ public static Uri.Builder getProviderUriBuilder(final String path) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(DictionaryPackConstants.AUTHORITY).appendPath(path);
}
@@ -142,7 +142,7 @@ public final class BinaryDictionaryFileDumper {
final ContentProviderClient client = context.getContentResolver().
acquireContentProviderClient(getProviderUriBuilder("").build());
if (null == client) return Collections.<WordListInfo>emptyList();
-
+ Cursor cursor = null;
try {
final Uri.Builder builder = getContentUriBuilderForType(clientId, client,
QUERY_PATH_DICT_INFO, locale.toString());
@@ -154,24 +154,22 @@ public final class BinaryDictionaryFileDumper {
final boolean isProtocolV2 = (QUERY_PARAMETER_PROTOCOL_VALUE.equals(
queryUri.getQueryParameter(QUERY_PARAMETER_PROTOCOL)));
- Cursor c = client.query(queryUri, DICTIONARY_PROJECTION, null, null, null);
- if (isProtocolV2 && null == c) {
+ cursor = client.query(queryUri, DICTIONARY_PROJECTION, null, null, null);
+ if (isProtocolV2 && null == cursor) {
reinitializeClientRecordInDictionaryContentProvider(context, client, clientId);
- c = client.query(queryUri, DICTIONARY_PROJECTION, null, null, null);
+ cursor = client.query(queryUri, DICTIONARY_PROJECTION, null, null, null);
}
- if (null == c) return Collections.<WordListInfo>emptyList();
- if (c.getCount() <= 0 || !c.moveToFirst()) {
- c.close();
+ if (null == cursor) return Collections.<WordListInfo>emptyList();
+ if (cursor.getCount() <= 0 || !cursor.moveToFirst()) {
return Collections.<WordListInfo>emptyList();
}
final ArrayList<WordListInfo> list = CollectionUtils.newArrayList();
do {
- final String wordListId = c.getString(0);
- final String wordListLocale = c.getString(1);
+ final String wordListId = cursor.getString(0);
+ final String wordListLocale = cursor.getString(1);
if (TextUtils.isEmpty(wordListId)) continue;
list.add(new WordListInfo(wordListId, wordListLocale));
- } while (c.moveToNext());
- c.close();
+ } while (cursor.moveToNext());
return list;
} catch (RemoteException e) {
// The documentation is unclear as to in which cases this may happen, but it probably
@@ -186,6 +184,9 @@ public final class BinaryDictionaryFileDumper {
Log.e(TAG, "Unexpected exception communicating with the dictionary pack", e);
return Collections.<WordListInfo>emptyList();
} finally {
+ if (null != cursor) {
+ cursor.close();
+ }
client.release();
}
}
@@ -339,15 +340,25 @@ public final class BinaryDictionaryFileDumper {
Log.e(TAG, "Could not copy a word list. Will not be able to use it.");
// If we can't copy it we should warn the dictionary provider so that it can mark it
// as invalid.
- wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
- QUERY_PARAMETER_FAILURE);
+ reportBrokenFileToDictionaryProvider(providerClient, clientId, wordlistId);
+ }
+
+ public static boolean reportBrokenFileToDictionaryProvider(
+ final ContentProviderClient providerClient, final String clientId,
+ final String wordlistId) {
try {
+ final Uri.Builder wordListUriBuilder = getContentUriBuilderForType(clientId,
+ providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */);
+ wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
+ QUERY_PARAMETER_FAILURE);
if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
- Log.e(TAG, "In addition, we were unable to delete it.");
+ Log.e(TAG, "Unable to delete a word list.");
}
} catch (RemoteException e) {
- Log.e(TAG, "In addition, communication with the dictionary provider was cut", e);
+ Log.e(TAG, "Communication with the dictionary provider was cut", e);
+ return false;
}
+ return true;
}
// Ideally the two following methods should be merged, but AssetFileDescriptor does not
@@ -432,8 +443,9 @@ public final class BinaryDictionaryFileDumper {
// Actually copy the file
final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE];
- for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer))
+ for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) {
output.write(buffer, 0, readBytes);
+ }
input.close();
}
@@ -478,8 +490,7 @@ public final class BinaryDictionaryFileDumper {
* @param context the context for resources and providers.
* @param clientId the client ID to use.
*/
- public static void initializeClientRecordHelper(final Context context,
- final String clientId) {
+ public static void initializeClientRecordHelper(final Context context, final String clientId) {
try {
final ContentProviderClient client = context.getContentResolver().
acquireContentProviderClient(getProviderUriBuilder("").build());
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 181ad17ea..4c49cb31c 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -21,10 +21,9 @@ import android.content.SharedPreferences;
import android.content.res.AssetFileDescriptor;
import android.util.Log;
-import com.android.inputmethod.latin.makedict.DictDecoder;
-import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
@@ -112,7 +111,7 @@ final public class BinaryDictionaryGetter {
public DictPackSettings(final Context context) {
mDictPreferences = null == context ? null
: context.getSharedPreferences(COMMON_PREFERENCES_NAME,
- Context.MODE_WORLD_READABLE | Context.MODE_MULTI_PROCESS);
+ Context.MODE_MULTI_PROCESS);
}
public boolean isWordListActive(final String dictId) {
if (null == mDictPreferences) {
@@ -226,12 +225,10 @@ final public class BinaryDictionaryGetter {
// ## HACK ## we prevent usage of a dictionary before version 18. The reason for this is, since
// those do not include whitelist entries, the new code with an old version of the dictionary
// would lose whitelist functionality.
- private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
+ private static boolean hackCanUseDictionaryFile(final Locale locale, final File file) {
try {
// Read the version of the file
- final DictDecoder dictDecoder = FormatSpec.getDictDecoder(f);
- final FileHeader header = dictDecoder.readHeader();
-
+ final DictionaryHeader header = BinaryDictionaryUtils.getHeader(file);
final String version = header.mDictionaryOptions.mAttributes.get(VERSION_KEY);
if (null == version) {
// No version in the options : the format is unexpected
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 9a9653094..e71723a15 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -70,38 +70,47 @@ public final class Constants {
public static final class ExtraValue {
/**
- * The subtype extra value used to indicate that the subtype keyboard layout is capable
- * for typing ASCII characters.
+ * The subtype extra value used to indicate that this subtype is capable of
+ * entering ASCII characters.
*/
public static final String ASCII_CAPABLE = "AsciiCapable";
/**
- * The subtype extra value used to indicate that the subtype keyboard layout is capable
- * for typing EMOJI characters.
+ * The subtype extra value used to indicate that this subtype is enabled
+ * when the default subtype is not marked as ascii capable.
+ */
+ public static final String ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
+ "EnabledWhenDefaultIsNotAsciiCapable";
+
+ /**
+ * The subtype extra value used to indicate that this subtype is capable of
+ * entering emoji characters.
*/
public static final String EMOJI_CAPABLE = "EmojiCapable";
+
/**
- * The subtype extra value used to indicate that the subtype require network connection
- * to work.
+ * The subtype extra value used to indicate that this subtype requires a network
+ * connection to work.
*/
public static final String REQ_NETWORK_CONNECTIVITY = "requireNetworkConnectivity";
/**
- * The subtype extra value used to indicate that the subtype display name contains "%s"
- * for replacement mark and it should be replaced by this extra value.
+ * The subtype extra value used to indicate that the display name of this subtype
+ * contains a "%s" for printf-like replacement and it should be replaced by
+ * this extra value.
* This extra value is supported on JellyBean and later.
*/
public static final String UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME =
"UntranslatableReplacementStringInSubtypeName";
/**
- * The subtype extra value used to indicate that the subtype keyboard layout set name.
+ * The subtype extra value used to indicate this subtype keyboard layout set name.
* This extra value is private to LatinIME.
*/
public static final String KEYBOARD_LAYOUT_SET = "KeyboardLayoutSet";
/**
- * The subtype extra value used to indicate that the subtype is additional subtype
+ * The subtype extra value used to indicate that this subtype is an additional subtype
* that the user defined. This extra value is private to LatinIME.
*/
public static final String IS_ADDITIONAL_SUBTYPE = "isAdditionalSubtype";
@@ -124,6 +133,8 @@ public final class Constants {
* {@link android.text.TextUtils#CAP_MODE_WORDS}, and
* {@link android.text.TextUtils#CAP_MODE_SENTENCES}.
*/
+ // TODO: Straighten this out. It's bizarre to have to use android.text.TextUtils.CAP_MODE_*
+ // except for OFF that is in Constants.TextUtils.
public static final int CAP_MODE_OFF = 0;
private TextUtils() {
@@ -132,7 +143,8 @@ public final class Constants {
}
public static final int NOT_A_CODE = -1;
-
+ public static final int NOT_A_CURSOR_POSITION = -1;
+ // TODO: replace the following constants with state in InputTransaction?
public static final int NOT_A_COORDINATE = -1;
public static final int SUGGESTION_STRIP_COORDINATE = -2;
public static final int SPELL_CHECKER_COORDINATE = -3;
@@ -145,6 +157,13 @@ public final class Constants {
// Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
+ // Key events coming any faster than this are long-presses.
+ public static final int LONG_PRESS_MILLISECONDS = 200;
+ // TODO: Set this value appropriately.
+ public static final int GET_SUGGESTED_WORDS_TIMEOUT = 200;
+ // How many continuous deletes at which to start deleting at a higher speed.
+ public static final int DELETE_ACCELERATE_AT = 20;
+
public static boolean isValidCoordinate(final int coordinate) {
// Detect {@link NOT_A_COORDINATE}, {@link SUGGESTION_STRIP_COORDINATE},
// and {@link SPELL_CHECKER_COORDINATE}.
@@ -165,6 +184,7 @@ public final class Constants {
public static final int CODE_TAB = '\t';
public static final int CODE_SPACE = ' ';
public static final int CODE_PERIOD = '.';
+ public static final int CODE_COMMA = ',';
public static final int CODE_ARMENIAN_PERIOD = 0x0589;
public static final int CODE_DASH = '-';
public static final int CODE_SINGLE_QUOTE = '\'';
@@ -172,6 +192,8 @@ public final class Constants {
public static final int CODE_QUESTION_MARK = '?';
public static final int CODE_EXCLAMATION_MARK = '!';
public static final int CODE_SLASH = '/';
+ public static final int CODE_BACKSLASH = '\\';
+ public static final int CODE_VERTICAL_BAR = '|';
public static final int CODE_COMMERCIAL_AT = '@';
public static final int CODE_PLUS = '+';
public static final int CODE_PERCENT = '%';
@@ -197,8 +219,10 @@ public final class Constants {
public static final int CODE_LANGUAGE_SWITCH = -10;
public static final int CODE_EMOJI = -11;
public static final int CODE_SHIFT_ENTER = -12;
+ public static final int CODE_SYMBOL_SHIFT = -13;
+ public static final int CODE_ALPHA_FROM_EMOJI = -14;
// Code value representing the code is not specified.
- public static final int CODE_UNSPECIFIED = -13;
+ public static final int CODE_UNSPECIFIED = -15;
public static boolean isLetterCode(final int code) {
return code >= CODE_SPACE;
@@ -221,6 +245,7 @@ public final class Constants {
case CODE_UNSPECIFIED: return "unspec";
case CODE_TAB: return "tab";
case CODE_ENTER: return "enter";
+ case CODE_ALPHA_FROM_EMOJI: return "alpha";
default:
if (code < CODE_SPACE) return String.format("'\\u%02x'", code);
if (code < 0x100) return String.format("'%c'", code);
@@ -228,10 +253,37 @@ public final class Constants {
}
}
+ public static String printableCodes(final int[] codes) {
+ final StringBuilder sb = new StringBuilder();
+ boolean addDelimiter = false;
+ for (final int code : codes) {
+ if (code == NOT_A_CODE) break;
+ if (addDelimiter) sb.append(", ");
+ sb.append(printableCode(code));
+ addDelimiter = true;
+ }
+ return "[" + sb + "]";
+ }
+
public static final int MAX_INT_BIT_COUNT = 32;
+ /**
+ * Screen metrics (a.k.a. Device form factor) constants of
+ * {@link R.integer#config_screen_metrics}.
+ */
+ public static final int SCREEN_METRICS_SMALL_PHONE = 0;
+ public static final int SCREEN_METRICS_LARGE_PHONE = 1;
+ public static final int SCREEN_METRICS_LARGE_TABLET = 2;
+ public static final int SCREEN_METRICS_SMALL_TABLET = 3;
+
+ /**
+ * Default capacity of gesture points container.
+ * This constant is used by {@link BatchInputArbiter} and etc. to preallocate regions that
+ * contain gesture event points.
+ */
+ public static final int DEFAULT_GESTURE_POINTS_CAPACITY = 128;
+
private Constants() {
// This utility class is not publicly instantiable.
}
-
}
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 47891c6b7..d5873d70f 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -16,8 +16,6 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.latin.personalization.AccountUtils;
-
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -31,8 +29,10 @@ import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.latin.personalization.AccountUtils;
import com.android.inputmethod.latin.utils.StringUtils;
+import java.io.File;
import java.util.List;
import java.util.Locale;
@@ -44,7 +44,8 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private static final String TAG = ContactsBinaryDictionary.class.getSimpleName();
private static final String NAME = "contacts";
- private static boolean DEBUG = false;
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_DUMP = false;
/**
* Frequency for contacts information into the dictionary
@@ -71,8 +72,13 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private final boolean mUseFirstLastBigrams;
public ContactsBinaryDictionary(final Context context, final Locale locale) {
- super(context, getFilenameWithLocale(NAME, locale.toString()), Dictionary.TYPE_CONTACTS,
- false /* isUpdatable */);
+ this(context, locale, null /* dictFile */);
+ }
+
+ public ContactsBinaryDictionary(final Context context, final Locale locale,
+ final File dictFile) {
+ super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_CONTACTS,
+ dictFile);
mLocale = locale;
mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale);
registerObserver(context);
@@ -83,8 +89,6 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
}
private synchronized void registerObserver(final Context context) {
- // Perform a managed query. The Activity will handle closing and requerying the cursor
- // when needed.
if (mObserver != null) return;
ContentResolver cres = context.getContentResolver();
cres.registerContentObserver(Contacts.CONTENT_URI, true, mObserver =
@@ -96,10 +100,6 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
});
}
- public void reopen(final Context context) {
- registerObserver(context);
- }
-
@Override
public synchronized void close() {
if (mObserver != null) {
@@ -110,14 +110,14 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
}
@Override
- public void loadDictionaryAsync() {
- loadDeviceAccountsEmailAddresses();
- loadDictionaryAsyncForUri(ContactsContract.Profile.CONTENT_URI);
+ public void loadInitialContentsLocked() {
+ loadDeviceAccountsEmailAddressesLocked();
+ loadDictionaryForUriLocked(ContactsContract.Profile.CONTENT_URI);
// TODO: Switch this URL to the newer ContactsContract too
- loadDictionaryAsyncForUri(Contacts.CONTENT_URI);
+ loadDictionaryForUriLocked(Contacts.CONTENT_URI);
}
- private void loadDeviceAccountsEmailAddresses() {
+ private void loadDeviceAccountsEmailAddressesLocked() {
final List<String> accountVocabulary =
AccountUtils.getDeviceAccountsEmailAddresses(mContext);
if (accountVocabulary == null || accountVocabulary.isEmpty()) {
@@ -127,29 +127,32 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
if (DEBUG) {
Log.d(TAG, "loadAccountVocabulary: " + word);
}
- super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, 0 /* shortcutFreq */,
- false /* isNotAWord */);
+ runGCIfRequiredLocked(true /* mindsBlockByGC */);
+ addWordDynamicallyLocked(word, FREQUENCY_FOR_CONTACTS, null /* shortcut */,
+ 0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */,
+ BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
}
- private void loadDictionaryAsyncForUri(final Uri uri) {
+ private void loadDictionaryForUriLocked(final Uri uri) {
+ Cursor cursor = null;
try {
- Cursor cursor = mContext.getContentResolver()
- .query(uri, PROJECTION, null, null, null);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- sContactCountAtLastRebuild = getContactCount();
- addWords(cursor);
- }
- } finally {
- cursor.close();
- }
+ cursor = mContext.getContentResolver().query(uri, PROJECTION, null, null, null);
+ if (null == cursor) {
+ return;
+ }
+ if (cursor.moveToFirst()) {
+ sContactCountAtLastRebuild = getContactCount();
+ addWordsLocked(cursor);
}
} catch (final SQLiteException e) {
Log.e(TAG, "SQLiteException in the remote Contacts process.", e);
} catch (final IllegalStateException e) {
Log.e(TAG, "Contacts DB is having problems", e);
+ } finally {
+ if (null != cursor) {
+ cursor.close();
+ }
}
}
@@ -161,13 +164,17 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
return false;
}
- private void addWords(final Cursor cursor) {
+ private void addWordsLocked(final Cursor cursor) {
int count = 0;
while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) {
String name = cursor.getString(INDEX_NAME);
if (isValidName(name)) {
- addName(name);
+ addNameLocked(name);
++count;
+ } else {
+ if (DEBUG_DUMP) {
+ Log.d(TAG, "Invalid name: " + name);
+ }
}
cursor.moveToNext();
}
@@ -176,18 +183,20 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private int getContactCount() {
// TODO: consider switching to a rawQuery("select count(*)...") on the database if
// performance is a bottleneck.
+ Cursor cursor = null;
try {
- final Cursor cursor = mContext.getContentResolver().query(
- Contacts.CONTENT_URI, PROJECTION_ID_ONLY, null, null, null);
- if (cursor != null) {
- try {
- return cursor.getCount();
- } finally {
- cursor.close();
- }
+ cursor = mContext.getContentResolver().query(Contacts.CONTENT_URI, PROJECTION_ID_ONLY,
+ null, null, null);
+ if (null == cursor) {
+ return 0;
}
+ return cursor.getCount();
} catch (final SQLiteException e) {
Log.e(TAG, "SQLiteException in the remote Contacts process.", e);
+ } finally {
+ if (null != cursor) {
+ cursor.close();
+ }
}
return 0;
}
@@ -196,7 +205,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
* Adds the words in a name (e.g., firstname/lastname) to the binary dictionary along with their
* bigrams depending on locale.
*/
- private void addName(final String name) {
+ private void addNameLocked(final String name) {
int len = StringUtils.codePointCount(name);
String prevWord = null;
// TODO: Better tokenization for non-Latin writing systems
@@ -204,6 +213,9 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
if (Character.isLetter(name.codePointAt(i))) {
int end = getWordEndPosition(name, len, i);
String word = name.substring(i, end);
+ if (DEBUG_DUMP) {
+ Log.d(TAG, "addName word = " + word);
+ }
i = end - 1;
// Don't add single letter words, possibly confuses
// capitalization of i.
@@ -212,13 +224,15 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
if (DEBUG) {
Log.d(TAG, "addName " + name + ", " + word + ", " + prevWord);
}
- super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS,
- 0 /* shortcutFreq */, false /* isNotAWord */);
- if (!TextUtils.isEmpty(prevWord)) {
- if (mUseFirstLastBigrams) {
- super.addBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM,
- 0 /* lastModifiedTime */);
- }
+ runGCIfRequiredLocked(true /* mindsBlockByGC */);
+ addWordDynamicallyLocked(word, FREQUENCY_FOR_CONTACTS,
+ null /* shortcut */, 0 /* shortcutFreq */, false /* isNotAWord */,
+ false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+ if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) {
+ runGCIfRequiredLocked(true /* mindsBlockByGC */);
+ addBigramDynamicallyLocked(prevWord, word,
+ FREQUENCY_FOR_CONTACTS_BIGRAM,
+ BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
prevWord = word;
}
@@ -244,12 +258,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
}
@Override
- protected boolean needsToReloadBeforeWriting() {
- return true;
- }
-
- @Override
- protected boolean hasContentChanged() {
+ protected boolean haveContentsChanged() {
final long startTime = SystemClock.uptimeMillis();
final int contactCount = getContactCount();
if (contactCount > MAX_CONTACT_COUNT) {
@@ -268,26 +277,27 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
// Check all contacts since it's not possible to find out which names have changed.
// This is needed because it's possible to receive extraneous onChange events even when no
// name has changed.
- Cursor cursor = mContext.getContentResolver().query(
- Contacts.CONTENT_URI, PROJECTION, null, null, null);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- while (!cursor.isAfterLast()) {
- String name = cursor.getString(INDEX_NAME);
- if (isValidName(name) && !isNameInDictionary(name)) {
- if (DEBUG) {
- Log.d(TAG, "Contact name missing: " + name + " (runtime = "
- + (SystemClock.uptimeMillis() - startTime) + " ms)");
- }
- return true;
+ final Cursor cursor = mContext.getContentResolver().query(Contacts.CONTENT_URI, PROJECTION,
+ null, null, null);
+ if (null == cursor) {
+ return false;
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ while (!cursor.isAfterLast()) {
+ String name = cursor.getString(INDEX_NAME);
+ if (isValidName(name) && !isNameInDictionaryLocked(name)) {
+ if (DEBUG) {
+ Log.d(TAG, "Contact name missing: " + name + " (runtime = "
+ + (SystemClock.uptimeMillis() - startTime) + " ms)");
}
- cursor.moveToNext();
+ return true;
}
+ cursor.moveToNext();
}
- } finally {
- cursor.close();
}
+ } finally {
+ cursor.close();
}
if (DEBUG) {
Log.d(TAG, "No contacts changed. (runtime = " + (SystemClock.uptimeMillis() - startTime)
@@ -306,7 +316,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
/**
* Checks if the words in a name are in the current binary dictionary.
*/
- private boolean isNameInDictionary(final String name) {
+ private boolean isNameInDictionaryLocked(final String name) {
int len = StringUtils.codePointCount(name);
String prevWord = null;
for (int i = 0; i < len; i++) {
@@ -317,11 +327,11 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
final int wordLen = StringUtils.codePointCount(word);
if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) {
- if (!super.isValidBigramLocked(prevWord, word)) {
+ if (!isValidBigramLocked(prevWord, word)) {
return false;
}
} else {
- if (!super.isValidWordLocked(word)) {
+ if (!isValidWordLocked(word)) {
return false;
}
}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index fa79f5af7..0742fbde9 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -27,6 +27,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;
// The following types do not actually come from real dictionary instances, so we create
// corresponding instances.
@@ -52,13 +53,10 @@ public abstract class Dictionary {
public static final String TYPE_CONTACTS = "contacts";
// User dictionary, the system-managed one.
public static final String TYPE_USER = "user";
- // User history dictionary internal to LatinIME. This assumes bigram prediction for now.
+ // User history dictionary internal to LatinIME.
public static final String TYPE_USER_HISTORY = "history";
- // Personalization binary dictionary internal to LatinIME.
+ // Personalization dictionary.
public static final String TYPE_PERSONALIZATION = "personalization";
- // Personalization prediction dictionary internal to LatinIME's Java code.
- public static final String TYPE_PERSONALIZATION_PREDICTION_IN_JAVA =
- "personalization_prediction_in_java";
public final String mDictType;
public Dictionary(final String dictType) {
@@ -73,22 +71,26 @@ public abstract class Dictionary {
* @param proximityInfo the object for key proximity. May be ignored by some implementations.
* @param blockOffensiveWords whether to block potentially offensive words
* @param additionalFeaturesOptions options about additional features used for the suggestion.
+ * @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.
* @return the list of suggestions (possibly null if none)
*/
// TODO: pass more context than just the previous word, to enable better suggestions (n-gram
// and more)
abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions);
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final float[] inOutLanguageWeight);
// The default implementation of this method ignores sessionId.
// Subclasses that want to use sessionId need to override this method.
public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final int sessionId) {
+ final int sessionId, final float[] inOutLanguageWeight) {
return getSuggestions(composer, prevWord, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions);
+ additionalFeaturesOptions, inOutLanguageWeight);
}
/**
@@ -162,7 +164,8 @@ public abstract class Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final float[] inOutLanguageWeight) {
return null;
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index bf075140e..16173fffc 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -58,18 +58,21 @@ public final class DictionaryCollection extends Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final float[] inOutLanguageWeight) {
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,
- prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions);
+ prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
+ inOutLanguageWeight);
if (null == suggestions) suggestions = CollectionUtils.newArrayList();
final int length = dictionaries.size();
for (int i = 1; i < length; ++ i) {
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
- prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions);
+ prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
+ inOutLanguageWeight);
if (null != sugg) suggestions.addAll(sugg);
}
return suggestions;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryDumpBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryDumpBroadcastReceiver.java
new file mode 100644
index 000000000..ee2fdc6c7
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryDumpBroadcastReceiver.java
@@ -0,0 +1,50 @@
+/*
+ * 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.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class DictionaryDumpBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = DictionaryDumpBroadcastReceiver.class.getSimpleName();
+
+ private static final String DOMAIN = "com.android.inputmethod.latin";
+ public static final String DICTIONARY_DUMP_INTENT_ACTION = DOMAIN + ".DICT_DUMP";
+ public static final String DICTIONARY_NAME_KEY = "dictName";
+
+ final LatinIME mLatinIme;
+
+ public DictionaryDumpBroadcastReceiver(final LatinIME latinIme) {
+ mLatinIme = latinIme;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(DICTIONARY_DUMP_INTENT_ACTION)) {
+ final String dictName = intent.getStringExtra(DICTIONARY_NAME_KEY);
+ if (dictName == null) {
+ Log.e(TAG, "Received dictionary dump intent action " +
+ "but the dictionary name is not set.");
+ return;
+ }
+ mLatinIme.dumpDictionaryForDebug(dictName);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
new file mode 100644
index 000000000..0b6258a7f
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
+import com.android.inputmethod.latin.personalization.PersonalizationHelper;
+import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+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;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+// TODO: Consolidate dictionaries in native code.
+public class DictionaryFacilitatorForSuggest {
+ public static final String TAG = DictionaryFacilitatorForSuggest.class.getSimpleName();
+
+ // HACK: This threshold is being used when adding a capitalized entry in the User History
+ // dictionary.
+ private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
+
+ private Dictionaries mDictionaries = new Dictionaries();
+ private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
+ // To synchronize assigning mDictionaries to ensure closing dictionaries.
+ private Object mLock = new Object();
+
+ private static final String[] dictTypesOrderedToGetSuggestion =
+ new String[] {
+ Dictionary.TYPE_MAIN,
+ Dictionary.TYPE_USER_HISTORY,
+ Dictionary.TYPE_PERSONALIZATION,
+ Dictionary.TYPE_USER,
+ Dictionary.TYPE_CONTACTS
+ };
+
+ /**
+ * Class contains dictionaries for a locale.
+ */
+ private static class Dictionaries {
+ public final Locale mLocale;
+ public final ConcurrentHashMap<String, Dictionary> mDictMap =
+ CollectionUtils.newConcurrentHashMap();
+ // Main dictionary will be asynchronously loaded.
+ public Dictionary mMainDictionary;
+ public final ContactsBinaryDictionary mContactsDictionary;
+ public final UserBinaryDictionary mUserDictionary;
+ public final UserHistoryDictionary mUserHistoryDictionary;
+ public final PersonalizationDictionary mPersonalizationDictionary;
+
+ public Dictionaries() {
+ mLocale = null;
+ mMainDictionary = null;
+ mContactsDictionary = null;
+ mUserDictionary = null;
+ mUserHistoryDictionary = null;
+ mPersonalizationDictionary = null;
+ }
+
+ public Dictionaries(final Locale locale, final Dictionary mainDict,
+ final ContactsBinaryDictionary contactsDict, final UserBinaryDictionary userDict,
+ final UserHistoryDictionary userHistoryDict,
+ final PersonalizationDictionary personalizationDict) {
+ mLocale = locale;
+ setMainDict(mainDict);
+ mContactsDictionary = contactsDict;
+ if (mContactsDictionary != null) {
+ mDictMap.put(Dictionary.TYPE_CONTACTS, mContactsDictionary);
+ }
+ mUserDictionary = userDict;
+ if (mUserDictionary != null) {
+ mDictMap.put(Dictionary.TYPE_USER, mUserDictionary);
+ }
+ mUserHistoryDictionary = userHistoryDict;
+ if (mUserHistoryDictionary != null) {
+ mDictMap.put(Dictionary.TYPE_USER_HISTORY, mUserHistoryDictionary);
+ }
+ mPersonalizationDictionary = personalizationDict;
+ if (mPersonalizationDictionary != null) {
+ mDictMap.put(Dictionary.TYPE_PERSONALIZATION, mPersonalizationDictionary);
+ }
+ }
+
+ public void setMainDict(final Dictionary mainDict) {
+ mMainDictionary = mainDict;
+ // Close old dictionary if exists. Main dictionary can be assigned multiple times.
+ final Dictionary oldDict;
+ if (mMainDictionary != null) {
+ oldDict = mDictMap.put(Dictionary.TYPE_MAIN, mMainDictionary);
+ } else {
+ oldDict = mDictMap.remove(Dictionary.TYPE_MAIN);
+ }
+ if (oldDict != null && mMainDictionary != oldDict) {
+ oldDict.close();
+ }
+ }
+
+ public boolean hasMainDict() {
+ return mMainDictionary != null;
+ }
+
+ public boolean hasContactsDict() {
+ return mContactsDictionary != null;
+ }
+
+ public boolean hasUserDict() {
+ return mUserDictionary != null;
+ }
+
+ public boolean hasUserHistoryDict() {
+ return mUserHistoryDictionary != null;
+ }
+
+ public boolean hasPersonalizationDict() {
+ return mPersonalizationDictionary != null;
+ }
+ }
+
+ public interface DictionaryInitializationListener {
+ public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
+ }
+
+ public DictionaryFacilitatorForSuggest() {}
+
+ public Locale getLocale() {
+ return mDictionaries.mLocale;
+ }
+
+ public void resetDictionaries(final Context context, final Locale newLocale,
+ final boolean useContactsDict, final boolean usePersonalizedDicts,
+ final boolean forceReloadMainDictionary,
+ final DictionaryInitializationListener listener) {
+ 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 boolean closeContactsDictionary = localeHasBeenChanged || !useContactsDict;
+ final boolean closeUserDictionary = localeHasBeenChanged;
+ final boolean closeUserHistoryDictionary = localeHasBeenChanged || !usePersonalizedDicts;
+ final boolean closePersonalizationDictionary =
+ localeHasBeenChanged || !usePersonalizedDicts;
+
+ final Dictionary newMainDict;
+ if (reloadMainDictionary) {
+ // The main dictionary will be asynchronously loaded.
+ newMainDict = null;
+ } else {
+ newMainDict = mDictionaries.mMainDictionary;
+ }
+
+ // Open or move contacts dictionary.
+ final ContactsBinaryDictionary newContactsDict;
+ if (!closeContactsDictionary && mDictionaries.hasContactsDict()) {
+ newContactsDict = mDictionaries.mContactsDictionary;
+ } else if (useContactsDict) {
+ newContactsDict = new ContactsBinaryDictionary(context, newLocale);
+ } else {
+ newContactsDict = null;
+ }
+
+ // Open or move user dictionary.
+ final UserBinaryDictionary newUserDictionary;
+ if (!closeUserDictionary && mDictionaries.hasUserDict()) {
+ newUserDictionary = mDictionaries.mUserDictionary;
+ } else {
+ newUserDictionary = new UserBinaryDictionary(context, newLocale);
+ }
+
+ // Open or move user history dictionary.
+ final UserHistoryDictionary newUserHistoryDict;
+ if (!closeUserHistoryDictionary && mDictionaries.hasUserHistoryDict()) {
+ newUserHistoryDict = mDictionaries.mUserHistoryDictionary;
+ } else if (usePersonalizedDicts) {
+ newUserHistoryDict = PersonalizationHelper.getUserHistoryDictionary(context, newLocale);
+ } else {
+ newUserHistoryDict = null;
+ }
+
+ // Open or move personalization dictionary.
+ final PersonalizationDictionary newPersonalizationDict;
+ if (!closePersonalizationDictionary && mDictionaries.hasPersonalizationDict()) {
+ newPersonalizationDict = mDictionaries.mPersonalizationDictionary;
+ } else if (usePersonalizedDicts) {
+ newPersonalizationDict =
+ PersonalizationHelper.getPersonalizationDictionary(context, newLocale);
+ } else {
+ newPersonalizationDict = null;
+ }
+
+ // Replace Dictionaries.
+ final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict,
+ newContactsDict, newUserDictionary, newUserHistoryDict, newPersonalizationDict);
+ if (listener != null) {
+ listener.onUpdateMainDictionaryAvailability(newDictionaries.hasMainDict());
+ }
+ final Dictionaries oldDictionaries;
+ synchronized (mLock) {
+ oldDictionaries = mDictionaries;
+ mDictionaries = newDictionaries;
+ if (reloadMainDictionary) {
+ asyncReloadMainDictionary(context, newLocale, listener);
+ }
+ }
+
+ // Clean up old dictionaries.
+ oldDictionaries.mDictMap.clear();
+ if (reloadMainDictionary && oldDictionaries.hasMainDict()) {
+ oldDictionaries.mMainDictionary.close();
+ }
+ if (closeContactsDictionary && oldDictionaries.hasContactsDict()) {
+ oldDictionaries.mContactsDictionary.close();
+ }
+ if (closeUserDictionary && oldDictionaries.hasUserDict()) {
+ oldDictionaries.mUserDictionary.close();
+ }
+ if (closeUserHistoryDictionary && oldDictionaries.hasUserHistoryDict()) {
+ oldDictionaries.mUserHistoryDictionary.close();
+ }
+ if (closePersonalizationDictionary && oldDictionaries.hasPersonalizationDict()) {
+ oldDictionaries.mPersonalizationDictionary.close();
+ }
+ }
+
+ private void asyncReloadMainDictionary(final Context context, final Locale locale,
+ final DictionaryInitializationListener listener) {
+ final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
+ mLatchForWaitingLoadingMainDictionary = 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();
+ }
+ }
+ if (listener != null) {
+ listener.onUpdateMainDictionaryAvailability(mDictionaries.hasMainDict());
+ }
+ latchForWaitingLoadingMainDictionary.countDown();
+ }
+ });
+ }
+
+ @UsedForTesting
+ public void resetDictionariesForTesting(final Context context, final Locale locale,
+ final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles,
+ final Map<String, Map<String, String>> additionalDictAttributes) {
+ Dictionary mainDictionary = null;
+ ContactsBinaryDictionary contactsDictionary = null;
+ UserBinaryDictionary userDictionary = null;
+ UserHistoryDictionary userHistoryDictionary = null;
+ PersonalizationDictionary personalizationDictionary = null;
+
+ for (final String dictType : dictionaryTypes) {
+ if (dictType.equals(Dictionary.TYPE_MAIN)) {
+ mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context, locale);
+ } else if (dictType.equals(Dictionary.TYPE_USER_HISTORY)) {
+ userHistoryDictionary =
+ PersonalizationHelper.getUserHistoryDictionary(context, locale);
+ // Staring with an empty user history dictionary for testing.
+ // Testing program may populate this dictionary before actual testing.
+ userHistoryDictionary.reloadDictionaryIfRequired();
+ userHistoryDictionary.waitAllTasksForTests();
+ if (additionalDictAttributes.containsKey(dictType)) {
+ userHistoryDictionary.clearAndFlushDictionaryWithAdditionalAttributes(
+ additionalDictAttributes.get(dictType));
+ }
+ } else if (dictType.equals(Dictionary.TYPE_PERSONALIZATION)) {
+ personalizationDictionary =
+ PersonalizationHelper.getPersonalizationDictionary(context, locale);
+ // Staring with an empty personalization dictionary for testing.
+ // Testing program may populate this dictionary before actual testing.
+ personalizationDictionary.reloadDictionaryIfRequired();
+ personalizationDictionary.waitAllTasksForTests();
+ if (additionalDictAttributes.containsKey(dictType)) {
+ personalizationDictionary.clearAndFlushDictionaryWithAdditionalAttributes(
+ additionalDictAttributes.get(dictType));
+ }
+ } else if (dictType.equals(Dictionary.TYPE_USER)) {
+ final File file = dictionaryFiles.get(dictType);
+ userDictionary = new UserBinaryDictionary(context, locale, file);
+ userDictionary.reloadDictionaryIfRequired();
+ userDictionary.waitAllTasksForTests();
+ } else if (dictType.equals(Dictionary.TYPE_CONTACTS)) {
+ final File file = dictionaryFiles.get(dictType);
+ contactsDictionary = new ContactsBinaryDictionary(context, locale, file);
+ contactsDictionary.reloadDictionaryIfRequired();
+ contactsDictionary.waitAllTasksForTests();
+ } else {
+ throw new RuntimeException("Unknown dictionary type: " + dictType);
+ }
+ }
+ mDictionaries = new Dictionaries(locale, mainDictionary, contactsDictionary,
+ userDictionary, userHistoryDictionary, personalizationDictionary);
+ }
+
+ public void closeDictionaries() {
+ final Dictionaries dictionaries;
+ synchronized (mLock) {
+ dictionaries = mDictionaries;
+ mDictionaries = new Dictionaries();
+ }
+ if (dictionaries.hasMainDict()) {
+ dictionaries.mMainDictionary.close();
+ }
+ if (dictionaries.hasContactsDict()) {
+ dictionaries.mContactsDictionary.close();
+ }
+ if (dictionaries.hasUserDict()) {
+ dictionaries.mUserDictionary.close();
+ }
+ if (dictionaries.hasUserHistoryDict()) {
+ dictionaries.mUserHistoryDictionary.close();
+ }
+ if (dictionaries.hasPersonalizationDict()) {
+ dictionaries.mPersonalizationDictionary.close();
+ }
+ }
+
+ // The main dictionary could have been loaded asynchronously. Don't cache the return value
+ // of this method.
+ public boolean hasInitializedMainDictionary() {
+ final Dictionaries dictionaries = mDictionaries;
+ return dictionaries.hasMainDict() && dictionaries.mMainDictionary.isInitialized();
+ }
+
+ public boolean hasPersonalizationDictionary() {
+ return mDictionaries.hasPersonalizationDict();
+ }
+
+ public void flushPersonalizationDictionary() {
+ final PersonalizationDictionary personalizationDict =
+ mDictionaries.mPersonalizationDictionary;
+ if (personalizationDict != null) {
+ personalizationDict.flush();
+ }
+ }
+
+ public void waitForLoadingMainDictionary(final long timeout, final TimeUnit unit)
+ throws InterruptedException {
+ mLatchForWaitingLoadingMainDictionary.await(timeout, unit);
+ }
+
+ @UsedForTesting
+ public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
+ throws InterruptedException {
+ waitForLoadingMainDictionary(timeout, unit);
+ final Dictionaries dictionaries = mDictionaries;
+ if (dictionaries.hasContactsDict()) {
+ dictionaries.mContactsDictionary.waitAllTasksForTests();
+ }
+ if (dictionaries.hasUserDict()) {
+ dictionaries.mUserDictionary.waitAllTasksForTests();
+ }
+ if (dictionaries.hasUserHistoryDict()) {
+ dictionaries.mUserHistoryDictionary.waitAllTasksForTests();
+ }
+ if (dictionaries.hasPersonalizationDict()) {
+ dictionaries.mPersonalizationDictionary.waitAllTasksForTests();
+ }
+ }
+
+ public boolean isUserDictionaryEnabled() {
+ final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary;
+ if (userDictionary == null) {
+ return false;
+ }
+ return userDictionary.mEnabled;
+ }
+
+ public void addWordToUserDictionary(String word) {
+ final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary;
+ if (userDictionary == null) {
+ return;
+ }
+ userDictionary.addWordToUserDictionary(word);
+ }
+
+ public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
+ final String previousWord, final int timeStampInSeconds) {
+ final Dictionaries dictionaries = mDictionaries;
+ if (!dictionaries.hasUserHistoryDict()) {
+ return;
+ }
+ final int maxFreq = getMaxFrequency(suggestion);
+ if (maxFreq == 0) {
+ return;
+ }
+ final String suggestionLowerCase = suggestion.toLowerCase(dictionaries.mLocale);
+ final String secondWord;
+ if (wasAutoCapitalized) {
+ if (isValidWord(suggestion, false /* ignoreCase */)
+ && !isValidWord(suggestionLowerCase, false /* ignoreCase */)) {
+ // If the word was auto-capitalized and exists only as a capitalized word in the
+ // dictionary, then we must not downcase it before registering it. For example,
+ // the name of the contacts in start-of-sentence position would come here with the
+ // wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version
+ // of that contact's name which would end up popping in suggestions.
+ secondWord = suggestion;
+ } else {
+ // If however the word is not in the dictionary, or exists as a lower-case word
+ // only, then we consider that was a lower-case word that had been auto-capitalized.
+ secondWord = suggestionLowerCase;
+ }
+ } else {
+ // HACK: We'd like to avoid adding the capitalized form of common words to the User
+ // History dictionary in order to avoid suggesting them until the dictionary
+ // consolidation is done.
+ // TODO: Remove this hack when ready.
+ final int lowerCaseFreqInMainDict = dictionaries.hasMainDict() ?
+ dictionaries.mMainDictionary.getFrequency(suggestionLowerCase) :
+ Dictionary.NOT_A_PROBABILITY;
+ if (maxFreq < lowerCaseFreqInMainDict
+ && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
+ // Use lower cased word as the word can be a distracter of the popular word.
+ secondWord = suggestionLowerCase;
+ } else {
+ secondWord = suggestion;
+ }
+ }
+ // 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;
+ dictionaries.mUserHistoryDictionary.addToDictionary(
+ previousWord, secondWord, isValid, timeStampInSeconds);
+ }
+
+ public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
+ final UserHistoryDictionary userHistoryDictionary = mDictionaries.mUserHistoryDictionary;
+ if (userHistoryDictionary != null) {
+ userHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
+ }
+ }
+
+ // TODO: Revise the way to fusion suggestion results.
+ public SuggestionResults getSuggestionResults(final WordComposer composer,
+ final String prevWord, final ProximityInfo proximityInfo,
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final int sessionId, final ArrayList<SuggestedWordInfo> rawSuggestions) {
+ final Dictionaries dictionaries = mDictionaries;
+ final Map<String, Dictionary> dictMap = dictionaries.mDictMap;
+ final SuggestionResults suggestionResults =
+ new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS);
+ final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT };
+ for (final String dictType : dictTypesOrderedToGetSuggestion) {
+ final Dictionary dictionary = dictMap.get(dictType);
+ if (null == dictionary) continue;
+ final ArrayList<SuggestedWordInfo> dictionarySuggestions =
+ dictionary.getSuggestionsWithSessionId(composer, prevWord, proximityInfo,
+ blockOffensiveWords, additionalFeaturesOptions, sessionId,
+ languageWeight);
+ if (null == dictionarySuggestions) continue;
+ suggestionResults.addAll(dictionarySuggestions);
+ if (null != rawSuggestions) {
+ rawSuggestions.addAll(dictionarySuggestions);
+ }
+ }
+ return suggestionResults;
+ }
+
+ public boolean isValidMainDictWord(final String word) {
+ final Dictionaries dictionaries = mDictionaries;
+ if (TextUtils.isEmpty(word) || !dictionaries.hasMainDict()) {
+ return false;
+ }
+ return dictionaries.mMainDictionary.isValidWord(word);
+ }
+
+ public boolean isValidWord(final String word, final boolean ignoreCase) {
+ if (TextUtils.isEmpty(word)) {
+ return false;
+ }
+ final Dictionaries dictionaries = mDictionaries;
+ if (dictionaries.mLocale == null) {
+ return false;
+ }
+ final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
+ final Map<String, Dictionary> dictMap = dictionaries.mDictMap;
+ for (final Dictionary dictionary : dictMap.values()) {
+ // 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;
+ }
+
+ private int getMaxFrequency(final String word) {
+ if (TextUtils.isEmpty(word)) {
+ return Dictionary.NOT_A_PROBABILITY;
+ }
+ int maxFreq = -1;
+ final Map<String, Dictionary> dictMap = mDictionaries.mDictMap;
+ for (final Dictionary dictionary : dictMap.values()) {
+ final int tempFreq = dictionary.getFrequency(word);
+ if (tempFreq >= maxFreq) {
+ maxFreq = tempFreq;
+ }
+ }
+ return maxFreq;
+ }
+
+
+ public void clearUserHistoryDictionary() {
+ final UserHistoryDictionary userHistoryDict = mDictionaries.mUserHistoryDictionary;
+ if (userHistoryDict == null) {
+ return;
+ }
+ userHistoryDict.clearAndFlushDictionary();
+ }
+
+ // This method gets called only when the IME receives a notification to remove the
+ // personalization dictionary.
+ public void clearPersonalizationDictionary() {
+ final PersonalizationDictionary personalizationDict =
+ mDictionaries.mPersonalizationDictionary;
+ if (personalizationDict == null) {
+ return;
+ }
+ personalizationDict.clearAndFlushDictionary();
+ }
+
+ public void addMultipleDictionaryEntriesToPersonalizationDictionary(
+ final ArrayList<LanguageModelParam> languageModelParams,
+ final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
+ final PersonalizationDictionary personalizationDict =
+ mDictionaries.mPersonalizationDictionary;
+ if (personalizationDict == null) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ personalizationDict.addMultipleDictionaryEntriesToDictionary(languageModelParams, callback);
+ }
+
+ public void dumpDictionaryForDebug(final String dictName) {
+ final ExpandableBinaryDictionary dictToDump;
+ if (dictName.equals(Dictionary.TYPE_CONTACTS)) {
+ dictToDump = mDictionaries.mContactsDictionary;
+ } else if (dictName.equals(Dictionary.TYPE_USER)) {
+ dictToDump = mDictionaries.mUserDictionary;
+ } else if (dictName.equals(Dictionary.TYPE_USER_HISTORY)) {
+ dictToDump = mDictionaries.mUserHistoryDictionary;
+ } else if (dictName.equals(Dictionary.TYPE_PERSONALIZATION)) {
+ dictToDump = mDictionaries.mPersonalizationDictionary;
+ } else {
+ dictToDump = null;
+ }
+ if (dictToDump == null) {
+ Log.e(TAG, "Cannot dump " + dictName + ". "
+ + "The dictionary is not being used for suggestion or cannot be dumped.");
+ return;
+ }
+ dictToDump.dumpAllWordsForDebug();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 828e54f14..e09c309ea 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin;
+import android.content.ContentProviderClient;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
@@ -64,6 +65,10 @@ public final class DictionaryFactory {
useFullEditDistance, locale, Dictionary.TYPE_MAIN);
if (readOnlyBinaryDictionary.isValidDictionary()) {
dictList.add(readOnlyBinaryDictionary);
+ } else {
+ readOnlyBinaryDictionary.close();
+ // Prevent this dictionary to do any further harm.
+ killDictionary(context, f);
}
}
}
@@ -75,6 +80,51 @@ public final class DictionaryFactory {
}
/**
+ * Kills a dictionary so that it is never used again, if possible.
+ * @param context The context to contact the dictionary provider, if possible.
+ * @param f A file address to the dictionary to kill.
+ */
+ private static void killDictionary(final Context context, final AssetFileAddress f) {
+ if (f.pointsToPhysicalFile()) {
+ f.deleteUnderlyingFile();
+ // Warn the dictionary provider if the dictionary came from there.
+ final ContentProviderClient providerClient;
+ try {
+ providerClient = context.getContentResolver().acquireContentProviderClient(
+ BinaryDictionaryFileDumper.getProviderUriBuilder("").build());
+ } catch (final SecurityException e) {
+ Log.e(TAG, "No permission to communicate with the dictionary provider", e);
+ return;
+ }
+ if (null == providerClient) {
+ Log.e(TAG, "Can't establish communication with the dictionary provider");
+ return;
+ }
+ final String wordlistId =
+ DictionaryInfoUtils.getWordListIdFromFileName(new File(f.mFilename).getName());
+ if (null != wordlistId) {
+ // TODO: this is a reasonable last resort, but it is suboptimal.
+ // The following will remove the entry for this dictionary with the dictionary
+ // provider. When the metadata is downloaded again, we will try downloading it
+ // again.
+ // However, in the practice that will mean the user will find themselves without
+ // the new dictionary. That's fine for languages where it's included in the APK,
+ // but for other languages it will leave the user without a dictionary at all until
+ // the next update, which may be a few days away.
+ // Ideally, we would trigger a new download right away, and use increasing retry
+ // delays for this particular id/version combination.
+ // Then again, this is expected to only ever happen in case of human mistake. If
+ // the wrong file is on the server, the following is still doing the right thing.
+ // If it's a file left over from the last version however, it's not great.
+ BinaryDictionaryFileDumper.reportBrokenFileToDictionaryProvider(
+ providerClient,
+ context.getString(R.string.dictionary_pack_client_id),
+ wordlistId);
+ }
+ }
+ }
+
+ /**
* Initializes a main dictionary collection from a dictionary pack, with default flags.
*
* This searches for a content provider providing a dictionary pack for the specified
diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
deleted file mode 100644
index 3df2a2b63..000000000
--- a/java/src/com/android/inputmethod/latin/DictionaryWriter.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.makedict.DictEncoder;
-import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * An in memory dictionary for memorizing entries and writing a binary dictionary.
- */
-public class DictionaryWriter extends AbstractDictionaryWriter {
- private static final int BINARY_DICT_VERSION = 3;
- private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
- new FormatSpec.FormatOptions(BINARY_DICT_VERSION, true /* supportsDynamicUpdate */);
-
- private FusionDictionary mFusionDictionary;
-
- public DictionaryWriter(final Context context, final String dictType) {
- super(context, dictType);
- clear();
- }
-
- @Override
- public void clear() {
- final HashMap<String, String> attributes = CollectionUtils.newHashMap();
- mFusionDictionary = new FusionDictionary(new PtNodeArray(),
- new FusionDictionary.DictionaryOptions(attributes, false, false));
- }
-
- /**
- * Adds a word unigram to the fusion dictionary.
- */
- // TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries,
- // considering performance regression.
- @Override
- public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
- final int shortcutFreq, final boolean isNotAWord) {
- if (shortcutTarget == null) {
- mFusionDictionary.add(word, frequency, null, isNotAWord);
- } else {
- // TODO: Do this in the subclass, with this class taking an arraylist.
- final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
- shortcutTargets.add(new WeightedString(shortcutTarget, shortcutFreq));
- mFusionDictionary.add(word, frequency, shortcutTargets, isNotAWord);
- }
- }
-
- @Override
- public void addBigramWords(final String word0, final String word1, final int frequency,
- final boolean isValid, final long lastModifiedTime) {
- mFusionDictionary.setBigram(word0, word1, frequency);
- }
-
- @Override
- public void removeBigramWords(final String word0, final String word1) {
- // This class don't support removing bigram words.
- }
-
- @Override
- protected void writeDictionary(final DictEncoder dictEncoder,
- final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException {
- for (final Map.Entry<String, String> entry : attributeMap.entrySet()) {
- mFusionDictionary.addOptionAttribute(entry.getKey(), entry.getValue());
- }
- dictEncoder.writeDictionary(mFusionDictionary, FORMAT_OPTIONS);
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- // This class doesn't support suggestion.
- return null;
- }
-
- @Override
- public boolean isValidWord(String word) {
- // This class doesn't support dictionary retrieval.
- return false;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index eb8650e6f..64e9d2b51 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -17,23 +17,31 @@
package com.android.inputmethod.latin;
import android.content.Context;
-import android.os.SystemClock;
import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.personalization.DynamicPersonalizationDictionaryWriter;
+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.utils.AsyncResultHolder;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.PrioritizedSerialExecutor;
+import com.android.inputmethod.latin.utils.CombinedFormatUtils;
+import com.android.inputmethod.latin.utils.ExecutorUtils;
+import com.android.inputmethod.latin.utils.FileUtils;
+import com.android.inputmethod.latin.utils.LanguageModelParam;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -52,34 +60,29 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/** Whether to print debug output to log */
private static boolean DEBUG = false;
-
- // TODO: Remove.
- /** Whether to call binary dictionary dynamically updating methods. */
- public static boolean ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE = true;
+ private static final boolean DBG_STRESS_TEST = false;
private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
+ private static final int TIMEOUT_FOR_READ_OPS_FOR_TESTS_IN_MILLISECONDS = 10000;
+
+ private static final int DEFAULT_MAX_UNIGRAM_COUNT = 10000;
+ private static final int DEFAULT_MAX_BIGRAM_COUNT = 10000;
/**
* The maximum length of a word in this dictionary.
*/
protected static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
- private static final int DICTIONARY_FORMAT_VERSION = 3;
-
- private static final String SUPPORTS_DYNAMIC_UPDATE =
- FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE;
+ private static final int DICTIONARY_FORMAT_VERSION = FormatSpec.VERSION4;
/**
* A static map of update controllers, each of which records the time of accesses to a single
* binary dictionary file and tracks whether the file is regenerating. The key for this map is
- * the filename and the value is the shared dictionary time recorder associated with that
- * filename.
+ * the dictionary name and the value is the shared dictionary time recorder associated with
+ * that dictionary name.
*/
private static final ConcurrentHashMap<String, DictionaryUpdateController>
- sFilenameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap();
-
- private static final ConcurrentHashMap<String, PrioritizedSerialExecutor>
- sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap();
+ sDictNameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap();
/** The application context. */
protected final Context mContext;
@@ -90,23 +93,22 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
private BinaryDictionary mBinaryDictionary;
- // TODO: Remove and handle dictionaries in native code.
- /** The in-memory dictionary used to generate the binary dictionary. */
- protected AbstractDictionaryWriter mDictionaryWriter;
-
/**
- * The name of this dictionary, used as the filename for storing the binary dictionary. Multiple
- * dictionary instances with the same filename is supported, with access controlled by
- * DictionaryTimeRecorder.
+ * The name of this dictionary, used as a part of the filename for storing the binary
+ * dictionary. Multiple dictionary instances with the same name is supported, with access
+ * controlled by DictionaryUpdateController.
*/
- private final String mFilename;
+ private final String mDictName;
- /** Whether to support dynamically updating the dictionary */
- private final boolean mIsUpdatable;
+ /** Dictionary locale */
+ private final Locale mLocale;
+
+ /** Dictionary file */
+ private final File mDictFile;
// TODO: remove, once dynamic operations is serialized
/** Controls updating the shared binary dictionary file across multiple instances. */
- private final DictionaryUpdateController mFilenameDictionaryUpdateController;
+ private final DictionaryUpdateController mDictNameDictionaryUpdateController;
// TODO: remove, once dynamic operations is serialized
/** Controls updating the local binary dictionary for this instance. */
@@ -114,90 +116,82 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
new DictionaryUpdateController();
/* A extension for a binary dictionary file. */
- public static final String DICT_FILE_EXTENSION = ".dict";
+ protected static final String DICT_FILE_EXTENSION = ".dict";
private final AtomicReference<Runnable> mUnfinishedFlushingTask =
new AtomicReference<Runnable>();
/**
- * Abstract method for loading the unigrams and bigrams of a given dictionary in a background
- * thread.
+ * Abstract method for loading initial contents of a given dictionary.
*/
- protected abstract void loadDictionaryAsync();
+ protected abstract void loadInitialContentsLocked();
/**
- * Indicates that the source dictionary content has changed and a rebuild of the binary file is
- * required. If it returns false, the next reload will only read the current binary dictionary
- * from file. Note that the shared binary dictionary is locked when this is called.
+ * Indicates that the source dictionary contents have changed and a rebuild of the binary file
+ * is required. If it returns false, the next reload will only read the current binary
+ * dictionary from file. Note that the shared binary dictionary is locked when this is called.
*/
- protected abstract boolean hasContentChanged();
+ protected abstract boolean haveContentsChanged();
+
+ private boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) {
+ return formatVersion == FormatSpec.VERSION4;
+ }
+
+ private boolean needsToMigrateDictionary(final int formatVersion) {
+ // TODO: Check version.
+ return false;
+ }
+
+ public boolean isValidDictionaryLocked() {
+ return mBinaryDictionary.isValidDictionary();
+ }
/**
- * Gets the dictionary update controller for the given filename.
+ * Gets the dictionary update controller for the given dictionary name.
*/
private static DictionaryUpdateController getDictionaryUpdateController(
- String filename) {
- DictionaryUpdateController recorder = sFilenameDictionaryUpdateControllerMap.get(filename);
+ final String dictName) {
+ DictionaryUpdateController recorder = sDictNameDictionaryUpdateControllerMap.get(dictName);
if (recorder == null) {
- synchronized(sFilenameDictionaryUpdateControllerMap) {
+ synchronized(sDictNameDictionaryUpdateControllerMap) {
recorder = new DictionaryUpdateController();
- sFilenameDictionaryUpdateControllerMap.put(filename, recorder);
+ sDictNameDictionaryUpdateControllerMap.put(dictName, recorder);
}
}
return recorder;
}
/**
- * Gets the executor for the given filename.
- */
- private static PrioritizedSerialExecutor getExecutor(final String filename) {
- PrioritizedSerialExecutor executor = sFilenameExecutorMap.get(filename);
- if (executor == null) {
- synchronized(sFilenameExecutorMap) {
- executor = new PrioritizedSerialExecutor();
- sFilenameExecutorMap.put(filename, executor);
- }
- }
- return executor;
- }
-
- private static AbstractDictionaryWriter getDictionaryWriter(final Context context,
- final String dictType, final boolean isDynamicPersonalizationDictionary) {
- if (isDynamicPersonalizationDictionary) {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- return null;
- } else {
- return new DynamicPersonalizationDictionaryWriter(context, dictType);
- }
- } else {
- return new DictionaryWriter(context, dictType);
- }
- }
-
- /**
* Creates a new expandable binary dictionary.
*
* @param context The application context of the parent.
- * @param filename The filename for this binary dictionary. Multiple dictionaries with the same
- * filename is supported.
+ * @param dictName The name of the dictionary. Multiple instances with the same
+ * name is supported.
+ * @param locale the dictionary locale.
* @param dictType the dictionary type, as a human-readable string
- * @param isUpdatable whether to support dynamically updating the dictionary. Please note that
- * dynamic dictionary has negative effects on memory space and computation time.
+ * @param dictFile dictionary file path. if null, use default dictionary path based on
+ * dictionary type.
*/
- public ExpandableBinaryDictionary(final Context context, final String filename,
- final String dictType, final boolean isUpdatable) {
+ public ExpandableBinaryDictionary(final Context context, final String dictName,
+ final Locale locale, final String dictType, final File dictFile) {
super(dictType);
- mFilename = filename;
+ mDictName = dictName;
mContext = context;
- mIsUpdatable = isUpdatable;
+ mLocale = locale;
+ mDictFile = getDictFile(context, dictName, dictFile);
mBinaryDictionary = null;
- mFilenameDictionaryUpdateController = getDictionaryUpdateController(filename);
- // Currently, only dynamic personalization dictionary is updatable.
- mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable);
+ mDictNameDictionaryUpdateController = getDictionaryUpdateController(dictName);
}
- protected static String getFilenameWithLocale(final String name, final String localeStr) {
- return name + "." + localeStr + DICT_FILE_EXTENSION;
+ public static File getDictFile(final Context context, final String dictName,
+ final File dictFile) {
+ return (dictFile != null) ? dictFile
+ : new File(context.getFilesDir(), dictName + DICT_FILE_EXTENSION);
+ }
+
+ public static String getDictName(final String name, final Locale locale,
+ final File dictFile) {
+ return dictFile != null ? dictFile.getName() : name + "." + locale.toString();
}
/**
@@ -205,23 +199,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
@Override
public void close() {
- getExecutor(mFilename).execute(new Runnable() {
- @Override
- public void run() {
- if (mBinaryDictionary!= null) {
- mBinaryDictionary.close();
- mBinaryDictionary = null;
- }
- if (mDictionaryWriter != null) {
- mDictionaryWriter.close();
- }
- }
- });
- }
-
- protected void closeBinaryDictionary() {
- // Ensure that no other threads are accessing the local binary dictionary.
- getExecutor(mFilename).execute(new Runnable() {
+ ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
if (mBinaryDictionary != null) {
@@ -234,80 +212,82 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected Map<String, String> getHeaderAttributeMap() {
HashMap<String, String> attributeMap = new HashMap<String, String>();
- attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE,
- SUPPORTS_DYNAMIC_UPDATE);
- attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mFilename);
+ attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, mDictName);
+ attributeMap.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, mLocale.toString());
+ attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY,
+ String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
+ attributeMap.put(DictionaryHeader.MAX_UNIGRAM_COUNT_KEY,
+ String.valueOf(DEFAULT_MAX_UNIGRAM_COUNT));
+ attributeMap.put(DictionaryHeader.MAX_BIGRAM_COUNT_KEY,
+ String.valueOf(DEFAULT_MAX_BIGRAM_COUNT));
return attributeMap;
}
+ private void removeBinaryDictionaryLocked() {
+ if (mBinaryDictionary != null) {
+ mBinaryDictionary.close();
+ }
+ if (mDictFile.exists() && !FileUtils.deleteRecursively(mDictFile)) {
+ Log.e(TAG, "Can't remove a file: " + mDictFile.getName());
+ }
+ mBinaryDictionary = null;
+ }
+
+ private void createBinaryDictionaryLocked() {
+ BinaryDictionaryUtils.createEmptyDictFile(mDictFile.getAbsolutePath(),
+ DICTIONARY_FORMAT_VERSION, mLocale, getHeaderAttributeMap());
+ }
+
+ private void openBinaryDictionaryLocked() {
+ mBinaryDictionary = new BinaryDictionary(
+ mDictFile.getAbsolutePath(), 0 /* offset */, mDictFile.length(),
+ true /* useFullEditDistance */, mLocale, mDictType, true /* isUpdatable */);
+ }
+
protected void clear() {
- getExecutor(mFilename).execute(new Runnable() {
+ ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE && mDictionaryWriter == null) {
- mBinaryDictionary.close();
- final File file = new File(mContext.getFilesDir(), mFilename);
- BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
- DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
- mBinaryDictionary = new BinaryDictionary(
- file.getAbsolutePath(), 0 /* offset */, file.length(),
- true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
- } else {
- mDictionaryWriter.clear();
- }
+ removeBinaryDictionaryLocked();
+ createBinaryDictionaryLocked();
+ openBinaryDictionaryLocked();
}
});
}
/**
- * Adds a word unigram to the dictionary. Used for loading a dictionary.
- * @param word The word to add.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this unigram.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
- * if shortcutTarget is null.
- * @param isNotAWord true if this is not a word, i.e. shortcut only.
- */
- protected void addWord(final String word, final String shortcutTarget,
- final int frequency, final int shortcutFreq, final boolean isNotAWord) {
- mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, shortcutFreq, isNotAWord);
- }
-
- /**
- * Adds a word bigram in the dictionary. Used for loading a dictionary.
- */
- protected void addBigram(final String prevWord, final String word, final int frequency,
- final long lastModifiedTime) {
- mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */,
- lastModifiedTime);
- }
-
- /**
* Check whether GC is needed and run GC if required.
*/
protected void runGCIfRequired(final boolean mindsBlockByGC) {
- if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) return;
- getExecutor(mFilename).execute(new Runnable() {
+ ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- runGCIfRequiredInternalLocked(mindsBlockByGC);
+ if (mBinaryDictionary == null) {
+ return;
+ }
+ runGCAfterAllPrioritizedTasksIfRequiredLocked(mindsBlockByGC);
}
});
}
- private void runGCIfRequiredInternalLocked(final boolean mindsBlockByGC) {
- if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) return;
- // Calls to needsToRunGC() need to be serialized.
+ protected void runGCIfRequiredLocked(final boolean mindsBlockByGC) {
+ if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) {
+ mBinaryDictionary.flushWithGC();
+ }
+ }
+
+ private void runGCAfterAllPrioritizedTasksIfRequiredLocked(final boolean mindsBlockByGC) {
+ // needsToRunGC() have to be called with lock.
if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) {
- if (setIsRegeneratingIfNotRegenerating()) {
+ if (setProcessingLargeTaskIfNot()) {
// Run GC after currently existing time sensitive operations.
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
try {
mBinaryDictionary.flushWithGC();
} finally {
- mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
+ mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false);
}
}
});
@@ -318,70 +298,99 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/**
* Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
*/
- protected void addWordDynamically(final String word, final String shortcutTarget,
- final int frequency, final int shortcutFreq, final boolean isNotAWord) {
- if (!mIsUpdatable) {
- Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename);
- return;
- }
- getExecutor(mFilename).execute(new Runnable() {
+ protected void addWordDynamically(final String word, final int frequency,
+ final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
+ final boolean isBlacklisted, final int timestamp) {
+ reloadDictionaryIfRequired();
+ ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
- mBinaryDictionary.addUnigramWord(word, frequency);
- } else {
- // TODO: Remove.
- mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, shortcutFreq,
- isNotAWord);
+ if (mBinaryDictionary == null) {
+ return;
}
+ runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */);
+ addWordDynamicallyLocked(word, frequency, shortcutTarget, shortcutFreq,
+ isNotAWord, isBlacklisted, timestamp);
}
});
}
+ protected void addWordDynamicallyLocked(final String word, final int frequency,
+ final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
+ final boolean isBlacklisted, final int timestamp) {
+ mBinaryDictionary.addUnigramWord(word, frequency, shortcutTarget, shortcutFreq,
+ isNotAWord, isBlacklisted, timestamp);
+ }
+
/**
* Dynamically adds a word bigram in the dictionary. May overwrite an existing entry.
*/
protected void addBigramDynamically(final String word0, final String word1,
- final int frequency, final boolean isValid) {
- if (!mIsUpdatable) {
- Log.w(TAG, "addBigramDynamically is called for non-updatable dictionary: "
- + mFilename);
- return;
- }
- getExecutor(mFilename).execute(new Runnable() {
+ final int frequency, final int timestamp) {
+ reloadDictionaryIfRequired();
+ ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
- mBinaryDictionary.addBigramWords(word0, word1, frequency);
- } else {
- // TODO: Remove.
- mDictionaryWriter.addBigramWords(word0, word1, frequency, isValid,
- 0 /* lastTouchedTime */);
+ if (mBinaryDictionary == null) {
+ return;
}
+ runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */);
+ addBigramDynamicallyLocked(word0, word1, frequency, timestamp);
}
});
}
+ protected void addBigramDynamicallyLocked(final String word0, final String word1,
+ final int frequency, final int timestamp) {
+ mBinaryDictionary.addBigramWords(word0, word1, frequency, timestamp);
+ }
+
/**
* Dynamically remove a word bigram in the dictionary.
*/
protected void removeBigramDynamically(final String word0, final String word1) {
- if (!mIsUpdatable) {
- Log.w(TAG, "removeBigramDynamically is called for non-updatable dictionary: "
- + mFilename);
- return;
- }
- getExecutor(mFilename).execute(new Runnable() {
+ reloadDictionaryIfRequired();
+ ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
- mBinaryDictionary.removeBigramWords(word0, word1);
- } else {
- // TODO: Remove.
- mDictionaryWriter.removeBigramWords(word0, word1);
+ if (mBinaryDictionary == null) {
+ return;
+ }
+ runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */);
+ mBinaryDictionary.removeBigramWords(word0, word1);
+ }
+ });
+ }
+
+ public interface AddMultipleDictionaryEntriesCallback {
+ public void onFinished();
+ }
+
+ /**
+ * Dynamically add multiple entries to the dictionary.
+ */
+ protected void addMultipleDictionaryEntriesDynamically(
+ final ArrayList<LanguageModelParam> languageModelParams,
+ final AddMultipleDictionaryEntriesCallback callback) {
+ reloadDictionaryIfRequired();
+ ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
+ @Override
+ public void run() {
+ if (mBinaryDictionary == null) {
+ return;
+ }
+ final boolean locked = setProcessingLargeTaskIfNot();
+ try {
+ mBinaryDictionary.addMultipleDictionaryEntries(
+ languageModelParams.toArray(
+ new LanguageModelParam[languageModelParams.size()]));
+ } finally {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ if (locked) {
+ mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false);
+ }
}
}
});
@@ -391,50 +400,27 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final int sessionId) {
+ final int sessionId, final float[] inOutLanguageWeight) {
reloadDictionaryIfRequired();
- if (isRegenerating()) {
+ if (processingLargeTask()) {
return null;
}
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
new AsyncResultHolder<ArrayList<SuggestedWordInfo>>();
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- if (mBinaryDictionary == null) {
- holder.set(null);
- return;
- }
- final ArrayList<SuggestedWordInfo> binarySuggestion =
- mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
- proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
- sessionId);
- holder.set(binarySuggestion);
- } else {
- final ArrayList<SuggestedWordInfo> inMemDictSuggestion =
- composer.isBatchMode() ? null :
- mDictionaryWriter.getSuggestionsWithSessionId(composer,
- prevWord, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, sessionId);
- // TODO: Remove checking mIsUpdatable and use native suggestion.
- if (mBinaryDictionary != null && !mIsUpdatable) {
- final ArrayList<SuggestedWordInfo> binarySuggestion =
- mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
- proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, sessionId);
- if (inMemDictSuggestion == null) {
- holder.set(binarySuggestion);
- } else if (binarySuggestion == null) {
- holder.set(inMemDictSuggestion);
- } else {
- binarySuggestion.addAll(inMemDictSuggestion);
- holder.set(binarySuggestion);
- }
- } else {
- holder.set(inMemDictSuggestion);
- }
+ if (mBinaryDictionary == null) {
+ holder.set(null);
+ return;
+ }
+ final ArrayList<SuggestedWordInfo> binarySuggestion =
+ mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
+ proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
+ sessionId, inOutLanguageWeight);
+ holder.set(binarySuggestion);
+ if (mBinaryDictionary.isCorrupted()) {
+ removeBinaryDictionaryLocked();
}
}
});
@@ -444,23 +430,20 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final float[] inOutLanguageWeight) {
return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, 0 /* sessionId */);
+ additionalFeaturesOptions, 0 /* sessionId */, inOutLanguageWeight);
}
@Override
public boolean isValidWord(final String word) {
reloadDictionaryIfRequired();
- return isValidWordInner(word);
- }
-
- protected boolean isValidWordInner(final String word) {
- if (isRegenerating()) {
+ if (processingLargeTask()) {
return false;
}
final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
holder.set(isValidWordLocked(word));
@@ -484,7 +467,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* dictionary exists, this method will generate one.
*/
protected void loadDictionary() {
- mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = SystemClock.uptimeMillis();
+ mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = System.currentTimeMillis();
reloadDictionaryIfRequired();
}
@@ -492,71 +475,57 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* Loads the current binary dictionary from internal storage. Assumes the dictionary file
* exists.
*/
- private void loadBinaryDictionary() {
+ private void loadBinaryDictionaryLocked() {
if (DEBUG) {
- Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
- + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
- + mFilenameDictionaryUpdateController.mLastUpdateTime);
+ Log.d(TAG, "Loading binary dictionary: " + mDictName + " request="
+ + mDictNameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+ + mDictNameDictionaryUpdateController.mLastUpdateTime);
}
-
- final File file = new File(mContext.getFilesDir(), mFilename);
- final String filename = file.getAbsolutePath();
- final long length = file.length();
-
- // Build the new binary dictionary
- final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0 /* offset */,
- length, true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
-
- // Ensure all threads accessing the current dictionary have finished before
- // swapping in the new one.
- // TODO: Ensure multi-thread assignment of mBinaryDictionary.
- final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
- getExecutor(mFilename).executePrioritized(new Runnable() {
- @Override
- public void run() {
- mBinaryDictionary = newBinaryDictionary;
- if (oldBinaryDictionary != null) {
- oldBinaryDictionary.close();
- }
+ if (DBG_STRESS_TEST) {
+ // Test if this class does not cause problems when it takes long time to load binary
+ // dictionary.
+ try {
+ Log.w(TAG, "Start stress in loading: " + mDictName);
+ Thread.sleep(15000);
+ Log.w(TAG, "End stress in loading");
+ } catch (InterruptedException e) {
}
- });
+ }
+ final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
+ openBinaryDictionaryLocked();
+ if (oldBinaryDictionary != null) {
+ oldBinaryDictionary.close();
+ }
+ if (mBinaryDictionary.isValidDictionary()
+ && needsToMigrateDictionary(mBinaryDictionary.getFormatVersion())) {
+ mBinaryDictionary.migrateTo(DICTIONARY_FORMAT_VERSION);
+ }
}
/**
- * Abstract method for checking if it is required to reload the dictionary before writing
- * a binary dictionary.
+ * Create a new binary dictionary and load initial contents.
*/
- abstract protected boolean needsToReloadBeforeWriting();
-
- /**
- * Writes a new binary dictionary based on the contents of the fusion dictionary.
- */
- private void writeBinaryDictionary() {
+ private void createNewDictionaryLocked() {
if (DEBUG) {
- Log.d(TAG, "Generating binary dictionary: " + mFilename + " request="
- + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
- + mFilenameDictionaryUpdateController.mLastUpdateTime);
+ Log.d(TAG, "Generating binary dictionary: " + mDictName + " request="
+ + mDictNameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+ + mDictNameDictionaryUpdateController.mLastUpdateTime);
}
- if (needsToReloadBeforeWriting()) {
- mDictionaryWriter.clear();
- loadDictionaryAsync();
- mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
+ removeBinaryDictionaryLocked();
+ createBinaryDictionaryLocked();
+ openBinaryDictionaryLocked();
+ loadInitialContentsLocked();
+ mBinaryDictionary.flushWithGC();
+ }
+
+ private void flushDictionaryLocked() {
+ if (mBinaryDictionary == null) {
+ return;
+ }
+ if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
+ mBinaryDictionary.flushWithGC();
} else {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- if (mBinaryDictionary == null || !mBinaryDictionary.isValidDictionary()) {
- final File file = new File(mContext.getFilesDir(), mFilename);
- BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
- DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
- } else {
- if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
- mBinaryDictionary.flushWithGC();
- } else {
- mBinaryDictionary.flush();
- }
- }
- } else {
- mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
- }
+ mBinaryDictionary.flush();
}
}
@@ -568,12 +537,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* the current binary dictionary from file.
*/
protected void setRequiresReload(final boolean requiresRebuild) {
- final long time = SystemClock.uptimeMillis();
+ final long time = System.currentTimeMillis();
mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = time;
- mFilenameDictionaryUpdateController.mLastUpdateRequestTime = time;
+ mDictNameDictionaryUpdateController.mLastUpdateRequestTime = time;
if (DEBUG) {
- Log.d(TAG, "Reload request: " + mFilename + ": request=" + time + " update="
- + mFilenameDictionaryUpdateController.mLastUpdateTime);
+ Log.d(TAG, "Reload request: " + mDictName + ": request=" + time + " update="
+ + mDictNameDictionaryUpdateController.mLastUpdateTime);
}
}
@@ -582,7 +551,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
public final void reloadDictionaryIfRequired() {
if (!isReloadRequired()) return;
- if (setIsRegeneratingIfNotRegenerating()) {
+ if (setProcessingLargeTaskIfNot()) {
reloadDictionary();
}
}
@@ -594,13 +563,14 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return mBinaryDictionary == null || mPerInstanceDictionaryUpdateController.isOutOfDate();
}
- private boolean isRegenerating() {
- return mFilenameDictionaryUpdateController.mIsRegenerating.get();
+ private boolean processingLargeTask() {
+ return mDictNameDictionaryUpdateController.mProcessingLargeTask.get();
}
- // Returns whether the dictionary can be regenerated.
- private boolean setIsRegeneratingIfNotRegenerating() {
- return mFilenameDictionaryUpdateController.mIsRegenerating.compareAndSet(
+ // Returns whether the dictionary is being used for a large task. If true, we should not use
+ // this dictionary for latency sensitive operations.
+ private boolean setProcessingLargeTaskIfNot() {
+ return mDictNameDictionaryUpdateController.mProcessingLargeTask.compareAndSet(
false /* expect */ , true /* update */);
}
@@ -611,46 +581,45 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private final void reloadDictionary() {
// Ensure that only one thread attempts to read or write to the shared binary dictionary
// file at the same time.
- getExecutor(mFilename).execute(new Runnable() {
+ ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
try {
- final long time = SystemClock.uptimeMillis();
- final boolean dictionaryFileExists = dictionaryFileExists();
- if (mFilenameDictionaryUpdateController.isOutOfDate()
- || !dictionaryFileExists) {
- // If the shared dictionary file does not exist or is out of date, the
- // first instance that acquires the lock will generate a new one.
- if (hasContentChanged() || !dictionaryFileExists) {
- // If the source content has changed or the dictionary does not exist,
- // rebuild the binary dictionary. Empty dictionaries are supported (in
- // the case where loadDictionaryAsync() adds nothing) in order to
- // provide a uniform framework.
- mFilenameDictionaryUpdateController.mLastUpdateTime = time;
- writeBinaryDictionary();
- loadBinaryDictionary();
- } else {
- // If not, the reload request was unnecessary so revert
- // LastUpdateRequestTime to LastUpdateTime.
- mFilenameDictionaryUpdateController.mLastUpdateRequestTime =
- mFilenameDictionaryUpdateController.mLastUpdateTime;
- }
+ final long time = System.currentTimeMillis();
+ final boolean openedDictIsOutOfDate =
+ mDictNameDictionaryUpdateController.isOutOfDate();
+ if (!dictionaryFileExists()
+ || (openedDictIsOutOfDate && haveContentsChanged())) {
+ // If the shared dictionary file does not exist or is out of date and
+ // contents have been updated, the first instance that acquires the lock
+ // will generate a new one
+ mDictNameDictionaryUpdateController.mLastUpdateTime = time;
+ createNewDictionaryLocked();
+ } else if (openedDictIsOutOfDate) {
+ // If not, the reload request was unnecessary so revert
+ // LastUpdateRequestTime to LastUpdateTime.
+ mDictNameDictionaryUpdateController.mLastUpdateRequestTime =
+ mDictNameDictionaryUpdateController.mLastUpdateTime;
} else if (mBinaryDictionary == null ||
mPerInstanceDictionaryUpdateController.mLastUpdateTime
- < mFilenameDictionaryUpdateController.mLastUpdateTime) {
+ < mDictNameDictionaryUpdateController.mLastUpdateTime) {
// Otherwise, if the local dictionary is older than the shared dictionary,
// load the shared dictionary.
- loadBinaryDictionary();
+ loadBinaryDictionaryLocked();
}
- if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
- // Binary dictionary is not valid. Regenerate the dictionary file.
- mFilenameDictionaryUpdateController.mLastUpdateTime = time;
- writeBinaryDictionary();
- loadBinaryDictionary();
+ if (mBinaryDictionary != null && !(isValidDictionaryLocked()
+ // TODO: remove the check below
+ && matchesExpectedBinaryDictFormatVersionForThisType(
+ mBinaryDictionary.getFormatVersion()))) {
+ // Binary dictionary or its format version is not valid. Regenerate
+ // the dictionary file. writeBinaryDictionary will remove the
+ // existing files if appropriate.
+ mDictNameDictionaryUpdateController.mLastUpdateTime = time;
+ createNewDictionaryLocked();
}
mPerInstanceDictionaryUpdateController.mLastUpdateTime = time;
} finally {
- mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
+ mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false);
}
}
});
@@ -658,79 +627,95 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
// TODO: cache the file's existence so that we avoid doing a disk access each time.
private boolean dictionaryFileExists() {
- final File file = new File(mContext.getFilesDir(), mFilename);
- return file.exists();
+ return mDictFile.exists();
}
/**
- * Load the dictionary to memory.
+ * Flush binary dictionary to dictionary file.
*/
- protected void asyncLoadDictionaryToMemory() {
- getExecutor(mFilename).executePrioritized(new Runnable() {
- @Override
- public void run() {
- if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- loadDictionaryAsync();
- }
- }
- });
- }
-
- /**
- * Generate binary dictionary using DictionaryWriter.
- */
- protected void asyncFlashAllBinaryDictionary() {
+ protected void asyncFlushBinaryDictionary() {
final Runnable newTask = new Runnable() {
@Override
public void run() {
- writeBinaryDictionary();
+ flushDictionaryLocked();
}
};
final Runnable oldTask = mUnfinishedFlushingTask.getAndSet(newTask);
- getExecutor(mFilename).replaceAndExecute(oldTask, newTask);
+ ExecutorUtils.getExecutor(mDictName).replaceAndExecute(oldTask, newTask);
}
/**
- * For tracking whether the dictionary is out of date and the dictionary is regenerating.
- * Can be shared across multiple dictionary instances that access the same filename.
+ * For tracking whether the dictionary is out of date and the dictionary is used in a large
+ * task. Can be shared across multiple dictionary instances that access the same filename.
*/
private static class DictionaryUpdateController {
public volatile long mLastUpdateTime = 0;
public volatile long mLastUpdateRequestTime = 0;
- public volatile AtomicBoolean mIsRegenerating = new AtomicBoolean();
+ public volatile AtomicBoolean mProcessingLargeTask = new AtomicBoolean();
public boolean isOutOfDate() {
return (mLastUpdateRequestTime > mLastUpdateTime);
}
}
- // TODO: Implement native binary methods once the dynamic dictionary implementation is done.
+ // TODO: Implement BinaryDictionary.isInDictionary().
@UsedForTesting
- public boolean isInDictionaryForTests(final String word) {
+ public boolean isInUnderlyingBinaryDictionaryForTests(final String word) {
final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
if (mDictType == Dictionary.TYPE_USER_HISTORY) {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- holder.set(mBinaryDictionary.isValidWord(word));
- } else {
- holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
- .isInBigramListForTests(word));
- }
+ holder.set(mBinaryDictionary.isValidWord(word));
}
}
});
- return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
+ return holder.get(false, TIMEOUT_FOR_READ_OPS_FOR_TESTS_IN_MILLISECONDS);
}
@UsedForTesting
- public void shutdownExecutorForTests() {
- getExecutor(mFilename).shutdown();
+ public void waitAllTasksForTests() {
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
+ @Override
+ public void run() {
+ countDownLatch.countDown();
+ }
+ });
+ try {
+ countDownLatch.await();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted while waiting for finishing dictionary operations.", e);
+ }
}
@UsedForTesting
- public boolean isTerminatedForTests() {
- return getExecutor(mFilename).isTerminated();
+ public void dumpAllWordsForDebug() {
+ reloadDictionaryIfRequired();
+ ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "Dump dictionary: " + mDictName);
+ try {
+ final DictionaryHeader header = mBinaryDictionary.getHeader();
+ Log.d(TAG, CombinedFormatUtils.formatAttributeMap(
+ header.mDictionaryOptions.mAttributes));
+ } catch (final UnsupportedFormatException e) {
+ Log.d(TAG, "Cannot fetch header information.", e);
+ }
+ int token = 0;
+ do {
+ final BinaryDictionary.GetNextWordPropertyResult result =
+ mBinaryDictionary.getNextWordProperty(token);
+ final WordProperty wordProperty = result.mWordProperty;
+ if (wordProperty == null) {
+ Log.d(TAG, " dictionary is empty.");
+ break;
+ }
+ Log.d(TAG, wordProperty.toString());
+ token = result.mNextToken;
+ } while (token != 0);
+ }
+ });
}
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
deleted file mode 100644
index 95c9bcab9..000000000
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ /dev/null
@@ -1,894 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-
-/**
- * Class for an in-memory dictionary that can grow dynamically and can
- * be searched for suggestions and valid words.
- */
-// TODO: Remove after binary dictionary supports dynamic update.
-public class ExpandableDictionary extends Dictionary {
- private static final String TAG = ExpandableDictionary.class.getSimpleName();
- /**
- * The weight to give to a word if it's length is the same as the number of typed characters.
- */
- private static final int FULL_WORD_SCORE_MULTIPLIER = 2;
-
- private char[] mWordBuilder = new char[Constants.DICTIONARY_MAX_WORD_LENGTH];
- private int mMaxDepth;
- private int mInputLength;
-
- private static final class Node {
- char mCode;
- int mFrequency;
- boolean mTerminal;
- Node mParent;
- NodeArray mChildren;
- ArrayList<char[]> mShortcutTargets;
- boolean mShortcutOnly;
- LinkedList<NextWord> mNGrams; // Supports ngram
- }
-
- private static final class NodeArray {
- Node[] mData;
- int mLength = 0;
- private static final int INCREMENT = 2;
-
- NodeArray() {
- mData = new Node[INCREMENT];
- }
-
- void add(final Node n) {
- if (mLength + 1 > mData.length) {
- Node[] tempData = new Node[mLength + INCREMENT];
- if (mLength > 0) {
- System.arraycopy(mData, 0, tempData, 0, mLength);
- }
- mData = tempData;
- }
- mData[mLength++] = n;
- }
- }
-
- public interface NextWord {
- public Node getWordNode();
- public int getFrequency();
- public ForgettingCurveParams getFcParams();
- public int notifyTypedAgainAndGetFrequency();
- }
-
- private static final class NextStaticWord implements NextWord {
- public final Node mWord;
- private final int mFrequency;
- public NextStaticWord(Node word, int frequency) {
- mWord = word;
- mFrequency = frequency;
- }
-
- @Override
- public Node getWordNode() {
- return mWord;
- }
-
- @Override
- public int getFrequency() {
- return mFrequency;
- }
-
- @Override
- public ForgettingCurveParams getFcParams() {
- return null;
- }
-
- @Override
- public int notifyTypedAgainAndGetFrequency() {
- return mFrequency;
- }
- }
-
- private static final class NextHistoryWord implements NextWord {
- public final Node mWord;
- public final ForgettingCurveParams mFcp;
-
- public NextHistoryWord(Node word, ForgettingCurveParams fcp) {
- mWord = word;
- mFcp = fcp;
- }
-
- @Override
- public Node getWordNode() {
- return mWord;
- }
-
- @Override
- public int getFrequency() {
- return mFcp.getFrequency();
- }
-
- @Override
- public ForgettingCurveParams getFcParams() {
- return mFcp;
- }
-
- @Override
- public int notifyTypedAgainAndGetFrequency() {
- return mFcp.notifyTypedAgainAndGetFrequency();
- }
- }
-
- private NodeArray mRoots;
-
- private int[][] mCodes;
-
- public ExpandableDictionary(final String dictType) {
- super(dictType);
- clearDictionary();
- mCodes = new int[Constants.DICTIONARY_MAX_WORD_LENGTH][];
- }
-
- public int getMaxWordLength() {
- return Constants.DICTIONARY_MAX_WORD_LENGTH;
- }
-
- /**
- * Add a word with an optional shortcut to the dictionary.
- * @param word The word to add.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this unigram.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
- * if shortcutTarget is null.
- */
- public void addWord(final String word, final String shortcutTarget, final int frequency,
- final int shortcutFreq) {
- if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
- return;
- }
- addWordRec(mRoots, word, 0, shortcutTarget, frequency, shortcutFreq, null);
- }
-
- /**
- * Add a word, recursively searching for its correct place in the trie tree.
- * @param children The node to recursively search for addition. Initially, the root of the tree.
- * @param word The word to add.
- * @param depth The current depth in the tree.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this unigram.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
- * if shortcutTarget is null.
- * @param parentNode The parent node, for up linking. Initially null, as the root has no parent.
- */
- private void addWordRec(final NodeArray children, final String word, final int depth,
- final String shortcutTarget, final int frequency, final int shortcutFreq,
- final Node parentNode) {
- final int wordLength = word.length();
- if (wordLength <= depth) return;
- final char c = word.charAt(depth);
- // Does children have the current character?
- final int childrenLength = children.mLength;
- Node childNode = null;
- for (int i = 0; i < childrenLength; i++) {
- final Node node = children.mData[i];
- if (node.mCode == c) {
- childNode = node;
- break;
- }
- }
- final boolean isShortcutOnly = (null != shortcutTarget);
- if (childNode == null) {
- childNode = new Node();
- childNode.mCode = c;
- childNode.mParent = parentNode;
- childNode.mShortcutOnly = isShortcutOnly;
- children.add(childNode);
- }
- if (wordLength == depth + 1) {
- // Terminate this word
- childNode.mTerminal = true;
- if (isShortcutOnly) {
- if (null == childNode.mShortcutTargets) {
- childNode.mShortcutTargets = CollectionUtils.newArrayList();
- }
- childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
- } else {
- childNode.mShortcutOnly = false;
- }
- childNode.mFrequency = Math.max(frequency, childNode.mFrequency);
- if (childNode.mFrequency > 255) childNode.mFrequency = 255;
- return;
- }
- if (childNode.mChildren == null) {
- childNode.mChildren = new NodeArray();
- }
- addWordRec(childNode.mChildren, word, depth + 1, shortcutTarget, frequency, shortcutFreq,
- childNode);
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- if (composer.size() > 1) {
- if (composer.size() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
- return null;
- }
- final ArrayList<SuggestedWordInfo> suggestions =
- getWordsInner(composer, prevWord, proximityInfo);
- return suggestions;
- } else {
- if (TextUtils.isEmpty(prevWord)) return null;
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
- runBigramReverseLookUp(prevWord, suggestions);
- return suggestions;
- }
- }
-
- private ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
- final String prevWordForBigrams, final ProximityInfo proximityInfo) {
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
- mInputLength = codes.size();
- if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
- final InputPointers ips = codes.getInputPointers();
- final int[] xCoordinates = ips.getXCoordinates();
- final int[] yCoordinates = ips.getYCoordinates();
- // Cache the codes so that we don't have to lookup an array list
- for (int i = 0; i < mInputLength; i++) {
- // TODO: Calculate proximity info here.
- if (mCodes[i] == null || mCodes[i].length < 1) {
- mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE];
- }
- final int x = xCoordinates != null && i < xCoordinates.length ?
- xCoordinates[i] : Constants.NOT_A_COORDINATE;
- final int y = xCoordinates != null && i < yCoordinates.length ?
- yCoordinates[i] : Constants.NOT_A_COORDINATE;
- proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
- }
- mMaxDepth = mInputLength * 3;
- getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, -1, suggestions);
- for (int i = 0; i < mInputLength; i++) {
- getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, i, suggestions);
- }
- return suggestions;
- }
-
- @Override
- public synchronized boolean isValidWord(final String word) {
- final Node node = searchNode(mRoots, word, 0, word.length());
- // If node is null, we didn't find the word, so it's not valid.
- // If node.mShortcutOnly is true, then it exists as a shortcut but not as a word,
- // so that means it's not a valid word.
- // If node.mShortcutOnly is false, then it exists as a word (it may also exist as
- // a shortcut, but this does not matter), so it's a valid word.
- return (node == null) ? false : !node.mShortcutOnly;
- }
-
- public boolean removeBigram(final String word0, final String word1) {
- // Refer to addOrSetBigram() about word1.toLowerCase()
- final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null);
- final Node secondWord = searchWord(mRoots, word1, 0, null);
- LinkedList<NextWord> bigrams = firstWord.mNGrams;
- NextWord bigramNode = null;
- if (bigrams == null || bigrams.size() == 0) {
- return false;
- } else {
- for (NextWord nw : bigrams) {
- if (nw.getWordNode() == secondWord) {
- bigramNode = nw;
- break;
- }
- }
- }
- if (bigramNode == null) {
- return false;
- }
- return bigrams.remove(bigramNode);
- }
-
- /**
- * Returns the word's frequency or -1 if not found
- */
- @UsedForTesting
- public int getWordFrequency(final String word) {
- // Case-sensitive search
- final Node node = searchNode(mRoots, word, 0, word.length());
- return (node == null) ? -1 : node.mFrequency;
- }
-
- public NextWord getBigramWord(final String word0, final String word1) {
- // Refer to addOrSetBigram() about word0.toLowerCase()
- final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null);
- final Node secondWord = searchWord(mRoots, word1, 0, null);
- LinkedList<NextWord> bigrams = firstWord.mNGrams;
- if (bigrams == null || bigrams.size() == 0) {
- return null;
- } else {
- for (NextWord nw : bigrams) {
- if (nw.getWordNode() == secondWord) {
- return nw;
- }
- }
- }
- return null;
- }
-
- private static int computeSkippedWordFinalFreq(final int freq, final int snr,
- final int inputLength) {
- // The computation itself makes sense for >= 2, but the == 2 case returns 0
- // anyway so we may as well test against 3 instead and return the constant
- if (inputLength >= 3) {
- return (freq * snr * (inputLength - 2)) / (inputLength - 1);
- } else {
- return 0;
- }
- }
-
- /**
- * Helper method to add a word and its shortcuts.
- *
- * @param node the terminal node
- * @param word the word to insert, as an array of code points
- * @param depth the depth of the node in the tree
- * @param finalFreq the frequency for this word
- * @param suggestions the suggestion collection to add the suggestions to
- * @return whether there is still space for more words.
- */
- private boolean addWordAndShortcutsFromNode(final Node node, final char[] word, final int depth,
- final int finalFreq, final ArrayList<SuggestedWordInfo> suggestions) {
- if (finalFreq > 0 && !node.mShortcutOnly) {
- // Use KIND_CORRECTION always. This dictionary does not really have a notion of
- // COMPLETION against CORRECTION; we could artificially add one by looking at
- // the respective size of the typed word and the suggestion if it matters sometime
- // in the future.
- suggestions.add(new SuggestedWordInfo(new String(word, 0, depth + 1), finalFreq,
- SuggestedWordInfo.KIND_CORRECTION, this /* sourceDict */,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- if (suggestions.size() >= Suggest.MAX_SUGGESTIONS) return false;
- }
- if (null != node.mShortcutTargets) {
- final int length = node.mShortcutTargets.size();
- for (int shortcutIndex = 0; shortcutIndex < length; ++shortcutIndex) {
- final char[] shortcut = node.mShortcutTargets.get(shortcutIndex);
- suggestions.add(new SuggestedWordInfo(new String(shortcut, 0, shortcut.length),
- finalFreq, SuggestedWordInfo.KIND_SHORTCUT, this /* sourceDict */,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- if (suggestions.size() > Suggest.MAX_SUGGESTIONS) return false;
- }
- }
- return true;
- }
-
- /**
- * Recursively traverse the tree for words that match the input. Input consists of
- * a list of arrays. Each item in the list is one input character position. An input
- * character is actually an array of multiple possible candidates. This function is not
- * optimized for speed, assuming that the user dictionary will only be a few hundred words in
- * size.
- * @param roots node whose children have to be search for matches
- * @param codes the input character codes
- * @param word the word being composed as a possible match
- * @param depth the depth of traversal - the length of the word being composed thus far
- * @param completion whether the traversal is now in completion mode - meaning that we've
- * exhausted the input and we're looking for all possible suffixes.
- * @param snr current weight of the word being formed
- * @param inputIndex position in the input characters. This can be off from the depth in
- * case we skip over some punctuations such as apostrophe in the traversal. That is, if you type
- * "wouldve", it could be matching "would've", so the depth will be one more than the
- * inputIndex
- * @param suggestions the list in which to add suggestions
- */
- // TODO: Share this routine with the native code for BinaryDictionary
- private void getWordsRec(final NodeArray roots, final WordComposer codes, final char[] word,
- final int depth, final boolean completion, final int snr, final int inputIndex,
- final int skipPos, final ArrayList<SuggestedWordInfo> suggestions) {
- final int count = roots.mLength;
- final int codeSize = mInputLength;
- // Optimization: Prune out words that are too long compared to how much was typed.
- if (depth > mMaxDepth) {
- return;
- }
- final int[] currentChars;
- if (codeSize <= inputIndex) {
- currentChars = null;
- } else {
- currentChars = mCodes[inputIndex];
- }
-
- for (int i = 0; i < count; i++) {
- final Node node = roots.mData[i];
- final char c = node.mCode;
- final char lowerC = toLowerCase(c);
- final boolean terminal = node.mTerminal;
- final NodeArray children = node.mChildren;
- final int freq = node.mFrequency;
- if (completion || currentChars == null) {
- word[depth] = c;
- if (terminal) {
- final int finalFreq;
- if (skipPos < 0) {
- finalFreq = freq * snr;
- } else {
- finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
- }
- if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, suggestions)) {
- // No space left in the queue, bail out
- return;
- }
- }
- if (children != null) {
- getWordsRec(children, codes, word, depth + 1, true, snr, inputIndex,
- skipPos, suggestions);
- }
- } else if ((c == Constants.CODE_SINGLE_QUOTE
- && currentChars[0] != Constants.CODE_SINGLE_QUOTE) || depth == skipPos) {
- // Skip the ' and continue deeper
- word[depth] = c;
- if (children != null) {
- getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
- skipPos, suggestions);
- }
- } else {
- // Don't use alternatives if we're looking for missing characters
- final int alternativesSize = skipPos >= 0 ? 1 : currentChars.length;
- for (int j = 0; j < alternativesSize; j++) {
- final int addedAttenuation = (j > 0 ? 1 : 2);
- final int currentChar = currentChars[j];
- if (currentChar == Constants.NOT_A_CODE) {
- break;
- }
- if (currentChar == lowerC || currentChar == c) {
- word[depth] = c;
-
- if (codeSize == inputIndex + 1) {
- if (terminal) {
- final int finalFreq;
- if (skipPos < 0) {
- finalFreq = freq * snr * addedAttenuation
- * FULL_WORD_SCORE_MULTIPLIER;
- } else {
- finalFreq = computeSkippedWordFinalFreq(freq,
- snr * addedAttenuation, mInputLength);
- }
- if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq,
- suggestions)) {
- // No space left in the queue, bail out
- return;
- }
- }
- if (children != null) {
- getWordsRec(children, codes, word, depth + 1,
- true, snr * addedAttenuation, inputIndex + 1,
- skipPos, suggestions);
- }
- } else if (children != null) {
- getWordsRec(children, codes, word, depth + 1,
- false, snr * addedAttenuation, inputIndex + 1,
- skipPos, suggestions);
- }
- }
- }
- }
- }
- }
-
- public int setBigramAndGetFrequency(final String word0, final String word1,
- final int frequency) {
- return setBigramAndGetFrequency(word0, word1, frequency, null /* unused */);
- }
-
- public int setBigramAndGetFrequency(final String word0, final String word1,
- final ForgettingCurveParams fcp) {
- return setBigramAndGetFrequency(word0, word1, 0 /* unused */, fcp);
- }
-
- /**
- * Adds bigrams to the in-memory trie structure that is being used to retrieve any word
- * @param word0 the first word of this bigram
- * @param word1 the second word of this bigram
- * @param frequency frequency for this bigram
- * @param fcp an instance of ForgettingCurveParams to use for decay policy
- * @return returns the final bigram frequency
- */
- private int setBigramAndGetFrequency(final String word0, final String word1,
- final int frequency, final ForgettingCurveParams fcp) {
- if (TextUtils.isEmpty(word0)) {
- Log.e(TAG, "Invalid bigram previous word: " + word0);
- return frequency;
- }
- // We don't want results to be different according to case of the looked up left hand side
- // word. We do want however to return the correct case for the right hand side.
- // So we want to squash the case of the left hand side, and preserve that of the right
- // hand side word.
- final String word0Lower = word0.toLowerCase();
- if (TextUtils.isEmpty(word0Lower) || TextUtils.isEmpty(word1)) {
- Log.e(TAG, "Invalid bigram pair: " + word0 + ", " + word0Lower + ", " + word1);
- return frequency;
- }
- final Node firstWord = searchWord(mRoots, word0Lower, 0, null);
- final Node secondWord = searchWord(mRoots, word1, 0, null);
- LinkedList<NextWord> bigrams = firstWord.mNGrams;
- if (bigrams == null || bigrams.size() == 0) {
- firstWord.mNGrams = CollectionUtils.newLinkedList();
- bigrams = firstWord.mNGrams;
- } else {
- for (NextWord nw : bigrams) {
- if (nw.getWordNode() == secondWord) {
- return nw.notifyTypedAgainAndGetFrequency();
- }
- }
- }
- if (fcp != null) {
- // history
- firstWord.mNGrams.add(new NextHistoryWord(secondWord, fcp));
- } else {
- firstWord.mNGrams.add(new NextStaticWord(secondWord, frequency));
- }
- return frequency;
- }
-
- /**
- * Searches for the word and add the word if it does not exist.
- * @return Returns the terminal node of the word we are searching for.
- */
- private Node searchWord(final NodeArray children, final String word, final int depth,
- final Node parentNode) {
- final int wordLength = word.length();
- final char c = word.charAt(depth);
- // Does children have the current character?
- final int childrenLength = children.mLength;
- Node childNode = null;
- for (int i = 0; i < childrenLength; i++) {
- final Node node = children.mData[i];
- if (node.mCode == c) {
- childNode = node;
- break;
- }
- }
- if (childNode == null) {
- childNode = new Node();
- childNode.mCode = c;
- childNode.mParent = parentNode;
- children.add(childNode);
- }
- if (wordLength == depth + 1) {
- // Terminate this word
- childNode.mTerminal = true;
- return childNode;
- }
- if (childNode.mChildren == null) {
- childNode.mChildren = new NodeArray();
- }
- return searchWord(childNode.mChildren, word, depth + 1, childNode);
- }
-
- private void runBigramReverseLookUp(final String previousWord,
- final ArrayList<SuggestedWordInfo> suggestions) {
- // Search for the lowercase version of the word only, because that's where bigrams
- // store their sons.
- final Node prevWord = searchNode(mRoots, previousWord.toLowerCase(), 0,
- previousWord.length());
- if (prevWord != null && prevWord.mNGrams != null) {
- reverseLookUp(prevWord.mNGrams, suggestions);
- }
- }
-
- // Local to reverseLookUp, but do not allocate each time.
- private final char[] mLookedUpString = new char[Constants.DICTIONARY_MAX_WORD_LENGTH];
-
- /**
- * reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
- * to the suggestions list passed as an argument.
- * @param terminalNodes list of terminal nodes we want to add
- * @param suggestions the suggestion collection to add the word to
- */
- private void reverseLookUp(final LinkedList<NextWord> terminalNodes,
- final ArrayList<SuggestedWordInfo> suggestions) {
- Node node;
- int freq;
- for (NextWord nextWord : terminalNodes) {
- node = nextWord.getWordNode();
- freq = nextWord.getFrequency();
- int index = Constants.DICTIONARY_MAX_WORD_LENGTH;
- do {
- --index;
- mLookedUpString[index] = node.mCode;
- node = node.mParent;
- } while (node != null && index > 0);
-
- // If node is null, we have a word longer than MAX_WORD_LENGTH in the dictionary.
- // It's a little unclear how this can happen, but just in case it does it's safer
- // to ignore the word in this case.
- if (freq >= 0 && node == null) {
- suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index,
- Constants.DICTIONARY_MAX_WORD_LENGTH - index),
- freq, SuggestedWordInfo.KIND_CORRECTION, this /* sourceDict */,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- }
- }
- }
-
- /**
- * Recursively search for the terminal node of the word.
- *
- * One iteration takes the full word to search for and the current index of the recursion.
- *
- * @param children the node of the trie to search under.
- * @param word the word to search for. Only read [offset..length] so there may be trailing chars
- * @param offset the index in {@code word} this recursion should operate on.
- * @param length the length of the input word.
- * @return Returns the terminal node of the word if the word exists
- */
- private Node searchNode(final NodeArray children, final CharSequence word, final int offset,
- final int length) {
- final int count = children.mLength;
- final char currentChar = word.charAt(offset);
- for (int j = 0; j < count; j++) {
- final Node node = children.mData[j];
- if (node.mCode == currentChar) {
- if (offset == length - 1) {
- if (node.mTerminal) {
- return node;
- }
- } else {
- if (node.mChildren != null) {
- Node returnNode = searchNode(node.mChildren, word, offset + 1, length);
- if (returnNode != null) return returnNode;
- }
- }
- }
- }
- return null;
- }
-
- public void clearDictionary() {
- mRoots = new NodeArray();
- }
-
- private static char toLowerCase(final char c) {
- char baseChar = c;
- if (c < BASE_CHARS.length) {
- baseChar = BASE_CHARS[c];
- }
- if (baseChar >= 'A' && baseChar <= 'Z') {
- return (char)(baseChar | 32);
- } else if (baseChar > 127) {
- return Character.toLowerCase(baseChar);
- }
- return baseChar;
- }
-
- /**
- * Table mapping most combined Latin, Greek, and Cyrillic characters
- * to their base characters. If c is in range, BASE_CHARS[c] == c
- * if c is not a combined character, or the base character if it
- * is combined.
- *
- * cf. native/jni/src/utils/char_utils.cpp
- */
- private static final char BASE_CHARS[] = {
- /* U+0000 */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
- /* U+0008 */ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
- /* U+0010 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
- /* U+0018 */ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
- /* U+0020 */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
- /* U+0028 */ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
- /* U+0030 */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
- /* U+0038 */ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
- /* U+0040 */ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
- /* U+0048 */ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
- /* U+0050 */ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
- /* U+0058 */ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
- /* U+0060 */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
- /* U+0068 */ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
- /* U+0070 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
- /* U+0078 */ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
- /* U+0080 */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
- /* U+0088 */ 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
- /* U+0090 */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
- /* U+0098 */ 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
- /* U+00A0 */ 0x0020, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
- /* U+00A8 */ 0x0020, 0x00A9, 0x0061, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0020,
- /* U+00B0 */ 0x00B0, 0x00B1, 0x0032, 0x0033, 0x0020, 0x03BC, 0x00B6, 0x00B7,
- /* U+00B8 */ 0x0020, 0x0031, 0x006F, 0x00BB, 0x0031, 0x0031, 0x0033, 0x00BF,
- /* U+00C0 */ 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00C6, 0x0043,
- /* U+00C8 */ 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
- /* U+00D0 */ 0x00D0, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x00D7,
- /* U+00D8 */ 0x004F, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00DE, 0x0073,
- // U+00D8: Manually changed from 00D8 to 004F
- // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
- // U+00DF: Manually changed from 00DF to 0073
- /* U+00E0 */ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00E6, 0x0063,
- /* U+00E8 */ 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
- /* U+00F0 */ 0x00F0, 0x006E, 0x006F, 0x006F, 0x006F, 0x006F, 0x006F, 0x00F7,
- /* U+00F8 */ 0x006F, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00FE, 0x0079,
- // U+00F8: Manually changed from 00F8 to 006F
- // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
- /* U+0100 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063,
- /* U+0108 */ 0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064,
- /* U+0110 */ 0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065,
- /* U+0118 */ 0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067,
- /* U+0120 */ 0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127,
- /* U+0128 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069,
- /* U+0130 */ 0x0049, 0x0131, 0x0049, 0x0069, 0x004A, 0x006A, 0x004B, 0x006B,
- /* U+0138 */ 0x0138, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C,
- /* U+0140 */ 0x006C, 0x004C, 0x006C, 0x004E, 0x006E, 0x004E, 0x006E, 0x004E,
- // U+0141: Manually changed from 0141 to 004C
- // U+0142: Manually changed from 0142 to 006C
- /* U+0148 */ 0x006E, 0x02BC, 0x014A, 0x014B, 0x004F, 0x006F, 0x004F, 0x006F,
- /* U+0150 */ 0x004F, 0x006F, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072,
- /* U+0158 */ 0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073,
- /* U+0160 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167,
- /* U+0168 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075,
- /* U+0170 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079,
- /* U+0178 */ 0x0059, 0x005A, 0x007A, 0x005A, 0x007A, 0x005A, 0x007A, 0x0073,
- /* U+0180 */ 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187,
- /* U+0188 */ 0x0188, 0x0189, 0x018A, 0x018B, 0x018C, 0x018D, 0x018E, 0x018F,
- /* U+0190 */ 0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197,
- /* U+0198 */ 0x0198, 0x0199, 0x019A, 0x019B, 0x019C, 0x019D, 0x019E, 0x019F,
- /* U+01A0 */ 0x004F, 0x006F, 0x01A2, 0x01A3, 0x01A4, 0x01A5, 0x01A6, 0x01A7,
- /* U+01A8 */ 0x01A8, 0x01A9, 0x01AA, 0x01AB, 0x01AC, 0x01AD, 0x01AE, 0x0055,
- /* U+01B0 */ 0x0075, 0x01B1, 0x01B2, 0x01B3, 0x01B4, 0x01B5, 0x01B6, 0x01B7,
- /* U+01B8 */ 0x01B8, 0x01B9, 0x01BA, 0x01BB, 0x01BC, 0x01BD, 0x01BE, 0x01BF,
- /* U+01C0 */ 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x0044, 0x0044, 0x0064, 0x004C,
- /* U+01C8 */ 0x004C, 0x006C, 0x004E, 0x004E, 0x006E, 0x0041, 0x0061, 0x0049,
- /* U+01D0 */ 0x0069, 0x004F, 0x006F, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055,
- // U+01D5: Manually changed from 00DC to 0055
- // U+01D6: Manually changed from 00FC to 0075
- // U+01D7: Manually changed from 00DC to 0055
- /* U+01D8 */ 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x01DD, 0x0041, 0x0061,
- // U+01D8: Manually changed from 00FC to 0075
- // U+01D9: Manually changed from 00DC to 0055
- // U+01DA: Manually changed from 00FC to 0075
- // U+01DB: Manually changed from 00DC to 0055
- // U+01DC: Manually changed from 00FC to 0075
- // U+01DE: Manually changed from 00C4 to 0041
- // U+01DF: Manually changed from 00E4 to 0061
- /* U+01E0 */ 0x0041, 0x0061, 0x00C6, 0x00E6, 0x01E4, 0x01E5, 0x0047, 0x0067,
- // U+01E0: Manually changed from 0226 to 0041
- // U+01E1: Manually changed from 0227 to 0061
- /* U+01E8 */ 0x004B, 0x006B, 0x004F, 0x006F, 0x004F, 0x006F, 0x01B7, 0x0292,
- // U+01EC: Manually changed from 01EA to 004F
- // U+01ED: Manually changed from 01EB to 006F
- /* U+01F0 */ 0x006A, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01F6, 0x01F7,
- /* U+01F8 */ 0x004E, 0x006E, 0x0041, 0x0061, 0x00C6, 0x00E6, 0x004F, 0x006F,
- // U+01FA: Manually changed from 00C5 to 0041
- // U+01FB: Manually changed from 00E5 to 0061
- // U+01FE: Manually changed from 00D8 to 004F
- // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
- // U+01FF: Manually changed from 00F8 to 006F
- // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
- /* U+0200 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065,
- /* U+0208 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x004F, 0x006F, 0x004F, 0x006F,
- /* U+0210 */ 0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075,
- /* U+0218 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x021C, 0x021D, 0x0048, 0x0068,
- /* U+0220 */ 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061,
- /* U+0228 */ 0x0045, 0x0065, 0x004F, 0x006F, 0x004F, 0x006F, 0x004F, 0x006F,
- // U+022A: Manually changed from 00D6 to 004F
- // U+022B: Manually changed from 00F6 to 006F
- // U+022C: Manually changed from 00D5 to 004F
- // U+022D: Manually changed from 00F5 to 006F
- /* U+0230 */ 0x004F, 0x006F, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237,
- // U+0230: Manually changed from 022E to 004F
- // U+0231: Manually changed from 022F to 006F
- /* U+0238 */ 0x0238, 0x0239, 0x023A, 0x023B, 0x023C, 0x023D, 0x023E, 0x023F,
- /* U+0240 */ 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247,
- /* U+0248 */ 0x0248, 0x0249, 0x024A, 0x024B, 0x024C, 0x024D, 0x024E, 0x024F,
- /* U+0250 */ 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
- /* U+0258 */ 0x0258, 0x0259, 0x025A, 0x025B, 0x025C, 0x025D, 0x025E, 0x025F,
- /* U+0260 */ 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
- /* U+0268 */ 0x0268, 0x0269, 0x026A, 0x026B, 0x026C, 0x026D, 0x026E, 0x026F,
- /* U+0270 */ 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
- /* U+0278 */ 0x0278, 0x0279, 0x027A, 0x027B, 0x027C, 0x027D, 0x027E, 0x027F,
- /* U+0280 */ 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
- /* U+0288 */ 0x0288, 0x0289, 0x028A, 0x028B, 0x028C, 0x028D, 0x028E, 0x028F,
- /* U+0290 */ 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
- /* U+0298 */ 0x0298, 0x0299, 0x029A, 0x029B, 0x029C, 0x029D, 0x029E, 0x029F,
- /* U+02A0 */ 0x02A0, 0x02A1, 0x02A2, 0x02A3, 0x02A4, 0x02A5, 0x02A6, 0x02A7,
- /* U+02A8 */ 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC, 0x02AD, 0x02AE, 0x02AF,
- /* U+02B0 */ 0x0068, 0x0266, 0x006A, 0x0072, 0x0279, 0x027B, 0x0281, 0x0077,
- /* U+02B8 */ 0x0079, 0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF,
- /* U+02C0 */ 0x02C0, 0x02C1, 0x02C2, 0x02C3, 0x02C4, 0x02C5, 0x02C6, 0x02C7,
- /* U+02C8 */ 0x02C8, 0x02C9, 0x02CA, 0x02CB, 0x02CC, 0x02CD, 0x02CE, 0x02CF,
- /* U+02D0 */ 0x02D0, 0x02D1, 0x02D2, 0x02D3, 0x02D4, 0x02D5, 0x02D6, 0x02D7,
- /* U+02D8 */ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02DE, 0x02DF,
- /* U+02E0 */ 0x0263, 0x006C, 0x0073, 0x0078, 0x0295, 0x02E5, 0x02E6, 0x02E7,
- /* U+02E8 */ 0x02E8, 0x02E9, 0x02EA, 0x02EB, 0x02EC, 0x02ED, 0x02EE, 0x02EF,
- /* U+02F0 */ 0x02F0, 0x02F1, 0x02F2, 0x02F3, 0x02F4, 0x02F5, 0x02F6, 0x02F7,
- /* U+02F8 */ 0x02F8, 0x02F9, 0x02FA, 0x02FB, 0x02FC, 0x02FD, 0x02FE, 0x02FF,
- /* U+0300 */ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
- /* U+0308 */ 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F,
- /* U+0310 */ 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
- /* U+0318 */ 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F,
- /* U+0320 */ 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
- /* U+0328 */ 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F,
- /* U+0330 */ 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
- /* U+0338 */ 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F,
- /* U+0340 */ 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347,
- /* U+0348 */ 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F,
- /* U+0350 */ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
- /* U+0358 */ 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F,
- /* U+0360 */ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
- /* U+0368 */ 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F,
- /* U+0370 */ 0x0370, 0x0371, 0x0372, 0x0373, 0x02B9, 0x0375, 0x0376, 0x0377,
- /* U+0378 */ 0x0378, 0x0379, 0x0020, 0x037B, 0x037C, 0x037D, 0x003B, 0x037F,
- /* U+0380 */ 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00A8, 0x0391, 0x00B7,
- /* U+0388 */ 0x0395, 0x0397, 0x0399, 0x038B, 0x039F, 0x038D, 0x03A5, 0x03A9,
- /* U+0390 */ 0x03CA, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
- /* U+0398 */ 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
- /* U+03A0 */ 0x03A0, 0x03A1, 0x03A2, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7,
- /* U+03A8 */ 0x03A8, 0x03A9, 0x0399, 0x03A5, 0x03B1, 0x03B5, 0x03B7, 0x03B9,
- /* U+03B0 */ 0x03CB, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
- /* U+03B8 */ 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
- /* U+03C0 */ 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
- /* U+03C8 */ 0x03C8, 0x03C9, 0x03B9, 0x03C5, 0x03BF, 0x03C5, 0x03C9, 0x03CF,
- /* U+03D0 */ 0x03B2, 0x03B8, 0x03A5, 0x03D2, 0x03D2, 0x03C6, 0x03C0, 0x03D7,
- /* U+03D8 */ 0x03D8, 0x03D9, 0x03DA, 0x03DB, 0x03DC, 0x03DD, 0x03DE, 0x03DF,
- /* U+03E0 */ 0x03E0, 0x03E1, 0x03E2, 0x03E3, 0x03E4, 0x03E5, 0x03E6, 0x03E7,
- /* U+03E8 */ 0x03E8, 0x03E9, 0x03EA, 0x03EB, 0x03EC, 0x03ED, 0x03EE, 0x03EF,
- /* U+03F0 */ 0x03BA, 0x03C1, 0x03C2, 0x03F3, 0x0398, 0x03B5, 0x03F6, 0x03F7,
- /* U+03F8 */ 0x03F8, 0x03A3, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF,
- /* U+0400 */ 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406,
- /* U+0408 */ 0x0408, 0x0409, 0x040A, 0x040B, 0x041A, 0x0418, 0x0423, 0x040F,
- /* U+0410 */ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
- /* U+0418 */ 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
- // U+0419: Manually changed from 0418 to 0419
- /* U+0420 */ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
- /* U+0428 */ 0x0428, 0x0429, 0x042C, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
- // U+042A: Manually changed from 042A to 042C
- /* U+0430 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
- /* U+0438 */ 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
- // U+0439: Manually changed from 0438 to 0439
- /* U+0440 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
- /* U+0448 */ 0x0448, 0x0449, 0x044C, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
- // U+044A: Manually changed from 044A to 044C
- /* U+0450 */ 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
- /* U+0458 */ 0x0458, 0x0459, 0x045A, 0x045B, 0x043A, 0x0438, 0x0443, 0x045F,
- /* U+0460 */ 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467,
- /* U+0468 */ 0x0468, 0x0469, 0x046A, 0x046B, 0x046C, 0x046D, 0x046E, 0x046F,
- /* U+0470 */ 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475,
- /* U+0478 */ 0x0478, 0x0479, 0x047A, 0x047B, 0x047C, 0x047D, 0x047E, 0x047F,
- /* U+0480 */ 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
- /* U+0488 */ 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048D, 0x048E, 0x048F,
- /* U+0490 */ 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497,
- /* U+0498 */ 0x0498, 0x0499, 0x049A, 0x049B, 0x049C, 0x049D, 0x049E, 0x049F,
- /* U+04A0 */ 0x04A0, 0x04A1, 0x04A2, 0x04A3, 0x04A4, 0x04A5, 0x04A6, 0x04A7,
- /* U+04A8 */ 0x04A8, 0x04A9, 0x04AA, 0x04AB, 0x04AC, 0x04AD, 0x04AE, 0x04AF,
- /* U+04B0 */ 0x04B0, 0x04B1, 0x04B2, 0x04B3, 0x04B4, 0x04B5, 0x04B6, 0x04B7,
- /* U+04B8 */ 0x04B8, 0x04B9, 0x04BA, 0x04BB, 0x04BC, 0x04BD, 0x04BE, 0x04BF,
- /* U+04C0 */ 0x04C0, 0x0416, 0x0436, 0x04C3, 0x04C4, 0x04C5, 0x04C6, 0x04C7,
- /* U+04C8 */ 0x04C8, 0x04C9, 0x04CA, 0x04CB, 0x04CC, 0x04CD, 0x04CE, 0x04CF,
- /* U+04D0 */ 0x0410, 0x0430, 0x0410, 0x0430, 0x04D4, 0x04D5, 0x0415, 0x0435,
- /* U+04D8 */ 0x04D8, 0x04D9, 0x04D8, 0x04D9, 0x0416, 0x0436, 0x0417, 0x0437,
- /* U+04E0 */ 0x04E0, 0x04E1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041E, 0x043E,
- /* U+04E8 */ 0x04E8, 0x04E9, 0x04E8, 0x04E9, 0x042D, 0x044D, 0x0423, 0x0443,
- /* U+04F0 */ 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04F6, 0x04F7,
- /* U+04F8 */ 0x042B, 0x044B, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF,
- };
-}
diff --git a/java/src/com/android/inputmethod/latin/ImportantNoticeDialog.java b/java/src/com/android/inputmethod/latin/ImportantNoticeDialog.java
new file mode 100644
index 000000000..567087c81
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ImportantNoticeDialog.java
@@ -0,0 +1,78 @@
+/*
+ * 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.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+
+import com.android.inputmethod.latin.utils.DialogUtils;
+import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
+
+/**
+ * The dialog box that shows the important notice contents.
+ */
+public final class ImportantNoticeDialog extends AlertDialog implements OnClickListener {
+ public interface ImportantNoticeDialogListener {
+ public void onUserAcknowledgmentOfImportantNoticeDialog(final int nextVersion);
+ public void onClickSettingsOfImportantNoticeDialog(final int nextVersion);
+ }
+
+ private final ImportantNoticeDialogListener mListener;
+ private final int mNextImportantNoticeVersion;
+
+ public ImportantNoticeDialog(
+ final Context context, final ImportantNoticeDialogListener listener) {
+ super(DialogUtils.getPlatformDialogThemeContext(context));
+ mListener = listener;
+ mNextImportantNoticeVersion = ImportantNoticeUtils.getNextImportantNoticeVersion(context);
+ setMessage(ImportantNoticeUtils.getNextImportantNoticeContents(context));
+ // Create buttons and set listeners.
+ setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok), this);
+ if (shouldHaveSettingsButton()) {
+ setButton(BUTTON_NEGATIVE, context.getString(R.string.go_to_settings), this);
+ }
+ // This dialog is cancelable by pressing back key. See {@link #onBackPress()}.
+ setCancelable(true /* cancelable */);
+ setCanceledOnTouchOutside(false /* cancelable */);
+ }
+
+ private boolean shouldHaveSettingsButton() {
+ return mNextImportantNoticeVersion
+ == ImportantNoticeUtils.VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS;
+ }
+
+ private void userAcknowledged() {
+ ImportantNoticeUtils.updateLastImportantNoticeVersion(getContext());
+ mListener.onUserAcknowledgmentOfImportantNoticeDialog(mNextImportantNoticeVersion);
+ }
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ if (shouldHaveSettingsButton() && which == BUTTON_NEGATIVE) {
+ mListener.onClickSettingsOfImportantNoticeDialog(mNextImportantNoticeVersion);
+ }
+ userAcknowledged();
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ userAcknowledged();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 8caf6f17f..726b3d141 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -20,22 +20,29 @@ import android.text.InputType;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+
/**
* Class to hold attributes of the input field.
*/
public final class InputAttributes {
private final String TAG = InputAttributes.class.getSimpleName();
+ final public String mTargetApplicationPackageName;
final public boolean mInputTypeNoAutoCorrect;
+ final public boolean mIsPasswordField;
final public boolean mIsSettingsSuggestionStripOn;
final public boolean mApplicationSpecifiedCompletionOn;
final public boolean mShouldInsertSpacesAutomatically;
final private int mInputType;
public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
+ mTargetApplicationPackageName = null != editorInfo ? editorInfo.packageName : null;
final int inputType = null != editorInfo ? editorInfo.inputType : 0;
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
mInputType = inputType;
@@ -52,55 +59,50 @@ public final class InputAttributes {
} else if (inputClass == 0) {
// TODO: is this check still necessary?
Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
- + " imeOptions=0x%08x",
- inputType, editorInfo.imeOptions));
+ + " imeOptions=0x%08x", inputType, editorInfo.imeOptions));
}
+ mIsPasswordField = false;
mIsSettingsSuggestionStripOn = false;
mInputTypeNoAutoCorrect = false;
mApplicationSpecifiedCompletionOn = false;
mShouldInsertSpacesAutomatically = false;
- } else {
- final int variation = inputType & InputType.TYPE_MASK_VARIATION;
- final boolean flagNoSuggestions =
- 0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
- final boolean flagMultiLine =
- 0 != (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE);
- final boolean flagAutoCorrect =
- 0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
- final boolean flagAutoComplete =
- 0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
-
- // TODO: Have a helper method in InputTypeUtils
- // Make sure that passwords are not displayed in {@link SuggestionStripView}.
- if (InputTypeUtils.isPasswordInputType(inputType)
- || InputTypeUtils.isVisiblePasswordInputType(inputType)
- || InputTypeUtils.isEmailVariation(variation)
- || InputType.TYPE_TEXT_VARIATION_URI == variation
- || InputType.TYPE_TEXT_VARIATION_FILTER == variation
- || flagNoSuggestions
- || flagAutoComplete) {
- mIsSettingsSuggestionStripOn = false;
- } else {
- mIsSettingsSuggestionStripOn = true;
- }
+ return;
+ }
+ // inputClass == InputType.TYPE_CLASS_TEXT
+ final int variation = inputType & InputType.TYPE_MASK_VARIATION;
+ final boolean flagNoSuggestions =
+ 0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ final boolean flagMultiLine =
+ 0 != (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE);
+ final boolean flagAutoCorrect =
+ 0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
+ final boolean flagAutoComplete =
+ 0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
- mShouldInsertSpacesAutomatically = InputTypeUtils.isAutoSpaceFriendlyType(inputType);
-
- // 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.
- // If it's not multiline and the autoCorrect flag is not set, then don't correct
- if ((variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
- && !flagAutoCorrect)
- || flagNoSuggestions
- || (!flagAutoCorrect && !flagMultiLine)) {
- mInputTypeNoAutoCorrect = true;
- } else {
- mInputTypeNoAutoCorrect = false;
- }
+ mIsPasswordField = InputTypeUtils.isPasswordInputType(inputType)
+ || InputTypeUtils.isVisiblePasswordInputType(inputType);
+ // TODO: Have a helper method in InputTypeUtils
+ // Make sure that passwords are not displayed in {@link SuggestionStripView}.
+ final boolean noSuggestionStrip = mIsPasswordField
+ || InputTypeUtils.isEmailVariation(variation)
+ || InputType.TYPE_TEXT_VARIATION_URI == variation
+ || InputType.TYPE_TEXT_VARIATION_FILTER == variation
+ || flagNoSuggestions
+ || flagAutoComplete;
+ mIsSettingsSuggestionStripOn = !noSuggestionStrip;
- mApplicationSpecifiedCompletionOn = flagAutoComplete && isFullscreenMode;
- }
+ mShouldInsertSpacesAutomatically = InputTypeUtils.isAutoSpaceFriendlyType(inputType);
+
+ // 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.
+ // If it's not multiline and the autoCorrect flag is not set, then don't correct
+ mInputTypeNoAutoCorrect =
+ (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT && !flagAutoCorrect)
+ || flagNoSuggestions
+ || (!flagAutoCorrect && !flagMultiLine);
+
+ mApplicationSpecifiedCompletionOn = flagAutoComplete && isFullscreenMode;
}
public boolean isTypeNull() {
@@ -113,99 +115,144 @@ public final class InputAttributes {
@SuppressWarnings("unused")
private void dumpFlags(final int inputType) {
- Log.i(TAG, "Input class:");
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
- if (inputClass == InputType.TYPE_CLASS_TEXT)
- Log.i(TAG, " TYPE_CLASS_TEXT");
- if (inputClass == InputType.TYPE_CLASS_PHONE)
- Log.i(TAG, " TYPE_CLASS_PHONE");
- if (inputClass == InputType.TYPE_CLASS_NUMBER)
- Log.i(TAG, " TYPE_CLASS_NUMBER");
- if (inputClass == InputType.TYPE_CLASS_DATETIME)
- Log.i(TAG, " TYPE_CLASS_DATETIME");
- Log.i(TAG, "Variation:");
- switch (InputType.TYPE_MASK_VARIATION & inputType) {
- case InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS:
- Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_ADDRESS");
- break;
- case InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT:
- Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_SUBJECT");
- break;
- case InputType.TYPE_TEXT_VARIATION_FILTER:
- Log.i(TAG, " TYPE_TEXT_VARIATION_FILTER");
- break;
- case InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE:
- Log.i(TAG, " TYPE_TEXT_VARIATION_LONG_MESSAGE");
- break;
- case InputType.TYPE_TEXT_VARIATION_NORMAL:
- Log.i(TAG, " TYPE_TEXT_VARIATION_NORMAL");
- break;
- case InputType.TYPE_TEXT_VARIATION_PASSWORD:
- Log.i(TAG, " TYPE_TEXT_VARIATION_PASSWORD");
- break;
- case InputType.TYPE_TEXT_VARIATION_PERSON_NAME:
- Log.i(TAG, " TYPE_TEXT_VARIATION_PERSON_NAME");
- break;
- case InputType.TYPE_TEXT_VARIATION_PHONETIC:
- Log.i(TAG, " TYPE_TEXT_VARIATION_PHONETIC");
- break;
- case InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS:
- Log.i(TAG, " TYPE_TEXT_VARIATION_POSTAL_ADDRESS");
- break;
- case InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE:
- Log.i(TAG, " TYPE_TEXT_VARIATION_SHORT_MESSAGE");
- break;
- case InputType.TYPE_TEXT_VARIATION_URI:
- Log.i(TAG, " TYPE_TEXT_VARIATION_URI");
- break;
- case InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD:
- Log.i(TAG, " TYPE_TEXT_VARIATION_VISIBLE_PASSWORD");
- break;
- case InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT:
- Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EDIT_TEXT");
- break;
- case InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS:
- Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS");
- break;
- case InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD:
- Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_PASSWORD");
- break;
- default:
- Log.i(TAG, " Unknown variation");
- break;
+ final String inputClassString = toInputClassString(inputClass);
+ final String variationString = toVariationString(
+ inputClass, inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
+ final String flagsString = toFlagsString(inputType & InputType.TYPE_MASK_FLAGS);
+ Log.i(TAG, "Input class: " + inputClassString);
+ Log.i(TAG, "Variation: " + variationString);
+ Log.i(TAG, "Flags: " + flagsString);
+ }
+
+ private static String toInputClassString(final int inputClass) {
+ switch (inputClass) {
+ case InputType.TYPE_CLASS_TEXT:
+ return "TYPE_CLASS_TEXT";
+ case InputType.TYPE_CLASS_PHONE:
+ return "TYPE_CLASS_PHONE";
+ case InputType.TYPE_CLASS_NUMBER:
+ return "TYPE_CLASS_NUMBER";
+ case InputType.TYPE_CLASS_DATETIME:
+ return "TYPE_CLASS_DATETIME";
+ default:
+ return String.format("unknownInputClass<0x%08x>", inputClass);
+ }
+ }
+
+ private static String toVariationString(final int inputClass, final int variation) {
+ switch (inputClass) {
+ case InputType.TYPE_CLASS_TEXT:
+ return toTextVariationString(variation);
+ case InputType.TYPE_CLASS_NUMBER:
+ return toNumberVariationString(variation);
+ case InputType.TYPE_CLASS_DATETIME:
+ return toDatetimeVariationString(variation);
+ default:
+ return "";
}
- Log.i(TAG, "Flags:");
- if (0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS))
- Log.i(TAG, " TYPE_TEXT_FLAG_NO_SUGGESTIONS");
- if (0 != (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE))
- Log.i(TAG, " TYPE_TEXT_FLAG_MULTI_LINE");
- if (0 != (inputType & InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE))
- Log.i(TAG, " TYPE_TEXT_FLAG_IME_MULTI_LINE");
- if (0 != (inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS))
- Log.i(TAG, " TYPE_TEXT_FLAG_CAP_WORDS");
- if (0 != (inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES))
- Log.i(TAG, " TYPE_TEXT_FLAG_CAP_SENTENCES");
- if (0 != (inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS))
- Log.i(TAG, " TYPE_TEXT_FLAG_CAP_CHARACTERS");
- if (0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT))
- Log.i(TAG, " TYPE_TEXT_FLAG_AUTO_CORRECT");
- if (0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE))
- Log.i(TAG, " TYPE_TEXT_FLAG_AUTO_COMPLETE");
+ }
+
+ private static String toTextVariationString(final int variation) {
+ switch (variation) {
+ case InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS:
+ return " TYPE_TEXT_VARIATION_EMAIL_ADDRESS";
+ case InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT:
+ return "TYPE_TEXT_VARIATION_EMAIL_SUBJECT";
+ case InputType.TYPE_TEXT_VARIATION_FILTER:
+ return "TYPE_TEXT_VARIATION_FILTER";
+ case InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE:
+ return "TYPE_TEXT_VARIATION_LONG_MESSAGE";
+ case InputType.TYPE_TEXT_VARIATION_NORMAL:
+ return "TYPE_TEXT_VARIATION_NORMAL";
+ case InputType.TYPE_TEXT_VARIATION_PASSWORD:
+ return "TYPE_TEXT_VARIATION_PASSWORD";
+ case InputType.TYPE_TEXT_VARIATION_PERSON_NAME:
+ return "TYPE_TEXT_VARIATION_PERSON_NAME";
+ case InputType.TYPE_TEXT_VARIATION_PHONETIC:
+ return "TYPE_TEXT_VARIATION_PHONETIC";
+ case InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS:
+ return "TYPE_TEXT_VARIATION_POSTAL_ADDRESS";
+ case InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE:
+ return "TYPE_TEXT_VARIATION_SHORT_MESSAGE";
+ case InputType.TYPE_TEXT_VARIATION_URI:
+ return "TYPE_TEXT_VARIATION_URI";
+ case InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD:
+ return "TYPE_TEXT_VARIATION_VISIBLE_PASSWORD";
+ case InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT:
+ return "TYPE_TEXT_VARIATION_WEB_EDIT_TEXT";
+ case InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS:
+ return "TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS";
+ case InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD:
+ return "TYPE_TEXT_VARIATION_WEB_PASSWORD";
+ default:
+ return String.format("unknownVariation<0x%08x>", variation);
+ }
+ }
+
+ private static String toNumberVariationString(final int variation) {
+ switch (variation) {
+ case InputType.TYPE_NUMBER_VARIATION_NORMAL:
+ return "TYPE_NUMBER_VARIATION_NORMAL";
+ case InputType.TYPE_NUMBER_VARIATION_PASSWORD:
+ return "TYPE_NUMBER_VARIATION_PASSWORD";
+ default:
+ return String.format("unknownVariation<0x%08x>", variation);
+ }
+ }
+
+ private static String toDatetimeVariationString(final int variation) {
+ switch (variation) {
+ case InputType.TYPE_DATETIME_VARIATION_NORMAL:
+ return "TYPE_DATETIME_VARIATION_NORMAL";
+ case InputType.TYPE_DATETIME_VARIATION_DATE:
+ return "TYPE_DATETIME_VARIATION_DATE";
+ case InputType.TYPE_DATETIME_VARIATION_TIME:
+ return "TYPE_DATETIME_VARIATION_TIME";
+ default:
+ return String.format("unknownVariation<0x%08x>", variation);
+ }
+ }
+
+ private static String toFlagsString(final int flags) {
+ final ArrayList<String> flagsArray = CollectionUtils.newArrayList();
+ if (0 != (flags & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS))
+ flagsArray.add("TYPE_TEXT_FLAG_NO_SUGGESTIONS");
+ if (0 != (flags & InputType.TYPE_TEXT_FLAG_MULTI_LINE))
+ flagsArray.add("TYPE_TEXT_FLAG_MULTI_LINE");
+ if (0 != (flags & InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE))
+ flagsArray.add("TYPE_TEXT_FLAG_IME_MULTI_LINE");
+ if (0 != (flags & InputType.TYPE_TEXT_FLAG_CAP_WORDS))
+ flagsArray.add("TYPE_TEXT_FLAG_CAP_WORDS");
+ if (0 != (flags & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES))
+ flagsArray.add("TYPE_TEXT_FLAG_CAP_SENTENCES");
+ if (0 != (flags & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS))
+ flagsArray.add("TYPE_TEXT_FLAG_CAP_CHARACTERS");
+ if (0 != (flags & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT))
+ flagsArray.add("TYPE_TEXT_FLAG_AUTO_CORRECT");
+ if (0 != (flags & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE))
+ flagsArray.add("TYPE_TEXT_FLAG_AUTO_COMPLETE");
+ return flagsArray.isEmpty() ? "" : Arrays.toString(flagsArray.toArray());
}
// Pretty print
@Override
public String toString() {
- return "\n mInputTypeNoAutoCorrect = " + mInputTypeNoAutoCorrect
- + "\n mIsSettingsSuggestionStripOn = " + mIsSettingsSuggestionStripOn
- + "\n mApplicationSpecifiedCompletionOn = " + mApplicationSpecifiedCompletionOn;
+ return String.format(
+ "%s: inputType=0x%08x%s%s%s%s%s targetApp=%s\n", getClass().getSimpleName(),
+ mInputType,
+ (mInputTypeNoAutoCorrect ? " noAutoCorrect" : ""),
+ (mIsPasswordField ? " password" : ""),
+ (mIsSettingsSuggestionStripOn ? " suggestionStrip" : ""),
+ (mApplicationSpecifiedCompletionOn ? " appSpecified" : ""),
+ (mShouldInsertSpacesAutomatically ? " insertSpaces" : ""),
+ mTargetApplicationPackageName);
}
- public static boolean inPrivateImeOptions(String packageName, String key,
- EditorInfo editorInfo) {
+ public static boolean inPrivateImeOptions(final String packageName, final String key,
+ final EditorInfo editorInfo) {
if (editorInfo == null) return false;
- final String findingKey = (packageName != null) ? packageName + "." + key
- : key;
+ final String findingKey = (packageName != null) ? packageName + "." + key : key;
return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions);
}
}
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index 2e638aaf3..47bc6b078 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -16,14 +16,17 @@
package com.android.inputmethod.latin;
+import android.util.Log;
+import android.util.SparseIntArray;
+
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.utils.ResizableIntArray;
-import android.util.Log;
-
// TODO: This class is not thread-safe.
public final class InputPointers {
private static final String TAG = InputPointers.class.getSimpleName();
+ private static final boolean DEBUG_TIME = false;
+
private final int mDefaultCapacity;
private final ResizableIntArray mXCoordinates;
private final ResizableIntArray mYCoordinates;
@@ -38,11 +41,29 @@ public final class InputPointers {
mTimes = new ResizableIntArray(defaultCapacity);
}
- public void addPointer(int index, int x, int y, int pointerId, int time) {
- mXCoordinates.add(index, x);
- mYCoordinates.add(index, y);
- mPointerIds.add(index, pointerId);
- mTimes.add(index, time);
+ private void fillWithLastTimeUntil(final int index) {
+ final int fromIndex = mTimes.getLength();
+ // Fill the gap with the latest time.
+ // See {@link #getTime(int)} and {@link #isValidTimeStamps()}.
+ if (fromIndex <= 0) {
+ return;
+ }
+ final int fillLength = index - fromIndex + 1;
+ if (fillLength <= 0) {
+ return;
+ }
+ final int lastTime = mTimes.get(fromIndex - 1);
+ mTimes.fill(lastTime, fromIndex, fillLength);
+ }
+
+ public void addPointerAt(int index, int x, int y, int pointerId, int time) {
+ mXCoordinates.addAt(index, x);
+ mYCoordinates.addAt(index, y);
+ mPointerIds.addAt(index, pointerId);
+ if (LatinImeLogger.sDBG || DEBUG_TIME) {
+ fillWithLastTimeUntil(index);
+ }
+ mTimes.addAt(index, time);
}
@UsedForTesting
@@ -68,23 +89,6 @@ public final class InputPointers {
}
/**
- * Append the pointers in the specified {@link InputPointers} to the end of this.
- * @param src the source {@link InputPointers} to read the data from.
- * @param startPos the starting index of the pointers in {@code src}.
- * @param length the number of pointers to be appended.
- */
- @UsedForTesting
- void append(InputPointers src, int startPos, int length) {
- if (length == 0) {
- return;
- }
- mXCoordinates.append(src.mXCoordinates, startPos, length);
- mYCoordinates.append(src.mYCoordinates, startPos, length);
- mPointerIds.append(src.mPointerIds, startPos, length);
- mTimes.append(src.mTimes, startPos, length);
- }
-
- /**
* Append the times, x-coordinates and y-coordinates in the specified {@link ResizableIntArray}
* to the end of this.
* @param pointerId the pointer id of the source.
@@ -141,7 +145,7 @@ public final class InputPointers {
}
public int[] getTimes() {
- if (LatinImeLogger.sDBG) {
+ if (LatinImeLogger.sDBG || DEBUG_TIME) {
if (!isValidTimeStamps()) {
throw new RuntimeException("Time stamps are invalid.");
}
@@ -157,14 +161,21 @@ public final class InputPointers {
private boolean isValidTimeStamps() {
final int[] times = mTimes.getPrimitiveArray();
- for (int i = 1; i < getPointerSize(); ++i) {
- if (times[i] < times[i - 1]) {
+ final int[] pointerIds = mPointerIds.getPrimitiveArray();
+ final SparseIntArray lastTimeOfPointers = new SparseIntArray();
+ final int size = getPointerSize();
+ for (int i = 0; i < size; ++i) {
+ final int pointerId = pointerIds[i];
+ final int time = times[i];
+ final int lastTime = lastTimeOfPointers.get(pointerId, time);
+ if (time < lastTime) {
// dump
- for (int j = 0; j < times.length; ++j) {
+ for (int j = 0; j < size; ++j) {
Log.d(TAG, "--- (" + j + ") " + times[j]);
}
return false;
}
+ lastTimeOfPointers.put(pointerId, time);
}
return true;
}
diff --git a/java/src/com/android/inputmethod/latin/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java
index 81ccf83d8..ea7859e60 100644
--- a/java/src/com/android/inputmethod/latin/InputView.java
+++ b/java/src/com/android/inputmethod/latin/InputView.java
@@ -23,87 +23,210 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
-public final class InputView extends LinearLayout {
- private View mSuggestionStripView;
- private View mKeyboardView;
- private int mKeyboardTopPadding;
+import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.suggestions.MoreSuggestionsView;
+import com.android.inputmethod.latin.suggestions.SuggestionStripView;
- private boolean mIsForwardingEvent;
+public final class InputView extends LinearLayout {
private final Rect mInputViewRect = new Rect();
- private final Rect mEventForwardingRect = new Rect();
- private final Rect mEventReceivingRect = new Rect();
+ private KeyboardTopPaddingForwarder mKeyboardTopPaddingForwarder;
+ private MoreSuggestionsViewCanceler mMoreSuggestionsViewCanceler;
+ private MotionEventForwarder<?, ?> mActiveForwarder;
public InputView(final Context context, final AttributeSet attrs) {
super(context, attrs, 0);
}
- public void setKeyboardGeometry(final int keyboardTopPadding) {
- mKeyboardTopPadding = keyboardTopPadding;
- }
-
@Override
protected void onFinishInflate() {
- mSuggestionStripView = findViewById(R.id.suggestion_strip_view);
- mKeyboardView = findViewById(R.id.keyboard_view);
+ final SuggestionStripView suggestionStripView =
+ (SuggestionStripView)findViewById(R.id.suggestion_strip_view);
+ final MainKeyboardView mainKeyboardView =
+ (MainKeyboardView)findViewById(R.id.keyboard_view);
+ mKeyboardTopPaddingForwarder = new KeyboardTopPaddingForwarder(
+ mainKeyboardView, suggestionStripView);
+ mMoreSuggestionsViewCanceler = new MoreSuggestionsViewCanceler(
+ mainKeyboardView, suggestionStripView);
+ }
+
+ public void setKeyboardTopPadding(final int keyboardTopPadding) {
+ mKeyboardTopPaddingForwarder.setKeyboardTopPadding(keyboardTopPadding);
}
@Override
- public boolean dispatchTouchEvent(final MotionEvent me) {
- if (mSuggestionStripView.getVisibility() != VISIBLE
- || mKeyboardView.getVisibility() != VISIBLE) {
- return super.dispatchTouchEvent(me);
- }
+ public boolean onInterceptTouchEvent(final MotionEvent me) {
+ final Rect rect = mInputViewRect;
+ getGlobalVisibleRect(rect);
+ final int index = me.getActionIndex();
+ final int x = (int)me.getX(index) + rect.left;
+ final int y = (int)me.getY(index) + rect.top;
// The touch events that hit the top padding of keyboard should be forwarded to
// {@link SuggestionStripView}.
+ if (mKeyboardTopPaddingForwarder.onInterceptTouchEvent(x, y, me)) {
+ mActiveForwarder = mKeyboardTopPaddingForwarder;
+ return true;
+ }
+
+ // To cancel {@link MoreSuggestionsView}, we should intercept a touch event to
+ // {@link MainKeyboardView} and dismiss the {@link MoreSuggestionsView}.
+ if (mMoreSuggestionsViewCanceler.onInterceptTouchEvent(x, y, me)) {
+ mActiveForwarder = mMoreSuggestionsViewCanceler;
+ return true;
+ }
+
+ mActiveForwarder = null;
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(final MotionEvent me) {
+ if (mActiveForwarder == null) {
+ return super.onTouchEvent(me);
+ }
+
final Rect rect = mInputViewRect;
- this.getGlobalVisibleRect(rect);
- final int x = (int)me.getX() + rect.left;
- final int y = (int)me.getY() + rect.top;
+ getGlobalVisibleRect(rect);
+ final int index = me.getActionIndex();
+ final int x = (int)me.getX(index) + rect.left;
+ final int y = (int)me.getY(index) + rect.top;
+ return mActiveForwarder.onTouchEvent(x, y, me);
+ }
- final Rect forwardingRect = mEventForwardingRect;
- mKeyboardView.getGlobalVisibleRect(forwardingRect);
- if (!mIsForwardingEvent && !forwardingRect.contains(x, y)) {
- return super.dispatchTouchEvent(me);
+ /**
+ * This class forwards series of {@link MotionEvent}s from <code>SenderView</code> to
+ * <code>ReceiverView</code>.
+ *
+ * @param <SenderView> a {@link View} that may send a {@link MotionEvent} to <ReceiverView>.
+ * @param <ReceiverView> a {@link View} that receives forwarded {@link MotionEvent} from
+ * <SenderView>.
+ */
+ private static abstract class
+ MotionEventForwarder<SenderView extends View, ReceiverView extends View> {
+ protected final SenderView mSenderView;
+ protected final ReceiverView mReceiverView;
+
+ protected final Rect mEventSendingRect = new Rect();
+ protected final Rect mEventReceivingRect = new Rect();
+
+ public MotionEventForwarder(final SenderView senderView, final ReceiverView receiverView) {
+ mSenderView = senderView;
+ mReceiverView = receiverView;
}
- final int forwardingLimitY = forwardingRect.top + mKeyboardTopPadding;
- boolean sendToTarget = false;
+ // Return true if a touch event of global coordinate x, y needs to be forwarded.
+ protected abstract boolean needsToForward(final int x, final int y);
- switch (me.getAction()) {
- case MotionEvent.ACTION_DOWN:
- if (y < forwardingLimitY) {
- // This down event and further move and up events should be forwarded to the target.
- mIsForwardingEvent = true;
- sendToTarget = true;
+ // Translate global x-coordinate to <code>ReceiverView</code> local coordinate.
+ protected int translateX(final int x) {
+ return x - mEventReceivingRect.left;
+ }
+
+ // Translate global y-coordinate to <code>ReceiverView</code> local coordinate.
+ protected int translateY(final int y) {
+ return y - mEventReceivingRect.top;
+ }
+
+ // Callback when a {@link MotionEvent} is forwarded.
+ protected void onForwardingEvent(final MotionEvent me) {}
+
+ // Returns true if a {@link MotionEvent} is needed to be forwarded to
+ // <code>ReceiverView</code>. Otherwise returns false.
+ public boolean onInterceptTouchEvent(final int x, final int y, final MotionEvent me) {
+ // Forwards a {link MotionEvent} only if both <code>SenderView</code> and
+ // <code>ReceiverView</code> are visible.
+ if (mSenderView.getVisibility() != View.VISIBLE ||
+ mReceiverView.getVisibility() != View.VISIBLE) {
+ return false;
+ }
+ mSenderView.getGlobalVisibleRect(mEventSendingRect);
+ if (!mEventSendingRect.contains(x, y)) {
+ return false;
}
- break;
- case MotionEvent.ACTION_MOVE:
- sendToTarget = mIsForwardingEvent;
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- sendToTarget = mIsForwardingEvent;
- mIsForwardingEvent = false;
- break;
- }
-
- if (!sendToTarget) {
- return super.dispatchTouchEvent(me);
- }
-
- final Rect receivingRect = mEventReceivingRect;
- mSuggestionStripView.getGlobalVisibleRect(receivingRect);
- final int translatedX = x - receivingRect.left;
- final int translatedY;
- if (y < forwardingLimitY) {
- // The forwarded event should have coordinates that are inside of the target.
- translatedY = Math.min(y - receivingRect.top, receivingRect.height() - 1);
- } else {
- translatedY = y - receivingRect.top;
- }
- me.setLocation(translatedX, translatedY);
- mSuggestionStripView.dispatchTouchEvent(me);
- return true;
+
+ if (me.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ // If the down event happens in the forwarding area, successive
+ // {@link MotionEvent}s should be forwarded to <code>ReceiverView</code>.
+ if (needsToForward(x, y)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Returns true if a {@link MotionEvent} is forwarded to <code>ReceiverView</code>.
+ // Otherwise returns false.
+ public boolean onTouchEvent(final int x, final int y, final MotionEvent me) {
+ mReceiverView.getGlobalVisibleRect(mEventReceivingRect);
+ // Translate global coordinates to <code>ReceiverView</code> local coordinates.
+ me.setLocation(translateX(x), translateY(y));
+ mReceiverView.dispatchTouchEvent(me);
+ onForwardingEvent(me);
+ return true;
+ }
+ }
+
+ /**
+ * This class forwards {@link MotionEvent}s happened in the top padding of
+ * {@link MainKeyboardView} to {@link SuggestionStripView}.
+ */
+ private static class KeyboardTopPaddingForwarder
+ extends MotionEventForwarder<MainKeyboardView, SuggestionStripView> {
+ private int mKeyboardTopPadding;
+
+ public KeyboardTopPaddingForwarder(final MainKeyboardView mainKeyboardView,
+ final SuggestionStripView suggestionStripView) {
+ super(mainKeyboardView, suggestionStripView);
+ }
+
+ public void setKeyboardTopPadding(final int keyboardTopPadding) {
+ mKeyboardTopPadding = keyboardTopPadding;
+ }
+
+ private boolean isInKeyboardTopPadding(final int y) {
+ return y < mEventSendingRect.top + mKeyboardTopPadding;
+ }
+
+ @Override
+ protected boolean needsToForward(final int x, final int y) {
+ return isInKeyboardTopPadding(y);
+ }
+
+ @Override
+ protected int translateY(final int y) {
+ final int translatedY = super.translateY(y);
+ if (isInKeyboardTopPadding(y)) {
+ // The forwarded event should have coordinates that are inside of the target.
+ return Math.min(translatedY, mEventReceivingRect.height() - 1);
+ }
+ return translatedY;
+ }
+ }
+
+ /**
+ * This class forwards {@link MotionEvent}s happened in the {@link MainKeyboardView} to
+ * {@link SuggestionStripView} when the {@link MoreSuggestionsView} is showing.
+ * {@link SuggestionStripView} dismisses {@link MoreSuggestionsView} when it receives any event
+ * outside of it.
+ */
+ private static class MoreSuggestionsViewCanceler
+ extends MotionEventForwarder<MainKeyboardView, SuggestionStripView> {
+ public MoreSuggestionsViewCanceler(final MainKeyboardView mainKeyboardView,
+ final SuggestionStripView suggestionStripView) {
+ super(mainKeyboardView, suggestionStripView);
+ }
+
+ @Override
+ protected boolean needsToForward(final int x, final int y) {
+ return mReceiverView.isShowingMoreSuggestionPanel() && mEventSendingRect.contains(x, y);
+ }
+
+ @Override
+ protected void onForwardingEvent(final MotionEvent me) {
+ if (me.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mReceiverView.dismissMoreSuggestionsPanel();
+ }
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 2e9280c77..232bf7407 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -18,6 +18,10 @@ package com.android.inputmethod.latin;
import android.text.TextUtils;
+import com.android.inputmethod.event.Event;
+
+import java.util.ArrayList;
+
/**
* This class encapsulates data about a word previously composed, but that has been
* committed already. This is used for resuming suggestion, and cancel auto-correction.
@@ -40,9 +44,9 @@ public final class LastComposedWord {
public static final String NOT_A_SEPARATOR = "";
- public final int[] mPrimaryKeyCodes;
+ public final ArrayList<Event> mEvents;
public final String mTypedWord;
- public final String mCommittedWord;
+ public final CharSequence mCommittedWord;
public final String mSeparatorString;
public final String mPrevWord;
public final int mCapitalizedMode;
@@ -52,19 +56,20 @@ public final class LastComposedWord {
private boolean mActive;
public static final LastComposedWord NOT_A_COMPOSED_WORD =
- new LastComposedWord(null, null, "", "", NOT_A_SEPARATOR, null,
- WordComposer.CAPS_MODE_OFF);
+ new LastComposedWord(new ArrayList<Event>(), null, "", "",
+ NOT_A_SEPARATOR, null, WordComposer.CAPS_MODE_OFF);
// Warning: this is using the passed objects as is and fully expects them to be
// immutable. Do not fiddle with their contents after you passed them to this constructor.
- public LastComposedWord(final int[] primaryKeyCodes, final InputPointers inputPointers,
- final String typedWord, final String committedWord, final String separatorString,
+ public LastComposedWord(final ArrayList<Event> events,
+ final InputPointers inputPointers, final String typedWord,
+ final CharSequence committedWord, final String separatorString,
final String prevWord, final int capitalizedMode) {
- mPrimaryKeyCodes = primaryKeyCodes;
if (inputPointers != null) {
mInputPointers.copy(inputPointers);
}
mTypedWord = typedWord;
+ mEvents = new ArrayList<Event>(events);
mCommittedWord = committedWord;
mSeparatorString = separatorString;
mActive = true;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 77d07019f..81b02c396 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -25,10 +25,10 @@ import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -36,39 +36,33 @@ import android.inputmethodservice.InputMethodService;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.os.Debug;
-import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
-import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
import android.util.Log;
-import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Printer;
-import android.view.KeyCharacterMap;
+import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.compat.AppWorkaroundsUtils;
import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
-import com.android.inputmethod.compat.SuggestionSpanUtils;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
-import com.android.inputmethod.event.EventInterpreter;
-import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.event.Event;
+import com.android.inputmethod.event.HardwareEventDecoder;
+import com.android.inputmethod.event.HardwareKeyboardEventDecoder;
+import com.android.inputmethod.event.InputTransaction;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardId;
@@ -77,232 +71,174 @@ import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.inputlogic.InputLogic;
import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever;
-import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
-import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegistrar;
import com.android.inputmethod.latin.personalization.PersonalizationHelper;
-import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsActivity;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.suggestions.SuggestionStripView;
+import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
import com.android.inputmethod.latin.utils.ApplicationUtils;
-import com.android.inputmethod.latin.utils.AsyncResultHolder;
-import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.CapsModeUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.CompletionInfoUtils;
-import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.utils.DialogUtils;
+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.LatinImeLoggerUtils;
-import com.android.inputmethod.latin.utils.RecapitalizeStatus;
-import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
-import com.android.inputmethod.latin.utils.StringUtils;
-import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
-import com.android.inputmethod.latin.utils.TextRange;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
+import com.android.inputmethod.latin.utils.StatsUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Locale;
-import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
/**
* Input method implementation for Qwerty'ish keyboard.
*/
public class LatinIME extends InputMethodService implements KeyboardActionListener,
- SuggestionStripView.Listener, TargetPackageInfoGetterTask.OnTargetPackageInfoKnownListener,
- Suggest.SuggestInitializationListener {
+ SuggestionStripView.Listener, SuggestionStripViewAccessor,
+ DictionaryFacilitatorForSuggest.DictionaryInitializationListener,
+ ImportantNoticeDialog.ImportantNoticeDialogListener {
private static final String TAG = LatinIME.class.getSimpleName();
private static final boolean TRACE = false;
- private static boolean DEBUG;
+ private static boolean DEBUG = false;
private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100;
- // How many continuous deletes at which to start deleting at a higher speed.
- private static final int DELETE_ACCELERATE_AT = 20;
- // Key events coming any faster than this are long-presses.
- private static final int QUICK_PRESS = 200;
-
private static final int PENDING_IMS_CALLBACK_DURATION = 800;
private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2;
- // TODO: Set this value appropriately.
- private static final int GET_SUGGESTED_WORDS_TIMEOUT = 200;
-
/**
* The name of the scheme used by the Package Manager to warn of a new package installation,
* replacement or removal.
*/
private static final String SCHEME_PACKAGE = "package";
- private static final int SPACE_STATE_NONE = 0;
- // Double space: the state where the user pressed space twice quickly, which LatinIME
- // resolved as period-space. Undoing this converts the period to a space.
- private static final int SPACE_STATE_DOUBLE = 1;
- // Swap punctuation: the state where a weak space and a punctuation from the suggestion strip
- // have just been swapped. Undoing this swaps them back; the space is still considered weak.
- private static final int SPACE_STATE_SWAP_PUNCTUATION = 2;
- // Weak space: a space that should be swapped only by suggestion strip punctuation. Weak
- // spaces happen when the user presses space, accepting the current suggestion (whether
- // it's an auto-correction or not).
- private static final int SPACE_STATE_WEAK = 3;
- // Phantom space: a not-yet-inserted space that should get inserted on the next input,
- // character provided it's not a separator. If it's a separator, the phantom space is dropped.
- // Phantom spaces happen when a user chooses a word from the suggestion strip.
- private static final int SPACE_STATE_PHANTOM = 4;
-
- // Current space state of the input method. This can be any of the above constants.
- private int mSpaceState;
-
private final Settings mSettings;
+ private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
+ this /* SuggestionStripViewAccessor */);
+ // We expect to have only one decoder in almost all cases, hence the default capacity of 1.
+ // If it turns out we need several, it will get grown seamlessly.
+ final SparseArray<HardwareEventDecoder> mHardwareEventDecoders
+ = new SparseArray<HardwareEventDecoder>(1);
private View mExtractArea;
private View mKeyPreviewBackingView;
private SuggestionStripView mSuggestionStripView;
- // Never null
- private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
- private Suggest mSuggest;
- private CompletionInfo[] mApplicationSpecifiedCompletions;
- private AppWorkaroundsUtils mAppWorkAroundsUtils = new AppWorkaroundsUtils();
private RichInputMethodManager mRichImm;
@UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
private final SubtypeSwitcher mSubtypeSwitcher;
private final SubtypeState mSubtypeState = new SubtypeState();
- // At start, create a default event interpreter that does nothing by passing it no decoder spec.
- // The event interpreter should never be null.
- private EventInterpreter mEventInterpreter = new EventInterpreter(this);
-
- private boolean mIsMainDictionaryAvailable;
- private UserBinaryDictionary mUserDictionary;
- private UserHistoryDictionary mUserHistoryDictionary;
- private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary;
- private PersonalizationDictionary mPersonalizationDictionary;
- private boolean mIsUserDictionaryAvailable;
-
- private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
- private final WordComposer mWordComposer = new WordComposer();
- private final RichInputConnection mConnection = new RichInputConnection(this);
- private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
-
- // Keep track of the last selection range to decide if we need to show word alternatives
- private static final int NOT_A_CURSOR_POSITION = -1;
- private int mLastSelectionStart = NOT_A_CURSOR_POSITION;
- private int mLastSelectionEnd = NOT_A_CURSOR_POSITION;
-
- // Whether we are expecting an onUpdateSelection event to fire. If it does when we don't
- // "expect" it, it means the user actually moved the cursor.
- private boolean mExpectingUpdateSelection;
- private int mDeleteCount;
- private long mLastKeyTime;
- private final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
- // Personalization debugging params
- private boolean mUseOnlyPersonalizationDictionaryForDebug = false;
- private boolean mBoostPersonalizationDictionaryForDebug = false;
-
- // Member variables for remembering the current device orientation.
- private int mDisplayOrientation;
// Object for reacting to adding/removing a dictionary pack.
private BroadcastReceiver mDictionaryPackInstallReceiver =
new DictionaryPackInstallBroadcastReceiver(this);
- // Keeps track of most recently inserted text (multi-character key) for reverting
- private String mEnteredText;
-
- // TODO: This boolean is persistent state and causes large side effects at unexpected times.
- // Find a way to remove it for readability.
- private boolean mIsAutoCorrectionIndicatorOn;
+ private BroadcastReceiver mDictionaryDumpBroadcastReceiver =
+ new DictionaryDumpBroadcastReceiver(this);
private AlertDialog mOptionsDialog;
private final boolean mIsHardwareAcceleratedDrawingEnabled;
public final UIHandler mHandler = new UIHandler(this);
- private InputUpdater mInputUpdater;
- public static final class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
+ public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> {
private static final int MSG_UPDATE_SHIFT_STATE = 0;
private static final int MSG_PENDING_IMS_CALLBACK = 1;
private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3;
private static final int MSG_RESUME_SUGGESTIONS = 4;
private static final int MSG_REOPEN_DICTIONARIES = 5;
- private static final int MSG_ON_END_BATCH_INPUT = 6;
+ private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6;
private static final int MSG_RESET_CACHES = 7;
+ // Update this when adding new messages
+ private static final int MSG_LAST = MSG_RESET_CACHES;
private static final int ARG1_NOT_GESTURE_INPUT = 0;
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
private static final int ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT = 2;
- private static final int ARG2_WITHOUT_TYPED_WORD = 0;
- private static final int ARG2_WITH_TYPED_WORD = 1;
+ private static final int ARG2_UNUSED = 0;
private int mDelayUpdateSuggestions;
private int mDelayUpdateShiftState;
- private long mDoubleSpacePeriodTimeout;
- private long mDoubleSpacePeriodTimerStart;
- public UIHandler(final LatinIME outerInstance) {
- super(outerInstance);
+ public UIHandler(final LatinIME ownerInstance) {
+ super(ownerInstance);
}
public void onCreate() {
- final Resources res = getOuterInstance().getResources();
- mDelayUpdateSuggestions =
- res.getInteger(R.integer.config_delay_update_suggestions);
- mDelayUpdateShiftState =
- res.getInteger(R.integer.config_delay_update_shift_state);
- mDoubleSpacePeriodTimeout =
- res.getInteger(R.integer.config_double_space_period_timeout);
+ final LatinIME latinIme = getOwnerInstance();
+ if (latinIme == null) {
+ return;
+ }
+ final Resources res = latinIme.getResources();
+ mDelayUpdateSuggestions = res.getInteger(R.integer.config_delay_update_suggestions);
+ mDelayUpdateShiftState = res.getInteger(R.integer.config_delay_update_shift_state);
}
@Override
public void handleMessage(final Message msg) {
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
+ if (latinIme == null) {
+ return;
+ }
final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher;
switch (msg.what) {
case MSG_UPDATE_SUGGESTION_STRIP:
- latinIme.updateSuggestionStrip();
+ cancelUpdateSuggestionStrip();
+ latinIme.mInputLogic.performUpdateSuggestionStripSync(
+ latinIme.mSettings.getCurrent());
break;
case MSG_UPDATE_SHIFT_STATE:
- switcher.updateShiftState();
+ switcher.requestUpdatingShiftState(latinIme.getCurrentAutoCapsState(),
+ latinIme.getCurrentRecapitalizeState());
break;
case MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
if (msg.arg1 == ARG1_NOT_GESTURE_INPUT) {
- if (msg.arg2 == ARG2_WITH_TYPED_WORD) {
- final Pair<SuggestedWords, String> p =
- (Pair<SuggestedWords, String>) msg.obj;
- latinIme.showSuggestionStripWithTypedWord(p.first, p.second);
- } else {
- latinIme.showSuggestionStrip((SuggestedWords) msg.obj);
- }
+ final SuggestedWords suggestedWords = (SuggestedWords) msg.obj;
+ latinIme.showSuggestionStrip(suggestedWords);
} else {
latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords) msg.obj,
msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
}
break;
case MSG_RESUME_SUGGESTIONS:
- latinIme.restartSuggestionsOnWordTouchedByCursor();
+ latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
+ latinIme.mSettings.getCurrent(),
+ false /* includeResumedWordInSuggestions */);
break;
case MSG_REOPEN_DICTIONARIES:
- latinIme.initSuggest();
+ latinIme.resetSuggest();
// In theory we could call latinIme.updateSuggestionStrip() right away, but
// in the practice, the dictionary is not finished opening yet so we wouldn't
// get any suggestions. Wait one frame.
postUpdateSuggestionStrip();
break;
- case MSG_ON_END_BATCH_INPUT:
- latinIme.onEndBatchInputAsyncInternal((SuggestedWords) msg.obj);
+ case MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED:
+ latinIme.mInputLogic.onUpdateTailBatchInputCompleted(
+ latinIme.mSettings.getCurrent(),
+ (SuggestedWords) msg.obj, latinIme.mKeyboardSwitcher);
break;
case MSG_RESET_CACHES:
- latinIme.retryResetCaches(msg.arg1 == 1 /* tryResumeSuggestions */,
- msg.arg2 /* remainingTries */);
+ final SettingsValues settingsValues = latinIme.mSettings.getCurrent();
+ if (latinIme.mInputLogic.retryResetCachesAndReturnSuccess(settingsValues,
+ msg.arg1 == 1 /* tryResumeSuggestions */,
+ msg.arg2 /* remainingTries */, this /* handler */)) {
+ // If we were able to reset the caches, then we can reload the keyboard.
+ // Otherwise, we'll do it when we can.
+ latinIme.mKeyboardSwitcher.loadKeyboard(latinIme.getCurrentInputEditorInfo(),
+ settingsValues, latinIme.getCurrentAutoCapsState(),
+ latinIme.getCurrentRecapitalizeState());
+ }
break;
}
}
@@ -316,6 +252,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
public void postResumeSuggestions() {
+ final LatinIME latinIme = getOwnerInstance();
+ if (latinIme == null) {
+ return;
+ }
+ if (!latinIme.mSettings.getCurrent().isSuggestionStripVisible()) {
+ return;
+ }
removeMessages(MSG_RESUME_SUGGESTIONS);
sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
}
@@ -343,8 +286,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), mDelayUpdateShiftState);
}
- public void cancelUpdateShiftState() {
- removeMessages(MSG_UPDATE_SHIFT_STATE);
+ @UsedForTesting
+ public void removeAllMessages() {
+ for (int i = 0; i <= MSG_LAST; ++i) {
+ removeMessages(i);
+ }
}
public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
@@ -354,39 +300,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
? ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT
: ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT;
obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1,
- ARG2_WITHOUT_TYPED_WORD, suggestedWords).sendToTarget();
+ ARG2_UNUSED, suggestedWords).sendToTarget();
}
public void showSuggestionStrip(final SuggestedWords suggestedWords) {
removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP,
- ARG1_NOT_GESTURE_INPUT, ARG2_WITHOUT_TYPED_WORD, suggestedWords).sendToTarget();
+ ARG1_NOT_GESTURE_INPUT, ARG2_UNUSED, suggestedWords).sendToTarget();
}
- // TODO: Remove this method.
- public void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
- final String typedWord) {
- removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
- obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, ARG1_NOT_GESTURE_INPUT,
- ARG2_WITH_TYPED_WORD,
- new Pair<SuggestedWords, String>(suggestedWords, typedWord)).sendToTarget();
- }
-
- public void onEndBatchInput(final SuggestedWords suggestedWords) {
- obtainMessage(MSG_ON_END_BATCH_INPUT, suggestedWords).sendToTarget();
- }
-
- public void startDoubleSpacePeriodTimer() {
- mDoubleSpacePeriodTimerStart = SystemClock.uptimeMillis();
- }
-
- public void cancelDoubleSpacePeriodTimer() {
- mDoubleSpacePeriodTimerStart = 0;
- }
-
- public boolean isAcceptingDoubleSpacePeriod() {
- return SystemClock.uptimeMillis() - mDoubleSpacePeriodTimerStart
- < mDoubleSpacePeriodTimeout;
+ public void showTailBatchInputResult(final SuggestedWords suggestedWords) {
+ obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget();
}
// Working variables for the following methods.
@@ -401,7 +325,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
removeMessages(MSG_PENDING_IMS_CALLBACK);
resetPendingImsCallback();
mIsOrientationChanging = true;
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
+ if (latinIme == null) {
+ return;
+ }
if (latinIme.isInputViewShown()) {
latinIme.mKeyboardSwitcher.saveKeyboardState();
}
@@ -415,12 +342,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void executePendingImsCallback(final LatinIME latinIme, final EditorInfo editorInfo,
boolean restarting) {
- if (mHasPendingFinishInputView)
+ if (mHasPendingFinishInputView) {
latinIme.onFinishInputViewInternal(mHasPendingFinishInput);
- if (mHasPendingFinishInput)
+ }
+ if (mHasPendingFinishInput) {
latinIme.onFinishInputInternal();
- if (mHasPendingStartInput)
+ }
+ if (mHasPendingStartInput) {
latinIme.onStartInputInternal(editorInfo, restarting);
+ }
resetPendingImsCallback();
}
@@ -434,9 +364,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mIsOrientationChanging = false;
mPendingSuccessiveImsCallback = true;
}
- final LatinIME latinIme = getOuterInstance();
- executePendingImsCallback(latinIme, editorInfo, restarting);
- latinIme.onStartInputInternal(editorInfo, restarting);
+ final LatinIME latinIme = getOwnerInstance();
+ if (latinIme != null) {
+ executePendingImsCallback(latinIme, editorInfo, restarting);
+ latinIme.onStartInputInternal(editorInfo, restarting);
+ if (ProductionFlag.USES_CURSOR_ANCHOR_MONITOR) {
+ // Currently we need to call this every time when the IME is attached to
+ // new application.
+ // TODO: Consider if we can do this automatically in the framework.
+ InputMethodServiceCompatUtils.setCursorAnchorMonitorMode(latinIme, 1);
+ }
+ }
}
}
@@ -453,10 +391,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
PENDING_IMS_CALLBACK_DURATION);
}
- final LatinIME latinIme = getOuterInstance();
- executePendingImsCallback(latinIme, editorInfo, restarting);
- latinIme.onStartInputViewInternal(editorInfo, restarting);
- mAppliedEditorInfo = editorInfo;
+ final LatinIME latinIme = getOwnerInstance();
+ if (latinIme != null) {
+ executePendingImsCallback(latinIme, editorInfo, restarting);
+ latinIme.onStartInputViewInternal(editorInfo, restarting);
+ mAppliedEditorInfo = editorInfo;
+ }
}
}
@@ -465,9 +405,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Typically this is the first onFinishInputView after orientation changed.
mHasPendingFinishInputView = true;
} else {
- final LatinIME latinIme = getOuterInstance();
- latinIme.onFinishInputViewInternal(finishingInput);
- mAppliedEditorInfo = null;
+ final LatinIME latinIme = getOwnerInstance();
+ if (latinIme != null) {
+ latinIme.onFinishInputViewInternal(finishingInput);
+ mAppliedEditorInfo = null;
+ }
}
}
@@ -476,9 +418,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Typically this is the first onFinishInput after orientation changed.
mHasPendingFinishInput = true;
} else {
- final LatinIME latinIme = getOuterInstance();
- executePendingImsCallback(latinIme, null, false);
- latinIme.onFinishInputInternal();
+ final LatinIME latinIme = getOwnerInstance();
+ if (latinIme != null) {
+ executePendingImsCallback(latinIme, null, false);
+ latinIme.onFinishInputInternal();
+ }
}
}
}
@@ -536,7 +480,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
KeyboardSwitcher.init(this);
AudioAndHapticFeedbackManager.init(this);
AccessibilityUtils.init(this);
- PersonalizationDictionarySessionRegister.init(this);
super.onCreate();
@@ -545,19 +488,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}.
loadSettings();
- initSuggest();
+ resetSuggest();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().init(this, mKeyboardSwitcher, mSuggest);
+ ResearchLogger.getInstance().init(this, mKeyboardSwitcher);
+ ResearchLogger.getInstance().initDictionary(
+ mInputLogic.mSuggest.mDictionaryFacilitator);
}
- mDisplayOrientation = getResources().getConfiguration().orientation;
// Register to receive ringer mode change and network state change.
// Also receive installation and removal of a dictionary pack.
final IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
- registerReceiver(mReceiver, filter);
+ registerReceiver(mConnectivityAndRingerModeChangeReceiver, filter);
final IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -569,47 +513,74 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
+ final IntentFilter dictDumpFilter = new IntentFilter();
+ dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
+ registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter);
+
DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this);
- mInputUpdater = new InputUpdater(this);
+ StatsUtils.onCreateCompleted(this);
}
// Has to be package-visible for unit tests
@UsedForTesting
void loadSettings() {
final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- final InputAttributes inputAttributes =
- new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode());
- mSettings.loadSettings(locale, inputAttributes);
- AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(mSettings.getCurrent());
- // To load the keyboard we need to load all the settings once, but resetting the
- // contacts dictionary should be deferred until after the new layout has been displayed
- // to improve responsivity. In the language switching process, we post a reopenDictionaries
- // message, then come here to read the settings for the new language before we change
- // the layout; at this time, we need to skip resetting the contacts dictionary. It will
- // be done later inside {@see #initSuggest()} when the reopenDictionaries message is
- // processed.
+ final EditorInfo editorInfo = getCurrentInputEditorInfo();
+ final InputAttributes inputAttributes = new InputAttributes(editorInfo, isFullscreenMode());
+ mSettings.loadSettings(this, locale, inputAttributes);
+ final SettingsValues currentSettingsValues = mSettings.getCurrent();
+ AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
+ // This method is called on startup and language switch, before the new layout has
+ // been displayed. Opening dictionaries never affects responsivity as dictionaries are
+ // asynchronously loaded.
if (!mHandler.hasPendingReopenDictionaries()) {
- // May need to reset the contacts dictionary depending on the user settings.
- resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
+ resetSuggestForLocale(locale);
+ }
+ refreshPersonalizationDictionarySession();
+ }
+
+ private void refreshPersonalizationDictionarySession() {
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator =
+ mInputLogic.mSuggest.mDictionaryFacilitator;
+ final boolean shouldKeepUserHistoryDictionaries;
+ final boolean shouldKeepPersonalizationDictionaries;
+ if (mSettings.getCurrent().mUsePersonalizedDicts) {
+ shouldKeepUserHistoryDictionaries = true;
+ // TODO: Eliminate this restriction
+ shouldKeepPersonalizationDictionaries =
+ mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes();
+ } else {
+ shouldKeepUserHistoryDictionaries = false;
+ shouldKeepPersonalizationDictionaries = false;
+ }
+ if (!shouldKeepUserHistoryDictionaries) {
+ // Remove user history dictionaries.
+ PersonalizationHelper.removeAllUserHistoryDictionaries(this);
+ dictionaryFacilitator.clearUserHistoryDictionary();
+ }
+ if (!shouldKeepPersonalizationDictionaries) {
+ // Remove personalization dictionaries.
+ PersonalizationHelper.removeAllPersonalizationDictionaries(this);
+ PersonalizationDictionarySessionRegistrar.resetAll(this);
+ } else {
+ PersonalizationDictionarySessionRegistrar.init(this, dictionaryFacilitator);
}
}
// Note that this method is called from a non-UI thread.
@Override
public void onUpdateMainDictionaryAvailability(final boolean isMainDictionaryAvailable) {
- mIsMainDictionaryAvailable = isMainDictionaryAvailable;
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable);
}
}
- private void initSuggest() {
+ private void resetSuggest() {
final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
final String switcherLocaleStr = switcherSubtypeLocale.toString();
final Locale subtypeLocale;
- final String localeStr;
if (TextUtils.isEmpty(switcherLocaleStr)) {
// This happens in very rare corner cases - for example, immediately after a switch
// to LatinIME has been requested, about a frame later another switch happens. In this
@@ -619,132 +590,80 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// of knowing anyway.
Log.e(TAG, "System is reporting no current subtype.");
subtypeLocale = getResources().getConfiguration().locale;
- localeStr = subtypeLocale.toString();
} else {
subtypeLocale = switcherSubtypeLocale;
- localeStr = switcherLocaleStr;
- }
-
- final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale,
- this /* SuggestInitializationListener */);
- final SettingsValues settingsValues = mSettings.getCurrent();
- if (settingsValues.mCorrectionEnabled) {
- newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold);
}
-
- mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().initSuggest(newSuggest);
- }
-
- mUserDictionary = new UserBinaryDictionary(this, localeStr);
- mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
- newSuggest.setUserDictionary(mUserDictionary);
-
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-
- mUserHistoryDictionary = PersonalizationHelper.getUserHistoryDictionary(
- this, localeStr, prefs);
- newSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
- mPersonalizationDictionary = PersonalizationHelper
- .getPersonalizationDictionary(this, localeStr, prefs);
- newSuggest.setPersonalizationDictionary(mPersonalizationDictionary);
- mPersonalizationPredictionDictionary = PersonalizationHelper
- .getPersonalizationPredictionDictionary(this, localeStr, prefs);
- newSuggest.setPersonalizationPredictionDictionary(mPersonalizationPredictionDictionary);
-
- final Suggest oldSuggest = mSuggest;
- resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null);
- mSuggest = newSuggest;
- if (oldSuggest != null) oldSuggest.close();
+ resetSuggestForLocale(subtypeLocale);
}
/**
- * Resets the contacts dictionary in mSuggest according to the user settings.
- *
- * This method takes an optional contacts dictionary to use when the locale hasn't changed
- * since the contacts dictionary can be opened or closed as necessary depending on the settings.
+ * Reset suggest by loading dictionaries for the locale and the current settings values.
*
- * @param oldContactsDictionary an optional dictionary to use, or null
+ * @param locale the locale
*/
- private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary) {
- final Suggest suggest = mSuggest;
- final boolean shouldSetDictionary =
- (null != suggest && mSettings.getCurrent().mUseContactsDict);
-
- final ContactsBinaryDictionary dictionaryToUse;
- if (!shouldSetDictionary) {
- // Make sure the dictionary is closed. If it is already closed, this is a no-op,
- // so it's safe to call it anyways.
- if (null != oldContactsDictionary) oldContactsDictionary.close();
- dictionaryToUse = null;
- } else {
- final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- if (null != oldContactsDictionary) {
- if (!oldContactsDictionary.mLocale.equals(locale)) {
- // If the locale has changed then recreate the contacts dictionary. This
- // allows locale dependent rules for handling bigram name predictions.
- oldContactsDictionary.close();
- dictionaryToUse = new ContactsBinaryDictionary(this, locale);
- } else {
- // Make sure the old contacts dictionary is opened. If it is already open,
- // this is a no-op, so it's safe to call it anyways.
- oldContactsDictionary.reopen(this);
- dictionaryToUse = oldContactsDictionary;
- }
- } else {
- dictionaryToUse = new ContactsBinaryDictionary(this, locale);
- }
- }
-
- if (null != suggest) {
- suggest.setContactsDictionary(dictionaryToUse);
+ private void resetSuggestForLocale(final Locale locale) {
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator =
+ mInputLogic.mSuggest.mDictionaryFacilitator;
+ final SettingsValues settingsValues = mSettings.getCurrent();
+ dictionaryFacilitator.resetDictionaries(this /* context */, locale,
+ settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
+ false /* forceReloadMainDictionary */, this);
+ if (settingsValues.mCorrectionEnabled) {
+ mInputLogic.mSuggest.setAutoCorrectionThreshold(
+ settingsValues.mAutoCorrectionThreshold);
}
}
+ /**
+ * Reset suggest by loading the main dictionary of the current locale.
+ */
/* package private */ void resetSuggestMainDict() {
- final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- mSuggest.resetMainDict(this, subtypeLocale, this /* SuggestInitializationListener */);
- mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator =
+ mInputLogic.mSuggest.mDictionaryFacilitator;
+ final SettingsValues settingsValues = mSettings.getCurrent();
+ dictionaryFacilitator.resetDictionaries(this /* context */,
+ dictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict,
+ settingsValues.mUsePersonalizedDicts, true /* forceReloadMainDictionary */, this);
}
@Override
public void onDestroy() {
- final Suggest suggest = mSuggest;
- if (suggest != null) {
- suggest.close();
- mSuggest = null;
- }
+ mInputLogic.mSuggest.mDictionaryFacilitator.closeDictionaries();
mSettings.onDestroy();
- unregisterReceiver(mReceiver);
+ unregisterReceiver(mConnectivityAndRingerModeChangeReceiver);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().onDestroy();
}
unregisterReceiver(mDictionaryPackInstallReceiver);
- PersonalizationDictionarySessionRegister.onDestroy(this);
+ unregisterReceiver(mDictionaryDumpBroadcastReceiver);
+ PersonalizationDictionarySessionRegistrar.close(this);
LatinImeLogger.commit();
LatinImeLogger.onDestroy();
- if (mInputUpdater != null) {
- mInputUpdater.quitLooper();
- }
+ StatsUtils.onDestroy();
super.onDestroy();
}
+ @UsedForTesting
+ public void recycle() {
+ unregisterReceiver(mDictionaryPackInstallReceiver);
+ unregisterReceiver(mDictionaryDumpBroadcastReceiver);
+ unregisterReceiver(mConnectivityAndRingerModeChangeReceiver);
+ mInputLogic.recycle();
+ }
+
@Override
public void onConfigurationChanged(final Configuration conf) {
// If orientation changed while predicting, commit the change
- if (mDisplayOrientation != conf.orientation) {
- mDisplayOrientation = conf.orientation;
+ final SettingsValues settingsValues = mSettings.getCurrent();
+ if (settingsValues.mDisplayOrientation != conf.orientation) {
mHandler.startOrientationChanging();
- mConnection.beginBatchEdit();
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
- mConnection.finishComposingText();
- mConnection.endBatchEdit();
- if (isShowingOptionDialog()) {
- mOptionsDialog.dismiss();
- }
+ mInputLogic.mConnection.beginBatchEdit();
+ mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR);
+ mInputLogic.mConnection.finishComposingText();
+ mInputLogic.mConnection.endBatchEdit();
}
- PersonalizationDictionarySessionRegister.onConfigurationChanged(this, conf);
+ PersonalizationDictionarySessionRegistrar.onConfigurationChanged(this, conf,
+ mInputLogic.mSuggest.mDictionaryFacilitator);
super.onConfigurationChanged(conf);
}
@@ -760,8 +679,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
.findViewById(android.R.id.extractArea);
mKeyPreviewBackingView = view.findViewById(R.id.key_preview_backing);
mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view);
- if (mSuggestionStripView != null)
+ if (hasSuggestionStripView()) {
mSuggestionStripView.setListener(this, view);
+ }
if (LatinImeLogger.sVISUALDEBUG) {
mKeyPreviewBackingView.setBackgroundColor(0x10FF0000);
}
@@ -834,29 +754,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ ", word caps = "
+ ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0));
}
+ Log.i(TAG, "Starting input. Cursor position = "
+ + editorInfo.initialSelStart + "," + editorInfo.initialSelEnd);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, prefs);
}
if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
- Log.w(TAG, "Deprecated private IME option specified: "
- + editorInfo.privateImeOptions);
+ Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead");
}
if (InputAttributes.inPrivateImeOptions(getPackageName(), FORCE_ASCII, editorInfo)) {
- Log.w(TAG, "Deprecated private IME option specified: "
- + editorInfo.privateImeOptions);
+ Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead");
}
- final PackageInfo packageInfo =
- TargetPackageInfoGetterTask.getCachedPackageInfo(editorInfo.packageName);
- mAppWorkAroundsUtils.setPackageInfo(packageInfo);
- if (null == packageInfo) {
- new TargetPackageInfoGetterTask(this /* context */, this /* listener */)
- .execute(editorInfo.packageName);
- }
-
LatinImeLogger.onStartInputView(editorInfo);
// In landscape mode, this method gets called without the input view being created.
if (mainKeyboardView == null) {
@@ -878,57 +790,53 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// The EditorInfo might have a flag that affects fullscreen mode.
// Note: This call should be done by InputMethodService?
updateFullscreenMode();
- mApplicationSpecifiedCompletions = null;
// The app calling setText() has the effect of clearing the composing
// span, so we should reset our state unconditionally, even if restarting is true.
- mEnteredText = null;
- resetComposingState(true /* alsoResetLastComposedWord */);
- mDeleteCount = 0;
- mSpaceState = SPACE_STATE_NONE;
- mRecapitalizeStatus.deactivate();
- mCurrentlyPressedHardwareKeys.clear();
+ mInputLogic.startInput(restarting, editorInfo);
// Note: the following does a round-trip IPC on the main thread: be careful
final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- final Suggest suggest = mSuggest;
- if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) {
- initSuggest();
+ final Suggest suggest = mInputLogic.mSuggest;
+ if (null != currentLocale && !currentLocale.equals(suggest.getLocale())) {
+ // TODO: Do this automatically.
+ resetSuggest();
}
- if (mSuggestionStripView != null) {
- // This will set the punctuation suggestions if next word suggestion is off;
- // otherwise it will clear the suggestion strip.
- setPunctuationSuggestions();
- }
- mSuggestedWords = SuggestedWords.EMPTY;
- // Sometimes, while rotating, for some reason the framework tells the app we are not
- // connected to it and that means we can't refresh the cache. In this case, schedule a
- // refresh later.
+ // TODO[IL]: Can the following be moved to InputLogic#startInput?
final boolean canReachInputConnection;
- if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(editorInfo.initialSelStart,
+ if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess(
+ editorInfo.initialSelStart, editorInfo.initialSelEnd,
false /* shouldFinishComposition */)) {
+ // Sometimes, while rotating, for some reason the framework tells the app we are not
+ // connected to it and that means we can't refresh the cache. In this case, schedule a
+ // refresh later.
// We try resetting the caches up to 5 times before giving up.
mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */);
// mLastSelection{Start,End} are reset later in this method, don't need to do it here
canReachInputConnection = false;
} else {
- if (isDifferentTextField) {
- mHandler.postResumeSuggestions();
- }
+ // When rotating, initialSelStart and initialSelEnd sometimes are lying. Make a best
+ // effort to work around this bug.
+ mInputLogic.mConnection.tryFixLyingCursorPosition();
+ mHandler.postResumeSuggestions();
canReachInputConnection = true;
}
+ if (isDifferentTextField ||
+ !currentSettingsValues.hasSameOrientation(getResources().getConfiguration())) {
+ loadSettings();
+ }
if (isDifferentTextField) {
mainKeyboardView.closing();
- loadSettings();
currentSettingsValues = mSettings.getCurrent();
- if (suggest != null && currentSettingsValues.mCorrectionEnabled) {
+ if (currentSettingsValues.mCorrectionEnabled) {
suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
}
- switcher.loadKeyboard(editorInfo, currentSettingsValues);
+ switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(),
+ getCurrentRecapitalizeState());
if (!canReachInputConnection) {
// If we can't reach the input connection, we will call loadKeyboard again later,
// so we need to save its state now. The call will be done in #retryResetCaches.
@@ -937,26 +845,23 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else if (restarting) {
// TODO: Come up with a more comprehensive way to reset the keyboard layout when
// a keyboard layout set doesn't get reloaded in this method.
- switcher.resetKeyboardStateToAlphabet();
+ switcher.resetKeyboardStateToAlphabet(getCurrentAutoCapsState(),
+ getCurrentRecapitalizeState());
// In apps like Talk, we come here when the text is sent and the field gets emptied and
// we need to re-evaluate the shift state, but not the whole layout which would be
// disruptive.
// Space state must be updated before calling updateShiftState
- switcher.updateShiftState();
+ switcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
+ getCurrentRecapitalizeState());
}
- setSuggestionStripShownInternal(
- isSuggestionsStripVisible(), /* needsInputViewShown */ false);
-
- mLastSelectionStart = editorInfo.initialSelStart;
- mLastSelectionEnd = editorInfo.initialSelEnd;
- // 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.
- tryFixLyingCursorPosition();
+ // This will set the punctuation suggestions if next word suggestion is off;
+ // otherwise it will clear the suggestion strip.
+ setNeutralSuggestionStrip();
mHandler.cancelUpdateSuggestionStrip();
- mHandler.cancelDoubleSpacePeriodTimer();
- mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable);
+ mainKeyboardView.setMainDictionaryAvailability(
+ suggest.mDictionaryFacilitator.hasInitializedMainDictionary());
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setSlidingKeyInputPreviewEnabled(
@@ -966,76 +871,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
currentSettingsValues.mGestureTrailEnabled,
currentSettingsValues.mGestureFloatingPreviewTextEnabled);
- initPersonalizationDebugSettings(currentSettingsValues);
-
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
}
- /**
- * 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
- * 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
- * than it really is.
- */
- private void tryFixLyingCursorPosition() {
- final CharSequence textBeforeCursor =
- mConnection.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
- if (null == textBeforeCursor) {
- mLastSelectionStart = mLastSelectionEnd = NOT_A_CURSOR_POSITION;
- } else {
- final int textLength = textBeforeCursor.length();
- if (textLength > mLastSelectionStart
- || (textLength < Constants.EDITOR_CONTENTS_CACHE_SIZE
- && mLastSelectionStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
- // It should not be possible to have only one of those variables be
- // NOT_A_CURSOR_POSITION, so if they are equal, either the selection is zero-sized
- // (simple cursor, no selection) or there is no cursor/we don't know its pos
- final boolean wasEqual = mLastSelectionStart == mLastSelectionEnd;
- mLastSelectionStart = textLength;
- // We can't figure out the value of mLastSelectionEnd :(
- // But at least if it's smaller than mLastSelectionStart something is wrong,
- // and if they used to be equal we also don't want to make it look like there is a
- // selection.
- if (wasEqual || mLastSelectionStart > mLastSelectionEnd) {
- mLastSelectionEnd = mLastSelectionStart;
- }
- }
- }
- }
-
- // Initialization of personalization debug settings. This must be called inside
- // onStartInputView.
- private void initPersonalizationDebugSettings(SettingsValues currentSettingsValues) {
- if (mUseOnlyPersonalizationDictionaryForDebug
- != currentSettingsValues.mUseOnlyPersonalizationDictionaryForDebug) {
- // Only for debug
- initSuggest();
- mUseOnlyPersonalizationDictionaryForDebug =
- currentSettingsValues.mUseOnlyPersonalizationDictionaryForDebug;
- }
-
- if (mBoostPersonalizationDictionaryForDebug !=
- currentSettingsValues.mBoostPersonalizationDictionaryForDebug) {
- // Only for debug
- mBoostPersonalizationDictionaryForDebug =
- currentSettingsValues.mBoostPersonalizationDictionaryForDebug;
- if (mBoostPersonalizationDictionaryForDebug) {
- UserHistoryForgettingCurveUtils.boostMaxFreqForDebug();
- } else {
- UserHistoryForgettingCurveUtils.resetMaxFreqForDebug();
- }
- }
- }
-
- // Callback for the TargetPackageInfoGetterTask
- @Override
- public void onTargetPackageInfoKnown(final PackageInfo info) {
- mAppWorkAroundsUtils.setPackageInfo(info);
- }
-
@Override
public void onWindowHidden() {
super.onWindowHidden();
@@ -1062,12 +900,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Remove pending messages related to update suggestions
mHandler.cancelUpdateSuggestionStrip();
// Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
- if (mWordComposer.isComposingWord()) mConnection.finishComposingText();
- resetComposingState(true /* alsoResetLastComposedWord */);
+ mInputLogic.finishInput();
// Notify ResearchLogger
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, mLastSelectionStart,
- mLastSelectionEnd, getCurrentInputConnection());
+ ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput);
}
}
@@ -1078,104 +914,38 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
composingSpanStart, composingSpanEnd);
if (DEBUG) {
- Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
- + ", ose=" + oldSelEnd
- + ", lss=" + mLastSelectionStart
- + ", lse=" + mLastSelectionEnd
- + ", nss=" + newSelStart
- + ", nse=" + newSelEnd
- + ", cs=" + composingSpanStart
- + ", ce=" + composingSpanEnd);
+ Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd
+ + ", nss=" + newSelStart + ", nse=" + newSelEnd
+ + ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final boolean expectingUpdateSelectionFromLogger =
- ResearchLogger.getAndClearLatinIMEExpectingUpdateSelection();
- ResearchLogger.latinIME_onUpdateSelection(mLastSelectionStart, mLastSelectionEnd,
+ ResearchLogger.latinIME_onUpdateSelection(oldSelStart, oldSelEnd,
oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart,
- composingSpanEnd, mExpectingUpdateSelection,
- expectingUpdateSelectionFromLogger, mConnection);
- if (expectingUpdateSelectionFromLogger) {
- // TODO: Investigate. Quitting now sounds wrong - we won't do the resetting work
- return;
- }
+ composingSpanEnd, mInputLogic.mConnection);
}
- final boolean selectionChanged = mLastSelectionStart != newSelStart
- || mLastSelectionEnd != newSelEnd;
-
- // if composingSpanStart and composingSpanEnd are -1, it means there is no composing
- // span in the view - we can use that to narrow down whether the cursor was moved
- // by us or not. If we are composing a word but there is no composing span, then
- // we know for sure the cursor moved while we were composing and we should reset
- // the state. TODO: rescind this policy: the framework never removes the composing
- // span on its own accord while editing. This test is useless.
- final boolean noComposingSpan = composingSpanStart == -1 && composingSpanEnd == -1;
-
// If the keyboard is not visible, we don't need to do all the housekeeping work, as it
// will be reset when the keyboard shows up anyway.
// TODO: revisit this when LatinIME supports hardware keyboards.
// NOTE: the test harness subclasses LatinIME and overrides isInputViewShown().
// TODO: find a better way to simulate actual execution.
- if (isInputViewShown() && !mExpectingUpdateSelection
- && !mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart)) {
- // TAKE CARE: there is a race condition when we enter this test even when the user
- // did not explicitly move the cursor. This happens when typing fast, where two keys
- // turn this flag on in succession and both onUpdateSelection() calls arrive after
- // the second one - the first call successfully avoids this test, but the second one
- // enters. For the moment we rely on noComposingSpan to further reduce the impact.
-
- // TODO: the following is probably better done in resetEntireInputState().
- // it should only happen when the cursor moved, and the very purpose of the
- // test below is to narrow down whether this happened or not. Likewise with
- // the call to updateShiftState.
- // We set this to NONE because after a cursor move, we don't want the space
- // state-related special processing to kick in.
- mSpaceState = SPACE_STATE_NONE;
-
- // TODO: is it still necessary to test for composingSpan related stuff?
- final boolean selectionChangedOrSafeToReset = selectionChanged
- || (!mWordComposer.isComposingWord()) || noComposingSpan;
- final boolean hasOrHadSelection = (oldSelStart != oldSelEnd
- || newSelStart != newSelEnd);
- final int moveAmount = newSelStart - oldSelStart;
- if (selectionChangedOrSafeToReset && (hasOrHadSelection
- || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
- // If we are composing a word and moving the cursor, we would want to set a
- // suggestion span for recorrection to work correctly. Unfortunately, that
- // would involve the keyboard committing some new text, which would move the
- // cursor back to where it was. Latin IME could then fix the position of the cursor
- // again, but the asynchronous nature of the calls results in this wreaking havoc
- // with selection on double tap and the like.
- // Another option would be to send suggestions each time we set the composing
- // text, but that is probably too expensive to do, so we decided to leave things
- // as is.
- resetEntireInputState(newSelStart);
- } else {
- // resetEntireInputState calls resetCachesUponCursorMove, but with the second
- // argument as true. But in all cases where we don't reset the entire input state,
- // we still want to tell the rich input connection about the new cursor position so
- // that it can update its caches.
- mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart,
- false /* shouldFinishComposition */);
- }
-
- // We moved the cursor. If we are touching a word, we need to resume suggestion,
- // unless suggestions are off.
- if (isSuggestionsStripVisible()) {
- mHandler.postResumeSuggestions();
- }
- // Reset the last recapitalization.
- mRecapitalizeStatus.deactivate();
- mKeyboardSwitcher.updateShiftState();
+ if (isInputViewShown() &&
+ mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd)) {
+ mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
+ getCurrentRecapitalizeState());
}
- mExpectingUpdateSelection = false;
- // Make a note of the cursor position
- mLastSelectionStart = newSelStart;
- mLastSelectionEnd = newSelEnd;
mSubtypeState.currentSubtypeUsed();
}
+ @Override
+ public void onUpdateCursor(Rect rect) {
+ if (DEBUG) {
+ Log.i(TAG, "onUpdateCursor:" + rect.toShortString());
+ }
+ super.onUpdateCursor(rect);
+ }
+
/**
* This is called when the user has clicked on the extracted text view,
* when running in fullscreen mode. The default implementation hides
@@ -1186,7 +956,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
*/
@Override
public void onExtractedTextClicked() {
- if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) return;
+ if (mSettings.getCurrent().isSuggestionsRequested()) {
+ return;
+ }
super.onExtractedTextClicked();
}
@@ -1202,7 +974,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
*/
@Override
public void onExtractedCursorMovement(final int dx, final int dy) {
- if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) return;
+ if (mSettings.getCurrent().isSuggestionsRequested()) {
+ return;
+ }
super.onExtractedCursorMovement(dx, dy);
}
@@ -1217,7 +991,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
if (TRACE) Debug.stopMethodTracing();
- if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
+ if (isShowingOptionDialog()) {
mOptionsDialog.dismiss();
mOptionsDialog = null;
}
@@ -1234,58 +1008,30 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
}
- if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) return;
+ if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) {
+ return;
+ }
if (applicationSpecifiedCompletions == null) {
- clearSuggestionStrip();
+ setNeutralSuggestionStrip();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onDisplayCompletions(null);
}
return;
}
- mApplicationSpecifiedCompletions =
- CompletionInfoUtils.removeNulls(applicationSpecifiedCompletions);
final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
SuggestedWords.getFromApplicationSpecifiedCompletions(
applicationSpecifiedCompletions);
- final SuggestedWords suggestedWords = new SuggestedWords(
- applicationSuggestedWords,
- false /* typedWordValid */,
- false /* hasAutoCorrectionCandidate */,
- false /* isPunctuationSuggestions */,
- false /* isObsoleteSuggestions */,
- false /* isPrediction */);
- // When in fullscreen mode, show completions generated by the application
- final boolean isAutoCorrection = false;
- setSuggestedWords(suggestedWords, isAutoCorrection);
- setAutoCorrectionIndicator(isAutoCorrection);
- setSuggestionStripShown(true);
+ final SuggestedWords suggestedWords = new SuggestedWords(applicationSuggestedWords,
+ null /* rawSuggestions */, false /* typedWordValid */, false /* willAutoCorrect */,
+ false /* isObsoleteSuggestions */, false /* isPrediction */);
+ // When in fullscreen mode, show completions generated by the application forcibly
+ setSuggestedWords(suggestedWords, true /* isSuggestionStripVisible */);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
}
}
- private void setSuggestionStripShownInternal(final boolean shown,
- final boolean needsInputViewShown) {
- // TODO: Modify this if we support suggestions with hard keyboard
- if (onEvaluateInputViewShown() && mSuggestionStripView != null) {
- final boolean inputViewShown = mKeyboardSwitcher.isShowingMainKeyboardOrEmojiPalettes();
- final boolean shouldShowSuggestions = shown
- && (needsInputViewShown ? inputViewShown : true);
- if (isFullscreenMode()) {
- mSuggestionStripView.setVisibility(
- shouldShowSuggestions ? View.VISIBLE : View.GONE);
- } else {
- mSuggestionStripView.setVisibility(
- shouldShowSuggestions ? View.VISIBLE : View.INVISIBLE);
- }
- }
- }
-
- private void setSuggestionStripShown(final boolean shown) {
- setSuggestionStripShownInternal(shown, /* needsInputViewShown */true);
- }
-
private int getAdjustedBackingViewHeight() {
final int currentHeight = mKeyPreviewBackingView.getHeight();
if (currentHeight > 0) {
@@ -1317,7 +1063,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void onComputeInsets(final InputMethodService.Insets outInsets) {
super.onComputeInsets(outInsets);
final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView();
- if (visibleKeyboardView == null || mSuggestionStripView == null) {
+ if (visibleKeyboardView == null || !hasSuggestionStripView()) {
return;
}
final int adjustedBackingHeight = getAdjustedBackingViewHeight();
@@ -1353,9 +1099,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public boolean onEvaluateFullscreenMode() {
- // Reread resource value here, because this method is called by framework anytime as needed.
- final boolean isFullscreenModeAllowed =
- Settings.readUseFullscreenMode(getResources());
+ // Reread resource value here, because this method is called by the framework as needed.
+ final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources());
if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) {
// TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI
// implies NO_FULLSCREEN. However, the framework mistakenly does. i.e. NO_EXTRACT_UI
@@ -1378,138 +1123,30 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mKeyPreviewBackingView.setVisibility(isFullscreenMode() ? View.GONE : View.VISIBLE);
}
- // This will reset the whole input state to the starting state. It will clear
- // the composing word, reset the last composed word, tell the inputconnection about it.
- private void resetEntireInputState(final int newCursorPosition) {
- final boolean shouldFinishComposition = mWordComposer.isComposingWord();
- resetComposingState(true /* alsoResetLastComposedWord */);
- final SettingsValues settingsValues = mSettings.getCurrent();
- if (settingsValues.mBigramPredictionEnabled) {
- clearSuggestionStrip();
- } else {
- setSuggestedWords(settingsValues.mSuggestPuncList, false);
- }
- mConnection.resetCachesUponCursorMoveAndReturnSuccess(newCursorPosition,
- shouldFinishComposition);
+ private int getCurrentAutoCapsState() {
+ return mInputLogic.getCurrentAutoCapsState(mSettings.getCurrent());
}
- private void resetComposingState(final boolean alsoResetLastComposedWord) {
- mWordComposer.reset();
- if (alsoResetLastComposedWord)
- mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+ private int getCurrentRecapitalizeState() {
+ return mInputLogic.getCurrentRecapitalizeState();
}
- private void commitTyped(final String separatorString) {
- if (!mWordComposer.isComposingWord()) return;
- final String typedWord = mWordComposer.getTypedWord();
- if (typedWord.length() > 0) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
- }
- commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD,
- separatorString);
- }
+ public Locale getCurrentSubtypeLocale() {
+ return mSubtypeSwitcher.getCurrentSubtypeLocale();
}
- // Called from the KeyboardSwitcher which needs to know auto caps state to display
- // the right layout.
- public int getCurrentAutoCapsState() {
- final SettingsValues currentSettingsValues = mSettings.getCurrent();
- if (!currentSettingsValues.mAutoCap) return Constants.TextUtils.CAP_MODE_OFF;
-
- final EditorInfo ei = getCurrentInputEditorInfo();
- if (ei == null) return Constants.TextUtils.CAP_MODE_OFF;
- final int inputType = ei.inputType;
- // Warning: this depends on mSpaceState, which may not be the most current value. If
- // mSpaceState gets updated later, whoever called this may need to be told about it.
- return mConnection.getCursorCapsMode(inputType, currentSettingsValues,
- SPACE_STATE_PHANTOM == mSpaceState);
- }
-
- public int getCurrentRecapitalizeState() {
- if (!mRecapitalizeStatus.isActive()
- || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
- // Not recapitalizing at the moment
- return RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
- }
- return mRecapitalizeStatus.getCurrentMode();
- }
-
- // Factor in auto-caps and manual caps and compute the current caps mode.
- private int getActualCapsMode() {
- final int keyboardShiftMode = mKeyboardSwitcher.getKeyboardShiftMode();
- if (keyboardShiftMode != WordComposer.CAPS_MODE_AUTO_SHIFTED) return keyboardShiftMode;
- final int auto = getCurrentAutoCapsState();
- if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
- return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
- }
- if (0 != auto) return WordComposer.CAPS_MODE_AUTO_SHIFTED;
- return WordComposer.CAPS_MODE_OFF;
- }
-
- private void swapSwapperAndSpace() {
- final CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
- // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
- if (lastTwo != null && lastTwo.length() == 2
- && lastTwo.charAt(0) == Constants.CODE_SPACE) {
- mConnection.deleteSurroundingText(2, 0);
- final String text = lastTwo.charAt(1) + " ";
- mConnection.commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text);
- }
- mKeyboardSwitcher.updateShiftState();
- }
- }
-
- private boolean maybeDoubleSpacePeriod() {
- final SettingsValues currentSettingsValues = mSettings.getCurrent();
- if (!currentSettingsValues.mUseDoubleSpacePeriod) return false;
- if (!mHandler.isAcceptingDoubleSpacePeriod()) return false;
- // We only do this when we see two spaces and an accepted code point before the cursor.
- // The code point may be a surrogate pair but the two spaces may not, so we need 4 chars.
- final CharSequence lastThree = mConnection.getTextBeforeCursor(4, 0);
- if (null == lastThree) return false;
- final int length = lastThree.length();
- if (length < 3) return false;
- if (lastThree.charAt(length - 1) != Constants.CODE_SPACE) return false;
- if (lastThree.charAt(length - 2) != Constants.CODE_SPACE) return false;
- // We know there are spaces in pos -1 and -2, and we have at least three chars.
- // If we have only three chars, isSurrogatePairs can't return true as charAt(1) is a space,
- // so this is fine.
- final int firstCodePoint =
- Character.isSurrogatePair(lastThree.charAt(0), lastThree.charAt(1)) ?
- Character.codePointAt(lastThree, 0) : lastThree.charAt(length - 3);
- if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) {
- mHandler.cancelDoubleSpacePeriodTimer();
- mConnection.deleteSurroundingText(2, 0);
- final String textToInsert = new String(
- new int[] { currentSettingsValues.mSentenceSeparator, Constants.CODE_SPACE },
- 0, 2);
- mConnection.commitText(textToInsert, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
- false /* isBatchMode */);
- }
- mKeyboardSwitcher.updateShiftState();
- return true;
+ /**
+ * @param codePoints code points to get coordinates for.
+ * @return x,y coordinates for this keyboard, as a flattened array.
+ */
+ public int[] getCoordinatesForCurrentKeyboard(final int[] codePoints) {
+ final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
+ if (null == keyboard) {
+ return CoordinateUtils.newCoordinateArray(codePoints.length,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+ } else {
+ return keyboard.getCoordinates(codePoints);
}
- return false;
- }
-
- private static boolean canBeFollowedByDoubleSpacePeriod(final int codePoint) {
- // TODO: Check again whether there really ain't a better way to check this.
- // TODO: This should probably be language-dependant...
- return Character.isLetterOrDigit(codePoint)
- || codePoint == Constants.CODE_SINGLE_QUOTE
- || codePoint == Constants.CODE_DOUBLE_QUOTE
- || codePoint == Constants.CODE_CLOSING_PARENTHESIS
- || codePoint == Constants.CODE_CLOSING_SQUARE_BRACKET
- || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET
- || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET
- || codePoint == Constants.CODE_PLUS
- || codePoint == Constants.CODE_PERCENT
- || Character.getType(codePoint) == Character.OTHER_SYMBOL;
}
// Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is
@@ -1521,16 +1158,37 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
final String wordToEdit;
- if (CapsModeUtils.isAutoCapsMode(mLastComposedWord.mCapitalizedMode)) {
- wordToEdit = word.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
+ if (CapsModeUtils.isAutoCapsMode(mInputLogic.mLastComposedWord.mCapitalizedMode)) {
+ wordToEdit = word.toLowerCase(getCurrentSubtypeLocale());
} else {
wordToEdit = word;
}
- mUserDictionary.addWordToUserDictionary(wordToEdit);
+ mInputLogic.mSuggest.mDictionaryFacilitator.addWordToUserDictionary(wordToEdit);
+ }
+
+ // Callback for the {@link SuggestionStripView}, to call when the important notice strip is
+ // pressed.
+ @Override
+ public void showImportantNoticeContents() {
+ showOptionDialog(new ImportantNoticeDialog(this /* context */, this /* listener */));
+ }
+
+ // Implement {@link ImportantNoticeDialog.ImportantNoticeDialogListener}
+ @Override
+ public void onClickSettingsOfImportantNoticeDialog(final int nextVersion) {
+ launchSettings();
}
- private void onSettingsKeyPressed() {
- if (isShowingOptionDialog()) return;
+ // Implement {@link ImportantNoticeDialog.ImportantNoticeDialogListener}
+ @Override
+ public void onUserAcknowledgmentOfImportantNoticeDialog(final int nextVersion) {
+ setNeutralSuggestionStrip();
+ }
+
+ public void displaySettingsDialog() {
+ if (isShowingOptionDialog()) {
+ return;
+ }
showSubtypeSelectorAndSettings();
}
@@ -1552,12 +1210,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return mOptionsDialog != null && mOptionsDialog.isShowing();
}
- private void performEditorAction(final int actionId) {
- mConnection.performEditorAction(actionId);
- }
-
// TODO: Revise the language switch key behavior to make it much smarter and more reasonable.
- private void handleLanguageSwitchKey() {
+ public void switchToNextSubtype() {
final IBinder token = getWindow().getWindow().getAttributes().token;
if (mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList) {
mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */);
@@ -1566,394 +1220,93 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeState.switchSubtype(token, mRichImm);
}
- private void sendDownUpKeyEvent(final int code) {
- final long eventTime = SystemClock.uptimeMillis();
- mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
- KeyEvent.ACTION_DOWN, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
- mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
- KeyEvent.ACTION_UP, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
- }
-
- private void sendKeyCodePoint(final int code) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_sendKeyCodePoint(code);
- }
- // TODO: Remove this special handling of digit letters.
- // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
- if (code >= '0' && code <= '9') {
- sendDownUpKeyEvent(code - '0' + KeyEvent.KEYCODE_0);
- return;
- }
-
- if (Constants.CODE_ENTER == code && mAppWorkAroundsUtils.isBeforeJellyBean()) {
- // 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.
- sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER);
- } else {
- mConnection.commitText(StringUtils.newSingleCodePointString(code), 1);
- }
- }
-
// Implementation of {@link KeyboardActionListener}.
@Override
- public void onCodeInput(final int primaryCode, final int x, final int y) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
- }
- final long when = SystemClock.uptimeMillis();
- if (primaryCode != Constants.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
- mDeleteCount = 0;
- }
- mLastKeyTime = when;
- mConnection.beginBatchEdit();
- final KeyboardSwitcher switcher = mKeyboardSwitcher;
- // The space state depends only on the last character pressed and its own previous
- // state. Here, we revert the space state to neutral if the key is actually modifying
- // the input contents (any non-shift key), which is what we should do for
- // all inputs that do not result in a special state. Each character handling is then
- // free to override the state as they see fit.
- final int spaceState = mSpaceState;
- if (!mWordComposer.isComposingWord()) mIsAutoCorrectionIndicatorOn = false;
-
- // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state.
- if (primaryCode != Constants.CODE_SPACE) {
- mHandler.cancelDoubleSpacePeriodTimer();
- }
-
- boolean didAutoCorrect = false;
- switch (primaryCode) {
- case Constants.CODE_DELETE:
- mSpaceState = SPACE_STATE_NONE;
- handleBackspace(spaceState);
- LatinImeLogger.logOnDelete(x, y);
- break;
- case Constants.CODE_SHIFT:
- // Note: Calling back to the keyboard on Shift key is handled in
- // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
- final Keyboard currentKeyboard = switcher.getKeyboard();
+ public void onCodeInput(final int codePoint, final int x, final int y,
+ final boolean isKeyRepeat) {
+ 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.
+ 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()) {
- // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
- // alphabetic shift and shift while in symbol layout.
- handleRecapitalize();
- }
- break;
- case Constants.CODE_CAPSLOCK:
- // Note: Changing keyboard to shift lock state is handled in
- // {@link KeyboardSwitcher#onCodeInput(int)}.
- break;
- case Constants.CODE_SWITCH_ALPHA_SYMBOL:
- // Note: Calling back to the keyboard on symbol key is handled in
- // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
- break;
- case Constants.CODE_SETTINGS:
- onSettingsKeyPressed();
- break;
- case Constants.CODE_SHORTCUT:
- mSubtypeSwitcher.switchToShortcutIME(this);
- break;
- case Constants.CODE_ACTION_NEXT:
- performEditorAction(EditorInfo.IME_ACTION_NEXT);
- break;
- case Constants.CODE_ACTION_PREVIOUS:
- performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
- break;
- case Constants.CODE_LANGUAGE_SWITCH:
- handleLanguageSwitchKey();
- break;
- case Constants.CODE_EMOJI:
- // Note: Switching emoji keyboard is being handled in
- // {@link KeyboardState#onCodeInput(int,int)}.
- break;
- case Constants.CODE_ENTER:
- final EditorInfo editorInfo = getCurrentInputEditorInfo();
- final int imeOptionsActionId =
- InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo);
- if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) {
- // Either we have an actionLabel and we should performEditorAction with actionId
- // regardless of its value.
- performEditorAction(editorInfo.actionId);
- } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) {
- // We didn't have an actionLabel, but we had another action to execute.
- // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast,
- // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it
- // means there should be an action and the app didn't bother to set a specific
- // code for it - presumably it only handles one. It does not have to be treated
- // in any specific way: anything that is not IME_ACTION_NONE should be sent to
- // performEditorAction.
- performEditorAction(imeOptionsActionId);
+ codeToSend = codePoint;
} else {
- // No action label, and the action from imeOptions is NONE: this is a regular
- // enter key that should input a carriage return.
- didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
+ codeToSend = Constants.CODE_SYMBOL_SHIFT;
}
- break;
- case Constants.CODE_SHIFT_ENTER:
- didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
- break;
- default:
- didAutoCorrect = handleNonSpecialCharacter(primaryCode, x, y, spaceState);
- break;
- }
- switcher.onCodeInput(primaryCode);
- // Reset after any single keystroke, except shift, capslock, and symbol-shift
- if (!didAutoCorrect && primaryCode != Constants.CODE_SHIFT
- && primaryCode != Constants.CODE_CAPSLOCK
- && primaryCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
- mLastComposedWord.deactivate();
- if (Constants.CODE_DELETE != primaryCode) {
- mEnteredText = null;
+ } else {
+ codeToSend = codePoint;
}
- mConnection.endBatchEdit();
- }
-
- private boolean handleNonSpecialCharacter(final int primaryCode, final int x, final int y,
- final int spaceState) {
- mSpaceState = SPACE_STATE_NONE;
- final boolean didAutoCorrect;
- final SettingsValues settingsValues = mSettings.getCurrent();
- if (settingsValues.isWordSeparator(primaryCode)
- || Character.getType(primaryCode) == Character.OTHER_SYMBOL) {
- didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
+ if (Constants.CODE_SHORTCUT == codePoint) {
+ 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(), mHandler);
+ updateStateAfterInputTransaction(completeInputTransaction);
+ mKeyboardSwitcher.onCodeInput(codePoint, 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,
+ final int keyY, final boolean isKeyRepeat) {
+ final int keyCode;
+ final int codePoint;
+ if (keyCodeOrCodePoint <= 0) {
+ keyCode = keyCodeOrCodePoint;
+ codePoint = Event.NOT_A_CODE_POINT;
} else {
- didAutoCorrect = false;
- if (SPACE_STATE_PHANTOM == spaceState) {
- if (settingsValues.mIsInternal) {
- if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
- LatinImeLoggerUtils.onAutoCorrection(
- "", mWordComposer.getTypedWord(), " ", mWordComposer);
- }
- }
- if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
- // If we are in the middle of a recorrection, we need to commit the recorrection
- // first so that we can insert the character at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
- } else {
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
- }
- }
- final int keyX, keyY;
- final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
- if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) {
- keyX = x;
- keyY = y;
- } else {
- keyX = Constants.NOT_A_COORDINATE;
- keyY = Constants.NOT_A_COORDINATE;
- }
- handleCharacter(primaryCode, keyX, keyY, spaceState);
+ keyCode = Event.NOT_A_KEY_CODE;
+ codePoint = keyCodeOrCodePoint;
}
- mExpectingUpdateSelection = true;
- return didAutoCorrect;
+ return Event.createSoftwareKeypressEvent(codePoint, keyCode, keyX, keyY, isKeyRepeat);
}
// Called from PointerTracker through the KeyboardActionListener interface
@Override
public void onTextInput(final String rawText) {
- mConnection.beginBatchEdit();
- if (mWordComposer.isComposingWord()) {
- commitCurrentAutoCorrection(rawText);
- } else {
- resetComposingState(true /* alsoResetLastComposedWord */);
- }
- mHandler.postUpdateSuggestionStrip();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS
- && ResearchLogger.RESEARCH_KEY_OUTPUT_TEXT.equals(rawText)) {
- ResearchLogger.getInstance().onResearchKeySelected(this);
- return;
- }
- final String text = specificTldProcessingOnTextInput(rawText);
- if (SPACE_STATE_PHANTOM == mSpaceState) {
- promotePhantomSpace();
- }
- mConnection.commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onTextInput(text, false /* isBatchMode */);
- }
- mConnection.endBatchEdit();
- // Space state must be updated before calling updateShiftState
- mSpaceState = SPACE_STATE_NONE;
- mKeyboardSwitcher.updateShiftState();
- mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT);
- mEnteredText = text;
+ // TODO: have the keyboard pass the correct key code when we need it.
+ final Event event = Event.createSoftwareTextEvent(rawText, Event.NOT_A_KEY_CODE);
+ mInputLogic.onTextInput(mSettings.getCurrent(), event, mHandler);
+ mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
+ getCurrentRecapitalizeState());
+ mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT, getCurrentAutoCapsState(),
+ getCurrentRecapitalizeState());
}
@Override
public void onStartBatchInput() {
- mInputUpdater.onStartBatchInput();
- mHandler.cancelUpdateSuggestionStrip();
- mConnection.beginBatchEdit();
- final SettingsValues settingsValues = mSettings.getCurrent();
- if (mWordComposer.isComposingWord()) {
- if (settingsValues.mIsInternal) {
- if (mWordComposer.isBatchMode()) {
- LatinImeLoggerUtils.onAutoCorrection(
- "", mWordComposer.getTypedWord(), " ", mWordComposer);
- }
- }
- final int wordComposerSize = mWordComposer.size();
- // Since isComposingWord() is true, the size is at least 1.
- if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
- // If we are in the middle of a recorrection, we need to commit the recorrection
- // first so that we can insert the batch input at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
- } else if (wordComposerSize <= 1) {
- // We auto-correct the previous (typed, not gestured) string iff it's one character
- // long. The reason for this is, even in the middle of gesture typing, you'll still
- // tap one-letter words and you want them auto-corrected (typically, "i" in English
- // should become "I"). However for any longer word, we assume that the reason for
- // tapping probably is that the word you intend to type is not in the dictionary,
- // so we do not attempt to correct, on the assumption that if that was a dictionary
- // word, the user would probably have gestured instead.
- commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
- } else {
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
- }
- mExpectingUpdateSelection = true;
- }
- final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
- if (Character.isLetterOrDigit(codePointBeforeCursor)
- || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
- mSpaceState = SPACE_STATE_PHANTOM;
- }
- mConnection.endBatchEdit();
- mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
+ mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler);
}
- static final class InputUpdater implements Handler.Callback {
- private final Handler mHandler;
- private final LatinIME mLatinIme;
- private final Object mLock = new Object();
- private boolean mInBatchInput; // synchronized using {@link #mLock}.
-
- InputUpdater(final LatinIME latinIme) {
- final HandlerThread handlerThread = new HandlerThread(
- InputUpdater.class.getSimpleName());
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper(), this);
- mLatinIme = latinIme;
- }
-
- private static final int MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 1;
- private static final int MSG_GET_SUGGESTED_WORDS = 2;
-
- @Override
- public boolean handleMessage(final Message msg) {
- // TODO: straighten message passing - we don't need two kinds of messages calling
- // each other.
- switch (msg.what) {
- case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
- updateBatchInput((InputPointers)msg.obj, msg.arg2 /* sequenceNumber */);
- break;
- case MSG_GET_SUGGESTED_WORDS:
- mLatinIme.getSuggestedWords(msg.arg1 /* sessionId */,
- msg.arg2 /* sequenceNumber */, (OnGetSuggestedWordsCallback) msg.obj);
- break;
- }
- return true;
- }
-
- // Run in the UI thread.
- public void onStartBatchInput() {
- synchronized (mLock) {
- mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
- mInBatchInput = true;
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
- }
- }
-
- // Run in the Handler thread.
- private void updateBatchInput(final InputPointers batchPointers, final int sequenceNumber) {
- synchronized (mLock) {
- if (!mInBatchInput) {
- // Batch input has ended or canceled while the message was being delivered.
- return;
- }
-
- getSuggestedWordsGestureLocked(batchPointers, sequenceNumber,
- new OnGetSuggestedWordsCallback() {
- @Override
- public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- suggestedWords, false /* dismissGestureFloatingPreviewText */);
- }
- });
- }
- }
-
- // Run in the UI thread.
- public void onUpdateBatchInput(final InputPointers batchPointers,
- final int sequenceNumber) {
- if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) {
- return;
- }
- mHandler.obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, 0 /* arg1 */,
- sequenceNumber /* arg2 */, batchPointers /* obj */).sendToTarget();
- }
-
- public void onCancelBatchInput() {
- synchronized (mLock) {
- mInBatchInput = false;
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
- }
- }
-
- // Run in the UI thread.
- public void onEndBatchInput(final InputPointers batchPointers) {
- synchronized(mLock) {
- getSuggestedWordsGestureLocked(batchPointers, SuggestedWords.NOT_A_SEQUENCE_NUMBER,
- new OnGetSuggestedWordsCallback() {
- @Override
- public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
- mInBatchInput = false;
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWords,
- true /* dismissGestureFloatingPreviewText */);
- mLatinIme.mHandler.onEndBatchInput(suggestedWords);
- }
- });
- }
- }
-
- // {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to
- // be synchronized.
- private void getSuggestedWordsGestureLocked(final InputPointers batchPointers,
- final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
- mLatinIme.mWordComposer.setBatchInputPointers(batchPointers);
- mLatinIme.getSuggestedWordsOrOlderSuggestionsAsync(Suggest.SESSION_GESTURE,
- sequenceNumber, new OnGetSuggestedWordsCallback() {
- @Override
- public void onGetSuggestedWords(SuggestedWords suggestedWords) {
- final int suggestionCount = suggestedWords.size();
- if (suggestionCount <= 1) {
- final String mostProbableSuggestion = (suggestionCount == 0) ? null
- : suggestedWords.getWord(0);
- callback.onGetSuggestedWords(
- mLatinIme.getOlderSuggestions(mostProbableSuggestion));
- }
- callback.onGetSuggestedWords(suggestedWords);
- }
- });
- }
+ @Override
+ public void onUpdateBatchInput(final InputPointers batchPointers) {
+ mInputLogic.onUpdateBatchInput(mSettings.getCurrent(), batchPointers, mKeyboardSwitcher);
+ }
- public void getSuggestedWords(final int sessionId, final int sequenceNumber,
- final OnGetSuggestedWordsCallback callback) {
- mHandler.obtainMessage(MSG_GET_SUGGESTED_WORDS, sessionId, sequenceNumber, callback)
- .sendToTarget();
- }
+ @Override
+ public void onEndBatchInput(final InputPointers batchPointers) {
+ mInputLogic.onEndBatchInput(batchPointers);
+ }
- void quitLooper() {
- mHandler.removeMessages(MSG_GET_SUGGESTED_WORDS);
- mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
- mHandler.getLooper().quit();
- }
+ @Override
+ public void onCancelBatchInput() {
+ mInputLogic.onCancelBatchInput(mHandler);
}
- // This method must run in UI Thread.
+ // This method must run on the UI Thread.
private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
final boolean dismissGestureFloatingPreviewText) {
showSuggestionStrip(suggestedWords);
@@ -1964,107 +1317,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- /* The sequence number member is only used in onUpdateBatchInput. It is increased each time
- * auto-commit happens. The reason we need this is, when auto-commit happens we trim the
- * input pointers that are held in a singleton, and to know how much to trim we rely on the
- * results of the suggestion process that is held in mSuggestedWords.
- * However, the suggestion process is asynchronous, and sometimes we may enter the
- * onUpdateBatchInput method twice without having recomputed suggestions yet, or having
- * received new suggestions generated from not-yet-trimmed input pointers. In this case, the
- * mIndexOfTouchPointOfSecondWords member will be out of date, and we must not use it lest we
- * remove an unrelated number of pointers (possibly even more than are left in the input
- * pointers, leading to a crash).
- * To avoid that, we increase the sequence number each time we auto-commit and trim the
- * input pointers, and we do not use any suggested words that have been generated with an
- * earlier sequence number.
- */
- private int mAutoCommitSequenceNumber = 1;
- @Override
- public void onUpdateBatchInput(final InputPointers batchPointers) {
- if (mSettings.getCurrent().mPhraseGestureEnabled) {
- final SuggestedWordInfo candidate = mSuggestedWords.getAutoCommitCandidate();
- // If these suggested words have been generated with out of date input pointers, then
- // we skip auto-commit (see comments above on the mSequenceNumber member).
- if (null != candidate && mSuggestedWords.mSequenceNumber >= mAutoCommitSequenceNumber) {
- if (candidate.mSourceDict.shouldAutoCommit(candidate)) {
- final String[] commitParts = candidate.mWord.split(" ", 2);
- batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord);
- promotePhantomSpace();
- mConnection.commitText(commitParts[0], 0);
- mSpaceState = SPACE_STATE_PHANTOM;
- mKeyboardSwitcher.updateShiftState();
- mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
- ++mAutoCommitSequenceNumber;
- }
- }
- }
- mInputUpdater.onUpdateBatchInput(batchPointers, mAutoCommitSequenceNumber);
- }
-
- // This method must run in UI Thread.
- public void onEndBatchInputAsyncInternal(final SuggestedWords suggestedWords) {
- final String batchInputText = suggestedWords.isEmpty()
- ? null : suggestedWords.getWord(0);
- if (TextUtils.isEmpty(batchInputText)) {
- return;
- }
- mConnection.beginBatchEdit();
- if (SPACE_STATE_PHANTOM == mSpaceState) {
- promotePhantomSpace();
- }
- if (mSettings.getCurrent().mPhraseGestureEnabled) {
- // Find the last space
- final int indexOfLastSpace = batchInputText.lastIndexOf(Constants.CODE_SPACE) + 1;
- if (0 != indexOfLastSpace) {
- mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), 1);
- showSuggestionStrip(suggestedWords.getSuggestedWordsForLastWordOfPhraseGesture());
- }
- final String lastWord = batchInputText.substring(indexOfLastSpace);
- mWordComposer.setBatchInputWord(lastWord);
- mConnection.setComposingText(lastWord, 1);
- } else {
- mWordComposer.setBatchInputWord(batchInputText);
- mConnection.setComposingText(batchInputText, 1);
- }
- mExpectingUpdateSelection = true;
- mConnection.endBatchEdit();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords);
- }
- // Space state must be updated before calling updateShiftState
- mSpaceState = SPACE_STATE_PHANTOM;
- mKeyboardSwitcher.updateShiftState();
- }
-
- @Override
- public void onEndBatchInput(final InputPointers batchPointers) {
- mInputUpdater.onEndBatchInput(batchPointers);
- }
-
- private String specificTldProcessingOnTextInput(final String text) {
- if (text.length() <= 1 || text.charAt(0) != Constants.CODE_PERIOD
- || !Character.isLetter(text.charAt(1))) {
- // Not a tld: do nothing.
- return text;
- }
- // We have a TLD (or something that looks like this): make sure we don't add
- // a space even if currently in phantom mode.
- mSpaceState = SPACE_STATE_NONE;
- // TODO: use getCodePointBeforeCursor instead to improve performance and simplify the code
- final CharSequence lastOne = mConnection.getTextBeforeCursor(1, 0);
- if (lastOne != null && lastOne.length() == 1
- && lastOne.charAt(0) == Constants.CODE_PERIOD) {
- return text.substring(1);
- } else {
- return text;
- }
- }
-
// Called from PointerTracker through the KeyboardActionListener interface
@Override
public void onFinishSlidingInput() {
// User finished sliding input.
- mKeyboardSwitcher.onFinishSlidingInput();
+ mKeyboardSwitcher.onFinishSlidingInput(getCurrentAutoCapsState(),
+ getCurrentRecapitalizeState());
}
// Called from PointerTracker through the KeyboardActionListener interface
@@ -2074,476 +1332,85 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Nothing to do so far.
}
- @Override
- public void onCancelBatchInput() {
- mInputUpdater.onCancelBatchInput();
- }
-
- private void handleBackspace(final int spaceState) {
- // We revert these in this method if the deletion doesn't happen.
- mDeleteCount++;
- mExpectingUpdateSelection = true;
-
- // In many cases, we may have to put the keyboard in auto-shift state again. However
- // we want to wait a few milliseconds before doing it to avoid the keyboard flashing
- // during key repeat.
- mHandler.postUpdateShiftState();
-
- if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
- // If we are in the middle of a recorrection, we need to commit the recorrection
- // first so that we can remove the character at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
- // When we exit this if-clause, mWordComposer.isComposingWord() will return false.
- }
- if (mWordComposer.isComposingWord()) {
- if (mWordComposer.isBatchMode()) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final String word = mWordComposer.getTypedWord();
- ResearchLogger.latinIME_handleBackspace_batch(word, 1);
- }
- final String rejectedSuggestion = mWordComposer.getTypedWord();
- mWordComposer.reset();
- mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
- } else {
- mWordComposer.deleteLast();
- }
- mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
- mHandler.postUpdateSuggestionStrip();
- if (!mWordComposer.isComposingWord()) {
- // If we just removed the last character, auto-caps mode may have changed so we
- // need to re-evaluate.
- mKeyboardSwitcher.updateShiftState();
- }
- } else {
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (mLastComposedWord.canRevertCommit()) {
- if (currentSettings.mIsInternal) {
- LatinImeLoggerUtils.onAutoCorrectionCancellation();
- }
- revertCommit();
- return;
- }
- if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
- // Cancel multi-character input: remove the text we just entered.
- // This is triggered on backspace after a key that inputs multiple characters,
- // like the smiley key or the .com key.
- mConnection.deleteSurroundingText(mEnteredText.length(), 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText);
- }
- 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
- // reverting any autocorrect at this point. So we can safely return.
- return;
- }
- if (SPACE_STATE_DOUBLE == spaceState) {
- mHandler.cancelDoubleSpacePeriodTimer();
- if (mConnection.revertDoubleSpacePeriod()) {
- // No need to reset mSpaceState, it has already be done (that's why we
- // receive it as a parameter)
- return;
- }
- } else if (SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
- if (mConnection.revertSwapPunctuation()) {
- // Likewise
- return;
- }
- }
-
- // No cancelling of commit/double space/swap: we have a regular backspace.
- // We should backspace one char and restart suggestion if at the end of a word.
- if (mLastSelectionStart != mLastSelectionEnd) {
- // If there is a selection, remove it.
- final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
- mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
- // Reset mLastSelectionEnd to mLastSelectionStart. This is what is supposed to
- // happen, and if it's wrong, the next call to onUpdateSelection will correct it,
- // but we want to set it right away to avoid it being used with the wrong values
- // later (typically, in a subsequent press on backspace).
- mLastSelectionEnd = mLastSelectionStart;
- mConnection.deleteSurroundingText(numCharsDeleted, 0);
- } else {
- // There is no selection, just delete one character.
- if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
- // This should never happen.
- Log.e(TAG, "Backspace when we don't know the selection position");
- }
- if (mAppWorkAroundsUtils.isBeforeJellyBean() ||
- currentSettings.mInputAttributes.isTypeNull()) {
- // There are two 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.
- sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
- if (mDeleteCount > DELETE_ACCELERATE_AT) {
- sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
- }
- } else {
- final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
- if (codePointBeforeCursor == Constants.NOT_A_CODE) {
- // Nothing to delete before the cursor. We have to revert the deletion
- // states that were updated at the beginning of this method.
- mDeleteCount--;
- mExpectingUpdateSelection = false;
- return;
- }
- final int lengthToDelete =
- Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
- mConnection.deleteSurroundingText(lengthToDelete, 0);
- if (mDeleteCount > DELETE_ACCELERATE_AT) {
- final int codePointBeforeCursorToDeleteAgain =
- mConnection.getCodePointBeforeCursor();
- if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
- final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
- codePointBeforeCursorToDeleteAgain) ? 2 : 1;
- mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
- }
- }
- }
- }
- if (currentSettings.isSuggestionsRequested(mDisplayOrientation)
- && currentSettings.mCurrentLanguageHasSpaces) {
- restartSuggestionsOnWordBeforeCursorIfAtEndOfWord();
- }
- // We just removed a character. We need to update the auto-caps state.
- mKeyboardSwitcher.updateShiftState();
- }
- }
-
- /*
- * Strip a trailing space if necessary and returns whether it's a swap weak space situation.
- */
- private boolean maybeStripSpace(final int code,
- final int spaceState, final boolean isFromSuggestionStrip) {
- if (Constants.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
- mConnection.removeTrailingSpace();
+ private boolean isSuggestionStripVisible() {
+ if (!hasSuggestionStripView()) {
return false;
}
- if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
- && isFromSuggestionStrip) {
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (currentSettings.isUsuallyPrecededBySpace(code)) return false;
- if (currentSettings.isUsuallyFollowedBySpace(code)) return true;
- mConnection.removeTrailingSpace();
- }
- return false;
- }
-
- private void handleCharacter(final int primaryCode, final int x,
- final int y, final int spaceState) {
- // TODO: refactor this method to stop flipping isComposingWord around all the time, and
- // make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
- // which has the same name as other handle* methods but is not the same.
- boolean isComposingWord = mWordComposer.isComposingWord();
-
- // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
- // See onStartBatchInput() to see how to do it.
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (SPACE_STATE_PHANTOM == spaceState && !currentSettings.isWordConnector(primaryCode)) {
- if (isComposingWord) {
- // Sanity check
- throw new RuntimeException("Should not be composing here");
- }
- promotePhantomSpace();
- }
-
- if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
- // If we are in the middle of a recorrection, we need to commit the recorrection
- // first so that we can insert the character at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
- isComposingWord = false;
- }
- // We want to find out whether to start composing a new word with this character. If so,
- // we need to reset the composing state and switch isComposingWord. The order of the
- // tests is important for good performance.
- // We only start composing if we're not already composing.
- if (!isComposingWord
- // We only start composing if this is a word code point. Essentially that means it's a
- // a letter or a word connector.
- && currentSettings.isWordCodePoint(primaryCode)
- // We never go into composing state if suggestions are not requested.
- && currentSettings.isSuggestionsRequested(mDisplayOrientation) &&
- // In languages with spaces, we only start composing a word when we are not already
- // touching a word. In languages without spaces, the above conditions are sufficient.
- (!mConnection.isCursorTouchingWord(currentSettings)
- || !currentSettings.mCurrentLanguageHasSpaces)) {
- // Reset entirely the composing state anyway, then start composing a new word unless
- // the character is a single quote or a dash. The idea here is, single quote and dash
- // are not separators and they should be treated as normal characters, except in the
- // first position where they should not start composing a word.
- isComposingWord = (Constants.CODE_SINGLE_QUOTE != primaryCode
- && Constants.CODE_DASH != primaryCode);
- // Here we don't need to reset the last composed word. It will be reset
- // when we commit this one, if we ever do; if on the other hand we backspace
- // it entirely and resume suggestions on the previous word, we'd like to still
- // have touch coordinates for it.
- resetComposingState(false /* alsoResetLastComposedWord */);
- }
- if (isComposingWord) {
- final int keyX, keyY;
- if (Constants.isValidCoordinate(x) && Constants.isValidCoordinate(y)) {
- final KeyDetector keyDetector =
- mKeyboardSwitcher.getMainKeyboardView().getKeyDetector();
- keyX = keyDetector.getTouchX(x);
- keyY = keyDetector.getTouchY(y);
- } else {
- keyX = x;
- keyY = y;
- }
- mWordComposer.add(primaryCode, keyX, keyY);
- // If it's the first letter, make note of auto-caps state
- if (mWordComposer.size() == 1) {
- mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
- }
- mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
- } else {
- final boolean swapWeakSpace = maybeStripSpace(primaryCode,
- spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
-
- sendKeyCodePoint(primaryCode);
-
- if (swapWeakSpace) {
- swapSwapperAndSpace();
- mSpaceState = SPACE_STATE_WEAK;
- }
- // In case the "add to dictionary" hint was still displayed.
- if (null != mSuggestionStripView) mSuggestionStripView.dismissAddToDictionaryHint();
- }
- mHandler.postUpdateSuggestionStrip();
- if (currentSettings.mIsInternal) {
- LatinImeLoggerUtils.onNonSeparator((char)primaryCode, x, y);
- }
- }
-
- private void handleRecapitalize() {
- if (mLastSelectionStart == mLastSelectionEnd) return; // No selection
- // If we have a recapitalize in progress, use it; otherwise, create a new one.
- if (!mRecapitalizeStatus.isActive()
- || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
- final CharSequence selectedText =
- mConnection.getSelectedText(0 /* flags, 0 for no styles */);
- if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection
- final SettingsValues currentSettings = mSettings.getCurrent();
- mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd,
- selectedText.toString(), currentSettings.mLocale,
- currentSettings.mWordSeparators);
- // We trim leading and trailing whitespace.
- mRecapitalizeStatus.trim();
- // Trimming the object may have changed the length of the string, and we need to
- // reposition the selection handles accordingly. As this result in an IPC call,
- // only do it if it's actually necessary, in other words if the recapitalize status
- // is not set at the same place as before.
- if (!mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
- mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
- mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
- }
+ if (mSuggestionStripView.isShowingAddToDictionaryHint()) {
+ return true;
}
- mConnection.finishComposingText();
- mRecapitalizeStatus.rotate();
- final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
- mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
- mConnection.deleteSurroundingText(numCharsDeleted, 0);
- mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0);
- mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
- mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
- mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd);
- // Match the keyboard to the new state.
- mKeyboardSwitcher.updateShiftState();
- }
-
- // Returns true if we do an autocorrection, false otherwise.
- private boolean handleSeparator(final int primaryCode, final int x, final int y,
- final int spaceState) {
- boolean didAutoCorrect = false;
final SettingsValues currentSettings = mSettings.getCurrent();
- // We avoid sending spaces in languages without spaces if we were composing.
- final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == primaryCode
- && !currentSettings.mCurrentLanguageHasSpaces && mWordComposer.isComposingWord();
- if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
- // If we are in the middle of a recorrection, we need to commit the recorrection
- // first so that we can insert the separator at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
- }
- if (mWordComposer.isComposingWord()) { // May have changed since we stored wasComposing
- if (currentSettings.mCorrectionEnabled) {
- final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
- : StringUtils.newSingleCodePointString(primaryCode);
- commitCurrentAutoCorrection(separator);
- didAutoCorrect = true;
- } else {
- commitTyped(StringUtils.newSingleCodePointString(primaryCode));
- }
- }
-
- final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
- Constants.SUGGESTION_STRIP_COORDINATE == x);
-
- if (SPACE_STATE_PHANTOM == spaceState &&
- currentSettings.isUsuallyPrecededBySpace(primaryCode)) {
- promotePhantomSpace();
- }
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
- }
-
- if (!shouldAvoidSendingCode) {
- sendKeyCodePoint(primaryCode);
+ if (null == currentSettings) {
+ return false;
}
-
- if (Constants.CODE_SPACE == primaryCode) {
- if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
- if (maybeDoubleSpacePeriod()) {
- mSpaceState = SPACE_STATE_DOUBLE;
- } else if (!isShowingPunctuationList()) {
- mSpaceState = SPACE_STATE_WEAK;
- }
- }
-
- mHandler.startDoubleSpacePeriodTimer();
- mHandler.postUpdateSuggestionStrip();
- } else {
- if (swapWeakSpace) {
- swapSwapperAndSpace();
- mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
- } else if (SPACE_STATE_PHANTOM == spaceState
- && currentSettings.isUsuallyFollowedBySpace(primaryCode)) {
- // If we are in phantom space state, and the user presses a separator, we want to
- // stay in phantom space state so that the next keypress has a chance to add the
- // space. For example, if I type "Good dat", pick "day" from the suggestion strip
- // then insert a comma and go on to typing the next word, I want the space to be
- // inserted automatically before the next word, the same way it is when I don't
- // input the comma.
- // The case is a little different if the separator is a space stripper. Such a
- // separator does not normally need a space on the right (that's the difference
- // between swappers and strippers), so we should not stay in phantom space state if
- // the separator is a stripper. Hence the additional test above.
- mSpaceState = SPACE_STATE_PHANTOM;
- }
-
- // Set punctuation right away. onUpdateSelection will fire but tests whether it is
- // already displayed or not, so it's okay.
- setPunctuationSuggestions();
+ if (ImportantNoticeUtils.shouldShowImportantNotice(this,
+ currentSettings.mInputAttributes)) {
+ return true;
}
- if (currentSettings.mIsInternal) {
- LatinImeLoggerUtils.onSeparator((char)primaryCode, x, y);
+ if (!currentSettings.isSuggestionStripVisible()) {
+ return false;
}
-
- mKeyboardSwitcher.updateShiftState();
- return didAutoCorrect;
- }
-
- private CharSequence getTextWithUnderline(final String text) {
- return mIsAutoCorrectionIndicatorOn
- ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text)
- : text;
- }
-
- private void handleClose() {
- // TODO: Verify that words are logged properly when IME is closed.
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
- requestHideSelf(0);
- final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
- if (mainKeyboardView != null) {
- mainKeyboardView.closing();
+ if (currentSettings.isApplicationSpecifiedCompletionsOn()) {
+ return true;
}
+ return currentSettings.isSuggestionsRequested();
}
- // TODO: make this private
- // Outside LatinIME, only used by the test suite.
- @UsedForTesting
- boolean isShowingPunctuationList() {
- if (mSuggestedWords == null) return false;
- return mSettings.getCurrent().mSuggestPuncList == mSuggestedWords;
- }
-
- private boolean isSuggestionsStripVisible() {
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (mSuggestionStripView == null)
- return false;
- if (mSuggestionStripView.isShowingAddToDictionaryHint())
- return true;
- if (null == currentSettings)
- return false;
- if (!currentSettings.isSuggestionStripVisibleInOrientation(mDisplayOrientation))
- return false;
- if (currentSettings.isApplicationSpecifiedCompletionsOn())
- return true;
- return currentSettings.isSuggestionsRequested(mDisplayOrientation);
+ public boolean hasSuggestionStripView() {
+ return null != mSuggestionStripView;
}
- private void clearSuggestionStrip() {
- setSuggestedWords(SuggestedWords.EMPTY, false);
- setAutoCorrectionIndicator(false);
+ @Override
+ public boolean isShowingAddToDictionaryHint() {
+ return hasSuggestionStripView() && mSuggestionStripView.isShowingAddToDictionaryHint();
}
- private void setSuggestedWords(final SuggestedWords words, final boolean isAutoCorrection) {
- mSuggestedWords = words;
- if (mSuggestionStripView != null) {
- mSuggestionStripView.setSuggestions(words);
- mKeyboardSwitcher.onAutoCorrectionStateChanged(isAutoCorrection);
+ @Override
+ public void dismissAddToDictionaryHint() {
+ if (!hasSuggestionStripView()) {
+ return;
}
+ mSuggestionStripView.dismissAddToDictionaryHint();
}
- private void setAutoCorrectionIndicator(final boolean newAutoCorrectionIndicator) {
- // Put a blue underline to a word in TextView which will be auto-corrected.
- if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator
- && mWordComposer.isComposingWord()) {
- mIsAutoCorrectionIndicatorOn = newAutoCorrectionIndicator;
- final CharSequence textWithUnderline =
- getTextWithUnderline(mWordComposer.getTypedWord());
- // TODO: when called from an updateSuggestionStrip() call that results from a posted
- // message, this is called outside any batch edit. Potentially, this may result in some
- // janky flickering of the screen, although the display speed makes it unlikely in
- // the practice.
- mConnection.setComposingText(textWithUnderline, 1);
+ // TODO[IL]: Define a clear interface for this
+ public void setSuggestedWords(final SuggestedWords suggestedWords,
+ final boolean isSuggestionStripVisible) {
+ mInputLogic.setSuggestedWords(suggestedWords);
+ // TODO: Modify this when we support suggestions with hard keyboard
+ if (!hasSuggestionStripView()) {
+ return;
}
- }
-
- private void updateSuggestionStrip() {
- mHandler.cancelUpdateSuggestionStrip();
- final SettingsValues currentSettings = mSettings.getCurrent();
-
- // Check if we have a suggestion engine attached.
- if (mSuggest == null
- || !currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
- if (mWordComposer.isComposingWord()) {
- Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
- + "requested!");
- }
+ mKeyboardSwitcher.onAutoCorrectionStateChanged(suggestedWords.mWillAutoCorrect);
+ if (!onEvaluateInputViewShown()) {
return;
}
-
- if (!mWordComposer.isComposingWord() && !currentSettings.mBigramPredictionEnabled) {
- setPunctuationSuggestions();
+ if (!isSuggestionStripVisible) {
+ mSuggestionStripView.setVisibility(isFullscreenMode() ? View.GONE : View.INVISIBLE);
return;
}
+ mSuggestionStripView.setVisibility(View.VISIBLE);
- final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<SuggestedWords>();
- getSuggestedWordsOrOlderSuggestionsAsync(Suggest.SESSION_TYPING,
- SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
- @Override
- public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
- holder.set(suggestedWords);
- }
- }
- );
-
- // This line may cause the current thread to wait.
- final SuggestedWords suggestedWords = holder.get(null, GET_SUGGESTED_WORDS_TIMEOUT);
- if (suggestedWords != null) {
- showSuggestionStrip(suggestedWords);
+ final SettingsValues currentSettings = mSettings.getCurrent();
+ final boolean showSuggestions;
+ if (SuggestedWords.EMPTY == suggestedWords || suggestedWords.isPunctuationSuggestions()
+ || !currentSettings.isSuggestionsRequested()) {
+ showSuggestions = !mSuggestionStripView.maybeShowImportantNoticeTitle(
+ currentSettings.mInputAttributes);
+ } else {
+ showSuggestions = true;
+ }
+ if (showSuggestions) {
+ mSuggestionStripView.setSuggestions(suggestedWords,
+ SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype()));
}
}
- private void getSuggestedWords(final int sessionId, final int sequenceNumber,
+ // TODO[IL]: Move this out of LatinIME.
+ public void getSuggestedWords(final int sessionId, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
- final Suggest suggest = mSuggest;
- if (keyboard == null || suggest == null) {
+ if (keyboard == null) {
callback.onGetSuggestedWords(SuggestedWords.EMPTY);
return;
}
@@ -2552,528 +1419,83 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// should just skip whitespace if any, so 1.
final SettingsValues currentSettings = mSettings.getCurrent();
final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues;
- final String prevWord;
- if (currentSettings.mCurrentLanguageHasSpaces) {
- // If we are typing in a language with spaces we can just look up the previous
- // word from textview.
- prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators,
- mWordComposer.isComposingWord() ? 2 : 1);
- } else {
- prevWord = LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
- : mLastComposedWord.mCommittedWord;
- }
- suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
- currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled,
- additionalFeaturesOptions, sessionId, sequenceNumber, callback);
- }
-
- private void getSuggestedWordsOrOlderSuggestionsAsync(final int sessionId,
- final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
- mInputUpdater.getSuggestedWords(sessionId, sequenceNumber,
- new OnGetSuggestedWordsCallback() {
- @Override
- public void onGetSuggestedWords(SuggestedWords suggestedWords) {
- callback.onGetSuggestedWords(maybeRetrieveOlderSuggestions(
- mWordComposer.getTypedWord(), suggestedWords));
- }
- });
- }
-
- private SuggestedWords maybeRetrieveOlderSuggestions(final String typedWord,
- final SuggestedWords suggestedWords) {
- // TODO: consolidate this into getSuggestedWords
- // We update the suggestion strip only when we have some suggestions to show, i.e. when
- // the suggestion count is > 1; else, we leave the old suggestions, with the typed word
- // replaced with the new one. However, when the word is a dictionary word, or when the
- // length of the typed word is 1 or 0 (after a deletion typically), we do want to remove the
- // old suggestions. Also, if we are showing the "add to dictionary" hint, we need to
- // revert to suggestions - although it is unclear how we can come here if it's displayed.
- if (suggestedWords.size() > 1 || typedWord.length() <= 1
- || suggestedWords.mTypedWordValid || null == mSuggestionStripView
- || mSuggestionStripView.isShowingAddToDictionaryHint()) {
- return suggestedWords;
- } else {
- return getOlderSuggestions(typedWord);
- }
- }
- private SuggestedWords getOlderSuggestions(final String typedWord) {
- SuggestedWords previousSuggestedWords = mSuggestedWords;
- if (previousSuggestedWords == mSettings.getCurrent().mSuggestPuncList) {
- previousSuggestedWords = SuggestedWords.EMPTY;
- }
- if (typedWord == null) {
- return previousSuggestedWords;
+ if (DEBUG) {
+ if (mInputLogic.mWordComposer.isComposingWord()
+ || mInputLogic.mWordComposer.isBatchMode()) {
+ final String previousWord
+ = mInputLogic.mWordComposer.getPreviousWordForSuggestion();
+ // TODO: this is for checking consistency with older versions. Remove this when
+ // we are confident this is stable.
+ // We're checking the previous word in the text field against the memorized previous
+ // word. If we are composing a word we should have the second word before the cursor
+ // memorized, otherwise we should have the first.
+ final CharSequence rereadPrevWord = mInputLogic.getNthPreviousWordForSuggestion(
+ currentSettings.mSpacingAndPunctuations,
+ mInputLogic.mWordComposer.isComposingWord() ? 2 : 1);
+ if (!TextUtils.equals(previousWord, rereadPrevWord)) {
+ throw new RuntimeException("Unexpected previous word: "
+ + previousWord + " <> " + rereadPrevWord);
+ }
+ }
}
- final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
- SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord,
- previousSuggestedWords);
- return new SuggestedWords(typedWordAndPreviousSuggestions,
- false /* typedWordValid */,
- false /* hasAutoCorrectionCandidate */,
- false /* isPunctuationSuggestions */,
- true /* isObsoleteSuggestions */,
- false /* isPrediction */);
+ mInputLogic.mSuggest.getSuggestedWords(mInputLogic.mWordComposer,
+ mInputLogic.mWordComposer.getPreviousWordForSuggestion(),
+ keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive,
+ currentSettings.mCorrectionEnabled, additionalFeaturesOptions, sessionId,
+ sequenceNumber, callback);
}
- private void setAutoCorrection(final SuggestedWords suggestedWords, final String typedWord) {
- if (suggestedWords.isEmpty()) return;
+ @Override
+ public void showSuggestionStrip(final SuggestedWords sourceSuggestedWords) {
+ final SuggestedWords suggestedWords =
+ sourceSuggestedWords.isEmpty() ? SuggestedWords.EMPTY : sourceSuggestedWords;
final String autoCorrection;
if (suggestedWords.mWillAutoCorrect) {
autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
} else {
// We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
// because it may differ from mWordComposer.mTypedWord.
- autoCorrection = typedWord;
- }
- mWordComposer.setAutoCorrection(autoCorrection);
- }
-
- private void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
- final String typedWord) {
- if (suggestedWords.isEmpty()) {
- // No auto-correction is available, clear the cached values.
- AccessibilityUtils.getInstance().setAutoCorrection(null, null);
- clearSuggestionStrip();
- return;
- }
- setAutoCorrection(suggestedWords, typedWord);
- final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
- setSuggestedWords(suggestedWords, isAutoCorrection);
- setAutoCorrectionIndicator(isAutoCorrection);
- setSuggestionStripShown(isSuggestionsStripVisible());
- // An auto-correction is available, cache it in accessibility code so
- // we can be speak it if the user touches a key that will insert it.
- AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords, typedWord);
- }
-
- private void showSuggestionStrip(final SuggestedWords suggestedWords) {
- if (suggestedWords.isEmpty()) {
- clearSuggestionStrip();
- return;
+ autoCorrection = sourceSuggestedWords.mTypedWord;
}
- showSuggestionStripWithTypedWord(suggestedWords,
- suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD));
- }
-
- private void commitCurrentAutoCorrection(final String separator) {
- // Complete any pending suggestions query first
- if (mHandler.hasPendingUpdateSuggestions()) {
- updateSuggestionStrip();
- }
- final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
- final String typedWord = mWordComposer.getTypedWord();
- final String autoCorrection = (typedAutoCorrection != null)
- ? typedAutoCorrection : typedWord;
- if (autoCorrection != null) {
- if (TextUtils.isEmpty(typedWord)) {
- throw new RuntimeException("We have an auto-correction but the typed word "
- + "is empty? Impossible! I must commit suicide.");
- }
- if (mSettings.isInternal()) {
- LatinImeLoggerUtils.onAutoCorrection(
- typedWord, autoCorrection, separator, mWordComposer);
- }
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final SuggestedWords suggestedWords = mSuggestedWords;
- ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
- separator, mWordComposer.isBatchMode(), suggestedWords);
- }
- mExpectingUpdateSelection = true;
- commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
- separator);
- if (!typedWord.equals(autoCorrection)) {
- // This will make the correction flash for a short while as a visual clue
- // to the user that auto-correction happened. It has no other effect; in particular
- // note that this won't affect the text inside the text field AT ALL: it only makes
- // the segment of text starting at the supplied index and running for the length
- // of the auto-correction flash. At this moment, the "typedWord" argument is
- // ignored by TextView.
- mConnection.commitCorrection(
- new CorrectionInfo(mLastSelectionEnd - typedWord.length(),
- typedWord, autoCorrection));
- }
+ if (SuggestedWords.EMPTY == suggestedWords) {
+ setNeutralSuggestionStrip();
+ } else {
+ mInputLogic.mWordComposer.setAutoCorrection(autoCorrection);
+ setSuggestedWords(suggestedWords, isSuggestionStripVisible());
}
+ // 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);
}
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
// interface
@Override
public void pickSuggestionManually(final int index, final SuggestedWordInfo suggestionInfo) {
- final SuggestedWords suggestedWords = mSuggestedWords;
- final String suggestion = suggestionInfo.mWord;
- // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
- if (suggestion.length() == 1 && isShowingPunctuationList()) {
- // Word separators are suggested before the user inputs something.
- // So, LatinImeLogger logs "" as a user's input.
- LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
- // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
- final int primaryCode = suggestion.charAt(0);
- onCodeInput(primaryCode,
- Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
- false /* isBatchMode */, suggestedWords.mIsPrediction);
- }
- return;
- }
-
- mConnection.beginBatchEdit();
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0
- // In the batch input mode, a manually picked suggested word should just replace
- // the current batch input text and there is no need for a phantom space.
- && !mWordComposer.isBatchMode()) {
- final int firstChar = Character.codePointAt(suggestion, 0);
- if (!currentSettings.isWordSeparator(firstChar)
- || currentSettings.isUsuallyPrecededBySpace(firstChar)) {
- promotePhantomSpace();
- }
- }
-
- if (currentSettings.isApplicationSpecifiedCompletionsOn()
- && mApplicationSpecifiedCompletions != null
- && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
- mSuggestedWords = SuggestedWords.EMPTY;
- if (mSuggestionStripView != null) {
- mSuggestionStripView.clear();
- }
- mKeyboardSwitcher.updateShiftState();
- resetComposingState(true /* alsoResetLastComposedWord */);
- final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
- mConnection.commitCompletion(completionInfo);
- mConnection.endBatchEdit();
- return;
- }
-
- // We need to log before we commit, because the word composer will store away the user
- // typed word.
- final String replacedWord = mWordComposer.getTypedWord();
- LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
- mExpectingUpdateSelection = true;
- commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
- LastComposedWord.NOT_A_SEPARATOR);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
- mWordComposer.isBatchMode(), suggestionInfo.mScore, suggestionInfo.mKind,
- suggestionInfo.mSourceDict.mDictType);
- }
- mConnection.endBatchEdit();
- // Don't allow cancellation of manual pick
- mLastComposedWord.deactivate();
- // Space state must be updated before calling updateShiftState
- mSpaceState = SPACE_STATE_PHANTOM;
- mKeyboardSwitcher.updateShiftState();
-
- // We should show the "Touch again to save" hint if the user pressed the first entry
- // AND it's in none of our current dictionaries (main, user or otherwise).
- // Please note that if mSuggest is null, it means that everything is off: suggestion
- // and correction, so we shouldn't try to show the hint
- final Suggest suggest = mSuggest;
- final boolean showingAddToDictionaryHint =
- (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
- || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
- && suggest != null
- // If the suggestion is not in the dictionary, the hint should be shown.
- && !AutoCorrectionUtils.isValidWord(suggest, suggestion, true);
-
- if (currentSettings.mIsInternal) {
- LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
- Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
- }
- if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
- mSuggestionStripView.showAddToDictionaryHint(
- suggestion, currentSettings.mHintToSaveText);
- } else {
- // If we're not showing the "Touch again to save", then update the suggestion strip.
- mHandler.postUpdateSuggestionStrip();
- }
+ final InputTransaction completeInputTransaction = mInputLogic.onPickSuggestionManually(
+ mSettings.getCurrent(), index, suggestionInfo,
+ mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
+ updateStateAfterInputTransaction(completeInputTransaction);
}
- /**
- * Commits the chosen word to the text field and saves it for later retrieval.
- */
- private void commitChosenWord(final String chosenWord, final int commitType,
- final String separatorString) {
- final SuggestedWords suggestedWords = mSuggestedWords;
- mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
- this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
- // Add the word to the user history dictionary
- final String prevWord = addToUserHistoryDictionary(chosenWord);
- // 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, chosenWord, separatorString,
- prevWord);
- }
-
- private void setPunctuationSuggestions() {
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (currentSettings.mBigramPredictionEnabled) {
- clearSuggestionStrip();
- } else {
- setSuggestedWords(currentSettings.mSuggestPuncList, false);
- }
- setAutoCorrectionIndicator(false);
- setSuggestionStripShown(isSuggestionsStripVisible());
- }
-
- private String addToUserHistoryDictionary(final String suggestion) {
- if (TextUtils.isEmpty(suggestion)) return null;
- final Suggest suggest = mSuggest;
- if (suggest == null) return null;
-
- // 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.
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (!currentSettings.mCorrectionEnabled) return null;
-
- final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
- if (userHistoryDictionary == null) return null;
-
- final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2);
- final String secondWord;
- if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
- secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
- } else {
- secondWord = suggestion;
- }
- // 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 int maxFreq = AutoCorrectionUtils.getMaxFrequency(
- suggest.getUnigramDictionaries(), suggestion);
- if (maxFreq == 0) return null;
- userHistoryDictionary.addToDictionary(prevWord, secondWord, maxFreq > 0);
- return prevWord;
- }
-
- private boolean isResumableWord(final String word, final SettingsValues settings) {
- final int firstCodePoint = word.codePointAt(0);
- return settings.isWordCodePoint(firstCodePoint)
- && Constants.CODE_SINGLE_QUOTE != firstCodePoint
- && Constants.CODE_DASH != firstCodePoint;
- }
-
- /**
- * Check if the cursor is touching a word. If so, restart suggestions on this word, else
- * do nothing.
- */
- private void restartSuggestionsOnWordTouchedByCursor() {
- // HACK: We may want to special-case some apps that exhibit bad behavior in case of
- // recorrection. This is a temporary, stopgap measure that will be removed later.
- // TODO: remove this.
- if (mAppWorkAroundsUtils.isBrokenByRecorrection()) return;
- // A simple way to test for support from the TextView.
- if (!isSuggestionsStripVisible()) return;
- // Recorrection is not supported in languages without spaces because we don't know
- // how to segment them yet.
- if (!mSettings.getCurrent().mCurrentLanguageHasSpaces) return;
- // If the cursor is not touching a word, or if there is a selection, return right away.
- if (mLastSelectionStart != mLastSelectionEnd) return;
- // If we don't know the cursor location, return.
- if (mLastSelectionStart < 0) return;
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (!mConnection.isCursorTouchingWord(currentSettings)) return;
- final TextRange range = mConnection.getWordRangeAtCursor(currentSettings.mWordSeparators,
- 0 /* additionalPrecedingWordsCount */);
- if (null == range) return; // Happens if we don't have an input connection at all
- if (range.length() <= 0) return; // Race condition. No text to resume on, so bail out.
- // If for some strange reason (editor bug or so) we measure the text before the cursor as
- // longer than what the entire text is supposed to be, the safe thing to do is bail out.
- final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor();
- if (numberOfCharsInWordBeforeCursor > mLastSelectionStart) return;
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
- final String typedWord = range.mWord.toString();
- if (!isResumableWord(typedWord, currentSettings)) return;
- int i = 0;
- for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) {
- for (final String s : span.getSuggestions()) {
- ++i;
- if (!TextUtils.equals(s, typedWord)) {
- suggestions.add(new SuggestedWordInfo(s,
- SuggestionStripView.MAX_SUGGESTIONS - i,
- SuggestedWordInfo.KIND_RESUMED, Dictionary.DICTIONARY_RESUMED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE
- /* autoCommitFirstWordConfidence */));
- }
- }
- }
- mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard());
- mWordComposer.setCursorPositionWithinWord(
- typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
- mConnection.setComposingRegion(
- mLastSelectionStart - numberOfCharsInWordBeforeCursor,
- mLastSelectionEnd + range.getNumberOfCharsInWordAfterCursor());
- if (suggestions.isEmpty()) {
- // We come here if there weren't any suggestion spans on this word. We will try to
- // compute suggestions for it instead.
- mInputUpdater.getSuggestedWords(Suggest.SESSION_TYPING,
- SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
- @Override
- public void onGetSuggestedWords(
- final SuggestedWords suggestedWordsIncludingTypedWord) {
- final SuggestedWords suggestedWords;
- 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 #getSuggestedWordsExcludingTypedWord() method sets
- // willAutoCorrect to false.
- suggestedWords = suggestedWordsIncludingTypedWord
- .getSuggestedWordsExcludingTypedWord();
- } else {
- // No saved suggestions, and we were unable to compute any good one
- // either. Rather than displaying an empty suggestion strip, we'll
- // display the original word alone in the middle.
- // Since there is only one word, willAutoCorrect is false.
- suggestedWords = suggestedWordsIncludingTypedWord;
- }
- // We need to pass typedWord because mWordComposer.mTypedWord may
- // differ from typedWord.
- unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(
- suggestedWords, typedWord);
- }});
- } else {
- // We found suggestion spans in the word. We'll create the SuggestedWords out of
- // them, and make willAutoCorrect false.
- final SuggestedWords suggestedWords = new SuggestedWords(suggestions,
- true /* typedWordValid */, false /* willAutoCorrect */,
- false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */,
- false /* isPrediction */);
- // We need to pass typedWord because mWordComposer.mTypedWord may differ from typedWord.
- unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords, typedWord);
- }
- }
-
- public void unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(
- final SuggestedWords suggestedWords, final String typedWord) {
- // Note that it's very important here that suggestedWords.mWillAutoCorrect is false.
- // We never want to auto-correct on a resumed suggestion. Please refer to the three places
- // above in restartSuggestionsOnWordTouchedByCursor() where suggestedWords is affected.
- // We also need to unset mIsAutoCorrectionIndicatorOn to avoid showSuggestionStrip touching
- // the text to adapt it.
- // TODO: remove mIsAutoCorrectionIndicatorOn (see comment on definition)
- mIsAutoCorrectionIndicatorOn = false;
- mHandler.showSuggestionStripWithTypedWord(suggestedWords, typedWord);
- }
-
- /**
- * Check if the cursor is actually at the end of a word. If so, restart suggestions on this
- * word, else do nothing.
- */
- private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() {
- final CharSequence word =
- mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent());
- if (null != word) {
- final String wordString = word.toString();
- restartSuggestionsOnWordBeforeCursor(wordString);
- // TODO: Handle the case where the user manually moves the cursor and then backs up over
- // a separator. In that case, the current log unit should not be uncommitted.
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString,
- true /* dumpCurrentLogUnit */);
- }
- }
- }
-
- private void restartSuggestionsOnWordBeforeCursor(final String word) {
- mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
- final int length = word.length();
- mConnection.deleteSurroundingText(length, 0);
- mConnection.setComposingText(word, 1);
- mHandler.postUpdateSuggestionStrip();
- }
-
- /**
- * Retry resetting caches in the rich input connection.
- *
- * When the editor can't be accessed we can't reset the caches, so we schedule a retry.
- * This method handles the retry, and re-schedules a new retry if we still can't access.
- * We only retry up to 5 times before giving up.
- *
- * @param tryResumeSuggestions Whether we should resume suggestions or not.
- * @param remainingTries How many times we may try again before giving up.
- */
- private void retryResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
- if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(mLastSelectionStart, false)) {
- if (0 < remainingTries) {
- mHandler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
- return;
- }
- // If remainingTries is 0, we should stop waiting for new tries, but it's still
- // better to load the keyboard (less things will be broken).
- }
- tryFixLyingCursorPosition();
- mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent());
- if (tryResumeSuggestions) mHandler.postResumeSuggestions();
- }
-
- private void revertCommit() {
- final String previousWord = mLastComposedWord.mPrevWord;
- final String originallyTypedWord = mLastComposedWord.mTypedWord;
- final String committedWord = mLastComposedWord.mCommittedWord;
- final int cancelLength = committedWord.length();
- // We want java chars, not codepoints for the following.
- final int separatorLength = mLastComposedWord.mSeparatorString.length();
- // TODO: should we check our saved separator against the actual contents of the text view?
- final int deleteLength = cancelLength + separatorLength;
- if (DEBUG) {
- if (mWordComposer.isComposingWord()) {
- throw new RuntimeException("revertCommit, but we are composing a word");
- }
- final CharSequence wordBeforeCursor =
- mConnection.getTextBeforeCursor(deleteLength, 0)
- .subSequence(0, cancelLength);
- if (!TextUtils.equals(committedWord, wordBeforeCursor)) {
- throw new RuntimeException("revertCommit check failed: we thought we were "
- + "reverting \"" + committedWord
- + "\", but before the cursor we found \"" + wordBeforeCursor + "\"");
- }
- }
- mConnection.deleteSurroundingText(deleteLength, 0);
- if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
- mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
- }
- final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
- if (mSettings.getCurrent().mCurrentLanguageHasSpaces) {
- // For languages with spaces, we revert to the typed string, but the cursor is still
- // after the separator so we don't resume suggestions. If the user wants to correct
- // the word, they have to press backspace again.
- mConnection.commitText(stringToCommit, 1);
- } 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.
- mWordComposer.setComposingWord(stringToCommit, mKeyboardSwitcher.getKeyboard());
- mConnection.setComposingText(stringToCommit, 1);
- }
- if (mSettings.isInternal()) {
- LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
- Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
- }
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_revertCommit(committedWord, originallyTypedWord,
- mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString);
+ @Override
+ public void showAddToDictionaryHint(final String word) {
+ if (!hasSuggestionStripView()) {
+ return;
}
- // Don't restart suggestion yet. We'll restart if the user deletes the
- // separator.
- mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
- // We have a separator between the word and the cursor: we should show predictions.
- mHandler.postUpdateSuggestionStrip();
+ mSuggestionStripView.showAddToDictionaryHint(word);
}
- // This essentially inserts a space, and that's it.
- public void promotePhantomSpace() {
+ // This will show either an empty suggestion strip (if prediction is enabled) or
+ // punctuation suggestions (if it's disabled).
+ @Override
+ public void setNeutralSuggestionStrip() {
final SettingsValues currentSettings = mSettings.getCurrent();
- if (currentSettings.shouldInsertSpacesAutomatically()
- && currentSettings.mCurrentLanguageHasSpaces
- && !mConnection.textBeforeCursorLooksLikeURL()) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_promotePhantomSpace();
- }
- sendKeyCodePoint(Constants.CODE_SPACE);
- }
+ final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled
+ ? SuggestedWords.EMPTY : currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
+ setSuggestedWords(neutralSuggestions, isSuggestionStripVisible());
}
// TODO: Make this private
@@ -3089,18 +1511,41 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
loadSettings();
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
// Reload keyboard because the current language has been changed.
- mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent());
+ mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent(),
+ getCurrentAutoCapsState(), getCurrentRecapitalizeState());
+ }
+ }
+
+ /**
+ * After an input transaction has been executed, some state must be updated. This includes
+ * the shift state of the keyboard and suggestions. This method looks at the finished
+ * inputTransaction to find out what is necessary and updates the state accordingly.
+ * @param inputTransaction The transaction that has been executed.
+ */
+ private void updateStateAfterInputTransaction(final InputTransaction inputTransaction) {
+ switch (inputTransaction.getRequiredShiftUpdate()) {
+ case InputTransaction.SHIFT_UPDATE_LATER:
+ mHandler.postUpdateShiftState();
+ break;
+ case InputTransaction.SHIFT_UPDATE_NOW:
+ mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
+ getCurrentRecapitalizeState());
+ break;
+ default: // SHIFT_NO_UPDATE
+ }
+ if (inputTransaction.requiresUpdateSuggestions()) {
+ mHandler.postUpdateSuggestionStrip();
}
}
private void hapticAndAudioFeedback(final int code, final int repeatCount) {
final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView();
- if (keyboardView != null && keyboardView.isInSlidingKeyInput()) {
- // No need to feedback while sliding input.
+ if (keyboardView != null && keyboardView.isInDraggingFinger()) {
+ // No need to feedback while finger is dragging.
return;
}
if (repeatCount > 0) {
- if (code == Constants.CODE_DELETE && !mConnection.canDeleteCharacters()) {
+ if (code == Constants.CODE_DELETE && !mInputLogic.mConnection.canDeleteCharacters()) {
// No need to feedback when repeat delete key will have no effect.
return;
}
@@ -3124,7 +1569,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onPressKey(final int primaryCode, final int repeatCount,
final boolean isSinglePointer) {
- mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer);
+ mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer, getCurrentAutoCapsState(),
+ getCurrentRecapitalizeState());
hapticAndAudioFeedback(primaryCode, repeatCount);
}
@@ -3132,7 +1578,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// press matching call is {@link #onPressKey(int,int,boolean)} above.
@Override
public void onReleaseKey(final int primaryCode, final boolean withSliding) {
- mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
+ mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding, getCurrentAutoCapsState(),
+ getCurrentRecapitalizeState());
// If accessibility is on, ensure the user receives keyboard state updates.
if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
@@ -3147,25 +1594,37 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
+ private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
+ final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId);
+ if (null != decoder) return decoder;
+ // TODO: create the decoder according to the specification
+ final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId);
+ mHardwareEventDecoders.put(deviceId, newDecoder);
+ return newDecoder;
+ }
+
// Hooks for hardware keyboard
@Override
- public boolean onKeyDown(final int keyCode, final KeyEvent event) {
- if (!ProductionFlag.IS_HARDWARE_KEYBOARD_SUPPORTED) return super.onKeyDown(keyCode, event);
- // onHardwareKeyEvent, like onKeyDown returns true if it handled the event, false if
- // it doesn't know what to do with it and leave it to the application. For example,
- // hardware key events for adjusting the screen's brightness are passed as is.
- if (mEventInterpreter.onHardwareKeyEvent(event)) {
- final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode();
- mCurrentlyPressedHardwareKeys.add(keyIdentifier);
+ public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
+ if (!ProductionFlag.IS_HARDWARE_KEYBOARD_SUPPORTED) {
+ return super.onKeyDown(keyCode, keyEvent);
+ }
+ final Event event = getHardwareKeyEventDecoder(
+ keyEvent.getDeviceId()).decodeHardwareKey(keyEvent);
+ // If the event is not handled by LatinIME, we just pass it to the parent implementation.
+ // If it's handled, we return true because we did handle it.
+ if (event.isHandled()) {
+ mInputLogic.onCodeInput(mSettings.getCurrent(), event,
+ mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
return true;
}
- return super.onKeyDown(keyCode, event);
+ return super.onKeyDown(keyCode, keyEvent);
}
@Override
public boolean onKeyUp(final int keyCode, final KeyEvent event) {
final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode();
- if (mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) {
+ if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) {
return true;
}
return super.onKeyUp(keyCode, event);
@@ -3177,7 +1636,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// boolean onKeyMultiple(final int keyCode, final int count, final KeyEvent event);
// receive ringer mode change and network state change.
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ private BroadcastReceiver mConnectivityAndRingerModeChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
@@ -3190,17 +1649,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
};
private void launchSettings() {
- handleClose();
+ mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR);
+ requestHideSelf(0);
+ final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+ if (mainKeyboardView != null) {
+ mainKeyboardView.closing();
+ }
launchSubActivity(SettingsActivity.class);
}
- public void launchKeyboardedDialogActivity(final Class<? extends Activity> activityClass) {
- // Put the text in the attached EditText into a safe, saved state before switching to a
- // new activity that will also use the soft keyboard.
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
- launchSubActivity(activityClass);
- }
-
private void launchSubActivity(final Class<? extends Activity> activityClass) {
Intent intent = new Intent();
intent.setClass(LatinIME.this, activityClass);
@@ -3215,9 +1672,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final CharSequence[] items = new CharSequence[] {
// TODO: Should use new string "Select active input modes".
getString(R.string.language_selection_title),
- getString(ApplicationUtils.getAcitivityTitleResId(this, SettingsActivity.class)),
+ getString(ApplicationUtils.getActivityTitleResId(this, SettingsActivity.class)),
};
- final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
+ final OnClickListener listener = new OnClickListener() {
@Override
public void onClick(DialogInterface di, int position) {
di.dismiss();
@@ -3226,8 +1683,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
mRichImm.getInputMethodIdOfThisIme(),
Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
- | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
break;
case 1:
@@ -3236,21 +1693,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
};
- final AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setItems(items, listener)
- .setTitle(title);
- showOptionDialog(builder.create());
+ final AlertDialog.Builder builder = new AlertDialog.Builder(
+ DialogUtils.getPlatformDialogThemeContext(this));
+ builder.setItems(items, listener).setTitle(title);
+ final AlertDialog dialog = builder.create();
+ dialog.setCancelable(true /* cancelable */);
+ dialog.setCanceledOnTouchOutside(true /* cancelable */);
+ showOptionDialog(dialog);
}
- public void showOptionDialog(final AlertDialog dialog) {
+ // TODO: Move this method out of {@link LatinIME}.
+ private void showOptionDialog(final AlertDialog dialog) {
final IBinder windowToken = mKeyboardSwitcher.getMainKeyboardView().getWindowToken();
if (windowToken == null) {
return;
}
- dialog.setCancelable(true);
- dialog.setCanceledOnTouchOutside(true);
-
final Window window = dialog.getWindow();
final WindowManager.LayoutParams lp = window.getAttributes();
lp.token = windowToken;
@@ -3264,31 +1722,48 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: can this be removed somehow without breaking the tests?
@UsedForTesting
- /* package for test */ String getFirstSuggestedWord() {
- return mSuggestedWords.size() > 0 ? mSuggestedWords.getWord(0) : null;
+ /* package for test */ SuggestedWords getSuggestedWordsForTest() {
+ // You may not use this method for anything else than debug
+ return DEBUG ? mInputLogic.mSuggestedWords : null;
}
// DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
@UsedForTesting
- /* package for test */ boolean isCurrentlyWaitingForMainDictionary() {
- return mSuggest.isCurrentlyWaitingForMainDictionary();
+ /* package for test */ void waitForLoadingDictionaries(final long timeout, final TimeUnit unit)
+ throws InterruptedException {
+ mInputLogic.mSuggest.mDictionaryFacilitator.waitForLoadingDictionariesForTesting(
+ timeout, unit);
}
- // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
+ // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
@UsedForTesting
- /* package for test */ boolean hasMainDictionary() {
- return mSuggest.hasMainDictionary();
+ /* package for test */ void replaceDictionariesForTest(final Locale locale) {
+ final SettingsValues settingsValues = mSettings.getCurrent();
+ mInputLogic.mSuggest.mDictionaryFacilitator.resetDictionaries(this, locale,
+ settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
+ false /* forceReloadMainDictionary */, this /* listener */);
}
- // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
+ // DO NOT USE THIS for any other purpose than testing.
@UsedForTesting
- /* package for test */ void replaceMainDictionaryForTest(final Locale locale) {
- mSuggest.resetMainDict(this, locale, null);
+ /* package for test */ void clearPersonalizedDictionariesForTest() {
+ mInputLogic.mSuggest.mDictionaryFacilitator.clearUserHistoryDictionary();
+ mInputLogic.mSuggest.mDictionaryFacilitator.clearPersonalizationDictionary();
+ }
+
+ public void dumpDictionaryForDebug(final String dictName) {
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator =
+ mInputLogic.mSuggest.mDictionaryFacilitator;
+ if (dictionaryFacilitator.getLocale() == null) {
+ resetSuggest();
+ }
+ mInputLogic.mSuggest.mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
}
public void debugDumpStateAndCrashWithException(final String context) {
- final StringBuilder s = new StringBuilder(mAppWorkAroundsUtils.toString());
- s.append("\nAttributes : ").append(mSettings.getCurrent().mInputAttributes)
+ final SettingsValues settingsValues = mSettings.getCurrent();
+ final StringBuilder s = new StringBuilder(settingsValues.toString());
+ s.append("\nAttributes : ").append(settingsValues.mInputAttributes)
.append("\nContext : ").append(context);
throw new RuntimeException(s.toString());
}
@@ -3299,17 +1774,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final Printer p = new PrintWriterPrinter(fout);
p.println("LatinIME state :");
+ p.println(" VersionCode = " + ApplicationUtils.getVersionCode(this));
+ p.println(" VersionName = " + ApplicationUtils.getVersionName(this));
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
p.println(" Keyboard mode = " + keyboardMode);
final SettingsValues settingsValues = mSettings.getCurrent();
- p.println(" mIsSuggestionsSuggestionsRequested = "
- + settingsValues.isSuggestionsRequested(mDisplayOrientation));
- p.println(" mCorrectionEnabled=" + settingsValues.mCorrectionEnabled);
- p.println(" isComposingWord=" + mWordComposer.isComposingWord());
- p.println(" mSoundOn=" + settingsValues.mSoundOn);
- p.println(" mVibrateOn=" + settingsValues.mVibrateOn);
- p.println(" mKeyPreviewPopupOn=" + settingsValues.mKeyPreviewPopupOn);
- p.println(" inputAttributes=" + settingsValues.mInputAttributes);
+ p.println(settingsValues.dump());
+ // TODO: Dump all settings values
}
}
diff --git a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
new file mode 100644
index 000000000..4911bcdf6
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
@@ -0,0 +1,116 @@
+/*
+ * 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 com.android.inputmethod.keyboard.internal.KeySpecParser;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * The extended {@link SuggestedWords} class to represent punctuation suggestions.
+ *
+ * Each punctuation specification string is the key specification that can be parsed by
+ * {@link KeySpecParser}.
+ */
+public final class PunctuationSuggestions extends SuggestedWords {
+ private PunctuationSuggestions(final ArrayList<SuggestedWordInfo> punctuationsList) {
+ super(punctuationsList,
+ null /* rawSuggestions */,
+ false /* typedWordValid */,
+ false /* hasAutoCorrectionCandidate */,
+ false /* isObsoleteSuggestions */,
+ false /* isPrediction */);
+ }
+
+ /**
+ * Create new instance of {@link PunctuationSuggestions} from the array of punctuation key
+ * specifications.
+ *
+ * @param punctuationSpecs The array of punctuation key specifications.
+ * @return The {@link PunctuationSuggestions} object.
+ */
+ public static PunctuationSuggestions newPunctuationSuggestions(
+ final String[] punctuationSpecs) {
+ final ArrayList<SuggestedWordInfo> puncuationsList = CollectionUtils.newArrayList();
+ for (final String puncSpec : punctuationSpecs) {
+ puncuationsList.add(newHardCodedWordInfo(puncSpec));
+ }
+ return new PunctuationSuggestions(puncuationsList);
+ }
+
+ /**
+ * {@inheritDoc}
+ * Note that {@link super#getWord(int)} returns a punctuation key specification text.
+ * The suggested punctuation should be gotten by parsing the key specification.
+ */
+ @Override
+ public String getWord(final int index) {
+ final String keySpec = super.getWord(index);
+ final int code = KeySpecParser.getCode(keySpec);
+ return (code == Constants.CODE_OUTPUT_TEXT)
+ ? KeySpecParser.getOutputText(keySpec)
+ : StringUtils.newSingleCodePointString(code);
+ }
+
+ /**
+ * {@inheritDoc}
+ * Note that {@link super#getWord(int)} returns a punctuation key specification text.
+ * The displayed text should be gotten by parsing the key specification.
+ */
+ @Override
+ public String getLabel(final int index) {
+ final String keySpec = super.getWord(index);
+ return KeySpecParser.getLabel(keySpec);
+ }
+
+ /**
+ * {@inheritDoc}
+ * Note that {@link #getWord(int)} returns a suggested punctuation. We should create a
+ * {@link SuggestedWordInfo} object that represents a hard coded word.
+ */
+ @Override
+ public SuggestedWordInfo getInfo(final int index) {
+ return newHardCodedWordInfo(getWord(index));
+ }
+
+ /**
+ * The predicator to tell whether this object represents punctuation suggestions.
+ * @return true if this object represents punctuation suggestions.
+ */
+ @Override
+ public boolean isPunctuationSuggestions() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "PunctuationSuggestions: "
+ + " words=" + Arrays.toString(mSuggestedWordInfoList.toArray());
+ }
+
+ private static SuggestedWordInfo newHardCodedWordInfo(final String keySpec) {
+ return new SuggestedWordInfo(keySpec, SuggestedWordInfo.MAX_SCORE,
+ SuggestedWordInfo.KIND_HARDCODED,
+ Dictionary.DICTIONARY_HARDCODED,
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
index 68505ce38..9f61d6c37 100644
--- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
@@ -51,20 +51,21 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final float[] inOutLanguageWeight) {
return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, 0 /* sessionId */);
+ additionalFeaturesOptions, 0 /* sessionId */, inOutLanguageWeight);
}
@Override
public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final int sessionId) {
+ final int sessionId, final float[] inOutLanguageWeight) {
if (mLock.readLock().tryLock()) {
try {
return mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
- blockOffensiveWords, additionalFeaturesOptions);
+ blockOffensiveWords, additionalFeaturesOptions, inOutLanguageWeight);
} 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 673d1b4c2..606bb775e 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -27,7 +27,7 @@ import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.latin.settings.SettingsValues;
+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.SpannableStringUtils;
@@ -35,7 +35,7 @@ import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
import com.android.inputmethod.research.ResearchLogger;
-import java.util.Locale;
+import java.util.Arrays;
import java.util.regex.Pattern;
/**
@@ -57,14 +57,19 @@ public final class RichInputConnection {
private static final int INVALID_CURSOR_POSITION = -1;
/**
- * This variable contains an expected value for the cursor position. This is where the
- * cursor may end up after all the keyboard-triggered updates have passed. We keep this to
- * compare it to the actual cursor position to guess whether the move was caused by a
- * keyboard command or not.
- * It's not really the cursor position: the cursor may not be there yet, and it's also expected
- * there be cases where it never actually comes to be there.
+ * This variable contains an expected value for the selection start position. This is where the
+ * cursor or selection start may end up after all the keyboard-triggered updates have passed. We
+ * keep this to compare it to the actual selection start to guess whether the move was caused by
+ * a keyboard command or not.
+ * It's not really the selection start position: the selection start may not be there yet, and
+ * in some cases, it may never arrive there.
*/
- private int mExpectedCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points
+ private int mExpectedSelStart = INVALID_CURSOR_POSITION; // in chars, not code points
+ /**
+ * The expected selection end. Only differs from mExpectedSelStart if a non-empty selection is
+ * expected. The same caveats as mExpectedSelStart apply.
+ */
+ private int mExpectedSelEnd = INVALID_CURSOR_POSITION; // in chars, not code points
/**
* This contains the committed text immediately preceding the cursor and the composing
* text if any. It is refreshed when the cursor moves by calling upon the TextView.
@@ -93,7 +98,7 @@ public final class RichInputConnection {
final ExtractedText et = mIC.getExtractedText(r, 0);
final CharSequence beforeCursor = getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE,
0);
- final StringBuilder internal = new StringBuilder().append(mCommittedTextBeforeComposingText)
+ final StringBuilder internal = new StringBuilder(mCommittedTextBeforeComposingText)
.append(mComposingText);
if (null == et || null == beforeCursor) return;
final int actualLength = Math.min(beforeCursor.length(), internal.length());
@@ -103,16 +108,16 @@ public final class RichInputConnection {
final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString()
: beforeCursor.subSequence(beforeCursor.length() - actualLength,
beforeCursor.length()).toString();
- if (et.selectionStart != mExpectedCursorPosition
+ if (et.selectionStart != mExpectedSelStart
|| !(reference.equals(internal.toString()))) {
- final String context = "Expected cursor position = " + mExpectedCursorPosition
- + "\nActual cursor position = " + et.selectionStart
+ final String context = "Expected selection start = " + mExpectedSelStart
+ + "\nActual selection start = " + et.selectionStart
+ "\nExpected text = " + internal.length() + " " + internal
+ "\nActual text = " + reference.length() + " " + reference;
((LatinIME)mParent).debugDumpStateAndCrashWithException(context);
} else {
Log.e(TAG, DebugLogUtils.getStackTrace(2));
- Log.e(TAG, "Exp <> Actual : " + mExpectedCursorPosition + " <> " + et.selectionStart);
+ Log.e(TAG, "Exp <> Actual : " + mExpectedSelStart + " <> " + et.selectionStart);
}
}
@@ -150,16 +155,38 @@ public final class RichInputConnection {
* data, so we empty the cache and note that we don't know the new cursor position, and we
* return false so that the caller knows about this and can retry later.
*
- * @param newCursorPosition The new position of the cursor, as received from the system.
- * @param shouldFinishComposition Whether we should finish the composition in progress.
+ * @param newSelStart the new position of the selection start, as received from the system.
+ * @param newSelEnd the new position of the selection end, as received from the system.
+ * @param shouldFinishComposition whether we should finish the composition in progress.
* @return true if we were able to connect to the editor successfully, false otherwise. When
* this method returns false, the caches could not be correctly refreshed so they were only
* reset: the caller should try again later to return to normal operation.
*/
- public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newCursorPosition,
- final boolean shouldFinishComposition) {
- mExpectedCursorPosition = newCursorPosition;
+ public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newSelStart,
+ final int newSelEnd, final boolean shouldFinishComposition) {
+ mExpectedSelStart = newSelStart;
+ mExpectedSelEnd = newSelEnd;
mComposingText.setLength(0);
+ final boolean didReloadTextSuccessfully = reloadTextCache();
+ if (!didReloadTextSuccessfully) {
+ Log.d(TAG, "Will try to retrieve text later.");
+ return false;
+ }
+ if (null != mIC && shouldFinishComposition) {
+ mIC.finishComposingText();
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.richInputConnection_finishComposingText();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Reload the cached text from the InputConnection.
+ *
+ * @return true if successful
+ */
+ private boolean reloadTextCache() {
mCommittedTextBeforeComposingText.setLength(0);
mIC = mParent.getCurrentInputConnection();
// Call upon the inputconnection directly since our own method is using the cache, and
@@ -169,27 +196,12 @@ public final class RichInputConnection {
if (null == textBeforeCursor) {
// For some reason the app thinks we are not connected to it. This looks like a
// framework bug... Fall back to ground state and return false.
- mExpectedCursorPosition = INVALID_CURSOR_POSITION;
- Log.e(TAG, "Unable to connect to the editor to retrieve text... will retry later");
+ mExpectedSelStart = INVALID_CURSOR_POSITION;
+ mExpectedSelEnd = INVALID_CURSOR_POSITION;
+ Log.e(TAG, "Unable to connect to the editor to retrieve text.");
return false;
}
mCommittedTextBeforeComposingText.append(textBeforeCursor);
- final int lengthOfTextBeforeCursor = textBeforeCursor.length();
- if (lengthOfTextBeforeCursor > newCursorPosition
- || (lengthOfTextBeforeCursor < Constants.EDITOR_CONTENTS_CACHE_SIZE
- && newCursorPosition < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
- // newCursorPosition may be lying -- when rotating the device (probably a framework
- // bug). If we have less chars than we asked for, then we know how many chars we have,
- // and if we got more than newCursorPosition says, then we know it was lying. In both
- // cases the length is more reliable
- mExpectedCursorPosition = lengthOfTextBeforeCursor;
- }
- if (null != mIC && shouldFinishComposition) {
- mIC.finishComposingText();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_finishComposingText();
- }
- }
return true;
}
@@ -204,6 +216,9 @@ public final class RichInputConnection {
public void finishComposingText() {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
+ // TODO: this is not correct! The cursor is not necessarily after the composing text.
+ // In the practice right now this is only called when input ends so it will be reset so
+ // it works, but it's wrong and should be fixed.
mCommittedTextBeforeComposingText.append(mComposingText);
mComposingText.setLength(0);
if (null != mIC) {
@@ -218,7 +233,11 @@ public final class RichInputConnection {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
mCommittedTextBeforeComposingText.append(text);
- mExpectedCursorPosition += text.length() - mComposingText.length();
+ // TODO: the following is exceedingly error-prone. Right now when the cursor is in the
+ // middle of the composing word mComposingText only holds the part of the composing text
+ // that is before the cursor, so this actually works, but it's terribly confusing. Fix this.
+ mExpectedSelStart += text.length() - mComposingText.length();
+ mExpectedSelEnd = mExpectedSelStart;
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitText(text, i);
@@ -226,12 +245,11 @@ public final class RichInputConnection {
}
public CharSequence getSelectedText(final int flags) {
- if (null == mIC) return null;
- return mIC.getSelectedText(flags);
+ return (null == mIC) ? null : mIC.getSelectedText(flags);
}
public boolean canDeleteCharacters() {
- return mExpectedCursorPosition > 0;
+ return mExpectedSelStart > 0;
}
/**
@@ -245,12 +263,12 @@ public final class RichInputConnection {
* American English, it's just the most common set of rules for English).
*
* @param inputType a mask of the caps modes to test for.
- * @param settingsValues the values of the settings to use for locale and separators.
+ * @param spacingAndPunctuations the values of the settings to use for locale and separators.
* @param hasSpaceBefore if we should consider there should be a space after the string.
* @return the caps modes that should be on as a set of bits
*/
- public int getCursorCapsMode(final int inputType, final SettingsValues settingsValues,
- final boolean hasSpaceBefore) {
+ public int getCursorCapsMode(final int inputType,
+ final SpacingAndPunctuations spacingAndPunctuations, final boolean hasSpaceBefore) {
mIC = mParent.getCurrentInputConnection();
if (null == mIC) return Constants.TextUtils.CAP_MODE_OFF;
if (!TextUtils.isEmpty(mComposingText)) {
@@ -268,23 +286,22 @@ public final class RichInputConnection {
// heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so.
// getCapsMode should be updated to be able to return a "not enough info" result so that
// we can get more context only when needed.
- if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) {
- final CharSequence textBeforeCursor = getTextBeforeCursor(
- Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
- if (!TextUtils.isEmpty(textBeforeCursor)) {
- mCommittedTextBeforeComposingText.append(textBeforeCursor);
+ if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedSelStart) {
+ if (!reloadTextCache()) {
+ Log.w(TAG, "Unable to connect to the editor. "
+ + "Setting caps mode without knowing text.");
}
}
// This never calls InputConnection#getCapsMode - in fact, it's a static method that
// never blocks or initiates IPC.
return CapsModeUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType,
- settingsValues, hasSpaceBefore);
+ spacingAndPunctuations, hasSpaceBefore);
}
public int getCodePointBeforeCursor() {
- if (mCommittedTextBeforeComposingText.length() < 1) return Constants.NOT_A_CODE;
- return Character.codePointBefore(mCommittedTextBeforeComposingText,
- mCommittedTextBeforeComposingText.length());
+ final int length = mCommittedTextBeforeComposingText.length();
+ if (length < 1) return Constants.NOT_A_CODE;
+ return Character.codePointBefore(mCommittedTextBeforeComposingText, length);
}
public CharSequence getTextBeforeCursor(final int n, final int flags) {
@@ -295,8 +312,8 @@ public final class RichInputConnection {
// However, if we don't have an expected cursor position, then we should always
// go fetch the cache again (as it happens, INVALID_CURSOR_POSITION < 0, so we need to
// test for this explicitly)
- if (INVALID_CURSOR_POSITION != mExpectedCursorPosition
- && (cachedLength >= n || cachedLength >= mExpectedCursorPosition)) {
+ if (INVALID_CURSOR_POSITION != mExpectedSelStart
+ && (cachedLength >= n || cachedLength >= mExpectedSelStart)) {
final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText);
// We call #toString() here to create a temporary object.
// In some situations, this method is called on a worker thread, and it's possible
@@ -312,20 +329,19 @@ public final class RichInputConnection {
return s;
}
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) {
- return mIC.getTextBeforeCursor(n, flags);
- }
- return null;
+ return (null == mIC) ? null : mIC.getTextBeforeCursor(n, flags);
}
public CharSequence getTextAfterCursor(final int n, final int flags) {
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) return mIC.getTextAfterCursor(n, flags);
- return null;
+ return (null == mIC) ? null : mIC.getTextAfterCursor(n, flags);
}
public void deleteSurroundingText(final int beforeLength, final int afterLength) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
+ // TODO: the following is incorrect if the cursor is not immediately after the composition.
+ // Right now we never come here in this case because we reset the composing state before we
+ // come here in this case, but we need to fix this.
final int remainingChars = mComposingText.length() - beforeLength;
if (remainingChars >= 0) {
mComposingText.setLength(remainingChars);
@@ -336,10 +352,14 @@ public final class RichInputConnection {
+ remainingChars, 0);
mCommittedTextBeforeComposingText.setLength(len);
}
- if (mExpectedCursorPosition > beforeLength) {
- mExpectedCursorPosition -= beforeLength;
+ if (mExpectedSelStart > beforeLength) {
+ mExpectedSelStart -= beforeLength;
+ mExpectedSelEnd -= beforeLength;
} else {
- mExpectedCursorPosition = 0;
+ // There are fewer characters before the cursor in the buffer than we are being asked to
+ // delete. Only delete what is there, and update the end with the amount deleted.
+ mExpectedSelEnd -= mExpectedSelStart;
+ mExpectedSelStart = 0;
}
if (null != mIC) {
mIC.deleteSurroundingText(beforeLength, afterLength);
@@ -373,7 +393,8 @@ public final class RichInputConnection {
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_ENTER:
mCommittedTextBeforeComposingText.append("\n");
- mExpectedCursorPosition += 1;
+ mExpectedSelStart += 1;
+ mExpectedSelEnd = mExpectedSelStart;
break;
case KeyEvent.KEYCODE_DEL:
if (0 == mComposingText.length()) {
@@ -385,18 +406,24 @@ public final class RichInputConnection {
} else {
mComposingText.delete(mComposingText.length() - 1, mComposingText.length());
}
- if (mExpectedCursorPosition > 0) mExpectedCursorPosition -= 1;
+ if (mExpectedSelStart > 0 && mExpectedSelStart == mExpectedSelEnd) {
+ // TODO: Handle surrogate pairs.
+ mExpectedSelStart -= 1;
+ }
+ mExpectedSelEnd = mExpectedSelStart;
break;
case KeyEvent.KEYCODE_UNKNOWN:
if (null != keyEvent.getCharacters()) {
mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
- mExpectedCursorPosition += keyEvent.getCharacters().length();
+ mExpectedSelStart += keyEvent.getCharacters().length();
+ mExpectedSelEnd = mExpectedSelStart;
}
break;
default:
- final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1);
+ final String text = StringUtils.newSingleCodePointString(keyEvent.getUnicodeChar());
mCommittedTextBeforeComposingText.append(text);
- mExpectedCursorPosition += text.length();
+ mExpectedSelStart += text.length();
+ mExpectedSelEnd = mExpectedSelStart;
break;
}
}
@@ -415,8 +442,12 @@ public final class RichInputConnection {
getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE + (end - start), 0);
mCommittedTextBeforeComposingText.setLength(0);
if (!TextUtils.isEmpty(textBeforeCursor)) {
+ // The cursor is not necessarily at the end of the composing text, but we have its
+ // position in mExpectedSelStart and mExpectedSelEnd. In this case we want the start
+ // of the text, so we should use mExpectedSelStart. In other words, the composing
+ // text starts (mExpectedSelStart - start) characters before the end of textBeforeCursor
final int indexOfStartOfComposingText =
- Math.max(textBeforeCursor.length() - (end - start), 0);
+ Math.max(textBeforeCursor.length() - (mExpectedSelStart - start), 0);
mComposingText.append(textBeforeCursor.subSequence(indexOfStartOfComposingText,
textBeforeCursor.length()));
mCommittedTextBeforeComposingText.append(
@@ -430,10 +461,12 @@ public final class RichInputConnection {
public void setComposingText(final CharSequence text, final int newCursorPosition) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
- mExpectedCursorPosition += text.length() - mComposingText.length();
+ mExpectedSelStart += text.length() - mComposingText.length();
+ mExpectedSelEnd = mExpectedSelStart;
mComposingText.setLength(0);
mComposingText.append(text);
- // TODO: support values of i != 1. At this time, this is never called with i != 1.
+ // TODO: support values of newCursorPosition != 1. At this time, this is never called with
+ // newCursorPosition != 1.
if (null != mIC) {
mIC.setComposingText(text, newCursorPosition);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -443,19 +476,35 @@ public final class RichInputConnection {
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
- public void setSelection(final int start, final int end) {
+ /**
+ * Set the selection of the text editor.
+ *
+ * Calls through to {@link InputConnection#setSelection(int, int)}.
+ *
+ * @param start the character index where the selection should start.
+ * @param end the character index where the selection should end.
+ * @return Returns true on success, false on failure: either the input connection is no longer
+ * valid when setting the selection or when retrieving the text cache at that point, or
+ * invalid arguments were passed.
+ */
+ public boolean setSelection(final int start, final int end) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
+ if (start < 0 || end < 0) {
+ return false;
+ }
+ mExpectedSelStart = start;
+ mExpectedSelEnd = end;
if (null != mIC) {
- mIC.setSelection(start, end);
+ final boolean isIcValid = mIC.setSelection(start, end);
+ if (!isIcValid) {
+ return false;
+ }
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_setSelection(start, end);
}
}
- mExpectedCursorPosition = start;
- mCommittedTextBeforeComposingText.setLength(0);
- mCommittedTextBeforeComposingText.append(
- getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0));
+ return reloadTextCache();
}
public void commitCorrection(final CorrectionInfo correctionInfo) {
@@ -476,7 +525,8 @@ public final class RichInputConnection {
// text should never be null, but just in case, it's better to insert nothing than to crash
if (null == text) text = "";
mCommittedTextBeforeComposingText.append(text);
- mExpectedCursorPosition += text.length() - mComposingText.length();
+ mExpectedSelStart += text.length() - mComposingText.length();
+ mExpectedSelEnd = mExpectedSelStart;
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitCompletion(completionInfo);
@@ -488,7 +538,8 @@ public final class RichInputConnection {
}
@SuppressWarnings("unused")
- public String getNthPreviousWord(final String sentenceSeperators, final int n) {
+ public String getNthPreviousWord(final SpacingAndPunctuations spacingAndPunctuations,
+ final int n) {
mIC = mParent.getCurrentInputConnection();
if (null == mIC) return null;
final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
@@ -496,6 +547,9 @@ public final class RichInputConnection {
final int checkLength = LOOKBACK_CHARACTER_NUM - 1;
final String reference = prev.length() <= checkLength ? prev.toString()
: prev.subSequence(prev.length() - checkLength, prev.length()).toString();
+ // TODO: right now the following works because mComposingText holds the part of the
+ // composing text that is before the cursor, but this is very confusing. We should
+ // fix it.
final StringBuilder internal = new StringBuilder()
.append(mCommittedTextBeforeComposingText).append(mComposingText);
if (internal.length() > checkLength) {
@@ -507,11 +561,11 @@ public final class RichInputConnection {
}
}
}
- return getNthPreviousWord(prev, sentenceSeperators, n);
+ return getNthPreviousWord(prev, spacingAndPunctuations, n);
}
- private static boolean isSeparator(int code, String sep) {
- return sep.indexOf(code) != -1;
+ private static boolean isSeparator(final int code, final int[] sortedSeparators) {
+ return Arrays.binarySearch(sortedSeparators, code) >= 0;
}
// Get the nth word before cursor. n = 1 retrieves the word immediately before the cursor,
@@ -531,7 +585,7 @@ public final class RichInputConnection {
// (n = 2) "abc |" -> null
// (n = 2) "abc. def|" -> null
public static String getNthPreviousWord(final CharSequence prev,
- final String sentenceSeperators, final int n) {
+ final SpacingAndPunctuations spacingAndPunctuations, final int n) {
if (prev == null) return null;
final String[] w = spaceRegex.split(prev);
@@ -543,35 +597,36 @@ public final class RichInputConnection {
// If ends in a separator, return null
final char lastChar = nthPrevWord.charAt(length - 1);
- if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
+ if (spacingAndPunctuations.isWordSeparator(lastChar)
+ || spacingAndPunctuations.isWordConnector(lastChar)) return null;
return nthPrevWord;
}
/**
- * @param separators characters which may separate words
+ * @param sortedSeparators a sorted array of code points which may separate words
* @return the word that surrounds the cursor, including up to one trailing
* separator. For example, if the field contains "he|llo world", where |
* represents the cursor, then "hello " will be returned.
*/
- public CharSequence getWordAtCursor(String separators) {
+ public CharSequence getWordAtCursor(final int[] sortedSeparators) {
// getWordRangeAtCursor returns null if the connection is null
- TextRange r = getWordRangeAtCursor(separators, 0);
+ final TextRange r = getWordRangeAtCursor(sortedSeparators, 0);
return (r == null) ? null : r.mWord;
}
/**
* Returns the text surrounding the cursor.
*
- * @param sep a string of characters that split words.
+ * @param sortedSeparators a sorted array of code points that split words.
* @param additionalPrecedingWordsCount the number of words before the current word that should
* be included in the returned range
* @return a range containing the text surrounding the cursor
*/
- public TextRange getWordRangeAtCursor(final String sep,
+ public TextRange getWordRangeAtCursor(final int[] sortedSeparators,
final int additionalPrecedingWordsCount) {
mIC = mParent.getCurrentInputConnection();
- if (mIC == null || sep == null) {
+ if (mIC == null) {
return null;
}
final CharSequence before = mIC.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE,
@@ -590,7 +645,7 @@ public final class RichInputConnection {
while (true) { // see comments below for why this is guaranteed to halt
while (startIndexInBefore > 0) {
final int codePoint = Character.codePointBefore(before, startIndexInBefore);
- if (isStoppingAtWhitespace == isSeparator(codePoint, sep)) {
+ if (isStoppingAtWhitespace == isSeparator(codePoint, sortedSeparators)) {
break; // inner loop
}
--startIndexInBefore;
@@ -611,7 +666,7 @@ public final class RichInputConnection {
int endIndexInAfter = -1;
while (++endIndexInAfter < after.length()) {
final int codePoint = Character.codePointAt(after, endIndexInAfter);
- if (isSeparator(codePoint, sep)) {
+ if (isSeparator(codePoint, sortedSeparators)) {
break;
}
if (Character.isSupplementaryCodePoint(codePoint)) {
@@ -619,27 +674,50 @@ public final class RichInputConnection {
}
}
+ final boolean hasUrlSpans =
+ SpannableStringUtils.hasUrlSpans(before, startIndexInBefore, before.length())
+ || SpannableStringUtils.hasUrlSpans(after, 0, endIndexInAfter);
// We don't use TextUtils#concat because it copies all spans without respect to their
// nature. If the text includes a PARAGRAPH span and it has been split, then
// TextUtils#concat will crash when it tries to concat both sides of it.
return new TextRange(
SpannableStringUtils.concatWithNonParagraphSuggestionSpansOnly(before, after),
- startIndexInBefore, before.length() + endIndexInAfter, before.length());
+ startIndexInBefore, before.length() + endIndexInAfter, before.length(),
+ hasUrlSpans);
}
- public boolean isCursorTouchingWord(final SettingsValues settingsValues) {
- final int codePointBeforeCursor = getCodePointBeforeCursor();
- if (Constants.NOT_A_CODE != codePointBeforeCursor
- && !settingsValues.isWordSeparator(codePointBeforeCursor)
- && !settingsValues.isWordConnector(codePointBeforeCursor)) {
+ public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations) {
+ if (isCursorFollowedByWordCharacter(spacingAndPunctuations)) {
+ // If what's after the cursor is a word character, then we're touching a word.
return true;
}
+ final String textBeforeCursor = mCommittedTextBeforeComposingText.toString();
+ int indexOfCodePointInJavaChars = textBeforeCursor.length();
+ int consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE
+ : textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars);
+ // Search for the first non word-connector char
+ if (spacingAndPunctuations.isWordConnector(consideredCodePoint)) {
+ indexOfCodePointInJavaChars -= Character.charCount(consideredCodePoint);
+ consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE
+ : textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars);
+ }
+ return !(Constants.NOT_A_CODE == consideredCodePoint
+ || spacingAndPunctuations.isWordSeparator(consideredCodePoint)
+ || spacingAndPunctuations.isWordConnector(consideredCodePoint));
+ }
+
+ public boolean isCursorFollowedByWordCharacter(
+ final SpacingAndPunctuations spacingAndPunctuations) {
final CharSequence after = getTextAfterCursor(1, 0);
- if (!TextUtils.isEmpty(after) && !settingsValues.isWordSeparator(after.charAt(0))
- && !settingsValues.isWordConnector(after.charAt(0))) {
- return true;
+ if (TextUtils.isEmpty(after)) {
+ return false;
+ }
+ final int codePointAfterCursor = Character.codePointAt(after, 0);
+ if (spacingAndPunctuations.isWordSeparator(codePointAfterCursor)
+ || spacingAndPunctuations.isWordConnector(codePointAfterCursor)) {
+ return false;
}
- return false;
+ return true;
}
public void removeTrailingSpace() {
@@ -655,45 +733,6 @@ public final class RichInputConnection {
return TextUtils.equals(text, beforeText);
}
- /* (non-javadoc)
- * Returns the word before the cursor if the cursor is at the end of a word, null otherwise
- */
- public CharSequence getWordBeforeCursorIfAtEndOfWord(final SettingsValues settings) {
- // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace,
- // separator or end of line/text)
- // Example: "test|"<EOL> "te|st" get rejected here
- final CharSequence textAfterCursor = getTextAfterCursor(1, 0);
- if (!TextUtils.isEmpty(textAfterCursor)
- && !settings.isWordSeparator(textAfterCursor.charAt(0))) return null;
-
- // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe)
- // Example: " -|" gets rejected here but "e-|" and "e|" are okay
- CharSequence word = getWordAtCursor(settings.mWordSeparators);
- // We don't suggest on leading single quotes, so we have to remove them from the word if
- // it starts with single quotes.
- while (!TextUtils.isEmpty(word) && Constants.CODE_SINGLE_QUOTE == word.charAt(0)) {
- word = word.subSequence(1, word.length());
- }
- if (TextUtils.isEmpty(word)) return null;
- // Find the last code point of the string
- final int lastCodePoint = Character.codePointBefore(word, word.length());
- // If for some reason the text field contains non-unicode binary data, or if the
- // charsequence is exactly one char long and the contents is a low surrogate, return null.
- if (!Character.isDefined(lastCodePoint)) return null;
- // Bail out if the cursor is not at the end of a word (cursor must be preceded by
- // non-whitespace, non-separator, non-start-of-text)
- // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here.
- if (settings.isWordSeparator(lastCodePoint)) return null;
- final char firstChar = word.charAt(0); // we just tested that word is not empty
- if (word.length() == 1 && !Character.isLetter(firstChar)) return null;
-
- // We don't restart suggestion if the first character is not a letter, because we don't
- // start composing when the first character is not a letter.
- if (!Character.isLetter(firstChar)) return null;
-
- return word;
- }
-
public boolean revertDoubleSpacePeriod() {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
// Here we test whether we indeed have a period and a space before us. This should not
@@ -758,20 +797,30 @@ public final class RichInputConnection {
* this update and not the ones in-between. This is almost impossible to achieve even trying
* very very hard.
*
- * @param oldSelStart The value of the old cursor position in the update.
- * @param newSelStart The value of the new cursor position in the update.
+ * @param oldSelStart The value of the old selection in the update.
+ * @param newSelStart The value of the new selection in the update.
+ * @param oldSelEnd The value of the old selection end in the update.
+ * @param newSelEnd The value of the new selection end in the update.
* @return whether this is a belated expected update or not.
*/
- public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart) {
- // If this is an update that arrives at our expected position, it's a belated update.
- if (newSelStart == mExpectedCursorPosition) return true;
- // If this is an update that moves the cursor from our expected position, it must be
- // an explicit move.
- if (oldSelStart == mExpectedCursorPosition) return false;
- // The following returns true if newSelStart is between oldSelStart and
- // mCurrentCursorPosition. We assume that if the updated position is between the old
- // position and the expected position, then it must be a belated update.
- return (newSelStart - oldSelStart) * (mExpectedCursorPosition - newSelStart) >= 0;
+ public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart,
+ final int oldSelEnd, final int newSelEnd) {
+ // This update is "belated" if we are expecting it. That is, mExpectedSelStart and
+ // mExpectedSelEnd match the new values that the TextView is updating TO.
+ if (mExpectedSelStart == newSelStart && mExpectedSelEnd == newSelEnd) return true;
+ // This update is not belated if mExpectedSelStart and mExpectedSelEnd match the old
+ // values, and one of newSelStart or newSelEnd is updated to a different value. In this
+ // case, it is likely that something other than the IME has moved the selection endpoint
+ // to the new value.
+ if (mExpectedSelStart == oldSelStart && mExpectedSelEnd == oldSelEnd
+ && (oldSelStart != newSelStart || oldSelEnd != newSelEnd)) return false;
+ // If neither of the above two cases hold, then the system may be having trouble keeping up
+ // with updates. If 1) the selection is a cursor, 2) newSelStart is between oldSelStart
+ // and mExpectedSelStart, and 3) newSelEnd is between oldSelEnd and mExpectedSelEnd, then
+ // assume a belated update.
+ return (newSelStart == newSelEnd)
+ && (newSelStart - oldSelStart) * (mExpectedSelStart - newSelStart) >= 0
+ && (newSelEnd - oldSelEnd) * (mExpectedSelEnd - newSelEnd) >= 0;
}
/**
@@ -784,4 +833,65 @@ public final class RichInputConnection {
public boolean textBeforeCursorLooksLikeURL() {
return StringUtils.lastPartLooksLikeURL(mCommittedTextBeforeComposingText);
}
+
+ /**
+ * Looks at the text just before the cursor to find out if we are inside a double quote.
+ *
+ * As with #textBeforeCursorLooksLikeURL, this is dependent on how much text we have cached.
+ * However this won't be a concrete problem in most situations, as the cache is almost always
+ * long enough for this use.
+ */
+ public boolean isInsideDoubleQuoteOrAfterDigit() {
+ return StringUtils.isInsideDoubleQuoteOrAfterDigit(mCommittedTextBeforeComposingText);
+ }
+
+ /**
+ * 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
+ * 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
+ * than it really is.
+ */
+ public void tryFixLyingCursorPosition() {
+ final CharSequence textBeforeCursor = getTextBeforeCursor(
+ Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
+ if (null == textBeforeCursor) {
+ mExpectedSelStart = mExpectedSelEnd = Constants.NOT_A_CURSOR_POSITION;
+ } else {
+ final int textLength = textBeforeCursor.length();
+ if (textLength < Constants.EDITOR_CONTENTS_CACHE_SIZE
+ && (textLength > mExpectedSelStart
+ || mExpectedSelStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
+ // It should not be possible to have only one of those variables be
+ // NOT_A_CURSOR_POSITION, so if they are equal, either the selection is zero-sized
+ // (simple cursor, no selection) or there is no cursor/we don't know its pos
+ final boolean wasEqual = mExpectedSelStart == mExpectedSelEnd;
+ mExpectedSelStart = textLength;
+ // We can't figure out the value of mLastSelectionEnd :(
+ // But at least if it's smaller than mLastSelectionStart something is wrong,
+ // and if they used to be equal we also don't want to make it look like there is a
+ // selection.
+ if (wasEqual || mExpectedSelStart > mExpectedSelEnd) {
+ mExpectedSelEnd = mExpectedSelStart;
+ }
+ }
+ }
+ }
+
+ public int getExpectedSelectionStart() {
+ return mExpectedSelStart;
+ }
+
+ public int getExpectedSelectionEnd() {
+ return mExpectedSelEnd;
+ }
+
+ /**
+ * @return whether there is a selection currently active.
+ */
+ public boolean hasSelection() {
+ return mExpectedSelEnd != mExpectedSelStart;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 6b6bbf3a7..630a03670 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -50,7 +50,7 @@ public final class RichInputMethodManager {
private static final RichInputMethodManager sInstance = new RichInputMethodManager();
private InputMethodManagerCompatWrapper mImmWrapper;
- private InputMethodInfo mInputMethodInfoOfThisIme;
+ private InputMethodInfoCache mInputMethodInfoCache;
final HashMap<InputMethodInfo, List<InputMethodSubtype>>
mSubtypeListCacheWithImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
final HashMap<InputMethodInfo, List<InputMethodSubtype>>
@@ -83,7 +83,8 @@ public final class RichInputMethodManager {
return;
}
mImmWrapper = new InputMethodManagerCompatWrapper(context);
- mInputMethodInfoOfThisIme = getInputMethodInfoOfThisIme(context);
+ mInputMethodInfoCache = new InputMethodInfoCache(
+ mImmWrapper.mImm, context.getPackageName());
// Initialize additional subtypes.
SubtypeLocaleUtils.init(context);
@@ -99,20 +100,10 @@ public final class RichInputMethodManager {
return mImmWrapper.mImm;
}
- private InputMethodInfo getInputMethodInfoOfThisIme(final Context context) {
- final String packageName = context.getPackageName();
- for (final InputMethodInfo imi : mImmWrapper.mImm.getInputMethodList()) {
- if (imi.getPackageName().equals(packageName)) {
- return imi;
- }
- }
- throw new RuntimeException("Input method id for " + packageName + " not found.");
- }
-
public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList(
boolean allowsImplicitlySelectedSubtypes) {
- return getEnabledInputMethodSubtypeList(mInputMethodInfoOfThisIme,
- allowsImplicitlySelectedSubtypes);
+ return getEnabledInputMethodSubtypeList(
+ getInputMethodInfoOfThisIme(), allowsImplicitlySelectedSubtypes);
}
public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
@@ -153,10 +144,10 @@ public final class RichInputMethodManager {
private boolean switchToNextInputMethodAndSubtype(final IBinder token) {
final InputMethodManager imm = mImmWrapper.mImm;
final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
- final int currentIndex = getImiIndexInList(mInputMethodInfoOfThisIme, enabledImis);
+ final int currentIndex = getImiIndexInList(getInputMethodInfoOfThisIme(), enabledImis);
if (currentIndex == INDEX_NOT_FOUND) {
Log.w(TAG, "Can't find current IME in enabled IMEs: IME package="
- + mInputMethodInfoOfThisIme.getPackageName());
+ + getInputMethodInfoOfThisIme().getPackageName());
return false;
}
final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis);
@@ -213,16 +204,45 @@ public final class RichInputMethodManager {
return true;
}
+ private static class InputMethodInfoCache {
+ private final InputMethodManager mImm;
+ private final String mImePackageName;
+
+ private InputMethodInfo mCachedValue;
+
+ public InputMethodInfoCache(final InputMethodManager imm, final String imePackageName) {
+ mImm = imm;
+ mImePackageName = imePackageName;
+ }
+
+ public synchronized InputMethodInfo get() {
+ if (mCachedValue != null) {
+ return mCachedValue;
+ }
+ for (final InputMethodInfo imi : mImm.getInputMethodList()) {
+ if (imi.getPackageName().equals(mImePackageName)) {
+ mCachedValue = imi;
+ return imi;
+ }
+ }
+ throw new RuntimeException("Input method id for " + mImePackageName + " not found.");
+ }
+
+ public synchronized void clear() {
+ mCachedValue = null;
+ }
+ }
+
public InputMethodInfo getInputMethodInfoOfThisIme() {
- return mInputMethodInfoOfThisIme;
+ return mInputMethodInfoCache.get();
}
public String getInputMethodIdOfThisIme() {
- return mInputMethodInfoOfThisIme.getId();
+ return getInputMethodInfoOfThisIme().getId();
}
public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) {
- return checkIfSubtypeBelongsToImeAndEnabled(mInputMethodInfoOfThisIme, subtype);
+ return checkIfSubtypeBelongsToImeAndEnabled(getInputMethodInfoOfThisIme(), subtype);
}
public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
@@ -258,7 +278,7 @@ public final class RichInputMethodManager {
}
public boolean checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype) {
- return getSubtypeIndexInIme(subtype, mInputMethodInfoOfThisIme) != INDEX_NOT_FOUND;
+ return getSubtypeIndexInIme(subtype, getInputMethodInfoOfThisIme()) != INDEX_NOT_FOUND;
}
private static int getSubtypeIndexInIme(final InputMethodSubtype subtype,
@@ -286,7 +306,8 @@ public final class RichInputMethodManager {
public boolean hasMultipleEnabledSubtypesInThisIme(
final boolean shouldIncludeAuxiliarySubtypes) {
- final List<InputMethodInfo> imiList = Collections.singletonList(mInputMethodInfoOfThisIme);
+ final List<InputMethodInfo> imiList = Collections.singletonList(
+ getInputMethodInfoOfThisIme());
return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
}
@@ -340,7 +361,7 @@ public final class RichInputMethodManager {
public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString,
final String keyboardLayoutSetName) {
- final InputMethodInfo myImi = mInputMethodInfoOfThisIme;
+ final InputMethodInfo myImi = getInputMethodInfoOfThisIme();
final int count = myImi.getSubtypeCount();
for (int i = 0; i < count; i++) {
final InputMethodSubtype subtype = myImi.getSubtypeAt(i);
@@ -355,13 +376,14 @@ public final class RichInputMethodManager {
public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) {
mImmWrapper.mImm.setInputMethodAndSubtype(
- token, mInputMethodInfoOfThisIme.getId(), subtype);
+ token, getInputMethodIdOfThisIme(), subtype);
}
public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) {
mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
- mInputMethodInfoOfThisIme.getId(), subtypes);
- // Clear the cache so that we go read the subtypes again next time.
+ getInputMethodIdOfThisIme(), subtypes);
+ // Clear the cache so that we go read the {@link InputMethodInfo} of this IME and list of
+ // subtypes again next time.
clearSubtypeCaches();
}
@@ -382,5 +404,6 @@ public final class RichInputMethodManager {
public void clearSubtypeCaches() {
mSubtypeListCacheWithImplicitlySelectedSubtypes.clear();
mSubtypeListCacheWithoutImplicitlySelectedSubtypes.clear();
+ mInputMethodInfoCache.clear();
}
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index cd9c89f04..021133945 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -32,12 +32,17 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
+import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
public final class SubtypeSwitcher {
private static boolean DBG = LatinImeLogger.sDBG;
@@ -49,47 +54,42 @@ public final class SubtypeSwitcher {
private /* final */ Resources mResources;
private /* final */ ConnectivityManager mConnectivityManager;
- private final NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage();
+ private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper =
+ new LanguageOnSpacebarHelper();
private InputMethodInfo mShortcutInputMethodInfo;
private InputMethodSubtype mShortcutSubtype;
private InputMethodSubtype mNoLanguageSubtype;
private InputMethodSubtype mEmojiSubtype;
private boolean mIsNetworkConnected;
+ private static final String KEYBOARD_MODE = "keyboard";
// Dummy no language QWERTY subtype. See {@link R.xml.method}.
- private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = new InputMethodSubtype(
- R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
- SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
- + SubtypeLocaleUtils.QWERTY
- + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
- + ",EnabledWhenDefaultIsNotAsciiCapable,"
- + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
- false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
+ private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3;
+ private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE =
+ "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY
+ + "," + 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(
+ 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);
// 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 InputMethodSubtype DUMMY_EMOJI_SUBTYPE = new InputMethodSubtype(
- R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
- SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
- + SubtypeLocaleUtils.EMOJI + ","
- + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
- false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
-
- static final class NeedsToDisplayLanguage {
- private int mEnabledSubtypeCount;
- private boolean mIsSystemLanguageSameAsInputLanguage;
-
- public boolean getValue() {
- return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage;
- }
-
- public void updateEnabledSubtypeCount(final int count) {
- mEnabledSubtypeCount = count;
- }
-
- public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
- mIsSystemLanguageSameAsInputLanguage = isSame;
- }
- }
+ 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 =
+ 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);
public static SubtypeSwitcher getInstance() {
return sInstance;
@@ -128,7 +128,7 @@ public final class SubtypeSwitcher {
public void updateParametersOnStartInputView() {
final List<InputMethodSubtype> enabledSubtypesOfThisIme =
mRichImm.getMyEnabledInputMethodSubtypeList(true);
- mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
+ mLanguageOnSpacebarHelper.updateEnabledSubtypes(enabledSubtypesOfThisIme);
updateShortcutIME();
}
@@ -177,7 +177,7 @@ public final class SubtypeSwitcher {
final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage());
final boolean implicitlyEnabled =
mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype);
- mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage(
+ mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(
sameLocale || (sameLanguage && implicitlyEnabled));
updateShortcutIME();
@@ -213,6 +213,7 @@ public final class SubtypeSwitcher {
}
public boolean isShortcutImeEnabled() {
+ updateShortcutIME();
if (mShortcutInputMethodInfo == null) {
return false;
}
@@ -224,10 +225,13 @@ public final class SubtypeSwitcher {
}
public boolean isShortcutImeReady() {
- if (mShortcutInputMethodInfo == null)
+ updateShortcutIME();
+ if (mShortcutInputMethodInfo == null) {
return false;
- if (mShortcutSubtype == null)
+ }
+ if (mShortcutSubtype == null) {
return true;
+ }
if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) {
return mIsNetworkConnected;
}
@@ -246,28 +250,53 @@ public final class SubtypeSwitcher {
// Subtype Switching functions //
//////////////////////////////////
- public boolean needsToDisplayLanguage(final Locale keyboardLocale) {
- if (keyboardLocale.toString().equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
- return true;
+ public int getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype) {
+ return mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(subtype);
+ }
+
+ public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
+ final Locale systemLocale = mResources.getConfiguration().locale;
+ final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes =
+ new HashSet<InputMethodSubtype>();
+ final InputMethodManager inputMethodManager = mRichImm.getInputMethodManager();
+ final List<InputMethodInfo> enabledInputMethodInfoList =
+ inputMethodManager.getEnabledInputMethodList();
+ for (final InputMethodInfo info : enabledInputMethodInfoList) {
+ final List<InputMethodSubtype> enabledSubtypes =
+ inputMethodManager.getEnabledInputMethodSubtypeList(
+ info, true /* allowsImplicitlySelectedSubtypes */);
+ if (enabledSubtypes.isEmpty()) {
+ // An IME with no subtypes is found.
+ return false;
+ }
+ enabledSubtypesOfEnabledImes.addAll(enabledSubtypes);
}
- if (!keyboardLocale.equals(getCurrentSubtypeLocale())) {
- return false;
+ for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) {
+ if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty()
+ && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) {
+ return false;
+ }
}
- return mNeedsToDisplayLanguage.getValue();
+ return true;
}
- private static Locale sForcedLocaleForTesting = null;
+ private static InputMethodSubtype sForcedSubtypeForTesting = null;
@UsedForTesting
- void forceLocale(final Locale locale) {
- sForcedLocaleForTesting = locale;
+ void forceSubtype(final InputMethodSubtype subtype) {
+ sForcedSubtypeForTesting = subtype;
}
public Locale getCurrentSubtypeLocale() {
- if (null != sForcedLocaleForTesting) return sForcedLocaleForTesting;
+ if (null != sForcedSubtypeForTesting) {
+ return LocaleUtils.constructLocaleFromString(sForcedSubtypeForTesting.getLocale());
+ }
return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype());
}
public InputMethodSubtype getCurrentSubtype() {
+ if (null != sForcedSubtypeForTesting) {
+ return sForcedSubtypeForTesting;
+ }
return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype());
}
@@ -279,8 +308,8 @@ public final class SubtypeSwitcher {
if (mNoLanguageSubtype != null) {
return mNoLanguageSubtype;
}
- Log.w(TAG, "Can't find no lanugage with QWERTY subtype");
- Log.w(TAG, "No input method subtype found; return dummy subtype: "
+ Log.w(TAG, "Can't find any language with QWERTY subtype");
+ Log.w(TAG, "No input method subtype found; returning dummy subtype: "
+ DUMMY_NO_LANGUAGE_SUBTYPE);
return DUMMY_NO_LANGUAGE_SUBTYPE;
}
@@ -293,8 +322,9 @@ public final class SubtypeSwitcher {
if (mEmojiSubtype != null) {
return mEmojiSubtype;
}
- Log.w(TAG, "Can't find Emoji subtype");
- Log.w(TAG, "No input method subtype found; return dummy subtype: " + DUMMY_EMOJI_SUBTYPE);
+ Log.w(TAG, "Can't find emoji subtype");
+ Log.w(TAG, "No input method subtype found; returning dummy subtype: "
+ + DUMMY_EMOJI_SUBTYPE);
return DUMMY_EMOJI_SUBTYPE;
}
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 0a4c7a55d..db0a8a81c 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -16,28 +16,20 @@
package com.android.inputmethod.latin;
-import android.content.Context;
-import android.preference.PreferenceManager;
import android.text.TextUtils;
-import android.util.Log;
-import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.event.Event;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
-import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
-import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
-import com.android.inputmethod.latin.utils.BoundedTreeSet;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.SuggestionResults;
import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashSet;
import java.util.Locale;
-import java.util.concurrent.ConcurrentHashMap;
/**
* This class loads a dictionary and provides a list of suggestions for a given sequence of
@@ -60,153 +52,17 @@ public final class Suggest {
// Close to -2**31
private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
- public static final int MAX_SUGGESTIONS = 18;
-
- public interface SuggestInitializationListener {
- public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
- }
-
private static final boolean DBG = LatinImeLogger.sDBG;
-
- private final ConcurrentHashMap<String, Dictionary> mDictionaries =
- CollectionUtils.newConcurrentHashMap();
- private HashSet<String> mOnlyDictionarySetForDebug = null;
- private Dictionary mMainDictionary;
- private ContactsBinaryDictionary mContactsDict;
- @UsedForTesting
- private boolean mIsCurrentlyWaitingForMainDictionary = false;
+ public final DictionaryFacilitatorForSuggest mDictionaryFacilitator =
+ new DictionaryFacilitatorForSuggest();
private float mAutoCorrectionThreshold;
- // Locale used for upper- and title-casing words
- public final Locale mLocale;
-
- public Suggest(final Context context, final Locale locale,
- final SuggestInitializationListener listener) {
- initAsynchronously(context, locale, listener);
- mLocale = locale;
- // initialize a debug flag for the personalization
- if (Settings.readUseOnlyPersonalizationDictionaryForDebug(
- PreferenceManager.getDefaultSharedPreferences(context))) {
- mOnlyDictionarySetForDebug = new HashSet<String>();
- mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
- mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
- }
- }
-
- @UsedForTesting
- Suggest(final AssetFileAddress[] dictionaryList, final Locale locale) {
- final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionaryList,
- false /* useFullEditDistance */, locale);
- mLocale = locale;
- mMainDictionary = mainDict;
- addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, mainDict);
- }
-
- private void initAsynchronously(final Context context, final Locale locale,
- final SuggestInitializationListener listener) {
- resetMainDict(context, locale, listener);
- }
-
- private void addOrReplaceDictionaryInternal(final String key, final Dictionary dict) {
- if (mOnlyDictionarySetForDebug != null && !mOnlyDictionarySetForDebug.contains(key)) {
- Log.w(TAG, "Ignore add " + key + " dictionary for debug.");
- return;
- }
- addOrReplaceDictionary(mDictionaries, key, dict);
- }
-
- private static void addOrReplaceDictionary(
- final ConcurrentHashMap<String, Dictionary> dictionaries,
- final String key, final Dictionary dict) {
- final Dictionary oldDict = (dict == null)
- ? dictionaries.remove(key)
- : dictionaries.put(key, dict);
- if (oldDict != null && dict != oldDict) {
- oldDict.close();
- }
- }
-
- public void resetMainDict(final Context context, final Locale locale,
- final SuggestInitializationListener listener) {
- mIsCurrentlyWaitingForMainDictionary = true;
- mMainDictionary = null;
- if (listener != null) {
- listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
- }
- new Thread("InitializeBinaryDictionary") {
- @Override
- public void run() {
- final DictionaryCollection newMainDict =
- DictionaryFactory.createMainDictionaryFromManager(context, locale);
- addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, newMainDict);
- mMainDictionary = newMainDict;
- if (listener != null) {
- listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
- }
- mIsCurrentlyWaitingForMainDictionary = false;
- }
- }.start();
- }
-
- // The main dictionary could have been loaded asynchronously. Don't cache the return value
- // of this method.
- public boolean hasMainDictionary() {
- return null != mMainDictionary && mMainDictionary.isInitialized();
- }
-
- @UsedForTesting
- public boolean isCurrentlyWaitingForMainDictionary() {
- return mIsCurrentlyWaitingForMainDictionary;
+ public Locale getLocale() {
+ return mDictionaryFacilitator.getLocale();
}
- public Dictionary getMainDictionary() {
- return mMainDictionary;
- }
-
- public ContactsBinaryDictionary getContactsDictionary() {
- return mContactsDict;
- }
-
- public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
- return mDictionaries;
- }
-
- /**
- * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
- * before the main dictionary, if set. This refers to the system-managed user dictionary.
- */
- public void setUserDictionary(final UserBinaryDictionary userDictionary) {
- addOrReplaceDictionaryInternal(Dictionary.TYPE_USER, userDictionary);
- }
-
- /**
- * Sets an optional contacts dictionary resource to be loaded. It is also possible to remove
- * the contacts dictionary by passing null to this method. In this case no contacts dictionary
- * won't be used.
- */
- public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
- mContactsDict = contactsDictionary;
- addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary);
- }
-
- public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
- addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
- }
-
- public void setPersonalizationPredictionDictionary(
- final PersonalizationPredictionDictionary personalizationPredictionDictionary) {
- addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
- personalizationPredictionDictionary);
- }
-
- public void setPersonalizationDictionary(
- final PersonalizationDictionary personalizationDictionary) {
- addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION,
- personalizationDictionary);
- }
-
- public void setAutoCorrectionThreshold(float threshold) {
+ public void setAutoCorrectionThreshold(final float threshold) {
mAutoCorrectionThreshold = threshold;
}
@@ -239,47 +95,53 @@ public final class Suggest {
final int[] additionalFeaturesOptions, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
- final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
- MAX_SUGGESTIONS);
-
final String typedWord = wordComposer.getTypedWord();
final String consideredWord = trailingSingleQuotesCount > 0
? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
: typedWord;
LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED);
- final WordComposer wordComposerForLookup;
- if (trailingSingleQuotesCount > 0) {
- wordComposerForLookup = new WordComposer(wordComposer);
- for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
- wordComposerForLookup.deleteLast();
- }
+ final ArrayList<SuggestedWordInfo> rawSuggestions;
+ if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) {
+ rawSuggestions = CollectionUtils.newArrayList();
} else {
- wordComposerForLookup = wordComposer;
- }
-
- for (final String key : mDictionaries.keySet()) {
- final Dictionary dictionary = mDictionaries.get(key);
- suggestionsSet.addAll(dictionary.getSuggestions(wordComposerForLookup,
- prevWordForBigram, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions));
+ rawSuggestions = null;
}
+ final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
+ wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords,
+ additionalFeaturesOptions, SESSION_TYPING, rawSuggestions);
+ final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
+ final boolean isAllUpperCase = wordComposer.isAllUpperCase();
+ final String firstSuggestion;
final String whitelistedWord;
- if (suggestionsSet.isEmpty()) {
- whitelistedWord = null;
- } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
- whitelistedWord = null;
+ if (suggestionResults.isEmpty()) {
+ whitelistedWord = firstSuggestion = null;
} else {
- whitelistedWord = suggestionsSet.first().mWord;
+ final SuggestedWordInfo firstSuggestedWordInfo = getTransformedSuggestedWordInfo(
+ suggestionResults.first(), suggestionResults.mLocale, isAllUpperCase,
+ isFirstCharCapitalized, trailingSingleQuotesCount);
+ firstSuggestion = firstSuggestedWordInfo.mWord;
+ if (SuggestedWordInfo.KIND_WHITELIST != firstSuggestedWordInfo.mKind) {
+ whitelistedWord = null;
+ } else {
+ whitelistedWord = firstSuggestion;
+ }
}
- // The word can be auto-corrected if it has a whitelist entry that is not itself,
- // or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
+ final boolean isPrediction = !wordComposer.isComposingWord();
+
+ // We allow auto-correction if we have a whitelisted word, or if the word is not a valid
+ // word of more than 1 char, except if the first suggestion is the same as the typed string
+ // because in this case if it's strong enough to auto-correct that will mistakenly designate
+ // the second candidate for auto-correction.
+ // TODO: stop relying on indices to find where is the auto-correction in the suggested
+ // words, and correct this test.
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
- && !whitelistedWord.equals(consideredWord))
- || (consideredWord.length() > 1 && !AutoCorrectionUtils.isValidWord(this,
- consideredWord, wordComposer.isFirstCharCapitalized()));
+ && !whitelistedWord.equals(typedWord))
+ || (consideredWord.length() > 1 && !mDictionaryFacilitator.isValidWord(
+ consideredWord, wordComposer.isFirstCharCapitalized())
+ && !typedWord.equals(firstSuggestion));
final boolean hasAutoCorrection;
// TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
@@ -287,10 +149,11 @@ public final class Suggest {
// same time, it feels wrong that the SuggestedWord object includes information about
// the current settings. It may also be useful to know, when the setting is off, whether
// the word *would* have been auto-corrected.
- if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord()
- || suggestionsSet.isEmpty() || wordComposer.hasDigits()
- || wordComposer.isMostlyCaps() || wordComposer.isResumed() || !hasMainDictionary()
- || SuggestedWordInfo.KIND_SHORTCUT == suggestionsSet.first().mKind) {
+ if (!isCorrectionEnabled || !allowsToBeAutoCorrected || isPrediction
+ || suggestionResults.isEmpty() || wordComposer.hasDigits()
+ || wordComposer.isMostlyCaps() || wordComposer.isResumed()
+ || !mDictionaryFacilitator.hasInitializedMainDictionary()
+ || SuggestedWordInfo.KIND_SHORTCUT == suggestionResults.first().mKind) {
// 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
// their language, and it will unexpectedly auto-correct. For example, if the user
@@ -302,19 +165,17 @@ public final class Suggest {
hasAutoCorrection = false;
} else {
hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
- suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold);
+ suggestionResults.first(), consideredWord, mAutoCorrectionThreshold);
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- CollectionUtils.newArrayList(suggestionsSet);
+ CollectionUtils.newArrayList(suggestionResults);
final int suggestionsCount = suggestionsContainer.size();
- final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
- final boolean isAllUpperCase = wordComposer.isAllUpperCase();
if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized,
+ wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized,
trailingSingleQuotesCount);
suggestionsContainer.set(i, transformedWordInfo);
}
@@ -342,15 +203,13 @@ public final class Suggest {
suggestionsList = suggestionsContainer;
}
- callback.onGetSuggestedWords(new SuggestedWords(suggestionsList,
+ callback.onGetSuggestedWords(new SuggestedWords(suggestionsList, rawSuggestions,
// TODO: this first argument is lying. If this is a whitelisted word which is an
// actual word, it says typedWordValid = false, which looks wrong. We should either
// rename the attribute or change the value.
- !allowsToBeAutoCorrected /* typedWordValid */,
+ !isPrediction && !allowsToBeAutoCorrected /* typedWordValid */,
hasAutoCorrection, /* willAutoCorrect */
- false /* isPunctuationSuggestions */,
- false /* isObsoleteSuggestions */,
- !wordComposer.isComposingWord() /* isPrediction */, sequenceNumber));
+ false /* isObsoleteSuggestions */, isPrediction, sequenceNumber));
}
// Retrieves suggestions for the batch input
@@ -360,23 +219,21 @@ public final class Suggest {
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
- final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
- MAX_SUGGESTIONS);
-
- // At second character typed, search the unigrams (scores being affected by bigrams)
- for (final String key : mDictionaries.keySet()) {
- final Dictionary dictionary = mDictionaries.get(key);
- suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(wordComposer,
- prevWordForBigram, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, sessionId));
+ final ArrayList<SuggestedWordInfo> rawSuggestions;
+ if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) {
+ rawSuggestions = CollectionUtils.newArrayList();
+ } else {
+ rawSuggestions = null;
}
-
- for (SuggestedWordInfo wordInfo : suggestionsSet) {
+ final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
+ wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords,
+ additionalFeaturesOptions, sessionId, rawSuggestions);
+ for (SuggestedWordInfo wordInfo : suggestionResults) {
LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType);
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- CollectionUtils.newArrayList(suggestionsSet);
+ CollectionUtils.newArrayList(suggestionResults);
final int suggestionsCount = suggestionsContainer.size();
final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
final boolean isAllUpperCase = wordComposer.isAllUpperCase();
@@ -384,7 +241,7 @@ public final class Suggest {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized,
+ wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized,
0 /* trailingSingleQuotesCount */);
suggestionsContainer.set(i, transformedWordInfo);
}
@@ -407,10 +264,9 @@ public final class Suggest {
// In the batch input mode, the most relevant suggested word should act as a "typed word"
// (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
- callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer,
+ callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer, rawSuggestions,
true /* typedWordValid */,
false /* willAutoCorrect */,
- false /* isPunctuationSuggestions */,
false /* isObsoleteSuggestions */,
false /* isPrediction */, sequenceNumber));
}
@@ -427,12 +283,13 @@ public final class Suggest {
// than i because we added the typed word to mSuggestions without touching mScores.
for (int i = 0; i < suggestionsSize - 1; ++i) {
final SuggestedWordInfo cur = suggestions.get(i + 1);
- final float normalizedScore = BinaryDictionary.calcNormalizedScore(
+ final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
typedWord, cur.toString(), cur.mScore);
final String scoreInfoString;
if (normalizedScore > 0) {
scoreInfoString = String.format(
- Locale.ROOT, "%d (%4.2f)", cur.mScore, normalizedScore);
+ Locale.ROOT, "%d (%4.2f), %s", cur.mScore, normalizedScore,
+ cur.mSourceDict.mDictType);
} else {
scoreInfoString = Integer.toString(cur.mScore);
}
@@ -442,22 +299,6 @@ public final class Suggest {
return suggestionsList;
}
- private static final class SuggestedWordInfoComparator
- implements Comparator<SuggestedWordInfo> {
- // This comparator ranks the word info with the higher frequency first. That's because
- // that's the order we want our elements in.
- @Override
- public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) {
- if (o1.mScore > o2.mScore) return -1;
- if (o1.mScore < o2.mScore) return 1;
- if (o1.mCodePointCount < o2.mCodePointCount) return -1;
- if (o1.mCodePointCount > o2.mCodePointCount) return 1;
- return o1.mWord.compareTo(o2.mWord);
- }
- }
- private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator =
- new SuggestedWordInfoComparator();
-
/* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo(
final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
@@ -481,13 +322,4 @@ public final class Suggest {
wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord,
wordInfo.mAutoCommitFirstWordConfidence);
}
-
- public void close() {
- final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
- dictionaries.addAll(mDictionaries.values());
- for (final Dictionary dictionary : dictionaries) {
- dictionary.close();
- }
- mMainDictionary = null;
- }
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 97c89dd4e..dc2c9fd0e 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -26,51 +26,71 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
-public final class SuggestedWords {
+public class SuggestedWords {
public static final int INDEX_OF_TYPED_WORD = 0;
public static final int INDEX_OF_AUTO_CORRECTION = 1;
public static final int NOT_A_SEQUENCE_NUMBER = -1;
+ // The maximum number of suggestions available.
+ public static final int MAX_SUGGESTIONS = 18;
+
private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
CollectionUtils.newArrayList(0);
public static final SuggestedWords EMPTY = new SuggestedWords(
- EMPTY_WORD_INFO_LIST, false, false, false, false, false);
+ EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, false, false, false, false);
+ public final String mTypedWord;
public final boolean mTypedWordValid;
// Note: this INCLUDES cases where the word will auto-correct to itself. A good definition
// of what this flag means would be "the top suggestion is strong enough to auto-correct",
// whether this exactly matches the user entry or not.
public final boolean mWillAutoCorrect;
- public final boolean mIsPunctuationSuggestions;
public final boolean mIsObsoleteSuggestions;
public final boolean mIsPrediction;
public final int mSequenceNumber; // Sequence number for auto-commit.
- private final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList;
+ protected final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList;
+ public final ArrayList<SuggestedWordInfo> mRawSuggestions;
public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList,
+ final ArrayList<SuggestedWordInfo> rawSuggestions,
final boolean typedWordValid,
final boolean willAutoCorrect,
- final boolean isPunctuationSuggestions,
final boolean isObsoleteSuggestions,
final boolean isPrediction) {
- this(suggestedWordInfoList, typedWordValid, willAutoCorrect, isPunctuationSuggestions,
+ this(suggestedWordInfoList, rawSuggestions, typedWordValid, willAutoCorrect,
isObsoleteSuggestions, isPrediction, NOT_A_SEQUENCE_NUMBER);
}
public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList,
+ final ArrayList<SuggestedWordInfo> rawSuggestions,
+ final boolean typedWordValid,
+ final boolean willAutoCorrect,
+ final boolean isObsoleteSuggestions,
+ final boolean isPrediction,
+ final int sequenceNumber) {
+ this(suggestedWordInfoList, rawSuggestions,
+ (suggestedWordInfoList.isEmpty() || isPrediction) ? null
+ : suggestedWordInfoList.get(INDEX_OF_TYPED_WORD).mWord,
+ typedWordValid, willAutoCorrect, isObsoleteSuggestions, isPrediction,
+ sequenceNumber);
+ }
+
+ public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList,
+ final ArrayList<SuggestedWordInfo> rawSuggestions,
+ final String typedWord,
final boolean typedWordValid,
final boolean willAutoCorrect,
- final boolean isPunctuationSuggestions,
final boolean isObsoleteSuggestions,
final boolean isPrediction,
final int sequenceNumber) {
mSuggestedWordInfoList = suggestedWordInfoList;
+ mRawSuggestions = rawSuggestions;
mTypedWordValid = typedWordValid;
mWillAutoCorrect = willAutoCorrect;
- mIsPunctuationSuggestions = isPunctuationSuggestions;
mIsObsoleteSuggestions = isObsoleteSuggestions;
mIsPrediction = isPrediction;
mSequenceNumber = sequenceNumber;
+ mTypedWord = typedWord;
}
public boolean isEmpty() {
@@ -81,10 +101,32 @@ public final class SuggestedWords {
return mSuggestedWordInfoList.size();
}
+ /**
+ * Get suggested word at <code>index</code>.
+ * @param index The index of the suggested word.
+ * @return The suggested word.
+ */
public String getWord(final int index) {
return mSuggestedWordInfoList.get(index).mWord;
}
+ /**
+ * Get displayed text at <code>index</code>.
+ * In RTL languages, the displayed text on the suggestion strip may be different from the
+ * suggested word that is returned from {@link #getWord(int)}. For example the displayed text
+ * of punctuation suggestion "(" should be ")".
+ * @param index The index of the text to display.
+ * @return The text to be displayed.
+ */
+ public String getLabel(final int index) {
+ return mSuggestedWordInfoList.get(index).mWord;
+ }
+
+ /**
+ * Get {@link SuggestedWordInfo} object at <code>index</code>.
+ * @param index The index of the {@link SuggestedWordInfo}.
+ * @return The {@link SuggestedWordInfo} object.
+ */
public SuggestedWordInfo getInfo(final int index) {
return mSuggestedWordInfoList.get(index);
}
@@ -104,8 +146,12 @@ public final class SuggestedWords {
return debugString;
}
- public boolean willAutoCorrect() {
- return mWillAutoCorrect;
+ /**
+ * The predicator to tell whether this object represents punctuation suggestions.
+ * @return false if this object desn't represent punctuation suggestions.
+ */
+ public boolean isPunctuationSuggestions() {
+ return false;
}
@Override
@@ -114,7 +160,6 @@ public final class SuggestedWords {
return "SuggestedWords:"
+ " mTypedWordValid=" + mTypedWordValid
+ " mWillAutoCorrect=" + mWillAutoCorrect
- + " mIsPunctuationSuggestions=" + mIsPunctuationSuggestions
+ " words=" + Arrays.toString(mSuggestedWordInfoList.toArray());
}
@@ -122,15 +167,10 @@ public final class SuggestedWords {
final CompletionInfo[] infos) {
final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList();
for (final CompletionInfo info : infos) {
- if (info == null) continue;
- final CharSequence text = info.getText();
- if (null == text) continue;
- final SuggestedWordInfo suggestedWordInfo = new SuggestedWordInfo(text.toString(),
- SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_APP_DEFINED,
- Dictionary.DICTIONARY_APPLICATION_DEFINED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
- result.add(suggestedWordInfo);
+ if (null == info || null == info.getText()) {
+ continue;
+ }
+ result.add(new SuggestedWordInfo(info));
}
return result;
}
@@ -150,7 +190,7 @@ public final class SuggestedWords {
for (int index = 1; index < previousSize; index++) {
final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(index);
final String prevWord = prevWordInfo.mWord;
- // Filter out duplicate suggestion.
+ // Filter out duplicate suggestions.
if (!alreadySeen.contains(prevWord)) {
suggestionsList.add(prevWordInfo);
alreadySeen.add(prevWord);
@@ -189,6 +229,9 @@ public final class SuggestedWords {
public static final int KIND_FLAG_EXACT_MATCH = 0x40000000;
public final String mWord;
+ // The completion info from the application. Null for suggestions that don't come from
+ // the application (including keyboard-computed ones, so this is almost always null)
+ public final CompletionInfo mApplicationSpecifiedCompletionInfo;
public final int mScore;
public final int mKind; // one of the KIND_* constants above
public final int mCodePointCount;
@@ -215,6 +258,7 @@ public final class SuggestedWords {
final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord,
final int autoCommitFirstWordConfidence) {
mWord = word;
+ mApplicationSpecifiedCompletionInfo = null;
mScore = score;
mKind = kind;
mSourceDict = sourceDict;
@@ -223,6 +267,22 @@ public final class SuggestedWords {
mAutoCommitFirstWordConfidence = autoCommitFirstWordConfidence;
}
+ /**
+ * Create a new suggested word info from an application-specified completion.
+ * If the passed argument or its contained text is null, this throws a NPE.
+ * @param applicationSpecifiedCompletion The application-specified completion info.
+ */
+ public SuggestedWordInfo(final CompletionInfo applicationSpecifiedCompletion) {
+ mWord = applicationSpecifiedCompletion.getText().toString();
+ mApplicationSpecifiedCompletionInfo = applicationSpecifiedCompletion;
+ mScore = SuggestedWordInfo.MAX_SCORE;
+ mKind = SuggestedWordInfo.KIND_APP_DEFINED;
+ mSourceDict = Dictionary.DICTIONARY_APPLICATION_DEFINED;
+ mCodePointCount = StringUtils.codePointCount(mWord);
+ mIndexOfTouchPointOfSecondWord = SuggestedWordInfo.NOT_AN_INDEX;
+ mAutoCommitFirstWordConfidence = SuggestedWordInfo.NOT_A_CONFIDENCE;
+ }
+
public boolean isEligibleForAutoCommit() {
return (KIND_CORRECTION == mKind && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord);
}
@@ -278,17 +338,21 @@ public final class SuggestedWords {
// words from the member ArrayList as some other parties may expect the object to never change.
public SuggestedWords getSuggestedWordsExcludingTypedWord() {
final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList();
+ String typedWord = null;
for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
if (SuggestedWordInfo.KIND_TYPED != info.mKind) {
newSuggestions.add(info);
+ } else {
+ assert(null == typedWord);
+ typedWord = info.mWord;
}
}
// We should never autocorrect, so we say the typed word is valid. Also, in this case,
// no auto-correction should take place hence willAutoCorrect = false.
- return new SuggestedWords(newSuggestions, true /* typedWordValid */,
- false /* willAutoCorrect */, mIsPunctuationSuggestions, mIsObsoleteSuggestions,
- mIsPrediction);
+ return new SuggestedWords(newSuggestions, null /* rawSuggestions */, typedWord,
+ true /* typedWordValid */, false /* willAutoCorrect */, mIsObsoleteSuggestions,
+ mIsPrediction, NOT_A_SEQUENCE_NUMBER);
}
// Creates a new SuggestedWordInfo from the currently suggested words that removes all but the
@@ -306,8 +370,7 @@ public final class SuggestedWords {
info.mSourceDict, SuggestedWordInfo.NOT_AN_INDEX,
SuggestedWordInfo.NOT_A_CONFIDENCE));
}
- return new SuggestedWords(newSuggestions, mTypedWordValid,
- mWillAutoCorrect, mIsPunctuationSuggestions, mIsObsoleteSuggestions,
- mIsPrediction);
+ return new SuggestedWords(newSuggestions, null /* rawSuggestions */, mTypedWordValid,
+ mWillAutoCorrect, mIsObsoleteSuggestions, mIsPrediction);
}
}
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
index 3213c92c7..c24ee4033 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -25,34 +25,27 @@ import java.util.ArrayList;
import java.util.Locale;
public final class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryDictionary {
- private boolean mClosed;
+ private final Object mLock = new Object();
public SynchronouslyLoadedContactsBinaryDictionary(final Context context, final Locale locale) {
super(context, locale);
}
@Override
- public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
+ public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
final String prevWordForBigrams, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- reloadDictionaryIfRequired();
- return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions);
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final float[] inOutLanguageWeight) {
+ synchronized (mLock) {
+ return super.getSuggestions(codes, prevWordForBigrams, proximityInfo,
+ blockOffensiveWords, additionalFeaturesOptions, inOutLanguageWeight);
+ }
}
@Override
- public synchronized boolean isValidWord(final String word) {
- reloadDictionaryIfRequired();
- return isValidWordInner(word);
- }
-
- // Protect against multiple closing
- @Override
- public synchronized void close() {
- // Actually with the current implementation of ContactsDictionary it's safe to close
- // several times, so the following protection is really only for foolproofing
- if (mClosed) return;
- mClosed = true;
- super.close();
+ public boolean isValidWord(final String word) {
+ synchronized (mLock) {
+ return super.isValidWord(word);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
index 6405b5e46..1d29d7ad0 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -22,30 +22,35 @@ import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.util.ArrayList;
+import java.util.Locale;
public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionary {
+ private final Object mLock = new Object();
- public SynchronouslyLoadedUserBinaryDictionary(final Context context, final String locale) {
- this(context, locale, false);
+ public SynchronouslyLoadedUserBinaryDictionary(final Context context, final Locale locale) {
+ this(context, locale, false /* alsoUseMoreRestrictiveLocales */);
}
- public SynchronouslyLoadedUserBinaryDictionary(final Context context, final String locale,
+ public SynchronouslyLoadedUserBinaryDictionary(final Context context, final Locale locale,
final boolean alsoUseMoreRestrictiveLocales) {
- super(context, locale, alsoUseMoreRestrictiveLocales);
+ super(context, locale, alsoUseMoreRestrictiveLocales, null /* dictFile */);
}
@Override
- public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
+ public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
final String prevWordForBigrams, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- reloadDictionaryIfRequired();
- return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions);
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final float[] inOutLanguageWeight) {
+ synchronized (mLock) {
+ return super.getSuggestions(codes, prevWordForBigrams, proximityInfo,
+ blockOffensiveWords, additionalFeaturesOptions, inOutLanguageWeight);
+ }
}
@Override
- public synchronized boolean isValidWord(final String word) {
- reloadDictionaryIfRequired();
- return isValidWordInner(word);
+ public boolean isValidWord(final String word) {
+ synchronized (mLock) {
+ return super.isValidWord(word);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 15b3d8d02..8838e27c4 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
-import android.content.ContentUris;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
@@ -33,6 +32,7 @@ import com.android.inputmethod.compat.UserDictionaryCompatUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import java.io.File;
import java.util.Arrays;
import java.util.Locale;
@@ -74,25 +74,28 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
private ContentObserver mObserver;
final private String mLocale;
final private boolean mAlsoUseMoreRestrictiveLocales;
+ final public boolean mEnabled;
- public UserBinaryDictionary(final Context context, final String locale) {
- this(context, locale, false);
+ public UserBinaryDictionary(final Context context, final Locale locale) {
+ this(context, locale, false /* alsoUseMoreRestrictiveLocales */, null /* dictFile */);
}
- public UserBinaryDictionary(final Context context, final String locale,
- final boolean alsoUseMoreRestrictiveLocales) {
- super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_USER,
- false /* isUpdatable */);
+ public UserBinaryDictionary(final Context context, final Locale locale, final File dictFile) {
+ this(context, locale, false /* alsoUseMoreRestrictiveLocales */, dictFile);
+ }
+
+ public UserBinaryDictionary(final Context context, final Locale locale,
+ final boolean alsoUseMoreRestrictiveLocales, final File dictFile) {
+ super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_USER, dictFile);
if (null == locale) throw new NullPointerException(); // Catch the error earlier
- if (SubtypeLocaleUtils.NO_LANGUAGE.equals(locale)) {
+ final String localeStr = locale.toString();
+ if (SubtypeLocaleUtils.NO_LANGUAGE.equals(localeStr)) {
// If we don't have a locale, insert into the "all locales" user dictionary.
mLocale = USER_DICTIONARY_ALL_LANGUAGES;
} else {
- mLocale = locale;
+ mLocale = localeStr;
}
mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales;
- // Perform a managed query. The Activity will handle closing and re-querying the cursor
- // when needed.
ContentResolver cres = context.getContentResolver();
mObserver = new ContentObserver(null) {
@@ -112,7 +115,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
}
};
cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
-
+ mEnabled = readIsEnabled();
loadDictionary();
}
@@ -126,7 +129,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
}
@Override
- public void loadDictionaryAsync() {
+ public void loadInitialContentsLocked() {
// Split the locale. For example "en" => ["en"], "de_DE" => ["de", "DE"],
// "en_US_foo_bar_qux" => ["en", "US", "foo_bar_qux"] because of the limit of 3.
// This is correct for locale processing.
@@ -178,7 +181,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
try {
cursor = mContext.getContentResolver().query(
Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
- addWords(cursor);
+ addWordsLocked(cursor);
} catch (final SQLiteException e) {
Log.e(TAG, "SQLiteException in the remote User dictionary process.", e);
} finally {
@@ -190,7 +193,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
}
}
- public boolean isEnabled() {
+ private boolean readIsEnabled() {
final ContentResolver cr = mContext.getContentResolver();
final ContentProviderClient client = cr.acquireContentProviderClient(Words.CONTENT_URI);
if (client != null) {
@@ -232,7 +235,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
}
}
- private void addWords(final Cursor cursor) {
+ private void addWordsLocked(final Cursor cursor) {
final boolean hasShortcutColumn = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
if (cursor == null) return;
if (cursor.moveToFirst()) {
@@ -246,12 +249,16 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
final int adjustedFrequency = scaleFrequencyFromDefaultToLatinIme(frequency);
// Safeguard against adding really long words.
if (word.length() < MAX_WORD_LENGTH) {
- super.addWord(word, null, adjustedFrequency, 0 /* shortcutFreq */,
- false /* isNotAWord */);
- }
- if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) {
- super.addWord(shortcut, word, adjustedFrequency, USER_DICT_SHORTCUT_FREQUENCY,
- true /* isNotAWord */);
+ runGCIfRequiredLocked(true /* mindsBlockByGC */);
+ addWordDynamicallyLocked(word, adjustedFrequency, null /* shortcutTarget */,
+ 0 /* shortcutFreq */, false /* isNotAWord */,
+ false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+ if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) {
+ runGCIfRequiredLocked(true /* mindsBlockByGC */);
+ addWordDynamicallyLocked(shortcut, adjustedFrequency, word,
+ USER_DICT_SHORTCUT_FREQUENCY, true /* isNotAWord */,
+ false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+ }
}
cursor.moveToNext();
}
@@ -259,12 +266,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
}
@Override
- protected boolean hasContentChanged() {
- return true;
- }
-
- @Override
- protected boolean needsToReloadBeforeWriting() {
+ protected boolean haveContentsChanged() {
return true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 039dadc66..d755195f2 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,11 +16,14 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.event.CombinerChain;
+import com.android.inputmethod.event.Event;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.StringUtils;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
/**
* A place to store the currently composing word with information such as adjacent key codes as well
@@ -37,17 +40,16 @@ public final class WordComposer {
public static final int CAPS_MODE_AUTO_SHIFTED = 0x5;
public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
- // An array of code points representing the characters typed so far.
- // The array is limited to MAX_WORD_LENGTH code points, but mTypedWord extends past that
- // and mCodePointSize can go past that. If mCodePointSize is greater than MAX_WORD_LENGTH,
- // this just does not contain the associated code points past MAX_WORD_LENGTH.
- private int[] mPrimaryKeyCodes;
+ private CombinerChain mCombinerChain;
+
+ // The list of events that served to compose this string.
+ private final ArrayList<Event> mEvents;
private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
- // This is the typed word, as a StringBuilder. This has the same contents as mPrimaryKeyCodes
- // but under a StringBuilder representation for ease of use, depending on what is more useful
- // at any given time. However this is not limited in size, while mPrimaryKeyCodes is limited
- // to MAX_WORD_LENGTH code points.
- private final StringBuilder mTypedWord;
+ // The previous word (before the composing word). Used as context for suggestions. May be null
+ // after resetting and before starting a new composing word, or when there is no context like
+ // at the start of text for example. It can also be set to null externally when the user
+ // enters a separator that does not let bigrams across, like a period or a comma.
+ private String mPreviousWordForSuggestion;
private String mAutoCorrection;
private boolean mIsResumed;
private boolean mIsBatchMode;
@@ -60,10 +62,10 @@ public final class WordComposer {
private String mRejectedBatchModeSuggestion;
// Cache these values for performance
+ private CharSequence mTypedWordCache;
private int mCapsCount;
private int mDigitsCount;
private int mCapitalizedMode;
- private int mTrailingSingleQuotesCount;
// This is the number of code points entered so far. This is not limited to MAX_WORD_LENGTH.
// In general, this contains the size of mPrimaryKeyCodes, except when this is greater than
// MAX_WORD_LENGTH in which case mPrimaryKeyCodes only contain the first MAX_WORD_LENGTH
@@ -77,79 +79,83 @@ public final class WordComposer {
private boolean mIsFirstCharCapitalized;
public WordComposer() {
- mPrimaryKeyCodes = new int[MAX_WORD_LENGTH];
- mTypedWord = new StringBuilder(MAX_WORD_LENGTH);
+ mCombinerChain = new CombinerChain();
+ mEvents = CollectionUtils.newArrayList();
mAutoCorrection = null;
- mTrailingSingleQuotesCount = 0;
mIsResumed = false;
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null;
- refreshSize();
- }
-
- public WordComposer(final WordComposer source) {
- mPrimaryKeyCodes = Arrays.copyOf(source.mPrimaryKeyCodes, source.mPrimaryKeyCodes.length);
- mTypedWord = new StringBuilder(source.mTypedWord);
- mInputPointers.copy(source.mInputPointers);
- mCapsCount = source.mCapsCount;
- mDigitsCount = source.mDigitsCount;
- mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
- mCapitalizedMode = source.mCapitalizedMode;
- mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
- mIsResumed = source.mIsResumed;
- mIsBatchMode = source.mIsBatchMode;
- mCursorPositionWithinWord = source.mCursorPositionWithinWord;
- mRejectedBatchModeSuggestion = source.mRejectedBatchModeSuggestion;
- refreshSize();
+ mPreviousWordForSuggestion = null;
+ refreshTypedWordCache();
}
/**
* Clear out the keys registered so far.
*/
public void reset() {
- mTypedWord.setLength(0);
+ mCombinerChain.reset();
+ mEvents.clear();
mAutoCorrection = null;
mCapsCount = 0;
mDigitsCount = 0;
mIsFirstCharCapitalized = false;
- mTrailingSingleQuotesCount = 0;
mIsResumed = false;
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null;
- refreshSize();
+ mPreviousWordForSuggestion = null;
+ refreshTypedWordCache();
}
- private final void refreshSize() {
- mCodePointSize = mTypedWord.codePointCount(0, mTypedWord.length());
+ private final void refreshTypedWordCache() {
+ mTypedWordCache = mCombinerChain.getComposingWordWithCombiningFeedback();
+ mCodePointSize = Character.codePointCount(mTypedWordCache, 0, mTypedWordCache.length());
}
/**
* Number of keystrokes in the composing word.
* @return the number of keystrokes
*/
- public final int size() {
+ // This may be made public if need be, but right now it's not used anywhere
+ /* package for tests */ int size() {
return mCodePointSize;
}
- public final boolean isComposingWord() {
- return size() > 0;
- }
+ /**
+ * Copy the code points in the typed word to a destination array of ints.
+ *
+ * If the array is too small to hold the code points in the typed word, nothing is copied and
+ * -1 is returned.
+ *
+ * @param destination the array of ints.
+ * @return the number of copied code points.
+ */
+ public int copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
+ final int[] destination) {
+ // lastIndex is exclusive
+ final int lastIndex = mTypedWordCache.length() - trailingSingleQuotesCount();
+ if (lastIndex <= 0) {
+ // The string is empty or contains only single quotes.
+ return 0;
+ }
- // TODO: make sure that the index should not exceed MAX_WORD_LENGTH
- public int getCodeAt(int index) {
- if (index >= MAX_WORD_LENGTH) {
+ // The following function counts the number of code points in the text range which begins
+ // at index 0 and extends to the character at lastIndex.
+ final int codePointSize = Character.codePointCount(mTypedWordCache, 0, lastIndex);
+ if (codePointSize > destination.length) {
return -1;
}
- return mPrimaryKeyCodes[index];
+ return StringUtils.copyCodePointsAndReturnCodePointCount(destination, mTypedWordCache, 0,
+ lastIndex, true /* downCase */);
}
- public int getCodeBeforeCursor() {
- if (mCursorPositionWithinWord < 1 || mCursorPositionWithinWord > mPrimaryKeyCodes.length) {
- return Constants.NOT_A_CODE;
- }
- return mPrimaryKeyCodes[mCursorPositionWithinWord - 1];
+ public boolean isSingleLetter() {
+ return size() == 1;
+ }
+
+ public final boolean isComposingWord() {
+ return size() > 0;
}
public InputPointers getInputPointers() {
@@ -163,38 +169,47 @@ public final class WordComposer {
}
/**
- * Add a new keystroke, with the pressed key's code point with the touch point coordinates.
+ * Process an input event.
+ *
+ * All input events should be supported, including software/hardware events, characters as well
+ * as deletions, multiple inputs and gestures.
+ *
+ * @param event the event to process.
*/
- public void add(final int primaryCode, final int keyX, final int keyY) {
+ public void processEvent(final Event event) {
+ final int primaryCode = event.mCodePoint;
+ final int keyX = event.mX;
+ final int keyY = event.mY;
final int newIndex = size();
- mTypedWord.appendCodePoint(primaryCode);
- refreshSize();
+ mCombinerChain.processEvent(mEvents, event);
+ mEvents.add(event);
+ refreshTypedWordCache();
mCursorPositionWithinWord = mCodePointSize;
- if (newIndex < MAX_WORD_LENGTH) {
- mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE
- ? Character.toLowerCase(primaryCode) : primaryCode;
- // In the batch input mode, the {@code mInputPointers} holds batch input points and
- // shouldn't be overridden by the "typed key" coordinates
- // (See {@link #setBatchInputWord}).
- if (!mIsBatchMode) {
- // TODO: Set correct pointer id and time
- mInputPointers.addPointer(newIndex, keyX, keyY, 0, 0);
- }
+ // We may have deleted the last one.
+ if (0 == mCodePointSize) {
+ mIsFirstCharCapitalized = false;
}
- mIsFirstCharCapitalized = isFirstCharCapitalized(
- newIndex, primaryCode, mIsFirstCharCapitalized);
- if (Character.isUpperCase(primaryCode)) mCapsCount++;
- if (Character.isDigit(primaryCode)) mDigitsCount++;
- if (Constants.CODE_SINGLE_QUOTE == primaryCode) {
- ++mTrailingSingleQuotesCount;
- } else {
- mTrailingSingleQuotesCount = 0;
+ if (Constants.CODE_DELETE != event.mKeyCode) {
+ if (newIndex < MAX_WORD_LENGTH) {
+ // In the batch input mode, the {@code mInputPointers} holds batch input points and
+ // shouldn't be overridden by the "typed key" coordinates
+ // (See {@link #setBatchInputWord}).
+ if (!mIsBatchMode) {
+ // TODO: Set correct pointer id and time
+ mInputPointers.addPointerAt(newIndex, keyX, keyY, 0, 0);
+ }
+ }
+ mIsFirstCharCapitalized = isFirstCharCapitalized(
+ newIndex, primaryCode, mIsFirstCharCapitalized);
+ if (Character.isUpperCase(primaryCode)) mCapsCount++;
+ if (Character.isDigit(primaryCode)) mDigitsCount++;
}
mAutoCorrection = null;
}
public void setCursorPositionWithinWord(final int posWithinWord) {
mCursorPositionWithinWord = posWithinWord;
+ // TODO: compute where that puts us inside the events
}
public boolean isCursorFrontOrMiddleOfComposingWord() {
@@ -215,17 +230,12 @@ public final class WordComposer {
* @return true if the cursor is still inside the composing word, false otherwise.
*/
public boolean moveCursorByAndReturnIfInsideComposingWord(final int expectedMoveAmount) {
+ // TODO: should uncommit the composing feedback
+ mCombinerChain.reset();
int actualMoveAmountWithinWord = 0;
int cursorPos = mCursorPositionWithinWord;
- final int[] codePoints;
- if (mCodePointSize >= MAX_WORD_LENGTH) {
- // If we have more than MAX_WORD_LENGTH characters, we don't have everything inside
- // mPrimaryKeyCodes. This should be rare enough that we can afford to just compute
- // the array on the fly when this happens.
- codePoints = StringUtils.toCodePointArray(mTypedWord.toString());
- } else {
- codePoints = mPrimaryKeyCodes;
- }
+ // TODO: Don't make that copy. We can do this directly from mTypedWordCache.
+ final int[] codePoints = StringUtils.toCodePointArray(mTypedWordCache);
if (expectedMoveAmount >= 0) {
// Moving the cursor forward for the expected amount or until the end of the word has
// been reached, whichever comes first.
@@ -261,78 +271,29 @@ public final class WordComposer {
final int codePoint = Character.codePointAt(word, i);
// We don't want to override the batch input points that are held in mInputPointers
// (See {@link #add(int,int,int)}).
- add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
- }
- }
-
- /**
- * Add a dummy key by retrieving reasonable coordinates
- */
- public void addKeyInfo(final int codePoint, final Keyboard keyboard) {
- final int x, y;
- final Key key;
- if (keyboard != null && (key = keyboard.getKey(codePoint)) != null) {
- x = key.getX() + key.getWidth() / 2;
- y = key.getY() + key.getHeight() / 2;
- } else {
- x = Constants.NOT_A_COORDINATE;
- y = Constants.NOT_A_COORDINATE;
+ processEvent(Event.createEventForCodePointFromUnknownSource(codePoint));
}
- add(codePoint, x, y);
}
/**
* Set the currently composing word to the one passed as an argument.
* This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
+ * @param codePoints the code points to set as the composing word.
+ * @param coordinates the x, y coordinates of the key in the CoordinateUtils format
+ * @param previousWord the previous word, to use as context for suggestions. Can be null if
+ * the context is nil (typically, at start of text).
*/
- public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
+ public void setComposingWord(final int[] codePoints, final int[] coordinates,
+ final CharSequence previousWord) {
reset();
- final int length = word.length();
- for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
- final int codePoint = Character.codePointAt(word, i);
- addKeyInfo(codePoint, keyboard);
+ final int length = codePoints.length;
+ for (int i = 0; i < length; ++i) {
+ processEvent(Event.createEventForCodePointFromAlreadyTypedText(codePoints[i],
+ CoordinateUtils.xFromArray(coordinates, i),
+ CoordinateUtils.yFromArray(coordinates, i)));
}
mIsResumed = true;
- }
-
- /**
- * Delete the last keystroke as a result of hitting backspace.
- */
- public void deleteLast() {
- final int size = size();
- if (size > 0) {
- // Note: mTypedWord.length() and mCodes.length differ when there are surrogate pairs
- final int stringBuilderLength = mTypedWord.length();
- if (stringBuilderLength < size) {
- throw new RuntimeException(
- "In WordComposer: mCodes and mTypedWords have non-matching lengths");
- }
- final int lastChar = mTypedWord.codePointBefore(stringBuilderLength);
- if (Character.isSupplementaryCodePoint(lastChar)) {
- mTypedWord.delete(stringBuilderLength - 2, stringBuilderLength);
- } else {
- mTypedWord.deleteCharAt(stringBuilderLength - 1);
- }
- if (Character.isUpperCase(lastChar)) mCapsCount--;
- if (Character.isDigit(lastChar)) mDigitsCount--;
- refreshSize();
- }
- // We may have deleted the last one.
- if (0 == size()) {
- mIsFirstCharCapitalized = false;
- }
- if (mTrailingSingleQuotesCount > 0) {
- --mTrailingSingleQuotesCount;
- } else {
- int i = mTypedWord.length();
- while (i > 0) {
- i = mTypedWord.offsetByCodePoints(i, -1);
- if (Constants.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
- ++mTrailingSingleQuotesCount;
- }
- }
- mCursorPositionWithinWord = mCodePointSize;
- mAutoCorrection = null;
+ mPreviousWordForSuggestion = null == previousWord ? null : previousWord.toString();
}
/**
@@ -340,7 +301,11 @@ public final class WordComposer {
* @return the word that was typed so far. Never returns null.
*/
public String getTypedWord() {
- return mTypedWord.toString();
+ return mTypedWordCache.toString();
+ }
+
+ public String getPreviousWordForSuggestion() {
+ return mPreviousWordForSuggestion;
}
/**
@@ -352,7 +317,12 @@ public final class WordComposer {
}
public int trailingSingleQuotesCount() {
- return mTrailingSingleQuotesCount;
+ final int lastIndex = mTypedWordCache.length() - 1;
+ int i = lastIndex;
+ while (i >= 0 && mTypedWordCache.charAt(i) == Constants.CODE_SINGLE_QUOTE) {
+ --i;
+ }
+ return lastIndex - i;
}
/**
@@ -388,18 +358,21 @@ public final class WordComposer {
}
/**
- * Saves the caps mode at the start of composing.
+ * Saves the caps mode and the previous word at the start of composing.
*
- * WordComposer needs to know about this for several reasons. The first is, we need to know
- * after the fact what the reason was, to register the correct form into the user history
- * dictionary: if the word was automatically capitalized, we should insert it in all-lower
- * case but if it's a manual pressing of shift, then it should be inserted as is.
+ * WordComposer needs to know about the caps mode for several reasons. The first is, we need
+ * to know after the fact what the reason was, to register the correct form into the user
+ * history dictionary: if the word was automatically capitalized, we should insert it in
+ * all-lower case but if it's a manual pressing of shift, then it should be inserted as is.
* Also, batch input needs to know about the current caps mode to display correctly
* capitalized suggestions.
* @param mode the mode at the time of start
+ * @param previousWord the previous word as context for suggestions. May be null if none.
*/
- public void setCapitalizedModeAtStartComposingTime(final int mode) {
+ public void setCapitalizedModeAndPreviousWordAtStartComposingTime(final int mode,
+ final CharSequence previousWord) {
mCapitalizedMode = mode;
+ mPreviousWordForSuggestion = null == previousWord ? null : previousWord.toString();
}
/**
@@ -433,15 +406,14 @@ public final class WordComposer {
}
// `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
- public LastComposedWord commitWord(final int type, final String committedWord,
+ // committedWord should contain suggestion spans if applicable.
+ public LastComposedWord commitWord(final int type, final CharSequence committedWord,
final String separatorString, final String prevWord) {
// 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 int[] primaryKeyCodes = mPrimaryKeyCodes;
- mPrimaryKeyCodes = new int[MAX_WORD_LENGTH];
- final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes,
- mInputPointers, mTypedWord.toString(), committedWord, separatorString,
+ final LastComposedWord lastComposedWord = new LastComposedWord(mEvents,
+ mInputPointers, mTypedWordCache.toString(), committedWord, separatorString,
prevWord, mCapitalizedMode);
mInputPointers.reset();
if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
@@ -451,12 +423,13 @@ public final class WordComposer {
mCapsCount = 0;
mDigitsCount = 0;
mIsBatchMode = false;
- mTypedWord.setLength(0);
+ mPreviousWordForSuggestion = committedWord.toString();
+ mCombinerChain.reset();
+ mEvents.clear();
mCodePointSize = 0;
- mTrailingSingleQuotesCount = 0;
mIsFirstCharCapitalized = false;
mCapitalizedMode = CAPS_MODE_OFF;
- refreshSize();
+ refreshTypedWordCache();
mAutoCorrection = null;
mCursorPositionWithinWord = 0;
mIsResumed = false;
@@ -464,17 +437,26 @@ public final class WordComposer {
return lastComposedWord;
}
- public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
- mPrimaryKeyCodes = lastComposedWord.mPrimaryKeyCodes;
+ // Call this when the recorded previous word should be discarded. This is typically called
+ // when the user inputs a separator that's not whitespace (including the case of the
+ // double-space-to-period feature).
+ public void discardPreviousWordForSuggestion() {
+ mPreviousWordForSuggestion = null;
+ }
+
+ public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord,
+ final String previousWord) {
+ mEvents.clear();
+ Collections.copy(mEvents, lastComposedWord.mEvents);
mInputPointers.set(lastComposedWord.mInputPointers);
- mTypedWord.setLength(0);
- mTypedWord.append(lastComposedWord.mTypedWord);
- refreshSize();
+ mCombinerChain.reset();
+ refreshTypedWordCache();
mCapitalizedMode = lastComposedWord.mCapitalizedMode;
mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
mCursorPositionWithinWord = mCodePointSize;
mRejectedBatchModeSuggestion = null;
mIsResumed = true;
+ mPreviousWordForSuggestion = previousWord;
}
public boolean isBatchMode() {
diff --git a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
index 028f78a87..139e73aa4 100644
--- a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
+++ b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
@@ -26,8 +26,9 @@ import android.os.Environment;
import com.android.inputmethod.latin.BinaryDictionaryFileDumper;
import com.android.inputmethod.latin.BinaryDictionaryGetter;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.DialogUtils;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
@@ -51,7 +52,7 @@ public class ExternalDictionaryGetterForDebug {
final File[] files = new File(SOURCE_FOLDER).listFiles();
final ArrayList<String> eligibleList = CollectionUtils.newArrayList();
for (File f : files) {
- final FileHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(f);
+ final DictionaryHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(f);
if (null == header) continue;
eligibleList.add(f.getName());
}
@@ -70,7 +71,7 @@ public class ExternalDictionaryGetterForDebug {
}
private static void showNoFileDialog(final Context context) {
- new AlertDialog.Builder(context)
+ new AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(context))
.setMessage(R.string.read_external_dictionary_no_files_message)
.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
@@ -81,8 +82,8 @@ public class ExternalDictionaryGetterForDebug {
}
private static void showChooseFileDialog(final Context context, final String[] fileNames) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.read_external_dictionary_multiple_files_title)
+ new AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(context))
+ .setTitle(R.string.read_external_dictionary_multiple_files_title)
.setItems(fileNames, new OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
@@ -99,7 +100,7 @@ public class ExternalDictionaryGetterForDebug {
public static void askInstallFile(final Context context, final String dirPath,
final String fileName, final Runnable completeRunnable) {
final File file = new File(dirPath, fileName.toString());
- final FileHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file);
+ final DictionaryHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file);
final StringBuilder message = new StringBuilder();
final String locale = header.getLocaleString();
for (String key : header.mDictionaryOptions.mAttributes.keySet()) {
@@ -111,7 +112,7 @@ public class ExternalDictionaryGetterForDebug {
final String title = String.format(
context.getString(R.string.read_external_dictionary_confirm_install_message),
languageName);
- new AlertDialog.Builder(context)
+ new AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(context))
.setTitle(title)
.setMessage(message)
.setNegativeButton(android.R.string.cancel, new OnClickListener() {
@@ -143,7 +144,7 @@ public class ExternalDictionaryGetterForDebug {
}
private static void installFile(final Context context, final File file,
- final FileHeader header) {
+ final DictionaryHeader header) {
BufferedOutputStream outputStream = null;
File tempFile = null;
try {
@@ -167,7 +168,7 @@ public class ExternalDictionaryGetterForDebug {
}
} catch (IOException e) {
// There was an error: show a dialog
- new AlertDialog.Builder(context)
+ new AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(context))
.setTitle(R.string.error)
.setMessage(e.toString())
.setPositiveButton(android.R.string.ok, new OnClickListener() {
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index dc937fb25..af899c040 100644
--- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -29,4 +29,13 @@ public final class ProductionFlag {
public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG = false;
public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false;
+
+ // When true, enable {@link InputMethodService#onUpdateCursor} callback with
+ // {@link InputMethodService#setCursorAnchorMonitorMode}, which is not yet available in
+ // API level 19. Do not turn this on in production until the new API becomes publicly
+ // available.
+ public static final boolean USES_CURSOR_ANCHOR_MONITOR = false;
+
+ // Include all suggestions from all dictionaries in {@link SuggestedWords#mRawSuggestions}.
+ public static final boolean INCLUDE_RAW_SUGGESTIONS = false;
}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
new file mode 100644
index 000000000..f1f906042
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -0,0 +1,2006 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.inputlogic;
+
+import android.os.SystemClock;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+import android.util.Log;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.compat.SuggestionSpanUtils;
+import com.android.inputmethod.event.Event;
+import com.android.inputmethod.event.InputTransaction;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
+import com.android.inputmethod.latin.InputPointers;
+import com.android.inputmethod.latin.LastComposedWord;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.RichInputConnection;
+import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.SettingsValues;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
+import com.android.inputmethod.latin.utils.AsyncResultHolder;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
+import com.android.inputmethod.latin.utils.RecapitalizeStatus;
+import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.TextRange;
+import com.android.inputmethod.research.ResearchLogger;
+
+import java.util.ArrayList;
+import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class manages the input logic.
+ */
+public final class InputLogic {
+ private static final String TAG = InputLogic.class.getSimpleName();
+
+ // TODO : Remove this member when we can.
+ private final LatinIME mLatinIME;
+ private final SuggestionStripViewAccessor mSuggestionStripViewAccessor;
+
+ // Never null.
+ private InputLogicHandler mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
+
+ // TODO : make all these fields private as soon as possible.
+ // 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 final Suggest mSuggest = new Suggest();
+
+ public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+ public final WordComposer mWordComposer;
+ public final RichInputConnection mConnection;
+ private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
+
+ private int mDeleteCount;
+ private long mLastKeyTime;
+ public final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
+
+ // Keeps track of most recently inserted text (multi-character key) for reverting
+ private String mEnteredText;
+
+ // TODO: This boolean is persistent state and causes large side effects at unexpected times.
+ // Find a way to remove it for readability.
+ private boolean mIsAutoCorrectionIndicatorOn;
+ private long mDoubleSpacePeriodCountdownStart;
+
+ public InputLogic(final LatinIME latinIME,
+ final SuggestionStripViewAccessor suggestionStripViewAccessor) {
+ mLatinIME = latinIME;
+ mSuggestionStripViewAccessor = suggestionStripViewAccessor;
+ mWordComposer = new WordComposer();
+ mConnection = new RichInputConnection(latinIME);
+ mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
+ }
+
+ /**
+ * Initializes the input logic for input in an editor.
+ *
+ * Call this when input starts or restarts in some editor (typically, in onStartInputView).
+ * If the input is starting in the same field as before, set `restarting' to true. This allows
+ * the input logic to reset only necessary stuff and save performance. Also, when restarting
+ * some things must not be done (for example, the keyboard should not be reset to the
+ * alphabetic layout), so do not send false to this just in case.
+ *
+ * @param restarting whether input is starting in the same field as before. Unused for now.
+ * @param editorInfo the editorInfo associated with the editor.
+ */
+ public void startInput(final boolean restarting, final EditorInfo editorInfo) {
+ mEnteredText = null;
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ mDeleteCount = 0;
+ mSpaceState = SpaceState.NONE;
+ mRecapitalizeStatus.deactivate();
+ mCurrentlyPressedHardwareKeys.clear();
+ mSuggestedWords = SuggestedWords.EMPTY;
+ // 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();
+ cancelDoubleSpacePeriodCountdown();
+ if (InputLogicHandler.NULL_HANDLER == mInputLogicHandler) {
+ mInputLogicHandler = new InputLogicHandler(mLatinIME, this);
+ } else {
+ mInputLogicHandler.reset();
+ }
+ }
+
+ /**
+ * Clean up the input logic after input is finished.
+ */
+ public void finishInput() {
+ if (mWordComposer.isComposingWord()) {
+ mConnection.finishComposingText();
+ }
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ mInputLogicHandler.reset();
+ }
+
+ // Normally this class just gets out of scope after the process ends, but in unit tests, we
+ // create several instances of LatinIME in the same process, which results in several
+ // instances of InputLogic. This cleans up the associated handler so that tests don't leak
+ // handlers.
+ public void recycle() {
+ final InputLogicHandler inputLogicHandler = mInputLogicHandler;
+ mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
+ inputLogicHandler.destroy();
+ mSuggest.mDictionaryFacilitator.closeDictionaries();
+ }
+
+ /**
+ * React to a string input.
+ *
+ * This is triggered by keys that input many characters at once, like the ".com" key or
+ * some additional keys for example.
+ *
+ * @param settingsValues the current values of the settings.
+ * @param event the input event containing the data.
+ */
+ public void onTextInput(final SettingsValues settingsValues, final Event event,
+ // TODO: remove this argument
+ final LatinIME.UIHandler handler) {
+ final String rawText = event.mText.toString();
+ mConnection.beginBatchEdit();
+ if (mWordComposer.isComposingWord()) {
+ commitCurrentAutoCorrection(settingsValues, rawText, handler);
+ } else {
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ }
+ handler.postUpdateSuggestionStrip();
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS
+ && ResearchLogger.RESEARCH_KEY_OUTPUT_TEXT.equals(rawText)) {
+ ResearchLogger.getInstance().onResearchKeySelected(mLatinIME);
+ return;
+ }
+ final String text = performSpecificTldProcessingOnTextInput(rawText);
+ if (SpaceState.PHANTOM == mSpaceState) {
+ promotePhantomSpace(settingsValues);
+ }
+ mConnection.commitText(text, 1);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_onTextInput(text, false /* isBatchMode */);
+ }
+ mConnection.endBatchEdit();
+ // Space state must be updated before calling updateShiftState
+ mSpaceState = SpaceState.NONE;
+ mEnteredText = text;
+ }
+
+ /**
+ * A suggestion was picked from the suggestion strip.
+ * @param settingsValues the current values of the settings.
+ * @param index the index of the suggestion.
+ * @param suggestionInfo the suggestion info.
+ * @param keyboardShiftState the shift state of the keyboard, as returned by
+ * {@link com.android.inputmethod.keyboard.KeyboardSwitcher#getKeyboardShiftMode()}
+ * @return the complete transaction object
+ */
+ // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
+ // interface
+ public InputTransaction onPickSuggestionManually(final SettingsValues settingsValues,
+ final int index, final SuggestedWordInfo suggestionInfo, final int keyboardShiftState,
+ // TODO: remove this argument
+ final LatinIME.UIHandler handler) {
+ final SuggestedWords suggestedWords = mSuggestedWords;
+ final String suggestion = suggestionInfo.mWord;
+ // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
+ if (suggestion.length() == 1 && suggestedWords.isPunctuationSuggestions()) {
+ // Word separators are suggested before the user inputs something.
+ // So, LatinImeLogger logs "" as a user's input.
+ LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
+ // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
+ final Event event = Event.createPunctuationSuggestionPickedEvent(suggestionInfo);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
+ false /* isBatchMode */, suggestedWords.mIsPrediction);
+ }
+ return onCodeInput(settingsValues, event, keyboardShiftState, handler);
+ }
+
+ final Event event = Event.createSuggestionPickedEvent(suggestionInfo);
+ final InputTransaction inputTransaction = new InputTransaction(settingsValues,
+ event, SystemClock.uptimeMillis(), mSpaceState, keyboardShiftState);
+ mConnection.beginBatchEdit();
+ if (SpaceState.PHANTOM == mSpaceState && suggestion.length() > 0
+ // In the batch input mode, a manually picked suggested word should just replace
+ // the current batch input text and there is no need for a phantom space.
+ && !mWordComposer.isBatchMode()) {
+ final int firstChar = Character.codePointAt(suggestion, 0);
+ if (!settingsValues.isWordSeparator(firstChar)
+ || settingsValues.isUsuallyPrecededBySpace(firstChar)) {
+ promotePhantomSpace(settingsValues);
+ }
+ }
+
+ // TODO: We should not need the following branch. We should be able to take the same
+ // code path as for other kinds, use commitChosenWord, and do everything normally. We will
+ // 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 (SuggestedWordInfo.KIND_APP_DEFINED == suggestionInfo.mKind) {
+ mSuggestedWords = SuggestedWords.EMPTY;
+ mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
+ inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ mConnection.commitCompletion(suggestionInfo.mApplicationSpecifiedCompletionInfo);
+ mConnection.endBatchEdit();
+ return inputTransaction;
+ }
+
+ // We need to log before we commit, because the word composer will store away the user
+ // typed word.
+ final String replacedWord = mWordComposer.getTypedWord();
+ LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
+ commitChosenWord(settingsValues, suggestion,
+ LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
+ mWordComposer.isBatchMode(), suggestionInfo.mScore,
+ suggestionInfo.mKind, suggestionInfo.mSourceDict.mDictType);
+ }
+ mConnection.endBatchEdit();
+ // Don't allow cancellation of manual pick
+ mLastComposedWord.deactivate();
+ // Space state must be updated before calling updateShiftState
+ mSpaceState = SpaceState.PHANTOM;
+ inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
+
+ // We should show the "Touch again to save" hint if the user pressed the first entry
+ // AND it's in none of our current dictionaries (main, user or otherwise).
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator =
+ mSuggest.mDictionaryFacilitator;
+ final boolean showingAddToDictionaryHint =
+ (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
+ || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
+ && !dictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */);
+
+ if (settingsValues.mIsInternal) {
+ LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+ }
+ if (showingAddToDictionaryHint && dictionaryFacilitator.isUserDictionaryEnabled()) {
+ mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
+ } else {
+ // If we're not showing the "Touch again to save", then update the suggestion strip.
+ handler.postUpdateSuggestionStrip();
+ }
+ return inputTransaction;
+ }
+
+ /**
+ * Consider an update to the cursor position. Evaluate whether this update has happened as
+ * part of normal typing or whether it was an explicit cursor move by the user. In any case,
+ * do the necessary adjustments.
+ * @param oldSelStart old selection start
+ * @param oldSelEnd old selection end
+ * @param newSelStart new selection start
+ * @param newSelEnd new selection end
+ * @return whether the cursor has moved as a result of user interaction.
+ */
+ public boolean onUpdateSelection(final int oldSelStart, final int oldSelEnd,
+ final int newSelStart, final int newSelEnd) {
+ if (mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart, oldSelEnd, newSelEnd)) {
+ return false;
+ }
+ // TODO: the following is probably better done in resetEntireInputState().
+ // it should only happen when the cursor moved, and the very purpose of the
+ // test below is to narrow down whether this happened or not. Likewise with
+ // the call to updateShiftState.
+ // We set this to NONE because after a cursor move, we don't want the space
+ // state-related special processing to kick in.
+ mSpaceState = SpaceState.NONE;
+
+ final boolean selectionChangedOrSafeToReset =
+ oldSelStart != newSelStart || oldSelEnd != newSelEnd // selection changed
+ || !mWordComposer.isComposingWord(); // safe to reset
+ final boolean hasOrHadSelection = (oldSelStart != oldSelEnd || newSelStart != newSelEnd);
+ final int moveAmount = newSelStart - oldSelStart;
+ if (selectionChangedOrSafeToReset && (hasOrHadSelection
+ || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
+ // If we are composing a word and moving the cursor, we would want to set a
+ // suggestion span for recorrection to work correctly. Unfortunately, that
+ // would involve the keyboard committing some new text, which would move the
+ // cursor back to where it was. Latin IME could then fix the position of the cursor
+ // again, but the asynchronous nature of the calls results in this wreaking havoc
+ // with selection on double tap and the like.
+ // Another option would be to send suggestions each time we set the composing
+ // text, but that is probably too expensive to do, so we decided to leave things
+ // as is.
+ // Also, we're posting a resume suggestions message, and this will update the
+ // suggestions strip in a few milliseconds, so if we cleared the suggestion strip here
+ // we'd have the suggestion strip noticeably janky. To avoid that, we don't clear
+ // it here, which means we'll keep outdated suggestions for a split second but the
+ // visual result is better.
+ resetEntireInputState(newSelStart, newSelEnd, false /* clearSuggestionStrip */);
+ } else {
+ // resetEntireInputState calls resetCachesUponCursorMove, but forcing the
+ // composition to end. But in all cases where we don't reset the entire input
+ // state, we still want to tell the rich input connection about the new cursor
+ // position so that it can update its caches.
+ mConnection.resetCachesUponCursorMoveAndReturnSuccess(
+ newSelStart, newSelEnd, false /* shouldFinishComposition */);
+ }
+
+ // We moved the cursor. If we are touching a word, we need to resume suggestion.
+ mLatinIME.mHandler.postResumeSuggestions();
+ // Reset the last recapitalization.
+ mRecapitalizeStatus.deactivate();
+ return true;
+ }
+
+ /**
+ * React to a code input. It may be a code point to insert, or a symbolic value that influences
+ * the keyboard behavior.
+ *
+ * Typically, this is called whenever a key is pressed on the software keyboard. This is not
+ * the entry point for gesture input; see the onBatchInput* family of functions for this.
+ *
+ * @param settingsValues the current settings values.
+ * @param event the event to handle.
+ * @param keyboardShiftMode the current shift mode of the keyboard, as returned by
+ * {@link com.android.inputmethod.keyboard.KeyboardSwitcher#getKeyboardShiftMode()}
+ * @return the complete transaction object
+ */
+ public InputTransaction onCodeInput(final SettingsValues settingsValues, final Event event,
+ final int keyboardShiftMode,
+ // TODO: remove this argument
+ final LatinIME.UIHandler handler) {
+ // TODO: rework the following to not squash the keycode and the code point into the same
+ // var because it's confusing. Instead the switch() should handle this in a readable manner.
+ final int code =
+ Event.NOT_A_CODE_POINT == event.mCodePoint ? event.mKeyCode : event.mCodePoint;
+ final InputTransaction inputTransaction = new InputTransaction(settingsValues, event,
+ SystemClock.uptimeMillis(), mSpaceState,
+ getActualCapsMode(settingsValues, keyboardShiftMode));
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_onCodeInput(code, event.mX, event.mY);
+ }
+ if (event.mKeyCode != Constants.CODE_DELETE
+ || inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
+ mDeleteCount = 0;
+ }
+ mLastKeyTime = inputTransaction.mTimestamp;
+ mConnection.beginBatchEdit();
+ if (!mWordComposer.isComposingWord()) {
+ mIsAutoCorrectionIndicatorOn = false;
+ }
+
+ // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state.
+ if (event.mCodePoint != Constants.CODE_SPACE) {
+ cancelDoubleSpacePeriodCountdown();
+ }
+
+ boolean didAutoCorrect = false;
+ if (Event.NOT_A_KEY_CODE != event.mKeyCode) {
+ // A special key, like delete, shift, emoji, or the settings key.
+ switch (event.mKeyCode) {
+ case Constants.CODE_DELETE:
+ handleBackspace(inputTransaction);
+ LatinImeLogger.logOnDelete(event.mX, event.mY);
+ break;
+ case Constants.CODE_SHIFT:
+ performRecapitalization(inputTransaction.mSettingsValues);
+ inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
+ break;
+ case Constants.CODE_CAPSLOCK:
+ // Note: Changing keyboard to shift lock state is handled in
+ // {@link KeyboardSwitcher#onCodeInput(int)}.
+ break;
+ case Constants.CODE_SYMBOL_SHIFT:
+ // Note: Calling back to the keyboard on the symbol Shift key is handled in
+ // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+ break;
+ case Constants.CODE_SWITCH_ALPHA_SYMBOL:
+ // Note: Calling back to the keyboard on symbol key is handled in
+ // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+ break;
+ case Constants.CODE_SETTINGS:
+ onSettingsKeyPressed();
+ break;
+ case Constants.CODE_SHORTCUT:
+ // We need to switch to the shortcut IME. This is handled by LatinIME since the
+ // input logic has no business with IME switching.
+ break;
+ case Constants.CODE_ACTION_NEXT:
+ performEditorAction(EditorInfo.IME_ACTION_NEXT);
+ break;
+ case Constants.CODE_ACTION_PREVIOUS:
+ performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
+ break;
+ case Constants.CODE_LANGUAGE_SWITCH:
+ handleLanguageSwitchKey();
+ break;
+ case Constants.CODE_EMOJI:
+ // Note: Switching emoji keyboard is being handled in
+ // {@link KeyboardState#onCodeInput(int,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)}.
+ break;
+ case Constants.CODE_SHIFT_ENTER:
+ // TODO: remove this object
+ final InputTransaction tmpTransaction = new InputTransaction(
+ inputTransaction.mSettingsValues, inputTransaction.mEvent,
+ inputTransaction.mTimestamp, inputTransaction.mSpaceState,
+ inputTransaction.mShiftState);
+ didAutoCorrect = handleNonSpecialCharacter(tmpTransaction, handler);
+ break;
+ default:
+ throw new RuntimeException("Unknown key code : " + event.mKeyCode);
+ }
+ } else {
+ switch (event.mCodePoint) {
+ case Constants.CODE_ENTER:
+ final EditorInfo editorInfo = getCurrentInputEditorInfo();
+ final int imeOptionsActionId =
+ InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo);
+ if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) {
+ // Either we have an actionLabel and we should performEditorAction with
+ // actionId regardless of its value.
+ performEditorAction(editorInfo.actionId);
+ } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) {
+ // We didn't have an actionLabel, but we had another action to execute.
+ // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast,
+ // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it
+ // means there should be an action and the app didn't bother to set a specific
+ // code for it - presumably it only handles one. It does not have to be treated
+ // in any specific way: anything that is not IME_ACTION_NONE should be sent to
+ // performEditorAction.
+ performEditorAction(imeOptionsActionId);
+ } else {
+ // No action label, and the action from imeOptions is NONE: this is a regular
+ // enter key that should input a carriage return.
+ didAutoCorrect = handleNonSpecialCharacter(inputTransaction, handler);
+ }
+ break;
+ default:
+ didAutoCorrect = handleNonSpecialCharacter(inputTransaction, handler);
+ break;
+ }
+ }
+ if (!didAutoCorrect && event.mKeyCode != Constants.CODE_SHIFT
+ && event.mKeyCode != Constants.CODE_CAPSLOCK
+ && event.mKeyCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
+ mLastComposedWord.deactivate();
+ if (Constants.CODE_DELETE != event.mKeyCode) {
+ mEnteredText = null;
+ }
+ mConnection.endBatchEdit();
+ return inputTransaction;
+ }
+
+ public void onStartBatchInput(final SettingsValues settingsValues,
+ // TODO: remove these arguments
+ final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+ mInputLogicHandler.onStartBatchInput();
+ handler.showGesturePreviewAndSuggestionStrip(
+ SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
+ handler.cancelUpdateSuggestionStrip();
+ ++mAutoCommitSequenceNumber;
+ mConnection.beginBatchEdit();
+ if (mWordComposer.isComposingWord()) {
+ if (settingsValues.mIsInternal) {
+ if (mWordComposer.isBatchMode()) {
+ LatinImeLoggerUtils.onAutoCorrection("", mWordComposer.getTypedWord(), " ",
+ mWordComposer);
+ }
+ }
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the batch input at the current cursor position.
+ resetEntireInputState(mConnection.getExpectedSelectionStart(),
+ mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
+ } else if (mWordComposer.isSingleLetter()) {
+ // We auto-correct the previous (typed, not gestured) string iff it's one character
+ // long. The reason for this is, even in the middle of gesture typing, you'll still
+ // tap one-letter words and you want them auto-corrected (typically, "i" in English
+ // should become "I"). However for any longer word, we assume that the reason for
+ // tapping probably is that the word you intend to type is not in the dictionary,
+ // so we do not attempt to correct, on the assumption that if that was a dictionary
+ // word, the user would probably have gestured instead.
+ commitCurrentAutoCorrection(settingsValues, LastComposedWord.NOT_A_SEPARATOR,
+ handler);
+ } else {
+ commitTyped(settingsValues, LastComposedWord.NOT_A_SEPARATOR);
+ }
+ }
+ final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
+ if (Character.isLetterOrDigit(codePointBeforeCursor)
+ || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+ final boolean autoShiftHasBeenOverriden = keyboardSwitcher.getKeyboardShiftMode() !=
+ getCurrentAutoCapsState(settingsValues);
+ mSpaceState = SpaceState.PHANTOM;
+ if (!autoShiftHasBeenOverriden) {
+ // When we change the space state, we need to update the shift state of the
+ // keyboard unless it has been overridden manually. This is happening for example
+ // after typing some letters and a period, then gesturing; the keyboard is not in
+ // caps mode yet, but since a gesture is starting, it should go in caps mode,
+ // unless the user explictly said it should not.
+ keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(settingsValues),
+ getCurrentRecapitalizeState());
+ }
+ }
+ mConnection.endBatchEdit();
+ mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
+ getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()),
+ // Prev word is 1st word before cursor
+ getNthPreviousWordForSuggestion(
+ settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */));
+ }
+
+ /* The sequence number member is only used in onUpdateBatchInput. It is increased each time
+ * auto-commit happens. The reason we need this is, when auto-commit happens we trim the
+ * input pointers that are held in a singleton, and to know how much to trim we rely on the
+ * results of the suggestion process that is held in mSuggestedWords.
+ * However, the suggestion process is asynchronous, and sometimes we may enter the
+ * onUpdateBatchInput method twice without having recomputed suggestions yet, or having
+ * received new suggestions generated from not-yet-trimmed input pointers. In this case, the
+ * mIndexOfTouchPointOfSecondWords member will be out of date, and we must not use it lest we
+ * remove an unrelated number of pointers (possibly even more than are left in the input
+ * pointers, leading to a crash).
+ * To avoid that, we increase the sequence number each time we auto-commit and trim the
+ * input pointers, and we do not use any suggested words that have been generated with an
+ * earlier sequence number.
+ */
+ private int mAutoCommitSequenceNumber = 1;
+ public void onUpdateBatchInput(final SettingsValues settingsValues,
+ final InputPointers batchPointers,
+ // TODO: remove these arguments
+ final KeyboardSwitcher keyboardSwitcher) {
+ if (settingsValues.mPhraseGestureEnabled) {
+ final SuggestedWordInfo candidate = mSuggestedWords.getAutoCommitCandidate();
+ // If these suggested words have been generated with out of date input pointers, then
+ // we skip auto-commit (see comments above on the mSequenceNumber member).
+ if (null != candidate
+ && mSuggestedWords.mSequenceNumber >= mAutoCommitSequenceNumber) {
+ if (candidate.mSourceDict.shouldAutoCommit(candidate)) {
+ final String[] commitParts = candidate.mWord.split(" ", 2);
+ batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord);
+ promotePhantomSpace(settingsValues);
+ mConnection.commitText(commitParts[0], 0);
+ mSpaceState = SpaceState.PHANTOM;
+ keyboardSwitcher.requestUpdatingShiftState(
+ getCurrentAutoCapsState(settingsValues), getCurrentRecapitalizeState());
+ mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
+ getActualCapsMode(settingsValues,
+ keyboardSwitcher.getKeyboardShiftMode()), commitParts[0]);
+ ++mAutoCommitSequenceNumber;
+ }
+ }
+ }
+ mInputLogicHandler.onUpdateBatchInput(batchPointers, mAutoCommitSequenceNumber);
+ }
+
+ public void onEndBatchInput(final InputPointers batchPointers) {
+ mInputLogicHandler.updateTailBatchInput(batchPointers, mAutoCommitSequenceNumber);
+ ++mAutoCommitSequenceNumber;
+ }
+
+ // TODO: remove this argument
+ public void onCancelBatchInput(final LatinIME.UIHandler handler) {
+ mInputLogicHandler.onCancelBatchInput();
+ handler.showGesturePreviewAndSuggestionStrip(
+ SuggestedWords.EMPTY, 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) {
+ mSuggestedWords = suggestedWords;
+ final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect;
+ // Put a blue underline to a word in TextView which will be auto-corrected.
+ if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator
+ && mWordComposer.isComposingWord()) {
+ mIsAutoCorrectionIndicatorOn = newAutoCorrectionIndicator;
+ final CharSequence textWithUnderline =
+ getTextWithUnderline(mWordComposer.getTypedWord());
+ // TODO: when called from an updateSuggestionStrip() call that results from a posted
+ // message, this is called outside any batch edit. Potentially, this may result in some
+ // janky flickering of the screen, although the display speed makes it unlikely in
+ // the practice.
+ mConnection.setComposingText(textWithUnderline, 1);
+ }
+ }
+
+ /**
+ * Handle inputting a code point to the editor.
+ *
+ * Non-special keys are those that generate a single code point.
+ * This includes all letters, digits, punctuation, separators, emoji. It excludes keys that
+ * manage keyboard-related stuff like shift, language switch, settings, layout switch, or
+ * any key that results in multiple code points like the ".com" key.
+ *
+ * @param inputTransaction The transaction in progress.
+ * @return whether this caused an auto-correction to happen.
+ */
+ private boolean handleNonSpecialCharacter(final InputTransaction inputTransaction,
+ // TODO: remove this argument
+ final LatinIME.UIHandler handler) {
+ final int codePoint = inputTransaction.mEvent.mCodePoint;
+ mSpaceState = SpaceState.NONE;
+ final boolean didAutoCorrect;
+ if (inputTransaction.mSettingsValues.isWordSeparator(codePoint)
+ || Character.getType(codePoint) == Character.OTHER_SYMBOL) {
+ didAutoCorrect = handleSeparator(inputTransaction,
+ inputTransaction.mEvent.isSuggestionStripPress(), handler);
+ if (inputTransaction.mSettingsValues.mIsInternal) {
+ LatinImeLoggerUtils.onSeparator((char)codePoint,
+ inputTransaction.mEvent.mX, inputTransaction.mEvent.mY);
+ }
+ } else {
+ didAutoCorrect = false;
+ if (SpaceState.PHANTOM == inputTransaction.mSpaceState) {
+ if (inputTransaction.mSettingsValues.mIsInternal) {
+ if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
+ LatinImeLoggerUtils.onAutoCorrection("", mWordComposer.getTypedWord(), " ",
+ mWordComposer);
+ }
+ }
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the character at the current cursor position.
+ resetEntireInputState(mConnection.getExpectedSelectionStart(),
+ mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
+ } else {
+ commitTyped(inputTransaction.mSettingsValues, LastComposedWord.NOT_A_SEPARATOR);
+ }
+ }
+ handleNonSeparator(inputTransaction.mSettingsValues, inputTransaction);
+ }
+ return didAutoCorrect;
+ }
+
+ /**
+ * Handle a non-separator.
+ * @param settingsValues The current settings values.
+ * @param inputTransaction The transaction in progress.
+ */
+ private void handleNonSeparator(final SettingsValues settingsValues,
+ final InputTransaction inputTransaction) {
+ final int codePoint = inputTransaction.mEvent.mCodePoint;
+ // TODO: refactor this method to stop flipping isComposingWord around all the time, and
+ // make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
+ // which has the same name as other handle* methods but is not the same.
+ boolean isComposingWord = mWordComposer.isComposingWord();
+
+ // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
+ // See onStartBatchInput() to see how to do it.
+ if (SpaceState.PHANTOM == inputTransaction.mSpaceState
+ && !settingsValues.isWordConnector(codePoint)) {
+ if (isComposingWord) {
+ // Sanity check
+ throw new RuntimeException("Should not be composing here");
+ }
+ promotePhantomSpace(settingsValues);
+ }
+
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the character at the current cursor position.
+ resetEntireInputState(mConnection.getExpectedSelectionStart(),
+ mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
+ isComposingWord = false;
+ }
+ // We want to find out whether to start composing a new word with this character. If so,
+ // we need to reset the composing state and switch isComposingWord. The order of the
+ // tests is important for good performance.
+ // We only start composing if we're not already composing.
+ if (!isComposingWord
+ // We only start composing if this is a word code point. Essentially that means it's a
+ // a letter or a word connector.
+ && settingsValues.isWordCodePoint(codePoint)
+ // We never go into composing state if suggestions are not requested.
+ && settingsValues.isSuggestionsRequested() &&
+ // In languages with spaces, we only start composing a word when we are not already
+ // touching a word. In languages without spaces, the above conditions are sufficient.
+ (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)
+ || !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces)) {
+ // Reset entirely the composing state anyway, then start composing a new word unless
+ // the character is a single quote or a dash. The idea here is, single quote and dash
+ // are not separators and they should be treated as normal characters, except in the
+ // first position where they should not start composing a word.
+ isComposingWord = (Constants.CODE_SINGLE_QUOTE != codePoint
+ && Constants.CODE_DASH != codePoint);
+ // Here we don't need to reset the last composed word. It will be reset
+ // when we commit this one, if we ever do; if on the other hand we backspace
+ // it entirely and resume suggestions on the previous word, we'd like to still
+ // have touch coordinates for it.
+ resetComposingState(false /* alsoResetLastComposedWord */);
+ }
+ if (isComposingWord) {
+ mWordComposer.processEvent(inputTransaction.mEvent);
+ // If it's the first letter, make note of auto-caps state
+ if (mWordComposer.isSingleLetter()) {
+ // We pass 1 to getPreviousWordForSuggestion because we were not composing a word
+ // yet, so the word we want is the 1st word before the cursor.
+ mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
+ inputTransaction.mShiftState, getNthPreviousWordForSuggestion(
+ settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */));
+ }
+ mConnection.setComposingText(getTextWithUnderline(
+ mWordComposer.getTypedWord()), 1);
+ } else {
+ final boolean swapWeakSpace = maybeStripSpace(inputTransaction,
+ inputTransaction.mEvent.isSuggestionStripPress());
+
+ sendKeyCodePoint(settingsValues, codePoint);
+
+ if (swapWeakSpace) {
+ swapSwapperAndSpace(inputTransaction);
+ mSpaceState = SpaceState.WEAK;
+ }
+ // In case the "add to dictionary" hint was still displayed.
+ mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
+ }
+ inputTransaction.setRequiresUpdateSuggestions();
+ if (settingsValues.mIsInternal) {
+ LatinImeLoggerUtils.onNonSeparator((char)codePoint, inputTransaction.mEvent.mX,
+ inputTransaction.mEvent.mY);
+ }
+ }
+
+ /**
+ * Handle input of a separator code point.
+ * @param inputTransaction The transaction in progress.
+ * @param isFromSuggestionStrip whether this code point comes from the suggestion strip.
+ * @return whether this caused an auto-correction to happen.
+ */
+ private boolean handleSeparator(final InputTransaction inputTransaction,
+ final boolean isFromSuggestionStrip,
+ // TODO: remove this argument
+ final LatinIME.UIHandler handler) {
+ final int codePoint = inputTransaction.mEvent.mCodePoint;
+ boolean didAutoCorrect = false;
+ // We avoid sending spaces in languages without spaces if we were composing.
+ final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint
+ && !inputTransaction.mSettingsValues.mSpacingAndPunctuations
+ .mCurrentLanguageHasSpaces
+ && mWordComposer.isComposingWord();
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the separator at the current cursor position.
+ resetEntireInputState(mConnection.getExpectedSelectionStart(),
+ mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
+ }
+ // isComposingWord() may have changed since we stored wasComposing
+ if (mWordComposer.isComposingWord()) {
+ if (inputTransaction.mSettingsValues.mCorrectionEnabled) {
+ final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
+ : StringUtils.newSingleCodePointString(codePoint);
+ commitCurrentAutoCorrection(inputTransaction.mSettingsValues, separator, handler);
+ didAutoCorrect = true;
+ } else {
+ commitTyped(inputTransaction.mSettingsValues,
+ StringUtils.newSingleCodePointString(codePoint));
+ }
+ }
+
+ final boolean swapWeakSpace = maybeStripSpace(inputTransaction, isFromSuggestionStrip);
+
+ final boolean isInsideDoubleQuoteOrAfterDigit = Constants.CODE_DOUBLE_QUOTE == codePoint
+ && mConnection.isInsideDoubleQuoteOrAfterDigit();
+
+ final boolean needsPrecedingSpace;
+ if (SpaceState.PHANTOM != inputTransaction.mSpaceState) {
+ needsPrecedingSpace = false;
+ } else if (Constants.CODE_DOUBLE_QUOTE == codePoint) {
+ // Double quotes behave like they are usually preceded by space iff we are
+ // not inside a double quote or after a digit.
+ needsPrecedingSpace = !isInsideDoubleQuoteOrAfterDigit;
+ } else {
+ needsPrecedingSpace = inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(
+ codePoint);
+ }
+
+ if (needsPrecedingSpace) {
+ promotePhantomSpace(inputTransaction.mSettingsValues);
+ }
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleSeparator(codePoint, mWordComposer.isComposingWord());
+ }
+
+ if (!shouldAvoidSendingCode) {
+ sendKeyCodePoint(inputTransaction.mSettingsValues, codePoint);
+ }
+
+ if (Constants.CODE_SPACE == codePoint) {
+ if (maybeDoubleSpacePeriod(inputTransaction)) {
+ inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
+ mSpaceState = SpaceState.DOUBLE;
+ } else if (!mSuggestedWords.isPunctuationSuggestions()) {
+ mSpaceState = SpaceState.WEAK;
+ }
+
+ startDoubleSpacePeriodCountdown(inputTransaction);
+ inputTransaction.setRequiresUpdateSuggestions();
+ } else {
+ if (swapWeakSpace) {
+ swapSwapperAndSpace(inputTransaction);
+ mSpaceState = SpaceState.SWAP_PUNCTUATION;
+ } else if ((SpaceState.PHANTOM == inputTransaction.mSpaceState
+ && inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(codePoint))
+ || (Constants.CODE_DOUBLE_QUOTE == codePoint
+ && isInsideDoubleQuoteOrAfterDigit)) {
+ // If we are in phantom space state, and the user presses a separator, we want to
+ // stay in phantom space state so that the next keypress has a chance to add the
+ // space. For example, if I type "Good dat", pick "day" from the suggestion strip
+ // then insert a comma and go on to typing the next word, I want the space to be
+ // inserted automatically before the next word, the same way it is when I don't
+ // input the comma. A double quote behaves like it's usually followed by space if
+ // we're inside a double quote.
+ // The case is a little different if the separator is a space stripper. Such a
+ // separator does not normally need a space on the right (that's the difference
+ // between swappers and strippers), so we should not stay in phantom space state if
+ // the separator is a stripper. Hence the additional test above.
+ mSpaceState = SpaceState.PHANTOM;
+ }
+
+ // Set punctuation right away. onUpdateSelection will fire but tests whether it is
+ // already displayed or not, so it's okay.
+ mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
+ }
+
+ inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
+ return didAutoCorrect;
+ }
+
+ /**
+ * Handle a press on the backspace key.
+ * @param inputTransaction The transaction in progress.
+ */
+ private void handleBackspace(final InputTransaction inputTransaction) {
+ mSpaceState = SpaceState.NONE;
+ mDeleteCount++;
+
+ // In many cases after backspace, we need to update the shift state. Normally we need
+ // to do this right away to avoid the shift state being out of date in case the user types
+ // backspace then some other character very fast. However, in the case of backspace key
+ // repeat, this can lead to flashiness when the cursor flies over positions where the
+ // shift state should be updated, so if this is a key repeat, we update after a small delay.
+ // Then again, even in the case of a key repeat, if the cursor is at start of text, it
+ // can't go any further back, so we can update right away even if it's a key repeat.
+ final int shiftUpdateKind =
+ inputTransaction.mEvent.isKeyRepeat() && mConnection.getExpectedSelectionStart() > 0
+ ? InputTransaction.SHIFT_UPDATE_LATER : InputTransaction.SHIFT_UPDATE_NOW;
+ inputTransaction.requireShiftUpdate(shiftUpdateKind);
+
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can remove the character at the current cursor position.
+ resetEntireInputState(mConnection.getExpectedSelectionStart(),
+ mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
+ // When we exit this if-clause, mWordComposer.isComposingWord() will return false.
+ }
+ if (mWordComposer.isComposingWord()) {
+ if (mWordComposer.isBatchMode()) {
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ final String word = mWordComposer.getTypedWord();
+ ResearchLogger.latinIME_handleBackspace_batch(word, 1);
+ }
+ final String rejectedSuggestion = mWordComposer.getTypedWord();
+ mWordComposer.reset();
+ mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
+ } else {
+ mWordComposer.processEvent(inputTransaction.mEvent);
+ }
+ mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+ inputTransaction.setRequiresUpdateSuggestions();
+ } else {
+ if (mLastComposedWord.canRevertCommit()) {
+ if (inputTransaction.mSettingsValues.mIsInternal) {
+ LatinImeLoggerUtils.onAutoCorrectionCancellation();
+ }
+ revertCommit(inputTransaction);
+ return;
+ }
+ if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
+ // Cancel multi-character input: remove the text we just entered.
+ // This is triggered on backspace after a key that inputs multiple characters,
+ // like the smiley key or the .com key.
+ mConnection.deleteSurroundingText(mEnteredText.length(), 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText);
+ }
+ 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
+ // reverting any autocorrect at this point. So we can safely return.
+ return;
+ }
+ if (SpaceState.DOUBLE == inputTransaction.mSpaceState) {
+ cancelDoubleSpacePeriodCountdown();
+ if (mConnection.revertDoubleSpacePeriod()) {
+ // No need to reset mSpaceState, it has already be done (that's why we
+ // receive it as a parameter)
+ return;
+ }
+ } else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
+ if (mConnection.revertSwapPunctuation()) {
+ // Likewise
+ return;
+ }
+ }
+
+ // No cancelling of commit/double space/swap: we have a regular backspace.
+ // We should backspace one char and restart suggestion if at the end of a word.
+ if (mConnection.hasSelection()) {
+ // If there is a selection, remove it.
+ final int numCharsDeleted = mConnection.getExpectedSelectionEnd()
+ - mConnection.getExpectedSelectionStart();
+ mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
+ mConnection.getExpectedSelectionEnd());
+ mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(numCharsDeleted,
+ false /* shouldUncommitLogUnit */);
+ }
+ } 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
+ // 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.
+ sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
+ sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ }
+ } else {
+ final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
+ if (codePointBeforeCursor == Constants.NOT_A_CODE) {
+ // HACK for backward compatibility with broken apps that haven't realized
+ // yet that hardware keyboards are not the only way of inputting text.
+ // Nothing to delete before the cursor. We should not do anything, but many
+ // broken apps expect something to happen in this case so that they can
+ // catch it and have their broken interface react. If you need the keyboard
+ // to do this, you're doing it wrong -- please fix your app.
+ mConnection.deleteSurroundingText(1, 0);
+ return;
+ }
+ final int lengthToDelete =
+ Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
+ mConnection.deleteSurroundingText(lengthToDelete, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(lengthToDelete,
+ true /* shouldUncommitLogUnit */);
+ }
+ if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
+ final int codePointBeforeCursorToDeleteAgain =
+ mConnection.getCodePointBeforeCursor();
+ if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
+ final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
+ codePointBeforeCursorToDeleteAgain) ? 2 : 1;
+ mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain,
+ true /* shouldUncommitLogUnit */);
+ }
+ }
+ }
+ }
+ }
+ if (inputTransaction.mSettingsValues.isSuggestionStripVisible()
+ && inputTransaction.mSettingsValues.mSpacingAndPunctuations
+ .mCurrentLanguageHasSpaces
+ && !mConnection.isCursorFollowedByWordCharacter(
+ inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
+ restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
+ true /* includeResumedWordInSuggestions */);
+ }
+ }
+ }
+
+ /**
+ * Handle a press on the language switch key (the "globe key")
+ */
+ private void handleLanguageSwitchKey() {
+ mLatinIME.switchToNextSubtype();
+ }
+
+ /**
+ * Swap a space with a space-swapping punctuation sign.
+ *
+ * This method will check that there are two characters before the cursor and that the first
+ * one is a space before it does the actual swapping.
+ * @param inputTransaction The transaction in progress.
+ */
+ private void swapSwapperAndSpace(final InputTransaction inputTransaction) {
+ final CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
+ // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
+ if (lastTwo != null && lastTwo.length() == 2 && lastTwo.charAt(0) == Constants.CODE_SPACE) {
+ mConnection.deleteSurroundingText(2, 0);
+ final String text = lastTwo.charAt(1) + " ";
+ mConnection.commitText(text, 1);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text);
+ }
+ inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
+ }
+ }
+
+ /*
+ * Strip a trailing space if necessary and returns whether it's a swap weak space situation.
+ * @param inputTransaction The transaction in progress.
+ * @param isFromSuggestionStrip Whether this code point is coming from the suggestion strip.
+ * @return whether we should swap the space instead of removing it.
+ */
+ private boolean maybeStripSpace(final InputTransaction inputTransaction,
+ final boolean isFromSuggestionStrip) {
+ final int codePoint = inputTransaction.mEvent.mCodePoint;
+ if (Constants.CODE_ENTER == codePoint &&
+ SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
+ mConnection.removeTrailingSpace();
+ return false;
+ }
+ if ((SpaceState.WEAK == inputTransaction.mSpaceState
+ || SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState)
+ && isFromSuggestionStrip) {
+ if (inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(codePoint)) {
+ return false;
+ }
+ if (inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(codePoint)) {
+ return true;
+ }
+ mConnection.removeTrailingSpace();
+ }
+ return false;
+ }
+
+ public void startDoubleSpacePeriodCountdown(final InputTransaction inputTransaction) {
+ mDoubleSpacePeriodCountdownStart = inputTransaction.mTimestamp;
+ }
+
+ public void cancelDoubleSpacePeriodCountdown() {
+ mDoubleSpacePeriodCountdownStart = 0;
+ }
+
+ public boolean isDoubleSpacePeriodCountdownActive(final InputTransaction inputTransaction) {
+ return inputTransaction.mTimestamp - mDoubleSpacePeriodCountdownStart
+ < inputTransaction.mSettingsValues.mDoubleSpacePeriodTimeout;
+ }
+
+ /**
+ * Apply the double-space-to-period transformation if applicable.
+ *
+ * The double-space-to-period transformation means that we replace two spaces with a
+ * period-space sequence of characters. This typically happens when the user presses space
+ * twice in a row quickly.
+ * This method will check that the double-space-to-period is active in settings, that the
+ * two spaces have been input close enough together, and that the previous character allows
+ * for the transformation to take place. If all of these conditions are fulfilled, this
+ * method applies the transformation and returns true. Otherwise, it does nothing and
+ * returns false.
+ *
+ * @param inputTransaction The transaction in progress.
+ * @return true if we applied the double-space-to-period transformation, false otherwise.
+ */
+ private boolean maybeDoubleSpacePeriod(final InputTransaction inputTransaction) {
+ if (!inputTransaction.mSettingsValues.mUseDoubleSpacePeriod) return false;
+ if (!isDoubleSpacePeriodCountdownActive(inputTransaction)) return false;
+ // We only do this when we see two spaces and an accepted code point before the cursor.
+ // The code point may be a surrogate pair but the two spaces may not, so we need 4 chars.
+ final CharSequence lastThree = mConnection.getTextBeforeCursor(4, 0);
+ if (null == lastThree) return false;
+ final int length = lastThree.length();
+ if (length < 3) return false;
+ if (lastThree.charAt(length - 1) != Constants.CODE_SPACE) return false;
+ if (lastThree.charAt(length - 2) != Constants.CODE_SPACE) return false;
+ // We know there are spaces in pos -1 and -2, and we have at least three chars.
+ // If we have only three chars, isSurrogatePairs can't return true as charAt(1) is a space,
+ // so this is fine.
+ final int firstCodePoint =
+ Character.isSurrogatePair(lastThree.charAt(0), lastThree.charAt(1)) ?
+ Character.codePointAt(lastThree, 0) : lastThree.charAt(length - 3);
+ if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) {
+ cancelDoubleSpacePeriodCountdown();
+ mConnection.deleteSurroundingText(2, 0);
+ final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations
+ .mSentenceSeparatorAndSpace;
+ mConnection.commitText(textToInsert, 1);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
+ false /* isBatchMode */);
+ }
+ mWordComposer.discardPreviousWordForSuggestion();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether this code point can be followed by the double-space-to-period transformation.
+ *
+ * See #maybeDoubleSpaceToPeriod for details.
+ * Generally, most word characters can be followed by the double-space-to-period transformation,
+ * while most punctuation can't. Some punctuation however does allow for this to take place
+ * after them, like the closing parenthesis for example.
+ *
+ * @param codePoint the code point after which we may want to apply the transformation
+ * @return whether it's fine to apply the transformation after this code point.
+ */
+ private static boolean canBeFollowedByDoubleSpacePeriod(final int codePoint) {
+ // TODO: This should probably be a blacklist rather than a whitelist.
+ // TODO: This should probably be language-dependant...
+ return Character.isLetterOrDigit(codePoint)
+ || codePoint == Constants.CODE_SINGLE_QUOTE
+ || codePoint == Constants.CODE_DOUBLE_QUOTE
+ || codePoint == Constants.CODE_CLOSING_PARENTHESIS
+ || codePoint == Constants.CODE_CLOSING_SQUARE_BRACKET
+ || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET
+ || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET
+ || codePoint == Constants.CODE_PLUS
+ || codePoint == Constants.CODE_PERCENT
+ || Character.getType(codePoint) == Character.OTHER_SYMBOL;
+ }
+
+ /**
+ * Performs a recapitalization event.
+ * @param settingsValues The current settings values.
+ */
+ private void performRecapitalization(final SettingsValues settingsValues) {
+ if (!mConnection.hasSelection()) {
+ return; // No selection
+ }
+ // If we have a recapitalize in progress, use it; otherwise, create a new one.
+ if (!mRecapitalizeStatus.isActive()
+ || !mRecapitalizeStatus.isSetAt(mConnection.getExpectedSelectionStart(),
+ mConnection.getExpectedSelectionEnd())) {
+ final CharSequence selectedText =
+ mConnection.getSelectedText(0 /* flags, 0 for no styles */);
+ if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection
+ mRecapitalizeStatus.initialize(mConnection.getExpectedSelectionStart(),
+ mConnection.getExpectedSelectionEnd(), selectedText.toString(),
+ settingsValues.mLocale,
+ settingsValues.mSpacingAndPunctuations.mSortedWordSeparators);
+ // We trim leading and trailing whitespace.
+ mRecapitalizeStatus.trim();
+ }
+ mConnection.finishComposingText();
+ mRecapitalizeStatus.rotate();
+ final int numCharsDeleted = mConnection.getExpectedSelectionEnd()
+ - mConnection.getExpectedSelectionStart();
+ mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
+ mConnection.getExpectedSelectionEnd());
+ mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0);
+ mConnection.setSelection(mRecapitalizeStatus.getNewCursorStart(),
+ mRecapitalizeStatus.getNewCursorEnd());
+ }
+
+ private void performAdditionToUserHistoryDictionary(final SettingsValues settingsValues,
+ final String suggestion, final String prevWord) {
+ // 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.
+ if (!settingsValues.mCorrectionEnabled) return;
+
+ if (TextUtils.isEmpty(suggestion)) return;
+ final boolean wasAutoCapitalized =
+ mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps();
+ final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
+ System.currentTimeMillis());
+ mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWord,
+ timeStampInSeconds);
+ }
+
+ public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) {
+ // Check if we have a suggestion engine attached.
+ if (!settingsValues.isSuggestionsRequested()) {
+ if (mWordComposer.isComposingWord()) {
+ Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
+ + "requested!");
+ }
+ return;
+ }
+
+ if (!mWordComposer.isComposingWord() && !settingsValues.mBigramPredictionEnabled) {
+ mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
+ return;
+ }
+
+ final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<SuggestedWords>();
+ mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING,
+ SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
+ @Override
+ public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
+ final String typedWord = mWordComposer.getTypedWord();
+ // Show new suggestions if we have at least one. Otherwise keep the old
+ // suggestions with the new typed word. Exception: if the length of the
+ // typed word is <= 1 (after a deletion typically) we clear old suggestions.
+ if (suggestedWords.size() > 1 || typedWord.length() <= 1) {
+ holder.set(suggestedWords);
+ } else {
+ holder.set(retrieveOlderSuggestions(typedWord, mSuggestedWords));
+ }
+ }
+ }
+ );
+
+ // This line may cause the current thread to wait.
+ final SuggestedWords suggestedWords = holder.get(null,
+ Constants.GET_SUGGESTED_WORDS_TIMEOUT);
+ if (suggestedWords != null) {
+ mSuggestionStripViewAccessor.showSuggestionStrip(suggestedWords);
+ }
+ }
+
+ /**
+ * Check if the cursor is touching a word. If so, restart suggestions on this word, else
+ * do nothing.
+ *
+ * @param settingsValues the current values of the settings.
+ * @param includeResumedWordInSuggestions 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 includeResumedWordInSuggestions) {
+ // HACK: We may want to special-case some apps that exhibit bad behavior in case of
+ // recorrection. This is a temporary, stopgap measure that will be removed later.
+ // TODO: remove this.
+ if (settingsValues.isBrokenByRecorrection()
+ // Recorrection is not supported in languages without spaces because we don't know
+ // how to segment them yet.
+ || !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
+ // If no suggestions are requested, don't try restarting suggestions.
+ || !settingsValues.isSuggestionsRequested()
+ // If we are currently in a batch input, we must not resume suggestions, or the result
+ // of the batch input will replace the new composition. This may happen in the corner case
+ // that the app moves the cursor on its own accord during a batch input.
+ || mInputLogicHandler.isInBatchInput()
+ // If the cursor is not touching a word, or if there is a selection, return right away.
+ || mConnection.hasSelection()
+ // If we don't know the cursor location, return.
+ || mConnection.getExpectedSelectionStart() < 0) {
+ mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
+ return;
+ }
+ final int expectedCursorPosition = mConnection.getExpectedSelectionStart();
+ if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)) {
+ // Show predictions.
+ mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
+ WordComposer.CAPS_MODE_OFF,
+ getNthPreviousWordForSuggestion(settingsValues.mSpacingAndPunctuations, 1));
+ mLatinIME.mHandler.postUpdateSuggestionStrip();
+ return;
+ }
+ final TextRange range = mConnection.getWordRangeAtCursor(
+ settingsValues.mSpacingAndPunctuations.mSortedWordSeparators,
+ 0 /* additionalPrecedingWordsCount */);
+ if (null == range) return; // Happens if we don't have an input connection at all
+ if (range.length() <= 0) return; // Race condition. No text to resume on, so bail out.
+ // If for some strange reason (editor bug or so) we measure the text before the cursor as
+ // longer than what the entire text is supposed to be, the safe thing to do is bail out.
+ if (range.mHasUrlSpans) return; // If there are links, we don't resume suggestions. Making
+ // edits to a linkified text through batch commands would ruin the URL spans, and unless
+ // we take very complicated steps to preserve the whole link, we can't do things right so
+ // we just do not resume because it's safer.
+ final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor();
+ if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return;
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
+ final String typedWord = range.mWord.toString();
+ if (includeResumedWordInSuggestions) {
+ 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;
+ }
+ int i = 0;
+ for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) {
+ for (final String s : span.getSuggestions()) {
+ ++i;
+ if (!TextUtils.equals(s, typedWord)) {
+ suggestions.add(new SuggestedWordInfo(s,
+ SuggestedWords.MAX_SUGGESTIONS - i,
+ SuggestedWordInfo.KIND_RESUMED, Dictionary.DICTIONARY_RESUMED,
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE
+ /* autoCommitFirstWordConfidence */));
+ }
+ }
+ }
+ final int[] codePoints = StringUtils.toCodePointArray(typedWord);
+ mWordComposer.setComposingWord(codePoints,
+ mLatinIME.getCoordinatesForCurrentKeyboard(codePoints),
+ getNthPreviousWordForSuggestion(settingsValues.mSpacingAndPunctuations,
+ // We want the previous word 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.
+ 0 == numberOfCharsInWordBeforeCursor ? 1 : 2));
+ mWordComposer.setCursorPositionWithinWord(
+ typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
+ mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
+ expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor());
+ if (suggestions.isEmpty()) {
+ // We come here if there weren't any suggestion spans on this word. We will try to
+ // compute suggestions for it instead.
+ mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING,
+ SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
+ @Override
+ public void onGetSuggestedWords(
+ final SuggestedWords suggestedWordsIncludingTypedWord) {
+ final SuggestedWords suggestedWords;
+ if (suggestedWordsIncludingTypedWord.size() > 1
+ && !includeResumedWordInSuggestions) {
+ // 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 #getSuggestedWordsExcludingTypedWord() method sets
+ // willAutoCorrect to false.
+ suggestedWords = suggestedWordsIncludingTypedWord
+ .getSuggestedWordsExcludingTypedWord();
+ } else {
+ // No saved suggestions, and we were unable to compute any good one
+ // either. Rather than displaying an empty suggestion strip, we'll
+ // display the original word alone in the middle.
+ // Since there is only one word, willAutoCorrect is false.
+ suggestedWords = suggestedWordsIncludingTypedWord;
+ }
+ mIsAutoCorrectionIndicatorOn = false;
+ mLatinIME.mHandler.showSuggestionStrip(suggestedWords);
+ }});
+ } else {
+ // We found suggestion spans in the word. We'll create the SuggestedWords out of
+ // them, and make willAutoCorrect false. We make typedWordValid false, because the
+ // color of the word in the suggestion strip changes according to this parameter,
+ // and false gives the correct color.
+ final SuggestedWords suggestedWords = new SuggestedWords(suggestions,
+ null /* rawSuggestions */, typedWord,
+ false /* typedWordValid */, false /* willAutoCorrect */,
+ false /* isObsoleteSuggestions */, false /* isPrediction */,
+ SuggestedWords.NOT_A_SEQUENCE_NUMBER);
+ mIsAutoCorrectionIndicatorOn = false;
+ mLatinIME.mHandler.showSuggestionStrip(suggestedWords);
+ }
+ }
+
+ /**
+ * Reverts a previous commit with auto-correction.
+ *
+ * This is triggered upon pressing backspace just after a commit with auto-correction.
+ *
+ * @param inputTransaction The transaction in progress.
+ */
+ private void revertCommit(final InputTransaction inputTransaction) {
+ final String previousWord = mLastComposedWord.mPrevWord;
+ final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord;
+ final CharSequence committedWord = mLastComposedWord.mCommittedWord;
+ final String committedWordString = committedWord.toString();
+ final int cancelLength = committedWord.length();
+ // We want java chars, not codepoints for the following.
+ final int separatorLength = mLastComposedWord.mSeparatorString.length();
+ // TODO: should we check our saved separator against the actual contents of the text view?
+ final int deleteLength = cancelLength + separatorLength;
+ if (LatinImeLogger.sDBG) {
+ if (mWordComposer.isComposingWord()) {
+ throw new RuntimeException("revertCommit, but we are composing a word");
+ }
+ final CharSequence wordBeforeCursor =
+ mConnection.getTextBeforeCursor(deleteLength, 0).subSequence(0, cancelLength);
+ if (!TextUtils.equals(committedWord, wordBeforeCursor)) {
+ throw new RuntimeException("revertCommit check failed: we thought we were "
+ + "reverting \"" + committedWord
+ + "\", but before the cursor we found \"" + wordBeforeCursor + "\"");
+ }
+ }
+ mConnection.deleteSurroundingText(deleteLength, 0);
+ if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
+ mSuggest.mDictionaryFacilitator.cancelAddingUserHistory(
+ previousWord, committedWordString);
+ }
+ final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
+ final SpannableString textToCommit = new SpannableString(stringToCommit);
+ if (committedWord instanceof SpannableString) {
+ final SpannableString committedWordWithSuggestionSpans = (SpannableString)committedWord;
+ final Object[] spans = committedWordWithSuggestionSpans.getSpans(0,
+ committedWord.length(), Object.class);
+ final int lastCharIndex = textToCommit.length() - 1;
+ // We will collect all suggestions in the following array.
+ final ArrayList<String> suggestions = CollectionUtils.newArrayList();
+ // First, add the committed word to the list of suggestions.
+ suggestions.add(committedWordString);
+ for (final Object span : spans) {
+ // If this is a suggestion span, we check that the locale is the right one, and
+ // that the word is not the committed word. That should mostly be the case.
+ // Given this, we add it to the list of suggestions, otherwise we discard it.
+ if (span instanceof SuggestionSpan) {
+ final SuggestionSpan suggestionSpan = (SuggestionSpan)span;
+ if (!suggestionSpan.getLocale().equals(
+ inputTransaction.mSettingsValues.mLocale.toString())) {
+ continue;
+ }
+ for (final String suggestion : suggestionSpan.getSuggestions()) {
+ if (!suggestion.equals(committedWordString)) {
+ suggestions.add(suggestion);
+ }
+ }
+ } else {
+ // If this is not a suggestion span, we just add it as is.
+ textToCommit.setSpan(span, 0 /* start */, lastCharIndex /* end */,
+ committedWordWithSuggestionSpans.getSpanFlags(span));
+ }
+ }
+ // Add the suggestion list to the list of suggestions.
+ textToCommit.setSpan(new SuggestionSpan(inputTransaction.mSettingsValues.mLocale,
+ suggestions.toArray(new String[suggestions.size()]), 0 /* flags */),
+ 0 /* start */, lastCharIndex /* end */, 0 /* flags */);
+ }
+ if (inputTransaction.mSettingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) {
+ // For languages with spaces, we revert to the typed string, but the cursor is still
+ // after the separator so we don't resume suggestions. If the user wants to correct
+ // the word, they have to press backspace again.
+ mConnection.commitText(textToCommit, 1);
+ } 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.
+ final int[] codePoints = StringUtils.toCodePointArray(stringToCommit);
+ mWordComposer.setComposingWord(codePoints,
+ mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), previousWord);
+ mConnection.setComposingText(textToCommit, 1);
+ }
+ if (inputTransaction.mSettingsValues.mIsInternal) {
+ LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+ }
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_revertCommit(committedWord.toString(),
+ originallyTypedWord.toString(),
+ mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString);
+ }
+ // Don't restart suggestion yet. We'll restart if the user deletes the
+ // separator.
+ mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+ // We have a separator between the word and the cursor: we should show predictions.
+ inputTransaction.setRequiresUpdateSuggestions();
+ }
+
+ /**
+ * Factor in auto-caps and manual caps and compute the current caps mode.
+ * @param settingsValues the current settings values.
+ * @param keyboardShiftMode the current shift mode of the keyboard. See
+ * KeyboardSwitcher#getKeyboardShiftMode() for possible values.
+ * @return the actual caps mode the keyboard is in right now.
+ */
+ private int getActualCapsMode(final SettingsValues settingsValues,
+ final int keyboardShiftMode) {
+ if (keyboardShiftMode != WordComposer.CAPS_MODE_AUTO_SHIFTED) {
+ return keyboardShiftMode;
+ }
+ final int auto = getCurrentAutoCapsState(settingsValues);
+ if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
+ return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
+ }
+ if (0 != auto) {
+ return WordComposer.CAPS_MODE_AUTO_SHIFTED;
+ }
+ return WordComposer.CAPS_MODE_OFF;
+ }
+
+ /**
+ * Gets the current auto-caps state, factoring in the space state.
+ *
+ * This method tries its best to do this in the most efficient possible manner. It avoids
+ * getting text from the editor if possible at all.
+ * This is called from the KeyboardSwitcher (through a trampoline in LatinIME) because it
+ * needs to know auto caps state to display the right layout.
+ *
+ * @param settingsValues the relevant settings values
+ * @return a caps mode from TextUtils.CAP_MODE_* or Constants.TextUtils.CAP_MODE_OFF.
+ */
+ public int getCurrentAutoCapsState(final SettingsValues settingsValues) {
+ if (!settingsValues.mAutoCap) return Constants.TextUtils.CAP_MODE_OFF;
+
+ final EditorInfo ei = getCurrentInputEditorInfo();
+ if (ei == null) return Constants.TextUtils.CAP_MODE_OFF;
+ final int inputType = ei.inputType;
+ // Warning: this depends on mSpaceState, which may not be the most current value. If
+ // mSpaceState gets updated later, whoever called this may need to be told about it.
+ return mConnection.getCursorCapsMode(inputType, settingsValues.mSpacingAndPunctuations,
+ SpaceState.PHANTOM == mSpaceState);
+ }
+
+ public int getCurrentRecapitalizeState() {
+ if (!mRecapitalizeStatus.isActive()
+ || !mRecapitalizeStatus.isSetAt(mConnection.getExpectedSelectionStart(),
+ mConnection.getExpectedSelectionEnd())) {
+ // Not recapitalizing at the moment
+ return RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
+ }
+ return mRecapitalizeStatus.getCurrentMode();
+ }
+
+ /**
+ * @return the editor info for the current editor
+ */
+ private EditorInfo getCurrentInputEditorInfo() {
+ return mLatinIME.getCurrentInputEditorInfo();
+ }
+
+ /**
+ * Get 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 nth previous word before the cursor.
+ */
+ // TODO: Make this private
+ public CharSequence getNthPreviousWordForSuggestion(
+ 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 from textview.
+ return mConnection.getNthPreviousWord(spacingAndPunctuations, nthPreviousWord);
+ } else {
+ return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
+ : mLastComposedWord.mCommittedWord;
+ }
+ }
+
+ /**
+ * Tests the passed word for resumability.
+ *
+ * We can resume suggestions on words whose first code point is a word code point (with some
+ * nuances: check the code for details).
+ *
+ * @param settings the current values of the settings.
+ * @param word the word to evaluate.
+ * @return whether it's fine to resume suggestions on this word.
+ */
+ private static boolean isResumableWord(final SettingsValues settings, final String word) {
+ final int firstCodePoint = word.codePointAt(0);
+ return settings.isWordCodePoint(firstCodePoint)
+ && Constants.CODE_SINGLE_QUOTE != firstCodePoint
+ && Constants.CODE_DASH != firstCodePoint;
+ }
+
+ /**
+ * @param actionId the action to perform
+ */
+ private void performEditorAction(final int actionId) {
+ mConnection.performEditorAction(actionId);
+ }
+
+ /**
+ * Perform the processing specific to inputting TLDs.
+ *
+ * Some keys input a TLD (specifically, the ".com" key) and this warrants some specific
+ * processing. First, if this is a TLD, we ignore PHANTOM spaces -- this is done by type
+ * of character in onCodeInput, but since this gets inputted as a whole string we need to
+ * do it here specifically. Then, if the last character before the cursor is a period, then
+ * we cut the dot at the start of ".com". This is because humans tend to type "www.google."
+ * and then press the ".com" key and instinctively don't expect to get "www.google..com".
+ *
+ * @param text the raw text supplied to onTextInput
+ * @return the text to actually send to the editor
+ */
+ private String performSpecificTldProcessingOnTextInput(final String text) {
+ if (text.length() <= 1 || text.charAt(0) != Constants.CODE_PERIOD
+ || !Character.isLetter(text.charAt(1))) {
+ // Not a tld: do nothing.
+ return text;
+ }
+ // We have a TLD (or something that looks like this): make sure we don't add
+ // a space even if currently in phantom mode.
+ mSpaceState = SpaceState.NONE;
+ final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
+ // If no code point, #getCodePointBeforeCursor returns NOT_A_CODE_POINT.
+ if (Constants.CODE_PERIOD == codePointBeforeCursor) {
+ return text.substring(1);
+ } else {
+ return text;
+ }
+ }
+
+ /**
+ * Handle a press on the settings key.
+ */
+ private void onSettingsKeyPressed() {
+ mLatinIME.displaySettingsDialog();
+ }
+
+ /**
+ * Resets the whole input state to the starting state.
+ *
+ * This will clear the composing word, reset the last composed word, clear the suggestion
+ * strip and tell the input connection about it so that it can refresh its caches.
+ *
+ * @param newSelStart the new selection start, in java characters.
+ * @param newSelEnd the new selection end, in java characters.
+ * @param clearSuggestionStrip whether this method should clear the suggestion strip.
+ */
+ // TODO: how is this different from startInput ?!
+ private void resetEntireInputState(final int newSelStart, final int newSelEnd,
+ final boolean clearSuggestionStrip) {
+ final boolean shouldFinishComposition = mWordComposer.isComposingWord();
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ if (clearSuggestionStrip) {
+ mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
+ }
+ mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd,
+ shouldFinishComposition);
+ }
+
+ /**
+ * Resets only the composing state.
+ *
+ * Compare #resetEntireInputState, which also clears the suggestion strip and resets the
+ * input connection caches. This only deals with the composing state.
+ *
+ * @param alsoResetLastComposedWord whether to also reset the last composed word.
+ */
+ private void resetComposingState(final boolean alsoResetLastComposedWord) {
+ mWordComposer.reset();
+ if (alsoResetLastComposedWord) {
+ mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+ }
+ }
+
+ /**
+ * Make a {@link com.android.inputmethod.latin.SuggestedWords} object containing a typed word
+ * and obsolete suggestions.
+ * See {@link com.android.inputmethod.latin.SuggestedWords#getTypedWordAndPreviousSuggestions(
+ * String, com.android.inputmethod.latin.SuggestedWords)}.
+ * @param typedWord The typed word as a string.
+ * @param previousSuggestedWords The previously suggested words.
+ * @return Obsolete suggestions with the newly typed word.
+ */
+ private SuggestedWords retrieveOlderSuggestions(final String typedWord,
+ final SuggestedWords previousSuggestedWords) {
+ final SuggestedWords oldSuggestedWords =
+ previousSuggestedWords.isPunctuationSuggestions() ? SuggestedWords.EMPTY
+ : previousSuggestedWords;
+ final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
+ SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords);
+ return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */,
+ false /* typedWordValid */, false /* hasAutoCorrectionCandidate */,
+ true /* isObsoleteSuggestions */, false /* isPrediction */);
+ }
+
+ /**
+ * Gets a chunk of text with or the auto-correction indicator underline span as appropriate.
+ *
+ * This method looks at the old state of the auto-correction indicator to put or not put
+ * the underline span as appropriate. It is important to note that this does not correspond
+ * exactly to whether this word will be auto-corrected to or not: what's important here is
+ * to keep the same indication as before.
+ * When we add a new code point to a composing word, we don't know yet if we are going to
+ * auto-correct it until the suggestions are computed. But in the mean time, we still need
+ * to display the character and to extend the previous underline. To avoid any flickering,
+ * the underline should keep the same color it used to have, even if that's not ultimately
+ * the correct color for this new word. When the suggestions are finished evaluating, we
+ * will call this method again to fix the color of the underline.
+ *
+ * @param text the text on which to maybe apply the span.
+ * @return the same text, with the auto-correction underline span if that's appropriate.
+ */
+ // TODO: Shouldn't this go in some *Utils class instead?
+ private CharSequence getTextWithUnderline(final String text) {
+ return mIsAutoCorrectionIndicatorOn
+ ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(mLatinIME, text)
+ : text;
+ }
+
+ /**
+ * Sends a DOWN key event followed by an UP key event to the editor.
+ *
+ * If possible at all, avoid using this method. It causes all sorts of race conditions with
+ * the text view because it goes through a different, asynchronous binder. Also, batch edits
+ * are ignored for key events. Use the normal software input methods instead.
+ *
+ * @param keyCode the key code to send inside the key event.
+ */
+ private void sendDownUpKeyEvent(final int keyCode) {
+ final long eventTime = SystemClock.uptimeMillis();
+ mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
+ KeyEvent.ACTION_DOWN, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
+ mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ KeyEvent.ACTION_UP, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
+ }
+
+ /**
+ * Sends a code point to the editor, using the most appropriate method.
+ *
+ * Normally we send code points with commitText, but there are some cases (where backward
+ * compatibility is a concern for example) where we want to use deprecated methods.
+ *
+ * @param settingsValues the current values of the settings.
+ * @param codePoint the code point to send.
+ */
+ // TODO: replace these two parameters with an InputTransaction
+ private void sendKeyCodePoint(final SettingsValues settingsValues, final int codePoint) {
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_sendKeyCodePoint(codePoint);
+ }
+ // TODO: Remove this special handling of digit letters.
+ // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
+ if (codePoint >= '0' && codePoint <= '9') {
+ sendDownUpKeyEvent(codePoint - '0' + KeyEvent.KEYCODE_0);
+ return;
+ }
+
+ // TODO: we should do this also when the editor has TYPE_NULL
+ if (Constants.CODE_ENTER == codePoint && settingsValues.isBeforeJellyBean()) {
+ // 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.
+ sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER);
+ } else {
+ mConnection.commitText(StringUtils.newSingleCodePointString(codePoint), 1);
+ }
+ }
+
+ /**
+ * Promote a phantom space to an actual space.
+ *
+ * This essentially inserts a space, and that's it. It just checks the options and the text
+ * before the cursor are appropriate before doing it.
+ *
+ * @param settingsValues the current values of the settings.
+ */
+ private void promotePhantomSpace(final SettingsValues settingsValues) {
+ if (settingsValues.shouldInsertSpacesAutomatically()
+ && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
+ && !mConnection.textBeforeCursorLooksLikeURL()) {
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_promotePhantomSpace();
+ }
+ sendKeyCodePoint(settingsValues, Constants.CODE_SPACE);
+ }
+ }
+
+ /**
+ * Do the final processing after a batch input has ended. This commits the word to the editor.
+ * @param settingsValues the current values of the settings.
+ * @param suggestedWords suggestedWords to use.
+ */
+ public void onUpdateTailBatchInputCompleted(final SettingsValues settingsValues,
+ final SuggestedWords suggestedWords,
+ // TODO: remove this argument
+ final KeyboardSwitcher keyboardSwitcher) {
+ final String batchInputText = suggestedWords.isEmpty() ? null : suggestedWords.getWord(0);
+ if (TextUtils.isEmpty(batchInputText)) {
+ return;
+ }
+ mConnection.beginBatchEdit();
+ if (SpaceState.PHANTOM == mSpaceState) {
+ promotePhantomSpace(settingsValues);
+ }
+ final SuggestedWordInfo autoCommitCandidate = mSuggestedWords.getAutoCommitCandidate();
+ // Commit except the last word for phrase gesture if the top suggestion is eligible for auto
+ // commit.
+ if (settingsValues.mPhraseGestureEnabled && null != autoCommitCandidate) {
+ // Find the last space
+ final int indexOfLastSpace = batchInputText.lastIndexOf(Constants.CODE_SPACE) + 1;
+ if (0 != indexOfLastSpace) {
+ mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), 1);
+ final SuggestedWords suggestedWordsForLastWordOfPhraseGesture =
+ suggestedWords.getSuggestedWordsForLastWordOfPhraseGesture();
+ mLatinIME.showSuggestionStrip(suggestedWordsForLastWordOfPhraseGesture);
+ }
+ final String lastWord = batchInputText.substring(indexOfLastSpace);
+ mWordComposer.setBatchInputWord(lastWord);
+ mConnection.setComposingText(lastWord, 1);
+ } else {
+ mWordComposer.setBatchInputWord(batchInputText);
+ mConnection.setComposingText(batchInputText, 1);
+ }
+ mConnection.endBatchEdit();
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords);
+ }
+ // Space state must be updated before calling updateShiftState
+ mSpaceState = SpaceState.PHANTOM;
+ keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(settingsValues),
+ getCurrentRecapitalizeState());
+ }
+
+ /**
+ * Commit the typed string to the editor.
+ *
+ * This is typically called when we should commit the currently composing word without applying
+ * auto-correction to it. Typically, we come here upon pressing a separator when the keyboard
+ * is configured to not do auto-correction at all (because of the settings or the properties of
+ * the editor). In this case, `separatorString' is set to the separator that was pressed.
+ * We also come here in a variety of cases with external user action. For example, when the
+ * cursor is moved while there is a composition, or when the keyboard is closed, or when the
+ * user presses the Send button for an SMS, we don't auto-correct as that would be unexpected.
+ * In this case, `separatorString' is set to NOT_A_SEPARATOR.
+ *
+ * @param settingsValues the current values of the settings.
+ * @param separatorString the separator that's causing the commit, or NOT_A_SEPARATOR if none.
+ */
+ // TODO: Make this private
+ public void commitTyped(final SettingsValues settingsValues, final String separatorString) {
+ if (!mWordComposer.isComposingWord()) return;
+ final String typedWord = mWordComposer.getTypedWord();
+ if (typedWord.length() > 0) {
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
+ }
+ commitChosenWord(settingsValues, typedWord,
+ LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString);
+ }
+ }
+
+ /**
+ * Commit the current auto-correction.
+ *
+ * This will commit the best guess of the keyboard regarding what the user meant by typing
+ * the currently composing word. The IME computes suggestions and assigns a confidence score
+ * to each of them; when it's confident enough in one suggestion, it replaces the typed string
+ * by this suggestion at commit time. When it's not confident enough, or when it has no
+ * suggestions, or when the settings or environment does not allow for auto-correction, then
+ * this method just commits the typed string.
+ * Note that if suggestions are currently being computed in the background, this method will
+ * block until the computation returns. This is necessary for consistency (it would be very
+ * strange if pressing space would commit a different word depending on how fast you press).
+ *
+ * @param settingsValues the current value of the settings.
+ * @param separator the separator that's causing the commit to happen.
+ */
+ private void commitCurrentAutoCorrection(final SettingsValues settingsValues,
+ final String separator,
+ // TODO: Remove this argument.
+ final LatinIME.UIHandler handler) {
+ // Complete any pending suggestions query first
+ if (handler.hasPendingUpdateSuggestions()) {
+ handler.cancelUpdateSuggestionStrip();
+ performUpdateSuggestionStripSync(settingsValues);
+ }
+ final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
+ final String typedWord = mWordComposer.getTypedWord();
+ final String autoCorrection = (typedAutoCorrection != null)
+ ? typedAutoCorrection : typedWord;
+ if (autoCorrection != null) {
+ if (TextUtils.isEmpty(typedWord)) {
+ throw new RuntimeException("We have an auto-correction but the typed word "
+ + "is empty? Impossible! I must commit suicide.");
+ }
+ if (settingsValues.mIsInternal) {
+ LatinImeLoggerUtils.onAutoCorrection(
+ typedWord, autoCorrection, separator, mWordComposer);
+ }
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ final SuggestedWords suggestedWords = mSuggestedWords;
+ ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
+ separator, mWordComposer.isBatchMode(), suggestedWords);
+ }
+ commitChosenWord(settingsValues, autoCorrection,
+ LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator);
+ if (!typedWord.equals(autoCorrection)) {
+ // This will make the correction flash for a short while as a visual clue
+ // to the user that auto-correction happened. It has no other effect; in particular
+ // note that this won't affect the text inside the text field AT ALL: it only makes
+ // the segment of text starting at the supplied index and running for the length
+ // of the auto-correction flash. At this moment, the "typedWord" argument is
+ // ignored by TextView.
+ mConnection.commitCorrection(new CorrectionInfo(
+ mConnection.getExpectedSelectionEnd() - autoCorrection.length(),
+ typedWord, autoCorrection));
+ }
+ }
+ }
+
+ /**
+ * Commits the chosen word to the text field and saves it for later retrieval.
+ *
+ * @param settingsValues the current values of the settings.
+ * @param chosenWord the word we want to commit.
+ * @param commitType the type of the commit, as one of LastComposedWord.COMMIT_TYPE_*
+ * @param separatorString the separator that's causing the commit, or NOT_A_SEPARATOR if none.
+ */
+ private void commitChosenWord(final SettingsValues settingsValues, final String chosenWord,
+ final int commitType, final String separatorString) {
+ final SuggestedWords suggestedWords = mSuggestedWords;
+ final CharSequence chosenWordWithSuggestions =
+ SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
+ suggestedWords);
+ mConnection.commitText(chosenWordWithSuggestions, 1);
+ // TODO: we pass 2 here, but would it be better to move this above and pass 1 instead?
+ final String prevWord = mConnection.getNthPreviousWord(
+ settingsValues.mSpacingAndPunctuations, 2);
+ // Add the word to the user history dictionary
+ performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWord);
+ // 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, prevWord);
+ final boolean shouldDiscardPreviousWordForSuggestion;
+ if (0 == StringUtils.codePointCount(separatorString)) {
+ // Separator is 0-length, we can keep the previous word for suggestion. Either this
+ // was a manual pick or the language has no spaces in which case we want to keep the
+ // previous word, or it was the keyboard closing or the cursor moving in which case it
+ // will be reset anyway.
+ shouldDiscardPreviousWordForSuggestion = false;
+ } else {
+ // Otherwise, we discard if the separator contains any non-whitespace.
+ shouldDiscardPreviousWordForSuggestion =
+ !StringUtils.containsOnlyWhitespace(separatorString);
+ }
+ if (shouldDiscardPreviousWordForSuggestion) {
+ mWordComposer.discardPreviousWordForSuggestion();
+ }
+ }
+
+ /**
+ * Retry resetting caches in the rich input connection.
+ *
+ * When the editor can't be accessed we can't reset the caches, so we schedule a retry.
+ * This method handles the retry, and re-schedules a new retry if we still can't access.
+ * We only retry up to 5 times before giving up.
+ *
+ * @param settingsValues the current values of the settings.
+ * @param tryResumeSuggestions Whether we should resume suggestions or not.
+ * @param remainingTries How many times we may try again before giving up.
+ * @return whether true if the caches were successfully reset, false otherwise.
+ */
+ // TODO: make this private
+ public boolean retryResetCachesAndReturnSuccess(final SettingsValues settingsValues,
+ final boolean tryResumeSuggestions, final int remainingTries,
+ // TODO: remove these arguments
+ final LatinIME.UIHandler handler) {
+ if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(
+ mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd(),
+ false /* shouldFinishComposition */)) {
+ if (0 < remainingTries) {
+ handler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
+ return false;
+ }
+ // If remainingTries is 0, we should stop waiting for new tries, however we'll still
+ // return true as we need to perform other tasks (for example, loading the keyboard).
+ }
+ mConnection.tryFixLyingCursorPosition();
+ if (tryResumeSuggestions) {
+ handler.postResumeSuggestions();
+ }
+ return true;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
new file mode 100644
index 000000000..9dbe2c38b
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.inputlogic;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+
+import com.android.inputmethod.compat.LooperCompatUtils;
+import com.android.inputmethod.latin.InputPointers;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
+
+/**
+ * A helper to manage deferred tasks for the input logic.
+ */
+class InputLogicHandler implements Handler.Callback {
+ final Handler mNonUIThreadHandler;
+ // TODO: remove this reference.
+ final LatinIME mLatinIME;
+ final InputLogic mInputLogic;
+ private final Object mLock = new Object();
+ private boolean mInBatchInput; // synchronized using {@link #mLock}.
+
+ private static final int MSG_GET_SUGGESTED_WORDS = 1;
+
+ // A handler that never does anything. This is used for cases where events come before anything
+ // is initialized, though probably only the monkey can actually do this.
+ public static final InputLogicHandler NULL_HANDLER = new InputLogicHandler() {
+ @Override
+ public void reset() {}
+ @Override
+ public boolean handleMessage(final Message msg) { return true; }
+ @Override
+ public void onStartBatchInput() {}
+ @Override
+ public void onUpdateBatchInput(final InputPointers batchPointers,
+ final int sequenceNumber) {}
+ @Override
+ public void onCancelBatchInput() {}
+ @Override
+ public void updateTailBatchInput(final InputPointers batchPointers,
+ final int sequenceNumber) {}
+ @Override
+ public void getSuggestedWords(final int sessionId, final int sequenceNumber,
+ final OnGetSuggestedWordsCallback callback) {}
+ };
+
+ private InputLogicHandler() {
+ mNonUIThreadHandler = null;
+ mLatinIME = null;
+ mInputLogic = null;
+ }
+
+ public InputLogicHandler(final LatinIME latinIME, final InputLogic inputLogic) {
+ final HandlerThread handlerThread = new HandlerThread(
+ InputLogicHandler.class.getSimpleName());
+ handlerThread.start();
+ mNonUIThreadHandler = new Handler(handlerThread.getLooper(), this);
+ mLatinIME = latinIME;
+ mInputLogic = inputLogic;
+ }
+
+ public void reset() {
+ mNonUIThreadHandler.removeCallbacksAndMessages(null);
+ }
+
+ // In unit tests, we create several instances of LatinIME, which results in several instances
+ // of InputLogicHandler. To avoid these handlers lingering, we call this.
+ public void destroy() {
+ LooperCompatUtils.quitSafely(mNonUIThreadHandler.getLooper());
+ }
+
+ /**
+ * Handle a message.
+ * @see android.os.Handler.Callback#handleMessage(android.os.Message)
+ */
+ // Called on the Non-UI handler thread by the Handler code.
+ @Override
+ public boolean handleMessage(final Message msg) {
+ switch (msg.what) {
+ case MSG_GET_SUGGESTED_WORDS:
+ mLatinIME.getSuggestedWords(msg.arg1 /* sessionId */,
+ msg.arg2 /* sequenceNumber */, (OnGetSuggestedWordsCallback) msg.obj);
+ break;
+ }
+ return true;
+ }
+
+ // Called on the UI thread by InputLogic.
+ public void onStartBatchInput() {
+ synchronized (mLock) {
+ mInBatchInput = true;
+ }
+ }
+
+ public boolean isInBatchInput() {
+ return mInBatchInput;
+ }
+
+ /**
+ * Fetch suggestions corresponding to an update of a batch input.
+ * @param batchPointers the updated pointers, including the part that was passed last time.
+ * @param sequenceNumber the sequence number associated with this batch input.
+ * @param isTailBatchInput true if this is the end of a batch input, false if it's an update.
+ */
+ // This method can be called from any thread and will see to it that the correct threads
+ // are used for parts that require it. This method will send a message to the Non-UI handler
+ // thread to pull suggestions, and get the inlined callback to get called on the Non-UI
+ // handler thread. If this is the end of a batch input, the callback will then proceed to
+ // send a message to the UI handler in LatinIME so that showing suggestions can be done on
+ // the UI thread.
+ private void updateBatchInput(final InputPointers batchPointers,
+ final int sequenceNumber, final boolean isTailBatchInput) {
+ synchronized (mLock) {
+ if (!mInBatchInput) {
+ // Batch input has ended or canceled while the message was being delivered.
+ return;
+ }
+ mInputLogic.mWordComposer.setBatchInputPointers(batchPointers);
+ getSuggestedWords(Suggest.SESSION_GESTURE, sequenceNumber,
+ new OnGetSuggestedWordsCallback() {
+ @Override
+ public void onGetSuggestedWords(SuggestedWords suggestedWords) {
+ // We're now inside the callback. This always runs on the Non-UI thread,
+ // no matter what thread updateBatchInput was originally called on.
+ if (suggestedWords.isEmpty()) {
+ // Use old suggestions if we don't have any new ones.
+ // Previous suggestions are found in InputLogic#mSuggestedWords.
+ // Since these are the most recent ones and we just recomputed
+ // new ones to update them, then the previous ones are there.
+ suggestedWords = mInputLogic.mSuggestedWords;
+ }
+ mLatinIME.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWords,
+ isTailBatchInput /* dismissGestureFloatingPreviewText */);
+ if (isTailBatchInput) {
+ mInBatchInput = false;
+ // The following call schedules onEndBatchInputInternal
+ // to be called on the UI thread.
+ mLatinIME.mHandler.showTailBatchInputResult(suggestedWords);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Update a batch input.
+ *
+ * This fetches suggestions and updates the suggestion strip and the floating text preview.
+ *
+ * @param batchPointers the updated batch pointers.
+ * @param sequenceNumber the sequence number associated with this batch input.
+ */
+ // Called on the UI thread by InputLogic.
+ public void onUpdateBatchInput(final InputPointers batchPointers,
+ final int sequenceNumber) {
+ updateBatchInput(batchPointers, sequenceNumber, false /* isTailBatchInput */);
+ }
+
+ /**
+ * Cancel a batch input.
+ *
+ * Note that as opposed to updateTailBatchInput, we do the UI side of this immediately on the
+ * same thread, rather than get this to call a method in LatinIME. This is because
+ * canceling a batch input does not necessitate the long operation of pulling suggestions.
+ */
+ // Called on the UI thread by InputLogic.
+ public void onCancelBatchInput() {
+ synchronized (mLock) {
+ mInBatchInput = false;
+ }
+ }
+
+ /**
+ * Trigger an update for a tail batch input.
+ *
+ * A tail batch input is the last update for a gesture, the one that is triggered after the
+ * user lifts their finger. This method schedules fetching suggestions on the non-UI thread,
+ * then when the suggestions are computed it comes back on the UI thread to update the
+ * suggestion strip, commit the first suggestion, and dismiss the floating text preview.
+ *
+ * @param batchPointers the updated batch pointers.
+ * @param sequenceNumber the sequence number associated with this batch input.
+ */
+ // Called on the UI thread by InputLogic.
+ public void updateTailBatchInput(final InputPointers batchPointers,
+ final int sequenceNumber) {
+ updateBatchInput(batchPointers, sequenceNumber, true /* isTailBatchInput */);
+ }
+
+ public void getSuggestedWords(final int sessionId, final int sequenceNumber,
+ final OnGetSuggestedWordsCallback callback) {
+ mNonUIThreadHandler.obtainMessage(
+ MSG_GET_SUGGESTED_WORDS, sessionId, sequenceNumber, callback).sendToTarget();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/SpaceState.java b/java/src/com/android/inputmethod/latin/inputlogic/SpaceState.java
new file mode 100644
index 000000000..ce80c0016
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/inputlogic/SpaceState.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.inputlogic;
+
+/**
+ * Class for managing space states.
+ *
+ * At any given time, the input logic is in one of five possible space states. Depending on the
+ * current space state, some behavior will change; the prime example of this is the PHANTOM state,
+ * in which any subsequent letter input will input a space before the letter. Read on the
+ * description inside this class for each of the space states.
+ */
+public class SpaceState {
+ // None: the state where all the keyboard behavior is the most "standard" and no automatic
+ // input is added or removed. In this state, all self-inserting keys only insert themselves,
+ // and backspace removes one character.
+ public static final int NONE = 0;
+ // Double space: the state where the user pressed space twice quickly, which LatinIME
+ // resolved as period-space. In this state, pressing backspace will undo the
+ // double-space-to-period insertion: it will replace ". " with " ".
+ public static final int DOUBLE = 1;
+ // Swap punctuation: the state where a weak space and a punctuation from the suggestion strip
+ // have just been swapped. In this state, pressing backspace will undo the swap: the
+ // characters will be swapped back back, and the space state will go to WEAK.
+ public static final int SWAP_PUNCTUATION = 2;
+ // Weak space: a space that should be swapped only by suggestion strip punctuation. Weak
+ // spaces happen when the user presses space, accepting the current suggestion (whether
+ // it's an auto-correction or not). In this state, pressing a punctuation from the suggestion
+ // strip inserts it before the space (while it inserts it after the space in the NONE state).
+ public static final int WEAK = 3;
+ // Phantom space: a not-yet-inserted space that should get inserted on the next input,
+ // character provided it's not a separator. If it's a separator, the phantom space is dropped.
+ // Phantom spaces happen when a user chooses a word from the suggestion strip. In this state,
+ // non-separators insert a space before they get inserted.
+ public static final int PHANTOM = 4;
+
+ private SpaceState() {
+ // This class is not publicly instantiable.
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java
deleted file mode 100644
index fda97dafc..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.TreeMap;
-
-/**
- * A base class of the binary dictionary decoder.
- */
-public abstract class AbstractDictDecoder implements DictDecoder {
- protected FileHeader readHeader(final DictBuffer dictBuffer)
- throws IOException, UnsupportedFormatException {
- if (dictBuffer == null) {
- openDictBuffer();
- }
-
- final int version = HeaderReader.readVersion(dictBuffer);
- if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
- || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
- throw new UnsupportedFormatException("Unsupported version : " + version);
- }
- // TODO: Remove this field.
- final int optionsFlags = HeaderReader.readOptionFlags(dictBuffer);
-
- final int headerSize = HeaderReader.readHeaderSize(dictBuffer);
-
- if (headerSize < 0) {
- throw new UnsupportedFormatException("header size can't be negative.");
- }
-
- final HashMap<String, String> attributes = HeaderReader.readAttributes(dictBuffer,
- headerSize);
-
- final FileHeader header = new FileHeader(headerSize,
- new FusionDictionary.DictionaryOptions(attributes,
- 0 != (optionsFlags & FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG),
- 0 != (optionsFlags & FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG)),
- new FormatOptions(version,
- 0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE),
- 0 != (optionsFlags & FormatSpec.CONTAINS_TIMESTAMP_FLAG)));
- return header;
- }
-
- @Override @UsedForTesting
- public int getTerminalPosition(final String word)
- throws IOException, UnsupportedFormatException {
- if (!isDictBufferOpen()) {
- openDictBuffer();
- }
- return BinaryDictIOUtils.getTerminalPosition(this, word);
- }
-
- @Override @UsedForTesting
- public void readUnigramsAndBigramsBinary(final TreeMap<Integer, String> words,
- final TreeMap<Integer, Integer> frequencies,
- final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams)
- throws IOException, UnsupportedFormatException {
- if (!isDictBufferOpen()) {
- openDictBuffer();
- }
- BinaryDictIOUtils.readUnigramsAndBigramsBinary(this, words, frequencies, bigrams);
- }
-
- /**
- * A utility class for reading a file header.
- */
- protected static class HeaderReader {
- protected static int readVersion(final DictBuffer dictBuffer)
- throws IOException, UnsupportedFormatException {
- return BinaryDictDecoderUtils.checkFormatVersion(dictBuffer);
- }
-
- protected static int readOptionFlags(final DictBuffer dictBuffer) {
- return dictBuffer.readUnsignedShort();
- }
-
- protected static int readHeaderSize(final DictBuffer dictBuffer) {
- return dictBuffer.readInt();
- }
-
- protected static HashMap<String, String> readAttributes(final DictBuffer dictBuffer,
- final int headerSize) {
- final HashMap<String, String> attributes = new HashMap<String, String>();
- while (dictBuffer.position() < headerSize) {
- // We can avoid an infinite loop here since dictBuffer.position() is always
- // increased by calling CharEncoding.readString.
- final String key = CharEncoding.readString(dictBuffer);
- final String value = CharEncoding.readString(dictBuffer);
- attributes.put(key, value);
- }
- dictBuffer.position(headerSize);
- return attributes;
- }
- }
-
- /**
- * A utility class for reading a PtNode.
- */
- protected static class PtNodeReader {
- protected static int readPtNodeOptionFlags(final DictBuffer dictBuffer) {
- return dictBuffer.readUnsignedByte();
- }
-
- protected static int readParentAddress(final DictBuffer dictBuffer,
- final FormatOptions formatOptions) {
- if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
- return BinaryDictDecoderUtils.readSInt24(dictBuffer);
- } else {
- return FormatSpec.NO_PARENT_ADDRESS;
- }
- }
-
- protected static int readChildrenAddress(final DictBuffer dictBuffer, final int optionFlags,
- final FormatOptions formatOptions) {
- if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
- final int address = BinaryDictDecoderUtils.readSInt24(dictBuffer);
- if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS;
- return address;
- } else {
- switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) {
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE:
- return dictBuffer.readUnsignedByte();
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES:
- return dictBuffer.readUnsignedShort();
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES:
- return dictBuffer.readUnsignedInt24();
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS:
- default:
- return FormatSpec.NO_CHILDREN_ADDRESS;
- }
- }
- }
-
- // Reads shortcuts and returns the read length.
- protected static int readShortcut(final DictBuffer dictBuffer,
- final ArrayList<WeightedString> shortcutTargets) {
- final int pointerBefore = dictBuffer.position();
- dictBuffer.readUnsignedShort(); // skip the size
- while (true) {
- final int targetFlags = dictBuffer.readUnsignedByte();
- final String word = CharEncoding.readString(dictBuffer);
- shortcutTargets.add(new WeightedString(word,
- targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
- if (0 == (targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
- }
- return dictBuffer.position() - pointerBefore;
- }
-
- protected static int readBigramAddresses(final DictBuffer dictBuffer,
- final ArrayList<PendingAttribute> bigrams, final int baseAddress) {
- int readLength = 0;
- int bigramCount = 0;
- while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- final int bigramFlags = dictBuffer.readUnsignedByte();
- ++readLength;
- final int sign = 0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE)
- ? 1 : -1;
- int bigramAddress = baseAddress + readLength;
- switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) {
- case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE:
- bigramAddress += sign * dictBuffer.readUnsignedByte();
- readLength += 1;
- break;
- case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES:
- bigramAddress += sign * dictBuffer.readUnsignedShort();
- readLength += 2;
- break;
- case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES:
- bigramAddress += sign * dictBuffer.readUnsignedInt24();
- readLength += 3;
- break;
- default:
- throw new RuntimeException("Has bigrams with no address");
- }
- bigrams.add(new PendingAttribute(
- bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
- bigramAddress));
- if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
- }
- return readLength;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
deleted file mode 100644
index 216492b4d..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
+++ /dev/null
@@ -1,623 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * Decodes binary files for a FusionDictionary.
- *
- * All the methods in this class are static.
- *
- * TODO: Remove calls from classes except Ver3DictDecoder
- * TODO: Move this file to makedict/internal.
- * TODO: Rename this class to DictDecoderUtils.
- */
-public final class BinaryDictDecoderUtils {
-
- private static final boolean DBG = MakedictLog.DBG;
-
- private BinaryDictDecoderUtils() {
- // This utility class is not publicly instantiable.
- }
-
- private static final int MAX_JUMPS = 12;
-
- @UsedForTesting
- public interface DictBuffer {
- public int readUnsignedByte();
- public int readUnsignedShort();
- public int readUnsignedInt24();
- public int readInt();
- public int position();
- public void position(int newPosition);
- public void put(final byte b);
- public int limit();
- @UsedForTesting
- public int capacity();
- }
-
- public static final class ByteBufferDictBuffer implements DictBuffer {
- private ByteBuffer mBuffer;
-
- public ByteBufferDictBuffer(final ByteBuffer buffer) {
- mBuffer = buffer;
- }
-
- @Override
- public int readUnsignedByte() {
- return mBuffer.get() & 0xFF;
- }
-
- @Override
- public int readUnsignedShort() {
- return mBuffer.getShort() & 0xFFFF;
- }
-
- @Override
- public int readUnsignedInt24() {
- final int retval = readUnsignedByte();
- return (retval << 16) + readUnsignedShort();
- }
-
- @Override
- public int readInt() {
- return mBuffer.getInt();
- }
-
- @Override
- public int position() {
- return mBuffer.position();
- }
-
- @Override
- public void position(int newPos) {
- mBuffer.position(newPos);
- }
-
- @Override
- public void put(final byte b) {
- mBuffer.put(b);
- }
-
- @Override
- public int limit() {
- return mBuffer.limit();
- }
-
- @Override
- public int capacity() {
- return mBuffer.capacity();
- }
- }
-
- /**
- * A class grouping utility function for our specific character encoding.
- */
- static final class CharEncoding {
- private static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
- private static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF;
-
- /**
- * Helper method to find out whether this code fits on one byte
- */
- private static boolean fitsOnOneByte(final int character) {
- return character >= MINIMAL_ONE_BYTE_CHARACTER_VALUE
- && character <= MAXIMAL_ONE_BYTE_CHARACTER_VALUE;
- }
-
- /**
- * Compute the size of a character given its character code.
- *
- * Char format is:
- * 1 byte = bbbbbbbb match
- * case 000xxxxx: xxxxx << 16 + next byte << 8 + next byte
- * else: if 00011111 (= 0x1F) : this is the terminator. This is a relevant choice because
- * unicode code points range from 0 to 0x10FFFF, so any 3-byte value starting with
- * 00011111 would be outside unicode.
- * else: iso-latin-1 code
- * This allows for the whole unicode range to be encoded, including chars outside of
- * the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control
- * characters which should never happen anyway (and still work, but take 3 bytes).
- *
- * @param character the character code.
- * @return the size in binary encoded-form, either 1 or 3 bytes.
- */
- static int getCharSize(final int character) {
- // See char encoding in FusionDictionary.java
- if (fitsOnOneByte(character)) return 1;
- if (FormatSpec.INVALID_CHARACTER == character) return 1;
- return 3;
- }
-
- /**
- * Compute the byte size of a character array.
- */
- static int getCharArraySize(final int[] chars) {
- int size = 0;
- for (int character : chars) size += getCharSize(character);
- return size;
- }
-
- /**
- * Writes a char array to a byte buffer.
- *
- * @param codePoints the code point array to write.
- * @param buffer the byte buffer to write to.
- * @param index the index in buffer to write the character array to.
- * @return the index after the last character.
- */
- static int writeCharArray(final int[] codePoints, final byte[] buffer, int index) {
- for (int codePoint : codePoints) {
- if (1 == getCharSize(codePoint)) {
- buffer[index++] = (byte)codePoint;
- } else {
- buffer[index++] = (byte)(0xFF & (codePoint >> 16));
- buffer[index++] = (byte)(0xFF & (codePoint >> 8));
- buffer[index++] = (byte)(0xFF & codePoint);
- }
- }
- return index;
- }
-
- /**
- * Writes a string with our character format to a byte buffer.
- *
- * This will also write the terminator byte.
- *
- * @param buffer the byte buffer to write to.
- * @param origin the offset to write from.
- * @param word the string to write.
- * @return the size written, in bytes.
- */
- static int writeString(final byte[] buffer, final int origin,
- final String word) {
- final int length = word.length();
- int index = origin;
- for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
- final int codePoint = word.codePointAt(i);
- if (1 == getCharSize(codePoint)) {
- buffer[index++] = (byte)codePoint;
- } else {
- buffer[index++] = (byte)(0xFF & (codePoint >> 16));
- buffer[index++] = (byte)(0xFF & (codePoint >> 8));
- buffer[index++] = (byte)(0xFF & codePoint);
- }
- }
- buffer[index++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
- return index - origin;
- }
-
- /**
- * Writes a string with our character format to an OutputStream.
- *
- * This will also write the terminator byte.
- *
- * @param buffer the OutputStream to write to.
- * @param word the string to write.
- */
- static void writeString(final OutputStream buffer, final String word) throws IOException {
- final int length = word.length();
- for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
- final int codePoint = word.codePointAt(i);
- if (1 == getCharSize(codePoint)) {
- buffer.write((byte) codePoint);
- } else {
- buffer.write((byte) (0xFF & (codePoint >> 16)));
- buffer.write((byte) (0xFF & (codePoint >> 8)));
- buffer.write((byte) (0xFF & codePoint));
- }
- }
- buffer.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
- }
-
- /**
- * Reads a string from a DictBuffer. This is the converse of the above method.
- */
- static String readString(final DictBuffer dictBuffer) {
- final StringBuilder s = new StringBuilder();
- int character = readChar(dictBuffer);
- while (character != FormatSpec.INVALID_CHARACTER) {
- s.appendCodePoint(character);
- character = readChar(dictBuffer);
- }
- return s.toString();
- }
-
- /**
- * Reads a character from the buffer.
- *
- * This follows the character format documented earlier in this source file.
- *
- * @param dictBuffer the buffer, positioned over an encoded character.
- * @return the character code.
- */
- static int readChar(final DictBuffer dictBuffer) {
- int character = dictBuffer.readUnsignedByte();
- if (!fitsOnOneByte(character)) {
- if (FormatSpec.PTNODE_CHARACTERS_TERMINATOR == character) {
- return FormatSpec.INVALID_CHARACTER;
- }
- character <<= 16;
- character += dictBuffer.readUnsignedShort();
- }
- return character;
- }
- }
-
- // Input methods: Read a binary dictionary to memory.
- // readDictionaryBinary is the public entry point for them.
-
- static int readSInt24(final DictBuffer dictBuffer) {
- final int retval = dictBuffer.readUnsignedInt24();
- final int sign = ((retval & FormatSpec.MSB24) != 0) ? -1 : 1;
- return sign * (retval & FormatSpec.SINT24_MAX);
- }
-
- static int readChildrenAddress(final DictBuffer dictBuffer,
- final int optionFlags, final FormatOptions options) {
- if (options.mSupportsDynamicUpdate) {
- final int address = dictBuffer.readUnsignedInt24();
- if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS;
- if ((address & FormatSpec.MSB24) != 0) {
- return -(address & FormatSpec.SINT24_MAX);
- } else {
- return address;
- }
- }
- switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) {
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE:
- return dictBuffer.readUnsignedByte();
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES:
- return dictBuffer.readUnsignedShort();
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES:
- return dictBuffer.readUnsignedInt24();
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS:
- default:
- return FormatSpec.NO_CHILDREN_ADDRESS;
- }
- }
-
- static int readParentAddress(final DictBuffer dictBuffer,
- final FormatOptions formatOptions) {
- if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
- final int parentAddress = dictBuffer.readUnsignedInt24();
- final int sign = ((parentAddress & FormatSpec.MSB24) != 0) ? -1 : 1;
- return sign * (parentAddress & FormatSpec.SINT24_MAX);
- } else {
- return FormatSpec.NO_PARENT_ADDRESS;
- }
- }
-
- /**
- * Reads and returns the PtNode count out of a buffer and forwards the pointer.
- */
- /* package */ static int readPtNodeCount(final DictBuffer dictBuffer) {
- final int msb = dictBuffer.readUnsignedByte();
- if (FormatSpec.MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT >= msb) {
- return msb;
- } else {
- return ((FormatSpec.MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT & msb) << 8)
- + dictBuffer.readUnsignedByte();
- }
- }
-
- /**
- * Finds, as a string, the word at the position passed as an argument.
- *
- * @param dictDecoder the dict decoder.
- * @param headerSize the size of the header.
- * @param pos the position to seek.
- * @param formatOptions file format options.
- * @return the word with its frequency, as a weighted string.
- */
- /* package for tests */ static WeightedString getWordAtPosition(final DictDecoder dictDecoder,
- final int headerSize, final int pos, final FormatOptions formatOptions) {
- final WeightedString result;
- final int originalPos = dictDecoder.getPosition();
- dictDecoder.setPosition(pos);
-
- if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
- result = getWordAtPositionWithParentAddress(dictDecoder, pos, formatOptions);
- } else {
- result = getWordAtPositionWithoutParentAddress(dictDecoder, headerSize, pos,
- formatOptions);
- }
-
- dictDecoder.setPosition(originalPos);
- return result;
- }
-
- @SuppressWarnings("unused")
- private static WeightedString getWordAtPositionWithParentAddress(final DictDecoder dictDecoder,
- final int pos, final FormatOptions options) {
- int currentPos = pos;
- int frequency = Integer.MIN_VALUE;
- final StringBuilder builder = new StringBuilder();
- // the length of the path from the root to the leaf is limited by MAX_WORD_LENGTH
- for (int count = 0; count < FormatSpec.MAX_WORD_LENGTH; ++count) {
- PtNodeInfo currentInfo;
- int loopCounter = 0;
- do {
- dictDecoder.setPosition(currentPos);
- currentInfo = dictDecoder.readPtNode(currentPos, options);
- if (BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags, options)) {
- currentPos = currentInfo.mParentAddress + currentInfo.mOriginalAddress;
- }
- if (DBG && loopCounter++ > MAX_JUMPS) {
- MakedictLog.d("Too many jumps - probably a bug");
- }
- } while (BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags, options));
- if (Integer.MIN_VALUE == frequency) frequency = currentInfo.mFrequency;
- builder.insert(0,
- new String(currentInfo.mCharacters, 0, currentInfo.mCharacters.length));
- if (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS) break;
- currentPos = currentInfo.mParentAddress + currentInfo.mOriginalAddress;
- }
- return new WeightedString(builder.toString(), frequency);
- }
-
- private static WeightedString getWordAtPositionWithoutParentAddress(
- final DictDecoder dictDecoder, final int headerSize, final int pos,
- final FormatOptions options) {
- dictDecoder.setPosition(headerSize);
- final int count = dictDecoder.readPtNodeCount();
- int groupPos = headerSize + BinaryDictIOUtils.getPtNodeCountSize(count);
- final StringBuilder builder = new StringBuilder();
- WeightedString result = null;
-
- PtNodeInfo last = null;
- for (int i = count - 1; i >= 0; --i) {
- PtNodeInfo info = dictDecoder.readPtNode(groupPos, options);
- groupPos = info.mEndAddress;
- if (info.mOriginalAddress == pos) {
- builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
- result = new WeightedString(builder.toString(), info.mFrequency);
- break; // and return
- }
- if (BinaryDictIOUtils.hasChildrenAddress(info.mChildrenAddress)) {
- if (info.mChildrenAddress > pos) {
- if (null == last) continue;
- builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
- dictDecoder.setPosition(last.mChildrenAddress);
- i = dictDecoder.readPtNodeCount();
- groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i);
- last = null;
- continue;
- }
- last = info;
- }
- if (0 == i && BinaryDictIOUtils.hasChildrenAddress(last.mChildrenAddress)) {
- builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
- dictDecoder.setPosition(last.mChildrenAddress);
- i = dictDecoder.readPtNodeCount();
- groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i);
- last = null;
- continue;
- }
- }
- return result;
- }
-
- /**
- * Reads a single node array from a buffer.
- *
- * This methods reads the file at the current position. A node array is fully expected to start
- * at the current position.
- * This will recursively read other node arrays into the structure, populating the reverse
- * maps on the fly and using them to keep track of already read nodes.
- *
- * @param dictDecoder the dict decoder, correctly positioned at the start of a node array.
- * @param headerSize the size, in bytes, of the file header.
- * @param reverseNodeArrayMap a mapping from addresses to already read node arrays.
- * @param reversePtNodeMap a mapping from addresses to already read PtNodes.
- * @param options file format options.
- * @return the read node array with all his children already read.
- */
- private static PtNodeArray readNodeArray(final DictDecoder dictDecoder,
- final int headerSize, final Map<Integer, PtNodeArray> reverseNodeArrayMap,
- final Map<Integer, PtNode> reversePtNodeMap, final FormatOptions options)
- throws IOException {
- final ArrayList<PtNode> nodeArrayContents = new ArrayList<PtNode>();
- final int nodeArrayOriginPos = dictDecoder.getPosition();
-
- do { // Scan the linked-list node.
- final int nodeArrayHeadPos = dictDecoder.getPosition();
- final int count = dictDecoder.readPtNodeCount();
- int groupOffsetPos = nodeArrayHeadPos + BinaryDictIOUtils.getPtNodeCountSize(count);
- for (int i = count; i > 0; --i) { // Scan the array of PtNode.
- PtNodeInfo info = dictDecoder.readPtNode(groupOffsetPos, options);
- if (BinaryDictIOUtils.isMovedPtNode(info.mFlags, options)) continue;
- ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
- ArrayList<WeightedString> bigrams = null;
- if (null != info.mBigrams) {
- bigrams = new ArrayList<WeightedString>();
- for (PendingAttribute bigram : info.mBigrams) {
- final WeightedString word = getWordAtPosition(dictDecoder, headerSize,
- bigram.mAddress, options);
- final int reconstructedFrequency =
- BinaryDictIOUtils.reconstructBigramFrequency(word.mFrequency,
- bigram.mFrequency);
- bigrams.add(new WeightedString(word.mWord, reconstructedFrequency));
- }
- }
- if (BinaryDictIOUtils.hasChildrenAddress(info.mChildrenAddress)) {
- PtNodeArray children = reverseNodeArrayMap.get(info.mChildrenAddress);
- if (null == children) {
- final int currentPosition = dictDecoder.getPosition();
- dictDecoder.setPosition(info.mChildrenAddress);
- children = readNodeArray(dictDecoder, headerSize, reverseNodeArrayMap,
- reversePtNodeMap, options);
- dictDecoder.setPosition(currentPosition);
- }
- nodeArrayContents.add(
- new PtNode(info.mCharacters, shortcutTargets, bigrams,
- info.mFrequency,
- 0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD),
- 0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED), children));
- } else {
- nodeArrayContents.add(
- new PtNode(info.mCharacters, shortcutTargets, bigrams,
- info.mFrequency,
- 0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD),
- 0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED)));
- }
- groupOffsetPos = info.mEndAddress;
- }
-
- // reach the end of the array.
- if (options.mSupportsDynamicUpdate) {
- final boolean hasValidForwardLink = dictDecoder.readAndFollowForwardLink();
- if (!hasValidForwardLink) break;
- }
- } while (options.mSupportsDynamicUpdate && dictDecoder.hasNextPtNodeArray());
-
- final PtNodeArray nodeArray = new PtNodeArray(nodeArrayContents);
- nodeArray.mCachedAddressBeforeUpdate = nodeArrayOriginPos;
- nodeArray.mCachedAddressAfterUpdate = nodeArrayOriginPos;
- reverseNodeArrayMap.put(nodeArray.mCachedAddressAfterUpdate, nodeArray);
- return nodeArray;
- }
-
- /**
- * Helper function to get the binary format version from the header.
- * @throws IOException
- */
- private static int getFormatVersion(final DictBuffer dictBuffer)
- throws IOException {
- final int magic = dictBuffer.readInt();
- if (FormatSpec.MAGIC_NUMBER == magic) return dictBuffer.readUnsignedShort();
- return FormatSpec.NOT_A_VERSION_NUMBER;
- }
-
- /**
- * Helper function to get and validate the binary format version.
- * @throws UnsupportedFormatException
- * @throws IOException
- */
- static int checkFormatVersion(final DictBuffer dictBuffer)
- throws IOException, UnsupportedFormatException {
- final int version = getFormatVersion(dictBuffer);
- if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
- || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
- throw new UnsupportedFormatException("This file has version " + version
- + ", but this implementation does not support versions above "
- + FormatSpec.MAXIMUM_SUPPORTED_VERSION);
- }
- return version;
- }
-
- /**
- * Reads a buffer and returns the memory representation of the dictionary.
- *
- * This high-level method takes a buffer and reads its contents, populating a
- * FusionDictionary structure. The optional dict argument is an existing dictionary to
- * which words from the buffer should be added. If it is null, a new dictionary is created.
- *
- * @param dictDecoder the dict decoder.
- * @param dict an optional dictionary to add words to, or null.
- * @return the created (or merged) dictionary.
- */
- @UsedForTesting
- /* package */ static FusionDictionary readDictionaryBinary(final DictDecoder dictDecoder,
- final FusionDictionary dict) throws IOException, UnsupportedFormatException {
- // Read header
- final FileHeader fileHeader = dictDecoder.readHeader();
-
- Map<Integer, PtNodeArray> reverseNodeArrayMapping = new TreeMap<Integer, PtNodeArray>();
- Map<Integer, PtNode> reversePtNodeMapping = new TreeMap<Integer, PtNode>();
- final PtNodeArray root = readNodeArray(dictDecoder, fileHeader.mHeaderSize,
- reverseNodeArrayMapping, reversePtNodeMapping, fileHeader.mFormatOptions);
-
- FusionDictionary newDict = new FusionDictionary(root, fileHeader.mDictionaryOptions);
- if (null != dict) {
- for (final Word w : dict) {
- if (w.mIsBlacklistEntry) {
- newDict.addBlacklistEntry(w.mWord, w.mShortcutTargets, w.mIsNotAWord);
- } else {
- newDict.add(w.mWord, w.mFrequency, w.mShortcutTargets, w.mIsNotAWord);
- }
- }
- for (final Word w : dict) {
- // By construction a binary dictionary may not have bigrams pointing to
- // words that are not also registered as unigrams so we don't have to avoid
- // them explicitly here.
- for (final WeightedString bigram : w.mBigrams) {
- newDict.setBigram(w.mWord, bigram.mWord, bigram.mFrequency);
- }
- }
- }
-
- return newDict;
- }
-
- /**
- * Helper method to pass a file name instead of a File object to isBinaryDictionary.
- */
- public static boolean isBinaryDictionary(final String filename) {
- final File file = new File(filename);
- return isBinaryDictionary(file);
- }
-
- /**
- * Basic test to find out whether the file is a binary dictionary or not.
- *
- * Concretely this only tests the magic number.
- *
- * @param file The file to test.
- * @return true if it's a binary dictionary, false otherwise
- */
- public static boolean isBinaryDictionary(final File file) {
- FileInputStream inStream = null;
- try {
- inStream = new FileInputStream(file);
- final ByteBuffer buffer = inStream.getChannel().map(
- FileChannel.MapMode.READ_ONLY, 0, file.length());
- final int version = getFormatVersion(new ByteBufferDictBuffer(buffer));
- return (version >= FormatSpec.MINIMUM_SUPPORTED_VERSION
- && version <= FormatSpec.MAXIMUM_SUPPORTED_VERSION);
- } catch (FileNotFoundException e) {
- return false;
- } catch (IOException e) {
- return false;
- } finally {
- if (inStream != null) {
- try {
- inStream.close();
- } catch (IOException e) {
- // do nothing
- }
- }
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
deleted file mode 100644
index f761829de..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ /dev/null
@@ -1,956 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
-import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-
-/**
- * Encodes binary files for a FusionDictionary.
- *
- * All the methods in this class are static.
- *
- * TODO: Rename this class to DictEncoderUtils.
- */
-public class BinaryDictEncoderUtils {
-
- private static final boolean DBG = MakedictLog.DBG;
-
- private BinaryDictEncoderUtils() {
- // This utility class is not publicly instantiable.
- }
-
- // Arbitrary limit to how much passes we consider address size compression should
- // terminate in. At the time of this writing, our largest dictionary completes
- // compression in five passes.
- // If the number of passes exceeds this number, makedict bails with an exception on
- // suspicion that a bug might be causing an infinite loop.
- private static final int MAX_PASSES = 24;
-
- /**
- * Compute the binary size of the character array.
- *
- * If only one character, this is the size of this character. If many, it's the sum of their
- * sizes + 1 byte for the terminator.
- *
- * @param characters the character array
- * @return the size of the char array, including the terminator if any
- */
- static int getPtNodeCharactersSize(final int[] characters) {
- int size = CharEncoding.getCharArraySize(characters);
- if (characters.length > 1) size += FormatSpec.PTNODE_TERMINATOR_SIZE;
- return size;
- }
-
- /**
- * Compute the binary size of the character array in a PtNode
- *
- * If only one character, this is the size of this character. If many, it's the sum of their
- * sizes + 1 byte for the terminator.
- *
- * @param ptNode the PtNode
- * @return the size of the char array, including the terminator if any
- */
- private static int getPtNodeCharactersSize(final PtNode ptNode) {
- return getPtNodeCharactersSize(ptNode.mChars);
- }
-
- /**
- * Compute the binary size of the PtNode count for a node array.
- * @param nodeArray the nodeArray
- * @return the size of the PtNode count, either 1 or 2 bytes.
- */
- private static int getPtNodeCountSize(final PtNodeArray nodeArray) {
- return BinaryDictIOUtils.getPtNodeCountSize(nodeArray.mData.size());
- }
-
- /**
- * Compute the size of a shortcut in bytes.
- */
- private static int getShortcutSize(final WeightedString shortcut) {
- int size = FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE;
- final String word = shortcut.mWord;
- final int length = word.length();
- for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
- final int codePoint = word.codePointAt(i);
- size += CharEncoding.getCharSize(codePoint);
- }
- size += FormatSpec.PTNODE_TERMINATOR_SIZE;
- return size;
- }
-
- /**
- * Compute the size of a shortcut list in bytes.
- *
- * This is known in advance and does not change according to position in the file
- * like address lists do.
- */
- static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) {
- if (null == shortcutList || shortcutList.isEmpty()) return 0;
- int size = FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE;
- for (final WeightedString shortcut : shortcutList) {
- size += getShortcutSize(shortcut);
- }
- return size;
- }
-
- /**
- * Compute the maximum size of a PtNode, assuming 3-byte addresses for everything.
- *
- * @param ptNode the PtNode to compute the size of.
- * @param options file format options.
- * @return the maximum size of the PtNode.
- */
- private static int getPtNodeMaximumSize(final PtNode ptNode, final FormatOptions options) {
- int size = getNodeHeaderSize(ptNode, options);
- if (ptNode.isTerminal()) {
- // If terminal, one byte for the frequency or four bytes for the terminal id.
- if (options.mHasTerminalId) {
- size += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
- } else {
- size += FormatSpec.PTNODE_FREQUENCY_SIZE;
- }
- }
- size += FormatSpec.PTNODE_MAX_ADDRESS_SIZE; // For children address
- size += getShortcutListSize(ptNode.mShortcutTargets);
- if (null != ptNode.mBigrams) {
- size += (FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE
- + FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE)
- * ptNode.mBigrams.size();
- }
- return size;
- }
-
- /**
- * Compute the maximum size of each PtNode of a PtNode array, assuming 3-byte addresses for
- * everything, and caches it in the `mCachedSize' member of the nodes; deduce the size of
- * the containing node array, and cache it it its 'mCachedSize' member.
- *
- * @param ptNodeArray the node array to compute the maximum size of.
- * @param options file format options.
- */
- private static void calculatePtNodeArrayMaximumSize(final PtNodeArray ptNodeArray,
- final FormatOptions options) {
- int size = getPtNodeCountSize(ptNodeArray);
- for (PtNode node : ptNodeArray.mData) {
- final int nodeSize = getPtNodeMaximumSize(node, options);
- node.mCachedSize = nodeSize;
- size += nodeSize;
- }
- if (options.mSupportsDynamicUpdate) {
- size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
- }
- ptNodeArray.mCachedSize = size;
- }
-
- /**
- * Compute the size of the header (flag + [parent address] + characters size) of a PtNode.
- *
- * @param ptNode the PtNode of which to compute the size of the header
- * @param options file format options.
- */
- private static int getNodeHeaderSize(final PtNode ptNode, final FormatOptions options) {
- if (BinaryDictIOUtils.supportsDynamicUpdate(options)) {
- return FormatSpec.PTNODE_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
- + getPtNodeCharactersSize(ptNode);
- } else {
- return FormatSpec.PTNODE_FLAGS_SIZE + getPtNodeCharactersSize(ptNode);
- }
- }
-
- /**
- * Compute the size, in bytes, that an address will occupy.
- *
- * This can be used either for children addresses (which are always positive) or for
- * attribute, which may be positive or negative but
- * store their sign bit separately.
- *
- * @param address the address
- * @return the byte size.
- */
- static int getByteSize(final int address) {
- assert(address <= FormatSpec.UINT24_MAX);
- if (!BinaryDictIOUtils.hasChildrenAddress(address)) {
- return 0;
- } else if (Math.abs(address) <= FormatSpec.UINT8_MAX) {
- return 1;
- } else if (Math.abs(address) <= FormatSpec.UINT16_MAX) {
- return 2;
- } else {
- return 3;
- }
- }
-
- static int writeUIntToBuffer(final byte[] buffer, int position, final int value,
- final int size) {
- switch(size) {
- case 4:
- buffer[position++] = (byte) ((value >> 24) & 0xFF);
- /* fall through */
- case 3:
- buffer[position++] = (byte) ((value >> 16) & 0xFF);
- /* fall through */
- case 2:
- buffer[position++] = (byte) ((value >> 8) & 0xFF);
- /* fall through */
- case 1:
- buffer[position++] = (byte) (value & 0xFF);
- break;
- default:
- /* nop */
- }
- return position;
- }
-
- static void writeUIntToStream(final OutputStream stream, final int value, final int size)
- throws IOException {
- switch(size) {
- case 4:
- stream.write((value >> 24) & 0xFF);
- /* fall through */
- case 3:
- stream.write((value >> 16) & 0xFF);
- /* fall through */
- case 2:
- stream.write((value >> 8) & 0xFF);
- /* fall through */
- case 1:
- stream.write(value & 0xFF);
- break;
- default:
- /* nop */
- }
- }
-
- // End utility methods
-
- // This method is responsible for finding a nice ordering of the nodes that favors run-time
- // cache performance and dictionary size.
- /* package for tests */ static ArrayList<PtNodeArray> flattenTree(
- final PtNodeArray rootNodeArray) {
- final int treeSize = FusionDictionary.countPtNodes(rootNodeArray);
- MakedictLog.i("Counted nodes : " + treeSize);
- final ArrayList<PtNodeArray> flatTree = new ArrayList<PtNodeArray>(treeSize);
- return flattenTreeInner(flatTree, rootNodeArray);
- }
-
- private static ArrayList<PtNodeArray> flattenTreeInner(final ArrayList<PtNodeArray> list,
- final PtNodeArray ptNodeArray) {
- // Removing the node is necessary if the tails are merged, because we would then
- // add the same node several times when we only want it once. A number of places in
- // the code also depends on any node being only once in the list.
- // Merging tails can only be done if there are no attributes. Searching for attributes
- // in LatinIME code depends on a total breadth-first ordering, which merging tails
- // breaks. If there are no attributes, it should be fine (and reduce the file size)
- // to merge tails, and removing the node from the list would be necessary. However,
- // we don't merge tails because breaking the breadth-first ordering would result in
- // extreme overhead at bigram lookup time (it would make the search function O(n) instead
- // of the current O(log(n)), where n=number of nodes in the dictionary which is pretty
- // high).
- // If no nodes are ever merged, we can't have the same node twice in the list, hence
- // searching for duplicates in unnecessary. It is also very performance consuming,
- // since `list' is an ArrayList so it's an O(n) operation that runs on all nodes, making
- // this simple list.remove operation O(n*n) overall. On Android this overhead is very
- // high.
- // For future reference, the code to remove duplicate is a simple : list.remove(node);
- list.add(ptNodeArray);
- final ArrayList<PtNode> branches = ptNodeArray.mData;
- for (PtNode ptNode : branches) {
- if (null != ptNode.mChildren) flattenTreeInner(list, ptNode.mChildren);
- }
- return list;
- }
-
- /**
- * Get the offset from a position inside a current node array to a target node array, during
- * update.
- *
- * If the current node array is before the target node array, the target node array has not
- * been updated yet, so we should return the offset from the old position of the current node
- * array to the old position of the target node array. If on the other hand the target is
- * before the current node array, it already has been updated, so we should return the offset
- * from the new position in the current node array to the new position in the target node
- * array.
- *
- * @param currentNodeArray node array containing the PtNode where the offset will be written
- * @param offsetFromStartOfCurrentNodeArray offset, in bytes, from the start of currentNodeArray
- * @param targetNodeArray the target node array to get the offset to
- * @return the offset to the target node array
- */
- private static int getOffsetToTargetNodeArrayDuringUpdate(final PtNodeArray currentNodeArray,
- final int offsetFromStartOfCurrentNodeArray, final PtNodeArray targetNodeArray) {
- final boolean isTargetBeforeCurrent = (targetNodeArray.mCachedAddressBeforeUpdate
- < currentNodeArray.mCachedAddressBeforeUpdate);
- if (isTargetBeforeCurrent) {
- return targetNodeArray.mCachedAddressAfterUpdate
- - (currentNodeArray.mCachedAddressAfterUpdate
- + offsetFromStartOfCurrentNodeArray);
- } else {
- return targetNodeArray.mCachedAddressBeforeUpdate
- - (currentNodeArray.mCachedAddressBeforeUpdate
- + offsetFromStartOfCurrentNodeArray);
- }
- }
-
- /**
- * Get the offset from a position inside a current node array to a target PtNode, during
- * update.
- *
- * @param currentNodeArray node array containing the PtNode where the offset will be written
- * @param offsetFromStartOfCurrentNodeArray offset, in bytes, from the start of currentNodeArray
- * @param targetPtNode the target PtNode to get the offset to
- * @return the offset to the target PtNode
- */
- // TODO: is there any way to factorize this method with the one above?
- private static int getOffsetToTargetPtNodeDuringUpdate(final PtNodeArray currentNodeArray,
- final int offsetFromStartOfCurrentNodeArray, final PtNode targetPtNode) {
- final int oldOffsetBasePoint = currentNodeArray.mCachedAddressBeforeUpdate
- + offsetFromStartOfCurrentNodeArray;
- final boolean isTargetBeforeCurrent = (targetPtNode.mCachedAddressBeforeUpdate
- < oldOffsetBasePoint);
- // If the target is before the current node array, then its address has already been
- // updated. We can use the AfterUpdate member, and compare it to our own member after
- // update. Otherwise, the AfterUpdate member is not updated yet, so we need to use the
- // BeforeUpdate member, and of course we have to compare this to our own address before
- // update.
- if (isTargetBeforeCurrent) {
- final int newOffsetBasePoint = currentNodeArray.mCachedAddressAfterUpdate
- + offsetFromStartOfCurrentNodeArray;
- return targetPtNode.mCachedAddressAfterUpdate - newOffsetBasePoint;
- } else {
- return targetPtNode.mCachedAddressBeforeUpdate - oldOffsetBasePoint;
- }
- }
-
- /**
- * Computes the actual node array size, based on the cached addresses of the children nodes.
- *
- * Each node array stores its tentative address. During dictionary address computing, these
- * are not final, but they can be used to compute the node array size (the node array size
- * depends on the address of the children because the number of bytes necessary to store an
- * address depends on its numeric value. The return value indicates whether the node array
- * contents (as in, any of the addresses stored in the cache fields) have changed with
- * respect to their previous value.
- *
- * @param ptNodeArray the node array to compute the size of.
- * @param dict the dictionary in which the word/attributes are to be found.
- * @param formatOptions file format options.
- * @return false if none of the cached addresses inside the node array changed, true otherwise.
- */
- private static boolean computeActualPtNodeArraySize(final PtNodeArray ptNodeArray,
- final FusionDictionary dict, final FormatOptions formatOptions) {
- boolean changed = false;
- int size = getPtNodeCountSize(ptNodeArray);
- for (PtNode ptNode : ptNodeArray.mData) {
- ptNode.mCachedAddressAfterUpdate = ptNodeArray.mCachedAddressAfterUpdate + size;
- if (ptNode.mCachedAddressAfterUpdate != ptNode.mCachedAddressBeforeUpdate) {
- changed = true;
- }
- int nodeSize = getNodeHeaderSize(ptNode, formatOptions);
- if (ptNode.isTerminal()) {
- if (formatOptions.mHasTerminalId) {
- nodeSize += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
- } else {
- nodeSize += FormatSpec.PTNODE_FREQUENCY_SIZE;
- }
- }
- if (formatOptions.mSupportsDynamicUpdate) {
- nodeSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
- } else if (null != ptNode.mChildren) {
- nodeSize += getByteSize(getOffsetToTargetNodeArrayDuringUpdate(ptNodeArray,
- nodeSize + size, ptNode.mChildren));
- }
- if (formatOptions.mVersion < FormatSpec.FIRST_VERSION_WITH_TERMINAL_ID) {
- nodeSize += getShortcutListSize(ptNode.mShortcutTargets);
- if (null != ptNode.mBigrams) {
- for (WeightedString bigram : ptNode.mBigrams) {
- final int offset = getOffsetToTargetPtNodeDuringUpdate(ptNodeArray,
- nodeSize + size + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE,
- FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord));
- nodeSize += getByteSize(offset) + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE;
- }
- }
- }
- ptNode.mCachedSize = nodeSize;
- size += nodeSize;
- }
- if (formatOptions.mSupportsDynamicUpdate) {
- size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
- }
- if (ptNodeArray.mCachedSize != size) {
- ptNodeArray.mCachedSize = size;
- changed = true;
- }
- return changed;
- }
-
- /**
- * Initializes the cached addresses of node arrays and their containing nodes from their size.
- *
- * @param flatNodes the list of node arrays.
- * @param formatOptions file format options.
- * @return the byte size of the entire stack.
- */
- private static int initializePtNodeArraysCachedAddresses(final ArrayList<PtNodeArray> flatNodes,
- final FormatOptions formatOptions) {
- int nodeArrayOffset = 0;
- for (final PtNodeArray nodeArray : flatNodes) {
- nodeArray.mCachedAddressBeforeUpdate = nodeArrayOffset;
- int nodeCountSize = getPtNodeCountSize(nodeArray);
- int nodeffset = 0;
- for (final PtNode ptNode : nodeArray.mData) {
- ptNode.mCachedAddressBeforeUpdate = ptNode.mCachedAddressAfterUpdate =
- nodeCountSize + nodeArrayOffset + nodeffset;
- nodeffset += ptNode.mCachedSize;
- }
- nodeArrayOffset += nodeArray.mCachedSize;
- }
- return nodeArrayOffset;
- }
-
- /**
- * Updates the cached addresses of node arrays after recomputing their new positions.
- *
- * @param flatNodes the list of node arrays.
- */
- private static void updatePtNodeArraysCachedAddresses(final ArrayList<PtNodeArray> flatNodes) {
- for (final PtNodeArray nodeArray : flatNodes) {
- nodeArray.mCachedAddressBeforeUpdate = nodeArray.mCachedAddressAfterUpdate;
- for (final PtNode ptNode : nodeArray.mData) {
- ptNode.mCachedAddressBeforeUpdate = ptNode.mCachedAddressAfterUpdate;
- }
- }
- }
-
- /**
- * Compute the cached parent addresses after all has been updated.
- *
- * The parent addresses are used by some binary formats at write-to-disk time. Not all formats
- * need them. In particular, version 2 does not need them, and version 3 does.
- *
- * @param flatNodes the flat array of node arrays to fill in
- */
- private static void computeParentAddresses(final ArrayList<PtNodeArray> flatNodes) {
- for (final PtNodeArray nodeArray : flatNodes) {
- for (final PtNode ptNode : nodeArray.mData) {
- if (null != ptNode.mChildren) {
- // Assign my address to children's parent address
- // Here BeforeUpdate and AfterUpdate addresses have the same value, so it
- // does not matter which we use.
- ptNode.mChildren.mCachedParentAddress = ptNode.mCachedAddressAfterUpdate
- - ptNode.mChildren.mCachedAddressAfterUpdate;
- }
- }
- }
- }
-
- /**
- * Compute the addresses and sizes of an ordered list of PtNode arrays.
- *
- * This method takes a list of PtNode arrays and will update their cached address and size
- * values so that they can be written into a file. It determines the smallest size each of the
- * PtNode arrays can be given the addresses of its children and attributes, and store that into
- * each PtNode.
- * The order of the PtNode is given by the order of the array. This method makes no effort
- * to find a good order; it only mechanically computes the size this order results in.
- *
- * @param dict the dictionary
- * @param flatNodes the ordered list of PtNode arrays
- * @param formatOptions file format options.
- * @return the same array it was passed. The nodes have been updated for address and size.
- */
- /* package */ static ArrayList<PtNodeArray> computeAddresses(final FusionDictionary dict,
- final ArrayList<PtNodeArray> flatNodes, final FormatOptions formatOptions) {
- // First get the worst possible sizes and offsets
- for (final PtNodeArray n : flatNodes) calculatePtNodeArrayMaximumSize(n, formatOptions);
- final int offset = initializePtNodeArraysCachedAddresses(flatNodes, formatOptions);
-
- MakedictLog.i("Compressing the array addresses. Original size : " + offset);
- MakedictLog.i("(Recursively seen size : " + offset + ")");
-
- int passes = 0;
- boolean changesDone = false;
- do {
- changesDone = false;
- int ptNodeArrayStartOffset = 0;
- for (final PtNodeArray ptNodeArray : flatNodes) {
- ptNodeArray.mCachedAddressAfterUpdate = ptNodeArrayStartOffset;
- final int oldNodeArraySize = ptNodeArray.mCachedSize;
- final boolean changed =
- computeActualPtNodeArraySize(ptNodeArray, dict, formatOptions);
- final int newNodeArraySize = ptNodeArray.mCachedSize;
- if (oldNodeArraySize < newNodeArraySize) {
- throw new RuntimeException("Increased size ?!");
- }
- ptNodeArrayStartOffset += newNodeArraySize;
- changesDone |= changed;
- }
- updatePtNodeArraysCachedAddresses(flatNodes);
- ++passes;
- if (passes > MAX_PASSES) throw new RuntimeException("Too many passes - probably a bug");
- } while (changesDone);
-
- if (formatOptions.mSupportsDynamicUpdate) {
- computeParentAddresses(flatNodes);
- }
- final PtNodeArray lastPtNodeArray = flatNodes.get(flatNodes.size() - 1);
- MakedictLog.i("Compression complete in " + passes + " passes.");
- MakedictLog.i("After address compression : "
- + (lastPtNodeArray.mCachedAddressAfterUpdate + lastPtNodeArray.mCachedSize));
-
- return flatNodes;
- }
-
- /**
- * Sanity-checking method.
- *
- * This method checks a list of PtNode arrays for juxtaposition, that is, it will do
- * nothing if each node array's cached address is actually the previous node array's address
- * plus the previous node's size.
- * If this is not the case, it will throw an exception.
- *
- * @param arrays the list of node arrays to check
- */
- /* package */ static void checkFlatPtNodeArrayList(final ArrayList<PtNodeArray> arrays) {
- int offset = 0;
- int index = 0;
- for (final PtNodeArray ptNodeArray : arrays) {
- // BeforeUpdate and AfterUpdate addresses are the same here, so it does not matter
- // which we use.
- if (ptNodeArray.mCachedAddressAfterUpdate != offset) {
- throw new RuntimeException("Wrong address for node " + index
- + " : expected " + offset + ", got " +
- ptNodeArray.mCachedAddressAfterUpdate);
- }
- ++index;
- offset += ptNodeArray.mCachedSize;
- }
- }
-
- /**
- * Helper method to write a children position to a file.
- *
- * @param buffer the buffer to write to.
- * @param index the index in the buffer to write the address to.
- * @param position the position to write.
- * @return the size in bytes the address actually took.
- */
- /* package */ static int writeChildrenPosition(final byte[] buffer, int index,
- final int position) {
- switch (getByteSize(position)) {
- case 1:
- buffer[index++] = (byte)position;
- return 1;
- case 2:
- buffer[index++] = (byte)(0xFF & (position >> 8));
- buffer[index++] = (byte)(0xFF & position);
- return 2;
- case 3:
- buffer[index++] = (byte)(0xFF & (position >> 16));
- buffer[index++] = (byte)(0xFF & (position >> 8));
- buffer[index++] = (byte)(0xFF & position);
- return 3;
- case 0:
- return 0;
- default:
- throw new RuntimeException("Position " + position + " has a strange size");
- }
- }
-
- /**
- * Helper method to write a signed children position to a file.
- *
- * @param buffer the buffer to write to.
- * @param index the index in the buffer to write the address to.
- * @param position the position to write.
- * @return the size in bytes the address actually took.
- */
- /* package */ static int writeSignedChildrenPosition(final byte[] buffer, int index,
- final int position) {
- if (!BinaryDictIOUtils.hasChildrenAddress(position)) {
- buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
- } else {
- final int absPosition = Math.abs(position);
- buffer[index++] =
- (byte)((position < 0 ? FormatSpec.MSB8 : 0) | (0xFF & (absPosition >> 16)));
- buffer[index++] = (byte)(0xFF & (absPosition >> 8));
- buffer[index++] = (byte)(0xFF & absPosition);
- }
- return 3;
- }
-
- /**
- * Makes the flag value for a PtNode.
- *
- * @param hasMultipleChars whether the PtNode has multiple chars.
- * @param isTerminal whether the PtNode is terminal.
- * @param childrenAddressSize the size of a children address.
- * @param hasShortcuts whether the PtNode has shortcuts.
- * @param hasBigrams whether the PtNode has bigrams.
- * @param isNotAWord whether the PtNode is not a word.
- * @param isBlackListEntry whether the PtNode is a blacklist entry.
- * @param formatOptions file format options.
- * @return the flags
- */
- static int makePtNodeFlags(final boolean hasMultipleChars, final boolean isTerminal,
- final int childrenAddressSize, final boolean hasShortcuts, final boolean hasBigrams,
- final boolean isNotAWord, final boolean isBlackListEntry,
- final FormatOptions formatOptions) {
- byte flags = 0;
- if (hasMultipleChars) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS;
- if (isTerminal) flags |= FormatSpec.FLAG_IS_TERMINAL;
- if (formatOptions.mSupportsDynamicUpdate) {
- flags |= FormatSpec.FLAG_IS_NOT_MOVED;
- } else if (true) {
- switch (childrenAddressSize) {
- case 1:
- flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE;
- break;
- case 2:
- flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES;
- break;
- case 3:
- flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES;
- break;
- case 0:
- flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS;
- break;
- default:
- throw new RuntimeException("Node with a strange address");
- }
- }
- if (hasShortcuts) flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS;
- if (hasBigrams) flags |= FormatSpec.FLAG_HAS_BIGRAMS;
- if (isNotAWord) flags |= FormatSpec.FLAG_IS_NOT_A_WORD;
- if (isBlackListEntry) flags |= FormatSpec.FLAG_IS_BLACKLISTED;
- return flags;
- }
-
- /* package */ static byte makePtNodeFlags(final PtNode node, final int childrenOffset,
- final FormatOptions formatOptions) {
- return (byte) makePtNodeFlags(node.mChars.length > 1, node.mFrequency >= 0,
- getByteSize(childrenOffset),
- node.mShortcutTargets != null && !node.mShortcutTargets.isEmpty(),
- node.mBigrams != null, node.mIsNotAWord, node.mIsBlacklistEntry, formatOptions);
- }
-
- /**
- * Makes the flag value for a bigram.
- *
- * @param more whether there are more bigrams after this one.
- * @param offset the offset of the bigram.
- * @param bigramFrequency the frequency of the bigram, 0..255.
- * @param unigramFrequency the unigram frequency of the same word, 0..255.
- * @param word the second bigram, for debugging purposes
- * @return the flags
- */
- /* package */ static final int makeBigramFlags(final boolean more, final int offset,
- int bigramFrequency, final int unigramFrequency, final String word) {
- int bigramFlags = (more ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0)
- + (offset < 0 ? FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE : 0);
- switch (getByteSize(offset)) {
- case 1:
- bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE;
- break;
- case 2:
- bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES;
- break;
- case 3:
- bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES;
- break;
- default:
- throw new RuntimeException("Strange offset size");
- }
- if (unigramFrequency > bigramFrequency) {
- MakedictLog.e("Unigram freq is superior to bigram freq for \"" + word
- + "\". Bigram freq is " + bigramFrequency + ", unigram freq for "
- + word + " is " + unigramFrequency);
- bigramFrequency = unigramFrequency;
- }
- // We compute the difference between 255 (which means probability = 1) and the
- // unigram score. We split this into a number of discrete steps.
- // Now, the steps are numbered 0~15; 0 represents an increase of 1 step while 15
- // represents an increase of 16 steps: a value of 15 will be interpreted as the median
- // value of the 16th step. In all justice, if the bigram frequency is low enough to be
- // rounded below the first step (which means it is less than half a step higher than the
- // unigram frequency) then the unigram frequency itself is the best approximation of the
- // bigram freq that we could possibly supply, hence we should *not* include this bigram
- // in the file at all.
- // until this is done, we'll write 0 and slightly overestimate this case.
- // In other words, 0 means "between 0.5 step and 1.5 step", 1 means "between 1.5 step
- // and 2.5 steps", and 15 means "between 15.5 steps and 16.5 steps". So we want to
- // divide our range [unigramFreq..MAX_TERMINAL_FREQUENCY] in 16.5 steps to get the
- // step size. Then we compute the start of the first step (the one where value 0 starts)
- // by adding half-a-step to the unigramFrequency. From there, we compute the integer
- // number of steps to the bigramFrequency. One last thing: we want our steps to include
- // their lower bound and exclude their higher bound so we need to have the first step
- // start at exactly 1 unit higher than floor(unigramFreq + half a step).
- // Note : to reconstruct the score, the dictionary reader will need to divide
- // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise to get the value of the step,
- // and add (discretizedFrequency + 0.5 + 0.5) times this value to get the best
- // approximation. (0.5 to get the first step start, and 0.5 to get the middle of the
- // step pointed by the discretized frequency.
- final float stepSize =
- (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency)
- / (1.5f + FormatSpec.MAX_BIGRAM_FREQUENCY);
- final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
- final int discretizedFrequency = (int)((bigramFrequency - firstStepStart) / stepSize);
- // If the bigram freq is less than half-a-step higher than the unigram freq, we get -1
- // here. The best approximation would be the unigram freq itself, so we should not
- // include this bigram in the dictionary. For now, register as 0, and live with the
- // small over-estimation that we get in this case. TODO: actually remove this bigram
- // if discretizedFrequency < 0.
- final int finalBigramFrequency = discretizedFrequency > 0 ? discretizedFrequency : 0;
- bigramFlags += finalBigramFrequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY;
- return bigramFlags;
- }
-
- /**
- * Makes the 2-byte value for options flags.
- */
- private static final int makeOptionsValue(final FusionDictionary dictionary,
- final FormatOptions formatOptions) {
- final DictionaryOptions options = dictionary.mOptions;
- final boolean hasBigrams = dictionary.hasBigrams();
- return (options.mFrenchLigatureProcessing ? FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG : 0)
- + (options.mGermanUmlautProcessing ? FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG : 0)
- + (hasBigrams ? FormatSpec.CONTAINS_BIGRAMS_FLAG : 0)
- + (formatOptions.mSupportsDynamicUpdate ? FormatSpec.SUPPORTS_DYNAMIC_UPDATE : 0);
- }
-
- /**
- * Makes the flag value for a shortcut.
- *
- * @param more whether there are more attributes after this one.
- * @param frequency the frequency of the attribute, 0..15
- * @return the flags
- */
- static final int makeShortcutFlags(final boolean more, final int frequency) {
- return (more ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0)
- + (frequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY);
- }
-
- /* package */ static final int writeParentAddress(final byte[] buffer, final int index,
- final int address, final FormatOptions formatOptions) {
- if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
- if (address == FormatSpec.NO_PARENT_ADDRESS) {
- buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
- } else {
- final int absAddress = Math.abs(address);
- assert(absAddress <= FormatSpec.SINT24_MAX);
- buffer[index] = (byte)((address < 0 ? FormatSpec.MSB8 : 0)
- | ((absAddress >> 16) & 0xFF));
- buffer[index + 1] = (byte)((absAddress >> 8) & 0xFF);
- buffer[index + 2] = (byte)(absAddress & 0xFF);
- }
- return index + 3;
- } else {
- return index;
- }
- }
-
- /* package */ static final int getChildrenPosition(final PtNode ptNode,
- final FormatOptions formatOptions) {
- int positionOfChildrenPosField = ptNode.mCachedAddressAfterUpdate
- + getNodeHeaderSize(ptNode, formatOptions);
- if (ptNode.isTerminal()) {
- // A terminal node has either the terminal id or the frequency.
- // If positionOfChildrenPosField is incorrect, we may crash when jumping to the children
- // position.
- if (formatOptions.mHasTerminalId) {
- positionOfChildrenPosField += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
- } else {
- positionOfChildrenPosField += FormatSpec.PTNODE_FREQUENCY_SIZE;
- }
- }
- return null == ptNode.mChildren ? FormatSpec.NO_CHILDREN_ADDRESS
- : ptNode.mChildren.mCachedAddressAfterUpdate - positionOfChildrenPosField;
- }
-
- /**
- * Write a PtNodeArray. The PtNodeArray is expected to have its final position cached.
- *
- * @param dict the dictionary the node array is a part of (for relative offsets).
- * @param dictEncoder the dictionary encoder.
- * @param ptNodeArray the node array to write.
- * @param formatOptions file format options.
- */
- @SuppressWarnings("unused")
- /* package */ static void writePlacedPtNodeArray(final FusionDictionary dict,
- final DictEncoder dictEncoder, final PtNodeArray ptNodeArray,
- final FormatOptions formatOptions) {
- // TODO: Make the code in common with BinaryDictIOUtils#writePtNode
- dictEncoder.setPosition(ptNodeArray.mCachedAddressAfterUpdate);
-
- final int ptNodeCount = ptNodeArray.mData.size();
- dictEncoder.writePtNodeCount(ptNodeCount);
- final int parentPosition =
- (ptNodeArray.mCachedParentAddress == FormatSpec.NO_PARENT_ADDRESS)
- ? FormatSpec.NO_PARENT_ADDRESS
- : ptNodeArray.mCachedParentAddress + ptNodeArray.mCachedAddressAfterUpdate;
- for (int i = 0; i < ptNodeCount; ++i) {
- final PtNode ptNode = ptNodeArray.mData.get(i);
- if (dictEncoder.getPosition() != ptNode.mCachedAddressAfterUpdate) {
- throw new RuntimeException("Bug: write index is not the same as the cached address "
- + "of the node : " + dictEncoder.getPosition() + " <> "
- + ptNode.mCachedAddressAfterUpdate);
- }
- // Sanity checks.
- if (DBG && ptNode.mFrequency > FormatSpec.MAX_TERMINAL_FREQUENCY) {
- throw new RuntimeException("A node has a frequency > "
- + FormatSpec.MAX_TERMINAL_FREQUENCY
- + " : " + ptNode.mFrequency);
- }
- dictEncoder.writePtNode(ptNode, parentPosition, formatOptions, dict);
- }
- if (formatOptions.mSupportsDynamicUpdate) {
- dictEncoder.writeForwardLinkAddress(FormatSpec.NO_FORWARD_LINK_ADDRESS);
- }
- if (dictEncoder.getPosition() != ptNodeArray.mCachedAddressAfterUpdate
- + ptNodeArray.mCachedSize) {
- throw new RuntimeException("Not the same size : written "
- + (dictEncoder.getPosition() - ptNodeArray.mCachedAddressAfterUpdate)
- + " bytes from a node that should have " + ptNodeArray.mCachedSize + " bytes");
- }
- }
-
- /**
- * Dumps a collection of useful statistics about a list of PtNode arrays.
- *
- * This prints purely informative stuff, like the total estimated file size, the
- * number of PtNode arrays, of PtNodes, the repartition of each address size, etc
- *
- * @param ptNodeArrays the list of PtNode arrays.
- */
- /* package */ static void showStatistics(ArrayList<PtNodeArray> ptNodeArrays) {
- int firstTerminalAddress = Integer.MAX_VALUE;
- int lastTerminalAddress = Integer.MIN_VALUE;
- int size = 0;
- int ptNodes = 0;
- int maxNodes = 0;
- int maxRuns = 0;
- for (final PtNodeArray ptNodeArray : ptNodeArrays) {
- if (maxNodes < ptNodeArray.mData.size()) maxNodes = ptNodeArray.mData.size();
- for (final PtNode ptNode : ptNodeArray.mData) {
- ++ptNodes;
- if (ptNode.mChars.length > maxRuns) maxRuns = ptNode.mChars.length;
- if (ptNode.mFrequency >= 0) {
- if (ptNodeArray.mCachedAddressAfterUpdate < firstTerminalAddress)
- firstTerminalAddress = ptNodeArray.mCachedAddressAfterUpdate;
- if (ptNodeArray.mCachedAddressAfterUpdate > lastTerminalAddress)
- lastTerminalAddress = ptNodeArray.mCachedAddressAfterUpdate;
- }
- }
- if (ptNodeArray.mCachedAddressAfterUpdate + ptNodeArray.mCachedSize > size) {
- size = ptNodeArray.mCachedAddressAfterUpdate + ptNodeArray.mCachedSize;
- }
- }
- final int[] ptNodeCounts = new int[maxNodes + 1];
- final int[] runCounts = new int[maxRuns + 1];
- for (final PtNodeArray ptNodeArray : ptNodeArrays) {
- ++ptNodeCounts[ptNodeArray.mData.size()];
- for (final PtNode ptNode : ptNodeArray.mData) {
- ++runCounts[ptNode.mChars.length];
- }
- }
-
- MakedictLog.i("Statistics:\n"
- + " total file size " + size + "\n"
- + " " + ptNodeArrays.size() + " node arrays\n"
- + " " + ptNodes + " PtNodes (" + ((float)ptNodes / ptNodeArrays.size())
- + " PtNodes per node)\n"
- + " first terminal at " + firstTerminalAddress + "\n"
- + " last terminal at " + lastTerminalAddress + "\n"
- + " PtNode stats : max = " + maxNodes);
- for (int i = 0; i < ptNodeCounts.length; ++i) {
- MakedictLog.i(" " + i + " : " + ptNodeCounts[i]);
- }
- MakedictLog.i(" Character run stats : max = " + maxRuns);
- for (int i = 0; i < runCounts.length; ++i) {
- MakedictLog.i(" " + i + " : " + runCounts[i]);
- }
- }
-
- /**
- * Writes a file header to an output stream.
- *
- * @param destination the stream to write the file header to.
- * @param dict the dictionary to write.
- * @param formatOptions file format options.
- * @return the size of the header.
- */
- /* package */ static int writeDictionaryHeader(final OutputStream destination,
- final FusionDictionary dict, final FormatOptions formatOptions)
- throws IOException, UnsupportedFormatException {
- final int version = formatOptions.mVersion;
- if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
- || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
- throw new UnsupportedFormatException("Requested file format version " + version
- + ", but this implementation only supports versions "
- + FormatSpec.MINIMUM_SUPPORTED_VERSION + " through "
- + FormatSpec.MAXIMUM_SUPPORTED_VERSION);
- }
-
- ByteArrayOutputStream headerBuffer = new ByteArrayOutputStream(256);
-
- // The magic number in big-endian order.
- // Magic number for all versions.
- headerBuffer.write((byte) (0xFF & (FormatSpec.MAGIC_NUMBER >> 24)));
- headerBuffer.write((byte) (0xFF & (FormatSpec.MAGIC_NUMBER >> 16)));
- headerBuffer.write((byte) (0xFF & (FormatSpec.MAGIC_NUMBER >> 8)));
- headerBuffer.write((byte) (0xFF & FormatSpec.MAGIC_NUMBER));
- // Dictionary version.
- headerBuffer.write((byte) (0xFF & (version >> 8)));
- headerBuffer.write((byte) (0xFF & version));
-
- // Options flags
- final int options = makeOptionsValue(dict, formatOptions);
- headerBuffer.write((byte) (0xFF & (options >> 8)));
- headerBuffer.write((byte) (0xFF & options));
- final int headerSizeOffset = headerBuffer.size();
- // Placeholder to be written later with header size.
- for (int i = 0; i < 4; ++i) {
- headerBuffer.write(0);
- }
- // Write out the options.
- for (final String key : dict.mOptions.mAttributes.keySet()) {
- final String value = dict.mOptions.mAttributes.get(key);
- CharEncoding.writeString(headerBuffer, key);
- CharEncoding.writeString(headerBuffer, value);
- }
- final int size = headerBuffer.size();
- final byte[] bytes = headerBuffer.toByteArray();
- // Write out the header size.
- bytes[headerSizeOffset] = (byte) (0xFF & (size >> 24));
- bytes[headerSizeOffset + 1] = (byte) (0xFF & (size >> 16));
- bytes[headerSizeOffset + 2] = (byte) (0xFF & (size >> 8));
- bytes[headerSizeOffset + 3] = (byte) (0xFF & (size >> 0));
- destination.write(bytes);
-
- headerBuffer.close();
- return size;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
deleted file mode 100644
index d5516ef46..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright (C) 2012 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.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-import com.android.inputmethod.latin.utils.ByteArrayDictBuffer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Stack;
-
-public final class BinaryDictIOUtils {
- private static final boolean DBG = false;
-
- private BinaryDictIOUtils() {
- // This utility class is not publicly instantiable.
- }
-
- private static final class Position {
- public static final int NOT_READ_PTNODE_COUNT = -1;
-
- public int mAddress;
- public int mNumOfPtNode;
- public int mPosition;
- public int mLength;
-
- public Position(int address, int length) {
- mAddress = address;
- mLength = length;
- mNumOfPtNode = NOT_READ_PTNODE_COUNT;
- }
- }
-
- /**
- * Retrieves all node arrays without recursive call.
- */
- private static void readUnigramsAndBigramsBinaryInner(final DictDecoder dictDecoder,
- final int headerSize, final Map<Integer, String> words,
- final Map<Integer, Integer> frequencies,
- final Map<Integer, ArrayList<PendingAttribute>> bigrams,
- final FormatOptions formatOptions) {
- int[] pushedChars = new int[FormatSpec.MAX_WORD_LENGTH + 1];
-
- Stack<Position> stack = new Stack<Position>();
- int index = 0;
-
- Position initPos = new Position(headerSize, 0);
- stack.push(initPos);
-
- while (!stack.empty()) {
- Position p = stack.peek();
-
- if (DBG) {
- MakedictLog.d("read: address=" + p.mAddress + ", numOfPtNode=" +
- p.mNumOfPtNode + ", position=" + p.mPosition + ", length=" + p.mLength);
- }
-
- if (dictDecoder.getPosition() != p.mAddress) dictDecoder.setPosition(p.mAddress);
- if (index != p.mLength) index = p.mLength;
-
- if (p.mNumOfPtNode == Position.NOT_READ_PTNODE_COUNT) {
- p.mNumOfPtNode = dictDecoder.readPtNodeCount();
- p.mAddress += getPtNodeCountSize(p.mNumOfPtNode);
- p.mPosition = 0;
- }
- if (p.mNumOfPtNode == 0) {
- stack.pop();
- continue;
- }
- PtNodeInfo info = dictDecoder.readPtNode(p.mAddress, formatOptions);
- for (int i = 0; i < info.mCharacters.length; ++i) {
- pushedChars[index++] = info.mCharacters[i];
- }
- p.mPosition++;
-
- final boolean isMovedPtNode = isMovedPtNode(info.mFlags,
- formatOptions);
- final boolean isDeletedPtNode = isDeletedPtNode(info.mFlags,
- formatOptions);
- if (!isMovedPtNode && !isDeletedPtNode
- && info.mFrequency != FusionDictionary.PtNode.NOT_A_TERMINAL) {// found word
- words.put(info.mOriginalAddress, new String(pushedChars, 0, index));
- frequencies.put(info.mOriginalAddress, info.mFrequency);
- if (info.mBigrams != null) bigrams.put(info.mOriginalAddress, info.mBigrams);
- }
-
- if (p.mPosition == p.mNumOfPtNode) {
- if (formatOptions.mSupportsDynamicUpdate) {
- final boolean hasValidForwardLinkAddress =
- dictDecoder.readAndFollowForwardLink();
- if (hasValidForwardLinkAddress && dictDecoder.hasNextPtNodeArray()) {
- // The node array has a forward link.
- p.mNumOfPtNode = Position.NOT_READ_PTNODE_COUNT;
- p.mAddress = dictDecoder.getPosition();
- } else {
- stack.pop();
- }
- } else {
- stack.pop();
- }
- } else {
- // The Ptnode array has more PtNodes.
- p.mAddress = dictDecoder.getPosition();
- }
-
- if (!isMovedPtNode && hasChildrenAddress(info.mChildrenAddress)) {
- final Position childrenPos = new Position(info.mChildrenAddress, index);
- stack.push(childrenPos);
- }
- }
- }
-
- /**
- * Reads unigrams and bigrams from the binary file.
- * Doesn't store a full memory representation of the dictionary.
- *
- * @param dictDecoder the dict decoder.
- * @param words the map to store the address as a key and the word as a value.
- * @param frequencies the map to store the address as a key and the frequency as a value.
- * @param bigrams the map to store the address as a key and the list of address as a value.
- * @throws IOException if the file can't be read.
- * @throws UnsupportedFormatException if the format of the file is not recognized.
- */
- /* package */ static void readUnigramsAndBigramsBinary(final DictDecoder dictDecoder,
- final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
- final Map<Integer, ArrayList<PendingAttribute>> bigrams) throws IOException,
- UnsupportedFormatException {
- // Read header
- final FileHeader header = dictDecoder.readHeader();
- readUnigramsAndBigramsBinaryInner(dictDecoder, header.mHeaderSize, words,
- frequencies, bigrams, header.mFormatOptions);
- }
-
- /**
- * Gets the address of the last PtNode of the exact matching word in the dictionary.
- * If no match is found, returns NOT_VALID_WORD.
- *
- * @param dictDecoder the dict decoder.
- * @param word the word we search for.
- * @return the address of the terminal node.
- * @throws IOException if the file can't be read.
- * @throws UnsupportedFormatException if the format of the file is not recognized.
- */
- @UsedForTesting
- /* package */ static int getTerminalPosition(final DictDecoder dictDecoder,
- final String word) throws IOException, UnsupportedFormatException {
- if (word == null) return FormatSpec.NOT_VALID_WORD;
- dictDecoder.setPosition(0);
-
- final FileHeader header = dictDecoder.readHeader();
- int wordPos = 0;
- final int wordLen = word.codePointCount(0, word.length());
- for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) {
- if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD;
-
- do {
- final int ptNodeCount = dictDecoder.readPtNodeCount();
- boolean foundNextPtNode = false;
- for (int i = 0; i < ptNodeCount; ++i) {
- final int ptNodePos = dictDecoder.getPosition();
- final PtNodeInfo currentInfo = dictDecoder.readPtNode(ptNodePos,
- header.mFormatOptions);
- final boolean isMovedNode = isMovedPtNode(currentInfo.mFlags,
- header.mFormatOptions);
- final boolean isDeletedNode = isDeletedPtNode(currentInfo.mFlags,
- header.mFormatOptions);
- if (isMovedNode) continue;
- boolean same = true;
- for (int p = 0, j = word.offsetByCodePoints(0, wordPos);
- p < currentInfo.mCharacters.length;
- ++p, j = word.offsetByCodePoints(j, 1)) {
- if (wordPos + p >= wordLen
- || word.codePointAt(j) != currentInfo.mCharacters[p]) {
- same = false;
- break;
- }
- }
-
- if (same) {
- // found the PtNode matches the word.
- if (wordPos + currentInfo.mCharacters.length == wordLen) {
- if (currentInfo.mFrequency == PtNode.NOT_A_TERMINAL
- || isDeletedNode) {
- return FormatSpec.NOT_VALID_WORD;
- } else {
- return ptNodePos;
- }
- }
- wordPos += currentInfo.mCharacters.length;
- if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) {
- return FormatSpec.NOT_VALID_WORD;
- }
- foundNextPtNode = true;
- dictDecoder.setPosition(currentInfo.mChildrenAddress);
- break;
- }
- }
-
- // If we found the next PtNode, it is under the file pointer.
- // But if not, we are at the end of this node array so we expect to have
- // a forward link address that we need to consult and possibly resume
- // search on the next node array in the linked list.
- if (foundNextPtNode) break;
- if (!header.mFormatOptions.mSupportsDynamicUpdate) {
- return FormatSpec.NOT_VALID_WORD;
- }
-
- final boolean hasValidForwardLinkAddress =
- dictDecoder.readAndFollowForwardLink();
- if (!hasValidForwardLinkAddress || !dictDecoder.hasNextPtNodeArray()) {
- return FormatSpec.NOT_VALID_WORD;
- }
- } while(true);
- }
- return FormatSpec.NOT_VALID_WORD;
- }
-
- /**
- * @return the size written, in bytes. Always 3 bytes.
- */
- static int writeSInt24ToBuffer(final DictBuffer dictBuffer,
- final int value) {
- final int absValue = Math.abs(value);
- dictBuffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
- dictBuffer.put((byte)((absValue >> 8) & 0xFF));
- dictBuffer.put((byte)(absValue & 0xFF));
- return 3;
- }
-
- /**
- * @return the size written, in bytes. Always 3 bytes.
- */
- static int writeSInt24ToStream(final OutputStream destination, final int value)
- throws IOException {
- final int absValue = Math.abs(value);
- destination.write((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
- destination.write((byte)((absValue >> 8) & 0xFF));
- destination.write((byte)(absValue & 0xFF));
- return 3;
- }
-
- /**
- * @return the size written, in bytes. 1, 2, or 3 bytes.
- */
- private static int writeVariableAddress(final OutputStream destination, final int value)
- throws IOException {
- switch (BinaryDictEncoderUtils.getByteSize(value)) {
- case 1:
- destination.write((byte)value);
- break;
- case 2:
- destination.write((byte)(0xFF & (value >> 8)));
- destination.write((byte)(0xFF & value));
- break;
- case 3:
- destination.write((byte)(0xFF & (value >> 16)));
- destination.write((byte)(0xFF & (value >> 8)));
- destination.write((byte)(0xFF & value));
- break;
- }
- return BinaryDictEncoderUtils.getByteSize(value);
- }
-
- static void skipString(final DictBuffer dictBuffer,
- final boolean hasMultipleChars) {
- if (hasMultipleChars) {
- int character = CharEncoding.readChar(dictBuffer);
- while (character != FormatSpec.INVALID_CHARACTER) {
- character = CharEncoding.readChar(dictBuffer);
- }
- } else {
- CharEncoding.readChar(dictBuffer);
- }
- }
-
- /**
- * Write a string to a stream.
- *
- * @param destination the stream to write.
- * @param word the string to be written.
- * @return the size written, in bytes.
- * @throws IOException
- */
- private static int writeString(final OutputStream destination, final String word)
- throws IOException {
- int size = 0;
- final int length = word.length();
- for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
- final int codePoint = word.codePointAt(i);
- if (CharEncoding.getCharSize(codePoint) == 1) {
- destination.write((byte)codePoint);
- size++;
- } else {
- destination.write((byte)(0xFF & (codePoint >> 16)));
- destination.write((byte)(0xFF & (codePoint >> 8)));
- destination.write((byte)(0xFF & codePoint));
- size += 3;
- }
- }
- destination.write((byte)FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
- size += FormatSpec.PTNODE_TERMINATOR_SIZE;
- return size;
- }
-
- /**
- * Write a PtNode to an output stream from a PtNodeInfo.
- * A PtNode is an in-memory representation of a node in the patricia trie.
- * A PtNode info is a container for low-level information about how the
- * PtNode is stored in the binary format.
- *
- * @param destination the stream to write.
- * @param info the PtNode info to be written.
- * @return the size written, in bytes.
- */
- private static int writePtNode(final OutputStream destination, final PtNodeInfo info)
- throws IOException {
- int size = FormatSpec.PTNODE_FLAGS_SIZE;
- destination.write((byte)info.mFlags);
- final int parentOffset = info.mParentAddress == FormatSpec.NO_PARENT_ADDRESS ?
- FormatSpec.NO_PARENT_ADDRESS : info.mParentAddress - info.mOriginalAddress;
- size += writeSInt24ToStream(destination, parentOffset);
-
- for (int i = 0; i < info.mCharacters.length; ++i) {
- if (CharEncoding.getCharSize(info.mCharacters[i]) == 1) {
- destination.write((byte)info.mCharacters[i]);
- size++;
- } else {
- size += writeSInt24ToStream(destination, info.mCharacters[i]);
- }
- }
- if (info.mCharacters.length > 1) {
- destination.write((byte)FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
- size++;
- }
-
- if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
- destination.write((byte)info.mFrequency);
- size++;
- }
-
- if (DBG) {
- MakedictLog.d("writePtNode origin=" + info.mOriginalAddress + ", size=" + size
- + ", child=" + info.mChildrenAddress + ", characters ="
- + new String(info.mCharacters, 0, info.mCharacters.length));
- }
- final int childrenOffset = info.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS ?
- 0 : info.mChildrenAddress - (info.mOriginalAddress + size);
- writeSInt24ToStream(destination, childrenOffset);
- size += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
-
- if (info.mShortcutTargets != null && info.mShortcutTargets.size() > 0) {
- final int shortcutListSize =
- BinaryDictEncoderUtils.getShortcutListSize(info.mShortcutTargets);
- destination.write((byte)(shortcutListSize >> 8));
- destination.write((byte)(shortcutListSize & 0xFF));
- size += 2;
- final Iterator<WeightedString> shortcutIterator = info.mShortcutTargets.iterator();
- while (shortcutIterator.hasNext()) {
- final WeightedString target = shortcutIterator.next();
- destination.write((byte)BinaryDictEncoderUtils.makeShortcutFlags(
- shortcutIterator.hasNext(), target.mFrequency));
- size++;
- size += writeString(destination, target.mWord);
- }
- }
-
- if (info.mBigrams != null) {
- // TODO: Consolidate this code with the code that computes the size of the bigram list
- // in BinaryDictEncoderUtils#computeActualNodeArraySize
- for (int i = 0; i < info.mBigrams.size(); ++i) {
-
- final int bigramFrequency = info.mBigrams.get(i).mFrequency;
- int bigramFlags = (i < info.mBigrams.size() - 1)
- ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0;
- size++;
- final int bigramOffset = info.mBigrams.get(i).mAddress - (info.mOriginalAddress
- + size);
- bigramFlags |= (bigramOffset < 0) ? FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE : 0;
- switch (BinaryDictEncoderUtils.getByteSize(bigramOffset)) {
- case 1:
- bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE;
- break;
- case 2:
- bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES;
- break;
- case 3:
- bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES;
- break;
- }
- bigramFlags |= bigramFrequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY;
- destination.write((byte)bigramFlags);
- size += writeVariableAddress(destination, Math.abs(bigramOffset));
- }
- }
- return size;
- }
-
- /**
- * Compute the size of the PtNode.
- */
- static int computePtNodeSize(final PtNodeInfo info, final FormatOptions formatOptions) {
- int size = FormatSpec.PTNODE_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
- + BinaryDictEncoderUtils.getPtNodeCharactersSize(info.mCharacters)
- + getChildrenAddressSize(info.mFlags, formatOptions);
- if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
- size += FormatSpec.PTNODE_FREQUENCY_SIZE;
- }
- if (info.mShortcutTargets != null && !info.mShortcutTargets.isEmpty()) {
- size += BinaryDictEncoderUtils.getShortcutListSize(info.mShortcutTargets);
- }
- if (info.mBigrams != null) {
- for (final PendingAttribute attr : info.mBigrams) {
- size += FormatSpec.PTNODE_FLAGS_SIZE;
- size += BinaryDictEncoderUtils.getByteSize(attr.mAddress);
- }
- }
- return size;
- }
-
- /**
- * Write a node array to the stream.
- *
- * @param destination the stream to write.
- * @param infos an array of PtNodeInfo to be written.
- * @return the size written, in bytes.
- * @throws IOException
- */
- static int writeNodes(final OutputStream destination, final PtNodeInfo[] infos)
- throws IOException {
- int size = getPtNodeCountSize(infos.length);
- switch (getPtNodeCountSize(infos.length)) {
- case 1:
- destination.write((byte)infos.length);
- break;
- case 2:
- final int encodedPtNodeCount =
- infos.length | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG;
- destination.write((byte)(encodedPtNodeCount >> 8));
- destination.write((byte)(encodedPtNodeCount & 0xFF));
- break;
- default:
- throw new RuntimeException("Invalid node count size.");
- }
- for (final PtNodeInfo info : infos) size += writePtNode(destination, info);
- writeSInt24ToStream(destination, FormatSpec.NO_FORWARD_LINK_ADDRESS);
- return size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
- }
-
- private static final int HEADER_READING_BUFFER_SIZE = 16384;
- /**
- * Convenience method to read the header of a binary file.
- *
- * This is quite resource intensive - don't call when performance is critical.
- *
- * @param file The file to read.
- * @param offset The offset in the file where to start reading the data.
- * @param length The length of the data file.
- */
- private static FileHeader getDictionaryFileHeader(
- final File file, final long offset, final long length)
- throws FileNotFoundException, IOException, UnsupportedFormatException {
- final byte[] buffer = new byte[HEADER_READING_BUFFER_SIZE];
- final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file,
- new DictDecoder.DictionaryBufferFactory() {
- @Override
- public DictBuffer getDictionaryBuffer(File file)
- throws FileNotFoundException, IOException {
- final FileInputStream inStream = new FileInputStream(file);
- try {
- inStream.skip(offset);
- inStream.read(buffer);
- return new ByteArrayDictBuffer(buffer);
- } finally {
- inStream.close();
- }
- }
- }
- );
- return dictDecoder.readHeader();
- }
-
- public static FileHeader getDictionaryFileHeaderOrNull(final File file, final long offset,
- final long length) {
- try {
- final FileHeader header = getDictionaryFileHeader(file, offset, length);
- return header;
- } catch (UnsupportedFormatException e) {
- return null;
- } catch (IOException e) {
- return null;
- }
- }
-
- /**
- * Helper method to hide the actual value of the no children address.
- */
- public static boolean hasChildrenAddress(final int address) {
- return FormatSpec.NO_CHILDREN_ADDRESS != address;
- }
-
- /**
- * Helper method to check whether the node is moved.
- */
- public static boolean isMovedPtNode(final int flags, final FormatOptions options) {
- return options.mSupportsDynamicUpdate
- && ((flags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) == FormatSpec.FLAG_IS_MOVED);
- }
-
- /**
- * Helper method to check whether the dictionary can be updated dynamically.
- */
- public static boolean supportsDynamicUpdate(final FormatOptions options) {
- return options.mVersion >= FormatSpec.FIRST_VERSION_WITH_DYNAMIC_UPDATE
- && options.mSupportsDynamicUpdate;
- }
-
- /**
- * Helper method to check whether the node is deleted.
- */
- public static boolean isDeletedPtNode(final int flags, final FormatOptions formatOptions) {
- return formatOptions.mSupportsDynamicUpdate
- && ((flags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) == FormatSpec.FLAG_IS_DELETED);
- }
-
- /**
- * Compute the binary size of the node count
- * @param count the node count
- * @return the size of the node count, either 1 or 2 bytes.
- */
- public static int getPtNodeCountSize(final int count) {
- if (FormatSpec.MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT >= count) {
- return 1;
- } else if (FormatSpec.MAX_PTNODES_IN_A_PT_NODE_ARRAY >= count) {
- return 2;
- } else {
- throw new RuntimeException("Can't have more than "
- + FormatSpec.MAX_PTNODES_IN_A_PT_NODE_ARRAY + " PtNode in a PtNodeArray (found "
- + count + ")");
- }
- }
-
- static int getChildrenAddressSize(final int optionFlags,
- final FormatOptions formatOptions) {
- if (formatOptions.mSupportsDynamicUpdate) return FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
- switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) {
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE:
- return 1;
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES:
- return 2;
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES:
- return 3;
- case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS:
- default:
- return 0;
- }
- }
-
- /**
- * Calculate bigram frequency from compressed value
- *
- * @param unigramFrequency
- * @param bigramFrequency compressed frequency
- * @return approximate bigram frequency
- */
- public static int reconstructBigramFrequency(final int unigramFrequency,
- final int bigramFrequency) {
- final float stepSize = (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency)
- / (1.5f + FormatSpec.MAX_BIGRAM_FREQUENCY);
- final float resultFreqFloat = unigramFrequency + stepSize * (bigramFrequency + 1.0f);
- return (int)resultFreqFloat;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
deleted file mode 100644
index 3dbeee099..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.utils.ByteArrayDictBuffer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.ArrayList;
-import java.util.TreeMap;
-
-/**
- * An interface of binary dictionary decoders.
- */
-public interface DictDecoder {
-
- /**
- * Reads and returns the file header.
- */
- public FileHeader readHeader() throws IOException, UnsupportedFormatException;
-
- /**
- * Reads PtNode from nodeAddress.
- * @param ptNodePos the position of PtNode.
- * @param formatOptions the format options.
- * @return PtNodeInfo.
- */
- public PtNodeInfo readPtNode(final int ptNodePos, final FormatOptions formatOptions);
-
- /**
- * Reads a buffer and returns the memory representation of the dictionary.
- *
- * This high-level method takes a buffer and reads its contents, populating a
- * FusionDictionary structure. The optional dict argument is an existing dictionary to
- * which words from the buffer should be added. If it is null, a new dictionary is created.
- *
- * @param dict an optional dictionary to add words to, or null.
- * @param deleteDictIfBroken a flag indicating whether this method should remove the broken
- * dictionary or not.
- * @return the created (or merged) dictionary.
- */
- @UsedForTesting
- public FusionDictionary readDictionaryBinary(final FusionDictionary dict,
- final boolean deleteDictIfBroken)
- throws FileNotFoundException, IOException, UnsupportedFormatException;
-
- /**
- * Gets the address of the last PtNode of the exact matching word in the dictionary.
- * If no match is found, returns NOT_VALID_WORD.
- *
- * @param word the word we search for.
- * @return the address of the terminal node.
- * @throws IOException if the file can't be read.
- * @throws UnsupportedFormatException if the format of the file is not recognized.
- */
- @UsedForTesting
- public int getTerminalPosition(final String word)
- throws IOException, UnsupportedFormatException;
-
- /**
- * Reads unigrams and bigrams from the binary file.
- * Doesn't store a full memory representation of the dictionary.
- *
- * @param words the map to store the address as a key and the word as a value.
- * @param frequencies the map to store the address as a key and the frequency as a value.
- * @param bigrams the map to store the address as a key and the list of address as a value.
- * @throws IOException if the file can't be read.
- * @throws UnsupportedFormatException if the format of the file is not recognized.
- */
- @UsedForTesting
- public void readUnigramsAndBigramsBinary(final TreeMap<Integer, String> words,
- final TreeMap<Integer, Integer> frequencies,
- final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams)
- throws IOException, UnsupportedFormatException;
-
- /**
- * Sets the position of the buffer to the given value.
- *
- * @param newPos the new position
- */
- public void setPosition(final int newPos);
-
- /**
- * Gets the position of the buffer.
- *
- * @return the position
- */
- public int getPosition();
-
- /**
- * Reads and returns the PtNode count out of a buffer and forwards the pointer.
- */
- public int readPtNodeCount();
-
- /**
- * Reads the forward link and advances the position.
- *
- * @return true if this method moves the file pointer, false otherwise.
- */
- public boolean readAndFollowForwardLink();
- public boolean hasNextPtNodeArray();
-
- /**
- * Opens the dictionary file and makes DictBuffer.
- */
- @UsedForTesting
- public void openDictBuffer() throws FileNotFoundException, IOException;
- @UsedForTesting
- public boolean isDictBufferOpen();
-
- // Constants for DictionaryBufferFactory.
- public static final int USE_READONLY_BYTEBUFFER = 0x01000000;
- public static final int USE_BYTEARRAY = 0x02000000;
- public static final int USE_WRITABLE_BYTEBUFFER = 0x03000000;
- public static final int MASK_DICTBUFFER = 0x0F000000;
-
- public interface DictionaryBufferFactory {
- public DictBuffer getDictionaryBuffer(final File file)
- throws FileNotFoundException, IOException;
- }
-
- /**
- * Creates DictionaryBuffer using a ByteBuffer
- *
- * This class uses less memory than DictionaryBufferFromByteArrayFactory,
- * but doesn't perform as fast.
- * When operating on a big dictionary, this class is preferred.
- */
- public static final class DictionaryBufferFromReadOnlyByteBufferFactory
- implements DictionaryBufferFactory {
- @Override
- public DictBuffer getDictionaryBuffer(final File file)
- throws FileNotFoundException, IOException {
- FileInputStream inStream = null;
- ByteBuffer buffer = null;
- try {
- inStream = new FileInputStream(file);
- buffer = inStream.getChannel().map(FileChannel.MapMode.READ_ONLY,
- 0, file.length());
- } finally {
- if (inStream != null) {
- inStream.close();
- }
- }
- if (buffer != null) {
- return new BinaryDictDecoderUtils.ByteBufferDictBuffer(buffer);
- }
- return null;
- }
- }
-
- /**
- * Creates DictionaryBuffer using a byte array
- *
- * This class performs faster than other classes, but consumes more memory.
- * When operating on a small dictionary, this class is preferred.
- */
- public static final class DictionaryBufferFromByteArrayFactory
- implements DictionaryBufferFactory {
- @Override
- public DictBuffer getDictionaryBuffer(final File file)
- throws FileNotFoundException, IOException {
- FileInputStream inStream = null;
- try {
- inStream = new FileInputStream(file);
- final byte[] array = new byte[(int) file.length()];
- inStream.read(array);
- return new ByteArrayDictBuffer(array);
- } finally {
- if (inStream != null) {
- inStream.close();
- }
- }
- }
- }
-
- /**
- * Creates DictionaryBuffer using a writable ByteBuffer and a RandomAccessFile.
- *
- * This class doesn't perform as fast as other classes,
- * but this class is the only option available for destructive operations (insert or delete)
- * on a dictionary.
- */
- @UsedForTesting
- public static final class DictionaryBufferFromWritableByteBufferFactory
- implements DictionaryBufferFactory {
- @Override
- public DictBuffer getDictionaryBuffer(final File file)
- throws FileNotFoundException, IOException {
- RandomAccessFile raFile = null;
- ByteBuffer buffer = null;
- try {
- raFile = new RandomAccessFile(file, "rw");
- buffer = raFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, file.length());
- } finally {
- if (raFile != null) {
- raFile.close();
- }
- }
- if (buffer != null) {
- return new BinaryDictDecoderUtils.ByteBufferDictBuffer(buffer);
- }
- return null;
- }
- }
-
- public void skipPtNode(final FormatOptions formatOptions);
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
deleted file mode 100644
index ea5d492d8..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
-
-import java.io.IOException;
-
-/**
- * An interface of binary dictionary encoder.
- */
-public interface DictEncoder {
- public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
- throws IOException, UnsupportedFormatException;
-
- public void setPosition(final int position);
- public int getPosition();
- public void writePtNodeCount(final int ptNodeCount);
- public void writeForwardLinkAddress(final int forwardLinkAddress);
-
- public void writePtNode(final PtNode ptNode, final int parentPosition,
- final FormatOptions formatOptions, final FusionDictionary dict);
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java
deleted file mode 100644
index c4f7ec91f..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * An interface of a binary dictionary updater.
- */
-@UsedForTesting
-public interface DictUpdater extends DictDecoder {
-
- /**
- * Deletes the word from the binary dictionary.
- *
- * @param word the word to be deleted.
- */
- @UsedForTesting
- public void deleteWord(final String word) throws IOException, UnsupportedFormatException;
-
- /**
- * Inserts a word into a binary dictionary.
- *
- * @param word the word to be inserted.
- * @param frequency the frequency of the new word.
- * @param bigramStrings bigram list, or null if none.
- * @param shortcuts shortcut list, or null if none.
- * @param isBlackListEntry whether this should be a blacklist entry.
- */
- // TODO: Support batch insertion.
- @UsedForTesting
- public void insertWord(final String word, final int frequency,
- final ArrayList<WeightedString> bigramStrings,
- final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
- final boolean isBlackListEntry) throws IOException, UnsupportedFormatException;
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java
new file mode 100644
index 000000000..df447fd75
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java
@@ -0,0 +1,89 @@
+/*
+ * 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.makedict;
+
+import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+
+/**
+ * Class representing dictionary header.
+ */
+public final class DictionaryHeader {
+ public final int mBodyOffset;
+ public final DictionaryOptions mDictionaryOptions;
+ public final FormatOptions mFormatOptions;
+
+ // Note that these are corresponding definitions in native code in latinime::HeaderPolicy
+ // and latinime::HeaderReadWriteUtils.
+ // TODO: Standardize the key names and bump up the format version, taking care not to
+ // break format version 2 dictionaries.
+ public static final String DICTIONARY_VERSION_KEY = "version";
+ public static final String DICTIONARY_LOCALE_KEY = "locale";
+ public static final String DICTIONARY_ID_KEY = "dictionary";
+ public static final String DICTIONARY_DESCRIPTION_KEY = "description";
+ public static final String DICTIONARY_DATE_KEY = "date";
+ public static final String HAS_HISTORICAL_INFO_KEY = "HAS_HISTORICAL_INFO";
+ public static final String USES_FORGETTING_CURVE_KEY = "USES_FORGETTING_CURVE";
+ public static final String FORGETTING_CURVE_OCCURRENCES_TO_LEVEL_UP_KEY =
+ "FORGETTING_CURVE_OCCURRENCES_TO_LEVEL_UP";
+ public static final String FORGETTING_CURVE_PROBABILITY_VALUES_TABLE_ID_KEY =
+ "FORGETTING_CURVE_PROBABILITY_VALUES_TABLE_ID";
+ public static final String FORGETTING_CURVE_DURATION_TO_LEVEL_DOWN_IN_SECONDS_KEY =
+ "FORGETTING_CURVE_DURATION_TO_LEVEL_DOWN_IN_SECONDS";
+ 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 DictionaryHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
+ final FormatOptions formatOptions) throws UnsupportedFormatException {
+ mDictionaryOptions = dictionaryOptions;
+ mFormatOptions = formatOptions;
+ mBodyOffset = formatOptions.mVersion < FormatSpec.VERSION4 ? headerSize : 0;
+ if (null == getLocaleString()) {
+ throw new UnsupportedFormatException("Cannot create a FileHeader without a locale");
+ }
+ if (null == getVersion()) {
+ throw new UnsupportedFormatException(
+ "Cannot create a FileHeader without a version");
+ }
+ if (null == getId()) {
+ throw new UnsupportedFormatException("Cannot create a FileHeader without an ID");
+ }
+ }
+
+ // Helper method to get the locale as a String
+ public String getLocaleString() {
+ return mDictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_KEY);
+ }
+
+ // Helper method to get the version String
+ public String getVersion() {
+ return mDictionaryOptions.mAttributes.get(DICTIONARY_VERSION_KEY);
+ }
+
+ // Helper method to get the dictionary ID as a String
+ public String getId() {
+ return mDictionaryOptions.mAttributes.get(DICTIONARY_ID_KEY);
+ }
+
+ // Helper method to get the description
+ public String getDescription() {
+ // TODO: Right now each dictionary file comes with a description in its own language.
+ // It will display as is no matter the device's locale. It should be internationalized.
+ return mDictionaryOptions.mAttributes.get(DICTIONARY_DESCRIPTION_KEY);
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
deleted file mode 100644
index 28da9ffdd..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
+++ /dev/null
@@ -1,492 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * The utility class to help dynamic updates on the binary dictionary.
- *
- * All the methods in this class are static.
- */
-@UsedForTesting
-public final class DynamicBinaryDictIOUtils {
- private static final boolean DBG = false;
- private static final int MAX_JUMPS = 10000;
-
- private DynamicBinaryDictIOUtils() {
- // This utility class is not publicly instantiable.
- }
-
- /* package */ static int markAsDeleted(final int flags) {
- return (flags & (~FormatSpec.MASK_CHILDREN_ADDRESS_TYPE)) | FormatSpec.FLAG_IS_DELETED;
- }
-
- /**
- * Update a parent address in a PtNode that is referred to by ptNodeOriginAddress.
- *
- * @param dictUpdater the DictUpdater to write.
- * @param ptNodeOriginAddress the address of the PtNode.
- * @param newParentAddress the absolute address of the parent.
- * @param formatOptions file format options.
- */
- private static void updateParentAddress(final Ver3DictUpdater dictUpdater,
- final int ptNodeOriginAddress, final int newParentAddress,
- final FormatOptions formatOptions) {
- final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
- final int originalPosition = dictBuffer.position();
- dictBuffer.position(ptNodeOriginAddress);
- if (!formatOptions.mSupportsDynamicUpdate) {
- throw new RuntimeException("this file format does not support parent addresses");
- }
- final int flags = dictBuffer.readUnsignedByte();
- if (BinaryDictIOUtils.isMovedPtNode(flags, formatOptions)) {
- // If the node is moved, the parent address is stored in the destination node.
- // We are guaranteed to process the destination node later, so there is no need to
- // update anything here.
- dictBuffer.position(originalPosition);
- return;
- }
- if (DBG) {
- MakedictLog.d("update parent address flags=" + flags + ", " + ptNodeOriginAddress);
- }
- final int parentOffset = newParentAddress - ptNodeOriginAddress;
- BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, parentOffset);
- dictBuffer.position(originalPosition);
- }
-
- /**
- * Update parent addresses in a node array stored at ptNodeOriginAddress.
- *
- * @param dictUpdater the DictUpdater to be modified.
- * @param ptNodeOriginAddress the address of the node array to update.
- * @param newParentAddress the address to be written.
- * @param formatOptions file format options.
- */
- private static void updateParentAddresses(final Ver3DictUpdater dictUpdater,
- final int ptNodeOriginAddress, final int newParentAddress,
- final FormatOptions formatOptions) {
- final int originalPosition = dictUpdater.getPosition();
- dictUpdater.setPosition(ptNodeOriginAddress);
- do {
- final int count = dictUpdater.readPtNodeCount();
- for (int i = 0; i < count; ++i) {
- updateParentAddress(dictUpdater, dictUpdater.getPosition(), newParentAddress,
- formatOptions);
- dictUpdater.skipPtNode(formatOptions);
- }
- if (!dictUpdater.readAndFollowForwardLink()) break;
- if (dictUpdater.getPosition() == FormatSpec.NO_FORWARD_LINK_ADDRESS) break;
- } while (formatOptions.mSupportsDynamicUpdate);
- dictUpdater.setPosition(originalPosition);
- }
-
- /**
- * Update a children address in a PtNode that is addressed by ptNodeOriginAddress.
- *
- * @param dictUpdater the DictUpdater to write.
- * @param ptNodeOriginAddress the address of the PtNode.
- * @param newChildrenAddress the absolute address of the child.
- * @param formatOptions file format options.
- */
- private static void updateChildrenAddress(final Ver3DictUpdater dictUpdater,
- final int ptNodeOriginAddress, final int newChildrenAddress,
- final FormatOptions formatOptions) {
- final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
- final int originalPosition = dictBuffer.position();
- dictBuffer.position(ptNodeOriginAddress);
- final int flags = dictBuffer.readUnsignedByte();
- BinaryDictDecoderUtils.readParentAddress(dictBuffer, formatOptions);
- BinaryDictIOUtils.skipString(dictBuffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
- if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) dictBuffer.readUnsignedByte();
- final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS
- ? FormatSpec.NO_CHILDREN_ADDRESS : newChildrenAddress - dictBuffer.position();
- BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, childrenOffset);
- dictBuffer.position(originalPosition);
- }
-
- /**
- * Helper method to move a PtNode to the tail of the file.
- */
- private static int movePtNode(final OutputStream destination,
- final Ver3DictUpdater dictUpdater, final PtNodeInfo info,
- final int nodeArrayOriginAddress, final int oldNodeAddress,
- final FormatOptions formatOptions) throws IOException {
- final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
- updateParentAddress(dictUpdater, oldNodeAddress, dictBuffer.limit() + 1, formatOptions);
- dictBuffer.position(oldNodeAddress);
- final int currentFlags = dictBuffer.readUnsignedByte();
- dictBuffer.position(oldNodeAddress);
- dictBuffer.put((byte)(FormatSpec.FLAG_IS_MOVED | (currentFlags
- & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG))));
- int size = FormatSpec.PTNODE_FLAGS_SIZE;
- updateForwardLink(dictUpdater, nodeArrayOriginAddress, dictBuffer.limit(), formatOptions);
- size += BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { info });
- return size;
- }
-
- @SuppressWarnings("unused")
- private static void updateForwardLink(final Ver3DictUpdater dictUpdater,
- final int nodeArrayOriginAddress, final int newNodeArrayAddress,
- final FormatOptions formatOptions) {
- final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
- dictUpdater.setPosition(nodeArrayOriginAddress);
- int jumpCount = 0;
- while (jumpCount++ < MAX_JUMPS) {
- final int count = dictUpdater.readPtNodeCount();
- for (int i = 0; i < count; ++i) {
- dictUpdater.readPtNode(dictUpdater.getPosition(), formatOptions);
- }
- final int forwardLinkAddress = dictBuffer.readUnsignedInt24();
- if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
- dictBuffer.position(dictBuffer.position() - FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
- BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, newNodeArrayAddress);
- return;
- }
- dictBuffer.position(forwardLinkAddress);
- }
- if (DBG && jumpCount >= MAX_JUMPS) {
- throw new RuntimeException("too many jumps, probably a bug.");
- }
- }
-
- /**
- * Move a PtNode that is referred to by oldPtNodeOrigin to the tail of the file, and set the
- * children address to the byte after the PtNode.
- *
- * @param fileEndAddress the address of the tail of the file.
- * @param codePoints the characters to put inside the PtNode.
- * @param length how many code points to read from codePoints.
- * @param flags the flags for this PtNode.
- * @param frequency the frequency of this terminal.
- * @param parentAddress the address of the parent PtNode of this PtNode.
- * @param shortcutTargets the shortcut targets for this PtNode.
- * @param bigrams the bigrams for this PtNode.
- * @param destination the stream representing the tail of the file.
- * @param dictUpdater the DictUpdater.
- * @param oldPtNodeArrayOrigin the origin of the old PtNode array this PtNode was a part of.
- * @param oldPtNodeOrigin the old origin where this PtNode used to be stored.
- * @param formatOptions format options for this dictionary.
- * @return the size written, in bytes.
- * @throws IOException if the file can't be accessed
- */
- private static int movePtNode(final int fileEndAddress, final int[] codePoints,
- final int length, final int flags, final int frequency, final int parentAddress,
- final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<PendingAttribute> bigrams, final OutputStream destination,
- final Ver3DictUpdater dictUpdater, final int oldPtNodeArrayOrigin,
- final int oldPtNodeOrigin, final FormatOptions formatOptions) throws IOException {
- int size = 0;
- final int newPtNodeOrigin = fileEndAddress + 1;
- final int[] writtenCharacters = Arrays.copyOfRange(codePoints, 0, length);
- final PtNodeInfo tmpInfo = new PtNodeInfo(newPtNodeOrigin, -1 /* endAddress */,
- flags, writtenCharacters, frequency, parentAddress, FormatSpec.NO_CHILDREN_ADDRESS,
- shortcutTargets, bigrams);
- size = BinaryDictIOUtils.computePtNodeSize(tmpInfo, formatOptions);
- final PtNodeInfo newInfo = new PtNodeInfo(newPtNodeOrigin, newPtNodeOrigin + size,
- flags, writtenCharacters, frequency, parentAddress,
- fileEndAddress + 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE, shortcutTargets,
- bigrams);
- movePtNode(destination, dictUpdater, newInfo, oldPtNodeArrayOrigin, oldPtNodeOrigin,
- formatOptions);
- return 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
- }
-
- /**
- * Converts a list of WeightedString to a list of PendingAttribute.
- */
- public static ArrayList<PendingAttribute> resolveBigramPositions(final DictUpdater dictUpdater,
- final ArrayList<WeightedString> bigramStrings)
- throws IOException, UnsupportedFormatException {
- if (bigramStrings == null) return CollectionUtils.newArrayList();
- final ArrayList<PendingAttribute> bigrams = CollectionUtils.newArrayList();
- for (final WeightedString bigram : bigramStrings) {
- final int pos = dictUpdater.getTerminalPosition(bigram.mWord);
- if (pos == FormatSpec.NOT_VALID_WORD) {
- // TODO: figure out what is the correct thing to do here.
- } else {
- bigrams.add(new PendingAttribute(bigram.mFrequency, pos));
- }
- }
- return bigrams;
- }
-
- /**
- * Insert a word into a binary dictionary.
- *
- * @param dictUpdater the dict updater.
- * @param destination a stream to the underlying file, with the pointer at the end of the file.
- * @param word the word to insert.
- * @param frequency the frequency of the new word.
- * @param bigramStrings bigram list, or null if none.
- * @param shortcuts shortcut list, or null if none.
- * @param isBlackListEntry whether this should be a blacklist entry.
- * @throws IOException if the file can't be accessed.
- * @throws UnsupportedFormatException if the existing dictionary is in an unexpected format.
- */
- // TODO: Support batch insertion.
- // TODO: Remove @UsedForTesting once UserHistoryDictionary is implemented by BinaryDictionary.
- @UsedForTesting
- public static void insertWord(final Ver3DictUpdater dictUpdater,
- final OutputStream destination, final String word, final int frequency,
- final ArrayList<WeightedString> bigramStrings,
- final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
- final boolean isBlackListEntry)
- throws IOException, UnsupportedFormatException {
- final ArrayList<PendingAttribute> bigrams = resolveBigramPositions(dictUpdater,
- bigramStrings);
- final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
-
- final boolean isTerminal = true;
- final boolean hasBigrams = !bigrams.isEmpty();
- final boolean hasShortcuts = shortcuts != null && !shortcuts.isEmpty();
-
- // find the insert position of the word.
- if (dictBuffer.position() != 0) dictBuffer.position(0);
- final FileHeader fileHeader = dictUpdater.readHeader();
-
- int wordPos = 0, address = dictBuffer.position(), nodeOriginAddress = dictBuffer.position();
- final int[] codePoints = FusionDictionary.getCodePoints(word);
- final int wordLen = codePoints.length;
-
- for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) {
- if (wordPos >= wordLen) break;
- nodeOriginAddress = dictBuffer.position();
- int nodeParentAddress = -1;
- final int ptNodeCount = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
- boolean foundNextNode = false;
-
- for (int i = 0; i < ptNodeCount; ++i) {
- address = dictBuffer.position();
- final PtNodeInfo currentInfo = dictUpdater.readPtNode(address,
- fileHeader.mFormatOptions);
- final boolean isMovedNode = BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags,
- fileHeader.mFormatOptions);
- if (isMovedNode) continue;
- nodeParentAddress = (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS)
- ? FormatSpec.NO_PARENT_ADDRESS : currentInfo.mParentAddress + address;
- boolean matched = true;
- for (int p = 0; p < currentInfo.mCharacters.length; ++p) {
- if (wordPos + p >= wordLen) {
- /*
- * splitting
- * before
- * abcd - ef
- *
- * insert "abc"
- *
- * after
- * abc - d - ef
- */
- final int newNodeAddress = dictBuffer.limit();
- final int flags = BinaryDictEncoderUtils.makePtNodeFlags(p > 1,
- isTerminal, 0, hasShortcuts, hasBigrams, false /* isNotAWord */,
- false /* isBlackListEntry */, fileHeader.mFormatOptions);
- int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p, flags,
- frequency, nodeParentAddress, shortcuts, bigrams, destination,
- dictUpdater, nodeOriginAddress, address, fileHeader.mFormatOptions);
-
- final int[] characters2 = Arrays.copyOfRange(currentInfo.mCharacters, p,
- currentInfo.mCharacters.length);
- if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
- updateParentAddresses(dictUpdater, currentInfo.mChildrenAddress,
- newNodeAddress + written + 1, fileHeader.mFormatOptions);
- }
- final PtNodeInfo newInfo2 = new PtNodeInfo(
- newNodeAddress + written + 1, -1 /* endAddress */,
- currentInfo.mFlags, characters2, currentInfo.mFrequency,
- newNodeAddress + 1, currentInfo.mChildrenAddress,
- currentInfo.mShortcutTargets, currentInfo.mBigrams);
- BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { newInfo2 });
- return;
- } else if (codePoints[wordPos + p] != currentInfo.mCharacters[p]) {
- if (p > 0) {
- /*
- * splitting
- * before
- * ab - cd
- *
- * insert "ac"
- *
- * after
- * a - b - cd
- * |
- * - c
- */
-
- final int newNodeAddress = dictBuffer.limit();
- final int childrenAddress = currentInfo.mChildrenAddress;
-
- // move prefix
- final int prefixFlags = BinaryDictEncoderUtils.makePtNodeFlags(p > 1,
- false /* isTerminal */, 0 /* childrenAddressSize*/,
- false /* hasShortcut */, false /* hasBigrams */,
- false /* isNotAWord */, false /* isBlackListEntry */,
- fileHeader.mFormatOptions);
- int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p,
- prefixFlags, -1 /* frequency */, nodeParentAddress, null, null,
- destination, dictUpdater, nodeOriginAddress, address,
- fileHeader.mFormatOptions);
-
- final int[] suffixCharacters = Arrays.copyOfRange(
- currentInfo.mCharacters, p, currentInfo.mCharacters.length);
- if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
- updateParentAddresses(dictUpdater, currentInfo.mChildrenAddress,
- newNodeAddress + written + 1, fileHeader.mFormatOptions);
- }
- final int suffixFlags = BinaryDictEncoderUtils.makePtNodeFlags(
- suffixCharacters.length > 1,
- (currentInfo.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0,
- 0 /* childrenAddressSize */,
- (currentInfo.mFlags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)
- != 0,
- (currentInfo.mFlags & FormatSpec.FLAG_HAS_BIGRAMS) != 0,
- isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
- final PtNodeInfo suffixInfo = new PtNodeInfo(
- newNodeAddress + written + 1, -1 /* endAddress */, suffixFlags,
- suffixCharacters, currentInfo.mFrequency, newNodeAddress + 1,
- currentInfo.mChildrenAddress, currentInfo.mShortcutTargets,
- currentInfo.mBigrams);
- written += BinaryDictIOUtils.computePtNodeSize(suffixInfo,
- fileHeader.mFormatOptions) + 1;
-
- final int[] newCharacters = Arrays.copyOfRange(codePoints, wordPos + p,
- codePoints.length);
- final int flags = BinaryDictEncoderUtils.makePtNodeFlags(
- newCharacters.length > 1, isTerminal,
- 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
- isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
- final PtNodeInfo newInfo = new PtNodeInfo(
- newNodeAddress + written, -1 /* endAddress */, flags,
- newCharacters, frequency, newNodeAddress + 1,
- FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
- BinaryDictIOUtils.writeNodes(destination,
- new PtNodeInfo[] { suffixInfo, newInfo });
- return;
- }
- matched = false;
- break;
- }
- }
-
- if (matched) {
- if (wordPos + currentInfo.mCharacters.length == wordLen) {
- // the word exists in the dictionary.
- // only update the PtNode.
- final int newNodeAddress = dictBuffer.limit();
- final boolean hasMultipleChars = currentInfo.mCharacters.length > 1;
- final int flags = BinaryDictEncoderUtils.makePtNodeFlags(hasMultipleChars,
- isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
- isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
- final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress + 1,
- -1 /* endAddress */, flags, currentInfo.mCharacters, frequency,
- nodeParentAddress, currentInfo.mChildrenAddress, shortcuts,
- bigrams);
- movePtNode(destination, dictUpdater, newInfo, nodeOriginAddress, address,
- fileHeader.mFormatOptions);
- return;
- }
- wordPos += currentInfo.mCharacters.length;
- if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) {
- /*
- * found the prefix of the word.
- * make new PtNode and link to the PtNode from this PtNode.
- *
- * before
- * ab - cd
- *
- * insert "abcde"
- *
- * after
- * ab - cd - e
- */
- final int newNodeArrayAddress = dictBuffer.limit();
- updateChildrenAddress(dictUpdater, address, newNodeArrayAddress,
- fileHeader.mFormatOptions);
- final int newNodeAddress = newNodeArrayAddress + 1;
- final boolean hasMultipleChars = (wordLen - wordPos) > 1;
- final int flags = BinaryDictEncoderUtils.makePtNodeFlags(hasMultipleChars,
- isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
- isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
- final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
- final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress, -1, flags,
- characters, frequency, address, FormatSpec.NO_CHILDREN_ADDRESS,
- shortcuts, bigrams);
- BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { newInfo });
- return;
- }
- dictBuffer.position(currentInfo.mChildrenAddress);
- foundNextNode = true;
- break;
- }
- }
-
- if (foundNextNode) continue;
-
- // reached the end of the array.
- final int linkAddressPosition = dictBuffer.position();
- int nextLink = dictBuffer.readUnsignedInt24();
- if ((nextLink & FormatSpec.MSB24) != 0) {
- nextLink = -(nextLink & FormatSpec.SINT24_MAX);
- }
- if (nextLink == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
- /*
- * expand this node.
- *
- * before
- * ab - cd
- *
- * insert "abef"
- *
- * after
- * ab - cd
- * |
- * - ef
- */
-
- // change the forward link address.
- final int newNodeAddress = dictBuffer.limit();
- dictBuffer.position(linkAddressPosition);
- BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, newNodeAddress);
-
- final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
- final int flags = BinaryDictEncoderUtils.makePtNodeFlags(characters.length > 1,
- isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
- isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
- final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress + 1,
- -1 /* endAddress */, flags, characters, frequency, nodeParentAddress,
- FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
- BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[]{ newInfo });
- return;
- } else {
- depth--;
- dictBuffer.position(nextLink);
- }
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index b56234f6d..f25503488 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -18,10 +18,9 @@ package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory;
-import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
-import java.io.File;
+import java.util.Date;
+import java.util.HashMap;
/**
* Dictionary File Format Specification.
@@ -40,12 +39,8 @@ public final class FormatSpec {
* 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
- * o | has bigrams ? 1 bit, 1 = yes, 0 = no : CONTAINS_BIGRAMS_FLAG
- * n | FRENCH_LIGATURE_PROCESSING_FLAG
- * f | supports dynamic updates ? 1 bit, 1 = yes, 0 = no : SUPPORTS_DYNAMIC_UPDATE
- * l | GERMAN_UMLAUT_PROCESSING_FLAG
- * a |
- * gs
+ * o |
+ * nflags
*
* h |
* e | size of the file header, 4bytes
@@ -82,45 +77,36 @@ public final class FormatSpec {
* s
*
* f |
- * o | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header)
- * r | forward link address, 3byte
- * w | 1 byte = bbbbbbbb match
- * a | case 1xxxxxxx => -((xxxxxxx << 16) + (next byte << 8) + next byte)
- * r | otherwise => (xxxxxxx << 16) + (next byte << 8) + next byte
- * d |
- * linkaddress
+ * o | forward link address, 3byte
+ * r | 1 byte = bbbbbbbb match
+ * w | case 1xxxxxxx => -((xxxxxxx << 16) + (next byte << 8) + next byte)
+ * a | otherwise => (xxxxxxx << 16) + (next byte << 8) + next byte
+ * r |
+ * dlinkaddress
*/
/* Node (FusionDictionary.PtNode) layout is as follows:
- * | IF !SUPPORTS_DYNAMIC_UPDATE
- * | addressType xx : mask with MASK_CHILDREN_ADDRESS_TYPE
- * | 2 bits, 00 = no children : FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS
- * f | 01 = 1 byte : FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE
- * l | 10 = 2 bytes : FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES
- * a | 11 = 3 bytes : FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
- * g | ELSE
- * s | 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
- * | the new address is stored in the same place as the parent address
- * | is deleted? 10 = yes : FLAG_IS_DELETED
- * | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS
- * | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL
- * | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS
+ * | 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
+ * 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
* | has bigrams ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_BIGRAMS
* | 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 | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header)
- * r | parent address, 3byte
- * e | 1 byte = bbbbbbbb match
- * n | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
- * t | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte
- * a | This address is relative to the head of the PtNode.
- * d | If the node doesn't have a parent, this field is set to 0.
+ * 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 |
- * ress
+ * dress
*
* c | IF FLAG_HAS_MULTIPLE_CHARS
* h | char, char, char, char n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers
@@ -134,23 +120,16 @@ public final class FormatSpec {
* e | frequency 1 byte
* q |
*
- * c | IF SUPPORTS_DYNAMIC_UPDATE
- * 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 | ELSIF 00 = FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS == addressType
- * a | // nothing
- * d | ELSIF 01 = FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE == addressType
- * d | children address, 1 byte
- * r | ELSIF 10 = FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES == addressType
- * e | children address, 2 bytes
- * s | ELSE // 11 = FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = addressType
- * s | children address, 3 bytes
- * | END
- * | This address is relative to the position of this field.
+ * 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
*
* | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS
* | shortcut string list
@@ -199,21 +178,18 @@ public final class FormatSpec {
*/
public static final int MAGIC_NUMBER = 0x9BC13AFE;
- static final int MINIMUM_SUPPORTED_VERSION = 2;
- static final int MAXIMUM_SUPPORTED_VERSION = 4;
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;
- static final int VERSION3 = 3;
- static final int VERSION4 = 4;
- // These options need to be the same numeric values as the one in the native reading code.
- static final int GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
- // TODO: Make the native reading code read this variable.
- static final int SUPPORTS_DYNAMIC_UPDATE = 0x2;
- static final int FRENCH_LIGATURE_PROCESSING_FLAG = 0x4;
- static final int CONTAINS_BIGRAMS_FLAG = 0x8;
- static final int CONTAINS_TIMESTAMP_FLAG = 0x10;
+ // 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
+ // us to change the format during development while having testing devices remove
+ // older files with each upgrade, while still having a readable versioning scheme.
+ public static final int VERSION2 = 2;
+ public static final int VERSION4 = 401;
+ static final int MINIMUM_SUPPORTED_VERSION = VERSION2;
+ static final int MAXIMUM_SUPPORTED_VERSION = VERSION4;
// TODO: Make this value adaptative to content data, store it in the header, and
// use it in the reading code.
@@ -263,29 +239,31 @@ public final class FormatSpec {
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.
+ // 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";
- static final String UNIGRAM_TIMESTAMP_FILE_EXTENSION = ".timestamp";
// 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 584KB with the block size being 4.
- // This is 91% of that of full address table.
- static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 4;
- static final int BIGRAM_CONTENT_COUNT = 2;
+ // 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 int BIGRAM_TIMESTAMP_CONTENT_INDEX = 1;
static final String BIGRAM_FREQ_CONTENT_ID = "_freq";
- static final String BIGRAM_TIMESTAMP_CONTENT_ID = "_timestamp";
static final int BIGRAM_TIMESTAMP_SIZE = 4;
static final int BIGRAM_COUNTER_SIZE = 1;
static final int BIGRAM_LEVEL_SIZE = 1;
@@ -293,7 +271,7 @@ public final class FormatSpec {
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
- // 29KB with the block size being 64.
+ // 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";
@@ -331,107 +309,56 @@ public final class FormatSpec {
*/
public static final class FormatOptions {
public final int mVersion;
- public final boolean mSupportsDynamicUpdate;
- public final boolean mHasTerminalId;
public final boolean mHasTimestamp;
- @UsedForTesting
- public FormatOptions(final int version) {
- this(version, false);
- }
@UsedForTesting
- public FormatOptions(final int version, final boolean supportsDynamicUpdate) {
- this(version, supportsDynamicUpdate, false /* hasTimestamp */);
+ public FormatOptions(final int version) {
+ this(version, false /* hasTimestamp */);
}
- public FormatOptions(final int version, final boolean supportsDynamicUpdate,
- final boolean hasTimestamp) {
+ public FormatOptions(final int version, final boolean hasTimestamp) {
mVersion = version;
- if (version < FIRST_VERSION_WITH_DYNAMIC_UPDATE && supportsDynamicUpdate) {
- throw new RuntimeException("Dynamic updates are only supported with versions "
- + FIRST_VERSION_WITH_DYNAMIC_UPDATE + " and ulterior.");
- }
- mSupportsDynamicUpdate = supportsDynamicUpdate;
- mHasTerminalId = (version >= FIRST_VERSION_WITH_TERMINAL_ID);
mHasTimestamp = hasTimestamp;
}
}
/**
- * Class representing file header.
+ * Options global to the dictionary.
*/
- public static final class FileHeader {
- public final int mHeaderSize;
- public final DictionaryOptions mDictionaryOptions;
- public final FormatOptions mFormatOptions;
- // Note that these are corresponding definitions in native code in latinime::HeaderPolicy
- // and latinime::HeaderReadWriteUtils.
- public static final String SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE = "SUPPORTS_DYNAMIC_UPDATE";
- public static final String USES_FORGETTING_CURVE_ATTRIBUTE = "USES_FORGETTING_CURVE";
- public static final String ATTRIBUTE_VALUE_TRUE = "1";
-
- public static final String DICTIONARY_VERSION_ATTRIBUTE = "version";
- public static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale";
- public static final String DICTIONARY_ID_ATTRIBUTE = "dictionary";
- private static final String DICTIONARY_DESCRIPTION_ATTRIBUTE = "description";
- public FileHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
- final FormatOptions formatOptions) {
- mHeaderSize = headerSize;
- mDictionaryOptions = dictionaryOptions;
- mFormatOptions = formatOptions;
- }
-
- // Helper method to get the locale as a String
- public String getLocaleString() {
- return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_LOCALE_ATTRIBUTE);
- }
-
- // Helper method to get the version String
- public String getVersion() {
- return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_VERSION_ATTRIBUTE);
+ public static final class DictionaryOptions {
+ public final HashMap<String, String> mAttributes;
+ public DictionaryOptions(final HashMap<String, String> attributes) {
+ mAttributes = attributes;
}
-
- // Helper method to get the dictionary ID as a String
- public String getId() {
- return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_ID_ATTRIBUTE);
- }
-
- // Helper method to get the description
- public String getDescription() {
- // TODO: Right now each dictionary file comes with a description in its own language.
- // It will display as is no matter the device's locale. It should be internationalized.
- return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_DESCRIPTION_ATTRIBUTE);
+ @Override
+ public String toString() { // Convenience method
+ return toString(0, false);
}
- }
-
- /**
- * Returns new dictionary decoder.
- *
- * @param dictFile the dictionary file.
- * @param bufferType The type of buffer, as one of USE_* in DictDecoder.
- * @return new dictionary decoder if the dictionary file exists, otherwise null.
- */
- public static DictDecoder getDictDecoder(final File dictFile, final int bufferType) {
- if (dictFile.isDirectory()) {
- return new Ver4DictDecoder(dictFile, bufferType);
- } else if (dictFile.isFile()) {
- return new Ver3DictDecoder(dictFile, bufferType);
- }
- return null;
- }
-
- public static DictDecoder getDictDecoder(final File dictFile,
- final DictionaryBufferFactory factory) {
- if (dictFile.isDirectory()) {
- return new Ver4DictDecoder(dictFile, factory);
- } else if (dictFile.isFile()) {
- return new Ver3DictDecoder(dictFile, factory);
+ public String toString(final int indentCount, final boolean plumbing) {
+ final StringBuilder indent = new StringBuilder();
+ if (plumbing) {
+ indent.append("H:");
+ } else {
+ for (int i = 0; i < indentCount; ++i) {
+ indent.append(" ");
+ }
+ }
+ final StringBuilder s = new StringBuilder();
+ for (final String optionKey : mAttributes.keySet()) {
+ s.append(indent);
+ s.append(optionKey);
+ s.append(" = ");
+ if ("date".equals(optionKey) && !plumbing) {
+ // Date needs a number of milliseconds, but the dictionary contains seconds
+ s.append(new Date(
+ 1000 * Long.parseLong(mAttributes.get(optionKey))).toString());
+ } else {
+ s.append(mAttributes.get(optionKey));
+ }
+ s.append("\n");
+ }
+ return s.toString();
}
- return null;
- }
-
- public static DictDecoder getDictDecoder(final File dictFile) {
- return getDictDecoder(dictFile, DictDecoder.USE_READONLY_BYTEBUFFER);
}
private FormatSpec() {
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
deleted file mode 100644
index 3bb218bea..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ /dev/null
@@ -1,916 +0,0 @@
-/*
- * Copyright (C) 2011 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.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-
-/**
- * A dictionary that can fusion heads and tails of words for more compression.
- */
-@UsedForTesting
-public final class FusionDictionary implements Iterable<Word> {
- private static final boolean DBG = MakedictLog.DBG;
-
- private static int CHARACTER_NOT_FOUND_INDEX = -1;
-
- /**
- * A node array of the dictionary, containing several PtNodes.
- *
- * A PtNodeArray is but an ordered array of PtNodes, which essentially contain all the
- * real information.
- * This class also contains fields to cache size and address, to help with binary
- * generation.
- */
- public static final class PtNodeArray {
- ArrayList<PtNode> mData;
- // To help with binary generation
- int mCachedSize = Integer.MIN_VALUE;
- // mCachedAddressBefore/AfterUpdate are helpers for binary dictionary generation. They
- // always hold the same value except between dictionary address compression, during which
- // the update process needs to know about both values at the same time. Updating will
- // update the AfterUpdate value, and the code will move them to BeforeUpdate before
- // the next update pass.
- int mCachedAddressBeforeUpdate = Integer.MIN_VALUE;
- int mCachedAddressAfterUpdate = Integer.MIN_VALUE;
- int mCachedParentAddress = 0;
-
- public PtNodeArray() {
- mData = new ArrayList<PtNode>();
- }
- public PtNodeArray(ArrayList<PtNode> data) {
- mData = data;
- }
- }
-
- /**
- * A string with a frequency.
- *
- * This represents an "attribute", that is either a bigram or a shortcut.
- */
- public static final class WeightedString {
- public final String mWord;
- public int mFrequency;
- public WeightedString(String word, int frequency) {
- mWord = word;
- mFrequency = frequency;
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(new Object[] { mWord, mFrequency });
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) return true;
- if (!(o instanceof WeightedString)) return false;
- WeightedString w = (WeightedString)o;
- return mWord.equals(w.mWord) && mFrequency == w.mFrequency;
- }
- }
-
- /**
- * PtNode is a group of characters, with a frequency, shortcut targets, bigrams, and children
- * (Pt means Patricia Trie).
- *
- * This is the central class of the in-memory representation. A PtNode is what can
- * be seen as a traditional "trie node", except it can hold several characters at the
- * same time. A PtNode essentially represents one or several characters in the middle
- * of the trie tree; as such, it can be a terminal, and it can have children.
- * In this in-memory representation, whether the PtNode is a terminal or not is represented
- * in the frequency, where NOT_A_TERMINAL (= -1) means this is not a terminal and any other
- * value is the frequency of this terminal. A terminal may have non-null shortcuts and/or
- * bigrams, but a non-terminal may not. Moreover, children, if present, are null.
- */
- public static final class PtNode {
- public static final int NOT_A_TERMINAL = -1;
- final int mChars[];
- ArrayList<WeightedString> mShortcutTargets;
- ArrayList<WeightedString> mBigrams;
- int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal.
- int mTerminalId; // NOT_A_TERMINAL == mTerminalId indicates this is not a terminal.
- PtNodeArray mChildren;
- boolean mIsNotAWord; // Only a shortcut
- boolean mIsBlacklistEntry;
- // mCachedSize and mCachedAddressBefore/AfterUpdate are helpers for binary dictionary
- // generation. Before and After always hold the same value except during dictionary
- // address compression, where the update process needs to know about both values at the
- // same time. Updating will update the AfterUpdate value, and the code will move them
- // to BeforeUpdate before the next update pass.
- // The update process does not need two versions of mCachedSize.
- int mCachedSize; // The size, in bytes, of this PtNode.
- int mCachedAddressBeforeUpdate; // The address of this PtNode (before update)
- int mCachedAddressAfterUpdate; // The address of this PtNode (after update)
-
- public PtNode(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams, final int frequency,
- final boolean isNotAWord, final boolean isBlacklistEntry) {
- mChars = chars;
- mFrequency = frequency;
- mTerminalId = frequency;
- mShortcutTargets = shortcutTargets;
- mBigrams = bigrams;
- mChildren = null;
- mIsNotAWord = isNotAWord;
- mIsBlacklistEntry = isBlacklistEntry;
- }
-
- public PtNode(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams, final int frequency,
- final boolean isNotAWord, final boolean isBlacklistEntry,
- final PtNodeArray children) {
- mChars = chars;
- mFrequency = frequency;
- mShortcutTargets = shortcutTargets;
- mBigrams = bigrams;
- mChildren = children;
- mIsNotAWord = isNotAWord;
- mIsBlacklistEntry = isBlacklistEntry;
- }
-
- public void addChild(PtNode n) {
- if (null == mChildren) {
- mChildren = new PtNodeArray();
- }
- mChildren.mData.add(n);
- }
-
- public int getTerminalId() {
- return mTerminalId;
- }
-
- public boolean isTerminal() {
- return NOT_A_TERMINAL != mFrequency;
- }
-
- public int getFrequency() {
- return mFrequency;
- }
-
- public boolean getIsNotAWord() {
- return mIsNotAWord;
- }
-
- public boolean getIsBlacklistEntry() {
- return mIsBlacklistEntry;
- }
-
- public ArrayList<WeightedString> getShortcutTargets() {
- // We don't want write permission to escape outside the package, so we return a copy
- if (null == mShortcutTargets) return null;
- final ArrayList<WeightedString> copyOfShortcutTargets =
- new ArrayList<WeightedString>(mShortcutTargets);
- return copyOfShortcutTargets;
- }
-
- public ArrayList<WeightedString> getBigrams() {
- // We don't want write permission to escape outside the package, so we return a copy
- if (null == mBigrams) return null;
- final ArrayList<WeightedString> copyOfBigrams = new ArrayList<WeightedString>(mBigrams);
- return copyOfBigrams;
- }
-
- public boolean hasSeveralChars() {
- assert(mChars.length > 0);
- return 1 < mChars.length;
- }
-
- /**
- * Adds a word to the bigram list. Updates the frequency if the word already
- * exists.
- */
- public void addBigram(final String word, final int frequency) {
- if (mBigrams == null) {
- mBigrams = new ArrayList<WeightedString>();
- }
- WeightedString bigram = getBigram(word);
- if (bigram != null) {
- bigram.mFrequency = frequency;
- } else {
- bigram = new WeightedString(word, frequency);
- mBigrams.add(bigram);
- }
- }
-
- /**
- * Gets the shortcut target for the given word. Returns null if the word is not in the
- * shortcut list.
- */
- public WeightedString getShortcut(final String word) {
- // TODO: Don't do a linear search
- if (mShortcutTargets != null) {
- final int size = mShortcutTargets.size();
- for (int i = 0; i < size; ++i) {
- WeightedString shortcut = mShortcutTargets.get(i);
- if (shortcut.mWord.equals(word)) {
- return shortcut;
- }
- }
- }
- return null;
- }
-
- /**
- * Gets the bigram for the given word.
- * Returns null if the word is not in the bigrams list.
- */
- public WeightedString getBigram(final String word) {
- // TODO: Don't do a linear search
- if (mBigrams != null) {
- final int size = mBigrams.size();
- for (int i = 0; i < size; ++i) {
- WeightedString bigram = mBigrams.get(i);
- if (bigram.mWord.equals(word)) {
- return bigram;
- }
- }
- }
- return null;
- }
-
- /**
- * Updates the PtNode with the given properties. Adds the shortcut and bigram lists to
- * the existing ones if any. Note: unigram, bigram, and shortcut frequencies are only
- * updated if they are higher than the existing ones.
- */
- public void update(final int frequency, final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams,
- final boolean isNotAWord, final boolean isBlacklistEntry) {
- if (frequency > mFrequency) {
- mFrequency = frequency;
- }
- if (shortcutTargets != null) {
- if (mShortcutTargets == null) {
- mShortcutTargets = shortcutTargets;
- } else {
- final int size = shortcutTargets.size();
- for (int i = 0; i < size; ++i) {
- final WeightedString shortcut = shortcutTargets.get(i);
- final WeightedString existingShortcut = getShortcut(shortcut.mWord);
- if (existingShortcut == null) {
- mShortcutTargets.add(shortcut);
- } else if (existingShortcut.mFrequency < shortcut.mFrequency) {
- existingShortcut.mFrequency = shortcut.mFrequency;
- }
- }
- }
- }
- if (bigrams != null) {
- if (mBigrams == null) {
- mBigrams = bigrams;
- } else {
- final int size = bigrams.size();
- for (int i = 0; i < size; ++i) {
- final WeightedString bigram = bigrams.get(i);
- final WeightedString existingBigram = getBigram(bigram.mWord);
- if (existingBigram == null) {
- mBigrams.add(bigram);
- } else if (existingBigram.mFrequency < bigram.mFrequency) {
- existingBigram.mFrequency = bigram.mFrequency;
- }
- }
- }
- }
- mIsNotAWord = isNotAWord;
- mIsBlacklistEntry = isBlacklistEntry;
- }
- }
-
- /**
- * Options global to the dictionary.
- */
- public static final class DictionaryOptions {
- public final boolean mGermanUmlautProcessing;
- public final boolean mFrenchLigatureProcessing;
- public final HashMap<String, String> mAttributes;
- public DictionaryOptions(final HashMap<String, String> attributes,
- final boolean germanUmlautProcessing, final boolean frenchLigatureProcessing) {
- mAttributes = attributes;
- mGermanUmlautProcessing = germanUmlautProcessing;
- mFrenchLigatureProcessing = frenchLigatureProcessing;
- }
- @Override
- public String toString() { // Convenience method
- return toString(0, false);
- }
- public String toString(final int indentCount, final boolean plumbing) {
- final StringBuilder indent = new StringBuilder();
- if (plumbing) {
- indent.append("H:");
- } else {
- for (int i = 0; i < indentCount; ++i) {
- indent.append(" ");
- }
- }
- final StringBuilder s = new StringBuilder();
- for (final String optionKey : mAttributes.keySet()) {
- s.append(indent);
- s.append(optionKey);
- s.append(" = ");
- if ("date".equals(optionKey) && !plumbing) {
- // Date needs a number of milliseconds, but the dictionary contains seconds
- s.append(new Date(
- 1000 * Long.parseLong(mAttributes.get(optionKey))).toString());
- } else {
- s.append(mAttributes.get(optionKey));
- }
- s.append("\n");
- }
- if (mGermanUmlautProcessing) {
- s.append(indent);
- s.append("Needs German umlaut processing\n");
- }
- if (mFrenchLigatureProcessing) {
- s.append(indent);
- s.append("Needs French ligature processing\n");
- }
- return s.toString();
- }
- }
-
- public final DictionaryOptions mOptions;
- public final PtNodeArray mRootNodeArray;
-
- public FusionDictionary(final PtNodeArray rootNodeArray, final DictionaryOptions options) {
- mRootNodeArray = rootNodeArray;
- mOptions = options;
- }
-
- public void addOptionAttribute(final String key, final String value) {
- mOptions.mAttributes.put(key, value);
- }
-
- /**
- * Helper method to convert a String to an int array.
- */
- static int[] getCodePoints(final String word) {
- // TODO: this is a copy-paste of the old contents of StringUtils.toCodePointArray,
- // which is not visible from the makedict package. Factor this code.
- final int length = word.length();
- if (length <= 0) return new int[] {};
- final char[] characters = word.toCharArray();
- final int[] codePoints = new int[Character.codePointCount(characters, 0, length)];
- int codePoint = Character.codePointAt(characters, 0);
- int dsti = 0;
- for (int srci = Character.charCount(codePoint);
- srci < length; srci += Character.charCount(codePoint), ++dsti) {
- codePoints[dsti] = codePoint;
- codePoint = Character.codePointAt(characters, srci);
- }
- codePoints[dsti] = codePoint;
- return codePoints;
- }
-
- /**
- * Helper method to add a word as a string.
- *
- * This method adds a word to the dictionary with the given frequency. Optional
- * lists of bigrams and shortcuts can be passed here. For each word inside,
- * they will be added to the dictionary as necessary.
- *
- * @param word the word to add.
- * @param frequency the frequency of the word, in the range [0..255].
- * @param shortcutTargets a list of shortcut targets for this word, or null.
- * @param isNotAWord true if this should not be considered a word (e.g. shortcut only)
- */
- public void add(final String word, final int frequency,
- final ArrayList<WeightedString> shortcutTargets, final boolean isNotAWord) {
- add(getCodePoints(word), frequency, shortcutTargets, isNotAWord,
- false /* isBlacklistEntry */);
- }
-
- /**
- * Helper method to add a blacklist entry as a string.
- *
- * @param word the word to add as a blacklist entry.
- * @param shortcutTargets a list of shortcut targets for this word, or null.
- * @param isNotAWord true if this is not a word for spellcheking purposes (shortcut only or so)
- */
- public void addBlacklistEntry(final String word,
- final ArrayList<WeightedString> shortcutTargets, final boolean isNotAWord) {
- add(getCodePoints(word), 0, shortcutTargets, isNotAWord, true /* isBlacklistEntry */);
- }
-
- /**
- * Sanity check for a PtNode array.
- *
- * This method checks that all PtNodes in a node array are ordered as expected.
- * If they are, nothing happens. If they aren't, an exception is thrown.
- */
- private void checkStack(PtNodeArray ptNodeArray) {
- ArrayList<PtNode> stack = ptNodeArray.mData;
- int lastValue = -1;
- for (int i = 0; i < stack.size(); ++i) {
- int currentValue = stack.get(i).mChars[0];
- if (currentValue <= lastValue)
- throw new RuntimeException("Invalid stack");
- else
- lastValue = currentValue;
- }
- }
-
- /**
- * Helper method to add a new bigram to the dictionary.
- *
- * @param word1 the previous word of the context
- * @param word2 the next word of the context
- * @param frequency the bigram frequency
- */
- public void setBigram(final String word1, final String word2, final int frequency) {
- PtNode ptNode = findWordInTree(mRootNodeArray, word1);
- if (ptNode != null) {
- final PtNode ptNode2 = findWordInTree(mRootNodeArray, word2);
- if (ptNode2 == null) {
- add(getCodePoints(word2), 0, null, false /* isNotAWord */,
- false /* isBlacklistEntry */);
- // The PtNode for the first word may have moved by the above insertion,
- // if word1 and word2 share a common stem that happens not to have been
- // a cutting point until now. In this case, we need to refresh ptNode.
- ptNode = findWordInTree(mRootNodeArray, word1);
- }
- ptNode.addBigram(word2, frequency);
- } else {
- throw new RuntimeException("First word of bigram not found");
- }
- }
-
- /**
- * Add a word to this dictionary.
- *
- * The shortcuts, if any, have to be in the dictionary already. If they aren't,
- * an exception is thrown.
- *
- * @param word the word, as an int array.
- * @param frequency the frequency of the word, in the range [0..255].
- * @param shortcutTargets an optional list of shortcut targets for this word (null if none).
- * @param isNotAWord true if this is not a word for spellcheking purposes (shortcut only or so)
- * @param isBlacklistEntry true if this is a blacklisted word, false otherwise
- */
- private void add(final int[] word, final int frequency,
- final ArrayList<WeightedString> shortcutTargets,
- final boolean isNotAWord, final boolean isBlacklistEntry) {
- assert(frequency >= 0 && frequency <= 255);
- if (word.length >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
- MakedictLog.w("Ignoring a word that is too long: word.length = " + word.length);
- return;
- }
-
- PtNodeArray currentNodeArray = mRootNodeArray;
- int charIndex = 0;
-
- PtNode currentPtNode = null;
- int differentCharIndex = 0; // Set by the loop to the index of the char that differs
- int nodeIndex = findIndexOfChar(mRootNodeArray, word[charIndex]);
- while (CHARACTER_NOT_FOUND_INDEX != nodeIndex) {
- currentPtNode = currentNodeArray.mData.get(nodeIndex);
- differentCharIndex = compareCharArrays(currentPtNode.mChars, word, charIndex);
- if (ARRAYS_ARE_EQUAL != differentCharIndex
- && differentCharIndex < currentPtNode.mChars.length) break;
- if (null == currentPtNode.mChildren) break;
- charIndex += currentPtNode.mChars.length;
- if (charIndex >= word.length) break;
- currentNodeArray = currentPtNode.mChildren;
- nodeIndex = findIndexOfChar(currentNodeArray, word[charIndex]);
- }
-
- if (CHARACTER_NOT_FOUND_INDEX == nodeIndex) {
- // No node at this point to accept the word. Create one.
- final int insertionIndex = findInsertionIndex(currentNodeArray, word[charIndex]);
- final PtNode newPtNode = new PtNode(Arrays.copyOfRange(word, charIndex, word.length),
- shortcutTargets, null /* bigrams */, frequency, isNotAWord, isBlacklistEntry);
- currentNodeArray.mData.add(insertionIndex, newPtNode);
- if (DBG) checkStack(currentNodeArray);
- } else {
- // There is a word with a common prefix.
- if (differentCharIndex == currentPtNode.mChars.length) {
- if (charIndex + differentCharIndex >= word.length) {
- // The new word is a prefix of an existing word, but the node on which it
- // should end already exists as is. Since the old PtNode was not a terminal,
- // make it one by filling in its frequency and other attributes
- currentPtNode.update(frequency, shortcutTargets, null, isNotAWord,
- isBlacklistEntry);
- } else {
- // The new word matches the full old word and extends past it.
- // We only have to create a new node and add it to the end of this.
- final PtNode newNode = new PtNode(
- Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length),
- shortcutTargets, null /* bigrams */, frequency, isNotAWord,
- isBlacklistEntry);
- currentPtNode.mChildren = new PtNodeArray();
- currentPtNode.mChildren.mData.add(newNode);
- }
- } else {
- if (0 == differentCharIndex) {
- // Exact same word. Update the frequency if higher. This will also add the
- // new shortcuts to the existing shortcut list if it already exists.
- currentPtNode.update(frequency, shortcutTargets, null,
- currentPtNode.mIsNotAWord && isNotAWord,
- currentPtNode.mIsBlacklistEntry || isBlacklistEntry);
- } else {
- // Partial prefix match only. We have to replace the current node with a node
- // containing the current prefix and create two new ones for the tails.
- PtNodeArray newChildren = new PtNodeArray();
- final PtNode newOldWord = new PtNode(
- Arrays.copyOfRange(currentPtNode.mChars, differentCharIndex,
- currentPtNode.mChars.length), currentPtNode.mShortcutTargets,
- currentPtNode.mBigrams, currentPtNode.mFrequency,
- currentPtNode.mIsNotAWord, currentPtNode.mIsBlacklistEntry,
- currentPtNode.mChildren);
- newChildren.mData.add(newOldWord);
-
- final PtNode newParent;
- if (charIndex + differentCharIndex >= word.length) {
- newParent = new PtNode(
- Arrays.copyOfRange(currentPtNode.mChars, 0, differentCharIndex),
- shortcutTargets, null /* bigrams */, frequency,
- isNotAWord, isBlacklistEntry, newChildren);
- } else {
- newParent = new PtNode(
- Arrays.copyOfRange(currentPtNode.mChars, 0, differentCharIndex),
- null /* shortcutTargets */, null /* bigrams */, -1,
- false /* isNotAWord */, false /* isBlacklistEntry */, newChildren);
- final PtNode newWord = new PtNode(Arrays.copyOfRange(word,
- charIndex + differentCharIndex, word.length),
- shortcutTargets, null /* bigrams */, frequency,
- isNotAWord, isBlacklistEntry);
- final int addIndex = word[charIndex + differentCharIndex]
- > currentPtNode.mChars[differentCharIndex] ? 1 : 0;
- newChildren.mData.add(addIndex, newWord);
- }
- currentNodeArray.mData.set(nodeIndex, newParent);
- }
- if (DBG) checkStack(currentNodeArray);
- }
- }
- }
-
- private static int ARRAYS_ARE_EQUAL = 0;
-
- /**
- * Custom comparison of two int arrays taken to contain character codes.
- *
- * This method compares the two arrays passed as an argument in a lexicographic way,
- * with an offset in the dst string.
- * This method does NOT test for the first character. It is taken to be equal.
- * I repeat: this method starts the comparison at 1 <> dstOffset + 1.
- * The index where the strings differ is returned. ARRAYS_ARE_EQUAL = 0 is returned if the
- * strings are equal. This works BECAUSE we don't look at the first character.
- *
- * @param src the left-hand side string of the comparison.
- * @param dst the right-hand side string of the comparison.
- * @param dstOffset the offset in the right-hand side string.
- * @return the index at which the strings differ, or ARRAYS_ARE_EQUAL = 0 if they don't.
- */
- private static int compareCharArrays(final int[] src, final int[] dst, int dstOffset) {
- // We do NOT test the first char, because we come from a method that already
- // tested it.
- for (int i = 1; i < src.length; ++i) {
- if (dstOffset + i >= dst.length) return i;
- if (src[i] != dst[dstOffset + i]) return i;
- }
- if (dst.length > src.length) return src.length;
- return ARRAYS_ARE_EQUAL;
- }
-
- /**
- * Helper class that compares and sorts two PtNodes according to their
- * first element only. I repeat: ONLY the first element is considered, the rest
- * is ignored.
- * This comparator imposes orderings that are inconsistent with equals.
- */
- static private final class PtNodeComparator implements java.util.Comparator<PtNode> {
- @Override
- public int compare(PtNode p1, PtNode p2) {
- if (p1.mChars[0] == p2.mChars[0]) return 0;
- return p1.mChars[0] < p2.mChars[0] ? -1 : 1;
- }
- }
- final static private PtNodeComparator PTNODE_COMPARATOR = new PtNodeComparator();
-
- /**
- * Finds the insertion index of a character within a node array.
- */
- private static int findInsertionIndex(final PtNodeArray nodeArray, int character) {
- final ArrayList<PtNode> data = nodeArray.mData;
- final PtNode reference = new PtNode(new int[] { character },
- null /* shortcutTargets */, null /* bigrams */, 0, false /* isNotAWord */,
- false /* isBlacklistEntry */);
- int result = Collections.binarySearch(data, reference, PTNODE_COMPARATOR);
- return result >= 0 ? result : -result - 1;
- }
-
- /**
- * Find the index of a char in a node array, if it exists.
- *
- * @param nodeArray the node array to search in.
- * @param character the character to search for.
- * @return the position of the character if it's there, or CHARACTER_NOT_FOUND_INDEX = -1 else.
- */
- private static int findIndexOfChar(final PtNodeArray nodeArray, int character) {
- final int insertionIndex = findInsertionIndex(nodeArray, character);
- if (nodeArray.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND_INDEX;
- return character == nodeArray.mData.get(insertionIndex).mChars[0] ? insertionIndex
- : CHARACTER_NOT_FOUND_INDEX;
- }
-
- /**
- * Helper method to find a word in a given branch.
- */
- @SuppressWarnings("unused")
- public static PtNode findWordInTree(PtNodeArray nodeArray, final String string) {
- int index = 0;
- final StringBuilder checker = DBG ? new StringBuilder() : null;
- final int[] codePoints = getCodePoints(string);
-
- PtNode currentPtNode;
- do {
- int indexOfGroup = findIndexOfChar(nodeArray, codePoints[index]);
- if (CHARACTER_NOT_FOUND_INDEX == indexOfGroup) return null;
- currentPtNode = nodeArray.mData.get(indexOfGroup);
-
- if (codePoints.length - index < currentPtNode.mChars.length) return null;
- int newIndex = index;
- while (newIndex < codePoints.length && newIndex - index < currentPtNode.mChars.length) {
- if (currentPtNode.mChars[newIndex - index] != codePoints[newIndex]) return null;
- newIndex++;
- }
- index = newIndex;
-
- if (DBG) {
- checker.append(new String(currentPtNode.mChars, 0, currentPtNode.mChars.length));
- }
- if (index < codePoints.length) {
- nodeArray = currentPtNode.mChildren;
- }
- } while (null != nodeArray && index < codePoints.length);
-
- if (index < codePoints.length) return null;
- if (!currentPtNode.isTerminal()) return null;
- if (DBG && !string.equals(checker.toString())) return null;
- return currentPtNode;
- }
-
- /**
- * Helper method to find out whether a word is in the dict or not.
- */
- public boolean hasWord(final String s) {
- if (null == s || "".equals(s)) {
- throw new RuntimeException("Can't search for a null or empty string");
- }
- return null != findWordInTree(mRootNodeArray, s);
- }
-
- /**
- * Recursively count the number of PtNodes in a given branch of the trie.
- *
- * @param nodeArray the parent node.
- * @return the number of PtNodes in all the branch under this node.
- */
- public static int countPtNodes(final PtNodeArray nodeArray) {
- final int nodeSize = nodeArray.mData.size();
- int size = nodeSize;
- for (int i = nodeSize - 1; i >= 0; --i) {
- PtNode ptNode = nodeArray.mData.get(i);
- if (null != ptNode.mChildren)
- size += countPtNodes(ptNode.mChildren);
- }
- return size;
- }
-
- /**
- * Recursively count the number of nodes in a given branch of the trie.
- *
- * @param nodeArray the node array to count.
- * @return the number of nodes in this branch.
- */
- public static int countNodeArrays(final PtNodeArray nodeArray) {
- int size = 1;
- for (int i = nodeArray.mData.size() - 1; i >= 0; --i) {
- PtNode ptNode = nodeArray.mData.get(i);
- if (null != ptNode.mChildren)
- size += countNodeArrays(ptNode.mChildren);
- }
- return size;
- }
-
- // Recursively find out whether there are any bigrams.
- // This can be pretty expensive especially if there aren't any (we return as soon
- // as we find one, so it's much cheaper if there are bigrams)
- private static boolean hasBigramsInternal(final PtNodeArray nodeArray) {
- if (null == nodeArray) return false;
- for (int i = nodeArray.mData.size() - 1; i >= 0; --i) {
- PtNode ptNode = nodeArray.mData.get(i);
- if (null != ptNode.mBigrams) return true;
- if (hasBigramsInternal(ptNode.mChildren)) return true;
- }
- return false;
- }
-
- /**
- * Finds out whether there are any bigrams in this dictionary.
- *
- * @return true if there is any bigram, false otherwise.
- */
- // TODO: this is expensive especially for large dictionaries without any bigram.
- // The up side is, this is always accurate and correct and uses no memory. We should
- // find a more efficient way of doing this, without compromising too much on memory
- // and ease of use.
- public boolean hasBigrams() {
- return hasBigramsInternal(mRootNodeArray);
- }
-
- // Historically, the tails of the words were going to be merged to save space.
- // However, that would prevent the code to search for a specific address in log(n)
- // time so this was abandoned.
- // The code is still of interest as it does add some compression to any dictionary
- // that has no need for attributes. Implementations that does not read attributes should be
- // able to read a dictionary with merged tails.
- // Also, the following code does support frequencies, as in, it will only merges
- // tails that share the same frequency. Though it would result in the above loss of
- // performance while searching by address, it is still technically possible to merge
- // tails that contain attributes, but this code does not take that into account - it does
- // not compare attributes and will merge terminals with different attributes regardless.
- public void mergeTails() {
- MakedictLog.i("Do not merge tails");
- return;
-
-// MakedictLog.i("Merging PtNodes. Number of PtNodes : " + countPtNodes(root));
-// MakedictLog.i("Number of PtNodes : " + countPtNodes(root));
-//
-// final HashMap<String, ArrayList<PtNodeArray>> repository =
-// new HashMap<String, ArrayList<PtNodeArray>>();
-// mergeTailsInner(repository, root);
-//
-// MakedictLog.i("Number of different pseudohashes : " + repository.size());
-// int size = 0;
-// for (ArrayList<PtNodeArray> a : repository.values()) {
-// size += a.size();
-// }
-// MakedictLog.i("Number of nodes after merge : " + (1 + size));
-// MakedictLog.i("Recursively seen nodes : " + countNodes(root));
- }
-
- // The following methods are used by the deactivated mergeTails()
-// private static boolean isEqual(PtNodeArray a, PtNodeArray b) {
-// if (null == a && null == b) return true;
-// if (null == a || null == b) return false;
-// if (a.data.size() != b.data.size()) return false;
-// final int size = a.data.size();
-// for (int i = size - 1; i >= 0; --i) {
-// PtNode aPtNode = a.data.get(i);
-// PtNode bPtNode = b.data.get(i);
-// if (aPtNode.frequency != bPtNode.frequency) return false;
-// if (aPtNode.alternates == null && bPtNode.alternates != null) return false;
-// if (aPtNode.alternates != null && !aPtNode.equals(bPtNode.alternates)) return false;
-// if (!Arrays.equals(aPtNode.chars, bPtNode.chars)) return false;
-// if (!isEqual(aPtNode.children, bPtNode.children)) return false;
-// }
-// return true;
-// }
-
-// static private HashMap<String, ArrayList<PtNodeArray>> mergeTailsInner(
-// final HashMap<String, ArrayList<PtNodeArray>> map, final PtNodeArray nodeArray) {
-// final ArrayList<PtNode> branches = nodeArray.data;
-// final int nodeSize = branches.size();
-// for (int i = 0; i < nodeSize; ++i) {
-// PtNode ptNode = branches.get(i);
-// if (null != ptNode.children) {
-// String pseudoHash = getPseudoHash(ptNode.children);
-// ArrayList<PtNodeArray> similarList = map.get(pseudoHash);
-// if (null == similarList) {
-// similarList = new ArrayList<PtNodeArray>();
-// map.put(pseudoHash, similarList);
-// }
-// boolean merged = false;
-// for (PtNodeArray similar : similarList) {
-// if (isEqual(ptNode.children, similar)) {
-// ptNode.children = similar;
-// merged = true;
-// break;
-// }
-// }
-// if (!merged) {
-// similarList.add(ptNode.children);
-// }
-// mergeTailsInner(map, ptNode.children);
-// }
-// }
-// return map;
-// }
-
-// private static String getPseudoHash(final PtNodeArray nodeArray) {
-// StringBuilder s = new StringBuilder();
-// for (PtNode ptNode : nodeArray.data) {
-// s.append(ptNode.frequency);
-// for (int ch : ptNode.chars) {
-// s.append(Character.toChars(ch));
-// }
-// }
-// return s.toString();
-// }
-
- /**
- * Iterator to walk through a dictionary.
- *
- * This is purely for convenience.
- */
- public static final class DictionaryIterator implements Iterator<Word> {
- private static final class Position {
- public Iterator<PtNode> pos;
- public int length;
- public Position(ArrayList<PtNode> ptNodes) {
- pos = ptNodes.iterator();
- length = 0;
- }
- }
- final StringBuilder mCurrentString;
- final LinkedList<Position> mPositions;
-
- public DictionaryIterator(ArrayList<PtNode> ptRoot) {
- mCurrentString = new StringBuilder();
- mPositions = new LinkedList<Position>();
- final Position rootPos = new Position(ptRoot);
- mPositions.add(rootPos);
- }
-
- @Override
- public boolean hasNext() {
- for (Position p : mPositions) {
- if (p.pos.hasNext()) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public Word next() {
- Position currentPos = mPositions.getLast();
- mCurrentString.setLength(currentPos.length);
-
- do {
- if (currentPos.pos.hasNext()) {
- final PtNode currentPtNode = currentPos.pos.next();
- currentPos.length = mCurrentString.length();
- for (int i : currentPtNode.mChars) {
- mCurrentString.append(Character.toChars(i));
- }
- if (null != currentPtNode.mChildren) {
- currentPos = new Position(currentPtNode.mChildren.mData);
- currentPos.length = mCurrentString.length();
- mPositions.addLast(currentPos);
- }
- if (currentPtNode.mFrequency >= 0) {
- return new Word(mCurrentString.toString(), currentPtNode.mFrequency,
- currentPtNode.mShortcutTargets, currentPtNode.mBigrams,
- currentPtNode.mIsNotAWord, currentPtNode.mIsBlacklistEntry);
- }
- } else {
- mPositions.removeLast();
- currentPos = mPositions.getLast();
- mCurrentString.setLength(mPositions.getLast().length);
- }
- } while (true);
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("Unsupported yet");
- }
-
- }
-
- /**
- * Method to return an iterator.
- *
- * This method enables Java's enhanced for loop. With this you can have a FusionDictionary x
- * and say : for (Word w : x) {}
- */
- @Override
- public Iterator<Word> iterator() {
- return new DictionaryIterator(mRootNodeArray.mData);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
deleted file mode 100644
index cf07209d9..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2011 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.makedict;
-
-import android.util.Log;
-
-/**
- * Wrapper to redirect log events to the right output medium.
- */
-public final class MakedictLog {
- public static final boolean DBG = false;
- private static final String TAG = MakedictLog.class.getSimpleName();
-
- public static void d(String message) {
- if (DBG) {
- Log.d(TAG, message);
- }
- }
-
- public static void i(String message) {
- if (DBG) {
- Log.i(TAG, message);
- }
- }
-
- public static void w(String message) {
- Log.w(TAG, message);
- }
-
- public static void e(String message) {
- Log.e(TAG, message);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java b/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
deleted file mode 100644
index 70e24cc98..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2011 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.makedict;
-
-/**
- * A not-yet-resolved attribute.
- *
- * An attribute is either a bigram or a shortcut.
- * All instances of this class are always immutable.
- */
-public final class PendingAttribute {
- public final int mFrequency;
- public final int mAddress;
- public PendingAttribute(final int frequency, final int address) {
- mFrequency = frequency;
- mAddress = address;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java b/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java
new file mode 100644
index 000000000..5fcbb6357
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java
@@ -0,0 +1,91 @@
+/*
+ * 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.makedict;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.utils.CombinedFormatUtils;
+
+import java.util.Arrays;
+
+public final class ProbabilityInfo {
+ public final int mProbability;
+ // mTimestamp, mLevel and mCount are historical info. These values are depend on the
+ // implementation in native code; thus, we must not use them and have any assumptions about
+ // them except for tests.
+ public final int mTimestamp;
+ public final int mLevel;
+ public final int mCount;
+
+ @UsedForTesting
+ public static ProbabilityInfo max(final ProbabilityInfo probabilityInfo1,
+ final ProbabilityInfo probabilityInfo2) {
+ if (probabilityInfo1 == null) {
+ return probabilityInfo2;
+ }
+ if (probabilityInfo2 == null) {
+ return probabilityInfo1;
+ }
+ if (probabilityInfo1.mProbability > probabilityInfo2.mProbability) {
+ return probabilityInfo1;
+ } else {
+ return probabilityInfo2;
+ }
+ }
+
+ public ProbabilityInfo(final int probability) {
+ this(probability, BinaryDictionary.NOT_A_VALID_TIMESTAMP, 0, 0);
+ }
+
+ public ProbabilityInfo(final int probability, final int timestamp, final int level,
+ final int count) {
+ mProbability = probability;
+ mTimestamp = timestamp;
+ mLevel = level;
+ mCount = count;
+ }
+
+ public boolean hasHistoricalInfo() {
+ return mTimestamp != BinaryDictionary.NOT_A_VALID_TIMESTAMP;
+ }
+
+ @Override
+ public int hashCode() {
+ if (hasHistoricalInfo()) {
+ return Arrays.hashCode(new Object[] { mProbability, mTimestamp, mLevel, mCount });
+ } else {
+ return Arrays.hashCode(new Object[] { mProbability });
+ }
+ }
+
+ @Override
+ public String toString() {
+ return CombinedFormatUtils.formatProbabilityInfo(this);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof ProbabilityInfo)) return false;
+ final ProbabilityInfo p = (ProbabilityInfo)o;
+ if (!hasHistoricalInfo() && !p.hasHistoricalInfo()) {
+ return mProbability == p.mProbability;
+ }
+ return mProbability == p.mProbability && mTimestamp == p.mTimestamp && mLevel == p.mLevel
+ && mCount == p.mCount;
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java b/java/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java
deleted file mode 100644
index 188de7a0f..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2011 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.makedict;
-
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.util.ArrayList;
-
-/**
- * Raw PtNode info straight out of a file. This will contain numbers for addresses.
- */
-public final class PtNodeInfo {
-
- public final int mOriginalAddress;
- public final int mEndAddress;
- public final int mFlags;
- public final int[] mCharacters;
- public final int mFrequency;
- public final int mChildrenAddress;
- public final int mParentAddress;
- public final ArrayList<WeightedString> mShortcutTargets;
- public final ArrayList<PendingAttribute> mBigrams;
-
- public PtNodeInfo(final int originalAddress, final int endAddress, final int flags,
- final int[] characters, final int frequency, final int parentAddress,
- final int childrenAddress, final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<PendingAttribute> bigrams) {
- mOriginalAddress = originalAddress;
- mEndAddress = endAddress;
- mFlags = flags;
- mCharacters = characters;
- mFrequency = frequency;
- mParentAddress = parentAddress;
- mChildrenAddress = childrenAddress;
- mShortcutTargets = shortcutTargets;
- mBigrams = bigrams;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTable.java b/java/src/com/android/inputmethod/latin/makedict/SparseTable.java
deleted file mode 100644
index 7592a0c13..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/SparseTable.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-
-/**
- * SparseTable is an extensible map from integer to integer.
- * This holds one value for every mBlockSize keys, so it uses 1/mBlockSize'th of the full index
- * memory.
- */
-@UsedForTesting
-public class SparseTable {
-
- /**
- * mLookupTable is indexed by terminal ID, containing exactly one entry for every mBlockSize
- * terminals.
- * It contains at index i = j / mBlockSize the index in each ArrayList in mContentsTables where
- * the values for terminals with IDs j to j + mBlockSize - 1 are stored as an mBlockSize-sized
- * integer array.
- */
- private final ArrayList<Integer> mLookupTable;
- private final ArrayList<ArrayList<Integer>> mContentTables;
-
- private final int mBlockSize;
- private final int mContentTableCount;
- public static final int NOT_EXIST = -1;
- public static final int SIZE_OF_INT_IN_BYTES = 4;
-
- @UsedForTesting
- public SparseTable(final int initialCapacity, final int blockSize,
- final int contentTableCount) {
- mBlockSize = blockSize;
- final int lookupTableSize = initialCapacity / mBlockSize
- + (initialCapacity % mBlockSize > 0 ? 1 : 0);
- mLookupTable = new ArrayList<Integer>(Collections.nCopies(lookupTableSize, NOT_EXIST));
- mContentTableCount = contentTableCount;
- mContentTables = CollectionUtils.newArrayList();
- for (int i = 0; i < mContentTableCount; ++i) {
- mContentTables.add(new ArrayList<Integer>());
- }
- }
-
- @UsedForTesting
- public SparseTable(final ArrayList<Integer> lookupTable,
- final ArrayList<ArrayList<Integer>> contentTables, final int blockSize) {
- mBlockSize = blockSize;
- mContentTableCount = contentTables.size();
- mLookupTable = lookupTable;
- mContentTables = contentTables;
- }
-
- /**
- * Converts an byte array to an int array considering each set of 4 bytes is an int stored in
- * big-endian.
- * The length of byteArray must be a multiple of four.
- * Otherwise, IndexOutOfBoundsException will be raised.
- */
- @UsedForTesting
- private static ArrayList<Integer> convertByteArrayToIntegerArray(final byte[] byteArray) {
- final ArrayList<Integer> integerArray = new ArrayList<Integer>(byteArray.length / 4);
- for (int i = 0; i < byteArray.length; i += 4) {
- int value = 0;
- for (int j = i; j < i + 4; ++j) {
- value <<= 8;
- value |= byteArray[j] & 0xFF;
- }
- integerArray.add(value);
- }
- return integerArray;
- }
-
- @UsedForTesting
- public int get(final int contentTableIndex, final int index) {
- if (!contains(index)) {
- return NOT_EXIST;
- }
- return mContentTables.get(contentTableIndex).get(
- mLookupTable.get(index / mBlockSize) + (index % mBlockSize));
- }
-
- @UsedForTesting
- public ArrayList<Integer> getAll(final int index) {
- final ArrayList<Integer> ret = CollectionUtils.newArrayList();
- for (int i = 0; i < mContentTableCount; ++i) {
- ret.add(get(i, index));
- }
- return ret;
- }
-
- @UsedForTesting
- public void set(final int contentTableIndex, final int index, final int value) {
- if (mLookupTable.get(index / mBlockSize) == NOT_EXIST) {
- mLookupTable.set(index / mBlockSize, mContentTables.get(contentTableIndex).size());
- for (int i = 0; i < mContentTableCount; ++i) {
- for (int j = 0; j < mBlockSize; ++j) {
- mContentTables.get(i).add(NOT_EXIST);
- }
- }
- }
- mContentTables.get(contentTableIndex).set(
- mLookupTable.get(index / mBlockSize) + (index % mBlockSize), value);
- }
-
- public void remove(final int indexOfContent, final int index) {
- set(indexOfContent, index, NOT_EXIST);
- }
-
- @UsedForTesting
- public int size() {
- return mLookupTable.size() * mBlockSize;
- }
-
- @UsedForTesting
- /* package */ int getContentTableSize() {
- // This class always has at least one content table.
- return mContentTables.get(0).size();
- }
-
- @UsedForTesting
- /* package */ int getLookupTableSize() {
- return mLookupTable.size();
- }
-
- public boolean contains(final int index) {
- if (index < 0 || index / mBlockSize >= mLookupTable.size()
- || mLookupTable.get(index / mBlockSize) == NOT_EXIST) {
- return false;
- }
- return true;
- }
-
- @UsedForTesting
- public void write(final OutputStream lookupOutStream, final OutputStream[] contentOutStreams)
- throws IOException {
- if (contentOutStreams.length != mContentTableCount) {
- throw new RuntimeException(contentOutStreams.length + " streams are given, but the"
- + " table has " + mContentTableCount + " content tables.");
- }
- for (final int index : mLookupTable) {
- BinaryDictEncoderUtils.writeUIntToStream(lookupOutStream, index, SIZE_OF_INT_IN_BYTES);
- }
-
- for (int i = 0; i < contentOutStreams.length; ++i) {
- for (final int data : mContentTables.get(i)) {
- BinaryDictEncoderUtils.writeUIntToStream(contentOutStreams[i], data,
- SIZE_OF_INT_IN_BYTES);
- }
- }
- }
-
- @UsedForTesting
- public void writeToFiles(final File lookupTableFile, final File[] contentFiles)
- throws IOException {
- FileOutputStream lookupTableOutStream = null;
- final FileOutputStream[] contentTableOutStreams = new FileOutputStream[mContentTableCount];
- try {
- lookupTableOutStream = new FileOutputStream(lookupTableFile);
- for (int i = 0; i < contentFiles.length; ++i) {
- contentTableOutStreams[i] = new FileOutputStream(contentFiles[i]);
- }
- write(lookupTableOutStream, contentTableOutStreams);
- } finally {
- if (lookupTableOutStream != null) {
- lookupTableOutStream.close();
- }
- for (int i = 0; i < contentTableOutStreams.length; ++i) {
- if (contentTableOutStreams[i] != null) {
- contentTableOutStreams[i].close();
- }
- }
- }
- }
-
- private static byte[] readFileToByteArray(final File file) throws IOException {
- final byte[] contents = new byte[(int) file.length()];
- FileInputStream inStream = null;
- try {
- inStream = new FileInputStream(file);
- inStream.read(contents);
- } finally {
- if (inStream != null) {
- inStream.close();
- }
- }
- return contents;
- }
-
- @UsedForTesting
- public static SparseTable readFromFiles(final File lookupTableFile, final File[] contentFiles,
- final int blockSize) throws IOException {
- final ArrayList<ArrayList<Integer>> contentTables =
- new ArrayList<ArrayList<Integer>>(contentFiles.length);
- for (int i = 0; i < contentFiles.length; ++i) {
- contentTables.add(convertByteArrayToIntegerArray(readFileToByteArray(contentFiles[i])));
- }
- return new SparseTable(convertByteArrayToIntegerArray(readFileToByteArray(lookupTableFile)),
- contentTables, blockSize);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
deleted file mode 100644
index acab4f8a5..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-import com.android.inputmethod.latin.utils.JniUtils;
-
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * An implementation of DictDecoder for version 3 binary dictionary.
- */
-@UsedForTesting
-public class Ver3DictDecoder extends AbstractDictDecoder {
- private static final String TAG = Ver3DictDecoder.class.getSimpleName();
-
- static {
- JniUtils.loadNativeLibrary();
- }
-
- // TODO: implement something sensical instead of just a phony method
- private static native int doNothing();
-
- protected static class PtNodeReader extends AbstractDictDecoder.PtNodeReader {
- private static int readFrequency(final DictBuffer dictBuffer) {
- return dictBuffer.readUnsignedByte();
- }
- }
-
- protected final File mDictionaryBinaryFile;
- private final DictionaryBufferFactory mBufferFactory;
- protected DictBuffer mDictBuffer;
-
- /* package */ Ver3DictDecoder(final File file, final int factoryFlag) {
- mDictionaryBinaryFile = file;
- mDictBuffer = null;
-
- if ((factoryFlag & MASK_DICTBUFFER) == USE_READONLY_BYTEBUFFER) {
- mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
- } else if ((factoryFlag & MASK_DICTBUFFER) == USE_BYTEARRAY) {
- mBufferFactory = new DictionaryBufferFromByteArrayFactory();
- } else if ((factoryFlag & MASK_DICTBUFFER) == USE_WRITABLE_BYTEBUFFER) {
- mBufferFactory = new DictionaryBufferFromWritableByteBufferFactory();
- } else {
- mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
- }
- }
-
- /* package */ Ver3DictDecoder(final File file, final DictionaryBufferFactory factory) {
- mDictionaryBinaryFile = file;
- mBufferFactory = factory;
- }
-
- @Override
- public void openDictBuffer() throws FileNotFoundException, IOException {
- mDictBuffer = mBufferFactory.getDictionaryBuffer(mDictionaryBinaryFile);
- }
-
- @Override
- public boolean isDictBufferOpen() {
- return mDictBuffer != null;
- }
-
- /* package */ DictBuffer getDictBuffer() {
- return mDictBuffer;
- }
-
- @UsedForTesting
- /* package */ DictBuffer openAndGetDictBuffer() throws FileNotFoundException, IOException {
- openDictBuffer();
- return getDictBuffer();
- }
-
- @Override
- public FileHeader readHeader() throws IOException, UnsupportedFormatException {
- if (mDictBuffer == null) {
- openDictBuffer();
- }
- final FileHeader header = super.readHeader(mDictBuffer);
- final int version = header.mFormatOptions.mVersion;
- if (!(version >= 2 && version <= 3)) {
- throw new UnsupportedFormatException("File header has a wrong version : " + version);
- }
- return header;
- }
-
- // TODO: Make this buffer multi thread safe.
- private final int[] mCharacterBuffer = new int[FormatSpec.MAX_WORD_LENGTH];
- @Override
- public PtNodeInfo readPtNode(final int ptNodePos, final FormatOptions options) {
- int addressPointer = ptNodePos;
- final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
- addressPointer += FormatSpec.PTNODE_FLAGS_SIZE;
-
- final int parentAddress = PtNodeReader.readParentAddress(mDictBuffer, options);
- if (BinaryDictIOUtils.supportsDynamicUpdate(options)) {
- addressPointer += FormatSpec.PARENT_ADDRESS_SIZE;
- }
-
- final int characters[];
- if (0 != (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS)) {
- int index = 0;
- int character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
- while (FormatSpec.INVALID_CHARACTER != character) {
- // FusionDictionary is making sure that the length of the word is smaller than
- // MAX_WORD_LENGTH.
- // So we'll never write past the end of mCharacterBuffer.
- mCharacterBuffer[index++] = character;
- character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
- }
- characters = Arrays.copyOfRange(mCharacterBuffer, 0, index);
- } else {
- final int character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
- characters = new int[] { character };
- }
- final int frequency;
- if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
- frequency = PtNodeReader.readFrequency(mDictBuffer);
- addressPointer += FormatSpec.PTNODE_FREQUENCY_SIZE;
- } else {
- frequency = PtNode.NOT_A_TERMINAL;
- }
- int childrenAddress = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options);
- if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
- childrenAddress += addressPointer;
- }
- addressPointer += BinaryDictIOUtils.getChildrenAddressSize(flags, options);
- final ArrayList<WeightedString> shortcutTargets;
- if (0 != (flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)) {
- // readShortcut will add shortcuts to shortcutTargets.
- shortcutTargets = new ArrayList<WeightedString>();
- addressPointer += PtNodeReader.readShortcut(mDictBuffer, shortcutTargets);
- } else {
- shortcutTargets = null;
- }
-
- final ArrayList<PendingAttribute> bigrams;
- if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
- bigrams = new ArrayList<PendingAttribute>();
- addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams,
- addressPointer);
- if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- throw new RuntimeException("Too many bigrams in a PtNode (" + bigrams.size()
- + " but max is " + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")");
- }
- } else {
- bigrams = null;
- }
- return new PtNodeInfo(ptNodePos, addressPointer, flags, characters, frequency,
- parentAddress, childrenAddress, shortcutTargets, bigrams);
- }
-
- @Override
- public FusionDictionary readDictionaryBinary(final FusionDictionary dict,
- final boolean deleteDictIfBroken)
- throws FileNotFoundException, IOException, UnsupportedFormatException {
- if (mDictBuffer == null) {
- openDictBuffer();
- }
- try {
- return BinaryDictDecoderUtils.readDictionaryBinary(this, dict);
- } catch (IOException e) {
- Log.e(TAG, "The dictionary " + mDictionaryBinaryFile.getName() + " is broken.", e);
- if (deleteDictIfBroken && !mDictionaryBinaryFile.delete()) {
- Log.e(TAG, "Failed to delete the broken dictionary.");
- }
- throw e;
- } catch (UnsupportedFormatException e) {
- Log.e(TAG, "The dictionary " + mDictionaryBinaryFile.getName() + " is broken.", e);
- if (deleteDictIfBroken && !mDictionaryBinaryFile.delete()) {
- Log.e(TAG, "Failed to delete the broken dictionary.");
- }
- throw e;
- }
- }
-
- @Override
- public void setPosition(int newPos) {
- mDictBuffer.position(newPos);
- }
-
- @Override
- public int getPosition() {
- return mDictBuffer.position();
- }
-
- @Override
- public int readPtNodeCount() {
- return BinaryDictDecoderUtils.readPtNodeCount(mDictBuffer);
- }
-
- @Override
- public boolean readAndFollowForwardLink() {
- final int nextAddress = mDictBuffer.readUnsignedInt24();
- if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) {
- mDictBuffer.position(nextAddress);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean hasNextPtNodeArray() {
- return mDictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS;
- }
-
- @Override
- public void skipPtNode(final FormatOptions formatOptions) {
- final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
- PtNodeReader.readParentAddress(mDictBuffer, formatOptions);
- BinaryDictIOUtils.skipString(mDictBuffer,
- (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
- PtNodeReader.readChildrenAddress(mDictBuffer, flags, formatOptions);
- if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) PtNodeReader.readFrequency(mDictBuffer);
- if ((flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) != 0) {
- final int shortcutsSize = mDictBuffer.readUnsignedShort();
- mDictBuffer.position(mDictBuffer.position() + shortcutsSize
- - FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE);
- }
- if ((flags & FormatSpec.FLAG_HAS_BIGRAMS) != 0) {
- int bigramCount = 0;
- while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- final int bigramFlags = mDictBuffer.readUnsignedByte();
- switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) {
- case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE:
- mDictBuffer.readUnsignedByte();
- break;
- case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES:
- mDictBuffer.readUnsignedShort();
- break;
- case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES:
- mDictBuffer.readUnsignedInt24();
- break;
- }
- if ((bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT) == 0) break;
- }
- if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- throw new RuntimeException("Too many bigrams in a PtNode.");
- }
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
deleted file mode 100644
index 5da34534e..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Iterator;
-
-/**
- * An implementation of DictEncoder for version 3 binary dictionary.
- */
-public class Ver3DictEncoder implements DictEncoder {
-
- private final File mDictFile;
- private OutputStream mOutStream;
- private byte[] mBuffer;
- private int mPosition;
-
- public Ver3DictEncoder(final File dictFile) {
- mDictFile = dictFile;
- mOutStream = null;
- mBuffer = null;
- }
-
- // This constructor is used only by BinaryDictOffdeviceUtilsTests.
- // If you want to use this in the production code, you should consider keeping consistency of
- // the interface of Ver3DictDecoder by using factory.
- public Ver3DictEncoder(final OutputStream outStream) {
- mDictFile = null;
- mOutStream = outStream;
- }
-
- private void openStream() throws FileNotFoundException {
- mOutStream = new FileOutputStream(mDictFile);
- }
-
- private void close() throws IOException {
- if (mOutStream != null) {
- mOutStream.close();
- mOutStream = null;
- }
- }
-
- @Override
- public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
- throws IOException, UnsupportedFormatException {
- if (formatOptions.mVersion > FormatSpec.VERSION3) {
- throw new UnsupportedFormatException(
- "The given format options has wrong version number : "
- + formatOptions.mVersion);
- }
-
- if (mOutStream == null) {
- openStream();
- }
- BinaryDictEncoderUtils.writeDictionaryHeader(mOutStream, dict, formatOptions);
-
- // Addresses are limited to 3 bytes, but since addresses can be relative to each node
- // array, the structure itself is not limited to 16MB. However, if it is over 16MB deciding
- // the order of the PtNode arrays becomes a quite complicated problem, because though the
- // dictionary itself does not have a size limit, each node array must still be within 16MB
- // of all its children and parents. As long as this is ensured, the dictionary file may
- // grow to any size.
-
- // Leave the choice of the optimal node order to the flattenTree function.
- MakedictLog.i("Flattening the tree...");
- ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray);
-
- MakedictLog.i("Computing addresses...");
- BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions);
- MakedictLog.i("Checking PtNode array...");
- if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes);
-
- // Create a buffer that matches the final dictionary size.
- final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1);
- final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize;
- mBuffer = new byte[bufferSize];
-
- MakedictLog.i("Writing file...");
-
- for (PtNodeArray nodeArray : flatNodes) {
- BinaryDictEncoderUtils.writePlacedPtNodeArray(dict, this, nodeArray, formatOptions);
- }
- if (MakedictLog.DBG) BinaryDictEncoderUtils.showStatistics(flatNodes);
- mOutStream.write(mBuffer, 0, mPosition);
-
- MakedictLog.i("Done");
- close();
- }
-
- @Override
- public void setPosition(final int position) {
- if (mBuffer == null || position < 0 || position >= mBuffer.length) return;
- mPosition = position;
- }
-
- @Override
- public int getPosition() {
- return mPosition;
- }
-
- @Override
- public void writePtNodeCount(final int ptNodeCount) {
- final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount);
- if (countSize != 1 && countSize != 2) {
- throw new RuntimeException("Strange size from getGroupCountSize : " + countSize);
- }
- final int encodedPtNodeCount = (countSize == 2) ?
- (ptNodeCount | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG) : ptNodeCount;
- mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, encodedPtNodeCount,
- countSize);
- }
-
- private void writePtNodeFlags(final PtNode ptNode, final FormatOptions formatOptions) {
- final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
- mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition,
- BinaryDictEncoderUtils.makePtNodeFlags(ptNode, childrenPos, formatOptions),
- FormatSpec.PTNODE_FLAGS_SIZE);
- }
-
- private void writeParentPosition(final int parentPosition, final PtNode ptNode,
- final FormatOptions formatOptions) {
- if (parentPosition == FormatSpec.NO_PARENT_ADDRESS) {
- mPosition = BinaryDictEncoderUtils.writeParentAddress(mBuffer, mPosition,
- parentPosition, formatOptions);
- } else {
- mPosition = BinaryDictEncoderUtils.writeParentAddress(mBuffer, mPosition,
- parentPosition - ptNode.mCachedAddressAfterUpdate, formatOptions);
- }
- }
-
- private void writeCharacters(final int[] codePoints, final boolean hasSeveralChars) {
- mPosition = CharEncoding.writeCharArray(codePoints, mBuffer, mPosition);
- if (hasSeveralChars) {
- mBuffer[mPosition++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
- }
- }
-
- private void writeFrequency(final int frequency) {
- if (frequency >= 0) {
- mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, frequency,
- FormatSpec.PTNODE_FREQUENCY_SIZE);
- }
- }
-
- private void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions) {
- final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
- if (formatOptions.mSupportsDynamicUpdate) {
- mPosition += BinaryDictEncoderUtils.writeSignedChildrenPosition(mBuffer, mPosition,
- childrenPos);
- } else {
- mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition,
- childrenPos);
- }
- }
-
- /**
- * Write a shortcut attributes list to mBuffer.
- *
- * @param shortcuts the shortcut attributes list.
- */
- private void writeShortcuts(final ArrayList<WeightedString> shortcuts) {
- if (null == shortcuts || shortcuts.isEmpty()) return;
-
- final int indexOfShortcutByteSize = mPosition;
- mPosition += FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE;
- final Iterator<WeightedString> shortcutIterator = shortcuts.iterator();
- while (shortcutIterator.hasNext()) {
- final WeightedString target = shortcutIterator.next();
- final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
- shortcutIterator.hasNext(),
- target.mFrequency);
- mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, shortcutFlags,
- FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
- final int shortcutShift = CharEncoding.writeString(mBuffer, mPosition, target.mWord);
- mPosition += shortcutShift;
- }
- final int shortcutByteSize = mPosition - indexOfShortcutByteSize;
- if (shortcutByteSize > FormatSpec.MAX_SHORTCUT_LIST_SIZE_IN_A_PTNODE) {
- throw new RuntimeException("Shortcut list too large");
- }
- BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, indexOfShortcutByteSize, shortcutByteSize,
- FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE);
- }
-
- /**
- * Write a bigram attributes list to mBuffer.
- *
- * @param bigrams the bigram attributes list.
- * @param dict the dictionary the node array is a part of (for relative offsets).
- */
- private void writeBigrams(final ArrayList<WeightedString> bigrams,
- final FusionDictionary dict) {
- if (bigrams == null) return;
-
- final Iterator<WeightedString> bigramIterator = bigrams.iterator();
- while (bigramIterator.hasNext()) {
- final WeightedString bigram = bigramIterator.next();
- final PtNode target =
- FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord);
- final int addressOfBigram = target.mCachedAddressAfterUpdate;
- final int unigramFrequencyForThisWord = target.mFrequency;
- final int offset = addressOfBigram
- - (mPosition + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
- final int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(),
- offset, bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord);
- mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, bigramFlags,
- FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
- mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition,
- Math.abs(offset));
- }
- }
-
- @Override
- public void writeForwardLinkAddress(final int forwardLinkAddress) {
- mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, forwardLinkAddress,
- FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
- }
-
- @Override
- public void writePtNode(final PtNode ptNode, final int parentPosition,
- final FormatOptions formatOptions, final FusionDictionary dict) {
- writePtNodeFlags(ptNode, formatOptions);
- writeParentPosition(parentPosition, ptNode, formatOptions);
- writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
- writeFrequency(ptNode.mFrequency);
- writeChildrenPosition(ptNode, formatOptions);
- writeShortcuts(ptNode.mShortcutTargets);
- writeBigrams(ptNode.mBigrams, dict);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java
deleted file mode 100644
index 07adda625..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-
-/**
- * An implementation of DictUpdater for version 3 binary dictionary.
- */
-@UsedForTesting
-public class Ver3DictUpdater extends Ver3DictDecoder implements DictUpdater {
- private OutputStream mOutStream;
-
- @UsedForTesting
- public Ver3DictUpdater(final File dictFile, final int factoryType) {
- // DictUpdater must have an updatable DictBuffer.
- super(dictFile, ((factoryType & MASK_DICTBUFFER) == USE_BYTEARRAY)
- ? USE_BYTEARRAY : USE_WRITABLE_BYTEBUFFER);
- mOutStream = null;
- }
-
- private void openStreamAndBuffer() throws FileNotFoundException, IOException {
- super.openDictBuffer();
- mOutStream = new FileOutputStream(mDictionaryBinaryFile, true /* append */);
- }
-
- private void close() throws IOException {
- if (mOutStream != null) {
- mOutStream.close();
- mOutStream = null;
- }
- }
-
- @Override @UsedForTesting
- public void deleteWord(final String word) throws IOException, UnsupportedFormatException {
- if (mOutStream == null) openStreamAndBuffer();
- mDictBuffer.position(0);
- readHeader();
- final int wordPos = getTerminalPosition(word);
- if (wordPos != FormatSpec.NOT_VALID_WORD) {
- mDictBuffer.position(wordPos);
- final int flags = mDictBuffer.readUnsignedByte();
- mDictBuffer.position(wordPos);
- mDictBuffer.put((byte) DynamicBinaryDictIOUtils.markAsDeleted(flags));
- }
- close();
- }
-
- @Override @UsedForTesting
- public void insertWord(final String word, final int frequency,
- final ArrayList<WeightedString> bigramStrings,
- final ArrayList<WeightedString> shortcuts,
- final boolean isNotAWord, final boolean isBlackListEntry)
- throws IOException, UnsupportedFormatException {
- if (mOutStream == null) openStreamAndBuffer();
- DynamicBinaryDictIOUtils.insertWord(this, mOutStream, word, frequency, bigramStrings,
- shortcuts, isNotAWord, isBlackListEntry);
- close();
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
deleted file mode 100644
index 734223ec2..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * An implementation of binary dictionary decoder for version 4 binary dictionary.
- */
-@UsedForTesting
-public class Ver4DictDecoder extends AbstractDictDecoder {
- private static final String TAG = Ver4DictDecoder.class.getSimpleName();
-
- private static final int FILETYPE_TRIE = 1;
- private static final int FILETYPE_FREQUENCY = 2;
- private static final int FILETYPE_TERMINAL_ADDRESS_TABLE = 3;
- private static final int FILETYPE_BIGRAM_FREQ = 4;
- private static final int FILETYPE_SHORTCUT = 5;
-
- private final File mDictDirectory;
- private final DictionaryBufferFactory mBufferFactory;
- protected DictBuffer mDictBuffer;
- private DictBuffer mFrequencyBuffer;
- private DictBuffer mTerminalAddressTableBuffer;
- private DictBuffer mBigramBuffer;
- private DictBuffer mShortcutBuffer;
- private SparseTable mBigramAddressTable;
- private SparseTable mShortcutAddressTable;
-
- @UsedForTesting
- /* package */ Ver4DictDecoder(final File dictDirectory, final int factoryFlag) {
- mDictDirectory = dictDirectory;
- mDictBuffer = mFrequencyBuffer = null;
-
- if ((factoryFlag & MASK_DICTBUFFER) == USE_READONLY_BYTEBUFFER) {
- mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
- } else if ((factoryFlag & MASK_DICTBUFFER) == USE_BYTEARRAY) {
- mBufferFactory = new DictionaryBufferFromByteArrayFactory();
- } else if ((factoryFlag & MASK_DICTBUFFER) == USE_WRITABLE_BYTEBUFFER) {
- mBufferFactory = new DictionaryBufferFromWritableByteBufferFactory();
- } else {
- mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
- }
- }
-
- @UsedForTesting
- /* package */ Ver4DictDecoder(final File dictDirectory, final DictionaryBufferFactory factory) {
- mDictDirectory = dictDirectory;
- mBufferFactory = factory;
- mDictBuffer = mFrequencyBuffer = null;
- }
-
- private File getFile(final int fileType) {
- if (fileType == FILETYPE_TRIE) {
- return new File(mDictDirectory,
- mDictDirectory.getName() + FormatSpec.TRIE_FILE_EXTENSION);
- } else if (fileType == FILETYPE_FREQUENCY) {
- return new File(mDictDirectory,
- mDictDirectory.getName() + FormatSpec.FREQ_FILE_EXTENSION);
- } else if (fileType == FILETYPE_TERMINAL_ADDRESS_TABLE) {
- return new File(mDictDirectory,
- mDictDirectory.getName() + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
- } else if (fileType == FILETYPE_BIGRAM_FREQ) {
- return new File(mDictDirectory,
- mDictDirectory.getName() + FormatSpec.BIGRAM_FILE_EXTENSION
- + FormatSpec.BIGRAM_FREQ_CONTENT_ID);
- } else if (fileType == FILETYPE_SHORTCUT) {
- return new File(mDictDirectory,
- mDictDirectory.getName() + FormatSpec.SHORTCUT_FILE_EXTENSION
- + FormatSpec.SHORTCUT_CONTENT_ID);
- } else {
- throw new RuntimeException("Unsupported kind of file : " + fileType);
- }
- }
-
- @Override
- public void openDictBuffer() throws FileNotFoundException, IOException {
- mDictBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_TRIE));
- mFrequencyBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_FREQUENCY));
- mTerminalAddressTableBuffer = mBufferFactory.getDictionaryBuffer(
- getFile(FILETYPE_TERMINAL_ADDRESS_TABLE));
- mBigramBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_BIGRAM_FREQ));
- loadBigramAddressSparseTable();
- mShortcutBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_SHORTCUT));
- loadShortcutAddressSparseTable();
- }
-
- @Override
- public boolean isDictBufferOpen() {
- return mDictBuffer != null;
- }
-
- /* package */ DictBuffer getDictBuffer() {
- return mDictBuffer;
- }
-
- @Override
- public FileHeader readHeader() throws IOException, UnsupportedFormatException {
- if (mDictBuffer == null) {
- openDictBuffer();
- }
- final FileHeader header = super.readHeader(mDictBuffer);
- final int version = header.mFormatOptions.mVersion;
- if (version != 4) {
- throw new UnsupportedFormatException("File header has a wrong version : " + version);
- }
- return header;
- }
-
- private void loadBigramAddressSparseTable() throws IOException {
- final File lookupIndexFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
- final File freqsFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
- + FormatSpec.BIGRAM_FREQ_CONTENT_ID);
- mBigramAddressTable = SparseTable.readFromFiles(lookupIndexFile, new File[] { freqsFile },
- FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE);
- }
-
- // TODO: Let's have something like SparseTableContentsReader in this class.
- private void loadShortcutAddressSparseTable() throws IOException {
- final File lookupIndexFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
- final File contentFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
- + FormatSpec.SHORTCUT_CONTENT_ID);
- final File timestampsFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
- + FormatSpec.SHORTCUT_CONTENT_ID);
- mShortcutAddressTable = SparseTable.readFromFiles(lookupIndexFile,
- new File[] { contentFile, timestampsFile },
- FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE);
- }
-
- protected static class PtNodeReader extends AbstractDictDecoder.PtNodeReader {
- protected static int readFrequency(final DictBuffer frequencyBuffer, final int terminalId) {
- frequencyBuffer.position(terminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE + 1);
- return frequencyBuffer.readUnsignedByte();
- }
-
- protected static int readTerminalId(final DictBuffer dictBuffer) {
- return dictBuffer.readInt();
- }
- }
-
- private ArrayList<WeightedString> readShortcuts(final int terminalId) {
- if (mShortcutAddressTable.get(0, terminalId) == SparseTable.NOT_EXIST) return null;
-
- final ArrayList<WeightedString> ret = CollectionUtils.newArrayList();
- final int posOfShortcuts = mShortcutAddressTable.get(FormatSpec.SHORTCUT_CONTENT_INDEX,
- terminalId);
- mShortcutBuffer.position(posOfShortcuts);
- while (true) {
- final int flags = mShortcutBuffer.readUnsignedByte();
- final String word = CharEncoding.readString(mShortcutBuffer);
- ret.add(new WeightedString(word,
- flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
- if (0 == (flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
- }
- return ret;
- }
-
- // TODO: Make this buffer thread safe.
- // TODO: Support words longer than FormatSpec.MAX_WORD_LENGTH.
- private final int[] mCharacterBuffer = new int[FormatSpec.MAX_WORD_LENGTH];
- @Override
- public PtNodeInfo readPtNode(int ptNodePos, FormatOptions options) {
- int addressPointer = ptNodePos;
- final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
- addressPointer += FormatSpec.PTNODE_FLAGS_SIZE;
-
- final int parentAddress = PtNodeReader.readParentAddress(mDictBuffer, options);
- if (BinaryDictIOUtils.supportsDynamicUpdate(options)) {
- addressPointer += FormatSpec.PARENT_ADDRESS_SIZE;
- }
-
- final int characters[];
- if (0 != (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS)) {
- int index = 0;
- int character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
- while (FormatSpec.INVALID_CHARACTER != character
- && index < FormatSpec.MAX_WORD_LENGTH) {
- mCharacterBuffer[index++] = character;
- character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
- }
- characters = Arrays.copyOfRange(mCharacterBuffer, 0, index);
- } else {
- final int character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
- characters = new int[] { character };
- }
- final int terminalId;
- if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
- terminalId = PtNodeReader.readTerminalId(mDictBuffer);
- addressPointer += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
- } else {
- terminalId = PtNode.NOT_A_TERMINAL;
- }
-
- final int frequency;
- if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
- frequency = PtNodeReader.readFrequency(mFrequencyBuffer, terminalId);
- } else {
- frequency = PtNode.NOT_A_TERMINAL;
- }
- int childrenAddress = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options);
- if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
- childrenAddress += addressPointer;
- }
- addressPointer += BinaryDictIOUtils.getChildrenAddressSize(flags, options);
- final ArrayList<WeightedString> shortcutTargets = readShortcuts(terminalId);
-
- final ArrayList<PendingAttribute> bigrams;
- if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
- bigrams = new ArrayList<PendingAttribute>();
- final int posOfBigrams = mBigramAddressTable.get(0 /* contentTableIndex */, terminalId);
- mBigramBuffer.position(posOfBigrams);
- while (bigrams.size() < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- // If bigrams.size() reaches FormatSpec.MAX_BIGRAMS_IN_A_PTNODE,
- // remaining bigram entries are ignored.
- final int bigramFlags = mBigramBuffer.readUnsignedByte();
- final int targetTerminalId = mBigramBuffer.readUnsignedInt24();
- mTerminalAddressTableBuffer.position(
- targetTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
- final int targetAddress = mTerminalAddressTableBuffer.readUnsignedInt24();
- bigrams.add(new PendingAttribute(
- bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
- targetAddress));
- if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
- }
- if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- throw new RuntimeException("Too many bigrams in a PtNode (" + bigrams.size()
- + " but max is " + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")");
- }
- } else {
- bigrams = null;
- }
- return new PtNodeInfo(ptNodePos, addressPointer, flags, characters, frequency,
- parentAddress, childrenAddress, shortcutTargets, bigrams);
- }
-
- private void deleteDictFiles() {
- final File[] files = mDictDirectory.listFiles();
- for (int i = 0; i < files.length; ++i) {
- files[i].delete();
- }
- }
-
- @Override
- public FusionDictionary readDictionaryBinary(final FusionDictionary dict,
- final boolean deleteDictIfBroken)
- throws FileNotFoundException, IOException, UnsupportedFormatException {
- if (mDictBuffer == null) {
- openDictBuffer();
- }
- try {
- return BinaryDictDecoderUtils.readDictionaryBinary(this, dict);
- } catch (IOException e) {
- Log.e(TAG, "The dictionary " + mDictDirectory.getName() + " is broken.", e);
- if (deleteDictIfBroken) {
- deleteDictFiles();
- }
- throw e;
- } catch (UnsupportedFormatException e) {
- Log.e(TAG, "The dictionary " + mDictDirectory.getName() + " is broken.", e);
- if (deleteDictIfBroken) {
- deleteDictFiles();
- }
- throw e;
- }
- }
-
- @Override
- public void setPosition(int newPos) {
- mDictBuffer.position(newPos);
- }
-
- @Override
- public int getPosition() {
- return mDictBuffer.position();
- }
-
- @Override
- public int readPtNodeCount() {
- return BinaryDictDecoderUtils.readPtNodeCount(mDictBuffer);
- }
-
- @Override
- public boolean readAndFollowForwardLink() {
- final int nextAddress = mDictBuffer.readUnsignedInt24();
- if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) {
- mDictBuffer.position(nextAddress);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean hasNextPtNodeArray() {
- return mDictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS;
- }
-
- @Override
- public void skipPtNode(final FormatOptions formatOptions) {
- final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
- PtNodeReader.readParentAddress(mDictBuffer, formatOptions);
- BinaryDictIOUtils.skipString(mDictBuffer,
- (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
- if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) PtNodeReader.readTerminalId(mDictBuffer);
- PtNodeReader.readChildrenAddress(mDictBuffer, flags, formatOptions);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
deleted file mode 100644
index 8d5b48a9b..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ /dev/null
@@ -1,475 +0,0 @@
-/*
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Iterator;
-
-/**
- * An implementation of DictEncoder for version 4 binary dictionary.
- */
-@UsedForTesting
-public class Ver4DictEncoder implements DictEncoder {
- private final File mDictPlacedDir;
- private byte[] mTrieBuf;
- private int mTriePos;
- private int mHeaderSize;
- private OutputStream mTrieOutStream;
- private OutputStream mFreqOutStream;
- private OutputStream mUnigramTimestampOutStream;
- private OutputStream mTerminalAddressTableOutStream;
- private File mDictDir;
- private String mBaseFilename;
- private BigramContentWriter mBigramWriter;
- private ShortcutContentWriter mShortcutWriter;
-
- @UsedForTesting
- public Ver4DictEncoder(final File dictPlacedDir) {
- mDictPlacedDir = dictPlacedDir;
- }
-
- private interface SparseTableContentWriterInterface {
- public void write(final OutputStream outStream) throws IOException;
- }
-
- private static class SparseTableContentWriter {
- private final int mContentCount;
- private final SparseTable mSparseTable;
- private final File mLookupTableFile;
- protected final File mBaseDir;
- private final File[] mAddressTableFiles;
- private final File[] mContentFiles;
- protected final OutputStream[] mContentOutStreams;
-
- public SparseTableContentWriter(final String name, final int initialCapacity,
- final int blockSize, final File baseDir, final String[] contentFilenames,
- final String[] contentIds) {
- if (contentFilenames.length != contentIds.length) {
- throw new RuntimeException("The length of contentFilenames and the length of"
- + " contentIds are different " + contentFilenames.length + ", "
- + contentIds.length);
- }
- mContentCount = contentFilenames.length;
- mSparseTable = new SparseTable(initialCapacity, blockSize, mContentCount);
- mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
- mAddressTableFiles = new File[mContentCount];
- mContentFiles = new File[mContentCount];
- mBaseDir = baseDir;
- for (int i = 0; i < mContentCount; ++i) {
- mAddressTableFiles[i] = new File(mBaseDir,
- name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentIds[i]);
- mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentIds[i]);
- }
- mContentOutStreams = new OutputStream[mContentCount];
- }
-
- public void openStreams() throws FileNotFoundException {
- for (int i = 0; i < mContentCount; ++i) {
- mContentOutStreams[i] = new FileOutputStream(mContentFiles[i]);
- }
- }
-
- protected void write(final int contentIndex, final int index,
- final SparseTableContentWriterInterface writer) throws IOException {
- mSparseTable.set(contentIndex, index, (int) mContentFiles[contentIndex].length());
- writer.write(mContentOutStreams[contentIndex]);
- mContentOutStreams[contentIndex].flush();
- }
-
- public void closeStreams() throws IOException {
- mSparseTable.writeToFiles(mLookupTableFile, mAddressTableFiles);
- for (int i = 0; i < mContentCount; ++i) {
- mContentOutStreams[i].close();
- }
- }
- }
-
- private static class BigramContentWriter extends SparseTableContentWriter {
- private final boolean mWriteTimestamp;
-
- public BigramContentWriter(final String name, final int initialCapacity,
- final File baseDir, final boolean writeTimestamp) {
- super(name + FormatSpec.BIGRAM_FILE_EXTENSION, initialCapacity,
- FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
- getContentFilenames(name, writeTimestamp), getContentIds(writeTimestamp));
- mWriteTimestamp = writeTimestamp;
- }
-
- private static String[] getContentFilenames(final String name,
- final boolean writeTimestamp) {
- final String[] contentFilenames;
- if (writeTimestamp) {
- contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION,
- name + FormatSpec.BIGRAM_FILE_EXTENSION };
- } else {
- contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION };
- }
- return contentFilenames;
- }
-
- private static String[] getContentIds(final boolean writeTimestamp) {
- final String[] contentIds;
- if (writeTimestamp) {
- contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID,
- FormatSpec.BIGRAM_TIMESTAMP_CONTENT_ID };
- } else {
- contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID };
- }
- return contentIds;
- }
-
- public void writeBigramsForOneWord(final int terminalId, final int bigramCount,
- final Iterator<WeightedString> bigramIterator, final FusionDictionary dict)
- throws IOException {
- write(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId,
- new SparseTableContentWriterInterface() {
- @Override
- public void write(final OutputStream outStream) throws IOException {
- writeBigramsForOneWordInternal(outStream, bigramIterator, dict);
- }});
- if (mWriteTimestamp) {
- write(FormatSpec.BIGRAM_TIMESTAMP_CONTENT_INDEX, terminalId,
- new SparseTableContentWriterInterface() {
- @Override
- public void write(final OutputStream outStream) throws IOException {
- initBigramTimestampsCountersAndLevelsForOneWordInternal(outStream,
- bigramCount);
- }});
- }
- }
-
- private void writeBigramsForOneWordInternal(final OutputStream outStream,
- final Iterator<WeightedString> bigramIterator, final FusionDictionary dict)
- throws IOException {
- while (bigramIterator.hasNext()) {
- final WeightedString bigram = bigramIterator.next();
- final PtNode target =
- FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord);
- final int unigramFrequencyForThisWord = target.mFrequency;
- final int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(
- bigramIterator.hasNext(), 0, bigram.mFrequency,
- unigramFrequencyForThisWord, bigram.mWord);
- BinaryDictEncoderUtils.writeUIntToStream(outStream, bigramFlags,
- FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
- BinaryDictEncoderUtils.writeUIntToStream(outStream, target.mTerminalId,
- FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE);
- }
- }
-
- private void initBigramTimestampsCountersAndLevelsForOneWordInternal(
- final OutputStream outStream, final int bigramCount) throws IOException {
- for (int i = 0; i < bigramCount; ++i) {
- // TODO: Figure out what initial values should be.
- BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */,
- FormatSpec.BIGRAM_TIMESTAMP_SIZE);
- BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */,
- FormatSpec.BIGRAM_COUNTER_SIZE);
- BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */,
- FormatSpec.BIGRAM_LEVEL_SIZE);
- }
- }
- }
-
- private static class ShortcutContentWriter extends SparseTableContentWriter {
- public ShortcutContentWriter(final String name, final int initialCapacity,
- final File baseDir) {
- super(name + FormatSpec.SHORTCUT_FILE_EXTENSION, initialCapacity,
- FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
- new String[] { name + FormatSpec.SHORTCUT_FILE_EXTENSION },
- new String[] { FormatSpec.SHORTCUT_CONTENT_ID });
- }
-
- public void writeShortcutForOneWord(final int terminalId,
- final Iterator<WeightedString> shortcutIterator) throws IOException {
- write(FormatSpec.SHORTCUT_CONTENT_INDEX, terminalId,
- new SparseTableContentWriterInterface() {
- @Override
- public void write(final OutputStream outStream) throws IOException {
- writeShortcutForOneWordInternal(outStream, shortcutIterator);
- }
- });
- }
-
- private void writeShortcutForOneWordInternal(final OutputStream outStream,
- final Iterator<WeightedString> shortcutIterator) throws IOException {
- while (shortcutIterator.hasNext()) {
- final WeightedString target = shortcutIterator.next();
- final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
- shortcutIterator.hasNext(), target.mFrequency);
- BinaryDictEncoderUtils.writeUIntToStream(outStream, shortcutFlags,
- FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
- CharEncoding.writeString(outStream, target.mWord);
- }
- }
- }
-
- private void openStreams(final FormatOptions formatOptions, final DictionaryOptions dictOptions)
- throws FileNotFoundException, IOException {
- final FileHeader header = new FileHeader(0, dictOptions, formatOptions);
- mBaseFilename = header.getId() + "." + header.getVersion();
- mDictDir = new File(mDictPlacedDir, mBaseFilename);
- final File trieFile = new File(mDictDir, mBaseFilename + FormatSpec.TRIE_FILE_EXTENSION);
- final File freqFile = new File(mDictDir, mBaseFilename + FormatSpec.FREQ_FILE_EXTENSION);
- final File timestampFile = new File(mDictDir,
- mBaseFilename + FormatSpec.UNIGRAM_TIMESTAMP_FILE_EXTENSION);
- final File terminalAddressTableFile = new File(mDictDir,
- mBaseFilename + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
- if (!mDictDir.isDirectory()) {
- if (mDictDir.exists()) mDictDir.delete();
- mDictDir.mkdirs();
- }
- mTrieOutStream = new FileOutputStream(trieFile);
- mFreqOutStream = new FileOutputStream(freqFile);
- mTerminalAddressTableOutStream = new FileOutputStream(terminalAddressTableFile);
- if (formatOptions.mHasTimestamp) {
- mUnigramTimestampOutStream = new FileOutputStream(timestampFile);
- }
- }
-
- private void close() throws IOException {
- try {
- if (mTrieOutStream != null) {
- mTrieOutStream.close();
- }
- if (mFreqOutStream != null) {
- mFreqOutStream.close();
- }
- if (mTerminalAddressTableOutStream != null) {
- mTerminalAddressTableOutStream.close();
- }
- if (mUnigramTimestampOutStream != null) {
- mUnigramTimestampOutStream.close();
- }
- } finally {
- mTrieOutStream = null;
- mFreqOutStream = null;
- mTerminalAddressTableOutStream = null;
- }
- }
-
- @Override
- public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
- throws IOException, UnsupportedFormatException {
- if (formatOptions.mVersion != FormatSpec.VERSION4) {
- throw new UnsupportedFormatException("File header has a wrong version number : "
- + formatOptions.mVersion);
- }
- if (!mDictPlacedDir.isDirectory()) {
- throw new UnsupportedFormatException("Given path is not a directory.");
- }
-
- if (mTrieOutStream == null) {
- openStreams(formatOptions, dict.mOptions);
- }
-
- mHeaderSize = BinaryDictEncoderUtils.writeDictionaryHeader(mTrieOutStream, dict,
- formatOptions);
-
- MakedictLog.i("Flattening the tree...");
- ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray);
- int terminalCount = 0;
- for (final PtNodeArray array : flatNodes) {
- for (final PtNode node : array.mData) {
- if (node.isTerminal()) node.mTerminalId = terminalCount++;
- }
- }
-
- MakedictLog.i("Computing addresses...");
- BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions);
- if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes);
-
- writeTerminalData(flatNodes, terminalCount);
- if (formatOptions.mHasTimestamp) {
- initUnigramTimestamps(terminalCount);
- }
- mBigramWriter = new BigramContentWriter(mBaseFilename, terminalCount, mDictDir,
- formatOptions.mHasTimestamp);
- writeBigrams(flatNodes, dict);
- mShortcutWriter = new ShortcutContentWriter(mBaseFilename, terminalCount, mDictDir);
- writeShortcuts(flatNodes);
-
- final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1);
- final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize;
- mTrieBuf = new byte[bufferSize];
-
- MakedictLog.i("Writing file...");
- for (PtNodeArray nodeArray : flatNodes) {
- BinaryDictEncoderUtils.writePlacedPtNodeArray(dict, this, nodeArray, formatOptions);
- }
- if (MakedictLog.DBG) {
- BinaryDictEncoderUtils.showStatistics(flatNodes);
- MakedictLog.i("has " + terminalCount + " terminals.");
- }
- mTrieOutStream.write(mTrieBuf);
-
- MakedictLog.i("Done");
- close();
- }
-
- @Override
- public void setPosition(int position) {
- if (mTrieBuf == null || position < 0 || position >- mTrieBuf.length) return;
- mTriePos = position;
- }
-
- @Override
- public int getPosition() {
- return mTriePos;
- }
-
- @Override
- public void writePtNodeCount(int ptNodeCount) {
- final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount);
- // ptNodeCount must fit on one byte or two bytes.
- // Please see comments in FormatSpec
- if (countSize != 1 && countSize != 2) {
- throw new RuntimeException("Strange size from getPtNodeCountSize : " + countSize);
- }
- final int encodedPtNodeCount = (countSize == 2) ?
- (ptNodeCount | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG) : ptNodeCount;
- mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, encodedPtNodeCount,
- countSize);
- }
-
- private void writePtNodeFlags(final PtNode ptNode, final FormatOptions formatOptions) {
- final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
- mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos,
- BinaryDictEncoderUtils.makePtNodeFlags(ptNode, childrenPos, formatOptions),
- FormatSpec.PTNODE_FLAGS_SIZE);
- }
-
- private void writeParentPosition(int parentPos, final PtNode ptNode,
- final FormatOptions formatOptions) {
- if (parentPos != FormatSpec.NO_PARENT_ADDRESS) {
- parentPos -= ptNode.mCachedAddressAfterUpdate;
- }
- mTriePos = BinaryDictEncoderUtils.writeParentAddress(mTrieBuf, mTriePos, parentPos,
- formatOptions);
- }
-
- private void writeCharacters(final int[] characters, final boolean hasSeveralChars) {
- mTriePos = CharEncoding.writeCharArray(characters, mTrieBuf, mTriePos);
- if (hasSeveralChars) {
- mTrieBuf[mTriePos++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
- }
- }
-
- private void writeTerminalId(final int terminalId) {
- mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, terminalId,
- FormatSpec.PTNODE_TERMINAL_ID_SIZE);
- }
-
- private void writeChildrenPosition(PtNode ptNode, FormatOptions formatOptions) {
- final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
- if (formatOptions.mSupportsDynamicUpdate) {
- mTriePos += BinaryDictEncoderUtils.writeSignedChildrenPosition(mTrieBuf,
- mTriePos, childrenPos);
- } else {
- mTriePos += BinaryDictEncoderUtils.writeChildrenPosition(mTrieBuf,
- mTriePos, childrenPos);
- }
- }
-
- private void writeBigrams(final ArrayList<PtNodeArray> flatNodes, final FusionDictionary dict)
- throws IOException {
- mBigramWriter.openStreams();
- for (final PtNodeArray nodeArray : flatNodes) {
- for (final PtNode ptNode : nodeArray.mData) {
- if (ptNode.mBigrams != null) {
- mBigramWriter.writeBigramsForOneWord(ptNode.mTerminalId, ptNode.mBigrams.size(),
- ptNode.mBigrams.iterator(), dict);
- }
- }
- }
- mBigramWriter.closeStreams();
- }
-
- private void writeShortcuts(final ArrayList<PtNodeArray> flatNodes) throws IOException {
- mShortcutWriter.openStreams();
- for (final PtNodeArray nodeArray : flatNodes) {
- for (final PtNode ptNode : nodeArray.mData) {
- if (ptNode.mShortcutTargets != null && !ptNode.mShortcutTargets.isEmpty()) {
- mShortcutWriter.writeShortcutForOneWord(ptNode.mTerminalId,
- ptNode.mShortcutTargets.iterator());
- }
- }
- }
- mShortcutWriter.closeStreams();
- }
-
- @Override
- public void writeForwardLinkAddress(int forwardLinkAddress) {
- mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos,
- forwardLinkAddress, FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
- }
-
- @Override
- public void writePtNode(final PtNode ptNode, final int parentPosition,
- final FormatOptions formatOptions, final FusionDictionary dict) {
- writePtNodeFlags(ptNode, formatOptions);
- writeParentPosition(parentPosition, ptNode, formatOptions);
- writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
- if (ptNode.isTerminal()) {
- writeTerminalId(ptNode.mTerminalId);
- }
- writeChildrenPosition(ptNode, formatOptions);
- }
-
- private void writeTerminalData(final ArrayList<PtNodeArray> flatNodes,
- final int terminalCount) throws IOException {
- final byte[] freqBuf = new byte[terminalCount * FormatSpec.FREQUENCY_AND_FLAGS_SIZE];
- final byte[] terminalAddressTableBuf =
- new byte[terminalCount * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE];
- for (final PtNodeArray nodeArray : flatNodes) {
- for (final PtNode ptNode : nodeArray.mData) {
- if (ptNode.isTerminal()) {
- BinaryDictEncoderUtils.writeUIntToBuffer(freqBuf,
- ptNode.mTerminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE,
- ptNode.mFrequency, FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
- BinaryDictEncoderUtils.writeUIntToBuffer(terminalAddressTableBuf,
- ptNode.mTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE,
- ptNode.mCachedAddressAfterUpdate + mHeaderSize,
- FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
- }
- }
- }
- mFreqOutStream.write(freqBuf);
- mTerminalAddressTableOutStream.write(terminalAddressTableBuf);
- }
-
- private void initUnigramTimestamps(final int terminalCount) throws IOException {
- // Initial value of time stamps for each word is 0.
- final byte[] unigramTimestampBuf =
- new byte[terminalCount * FormatSpec.UNIGRAM_TIMESTAMP_SIZE];
- mUnigramTimestampOutStream.write(unigramTimestampBuf);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java
deleted file mode 100644
index 3d8f186ba..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * An implementation of DictUpdater for version 4 binary dictionary.
- */
-@UsedForTesting
-public class Ver4DictUpdater extends Ver4DictDecoder implements DictUpdater {
-
- @UsedForTesting
- public Ver4DictUpdater(final File dictDirectory, final int factoryType) {
- // DictUpdater must have an updatable DictBuffer.
- super(dictDirectory, ((factoryType & MASK_DICTBUFFER) == USE_BYTEARRAY)
- ? USE_BYTEARRAY : USE_WRITABLE_BYTEBUFFER);
- }
-
- @Override
- public void deleteWord(final String word) throws IOException, UnsupportedFormatException {
- if (mDictBuffer == null) openDictBuffer();
- readHeader();
- final int wordPos = getTerminalPosition(word);
- if (wordPos != FormatSpec.NOT_VALID_WORD) {
- mDictBuffer.position(wordPos);
- final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
- mDictBuffer.position(wordPos);
- mDictBuffer.put((byte) DynamicBinaryDictIOUtils.markAsDeleted(flags));
- }
- }
-
- @Override
- public void insertWord(final String word, final int frequency,
- final ArrayList<WeightedString> bigramStrings, final ArrayList<WeightedString> shortcuts,
- final boolean isNotAWord, final boolean isBlackListEntry)
- throws IOException, UnsupportedFormatException {
- // TODO: Implement this method.
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/WeightedString.java b/java/src/com/android/inputmethod/latin/makedict/WeightedString.java
new file mode 100644
index 000000000..f6782df9e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/WeightedString.java
@@ -0,0 +1,62 @@
+/*
+ * 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.makedict;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.Arrays;
+
+/**
+ * A string with a probability.
+ *
+ * This represents an "attribute", that is either a bigram or a shortcut.
+ */
+public final class WeightedString {
+ public final String mWord;
+ public ProbabilityInfo mProbabilityInfo;
+
+ public WeightedString(final String word, final int probability) {
+ this(word, new ProbabilityInfo(probability));
+ }
+
+ public WeightedString(final String word, final ProbabilityInfo probabilityInfo) {
+ mWord = word;
+ mProbabilityInfo = probabilityInfo;
+ }
+
+ @UsedForTesting
+ public int getProbability() {
+ return mProbabilityInfo.mProbability;
+ }
+
+ public void setProbability(final int probability) {
+ mProbabilityInfo = new ProbabilityInfo(probability);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(new Object[] { mWord, mProbabilityInfo});
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof WeightedString)) return false;
+ final WeightedString w = (WeightedString)o;
+ return mWord.equals(w.mWord) && mProbabilityInfo.equals(w.mProbabilityInfo);
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/makedict/Word.java b/java/src/com/android/inputmethod/latin/makedict/Word.java
deleted file mode 100644
index 0eabb7bf3..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/Word.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2011 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.makedict;
-
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * Utility class for a word with a frequency.
- *
- * This is chiefly used to iterate a dictionary.
- */
-public final class Word implements Comparable<Word> {
- public final String mWord;
- public final int mFrequency;
- public final ArrayList<WeightedString> mShortcutTargets;
- public final ArrayList<WeightedString> mBigrams;
- public final boolean mIsNotAWord;
- public final boolean mIsBlacklistEntry;
-
- private int mHashCode = 0;
-
- public Word(final String word, final int frequency,
- final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams,
- final boolean isNotAWord, final boolean isBlacklistEntry) {
- mWord = word;
- mFrequency = frequency;
- mShortcutTargets = shortcutTargets;
- mBigrams = bigrams;
- mIsNotAWord = isNotAWord;
- mIsBlacklistEntry = isBlacklistEntry;
- }
-
- private static int computeHashCode(Word word) {
- return Arrays.hashCode(new Object[] {
- word.mWord,
- word.mFrequency,
- word.mShortcutTargets.hashCode(),
- word.mBigrams.hashCode(),
- word.mIsNotAWord,
- word.mIsBlacklistEntry
- });
- }
-
- /**
- * Three-way comparison.
- *
- * A Word x is greater than a word y if x has a higher frequency. If they have the same
- * frequency, they are sorted in lexicographic order.
- */
- @Override
- public int compareTo(Word w) {
- if (mFrequency < w.mFrequency) return 1;
- if (mFrequency > w.mFrequency) return -1;
- return mWord.compareTo(w.mWord);
- }
-
- /**
- * Equality test.
- *
- * Words are equal if they have the same frequency, the same spellings, and the same
- * attributes.
- */
- @Override
- public boolean equals(Object o) {
- if (o == this) return true;
- if (!(o instanceof Word)) return false;
- Word w = (Word)o;
- return mFrequency == w.mFrequency && mWord.equals(w.mWord)
- && mShortcutTargets.equals(w.mShortcutTargets)
- && mBigrams.equals(w.mBigrams)
- && mIsNotAWord == w.mIsNotAWord
- && mIsBlacklistEntry == w.mIsBlacklistEntry;
- }
-
- @Override
- public int hashCode() {
- if (mHashCode == 0) {
- mHashCode = computeHashCode(this);
- }
- return mHashCode;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
new file mode 100644
index 000000000..853392200
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2011 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.makedict;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CombinedFormatUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Utility class for a word with a probability.
+ *
+ * This is chiefly used to iterate a dictionary.
+ */
+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 boolean mIsNotAWord;
+ public final boolean mIsBlacklistEntry;
+ public final boolean mHasShortcuts;
+ public final boolean mHasBigrams;
+
+ private int mHashCode = 0;
+
+ @UsedForTesting
+ public WordProperty(final String word, final ProbabilityInfo probabilityInfo,
+ final ArrayList<WeightedString> shortcutTargets,
+ final ArrayList<WeightedString> bigrams,
+ final boolean isNotAWord, final boolean isBlacklistEntry) {
+ mWord = word;
+ mProbabilityInfo = probabilityInfo;
+ mShortcutTargets = shortcutTargets;
+ mBigrams = bigrams;
+ mIsNotAWord = isNotAWord;
+ mIsBlacklistEntry = isBlacklistEntry;
+ mHasBigrams = bigrams != null && !bigrams.isEmpty();
+ mHasShortcuts = shortcutTargets != null && !shortcutTargets.isEmpty();
+ }
+
+ private static ProbabilityInfo createProbabilityInfoFromArray(final int[] probabilityInfo) {
+ return new ProbabilityInfo(
+ probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_PROBABILITY_INDEX],
+ probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX],
+ probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_LEVEL_INDEX],
+ probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_COUNT_INDEX]);
+ }
+
+ // Construct word property using information from native code.
+ // This represents invalid word when the probability is BinaryDictionary.NOT_A_PROBABILITY.
+ public WordProperty(final int[] codePoints, final boolean isNotAWord,
+ final boolean isBlacklisted, final boolean hasBigram,
+ final boolean hasShortcuts, final int[] probabilityInfo,
+ final ArrayList<int[]> bigramTargets, final ArrayList<int[]> bigramProbabilityInfo,
+ final ArrayList<int[]> shortcutTargets,
+ final ArrayList<Integer> shortcutProbabilities) {
+ mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
+ mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo);
+ mShortcutTargets = CollectionUtils.newArrayList();
+ mBigrams = CollectionUtils.newArrayList();
+ mIsNotAWord = isNotAWord;
+ mIsBlacklistEntry = isBlacklisted;
+ mHasShortcuts = hasShortcuts;
+ mHasBigrams = hasBigram;
+
+ final int bigramTargetCount = bigramTargets.size();
+ for (int i = 0; i < bigramTargetCount; i++) {
+ final String bigramTargetString =
+ StringUtils.getStringFromNullTerminatedCodePointArray(bigramTargets.get(i));
+ mBigrams.add(new WeightedString(bigramTargetString,
+ createProbabilityInfoFromArray(bigramProbabilityInfo.get(i))));
+ }
+
+ final int shortcutTargetCount = shortcutTargets.size();
+ for (int i = 0; i < shortcutTargetCount; i++) {
+ final String shortcutTargetString =
+ StringUtils.getStringFromNullTerminatedCodePointArray(shortcutTargets.get(i));
+ mShortcutTargets.add(
+ new WeightedString(shortcutTargetString, shortcutProbabilities.get(i)));
+ }
+ }
+
+ public int getProbability() {
+ return mProbabilityInfo.mProbability;
+ }
+
+ private static int computeHashCode(WordProperty word) {
+ return Arrays.hashCode(new Object[] {
+ word.mWord,
+ word.mProbabilityInfo,
+ word.mShortcutTargets.hashCode(),
+ word.mBigrams.hashCode(),
+ word.mIsNotAWord,
+ word.mIsBlacklistEntry
+ });
+ }
+
+ /**
+ * Three-way comparison.
+ *
+ * A Word x is greater than a word y if x has a higher frequency. If they have the same
+ * frequency, they are sorted in lexicographic order.
+ */
+ @Override
+ public int compareTo(final WordProperty w) {
+ if (getProbability() < w.getProbability()) return 1;
+ if (getProbability() > w.getProbability()) return -1;
+ return mWord.compareTo(w.mWord);
+ }
+
+ /**
+ * Equality test.
+ *
+ * Words are equal if they have the same frequency, the same spellings, and the same
+ * attributes.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ 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)
+ && mIsNotAWord == w.mIsNotAWord && mIsBlacklistEntry == w.mIsBlacklistEntry
+ && mHasBigrams == w.mHasBigrams && mHasShortcuts && w.mHasBigrams;
+ }
+
+ @Override
+ public int hashCode() {
+ if (mHashCode == 0) {
+ mHashCode = computeHashCode(this);
+ }
+ return mHashCode;
+ }
+
+ @UsedForTesting
+ public boolean isValid() {
+ return getProbability() != BinaryDictionary.NOT_A_PROBABILITY;
+ }
+
+ @Override
+ public String toString() {
+ return CombinedFormatUtils.formatWordProperty(this);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 1de15a333..712e314a8 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -17,25 +17,17 @@
package com.android.inputmethod.latin.personalization;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Log;
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.LatinImeLogger;
-import com.android.inputmethod.latin.makedict.DictDecoder;
-import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.settings.Settings;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
-import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.utils.LanguageModelParam;
import java.io.File;
-import java.io.IOException;
import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
/**
@@ -44,9 +36,7 @@ import java.util.Map;
*/
public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableBinaryDictionary {
private static final String TAG = DecayingExpandableBinaryDictionaryBase.class.getSimpleName();
- public static final boolean DBG_SAVE_RESTORE = false;
- private static final boolean DBG_STRESS_TEST = false;
- private static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
+ private static final boolean DBG_DUMP_ON_CLOSE = false;
/** Any pair being typed or picked */
public static final int FREQUENCY_FOR_TYPED = 2;
@@ -54,63 +44,63 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
public static final int FREQUENCY_FOR_WORDS_IN_DICTS = FREQUENCY_FOR_TYPED;
public static final int FREQUENCY_FOR_WORDS_NOT_IN_DICTS = Dictionary.NOT_A_PROBABILITY;
- /** Locale for which this user history dictionary is storing words */
- private final String mLocale;
+ /** The locale for this dictionary. */
+ public final Locale mLocale;
- private final String mFileName;
+ private Map<String, String> mAdditionalAttributeMap = null;
- private final SharedPreferences mPrefs;
-
- private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
- CollectionUtils.newArrayList();
-
- // Should always be false except when we use this class for test
- @UsedForTesting boolean mIsTest = false;
-
- /* package */ DecayingExpandableBinaryDictionaryBase(final Context context,
- final String locale, final SharedPreferences sp, final String dictionaryType,
- final String fileName) {
- super(context, fileName, dictionaryType, true);
+ protected DecayingExpandableBinaryDictionaryBase(final Context context,
+ final String dictName, final Locale locale, final String dictionaryType,
+ final File dictFile) {
+ super(context, dictName, locale, dictionaryType, dictFile);
mLocale = locale;
- mFileName = fileName;
- mPrefs = sp;
- if (mLocale != null && mLocale.length() > 1) {
- asyncLoadDictionaryToMemory();
+ if (mLocale != null && mLocale.toString().length() > 1) {
reloadDictionaryIfRequired();
}
}
@Override
public void close() {
- if (!ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- closeBinaryDictionary();
+ if (DBG_DUMP_ON_CLOSE) {
+ dumpAllWordsForDebug();
}
// Flush pending writes.
- // TODO: Remove after this class become to use a dynamic binary dictionary.
- asyncFlashAllBinaryDictionary();
- Settings.writeLastUserHistoryWriteTime(mPrefs, mLocale);
+ flush();
+ super.close();
+ }
+
+ public void flush() {
+ asyncFlushBinaryDictionary();
}
@Override
protected Map<String, String> getHeaderAttributeMap() {
- HashMap<String, String> attributeMap = new HashMap<String, String>();
- attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE,
- FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
- attributeMap.put(FormatSpec.FileHeader.USES_FORGETTING_CURVE_ATTRIBUTE,
- FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
- attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mFileName);
- attributeMap.put(FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, mLocale);
+ final Map<String, String> attributeMap = super.getHeaderAttributeMap();
+ if (mAdditionalAttributeMap != null) {
+ attributeMap.putAll(mAdditionalAttributeMap);
+ }
+ attributeMap.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY,
+ DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
+ attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY,
+ DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
return attributeMap;
}
@Override
- protected boolean hasContentChanged() {
+ protected boolean haveContentsChanged() {
return false;
}
- @Override
- protected boolean needsToReloadBeforeWriting() {
- return false;
+ public void addMultipleDictionaryEntriesToDictionary(
+ final ArrayList<LanguageModelParam> languageModelParams,
+ final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
+ if (languageModelParams == null || languageModelParams.isEmpty()) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
}
/**
@@ -121,104 +111,28 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
* context, as in beginning of a sentence for example.
* The second word may not be null (a NullPointerException would be thrown).
*/
- public void addToDictionary(final String word0, final String word1, final boolean isValid) {
+ public void addToDictionary(final String word0, final String word1, final boolean isValid,
+ final int timestamp) {
if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
(word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
return;
}
- final int frequency = ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ?
- (isValid ? FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS) :
- FREQUENCY_FOR_TYPED;
- addWordDynamically(word1, null /* shortcutTarget */, frequency, 0 /* shortcutFreq */,
- false /* isNotAWord */);
+ final int frequency = isValid ?
+ FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
+ addWordDynamically(word1, frequency, null /* shortcutTarget */, 0 /* shortcutFreq */,
+ false /* isNotAWord */, false /* isBlacklisted */, timestamp);
// Do not insert a word as a bigram of itself
if (word1.equals(word0)) {
return;
}
if (null != word0) {
- addBigramDynamically(word0, word1, frequency, isValid);
+ addBigramDynamically(word0, word1, frequency, timestamp);
}
}
- public void cancelAddingUserHistory(final String word0, final String word1) {
- removeBigramDynamically(word0, word1);
- }
-
@Override
- protected void loadDictionaryAsync() {
- final int[] profTotalCount = { 0 };
- final String locale = getLocale();
- if (DBG_STRESS_TEST) {
- try {
- Log.w(TAG, "Start stress in loading: " + locale);
- Thread.sleep(15000);
- Log.w(TAG, "End stress in loading");
- } catch (InterruptedException e) {
- }
- }
- final long last = Settings.readLastUserHistoryWriteTime(mPrefs, locale);
- final long now = System.currentTimeMillis();
- final ExpandableBinaryDictionary dictionary = this;
- final OnAddWordListener listener = new OnAddWordListener() {
- @Override
- public void setUnigram(final String word, final String shortcutTarget,
- final int frequency, final int shortcutFreq) {
- if (DBG_SAVE_RESTORE) {
- Log.d(TAG, "load unigram: " + word + "," + frequency);
- }
- addWord(word, shortcutTarget, frequency, shortcutFreq, false /* isNotAWord */);
- ++profTotalCount[0];
- }
-
- @Override
- public void setBigram(final String word0, final String word1, final int frequency) {
- if (word0.length() < Constants.DICTIONARY_MAX_WORD_LENGTH
- && word1.length() < Constants.DICTIONARY_MAX_WORD_LENGTH) {
- if (DBG_SAVE_RESTORE) {
- Log.d(TAG, "load bigram: " + word0 + "," + word1 + "," + frequency);
- }
- ++profTotalCount[0];
- addBigram(word0, word1, frequency, last);
- }
- }
- };
-
- // Load the dictionary from binary file
- final File dictFile = new File(mContext.getFilesDir(), mFileName);
- final DictDecoder dictDecoder = FormatSpec.getDictDecoder(dictFile,
- DictDecoder.USE_BYTEARRAY);
- if (dictDecoder == null) {
- // This is an expected condition: we don't have a user history dictionary for this
- // language yet. It will be created sometime later.
- return;
- }
-
- try {
- dictDecoder.openDictBuffer();
- UserHistoryDictIOUtils.readDictionaryBinary(dictDecoder, listener);
- } catch (IOException e) {
- Log.d(TAG, "IOException on opening a bytebuffer", e);
- } finally {
- if (PROFILE_SAVE_RESTORE) {
- final long diff = System.currentTimeMillis() - now;
- Log.d(TAG, "PROF: Load UserHistoryDictionary: "
- + locale + ", " + diff + "ms. load " + profTotalCount[0] + "entries.");
- }
- }
- }
-
- protected String getLocale() {
- return mLocale;
- }
-
- public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
- session.setPredictionDictionary(this);
- mSessions.add(session);
- session.onDictionaryReady();
- }
-
- public void unRegisterUpdateSession(PersonalizationDictionaryUpdateSession session) {
- mSessions.remove(session);
+ protected void loadInitialContentsLocked() {
+ // No initial contents.
}
@UsedForTesting
@@ -226,10 +140,17 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
// Clear the node structure on memory
clear();
// Then flush the cleared state of the dictionary on disk.
- asyncFlashAllBinaryDictionary();
+ asyncFlushBinaryDictionary();
+ }
+
+ @UsedForTesting
+ public void clearAndFlushDictionaryWithAdditionalAttributes(
+ final Map<String, String> attributeMap) {
+ mAdditionalAttributeMap = attributeMap;
+ clearAndFlushDictionary();
}
- /* package */ void decayIfNeeded() {
+ /* package */ void runGCIfRequired() {
runGCIfRequired(false /* mindsBlockByGC */);
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
index e9ca662e7..de2744f29 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
@@ -43,7 +43,7 @@ public class DictionaryDecayBroadcastReciever extends BroadcastReceiver {
/**
* Interval to update for decaying dictionaries.
*/
- private static final long DICTIONARY_DECAY_INTERVAL = TimeUnit.MINUTES.toMillis(60);
+ /* package */ static final long DICTIONARY_DECAY_INTERVAL = TimeUnit.MINUTES.toMillis(60);
public static void setUpIntervalAlarmForDictionaryDecaying(Context context) {
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
@@ -60,7 +60,7 @@ public class DictionaryDecayBroadcastReciever extends BroadcastReceiver {
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
if (action.equals(DICTIONARY_DECAY_INTENT_ACTION)) {
- PersonalizationHelper.tryDecayingAllOpeningUserHistoryDictionary();
+ PersonalizationHelper.runGCOnAllOpenedUserHistoryDictionaries();
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
deleted file mode 100644
index 6f152bb91..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.personalization;
-
-import android.content.Context;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.compat.ActivityManagerCompatUtils;
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.AbstractDictionaryWriter;
-import com.android.inputmethod.latin.ExpandableDictionary;
-import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.ExpandableDictionary.NextWord;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.makedict.DictEncoder;
-import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
-import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Map;
-
-// Currently this class is used to implement dynamic prodiction dictionary.
-// TODO: Move to native code.
-public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWriter {
- private static final String TAG = DynamicPersonalizationDictionaryWriter.class.getSimpleName();
- /** Maximum number of pairs. Pruning will start when databases goes above this number. */
- public static final int DEFAULT_MAX_HISTORY_BIGRAMS = 10000;
- public static final int LOW_MEMORY_MAX_HISTORY_BIGRAMS = 2000;
-
- /** Any pair being typed or picked */
- private static final int FREQUENCY_FOR_TYPED = 2;
-
- private static final int BINARY_DICT_VERSION = 3;
- private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
- new FormatSpec.FormatOptions(BINARY_DICT_VERSION, true /* supportsDynamicUpdate */);
-
- private final UserHistoryDictionaryBigramList mBigramList =
- new UserHistoryDictionaryBigramList();
- private final ExpandableDictionary mExpandableDictionary;
- private final int mMaxHistoryBigrams;
-
- public DynamicPersonalizationDictionaryWriter(final Context context, final String dictType) {
- super(context, dictType);
- mExpandableDictionary = new ExpandableDictionary(dictType);
- final boolean isLowRamDevice = ActivityManagerCompatUtils.isLowRamDevice(context);
- mMaxHistoryBigrams = isLowRamDevice ?
- LOW_MEMORY_MAX_HISTORY_BIGRAMS : DEFAULT_MAX_HISTORY_BIGRAMS;
- }
-
- @Override
- public void clear() {
- mBigramList.evictAll();
- mExpandableDictionary.clearDictionary();
- }
-
- /**
- * Adds a word unigram to the fusion dictionary. Call updateBinaryDictionary when all changes
- * are done to update the binary dictionary.
- * @param word The word to add.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this unigram.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
- * if shortcutTarget is null.
- * @param isNotAWord true if this is not a word, i.e. shortcut only.
- */
- @Override
- public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
- final int shortcutFreq, final boolean isNotAWord) {
- if (mBigramList.size() > mMaxHistoryBigrams * 2) {
- // Too many entries: just stop adding new vocabulary and wait next refresh.
- return;
- }
- mExpandableDictionary.addWord(word, shortcutTarget, frequency, shortcutFreq);
- mBigramList.addBigram(null, word, (byte)frequency);
- }
-
- @Override
- public void addBigramWords(final String word0, final String word1, final int frequency,
- final boolean isValid, final long lastModifiedTime) {
- if (mBigramList.size() > mMaxHistoryBigrams * 2) {
- // Too many entries: just stop adding new vocabulary and wait next refresh.
- return;
- }
- if (lastModifiedTime > 0) {
- mExpandableDictionary.setBigramAndGetFrequency(word0, word1,
- new ForgettingCurveParams(frequency, System.currentTimeMillis(),
- lastModifiedTime));
- mBigramList.addBigram(word0, word1, (byte)frequency);
- } else {
- mExpandableDictionary.setBigramAndGetFrequency(word0, word1,
- new ForgettingCurveParams(isValid));
- mBigramList.addBigram(word0, word1, (byte)frequency);
- }
- }
-
- @Override
- public void removeBigramWords(final String word0, final String word1) {
- if (mBigramList.removeBigram(word0, word1)) {
- mExpandableDictionary.removeBigram(word0, word1);
- }
- }
-
- @Override
- protected void writeDictionary(final DictEncoder dictEncoder,
- final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException {
- UserHistoryDictIOUtils.writeDictionary(dictEncoder,
- new FrequencyProvider(mBigramList, mExpandableDictionary, mMaxHistoryBigrams),
- mBigramList, FORMAT_OPTIONS);
- }
-
- private static class FrequencyProvider implements BigramDictionaryInterface {
- private final UserHistoryDictionaryBigramList mBigramList;
- private final ExpandableDictionary mExpandableDictionary;
- private final int mMaxHistoryBigrams;
-
- public FrequencyProvider(final UserHistoryDictionaryBigramList bigramList,
- final ExpandableDictionary expandableDictionary, final int maxHistoryBigrams) {
- mBigramList = bigramList;
- mExpandableDictionary = expandableDictionary;
- mMaxHistoryBigrams = maxHistoryBigrams;
- }
-
- @Override
- public int getFrequency(final String word0, final String word1) {
- final int freq;
- if (word0 == null) { // unigram
- freq = FREQUENCY_FOR_TYPED;
- } else { // bigram
- final NextWord nw = mExpandableDictionary.getBigramWord(word0, word1);
- if (nw != null) {
- final ForgettingCurveParams forgettingCurveParams = nw.getFcParams();
- final byte prevFc = mBigramList.getBigrams(word0).get(word1);
- final byte fc = forgettingCurveParams.getFc();
- final boolean isValid = forgettingCurveParams.isValid();
- if (prevFc > 0 && prevFc == fc) {
- freq = fc & 0xFF;
- } else if (UserHistoryForgettingCurveUtils.
- needsToSave(fc, isValid, mBigramList.size() <= mMaxHistoryBigrams)) {
- freq = fc & 0xFF;
- } else {
- // Delete this entry
- freq = -1;
- }
- } else {
- // Delete this entry
- freq = -1;
- }
- }
- return freq;
- }
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- return mExpandableDictionary.getSuggestions(composer, prevWord, proximityInfo,
- blockOffensiveWords, additionalFeaturesOptions);
- }
-
- @Override
- public boolean isValidWord(final String word) {
- return mExpandableDictionary.isValidWord(word);
- }
-
- @UsedForTesting
- public boolean isInBigramListForTests(final String word) {
- // TODO: Use native method to determine whether the word is in dictionary or not
- return mBigramList.containsKey(word) || mBigramList.getBigrams(null).containsKey(word);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
index f257165cb..4afd5b4c9 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -16,58 +16,29 @@
package com.android.inputmethod.latin.personalization;
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableBinaryDictionary;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
import android.content.Context;
-import android.content.SharedPreferences;
-import java.util.ArrayList;
+import com.android.inputmethod.latin.Dictionary;
-/**
- * This class is a dictionary for the personalized language model that uses binary dictionary.
- */
-public class PersonalizationDictionary extends ExpandableBinaryDictionary {
- private static final String NAME = "personalization";
- private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
- CollectionUtils.newArrayList();
+import java.io.File;
+import java.util.Locale;
- /** Locale for which this user history dictionary is storing words */
- private final String mLocale;
+public class PersonalizationDictionary extends DecayingExpandableBinaryDictionaryBase {
+ /* package */ static final String NAME = PersonalizationDictionary.class.getSimpleName();
- public PersonalizationDictionary(final Context context, final String locale,
- final SharedPreferences prefs) {
- // TODO: Make isUpdatable true.
- super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_PERSONALIZATION,
- false /* isUpdatable */);
- mLocale = locale;
- // TODO: Restore last updated time
- loadDictionary();
+ /* package */ PersonalizationDictionary(final Context context, final Locale locale) {
+ this(context, locale, null /* dictFile */);
}
- @Override
- protected void loadDictionaryAsync() {
- // TODO: Implement
+ public PersonalizationDictionary(final Context context, final Locale locale,
+ final File dictFile) {
+ super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_PERSONALIZATION,
+ dictFile);
}
@Override
- protected boolean hasContentChanged() {
+ public boolean isValidWord(final String word) {
+ // Strings out of this dictionary should not be considered existing words.
return false;
}
-
- @Override
- protected boolean needsToReloadBeforeWriting() {
- return false;
- }
-
- public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
- session.setDictionary(this);
- mSessions.add(session);
- session.onDictionaryReady();
- }
-
- public void unRegisterUpdateSession(PersonalizationDictionaryUpdateSession session) {
- mSessions.remove(session);
- }
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
index c1833ff14..d6c0dc0dc 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
@@ -19,19 +19,26 @@ package com.android.inputmethod.latin.personalization;
import android.content.Context;
import android.content.res.Configuration;
-public class PersonalizationDictionarySessionRegister {
- public static void init(Context context) {
+import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
+
+public class PersonalizationDictionarySessionRegistrar {
+ public static void init(final Context context,
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
+ }
+
+ public static void onConfigurationChanged(final Context context, final Configuration conf,
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
}
- public static void onConfigurationChanged(final Context context, final Configuration conf) {
+ public static void onUpdateData(final Context context, final String type) {
}
- public static void onUpdateData(Context context, String type) {
+ public static void onRemoveData(final Context context, final String type) {
}
- public static void onRemoveData(Context context, String type) {
+ public static void resetAll(final Context context) {
}
- public static void onDestroy(Context context) {
+ public static void close(final Context context) {
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
deleted file mode 100644
index a86f6e584..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.personalization;
-
-import android.content.Context;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-/**
- * This class is a session where a data provider can communicate with a personalization
- * dictionary.
- */
-public abstract class PersonalizationDictionaryUpdateSession {
- /**
- * This class is a parameter for a new unigram or bigram word which will be added
- * to the personalization dictionary.
- */
- public static class PersonalizationLanguageModelParam {
- public final String mWord0;
- public final String mWord1;
- public final boolean mIsValid;
- public final int mFrequency;
- public PersonalizationLanguageModelParam(String word0, String word1, boolean isValid,
- int frequency) {
- mWord0 = word0;
- mWord1 = word1;
- mIsValid = isValid;
- mFrequency = frequency;
- }
- }
-
- // TODO: Use a dynamic binary dictionary instead
- public WeakReference<PersonalizationDictionary> mDictionary;
- public WeakReference<DecayingExpandableBinaryDictionaryBase> mPredictionDictionary;
- public final String mSystemLocale;
- public PersonalizationDictionaryUpdateSession(String locale) {
- mSystemLocale = locale;
- }
-
- public abstract void onDictionaryReady();
-
- public abstract void onDictionaryClosed(Context context);
-
- public void setDictionary(PersonalizationDictionary dictionary) {
- mDictionary = new WeakReference<PersonalizationDictionary>(dictionary);
- }
-
- public void setPredictionDictionary(DecayingExpandableBinaryDictionaryBase dictionary) {
- mPredictionDictionary =
- new WeakReference<DecayingExpandableBinaryDictionaryBase>(dictionary);
- }
-
- protected PersonalizationDictionary getDictionary() {
- return mDictionary == null ? null : mDictionary.get();
- }
-
- protected DecayingExpandableBinaryDictionaryBase getPredictionDictionary() {
- return mPredictionDictionary == null ? null : mPredictionDictionary.get();
- }
-
- private void unsetDictionary() {
- final PersonalizationDictionary dictionary = getDictionary();
- if (dictionary == null) {
- return;
- }
- dictionary.unRegisterUpdateSession(this);
- }
-
- private void unsetPredictionDictionary() {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
- if (dictionary == null) {
- return;
- }
- dictionary.unRegisterUpdateSession(this);
- }
-
- public void clearAndFlushPredictionDictionary(Context context) {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
- if (dictionary == null) {
- return;
- }
- dictionary.clearAndFlushDictionary();
- }
-
- public void closeSession(Context context) {
- unsetDictionary();
- unsetPredictionDictionary();
- onDictionaryClosed(context);
- }
-
- // TODO: Support multi locale to add bigram
- public void addBigramToPersonalizationDictionary(String word0, String word1, boolean isValid,
- int frequency) {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
- if (dictionary == null) {
- return;
- }
- dictionary.addToDictionary(word0, word1, isValid);
- }
-
- // Bulk import
- // TODO: Support multi locale to add bigram
- public void addBigramsToPersonalizationDictionary(
- final ArrayList<PersonalizationLanguageModelParam> lmParams) {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
- if (dictionary == null) {
- return;
- }
- for (final PersonalizationLanguageModelParam lmParam : lmParams) {
- dictionary.addToDictionary(lmParam.mWord0, lmParam.mWord1, lmParam.mIsValid);
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 221ddeeba..385b525b6 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -16,36 +16,35 @@
package com.android.inputmethod.latin.personalization;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.FileUtils;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
import android.util.Log;
+import java.io.File;
+import java.io.FilenameFilter;
import java.lang.ref.SoftReference;
+import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
public class PersonalizationHelper {
private static final String TAG = PersonalizationHelper.class.getSimpleName();
private static final boolean DEBUG = false;
private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
-
private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>>
sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
- private static final ConcurrentHashMap<String,
- SoftReference<PersonalizationPredictionDictionary>>
- sLangPersonalizationPredictionDictCache =
- CollectionUtils.newConcurrentHashMap();
-
public static UserHistoryDictionary getUserHistoryDictionary(
- final Context context, final String locale, final SharedPreferences sp) {
+ final Context context, final Locale locale) {
+ final String localeStr = locale.toString();
synchronized (sLangUserHistoryDictCache) {
- if (sLangUserHistoryDictCache.containsKey(locale)) {
+ if (sLangUserHistoryDictCache.containsKey(localeStr)) {
final SoftReference<UserHistoryDictionary> ref =
- sLangUserHistoryDictCache.get(locale);
+ sLangUserHistoryDictCache.get(localeStr);
final UserHistoryDictionary dict = ref == null ? null : ref.get();
if (dict != null) {
if (DEBUG) {
@@ -55,77 +54,111 @@ public class PersonalizationHelper {
return dict;
}
}
- final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale, sp);
- sLangUserHistoryDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict));
+ final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale);
+ sLangUserHistoryDictCache.put(localeStr,
+ new SoftReference<UserHistoryDictionary>(dict));
return dict;
}
}
- public static void tryDecayingAllOpeningUserHistoryDictionary() {
- for (final ConcurrentHashMap.Entry<String, SoftReference<UserHistoryDictionary>> entry
- : sLangUserHistoryDictCache.entrySet()) {
- if (entry.getValue() != null) {
- final UserHistoryDictionary dict = entry.getValue().get();
- if (dict != null) {
- dict.decayIfNeeded();
- }
- }
+ private static int sCurrentTimestampForTesting = 0;
+ public static void currentTimeChangedForTesting(final int currentTimestamp) {
+ if (TimeUnit.MILLISECONDS.toSeconds(
+ DictionaryDecayBroadcastReciever.DICTIONARY_DECAY_INTERVAL)
+ < currentTimestamp - sCurrentTimestampForTesting) {
+ // TODO: Run GC for both PersonalizationDictionary and UserHistoryDictionary.
+ runGCOnAllOpenedUserHistoryDictionaries();
}
}
- public static void registerPersonalizationDictionaryUpdateSession(final Context context,
- final PersonalizationDictionaryUpdateSession session, String locale) {
- final PersonalizationPredictionDictionary predictionDictionary =
- getPersonalizationPredictionDictionary(context, locale,
- PreferenceManager.getDefaultSharedPreferences(context));
- predictionDictionary.registerUpdateSession(session);
- final PersonalizationDictionary dictionary =
- getPersonalizationDictionary(context, locale,
- PreferenceManager.getDefaultSharedPreferences(context));
- dictionary.registerUpdateSession(session);
+ public static void runGCOnAllOpenedUserHistoryDictionaries() {
+ runGCOnAllDictionariesIfRequired(sLangUserHistoryDictCache);
+ }
+
+ @UsedForTesting
+ public static void runGCOnAllOpenedPersonalizationDictionaries() {
+ runGCOnAllDictionariesIfRequired(sLangPersonalizationDictCache);
+ }
+
+ private static <T extends DecayingExpandableBinaryDictionaryBase>
+ void runGCOnAllDictionariesIfRequired(
+ final ConcurrentHashMap<String, SoftReference<T>> dictionaryMap) {
+ for (final ConcurrentHashMap.Entry<String, SoftReference<T>> entry
+ : dictionaryMap.entrySet()) {
+ final DecayingExpandableBinaryDictionaryBase dict = entry.getValue().get();
+ if (dict != null) {
+ dict.runGCIfRequired();
+ } else {
+ dictionaryMap.remove(entry.getKey());
+ }
+ }
}
public static PersonalizationDictionary getPersonalizationDictionary(
- final Context context, final String locale, final SharedPreferences sp) {
+ final Context context, final Locale locale) {
+ final String localeStr = locale.toString();
synchronized (sLangPersonalizationDictCache) {
- if (sLangPersonalizationDictCache.containsKey(locale)) {
+ if (sLangPersonalizationDictCache.containsKey(localeStr)) {
final SoftReference<PersonalizationDictionary> ref =
- sLangPersonalizationDictCache.get(locale);
+ sLangPersonalizationDictCache.get(localeStr);
final PersonalizationDictionary dict = ref == null ? null : ref.get();
if (dict != null) {
if (DEBUG) {
- Log.w(TAG, "Use cached PersonalizationDictCache for " + locale);
+ Log.w(TAG, "Use cached PersonalizationDictionary for " + locale);
}
return dict;
}
}
- final PersonalizationDictionary dict =
- new PersonalizationDictionary(context, locale, sp);
+ final PersonalizationDictionary dict = new PersonalizationDictionary(context, locale);
sLangPersonalizationDictCache.put(
- locale, new SoftReference<PersonalizationDictionary>(dict));
+ localeStr, new SoftReference<PersonalizationDictionary>(dict));
return dict;
}
}
- public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
- final Context context, final String locale, final SharedPreferences sp) {
- synchronized (sLangPersonalizationPredictionDictCache) {
- if (sLangPersonalizationPredictionDictCache.containsKey(locale)) {
- final SoftReference<PersonalizationPredictionDictionary> ref =
- sLangPersonalizationPredictionDictCache.get(locale);
- final PersonalizationPredictionDictionary dict = ref == null ? null : ref.get();
- if (dict != null) {
- if (DEBUG) {
- Log.w(TAG, "Use cached PersonalizationPredictionDictionary for " + locale);
+ public static void removeAllPersonalizationDictionaries(final Context context) {
+ removeAllDictionaries(context, sLangPersonalizationDictCache,
+ PersonalizationDictionary.NAME);
+ }
+
+ public static void removeAllUserHistoryDictionaries(final Context context) {
+ removeAllDictionaries(context, sLangUserHistoryDictCache,
+ UserHistoryDictionary.NAME);
+ }
+
+ private static <T extends DecayingExpandableBinaryDictionaryBase> void removeAllDictionaries(
+ final Context context, final ConcurrentHashMap<String, SoftReference<T>> dictionaryMap,
+ final String dictNamePrefix) {
+ synchronized (dictionaryMap) {
+ for (final ConcurrentHashMap.Entry<String, SoftReference<T>> entry
+ : dictionaryMap.entrySet()) {
+ if (entry.getValue() != null) {
+ final DecayingExpandableBinaryDictionaryBase dict = entry.getValue().get();
+ if (dict != null) {
+ dict.clearAndFlushDictionary();
}
- return dict;
}
}
- final PersonalizationPredictionDictionary dict =
- new PersonalizationPredictionDictionary(context, locale, sp);
- sLangPersonalizationPredictionDictCache.put(
- locale, new SoftReference<PersonalizationPredictionDictionary>(dict));
- return dict;
+ dictionaryMap.clear();
+ if (!FileUtils.deleteFilteredFiles(
+ context.getFilesDir(), new DictFilter(dictNamePrefix))) {
+ Log.e(TAG, "Cannot remove all existing dictionary files. filesDir: "
+ + context.getFilesDir().getAbsolutePath() + ", dictNamePrefix: "
+ + dictNamePrefix);
+ }
+ }
+ }
+
+ private static class DictFilter implements FilenameFilter {
+ private final String mName;
+
+ DictFilter(final String name) {
+ mName = name;
+ }
+
+ @Override
+ public boolean accept(final File dir, final String name) {
+ return name.startsWith(mName);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
deleted file mode 100644
index 432954453..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.personalization;
-
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableBinaryDictionary;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-public class PersonalizationPredictionDictionary extends DecayingExpandableBinaryDictionaryBase {
- private static final String NAME = PersonalizationPredictionDictionary.class.getSimpleName();
-
- /* package */ PersonalizationPredictionDictionary(final Context context, final String locale,
- final SharedPreferences sp) {
- super(context, locale, sp, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
- getDictionaryFileName(locale));
- }
-
- private static String getDictionaryFileName(final String locale) {
- return NAME + "." + locale + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index a60226d7e..504e9b2f3 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -16,25 +16,37 @@
package com.android.inputmethod.latin.personalization;
+import android.content.Context;
+
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableBinaryDictionary;
-import android.content.Context;
-import android.content.SharedPreferences;
+import java.io.File;
+import java.util.Locale;
/**
* Locally gathers stats about the words user types and various other signals like auto-correction
* cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
*/
public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBase {
- /* package for tests */ static final String NAME =
- UserHistoryDictionary.class.getSimpleName();
- /* package */ UserHistoryDictionary(final Context context, final String locale,
- final SharedPreferences sp) {
- super(context, locale, sp, Dictionary.TYPE_USER_HISTORY, getDictionaryFileName(locale));
+ /* package */ static final String NAME = UserHistoryDictionary.class.getSimpleName();
+
+ /* package */ UserHistoryDictionary(final Context context, final Locale locale) {
+ this(context, locale, null /* dictFile */);
+ }
+
+ public UserHistoryDictionary(final Context context, final Locale locale,
+ final File dictFile) {
+ super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_USER_HISTORY,
+ dictFile);
+ }
+
+ public void cancelAddingUserHistory(final String word0, final String word1) {
+ removeBigramDynamically(word0, word1);
}
- private static String getDictionaryFileName(final String locale) {
- return NAME + "." + locale + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
+ @Override
+ public boolean isValidWord(final String word) {
+ // Strings out of this dictionary should not be considered existing words.
+ return false;
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
deleted file mode 100644
index 55a90ee51..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2012 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.personalization;
-
-import android.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
-import java.util.HashMap;
-import java.util.Set;
-
-/**
- * A store of bigrams which will be updated when the user history dictionary is closed
- * All bigrams including stale ones in SQL DB should be stored in this class to avoid adding stale
- * bigrams when we write to the SQL DB.
- */
-@UsedForTesting
-public final class UserHistoryDictionaryBigramList {
- public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0;
- private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName();
- private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = CollectionUtils.newHashMap();
- private final HashMap<String, HashMap<String, Byte>> mBigramMap = CollectionUtils.newHashMap();
- private int mSize = 0;
-
- public void evictAll() {
- mSize = 0;
- mBigramMap.clear();
- }
-
- /**
- * Called when the user typed a word.
- */
- @UsedForTesting
- public void addBigram(String word1, String word2) {
- addBigram(word1, word2, FORGETTING_CURVE_INITIAL_VALUE);
- }
-
- /**
- * Called when loaded from the SQL DB.
- */
- public void addBigram(String word1, String word2, byte fcValue) {
- if (DecayingExpandableBinaryDictionaryBase.DBG_SAVE_RESTORE) {
- Log.d(TAG, "--- add bigram: " + word1 + ", " + word2 + ", " + fcValue);
- }
- final HashMap<String, Byte> map;
- if (mBigramMap.containsKey(word1)) {
- map = mBigramMap.get(word1);
- } else {
- map = CollectionUtils.newHashMap();
- mBigramMap.put(word1, map);
- }
- if (!map.containsKey(word2)) {
- ++mSize;
- map.put(word2, fcValue);
- }
- }
-
- /**
- * Called when inserted to the SQL DB.
- */
- public void updateBigram(String word1, String word2, byte fcValue) {
- if (DecayingExpandableBinaryDictionaryBase.DBG_SAVE_RESTORE) {
- Log.d(TAG, "--- update bigram: " + word1 + ", " + word2 + ", " + fcValue);
- }
- final HashMap<String, Byte> map;
- if (mBigramMap.containsKey(word1)) {
- map = mBigramMap.get(word1);
- } else {
- return;
- }
- if (!map.containsKey(word2)) {
- return;
- }
- map.put(word2, fcValue);
- }
-
- public int size() {
- return mSize;
- }
-
- public boolean isEmpty() {
- return mBigramMap.isEmpty();
- }
-
- public boolean containsKey(String word) {
- return mBigramMap.containsKey(word);
- }
-
- public Set<String> keySet() {
- return mBigramMap.keySet();
- }
-
- public HashMap<String, Byte> getBigrams(String word1) {
- if (mBigramMap.containsKey(word1)) return mBigramMap.get(word1);
- // TODO: lower case according to locale
- final String lowerWord1 = word1.toLowerCase();
- if (mBigramMap.containsKey(lowerWord1)) return mBigramMap.get(lowerWord1);
- return EMPTY_BIGRAM_MAP;
- }
-
- public boolean removeBigram(String word1, String word2) {
- final HashMap<String, Byte> set = getBigrams(word1);
- if (set.isEmpty()) {
- return false;
- }
- if (set.containsKey(word2)) {
- set.remove(word2);
- --mSize;
- return true;
- }
- return false;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
index 4bf524cbb..39977e76f 100644
--- a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
@@ -16,8 +16,6 @@
package com.android.inputmethod.latin.settings;
-import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.ASCII_CAPABLE;
-
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
@@ -44,10 +42,13 @@ import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.Toast;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.DialogUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
@@ -111,7 +112,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
subtype.getLocale(), subtype.hashCode(), subtype.hashCode(),
SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)));
}
- if (subtype.containsExtraValueKey(ASCII_CAPABLE)) {
+ if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) {
items.add(createItem(context, subtype.getLocale()));
}
}
@@ -287,7 +288,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
final KeyboardLayoutSetItem layout =
(KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem();
final InputMethodSubtype subtype = AdditionalSubtypeUtils.createAdditionalSubtype(
- locale.first, layout.first, ASCII_CAPABLE);
+ locale.first, layout.first, Constants.Subtype.ExtraValue.ASCII_CAPABLE);
setSubtype(subtype);
notifyChanged();
if (isEditing) {
@@ -517,7 +518,8 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
private AlertDialog createDialog(
@SuppressWarnings("unused") final SubtypePreference subtypePref) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ final AlertDialog.Builder builder = new AlertDialog.Builder(
+ DialogUtils.getPlatformDialogThemeContext(getActivity()));
builder.setTitle(R.string.custom_input_styles_title)
.setMessage(R.string.custom_input_style_note_message)
.setNegativeButton(R.string.not_now, null)
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index da1fb73fe..11d369282 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -16,19 +16,24 @@
package com.android.inputmethod.latin.settings;
+import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.res.Resources;
import android.os.Bundle;
import android.os.Process;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.DictionaryDumpBroadcastReceiver;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.debug.ExternalDictionaryGetterForDebug;
import com.android.inputmethod.latin.utils.ApplicationUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
public final class DebugSettings extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -37,11 +42,20 @@ public final class DebugSettings extends PreferenceFragment
public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch";
public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
public static final String PREF_STATISTICS_LOGGING = "enable_logging";
- public static final String PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG =
- "use_only_personalization_dictionary_for_debug";
- public static final String PREF_BOOST_PERSONALIZATION_DICTIONARY_FOR_DEBUG =
- "boost_personalization_dictionary_for_debug";
+ public static final String PREF_KEY_PREVIEW_SHOW_UP_START_SCALE =
+ "pref_key_preview_show_up_start_scale";
+ public static final String PREF_KEY_PREVIEW_DISMISS_END_SCALE =
+ "pref_key_preview_dismiss_end_scale";
+ public static final String PREF_KEY_PREVIEW_SHOW_UP_DURATION =
+ "pref_key_preview_show_up_duration";
+ public static final String PREF_KEY_PREVIEW_DISMISS_DURATION =
+ "pref_key_preview_dismiss_duration";
private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary";
+ private static final String PREF_DUMP_CONTACTS_DICT = "dump_contacts_dict";
+ private static final String PREF_DUMP_USER_DICT = "dump_user_dict";
+ private static final String PREF_DUMP_USER_HISTORY_DICT = "dump_user_history_dict";
+ private static final String PREF_DUMP_PERSONALIZATION_DICT = "dump_personalization_dict";
+
private static final boolean SHOW_STATISTICS_LOGGING = false;
private boolean mServiceNeedsRestart = false;
@@ -85,11 +99,64 @@ public final class DebugSettings extends PreferenceFragment
});
}
+ final OnPreferenceClickListener dictDumpPrefClickListener =
+ new DictDumpPrefClickListener(this);
+ findPreference(PREF_DUMP_CONTACTS_DICT).setOnPreferenceClickListener(
+ dictDumpPrefClickListener);
+ findPreference(PREF_DUMP_USER_DICT).setOnPreferenceClickListener(
+ dictDumpPrefClickListener);
+ findPreference(PREF_DUMP_USER_HISTORY_DICT).setOnPreferenceClickListener(
+ dictDumpPrefClickListener);
+ findPreference(PREF_DUMP_PERSONALIZATION_DICT).setOnPreferenceClickListener(
+ dictDumpPrefClickListener);
+ final Resources res = getResources();
+ setupKeyPreviewAnimationDuration(prefs, res, PREF_KEY_PREVIEW_SHOW_UP_DURATION,
+ res.getInteger(R.integer.config_key_preview_show_up_duration));
+ setupKeyPreviewAnimationDuration(prefs, res, PREF_KEY_PREVIEW_DISMISS_DURATION,
+ res.getInteger(R.integer.config_key_preview_dismiss_duration));
+ setupKeyPreviewAnimationScale(prefs, res, PREF_KEY_PREVIEW_SHOW_UP_START_SCALE,
+ ResourceUtils.getFloatFromFraction(
+ res, R.fraction.config_key_preview_show_up_start_scale));
+ setupKeyPreviewAnimationScale(prefs, res, PREF_KEY_PREVIEW_DISMISS_END_SCALE,
+ ResourceUtils.getFloatFromFraction(
+ res, R.fraction.config_key_preview_dismiss_end_scale));
+
mServiceNeedsRestart = false;
mDebugMode = (CheckBoxPreference) findPreference(PREF_DEBUG_MODE);
updateDebugMode();
}
+ private static class DictDumpPrefClickListener implements OnPreferenceClickListener {
+ final PreferenceFragment mPreferenceFragment;
+
+ public DictDumpPrefClickListener(final PreferenceFragment preferenceFragment) {
+ mPreferenceFragment = preferenceFragment;
+ }
+
+ @Override
+ public boolean onPreferenceClick(final Preference arg0) {
+ final String dictName;
+ if (arg0.getKey().equals(PREF_DUMP_CONTACTS_DICT)) {
+ dictName = Dictionary.TYPE_CONTACTS;
+ } else if (arg0.getKey().equals(PREF_DUMP_USER_DICT)) {
+ dictName = Dictionary.TYPE_USER;
+ } else if (arg0.getKey().equals(PREF_DUMP_USER_HISTORY_DICT)) {
+ dictName = Dictionary.TYPE_USER_HISTORY;
+ } else if (arg0.getKey().equals(PREF_DUMP_PERSONALIZATION_DICT)) {
+ dictName = Dictionary.TYPE_PERSONALIZATION;
+ } else {
+ dictName = null;
+ }
+ if (dictName != null) {
+ final Intent intent =
+ new Intent(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
+ intent.putExtra(DictionaryDumpBroadcastReceiver.DICTIONARY_NAME_KEY, dictName);
+ mPreferenceFragment.getActivity().sendBroadcast(intent);
+ }
+ return true;
+ }
+ }
+
@Override
public void onStop() {
super.onStop();
@@ -112,8 +179,7 @@ public final class DebugSettings extends PreferenceFragment
updateDebugMode();
mServiceNeedsRestart = true;
}
- } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)
- || key.equals(PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG)) {
+ } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)) {
mServiceNeedsRestart = true;
}
}
@@ -133,4 +199,92 @@ public final class DebugSettings extends PreferenceFragment
mDebugMode.setSummary(version);
}
}
+
+ private void setupKeyPreviewAnimationScale(final SharedPreferences sp, final Resources res,
+ final String prefKey, final float defaultValue) {
+ final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(prefKey);
+ if (pref == null) {
+ return;
+ }
+ pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
+ private static final float PERCENTAGE_FLOAT = 100.0f;
+
+ private float getValueFromPercentage(final int percentage) {
+ return percentage / PERCENTAGE_FLOAT;
+ }
+
+ private int getPercentageFromValue(final float floatValue) {
+ return (int)(floatValue * PERCENTAGE_FLOAT);
+ }
+
+ @Override
+ public void writeValue(final int value, final String key) {
+ sp.edit().putFloat(key, getValueFromPercentage(value)).apply();
+ }
+
+ @Override
+ public void writeDefaultValue(final String key) {
+ sp.edit().remove(key).apply();
+ }
+
+ @Override
+ public int readValue(final String key) {
+ return getPercentageFromValue(
+ Settings.readKeyPreviewAnimationScale(sp, key, defaultValue));
+ }
+
+ @Override
+ public int readDefaultValue(final String key) {
+ return getPercentageFromValue(defaultValue);
+ }
+
+ @Override
+ public String getValueText(final int value) {
+ if (value < 0) {
+ return res.getString(R.string.settings_system_default);
+ }
+ return String.format("%d%%", value);
+ }
+
+ @Override
+ public void feedbackValue(final int value) {}
+ });
+ }
+
+ private void setupKeyPreviewAnimationDuration(final SharedPreferences sp, final Resources res,
+ final String prefKey, final int defaultValue) {
+ final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(prefKey);
+ if (pref == null) {
+ return;
+ }
+ pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
+ @Override
+ public void writeValue(final int value, final String key) {
+ sp.edit().putInt(key, value).apply();
+ }
+
+ @Override
+ public void writeDefaultValue(final String key) {
+ sp.edit().remove(key).apply();
+ }
+
+ @Override
+ public int readValue(final String key) {
+ return Settings.readKeyPreviewAnimationDuration(sp, key, defaultValue);
+ }
+
+ @Override
+ public int readDefaultValue(final String key) {
+ return defaultValue;
+ }
+
+ @Override
+ public String getValueText(final int value) {
+ return res.getString(R.string.abbreviation_unit_milliseconds, value);
+ }
+
+ @Override
+ public void feedbackValue(final int value) {}
+ });
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index df2c6907f..353b7463d 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -27,13 +27,13 @@ import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.latin.utils.RunInLocale;
import com.android.inputmethod.latin.utils.StringUtils;
-import java.util.HashMap;
+import java.util.Collections;
import java.util.Locale;
+import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -53,10 +53,9 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
public static final String PREF_MISC_SETTINGS = "misc_settings";
- public static final String PREF_LAST_USER_DICTIONARY_WRITE_TIME =
- "last_user_dictionary_write_time";
public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
+ public static final String PREF_KEY_USE_PERSONALIZED_DICTS = "pref_key_use_personalized_dicts";
public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD =
"pref_key_use_double_space_period";
public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE =
@@ -67,6 +66,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
"pref_include_other_imes_in_language_switch_list";
public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles";
+ // 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";
public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction";
@@ -96,6 +96,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
private static final String PREF_LAST_USED_PERSONALIZATION_TOKEN =
"pref_last_used_personalization_token";
+ private static final String PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME =
+ "pref_last_used_personalization_dict_wiped_time";
+ private static final String PREF_CORPUS_HANDLES_FOR_PERSONALIZATION =
+ "pref_corpus_handles_for_personalization";
public static final String PREF_SEND_FEEDBACK = "send_feedback";
public static final String PREF_ABOUT_KEYBOARD = "about_keyboard";
@@ -104,6 +108,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id";
public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_ID = "last_shown_emoji_category_id";
+ private static final float UNDEFINED_PREFERENCE_VALUE_FLOAT = -1.0f;
+ private static final int UNDEFINED_PREFERENCE_VALUE_INT = -1;
+
+ private Context mContext;
private Resources mRes;
private SharedPreferences mPrefs;
private SettingsValues mSettingsValues;
@@ -124,6 +132,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
}
private void onCreate(final Context context) {
+ mContext = context;
mRes = context.getResources();
mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
mPrefs.registerOnSharedPreferenceChangeListener(this);
@@ -143,20 +152,22 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
Log.w(TAG, "onSharedPreferenceChanged called before loadSettings.");
return;
}
- loadSettings(mSettingsValues.mLocale, mSettingsValues.mInputAttributes);
+ loadSettings(mContext, mSettingsValues.mLocale, mSettingsValues.mInputAttributes);
} finally {
mSettingsValuesLock.unlock();
}
}
- public void loadSettings(final Locale locale, final InputAttributes inputAttributes) {
+ public void loadSettings(final Context context, final Locale locale,
+ final InputAttributes inputAttributes) {
mSettingsValuesLock.lock();
+ mContext = context;
try {
final SharedPreferences prefs = mPrefs;
final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
@Override
protected SettingsValues job(final Resources res) {
- return new SettingsValues(prefs, locale, res, inputAttributes);
+ return new SettingsValues(context, prefs, res, inputAttributes);
}
};
mSettingsValues = job.runInLocale(mRes, locale);
@@ -174,10 +185,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return mSettingsValues.mIsInternal;
}
- public String getWordSeparators() {
- return mSettingsValues.mWordSeparators;
- }
-
public boolean isWordSeparator(final int code) {
return mSettingsValues.isWordSeparator(code);
}
@@ -229,16 +236,15 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
res.getBoolean(R.bool.config_default_phrase_gesture_enabled));
}
- public static boolean readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(
- final Resources res) {
- return res.getBoolean(R.bool.config_enable_show_option_of_key_preview_popup);
+ public static boolean readFromBuildConfigIfToShowKeyPreviewPopupOption(final Resources res) {
+ return res.getBoolean(R.bool.config_enable_show_key_preview_popup_option);
}
public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs,
final Resources res) {
final boolean defaultKeyPreviewPopup = res.getBoolean(
R.bool.config_default_key_preview_popup);
- if (!readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(res)) {
+ if (!readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
return defaultKeyPreviewPopup;
}
return prefs.getBoolean(PREF_POPUP_ON, defaultKeyPreviewPopup);
@@ -263,28 +269,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return prefs.getBoolean(PREF_SHOW_LANGUAGE_SWITCH_KEY, true);
}
- public static int readKeyboardThemeIndex(final SharedPreferences prefs, final Resources res) {
- final String defaultThemeIndex = res.getString(
- R.string.config_default_keyboard_theme_index);
- final String themeIndex = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultThemeIndex);
- try {
- return Integer.valueOf(themeIndex);
- } catch (final NumberFormatException e) {
- // Format error, returns default keyboard theme index.
- Log.e(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to "
- + defaultThemeIndex, e);
- return Integer.valueOf(defaultThemeIndex);
- }
- }
-
- public static int resetAndGetDefaultKeyboardThemeIndex(final SharedPreferences prefs,
- final Resources res) {
- final String defaultThemeIndex = res.getString(
- R.string.config_default_keyboard_theme_index);
- prefs.edit().putString(PREF_KEYBOARD_LAYOUT, defaultThemeIndex).apply();
- return Integer.valueOf(defaultThemeIndex);
- }
-
public static String readPrefAdditionalSubtypes(final SharedPreferences prefs,
final Resources res) {
final String predefinedPrefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(
@@ -299,19 +283,27 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static float readKeypressSoundVolume(final SharedPreferences prefs,
final Resources res) {
- final float volume = prefs.getFloat(PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
- return (volume >= 0) ? volume : readDefaultKeypressSoundVolume(res);
+ final float volume = prefs.getFloat(
+ PREF_KEYPRESS_SOUND_VOLUME, UNDEFINED_PREFERENCE_VALUE_FLOAT);
+ return (volume != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? volume
+ : readDefaultKeypressSoundVolume(res);
}
+ // Default keypress sound volume for unknown devices.
+ // The negative value means system default.
+ private static final String DEFAULT_KEYPRESS_SOUND_VOLUME = Float.toString(-1.0f);
+
public static float readDefaultKeypressSoundVolume(final Resources res) {
- return Float.parseFloat(
- ResourceUtils.getDeviceOverrideValue(res, R.array.keypress_volumes));
+ return Float.parseFloat(ResourceUtils.getDeviceOverrideValue(res,
+ R.array.keypress_volumes, DEFAULT_KEYPRESS_SOUND_VOLUME));
}
public static int readKeyLongpressTimeout(final SharedPreferences prefs,
final Resources res) {
- final int ms = prefs.getInt(PREF_KEY_LONGPRESS_TIMEOUT, -1);
- return (ms >= 0) ? ms : readDefaultKeyLongpressTimeout(res);
+ final int milliseconds = prefs.getInt(
+ PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT);
+ return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds
+ : readDefaultKeyLongpressTimeout(res);
}
public static int readDefaultKeyLongpressTimeout(final Resources res) {
@@ -320,36 +312,35 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static int readKeypressVibrationDuration(final SharedPreferences prefs,
final Resources res) {
- final int ms = prefs.getInt(PREF_VIBRATION_DURATION_SETTINGS, -1);
- return (ms >= 0) ? ms : readDefaultKeypressVibrationDuration(res);
+ final int milliseconds = prefs.getInt(
+ PREF_VIBRATION_DURATION_SETTINGS, UNDEFINED_PREFERENCE_VALUE_INT);
+ return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds
+ : readDefaultKeypressVibrationDuration(res);
}
+ // Default keypress vibration duration for unknown devices.
+ // The negative value means system default.
+ private static final String DEFAULT_KEYPRESS_VIBRATION_DURATION = Integer.toString(-1);
+
public static int readDefaultKeypressVibrationDuration(final Resources res) {
- return Integer.parseInt(
- ResourceUtils.getDeviceOverrideValue(res, R.array.keypress_vibration_durations));
+ return Integer.parseInt(ResourceUtils.getDeviceOverrideValue(res,
+ R.array.keypress_vibration_durations, DEFAULT_KEYPRESS_VIBRATION_DURATION));
}
public static boolean readUsabilityStudyMode(final SharedPreferences prefs) {
return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true);
}
- public static long readLastUserHistoryWriteTime(final SharedPreferences prefs,
- final String locale) {
- final String str = prefs.getString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(str);
- if (map.containsKey(locale)) {
- return map.get(locale);
- }
- return 0;
+ public static float readKeyPreviewAnimationScale(final SharedPreferences prefs,
+ final String prefKey, final float defaultValue) {
+ final float fraction = prefs.getFloat(prefKey, UNDEFINED_PREFERENCE_VALUE_FLOAT);
+ return (fraction != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? fraction : defaultValue;
}
- public static void writeLastUserHistoryWriteTime(final SharedPreferences prefs,
- final String locale) {
- final String oldStr = prefs.getString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(oldStr);
- map.put(locale, System.currentTimeMillis());
- final String newStr = LocaleUtils.localeAndTimeHashMapToStr(map);
- prefs.edit().putString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
+ public static int readKeyPreviewAnimationDuration(final SharedPreferences prefs,
+ final String prefKey, final int defaultValue) {
+ final int milliseconds = prefs.getInt(prefKey, UNDEFINED_PREFERENCE_VALUE_INT);
+ return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds : defaultValue;
}
public static boolean readUseFullscreenMode(final Resources res) {
@@ -377,21 +368,13 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return prefs.getBoolean(Settings.PREF_KEY_IS_INTERNAL, false);
}
- public static boolean readUseOnlyPersonalizationDictionaryForDebug(
- final SharedPreferences prefs) {
- return prefs.getBoolean(
- DebugSettings.PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
- }
-
- public static boolean readBoostPersonalizationDictionaryForDebug(
- final SharedPreferences prefs) {
- return prefs.getBoolean(
- DebugSettings.PREF_BOOST_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
- }
-
public void writeLastUsedPersonalizationToken(byte[] token) {
- final String tokenStr = StringUtils.byteArrayToHexString(token);
- mPrefs.edit().putString(PREF_LAST_USED_PERSONALIZATION_TOKEN, tokenStr).apply();
+ if (token == null) {
+ mPrefs.edit().remove(PREF_LAST_USED_PERSONALIZATION_TOKEN).apply();
+ } else {
+ final String tokenStr = StringUtils.byteArrayToHexString(token);
+ mPrefs.edit().putString(PREF_LAST_USED_PERSONALIZATION_TOKEN, tokenStr).apply();
+ }
}
public byte[] readLastUsedPersonalizationToken() {
@@ -399,6 +382,23 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return StringUtils.hexStringToByteArray(tokenStr);
}
+ public void writeLastPersonalizationDictWipedTime(final long timestamp) {
+ mPrefs.edit().putLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, timestamp).apply();
+ }
+
+ public long readLastPersonalizationDictGeneratedTime() {
+ return mPrefs.getLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, 0);
+ }
+
+ public void writeCorpusHandlesForPersonalization(final Set<String> corpusHandles) {
+ mPrefs.edit().putStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, corpusHandles).apply();
+ }
+
+ public Set<String> readCorpusHandlesForPersonalization() {
+ final Set<String> emptySet = Collections.emptySet();
+ return mPrefs.getStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, emptySet);
+ }
+
public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) {
prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply();
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 5c60a7350..bb5547fc9 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -48,7 +48,6 @@ import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
import com.android.inputmethod.latin.utils.ApplicationUtils;
import com.android.inputmethod.latin.utils.FeedbackUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
-import com.android.inputmethod.research.ResearchLogger;
import com.android.inputmethodcommon.InputMethodSettingsFragment;
import java.util.TreeSet;
@@ -61,13 +60,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
|| Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */;
- private CheckBoxPreference mVoiceInputKeyPreference;
- private ListPreference mShowCorrectionSuggestionsPreference;
- private ListPreference mAutoCorrectionThresholdPreference;
- private ListPreference mKeyPreviewPopupDismissDelay;
- // Use bigrams to predict the next word when there is no input for it yet
- private CheckBoxPreference mBigramPrediction;
-
private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) {
final Preference preference = findPreference(preferenceKey);
if (preference != null) {
@@ -75,6 +67,18 @@ public final class SettingsFragment extends InputMethodSettingsFragment
}
}
+ private void updateListPreferenceSummaryToCurrentValue(final String prefKey) {
+ // Because the "%s" summary trick of {@link ListPreference} doesn't work properly before
+ // KitKat, we need to update the summary programmatically.
+ final ListPreference listPreference = (ListPreference)findPreference(prefKey);
+ if (listPreference == null) {
+ return;
+ }
+ final CharSequence entries[] = listPreference.getEntries();
+ final int entryIndex = listPreference.findIndexOfValue(listPreference.getValue());
+ listPreference.setSummary(entryIndex < 0 ? null : entries[entryIndex]);
+ }
+
private static void removePreference(final String preferenceKey, final PreferenceGroup parent) {
if (parent == null) {
return;
@@ -94,7 +98,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
preferenceScreen.setTitle(
- ApplicationUtils.getAcitivityTitleResId(getActivity(), SettingsActivity.class));
+ ApplicationUtils.getActivityTitleResId(getActivity(), SettingsActivity.class));
}
final Resources res = getResources();
@@ -107,16 +111,9 @@ public final class SettingsFragment extends InputMethodSettingsFragment
SubtypeLocaleUtils.init(context);
AudioAndHapticFeedbackManager.init(context);
- mVoiceInputKeyPreference =
- (CheckBoxPreference) findPreference(Settings.PREF_VOICE_INPUT_KEY);
- mShowCorrectionSuggestionsPreference =
- (ListPreference) findPreference(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
- mAutoCorrectionThresholdPreference =
- (ListPreference) findPreference(Settings.PREF_AUTO_CORRECTION_THRESHOLD);
- mBigramPrediction = (CheckBoxPreference) findPreference(Settings.PREF_BIGRAM_PREDICTIONS);
ensureConsistencyOfAutoCorrectionSettings();
final PreferenceGroup generalSettings =
@@ -143,12 +140,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
feedbackSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference pref) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- // Use development-only feedback mechanism
- ResearchLogger.getInstance().presentFeedbackDialogFromSettings();
- } else {
- FeedbackUtils.showFeedbackForm(getActivity());
- }
+ FeedbackUtils.showFeedbackForm(getActivity());
return true;
}
});
@@ -167,7 +159,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);
if (!showVoiceKeyOption) {
- generalSettings.removePreference(mVoiceInputKeyPreference);
+ removePreference(Settings.PREF_VOICE_INPUT_KEY, generalSettings);
}
final PreferenceGroup advancedSettings =
@@ -177,26 +169,28 @@ public final class SettingsFragment extends InputMethodSettingsFragment
removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedSettings);
}
- mKeyPreviewPopupDismissDelay =
- (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
- if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(res)) {
+ // TODO: consolidate key preview dismiss delay with the key preview animation parameters.
+ if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
removePreference(Settings.PREF_POPUP_ON, generalSettings);
removePreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, advancedSettings);
} else {
+ // TODO: Cleanup this setup.
+ final ListPreference keyPreviewPopupDismissDelay =
+ (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
R.integer.config_key_preview_linger_timeout));
- mKeyPreviewPopupDismissDelay.setEntries(new String[] {
+ keyPreviewPopupDismissDelay.setEntries(new String[] {
res.getString(R.string.key_preview_popup_dismiss_no_delay),
res.getString(R.string.key_preview_popup_dismiss_default_delay),
});
- mKeyPreviewPopupDismissDelay.setEntryValues(new String[] {
+ keyPreviewPopupDismissDelay.setEntryValues(new String[] {
"0",
popupDismissDelayDefaultValue
});
- if (null == mKeyPreviewPopupDismissDelay.getValue()) {
- mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
+ if (null == keyPreviewPopupDismissDelay.getValue()) {
+ keyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
}
- mKeyPreviewPopupDismissDelay.setEnabled(
+ keyPreviewPopupDismissDelay.setEnabled(
Settings.readKeyPreviewPopupEnabled(prefs, res));
}
@@ -243,20 +237,25 @@ public final class SettingsFragment extends InputMethodSettingsFragment
@Override
public void onResume() {
super.onResume();
- final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
- if (!isShortcutImeEnabled) {
- getPreferenceScreen().removePreference(mVoiceInputKeyPreference);
- }
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+ final Resources res = getResources();
+ final Preference voiceInputKeyOption = findPreference(Settings.PREF_VOICE_INPUT_KEY);
+ if (voiceInputKeyOption != null) {
+ final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance()
+ .isShortcutImeEnabled();
+ voiceInputKeyOption.setEnabled(isShortcutImeEnabled);
+ voiceInputKeyOption.setSummary(isShortcutImeEnabled ? null
+ : res.getText(R.string.voice_input_disabled_summary));
+ }
final CheckBoxPreference showSetupWizardIcon =
(CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
if (showSetupWizardIcon != null) {
showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, getActivity()));
}
- updateShowCorrectionSuggestionsSummary();
- updateKeyPreviewPopupDelaySummary();
- updateColorSchemeSummary(prefs, getResources());
- updateCustomInputStylesSummary();
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
+ updateCustomInputStylesSummary(prefs, res);
}
@Override
@@ -287,50 +286,26 @@ public final class SettingsFragment extends InputMethodSettingsFragment
LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
}
ensureConsistencyOfAutoCorrectionSettings();
- updateShowCorrectionSuggestionsSummary();
- updateKeyPreviewPopupDelaySummary();
- updateColorSchemeSummary(prefs, res);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
}
private void ensureConsistencyOfAutoCorrectionSettings() {
final String autoCorrectionOff = getResources().getString(
R.string.auto_correction_threshold_mode_index_off);
- final String currentSetting = mAutoCorrectionThresholdPreference.getValue();
- mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
- }
-
- private void updateShowCorrectionSuggestionsSummary() {
- mShowCorrectionSuggestionsPreference.setSummary(
- getResources().getStringArray(R.array.prefs_suggestion_visibilities)
- [mShowCorrectionSuggestionsPreference.findIndexOfValue(
- mShowCorrectionSuggestionsPreference.getValue())]);
+ final ListPreference autoCorrectionThresholdPref = (ListPreference)findPreference(
+ Settings.PREF_AUTO_CORRECTION_THRESHOLD);
+ final String currentSetting = autoCorrectionThresholdPref.getValue();
+ setPreferenceEnabled(
+ Settings.PREF_BIGRAM_PREDICTIONS, !currentSetting.equals(autoCorrectionOff));
}
- private void updateColorSchemeSummary(final SharedPreferences prefs, final Resources res) {
- // Because the "%s" summary trick of {@link ListPreference} doesn't work properly before
- // KitKat, we need to update the summary by code.
- final Preference preference = findPreference(Settings.PREF_KEYBOARD_LAYOUT);
- if (!(preference instanceof ListPreference)) {
- Log.w(TAG, "Can't find Keyboard Color Scheme preference");
- return;
- }
- final ListPreference colorSchemePreference = (ListPreference)preference;
- final int themeIndex = Settings.readKeyboardThemeIndex(prefs, res);
- int entryIndex = colorSchemePreference.findIndexOfValue(Integer.toString(themeIndex));
- if (entryIndex < 0) {
- final int defaultThemeIndex = Settings.resetAndGetDefaultKeyboardThemeIndex(prefs, res);
- entryIndex = colorSchemePreference.findIndexOfValue(
- Integer.toString(defaultThemeIndex));
- }
- colorSchemePreference.setSummary(colorSchemePreference.getEntries()[entryIndex]);
- }
-
- private void updateCustomInputStylesSummary() {
+ private void updateCustomInputStylesSummary(final SharedPreferences prefs,
+ final Resources res) {
final PreferenceScreen customInputStyles =
(PreferenceScreen)findPreference(Settings.PREF_CUSTOM_INPUT_STYLES);
- final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
- final Resources res = getResources();
final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res);
final InputMethodSubtype[] subtypes =
AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype);
@@ -342,13 +317,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
customInputStyles.setSummary(styles);
}
- private void updateKeyPreviewPopupDelaySummary() {
- final ListPreference lp = mKeyPreviewPopupDismissDelay;
- final CharSequence[] entries = lp.getEntries();
- if (entries == null || entries.length <= 0) return;
- lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]);
- }
-
private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
final SharedPreferences sp, final Resources res) {
setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS,
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index f331c78e5..d47a61ed1 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -16,27 +16,22 @@
package com.android.inputmethod.latin.settings;
+import android.content.Context;
import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.keyboard.internal.KeySpecParser;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.compat.AppWorkaroundsUtils;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.InputTypeUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
-
-import java.util.ArrayList;
+import com.android.inputmethod.latin.utils.AsyncResultHolder;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
+
import java.util.Arrays;
import java.util.Locale;
@@ -50,27 +45,23 @@ public final class SettingsValues {
// Float.NEGATIVE_INFINITE and Float.MAX_VALUE. Currently used for auto-correction settings.
private static final String FLOAT_MAX_VALUE_MARKER_STRING = "floatMaxValue";
private static final String FLOAT_NEGATIVE_INFINITY_MARKER_STRING = "floatNegativeInfinity";
+ private static final int TIMEOUT_TO_GET_TARGET_PACKAGE = 5; // seconds
// From resources:
+ public final SpacingAndPunctuations mSpacingAndPunctuations;
public final int mDelayUpdateOldSuggestions;
- public final int[] mSymbolsPrecededBySpace;
- public final int[] mSymbolsFollowedBySpace;
- public final int[] mWordConnectors;
- public final SuggestedWords mSuggestPuncList;
- public final String mWordSeparators;
- public final int mSentenceSeparator;
- public final CharSequence mHintToSaveText;
- public final boolean mCurrentLanguageHasSpaces;
+ public final long mDoubleSpacePeriodTimeout;
// From preferences, in the same order as xml/prefs.xml:
public final boolean mAutoCap;
public final boolean mVibrateOn;
public final boolean mSoundOn;
public final boolean mKeyPreviewPopupOn;
- private final boolean mShowsVoiceInputKey;
+ public final boolean mShowsVoiceInputKey;
public final boolean mIncludesOtherImesInLanguageSwitchList;
public final boolean mShowsLanguageSwitchKey;
public final boolean mUseContactsDict;
+ public final boolean mUsePersonalizedDicts;
public final boolean mUseDoubleSpacePeriod;
public final boolean mBlockPotentiallyOffensive;
// Use bigrams to predict the next word when there is no input for it yet
@@ -94,8 +85,8 @@ public final class SettingsValues {
public final float mAutoCorrectionThreshold;
public final boolean mCorrectionEnabled;
public final int mSuggestionVisibility;
- public final boolean mBoostPersonalizationDictionaryForDebug;
- public final boolean mUseOnlyPersonalizationDictionaryForDebug;
+ public final int mDisplayOrientation;
+ private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds;
// Setting values for additional features
public final int[] mAdditionalFeaturesSettingValues =
@@ -103,28 +94,17 @@ public final class SettingsValues {
// Debug settings
public final boolean mIsInternal;
+ public final int mKeyPreviewShowUpDuration;
+ public final int mKeyPreviewDismissDuration;
+ public final float mKeyPreviewShowUpStartScale;
+ public final float mKeyPreviewDismissEndScale;
- public SettingsValues(final SharedPreferences prefs, final Locale locale, final Resources res,
+ public SettingsValues(final Context context, final SharedPreferences prefs, final Resources res,
final InputAttributes inputAttributes) {
- mLocale = locale;
+ mLocale = res.getConfiguration().locale;
// Get the resources
mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions);
- mSymbolsPrecededBySpace =
- StringUtils.toCodePointArray(res.getString(R.string.symbols_preceded_by_space));
- Arrays.sort(mSymbolsPrecededBySpace);
- mSymbolsFollowedBySpace =
- StringUtils.toCodePointArray(res.getString(R.string.symbols_followed_by_space));
- Arrays.sort(mSymbolsFollowedBySpace);
- mWordConnectors =
- StringUtils.toCodePointArray(res.getString(R.string.symbols_word_connectors));
- Arrays.sort(mWordConnectors);
- final String[] suggestPuncsSpec = KeySpecParser.splitKeySpecs(res.getString(
- R.string.suggested_punctuations));
- mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
- mWordSeparators = res.getString(R.string.symbols_word_separators);
- mSentenceSeparator = res.getInteger(R.integer.sentence_separator);
- mHintToSaveText = res.getText(R.string.hint_add_to_dictionary);
- mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces);
+ mSpacingAndPunctuations = new SpacingAndPunctuations(res);
// Store the input attributes
if (null == inputAttributes) {
@@ -148,10 +128,12 @@ public final class SettingsValues {
Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false);
mShowsLanguageSwitchKey = Settings.readShowsLanguageSwitchKey(prefs);
mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
+ mUsePersonalizedDicts = prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true);
mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true);
mBlockPotentiallyOffensive = Settings.readBlockPotentiallyOffensive(prefs, res);
mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(autoCorrectionThresholdRawValue, res);
mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res);
+ mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout);
// Compute other readable settings
mKeyLongpressTimeout = Settings.readKeyLongpressTimeout(prefs, res);
@@ -173,86 +155,53 @@ public final class SettingsValues {
AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
prefs, mAdditionalFeaturesSettingValues);
mIsInternal = Settings.isInternal(prefs);
- mBoostPersonalizationDictionaryForDebug =
- Settings.readBoostPersonalizationDictionaryForDebug(prefs);
- mUseOnlyPersonalizationDictionaryForDebug =
- Settings.readUseOnlyPersonalizationDictionaryForDebug(prefs);
- }
-
- // Only for tests
- private SettingsValues(final Locale locale) {
- // TODO: locale is saved, but not used yet. May have to change this if tests require.
- mLocale = locale;
- mDelayUpdateOldSuggestions = 0;
- mSymbolsPrecededBySpace = new int[] { '(', '[', '{', '&' };
- Arrays.sort(mSymbolsPrecededBySpace);
- mSymbolsFollowedBySpace = new int[] { '.', ',', ';', ':', '!', '?', ')', ']', '}', '&' };
- Arrays.sort(mSymbolsFollowedBySpace);
- mWordConnectors = new int[] { '\'', '-' };
- Arrays.sort(mWordConnectors);
- mSentenceSeparator = Constants.CODE_PERIOD;
- final String[] suggestPuncsSpec = new String[] { "!", "?", ",", ":", ";" };
- mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
- mWordSeparators = "&\t \n()[]{}*&<>+=|.,;:!?/_\"";
- mHintToSaveText = "Touch again to save";
- mCurrentLanguageHasSpaces = true;
- mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */);
- mAutoCap = true;
- mVibrateOn = true;
- mSoundOn = true;
- mKeyPreviewPopupOn = true;
- mSlidingKeyInputPreviewEnabled = true;
- mShowsVoiceInputKey = true;
- mIncludesOtherImesInLanguageSwitchList = false;
- mShowsLanguageSwitchKey = true;
- mUseContactsDict = true;
- mUseDoubleSpacePeriod = true;
- mBlockPotentiallyOffensive = true;
- mAutoCorrectEnabled = true;
- mBigramPredictionEnabled = true;
- mKeyLongpressTimeout = 300;
- mKeypressVibrationDuration = 5;
- mKeypressSoundVolume = 1;
- mKeyPreviewPopupDismissDelay = 70;
- mAutoCorrectionThreshold = 1;
- mGestureInputEnabled = true;
- mGestureTrailEnabled = true;
- mGestureFloatingPreviewTextEnabled = true;
- mPhraseGestureEnabled = true;
- mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
- mSuggestionVisibility = 0;
- mIsInternal = false;
- mBoostPersonalizationDictionaryForDebug = false;
- mUseOnlyPersonalizationDictionaryForDebug = false;
- }
-
- @UsedForTesting
- public static SettingsValues makeDummySettingsValuesForTest(final Locale locale) {
- return new SettingsValues(locale);
+ mKeyPreviewShowUpDuration = Settings.readKeyPreviewAnimationDuration(
+ prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION,
+ res.getInteger(R.integer.config_key_preview_show_up_duration));
+ mKeyPreviewDismissDuration = Settings.readKeyPreviewAnimationDuration(
+ prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION,
+ res.getInteger(R.integer.config_key_preview_dismiss_duration));
+ mKeyPreviewShowUpStartScale = Settings.readKeyPreviewAnimationScale(
+ prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_SCALE,
+ ResourceUtils.getFloatFromFraction(
+ res, R.fraction.config_key_preview_show_up_start_scale));
+ mKeyPreviewDismissEndScale = Settings.readKeyPreviewAnimationScale(
+ prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_SCALE,
+ ResourceUtils.getFloatFromFraction(
+ res, R.fraction.config_key_preview_dismiss_end_scale));
+ mDisplayOrientation = res.getConfiguration().orientation;
+ mAppWorkarounds = new AsyncResultHolder<AppWorkaroundsUtils>();
+ final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo(
+ mInputAttributes.mTargetApplicationPackageName);
+ if (null != packageInfo) {
+ mAppWorkarounds.set(new AppWorkaroundsUtils(packageInfo));
+ } else {
+ new TargetPackageInfoGetterTask(context, mAppWorkarounds)
+ .execute(mInputAttributes.mTargetApplicationPackageName);
+ }
}
public boolean isApplicationSpecifiedCompletionsOn() {
return mInputAttributes.mApplicationSpecifiedCompletionOn;
}
- public boolean isSuggestionsRequested(final int displayOrientation) {
+ public boolean isSuggestionsRequested() {
return mInputAttributes.mIsSettingsSuggestionStripOn
- && (mCorrectionEnabled
- || isSuggestionStripVisibleInOrientation(displayOrientation));
+ && (mCorrectionEnabled || isSuggestionStripVisible());
}
- public boolean isSuggestionStripVisibleInOrientation(final int orientation) {
+ public boolean isSuggestionStripVisible() {
return (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_VALUE)
|| (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE
- && orientation == Configuration.ORIENTATION_PORTRAIT);
+ && mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT);
}
public boolean isWordSeparator(final int code) {
- return mWordSeparators.contains(String.valueOf((char)code));
+ return mSpacingAndPunctuations.isWordSeparator(code);
}
public boolean isWordConnector(final int code) {
- return Arrays.binarySearch(mWordConnectors, code) >= 0;
+ return mSpacingAndPunctuations.isWordConnector(code);
}
public boolean isWordCodePoint(final int code) {
@@ -260,24 +209,17 @@ public final class SettingsValues {
}
public boolean isUsuallyPrecededBySpace(final int code) {
- return Arrays.binarySearch(mSymbolsPrecededBySpace, code) >= 0;
+ return mSpacingAndPunctuations.isUsuallyPrecededBySpace(code);
}
public boolean isUsuallyFollowedBySpace(final int code) {
- return Arrays.binarySearch(mSymbolsFollowedBySpace, code) >= 0;
+ return mSpacingAndPunctuations.isUsuallyFollowedBySpace(code);
}
public boolean shouldInsertSpacesAutomatically() {
return mInputAttributes.mShouldInsertSpacesAutomatically;
}
- public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) {
- final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
- final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
- return shortcutImeEnabled && mShowsVoiceInputKey
- && !InputTypeUtils.isPasswordInputType(inputType);
- }
-
public boolean isLanguageSwitchKeyEnabled() {
if (!mShowsLanguageSwitchKey) {
return false;
@@ -294,25 +236,20 @@ public final class SettingsValues {
return mInputAttributes.isSameInputType(editorInfo);
}
- // Helper functions to create member values.
- private static SuggestedWords createSuggestPuncList(final String[] puncs) {
- final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList();
- if (puncs != null) {
- for (final String puncSpec : puncs) {
- // TODO: Stop using KeySpceParser.getLabel().
- puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
- SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_HARDCODED,
- Dictionary.DICTIONARY_HARDCODED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- }
- }
- return new SuggestedWords(puncList,
- false /* typedWordValid */,
- false /* hasAutoCorrectionCandidate */,
- true /* isPunctuationSuggestions */,
- false /* isObsoleteSuggestions */,
- false /* isPrediction */);
+ public boolean hasSameOrientation(final Configuration configuration) {
+ return mDisplayOrientation == configuration.orientation;
+ }
+
+ public boolean isBeforeJellyBean() {
+ final AppWorkaroundsUtils appWorkaroundUtils
+ = mAppWorkarounds.get(null, TIMEOUT_TO_GET_TARGET_PACKAGE);
+ return null == appWorkaroundUtils ? false : appWorkaroundUtils.isBeforeJellyBean();
+ }
+
+ public boolean isBrokenByRecorrection() {
+ final AppWorkaroundsUtils appWorkaroundUtils
+ = mAppWorkarounds.get(null, TIMEOUT_TO_GET_TARGET_PACKAGE);
+ return null == appWorkaroundUtils ? false : appWorkaroundUtils.isBrokenByRecorrection();
}
private static final int SUGGESTION_VISIBILITY_SHOW_VALUE =
@@ -350,7 +287,7 @@ public final class SettingsValues {
// When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off.
final float autoCorrectionThreshold;
try {
- final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
+ final int arrayIndex = Integer.parseInt(currentAutoCorrectionSetting);
if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
final String val = autoCorrectionThresholdValues[arrayIndex];
if (FLOAT_MAX_VALUE_MARKER_STRING.equals(val)) {
@@ -374,17 +311,101 @@ public final class SettingsValues {
return autoCorrectionThreshold;
}
- private static boolean needsToShowVoiceInputKey(SharedPreferences prefs, Resources res) {
- final String voiceModeMain = res.getString(R.string.voice_mode_main);
- final String voiceMode = prefs.getString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain);
- final boolean showsVoiceInputKey = voiceMode == null || voiceMode.equals(voiceModeMain);
- if (!showsVoiceInputKey) {
- // Migrate settings from PREF_VOICE_MODE_OBSOLETE to PREF_VOICE_INPUT_KEY
- // Set voiceModeMain as a value of obsolete voice mode settings.
- prefs.edit().putString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain).apply();
- // Disable voice input key.
- prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, false).apply();
+ private static boolean needsToShowVoiceInputKey(final SharedPreferences prefs,
+ final Resources res) {
+ if (!prefs.contains(Settings.PREF_VOICE_INPUT_KEY)) {
+ // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to
+ // {@link Settings#PREF_VOICE_INPUT_KEY}.
+ final String voiceModeMain = res.getString(R.string.voice_mode_main);
+ final String voiceMode = prefs.getString(
+ Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain);
+ final boolean shouldShowVoiceInputKey = voiceModeMain.equals(voiceMode);
+ prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey).apply();
+ }
+ // Remove the obsolete preference if exists.
+ if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) {
+ prefs.edit().remove(Settings.PREF_VOICE_MODE_OBSOLETE).apply();
}
return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true);
}
+
+ public String dump() {
+ final StringBuilder sb = new StringBuilder("Current settings :");
+ sb.append("\n mSpacingAndPunctuations = ");
+ sb.append("" + mSpacingAndPunctuations.dump());
+ sb.append("\n mDelayUpdateOldSuggestions = ");
+ sb.append("" + mDelayUpdateOldSuggestions);
+ sb.append("\n mAutoCap = ");
+ sb.append("" + mAutoCap);
+ sb.append("\n mVibrateOn = ");
+ sb.append("" + mVibrateOn);
+ sb.append("\n mSoundOn = ");
+ sb.append("" + mSoundOn);
+ sb.append("\n mKeyPreviewPopupOn = ");
+ sb.append("" + mKeyPreviewPopupOn);
+ sb.append("\n mShowsVoiceInputKey = ");
+ sb.append("" + mShowsVoiceInputKey);
+ sb.append("\n mIncludesOtherImesInLanguageSwitchList = ");
+ sb.append("" + mIncludesOtherImesInLanguageSwitchList);
+ sb.append("\n mShowsLanguageSwitchKey = ");
+ sb.append("" + mShowsLanguageSwitchKey);
+ sb.append("\n mUseContactsDict = ");
+ sb.append("" + mUseContactsDict);
+ sb.append("\n mUsePersonalizedDicts = ");
+ sb.append("" + mUsePersonalizedDicts);
+ sb.append("\n mUseDoubleSpacePeriod = ");
+ sb.append("" + mUseDoubleSpacePeriod);
+ sb.append("\n mBlockPotentiallyOffensive = ");
+ sb.append("" + mBlockPotentiallyOffensive);
+ sb.append("\n mBigramPredictionEnabled = ");
+ sb.append("" + mBigramPredictionEnabled);
+ sb.append("\n mGestureInputEnabled = ");
+ sb.append("" + mGestureInputEnabled);
+ sb.append("\n mGestureTrailEnabled = ");
+ sb.append("" + mGestureTrailEnabled);
+ sb.append("\n mGestureFloatingPreviewTextEnabled = ");
+ sb.append("" + mGestureFloatingPreviewTextEnabled);
+ sb.append("\n mSlidingKeyInputPreviewEnabled = ");
+ sb.append("" + mSlidingKeyInputPreviewEnabled);
+ sb.append("\n mPhraseGestureEnabled = ");
+ sb.append("" + mPhraseGestureEnabled);
+ sb.append("\n mKeyLongpressTimeout = ");
+ sb.append("" + mKeyLongpressTimeout);
+ sb.append("\n mLocale = ");
+ sb.append("" + mLocale);
+ sb.append("\n mInputAttributes = ");
+ sb.append("" + mInputAttributes);
+ sb.append("\n mKeypressVibrationDuration = ");
+ sb.append("" + mKeypressVibrationDuration);
+ sb.append("\n mKeypressSoundVolume = ");
+ sb.append("" + mKeypressSoundVolume);
+ sb.append("\n mKeyPreviewPopupDismissDelay = ");
+ sb.append("" + mKeyPreviewPopupDismissDelay);
+ sb.append("\n mAutoCorrectEnabled = ");
+ sb.append("" + mAutoCorrectEnabled);
+ sb.append("\n mAutoCorrectionThreshold = ");
+ sb.append("" + mAutoCorrectionThreshold);
+ sb.append("\n mCorrectionEnabled = ");
+ sb.append("" + mCorrectionEnabled);
+ sb.append("\n mSuggestionVisibility = ");
+ sb.append("" + mSuggestionVisibility);
+ sb.append("\n mDisplayOrientation = ");
+ sb.append("" + mDisplayOrientation);
+ sb.append("\n mAppWorkarounds = ");
+ final AppWorkaroundsUtils awu = mAppWorkarounds.get(null, 0);
+ sb.append("" + (null == awu ? "null" : awu.toString()));
+ sb.append("\n mAdditionalFeaturesSettingValues = ");
+ sb.append("" + Arrays.toString(mAdditionalFeaturesSettingValues));
+ sb.append("\n mIsInternal = ");
+ sb.append("" + mIsInternal);
+ sb.append("\n mKeyPreviewShowUpDuration = ");
+ sb.append("" + mKeyPreviewShowUpDuration);
+ sb.append("\n mKeyPreviewDismissDuration = ");
+ sb.append("" + mKeyPreviewDismissDuration);
+ sb.append("\n mKeyPreviewShowUpStartScale = ");
+ sb.append("" + mKeyPreviewShowUpStartScale);
+ sb.append("\n mKeyPreviewDismissEndScale = ");
+ sb.append("" + mKeyPreviewDismissEndScale);
+ return sb.toString();
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
new file mode 100644
index 000000000..796921f71
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
@@ -0,0 +1,116 @@
+/*
+ * 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.content.res.Resources;
+
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.PunctuationSuggestions;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.StringUtils;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+public final class SpacingAndPunctuations {
+ private final int[] mSortedSymbolsPrecededBySpace;
+ private final int[] mSortedSymbolsFollowedBySpace;
+ private final int[] mSortedWordConnectors;
+ public final int[] mSortedWordSeparators;
+ public final PunctuationSuggestions mSuggestPuncList;
+ private final int mSentenceSeparator;
+ public final String mSentenceSeparatorAndSpace;
+ public final boolean mCurrentLanguageHasSpaces;
+ public final boolean mUsesAmericanTypography;
+ public final boolean mUsesGermanRules;
+
+ public SpacingAndPunctuations(final Resources res) {
+ // To be able to binary search the code point. See {@link #isUsuallyPrecededBySpace(int)}.
+ mSortedSymbolsPrecededBySpace = StringUtils.toSortedCodePointArray(
+ res.getString(R.string.symbols_preceded_by_space));
+ // To be able to binary search the code point. See {@link #isUsuallyFollowedBySpace(int)}.
+ mSortedSymbolsFollowedBySpace = StringUtils.toSortedCodePointArray(
+ res.getString(R.string.symbols_followed_by_space));
+ // To be able to binary search the code point. See {@link #isWordConnector(int)}.
+ mSortedWordConnectors = StringUtils.toSortedCodePointArray(
+ res.getString(R.string.symbols_word_connectors));
+ mSortedWordSeparators = StringUtils.toSortedCodePointArray(
+ res.getString(R.string.symbols_word_separators));
+ mSentenceSeparator = res.getInteger(R.integer.sentence_separator);
+ mSentenceSeparatorAndSpace = new String(new int[] {
+ mSentenceSeparator, Constants.CODE_SPACE }, 0, 2);
+ mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces);
+ final Locale locale = res.getConfiguration().locale;
+ // Heuristic: we use American Typography rules because it's the most common rules for all
+ // English variants. German rules (not "German typography") also have small gotchas.
+ mUsesAmericanTypography = Locale.ENGLISH.getLanguage().equals(locale.getLanguage());
+ mUsesGermanRules = Locale.GERMAN.getLanguage().equals(locale.getLanguage());
+ final String[] suggestPuncsSpec = MoreKeySpec.splitKeySpecs(
+ res.getString(R.string.suggested_punctuations));
+ mSuggestPuncList = PunctuationSuggestions.newPunctuationSuggestions(suggestPuncsSpec);
+ }
+
+ public boolean isWordSeparator(final int code) {
+ return Arrays.binarySearch(mSortedWordSeparators, code) >= 0;
+ }
+
+ public boolean isWordConnector(final int code) {
+ return Arrays.binarySearch(mSortedWordConnectors, code) >= 0;
+ }
+
+ public boolean isWordCodePoint(final int code) {
+ return Character.isLetter(code) || isWordConnector(code);
+ }
+
+ public boolean isUsuallyPrecededBySpace(final int code) {
+ return Arrays.binarySearch(mSortedSymbolsPrecededBySpace, code) >= 0;
+ }
+
+ public boolean isUsuallyFollowedBySpace(final int code) {
+ return Arrays.binarySearch(mSortedSymbolsFollowedBySpace, code) >= 0;
+ }
+
+ public boolean isSentenceSeparator(final int code) {
+ return code == mSentenceSeparator;
+ }
+
+ public String dump() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("mSortedSymbolsPrecededBySpace = ");
+ sb.append("" + Arrays.toString(mSortedSymbolsPrecededBySpace));
+ sb.append("\n mSortedSymbolsFollowedBySpace = ");
+ sb.append("" + Arrays.toString(mSortedSymbolsFollowedBySpace));
+ sb.append("\n mSortedWordConnectors = ");
+ sb.append("" + Arrays.toString(mSortedWordConnectors));
+ sb.append("\n mSortedWordSeparators = ");
+ sb.append("" + Arrays.toString(mSortedWordSeparators));
+ sb.append("\n mSuggestPuncList = ");
+ sb.append("" + mSuggestPuncList);
+ sb.append("\n mSentenceSeparator = ");
+ sb.append("" + mSentenceSeparator);
+ sb.append("\n mSentenceSeparatorAndSpace = ");
+ sb.append("" + mSentenceSeparatorAndSpace);
+ sb.append("\n mCurrentLanguageHasSpaces = ");
+ sb.append("" + mCurrentLanguageHasSpaces);
+ sb.append("\n mUsesAmericanTypography = ");
+ sb.append("" + mUsesAmericanTypography);
+ sb.append("\n mUsesGermanRules = ");
+ sb.append("" + mUsesGermanRules);
+ return sb.toString();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
index c4a813c24..5072fabd6 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
@@ -38,7 +38,7 @@ import com.android.inputmethod.compat.ViewCompatUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.settings.SettingsActivity;
import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import java.util.ArrayList;
@@ -74,21 +74,21 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
private SettingsPoolingHandler mHandler;
private static final class SettingsPoolingHandler
- extends StaticInnerHandlerWrapper<SetupWizardActivity> {
+ extends LeakGuardHandlerWrapper<SetupWizardActivity> {
private static final int MSG_POLLING_IME_SETTINGS = 0;
private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
private final InputMethodManager mImmInHandler;
- public SettingsPoolingHandler(final SetupWizardActivity outerInstance,
+ public SettingsPoolingHandler(final SetupWizardActivity ownerInstance,
final InputMethodManager imm) {
- super(outerInstance);
+ super(ownerInstance);
mImmInHandler = imm;
}
@Override
public void handleMessage(final Message msg) {
- final SetupWizardActivity setupWizardActivity = getOuterInstance();
+ final SetupWizardActivity setupWizardActivity = getOwnerInstance();
if (setupWizardActivity == null) {
return;
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 503b18b1b..6a52481b9 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -37,6 +37,7 @@ import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary
import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
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.StringUtils;
@@ -267,6 +268,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
// if it doesn't. See documentation for binarySearch.
final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
+ // Weak <- insertIndex == 0, ..., insertIndex == mLength -> Strong
if (insertIndex == 0 && mLength >= mMaxLength) {
// In the future, we may want to keep track of the best suggestion score even if
// we are asked for 0 suggestions. In this case, we can use the following
@@ -284,11 +286,6 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
// }
return true;
}
- if (insertIndex >= mMaxLength) {
- // We found a suggestion, but its score is too weak to be kept considering
- // the suggestion limit.
- return true;
- }
final String wordString = new String(word, wordOffset, wordLength);
if (mLength < mMaxLength) {
@@ -296,12 +293,13 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
++mLength;
System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen);
mSuggestions.add(insertIndex, wordString);
+ mScores[insertIndex] = score;
} else {
- System.arraycopy(mScores, 1, mScores, 0, insertIndex);
+ System.arraycopy(mScores, 1, mScores, 0, insertIndex - 1);
mSuggestions.add(insertIndex, wordString);
mSuggestions.remove(0);
+ mScores[insertIndex - 1] = score;
}
- mScores[insertIndex] = score;
return true;
}
@@ -320,7 +318,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
hasRecommendedSuggestions = false;
} else {
gatheredSuggestions = EMPTY_STRING_ARRAY;
- final float normalizedScore = BinaryDictionary.calcNormalizedScore(
+ final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
mOriginalText, mBestSuggestion, mBestScore);
hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
}
@@ -355,7 +353,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
final int bestScore = mScores[mLength - 1];
final String bestSuggestion = mSuggestions.get(0);
final float normalizedScore =
- BinaryDictionary.calcNormalizedScore(
+ BinaryDictionaryUtils.calcNormalizedScore(
mOriginalText, bestSuggestion.toString(), bestScore);
hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
if (DBG) {
@@ -383,6 +381,8 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
new Thread("spellchecker_close_dicts") {
@Override
public void run() {
+ // Contacts dictionary can be closed multiple times here. If the dictionary is
+ // already closed, extra closings are no-ops, so it's safe.
for (DictionaryPool pool : oldPools.values()) {
pool.close();
}
@@ -428,7 +428,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
final String localeStr = locale.toString();
UserBinaryDictionary userDictionary = mUserDictionaries.get(localeStr);
if (null == userDictionary) {
- userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, localeStr, true);
+ userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, locale, true);
mUserDictionaries.put(localeStr, userDictionary);
}
dictionaryCollection.addDictionary(userDictionary);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index d6e5b75ad..69d092751 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -28,11 +28,13 @@ import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
+import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.StringUtils;
@@ -312,16 +314,21 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
false /* reportAsTypo */);
}
final WordComposer composer = new WordComposer();
- final int length = text.length();
- for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
- final int codePoint = text.codePointAt(i);
- composer.addKeyInfo(codePoint, dictInfo.getKeyboard(codePoint));
+ final int[] codePoints = StringUtils.toCodePointArray(text);
+ final int[] coordinates;
+ if (null == dictInfo.mKeyboard) {
+ coordinates = CoordinateUtils.newCoordinateArray(codePoints.length,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+ } else {
+ coordinates = dictInfo.mKeyboard.getCoordinates(codePoints);
}
+ composer.setComposingWord(codePoints, coordinates, null /* previousWord */);
// TODO: make a spell checker option to block offensive words or not
final ArrayList<SuggestedWordInfo> suggestions =
dictInfo.mDictionary.getSuggestions(composer, prevWord,
dictInfo.getProximityInfo(), true /* blockOffensiveWords */,
- null /* additionalFeaturesOptions */);
+ null /* additionalFeaturesOptions */,
+ null /* inOutLanguageWeight */);
if (suggestions != null) {
for (final SuggestedWordInfo suggestion : suggestions) {
final String suggestionStr = suggestion.mWord;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
index b77f3e2c5..1ffe50681 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
@@ -27,7 +27,7 @@ import com.android.inputmethod.keyboard.ProximityInfo;
*/
public final class DictAndKeyboard {
public final Dictionary mDictionary;
- private final Keyboard mKeyboard;
+ public final Keyboard mKeyboard;
private final Keyboard mManualShiftedKeyboard;
public DictAndKeyboard(
@@ -43,13 +43,6 @@ public final class DictAndKeyboard {
keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED);
}
- public Keyboard getKeyboard(final int codePoint) {
- if (mKeyboard == null) {
- return null;
- }
- return mKeyboard.getKey(codePoint) != null ? mKeyboard : mManualShiftedKeyboard;
- }
-
public ProximityInfo getProximityInfo() {
return mKeyboard == null ? null : mKeyboard.getProximityInfo();
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index a0aed2829..c99264347 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -49,10 +49,12 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> {
final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
private final static DictAndKeyboard dummyDict = new DictAndKeyboard(
new Dictionary(Dictionary.TYPE_MAIN) {
+ // TODO: this dummy dictionary should be a singleton in the Dictionary class.
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final float[] inOutLanguageWeight) {
return noSuggestions;
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
index 999ca775b..186dafd29 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -39,7 +39,7 @@ public final class SpellCheckerSettingsFragment extends PreferenceFragment {
addPreferencesFromResource(R.xml.spell_checker_settings);
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
- preferenceScreen.setTitle(ApplicationUtils.getAcitivityTitleResId(
+ preferenceScreen.setTitle(ApplicationUtils.getActivityTitleResId(
getActivity(), SpellCheckerSettingsActivity.class));
}
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index acd47450b..a104baa08 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -47,10 +47,10 @@ public final class MoreSuggestions extends Keyboard {
}
private static final class MoreSuggestionsParam extends KeyboardParams {
- private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS];
- private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS];
- private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS];
- private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS];
+ private final int[] mWidths = new int[SuggestedWords.MAX_SUGGESTIONS];
+ private final int[] mRowNumbers = new int[SuggestedWords.MAX_SUGGESTIONS];
+ private final int[] mColumnOrders = new int[SuggestedWords.MAX_SUGGESTIONS];
+ private final int[] mNumColumnsInRow = new int[SuggestedWords.MAX_SUGGESTIONS];
private static final int MAX_COLUMNS_IN_ROW = 3;
private int mNumRows;
public Drawable mDivider;
@@ -66,16 +66,17 @@ public final class MoreSuggestions extends Keyboard {
clearKeys();
mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
mDividerWidth = mDivider.getIntrinsicWidth();
- final float padding = res.getDimension(R.dimen.more_suggestions_key_horizontal_padding);
+ final float padding = res.getDimension(
+ R.dimen.config_more_suggestions_key_horizontal_padding);
int row = 0;
int index = fromIndex;
int rowStartIndex = fromIndex;
- final int size = Math.min(suggestedWords.size(), SuggestionStripView.MAX_SUGGESTIONS);
+ final int size = Math.min(suggestedWords.size(), SuggestedWords.MAX_SUGGESTIONS);
while (index < size) {
- final String word = suggestedWords.getWord(index);
+ final String word = suggestedWords.getLabel(index);
// TODO: Should take care of text x-scaling.
- mWidths[index] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
+ mWidths[index] = (int)(TypefaceUtils.getStringWidth(word, paint) + padding);
final int numColumn = index - rowStartIndex + 1;
final int columnWidth =
(maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
@@ -205,13 +206,13 @@ public final class MoreSuggestions extends Keyboard {
final int x = params.getX(index);
final int y = params.getY(index);
final int width = params.getWidth(index);
- final String word = mSuggestedWords.getWord(index);
+ final String word = mSuggestedWords.getLabel(index);
final String info = mSuggestedWords.getDebugString(index);
final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE;
- final Key key = new Key(
- params, word, info, KeyboardIconsSet.ICON_UNDEFINED, indexInMoreSuggestions,
- null /* outputText */, x, y, width, params.mDefaultRowHeight,
- 0 /* labelFlags */, Key.BACKGROUND_TYPE_NORMAL);
+ final Key key = new Key(word, KeyboardIconsSet.ICON_UNDEFINED,
+ indexInMoreSuggestions, null /* outputText */, info, 0 /* labelFlags */,
+ Key.BACKGROUND_TYPE_NORMAL, x, y, width, params.mDefaultRowHeight,
+ params.mHorizontalGap, params.mVerticalGap);
params.markAsEdgeKey(key, index);
params.onAddKey(key);
final int columnNumber = params.getColumnNumber(index);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 0ebe37782..549ff0d9d 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -54,7 +54,7 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
public void adjustVerticalCorrectionForModalMode() {
// Set vertical correction to zero (Reset more keys keyboard sliding allowance
- // {@link R#dimen.more_keys_keyboard_slide_allowance}).
+ // {@link R#dimen.config_more_keys_keyboard_slide_allowance}).
mKeyDetector.setKeyboard(getKeyboard(), -getPaddingLeft(), -getPaddingTop());
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index faa5560e4..1d84bb59f 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -28,6 +28,7 @@ import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.support.v4.view.ViewCompat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
@@ -38,18 +39,18 @@ import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.util.AttributeSet;
import android.view.Gravity;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.PunctuationSuggestions;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.latin.utils.ViewLayoutUtils;
import java.util.ArrayList;
@@ -64,7 +65,7 @@ final class SuggestionStripLayoutHelper {
public final int mPadding;
public final int mDividerWidth;
public final int mSuggestionsStripHeight;
- public final int mSuggestionsCountInStrip;
+ private final int mSuggestionsCountInStrip;
public final int mMoreSuggestionsRowHeight;
private int mMaxMoreSuggestionsRow;
public final float mMinMoreSuggestionsWidth;
@@ -89,21 +90,18 @@ final class SuggestionStripLayoutHelper {
private final Drawable mMoreSuggestionsHint;
private static final String MORE_SUGGESTIONS_HINT = "\u2026";
private static final String LEFTWARDS_ARROW = "\u2190";
+ private static final String RIGHTWARDS_ARROW = "\u2192";
private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
- private final int mSuggestionStripOption;
+ private final int mSuggestionStripOptions;
// These constants are the flag values of
- // {@link R.styleable#SuggestionStripView_suggestionStripOption} attribute.
+ // {@link R.styleable#SuggestionStripView_suggestionStripOptions} attribute.
private static final int AUTO_CORRECT_BOLD = 0x01;
private static final int AUTO_CORRECT_UNDERLINE = 0x02;
private static final int VALID_TYPED_WORD_BOLD = 0x04;
- private final TextView mWordToSaveView;
- private final TextView mLeftwardsArrowView;
- private final TextView mHintToSaveView;
-
public SuggestionStripLayoutHelper(final Context context, final AttributeSet attrs,
final int defStyle, final ArrayList<TextView> wordViews,
final ArrayList<View> dividerViews, final ArrayList<TextView> debugInfoViews) {
@@ -119,12 +117,13 @@ final class SuggestionStripLayoutHelper {
mDividerWidth = dividerView.getMeasuredWidth();
final Resources res = wordView.getResources();
- mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
+ mSuggestionsStripHeight = res.getDimensionPixelSize(
+ R.dimen.config_suggestions_strip_height);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripView);
- mSuggestionStripOption = a.getInt(
- R.styleable.SuggestionStripView_suggestionStripOption, 0);
+ mSuggestionStripOptions = a.getInt(
+ R.styleable.SuggestionStripView_suggestionStripOptions, 0);
mAlphaObsoleted = ResourceUtils.getFraction(a,
R.styleable.SuggestionStripView_alphaObsoleted, 1.0f);
mColorValidTypedWord = a.getColor(R.styleable.SuggestionStripView_colorValidTypedWord, 0);
@@ -145,20 +144,17 @@ final class SuggestionStripLayoutHelper {
a.recycle();
mMoreSuggestionsHint = getMoreSuggestionsHint(res,
- res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect);
+ res.getDimension(R.dimen.config_more_suggestions_hint_text_size),
+ mColorAutoCorrect);
mCenterPositionInStrip = mSuggestionsCountInStrip / 2;
// Assuming there are at least three suggestions. Also, note that the suggestions are
// laid out according to script direction, so this is left of the center for LTR scripts
// and right of the center for RTL scripts.
mTypedWordPositionWhenAutocorrect = mCenterPositionInStrip - 1;
mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
- R.dimen.more_suggestions_bottom_gap);
- mMoreSuggestionsRowHeight = res.getDimensionPixelSize(R.dimen.more_suggestions_row_height);
-
- final LayoutInflater inflater = LayoutInflater.from(context);
- mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
- mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
- mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
+ R.dimen.config_more_suggestions_bottom_gap);
+ mMoreSuggestionsRowHeight = res.getDimensionPixelSize(
+ R.dimen.config_more_suggestions_row_height);
}
public int getMaxMoreSuggestionsRow() {
@@ -203,23 +199,25 @@ final class SuggestionStripLayoutHelper {
if (indexInSuggestedWords >= suggestedWords.size()) {
return null;
}
- final String word = suggestedWords.getWord(indexInSuggestedWords);
- final boolean isAutoCorrect = indexInSuggestedWords == 1
- && suggestedWords.willAutoCorrect();
- final boolean isTypedWordValid = indexInSuggestedWords == 0
- && suggestedWords.mTypedWordValid;
- if (!isAutoCorrect && !isTypedWordValid) {
+ final String word = suggestedWords.getLabel(indexInSuggestedWords);
+ // TODO: don't use the index to decide whether this is the auto-correction/typed word, as
+ // this is brittle
+ final boolean isAutoCorrection = suggestedWords.mWillAutoCorrect
+ && indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION;
+ final boolean isTypedWordValid = suggestedWords.mTypedWordValid
+ && indexInSuggestedWords == SuggestedWords.INDEX_OF_TYPED_WORD;
+ if (!isAutoCorrection && !isTypedWordValid) {
return word;
}
final int len = word.length();
final Spannable spannedWord = new SpannableString(word);
- final int option = mSuggestionStripOption;
- if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0)
- || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) {
+ final int options = mSuggestionStripOptions;
+ if ((isAutoCorrection && (options & AUTO_CORRECT_BOLD) != 0)
+ || (isTypedWordValid && (options & VALID_TYPED_WORD_BOLD) != 0)) {
spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
- if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) {
+ if (isAutoCorrection && (options & AUTO_CORRECT_UNDERLINE) != 0) {
spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
return spannedWord;
@@ -229,7 +227,7 @@ final class SuggestionStripLayoutHelper {
final SuggestedWords suggestedWords) {
final int indexToDisplayMostImportantSuggestion;
final int indexToDisplaySecondMostImportantSuggestion;
- if (suggestedWords.willAutoCorrect()) {
+ if (suggestedWords.mWillAutoCorrect) {
indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION;
indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD;
} else {
@@ -246,35 +244,36 @@ final class SuggestionStripLayoutHelper {
return indexInSuggestedWords;
}
- private int getSuggestionTextColor(final int indexInSuggestedWords,
- final SuggestedWords suggestedWords) {
+ private int getSuggestionTextColor(final SuggestedWords suggestedWords,
+ final int indexInSuggestedWords) {
final int positionInStrip =
getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords);
- // TODO: Need to revisit this logic with bigram suggestions
- final boolean isSuggested = (indexInSuggestedWords != SuggestedWords.INDEX_OF_TYPED_WORD);
+ // Use identity for strings, not #equals : it's the typed word if it's the same object
+ final boolean isTypedWord =
+ suggestedWords.getWord(indexInSuggestedWords) == suggestedWords.mTypedWord;
final int color;
- if (positionInStrip == mCenterPositionInStrip && suggestedWords.willAutoCorrect()) {
+ if (positionInStrip == mCenterPositionInStrip && suggestedWords.mWillAutoCorrect) {
color = mColorAutoCorrect;
- } else if (positionInStrip == mCenterPositionInStrip && suggestedWords.mTypedWordValid) {
+ } else if (isTypedWord && suggestedWords.mTypedWordValid) {
color = mColorValidTypedWord;
- } else if (isSuggested) {
- color = mColorSuggested;
- } else {
+ } else if (isTypedWord) {
color = mColorTypedWord;
+ } else {
+ color = mColorSuggested;
}
if (LatinImeLogger.sDBG && suggestedWords.size() > 1) {
// If we auto-correct, then the autocorrection is in slot 0 and the typed word
// is in slot 1.
if (positionInStrip == mCenterPositionInStrip
&& AutoCorrectionUtils.shouldBlockAutoCorrectionBySafetyNet(
- suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION),
- suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD))) {
+ suggestedWords.getLabel(SuggestedWords.INDEX_OF_AUTO_CORRECTION),
+ suggestedWords.getLabel(SuggestedWords.INDEX_OF_TYPED_WORD))) {
return 0xFFFF0000;
}
}
- if (suggestedWords.mIsObsoleteSuggestions && isSuggested) {
+ if (suggestedWords.mIsObsoleteSuggestions && !isTypedWord) {
return applyAlpha(color, mAlphaObsoleted);
}
return color;
@@ -292,54 +291,65 @@ final class SuggestionStripLayoutHelper {
params.gravity = Gravity.CENTER;
}
- public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView,
- final ViewGroup placerView) {
- if (suggestedWords.mIsPunctuationSuggestions) {
- layoutPunctuationSuggestions(suggestedWords, stripView);
- return;
+ /**
+ * Layout suggestions to the suggestions strip. And returns the number of suggestions displayed
+ * in the suggestions strip.
+ *
+ * @param suggestedWords suggestions to be shown in the suggestions strip.
+ * @param stripView the suggestions strip view.
+ * @param placerView the view where the debug info will be placed.
+ * @return the number of suggestions displayed in the suggestions strip
+ */
+ public int layoutAndReturnSuggestionCountInStrip(final SuggestedWords suggestedWords,
+ final ViewGroup stripView, final ViewGroup placerView) {
+ if (suggestedWords.isPunctuationSuggestions()) {
+ return layoutPunctuationSuggestionsAndReturnSuggestionCountInStrip(
+ (PunctuationSuggestions)suggestedWords, stripView);
}
- final int countInStrip = mSuggestionsCountInStrip;
- setupWordViewsTextAndColor(suggestedWords, countInStrip);
+ setupWordViewsTextAndColor(suggestedWords, mSuggestionsCountInStrip);
final TextView centerWordView = mWordViews.get(mCenterPositionInStrip);
final int availableStripWidth = placerView.getWidth()
- placerView.getPaddingRight() - placerView.getPaddingLeft();
final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, availableStripWidth);
- if (getTextScaleX(centerWordView.getText(), centerWidth, centerWordView.getPaint())
- < MIN_TEXT_XSCALE) {
+ final int countInStrip;
+ if (suggestedWords.size() == 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.
- mMoreSuggestionsAvailable = (suggestedWords.size() > 1);
+ countInStrip = 1;
+ mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
layoutWord(mCenterPositionInStrip, availableStripWidth - mPadding);
stripView.addView(centerWordView);
setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT);
if (SuggestionStripView.DBG) {
layoutDebugInfo(mCenterPositionInStrip, placerView, availableStripWidth);
}
- return;
- }
-
- mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
- int x = 0;
- for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
- if (positionInStrip != 0) {
- final View divider = mDividerViews.get(positionInStrip);
- // Add divider if this isn't the left most suggestion in suggestions strip.
- addDivider(stripView, divider);
- x += divider.getMeasuredWidth();
- }
-
- final int width = getSuggestionWidth(positionInStrip, availableStripWidth);
- final TextView wordView = layoutWord(positionInStrip, width);
- stripView.addView(wordView);
- setLayoutWeight(wordView, getSuggestionWeight(positionInStrip),
- ViewGroup.LayoutParams.MATCH_PARENT);
- x += wordView.getMeasuredWidth();
-
- if (SuggestionStripView.DBG) {
- layoutDebugInfo(positionInStrip, placerView, x);
+ } else {
+ countInStrip = mSuggestionsCountInStrip;
+ mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
+ int x = 0;
+ for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
+ if (positionInStrip != 0) {
+ final View divider = mDividerViews.get(positionInStrip);
+ // Add divider if this isn't the left most suggestion in suggestions strip.
+ addDivider(stripView, divider);
+ x += divider.getMeasuredWidth();
+ }
+
+ final int width = getSuggestionWidth(positionInStrip, availableStripWidth);
+ final TextView wordView = layoutWord(positionInStrip, width);
+ stripView.addView(wordView);
+ setLayoutWeight(wordView, getSuggestionWeight(positionInStrip),
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ x += wordView.getMeasuredWidth();
+
+ if (SuggestionStripView.DBG) {
+ layoutDebugInfo(positionInStrip, placerView, x);
+ }
}
}
+ return countInStrip;
}
/**
@@ -431,7 +441,7 @@ final class SuggestionStripLayoutHelper {
// {@link SuggestionStripView#onClick(View)}.
wordView.setTag(indexInSuggestedWords);
wordView.setText(getStyledSuggestedWord(suggestedWords, indexInSuggestedWords));
- wordView.setTextColor(getSuggestionTextColor(positionInStrip, suggestedWords));
+ wordView.setTextColor(getSuggestionTextColor(suggestedWords, indexInSuggestedWords));
if (SuggestionStripView.DBG) {
mDebugInfoViews.get(positionInStrip).setText(
suggestedWords.getDebugString(indexInSuggestedWords));
@@ -439,9 +449,9 @@ final class SuggestionStripLayoutHelper {
}
}
- private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords,
- final ViewGroup stripView) {
- final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
+ private int layoutPunctuationSuggestionsAndReturnSuggestionCountInStrip(
+ final PunctuationSuggestions punctuationSuggestions, final ViewGroup stripView) {
+ final int countInStrip = Math.min(punctuationSuggestions.size(), PUNCTUATIONS_IN_STRIP);
for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
if (positionInStrip != 0) {
// Add divider if this isn't the left most suggestion in suggestions strip.
@@ -454,66 +464,63 @@ final class SuggestionStripLayoutHelper {
// {@link TextView#getTag()} is used to get the index in suggestedWords at
// {@link SuggestionStripView#onClick(View)}.
wordView.setTag(positionInStrip);
- wordView.setText(suggestedWords.getWord(positionInStrip));
+ wordView.setText(punctuationSuggestions.getLabel(positionInStrip));
wordView.setTextScaleX(1.0f);
wordView.setCompoundDrawables(null, null, null, null);
stripView.addView(wordView);
setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight);
}
- mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
+ mMoreSuggestionsAvailable = (punctuationSuggestions.size() > countInStrip);
+ return countInStrip;
}
- public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView,
- final int stripWidth, final CharSequence hintText, final OnClickListener listener) {
+ public void layoutAddToDictionaryHint(final String word, final ViewGroup addToDictionaryStrip,
+ final int stripWidth) {
final int width = stripWidth - mDividerWidth - mPadding * 2;
- final TextView wordView = mWordToSaveView;
+ final TextView wordView = (TextView)addToDictionaryStrip.findViewById(R.id.word_to_save);
wordView.setTextColor(mColorTypedWord);
final int wordWidth = (int)(width * mCenterSuggestionWeight);
- final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint());
+ final CharSequence wordToSave = getEllipsizedText(word, wordWidth, wordView.getPaint());
final float wordScaleX = wordView.getTextScaleX();
- // {@link TextView#setTag()} is used to hold the word to be added to dictionary. The word
- // will be extracted at {@link #getAddToDictionaryWord()}.
- wordView.setTag(word);
- wordView.setText(text);
+ wordView.setText(wordToSave);
wordView.setTextScaleX(wordScaleX);
- stripView.addView(wordView);
setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
- stripView.addView(mDividerViews.get(0));
-
- final TextView leftArrowView = mLeftwardsArrowView;
- leftArrowView.setTextColor(mColorAutoCorrect);
- leftArrowView.setText(LEFTWARDS_ARROW);
- stripView.addView(leftArrowView);
-
- final TextView hintView = mHintToSaveView;
- hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ final TextView hintView = (TextView)addToDictionaryStrip.findViewById(
+ R.id.hint_add_to_dictionary);
hintView.setTextColor(mColorAutoCorrect);
- final int hintWidth = width - wordWidth - leftArrowView.getWidth();
- final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
- hintView.setText(hintText);
+ final boolean isRtlLanguage = (ViewCompat.getLayoutDirection(addToDictionaryStrip)
+ == ViewCompat.LAYOUT_DIRECTION_RTL);
+ final String arrow = isRtlLanguage ? RIGHTWARDS_ARROW : LEFTWARDS_ARROW;
+ final Resources res = addToDictionaryStrip.getResources();
+ final boolean isRtlSystem = SubtypeLocaleUtils.isRtlLanguage(res.getConfiguration().locale);
+ final CharSequence hintText = res.getText(R.string.hint_add_to_dictionary);
+ final String hintWithArrow = (isRtlLanguage == isRtlSystem)
+ ? (arrow + hintText) : (hintText + arrow);
+ final int hintWidth = width - wordWidth;
+ hintView.setTextScaleX(1.0f); // Reset textScaleX.
+ final float hintScaleX = getTextScaleX(hintWithArrow, hintWidth, hintView.getPaint());
+ hintView.setText(hintWithArrow);
hintView.setTextScaleX(hintScaleX);
- stripView.addView(hintView);
setLayoutWeight(
hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
-
- wordView.setOnClickListener(listener);
- leftArrowView.setOnClickListener(listener);
- hintView.setOnClickListener(listener);
- }
-
- public String getAddToDictionaryWord() {
- // String tag is set at
- // {@link #layoutAddToDictionaryHint(String,ViewGroup,int,CharSequence,OnClickListener}.
- return (String)mWordToSaveView.getTag();
}
- public boolean isAddToDictionaryShowing(final View v) {
- return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView;
+ public void layoutImportantNotice(final View importantNoticeStrip,
+ final String importantNoticeTitle) {
+ final TextView titleView = (TextView)importantNoticeStrip.findViewById(
+ R.id.important_notice_title);
+ final int width = titleView.getWidth() - titleView.getPaddingLeft()
+ - titleView.getPaddingRight();
+ titleView.setTextColor(mColorAutoCorrect);
+ titleView.setText(importantNoticeTitle);
+ titleView.setTextScaleX(1.0f); // Reset textScaleX.
+ final float titleScaleX = getTextScaleX(importantNoticeTitle, width, titleView.getPaint());
+ titleView.setTextScaleX(titleScaleX);
}
- private static void setLayoutWeight(final View v, final float weight, final int height) {
+ static void setLayoutWeight(final View v, final float weight, final int height) {
final ViewGroup.LayoutParams lp = v.getLayoutParams();
if (lp instanceof LinearLayout.LayoutParams) {
final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
@@ -527,7 +534,7 @@ final class SuggestionStripLayoutHelper {
final TextPaint paint) {
paint.setTextScaleX(1.0f);
final int width = getTextWidth(text, paint);
- if (width <= maxWidth) {
+ if (width <= maxWidth || maxWidth <= 0) {
return 1.0f;
}
return maxWidth / (float)width;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 75f17c559..a0793b133 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -18,7 +18,11 @@ package com.android.inputmethod.latin.suggestions;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Color;
+import android.support.v4.view.ViewCompat;
+import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -26,22 +30,25 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.keyboard.MoreKeysPanel;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
@@ -50,15 +57,16 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
OnLongClickListener {
public interface Listener {
public void addWordToUserDictionary(String word);
+ public void showImportantNoticeContents();
public void pickSuggestionManually(int index, SuggestedWordInfo word);
}
- // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
- public static final int MAX_SUGGESTIONS = 18;
-
static final boolean DBG = LatinImeLogger.sDBG;
+ private static final float DEBUG_INFO_TEXT_SIZE_IN_DIP = 6.0f;
private final ViewGroup mSuggestionsStrip;
+ private final ViewGroup mAddToDictionaryStrip;
+ private final View mImportantNoticeStrip;
MainKeyboardView mMainKeyboardView;
private final View mMoreSuggestionsContainer;
@@ -71,8 +79,54 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
Listener mListener;
private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+ private int mSuggestionsCountInStrip;
private final SuggestionStripLayoutHelper mLayoutHelper;
+ private final StripVisibilityGroup mStripVisibilityGroup;
+
+ private static class StripVisibilityGroup {
+ private final View mSuggestionsStrip;
+ private final View mAddToDictionaryStrip;
+ private final View mImportantNoticeStrip;
+
+ public StripVisibilityGroup(final View suggestionsStrip, final View addToDictionaryStrip,
+ final View importantNoticeStrip) {
+ mSuggestionsStrip = suggestionsStrip;
+ mAddToDictionaryStrip = addToDictionaryStrip;
+ mImportantNoticeStrip = importantNoticeStrip;
+ showSuggestionsStrip();
+ }
+
+ public void setLayoutDirection(final boolean isRtlLanguage) {
+ final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL
+ : ViewCompat.LAYOUT_DIRECTION_LTR;
+ ViewCompat.setLayoutDirection(mSuggestionsStrip, layoutDirection);
+ ViewCompat.setLayoutDirection(mAddToDictionaryStrip, layoutDirection);
+ ViewCompat.setLayoutDirection(mImportantNoticeStrip, layoutDirection);
+ }
+
+ public void showSuggestionsStrip() {
+ mSuggestionsStrip.setVisibility(VISIBLE);
+ mAddToDictionaryStrip.setVisibility(INVISIBLE);
+ mImportantNoticeStrip.setVisibility(INVISIBLE);
+ }
+
+ public void showAddToDictionaryStrip() {
+ mSuggestionsStrip.setVisibility(INVISIBLE);
+ mAddToDictionaryStrip.setVisibility(VISIBLE);
+ mImportantNoticeStrip.setVisibility(INVISIBLE);
+ }
+
+ public void showImportantNoticeStrip() {
+ mSuggestionsStrip.setVisibility(INVISIBLE);
+ mAddToDictionaryStrip.setVisibility(INVISIBLE);
+ mImportantNoticeStrip.setVisibility(VISIBLE);
+ }
+
+ public boolean isShowingAddToDictionaryStrip() {
+ return mAddToDictionaryStrip.getVisibility() == VISIBLE;
+ }
+ }
/**
* Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user.
@@ -91,15 +145,23 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
inflater.inflate(R.layout.suggestions_strip, this);
mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip);
- for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) {
- final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null);
+ mAddToDictionaryStrip = (ViewGroup)findViewById(R.id.add_to_dictionary_strip);
+ mImportantNoticeStrip = findViewById(R.id.important_notice_strip);
+ mStripVisibilityGroup = new StripVisibilityGroup(mSuggestionsStrip, mAddToDictionaryStrip,
+ mImportantNoticeStrip);
+
+ for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) {
+ final TextView word = new TextView(context, null, R.attr.suggestionWordStyle);
word.setOnClickListener(this);
word.setOnLongClickListener(this);
mWordViews.add(word);
final View divider = inflater.inflate(R.layout.suggestion_divider, null);
divider.setOnClickListener(this);
mDividerViews.add(divider);
- mDebugInfoViews.add((TextView)inflater.inflate(R.layout.suggestion_info, null));
+ final TextView info = new TextView(context, null, R.attr.suggestionWordStyle);
+ info.setTextColor(Color.WHITE);
+ info.setTextSize(TypedValue.COMPLEX_UNIT_DIP, DEBUG_INFO_TEXT_SIZE_IN_DIP);
+ mDebugInfoViews.add(info);
}
mLayoutHelper = new SuggestionStripLayoutHelper(
@@ -112,7 +174,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
final Resources res = context.getResources();
mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset(
- R.dimen.more_suggestions_modal_tolerance);
+ R.dimen.config_more_suggestions_modal_tolerance);
mMoreSuggestionsSlidingDetector = new GestureDetector(
context, mMoreSuggestionsSlidingListener);
}
@@ -126,13 +188,16 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view);
}
- public void setSuggestions(final SuggestedWords suggestedWords) {
+ public void setSuggestions(final SuggestedWords suggestedWords, final boolean isRtlLanguage) {
clear();
+ mStripVisibilityGroup.setLayoutDirection(isRtlLanguage);
mSuggestedWords = suggestedWords;
- mLayoutHelper.layout(mSuggestedWords, mSuggestionsStrip, this);
+ mSuggestionsCountInStrip = mLayoutHelper.layoutAndReturnSuggestionCountInStrip(
+ mSuggestedWords, mSuggestionsStrip, this);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords);
}
+ mStripVisibilityGroup.showSuggestionsStrip();
}
public int setMoreSuggestionsHeight(final int remainingHeight) {
@@ -140,14 +205,16 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
public boolean isShowingAddToDictionaryHint() {
- return mSuggestionsStrip.getChildCount() > 0
- && mLayoutHelper.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0));
+ return mStripVisibilityGroup.isShowingAddToDictionaryStrip();
}
- public void showAddToDictionaryHint(final String word, final CharSequence hintText) {
- clear();
- mLayoutHelper.layoutAddToDictionaryHint(
- word, mSuggestionsStrip, getWidth(), hintText, this);
+ public void showAddToDictionaryHint(final String word) {
+ mLayoutHelper.layoutAddToDictionaryHint(word, mAddToDictionaryStrip, getWidth());
+ // {@link TextView#setTag()} is used to hold the word to be added to dictionary. The word
+ // will be extracted at {@link #onClick(View)}.
+ mAddToDictionaryStrip.setTag(word);
+ mAddToDictionaryStrip.setOnClickListener(this);
+ mStripVisibilityGroup.showAddToDictionaryStrip();
}
public boolean dismissAddToDictionaryHint() {
@@ -158,31 +225,65 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
return false;
}
+ // This method checks if we should show the important notice (checks on permanent storage if
+ // it has been shown once already or not, and if in the setup wizard). If applicable, it shows
+ // the notice. In all cases, it returns true if it was shown, false otherwise.
+ public boolean maybeShowImportantNoticeTitle(final InputAttributes inputAttributes) {
+ if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext(), inputAttributes)) {
+ return false;
+ }
+ if (getWidth() <= 0) {
+ return false;
+ }
+ final String importantNoticeTitle = ImportantNoticeUtils.getNextImportantNoticeTitle(
+ getContext());
+ if (TextUtils.isEmpty(importantNoticeTitle)) {
+ return false;
+ }
+ if (isShowingMoreSuggestionPanel()) {
+ dismissMoreSuggestionsPanel();
+ }
+ mLayoutHelper.layoutImportantNotice(mImportantNoticeStrip, importantNoticeTitle);
+ mStripVisibilityGroup.showImportantNoticeStrip();
+ mImportantNoticeStrip.setOnClickListener(this);
+ return true;
+ }
+
public void clear() {
mSuggestionsStrip.removeAllViews();
- removeAllViews();
- addView(mSuggestionsStrip);
- mMoreSuggestionsView.dismissMoreKeysPanel();
+ removeAllDebugInfoViews();
+ mStripVisibilityGroup.showSuggestionsStrip();
+ dismissMoreSuggestionsPanel();
+ }
+
+ private void removeAllDebugInfoViews() {
+ // The debug info views may be placed as children views of this {@link SuggestionStripView}.
+ for (final View debugInfoView : mDebugInfoViews) {
+ final ViewParent parent = debugInfoView.getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup)parent).removeView(debugInfoView);
+ }
+ }
}
private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener() {
@Override
public void onSuggestionSelected(final int index, final SuggestedWordInfo wordInfo) {
mListener.pickSuggestionManually(index, wordInfo);
- mMoreSuggestionsView.dismissMoreKeysPanel();
+ dismissMoreSuggestionsPanel();
}
@Override
public void onCancelInput() {
- mMoreSuggestionsView.dismissMoreKeysPanel();
+ dismissMoreSuggestionsPanel();
}
};
private final MoreKeysPanel.Controller mMoreSuggestionsController =
new MoreKeysPanel.Controller() {
@Override
- public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {
- mMainKeyboardView.onDismissMoreKeysPanel(panel);
+ public void onDismissMoreKeysPanel() {
+ mMainKeyboardView.onDismissMoreKeysPanel();
}
@Override
@@ -191,11 +292,19 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
@Override
- public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {
- mMoreSuggestionsView.dismissMoreKeysPanel();
+ public void onCancelMoreKeysPanel() {
+ dismissMoreSuggestionsPanel();
}
};
+ public boolean isShowingMoreSuggestionPanel() {
+ return mMoreSuggestionsView.isShowingInParent();
+ }
+
+ public void dismissMoreSuggestionsPanel() {
+ mMoreSuggestionsView.dismissMoreKeysPanel();
+ }
+
@Override
public boolean onLongClick(final View view) {
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
@@ -204,7 +313,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
boolean showMoreSuggestions() {
- final Keyboard parentKeyboard = KeyboardSwitcher.getInstance().getKeyboard();
+ final Keyboard parentKeyboard = mMainKeyboardView.getKeyboard();
if (parentKeyboard == null) {
return false;
}
@@ -212,11 +321,17 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
if (!layoutHelper.mMoreSuggestionsAvailable) {
return false;
}
+ // Dismiss another {@link MoreKeysPanel} that may be being showed, for example
+ // {@link MoreKeysKeyboardView}.
+ mMainKeyboardView.onDismissMoreKeysPanel();
+ // Dismiss all key previews and sliding key input preview that may be being showed.
+ mMainKeyboardView.dismissAllKeyPreviews();
+ mMainKeyboardView.dismissSlidingKeyInputPreview();
final int stripWidth = getWidth();
final View container = mMoreSuggestionsContainer;
final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight();
final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
- builder.layout(mSuggestedWords, layoutHelper.mSuggestionsCountInStrip, maxWidth,
+ builder.layout(mSuggestedWords, mSuggestionsCountInStrip, maxWidth,
(int)(maxWidth * layoutHelper.mMinMoreSuggestionsWidth),
layoutHelper.getMaxMoreSuggestionsRow(), parentKeyboard);
mMoreSuggestionsView.setKeyboard(builder.build());
@@ -227,20 +342,16 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
final int pointY = -layoutHelper.mMoreSuggestionsBottomGap;
moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY,
mMoreSuggestionsListener);
- mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
mOriginX = mLastX;
mOriginY = mLastY;
- for (int i = 0; i < layoutHelper.mSuggestionsCountInStrip; i++) {
+ for (int i = 0; i < mSuggestionsCountInStrip; i++) {
mWordViews.get(i).setPressed(false);
}
return true;
}
- // Working variables for onLongClick and dispatchTouchEvent.
- private int mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE;
- private static final int MORE_SUGGESTIONS_IN_MODAL_MODE = 0;
- private static final int MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING = 1;
- private static final int MORE_SUGGESTIONS_IN_SLIDING_MODE = 2;
+ // Working variables for {@link #onLongClick(View)} and
+ // {@link onInterceptTouchEvent(MotionEvent)}.
private int mLastX;
private int mLastY;
private int mOriginX;
@@ -260,36 +371,39 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
};
@Override
- public boolean dispatchTouchEvent(final MotionEvent me) {
+ public boolean onInterceptTouchEvent(final MotionEvent me) {
if (!mMoreSuggestionsView.isShowingInParent()) {
mLastX = (int)me.getX();
mLastY = (int)me.getY();
- if (mMoreSuggestionsSlidingDetector.onTouchEvent(me)) {
- return true;
- }
- return super.dispatchTouchEvent(me);
+ return mMoreSuggestionsSlidingDetector.onTouchEvent(me);
}
final int action = me.getAction();
final int index = me.getActionIndex();
final int x = (int)me.getX(index);
final int y = (int)me.getY(index);
-
- if (mMoreSuggestionsMode == MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING) {
- if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance
- || mOriginY - y >= mMoreSuggestionsModalTolerance) {
- // Decided to be in the sliding input mode only when the touch point has been moved
- // upward.
- mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE;
- } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
- // Decided to be in the modal input mode
- mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE;
- mMoreSuggestionsView.adjustVerticalCorrectionForModalMode();
- }
+ if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance
+ || mOriginY - y >= mMoreSuggestionsModalTolerance) {
+ // Decided to be in the sliding input mode only when the touch point has been moved
+ // upward. Further {@link MotionEvent}s will be delivered to
+ // {@link #onTouchEvent(MotionEvent)}.
return true;
}
- // MORE_SUGGESTIONS_IN_SLIDING_MODE
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
+ // Decided to be in the modal input mode.
+ mMoreSuggestionsView.adjustVerticalCorrectionForModalMode();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(final MotionEvent me) {
+ // In the sliding input mode. {@link MotionEvent} should be forwarded to
+ // {@link MoreSuggestionsView}.
+ final int index = me.getActionIndex();
+ final int x = (int)me.getX(index);
+ final int y = (int)me.getY(index);
me.setLocation(mMoreSuggestionsView.translateX(x), mMoreSuggestionsView.translateY(y));
mMoreSuggestionsView.onTouchEvent(me);
return true;
@@ -297,31 +411,44 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
public void onClick(final View view) {
- if (mLayoutHelper.isAddToDictionaryShowing(view)) {
- mListener.addWordToUserDictionary(mLayoutHelper.getAddToDictionaryWord());
+ if (view == mImportantNoticeStrip) {
+ mListener.showImportantNoticeContents();
+ return;
+ }
+ final Object tag = view.getTag();
+ // {@link String} tag is set at {@link #showAddToDictionaryHint(String,CharSequence)}.
+ if (tag instanceof String) {
+ final String wordToSave = (String)tag;
+ mListener.addWordToUserDictionary(wordToSave);
clear();
return;
}
- final Object tag = view.getTag();
- // Integer tag is set at
+ // {@link Integer} tag is set at
// {@link SuggestionStripLayoutHelper#setupWordViewsTextAndColor(SuggestedWords,int)} and
// {@link SuggestionStripLayoutHelper#layoutPunctuationSuggestions(SuggestedWords,ViewGroup}
- if (!(tag instanceof Integer)) {
- return;
- }
- final int index = (Integer) tag;
- if (index >= mSuggestedWords.size()) {
- return;
+ if (tag instanceof Integer) {
+ final int index = (Integer) tag;
+ if (index >= mSuggestedWords.size()) {
+ return;
+ }
+ final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index);
+ mListener.pickSuggestionManually(index, wordInfo);
}
-
- final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index);
- mListener.pickSuggestionManually(index, wordInfo);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mMoreSuggestionsView.dismissMoreKeysPanel();
+ dismissMoreSuggestionsPanel();
+ }
+
+ @Override
+ protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
+ // Called by the framework when the size is known. Show the important notice if applicable.
+ // This may be overriden by showing suggestions later, if applicable.
+ if (oldw <= 0 && w > 0) {
+ maybeShowImportantNoticeTitle(Settings.getInstance().getCurrent().mInputAttributes);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/event/EventDecoderSpec.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
index 303b4b4c9..52708455e 100644
--- a/java/src/com/android/inputmethod/event/EventDecoderSpec.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -14,13 +14,17 @@
* limitations under the License.
*/
-package com.android.inputmethod.event;
+package com.android.inputmethod.latin.suggestions;
+
+import com.android.inputmethod.latin.SuggestedWords;
/**
- * Class describing a decoder chain. This will depend on the language and the input medium (soft
- * or hard keyboard for example).
+ * An object that gives basic control of a suggestion strip and some info on it.
*/
-public class EventDecoderSpec {
- public EventDecoderSpec() {
- }
+public interface SuggestionStripViewAccessor {
+ public void showAddToDictionaryHint(final String word);
+ public boolean isShowingAddToDictionaryHint();
+ public void dismissAddToDictionaryHint();
+ public void setNeutralSuggestionStrip();
+ public void showSuggestionStrip(final SuggestedWords suggestedWords);
}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
index 32c4950da..97a924d7b 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -53,20 +53,24 @@ public class UserDictionaryList extends PreferenceFragment {
}
public static TreeSet<String> getUserDictionaryLocalesSet(Activity activity) {
- @SuppressWarnings("deprecation")
- final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI,
+ final Cursor cursor = activity.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
new String[] { UserDictionary.Words.LOCALE },
null, null, null);
final TreeSet<String> localeSet = new TreeSet<String>();
if (null == cursor) {
// The user dictionary service is not present or disabled. Return null.
return null;
- } else if (cursor.moveToFirst()) {
- final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);
- do {
- final String locale = cursor.getString(columnIndex);
- localeSet.add(null != locale ? locale : "");
- } while (cursor.moveToNext());
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);
+ do {
+ final String locale = cursor.getString(columnIndex);
+ localeSet.add(null != locale ? locale : "");
+ } while (cursor.moveToNext());
+ }
+ } finally {
+ cursor.close();
}
if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
// For ICS, we need to show "For all languages" in case that the keyboard locale
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
index 7571e87c5..cf2014a1a 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
@@ -140,6 +140,11 @@ public class UserDictionarySettings extends ListFragment {
}
mLocale = locale;
+ // WARNING: The following cursor is never closed! TODO: don't put that in a member, and
+ // make sure all cursors are correctly closed. Also, this comes from a call to
+ // Activity#managedQuery, which has been deprecated for a long time (and which FORBIDS
+ // closing the cursor, so take care when resolving this TODO). We should either use a
+ // regular query and close the cursor, or switch to a LoaderManager and a CursorLoader.
mCursor = createCursor(locale);
TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
emptyView.setText(R.string.user_dict_settings_empty_text);
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index d87f6f3c4..2bb30a2ba 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -17,32 +17,43 @@
package com.android.inputmethod.latin.utils;
import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE;
+import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
import android.os.Build;
import android.text.TextUtils;
+import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import java.util.ArrayList;
+import java.util.Arrays;
public final class AdditionalSubtypeUtils {
+ private static final String TAG = AdditionalSubtypeUtils.class.getSimpleName();
+
private static final InputMethodSubtype[] EMPTY_SUBTYPE_ARRAY = new InputMethodSubtype[0];
private AdditionalSubtypeUtils() {
// This utility class is not publicly instantiable.
}
+ @UsedForTesting
public static boolean isAdditionalSubtype(final InputMethodSubtype subtype) {
return subtype.containsExtraValueKey(IS_ADDITIONAL_SUBTYPE);
}
private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":";
+ private static final int INDEX_OF_LOCALE = 0;
+ private static final int INDEX_OF_KEYBOARD_LAYOUT = 1;
+ private static final int INDEX_OF_EXTRA_VALUE = 2;
+ private static final int LENGTH_WITHOUT_EXTRA_VALUE = (INDEX_OF_KEYBOARD_LAYOUT + 1);
+ private static final int LENGTH_WITH_EXTRA_VALUE = (INDEX_OF_EXTRA_VALUE + 1);
private static final String PREF_SUBTYPE_SEPARATOR = ";";
public static InputMethodSubtype createAdditionalSubtype(final String localeString,
@@ -79,17 +90,6 @@ public final class AdditionalSubtypeUtils {
: basePrefSubtype + LOCALE_AND_LAYOUT_SEPARATOR + extraValue;
}
- public static InputMethodSubtype createAdditionalSubtype(final String prefSubtype) {
- final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
- if (elems.length < 2 || elems.length > 3) {
- throw new RuntimeException("Unknown additional subtype specified: " + prefSubtype);
- }
- final String localeString = elems[0];
- final String keyboardLayoutSetName = elems[1];
- final String extraValue = (elems.length == 3) ? elems[2] : null;
- return createAdditionalSubtype(localeString, keyboardLayoutSetName, extraValue);
- }
-
public static InputMethodSubtype[] createAdditionalSubtypesArray(final String prefSubtypes) {
if (TextUtils.isEmpty(prefSubtypes)) {
return EMPTY_SUBTYPE_ARRAY;
@@ -98,7 +98,19 @@ public final class AdditionalSubtypeUtils {
final ArrayList<InputMethodSubtype> subtypesList =
CollectionUtils.newArrayList(prefSubtypeArray.length);
for (final String prefSubtype : prefSubtypeArray) {
- final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
+ final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
+ if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE
+ && elems.length != LENGTH_WITH_EXTRA_VALUE) {
+ Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype + " in "
+ + prefSubtypes);
+ continue;
+ }
+ final String localeString = elems[INDEX_OF_LOCALE];
+ final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT];
+ final String extraValue = (elems.length == LENGTH_WITH_EXTRA_VALUE)
+ ? elems[INDEX_OF_EXTRA_VALUE] : null;
+ final InputMethodSubtype subtype = createAdditionalSubtype(
+ localeString, keyboardLayoutSetName, extraValue);
if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) {
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
// layout has been removed.
@@ -137,31 +149,36 @@ public final class AdditionalSubtypeUtils {
return sb.toString();
}
- private static InputMethodSubtype buildInputMethodSubtype(int nameId, String localeString,
- String layoutExtraValue, String additionalSubtypeExtraValue) {
- // CAVEAT! If you want to change subtypeId after changing the extra values,
- // you must change "getInputMethodSubtypeId". But it will remove the additional keyboard
- // from the current users. So, you should be really careful to change it.
- final int subtypeId = getInputMethodSubtypeId(nameId, localeString, layoutExtraValue,
- additionalSubtypeExtraValue);
+ private static InputMethodSubtype buildInputMethodSubtype(final int nameId,
+ final String localeString, final String layoutExtraValue,
+ final String additionalSubtypeExtraValue) {
+ // To preserve additional subtype settings and user's selection across OS updates, subtype
+ // id shouldn't be changed. New attributes, such as emojiCapable, are carefully excluded
+ // from the calculation of subtype id.
+ final String compatibleExtraValue = StringUtils.joinCommaSplittableText(
+ layoutExtraValue, additionalSubtypeExtraValue);
+ final int compatibleSubtypeId = getInputMethodSubtypeId(localeString, compatibleExtraValue);
final String extraValue;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- extraValue = layoutExtraValue + "," + additionalSubtypeExtraValue
- + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
- + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+ // Color Emoji is supported from KitKat.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ extraValue = StringUtils.appendToCommaSplittableTextIfNotExists(
+ EMOJI_CAPABLE, compatibleExtraValue);
} else {
- extraValue = layoutExtraValue + "," + additionalSubtypeExtraValue;
+ extraValue = compatibleExtraValue;
}
return InputMethodSubtypeCompatUtils.newInputMethodSubtype(nameId,
R.drawable.ic_ime_switcher_dark, localeString, KEYBOARD_MODE, extraValue,
- false, false, subtypeId);
+ false, false, compatibleSubtypeId);
}
- private static int getInputMethodSubtypeId(int nameId, String localeString,
- String layoutExtraValue, String additionalSubtypeExtraValue) {
- // TODO: Use InputMethodSubtypeBuilder once we use SDK version 19.
- return (new InputMethodSubtype(nameId, R.drawable.ic_ime_switcher_dark,
- localeString, KEYBOARD_MODE, layoutExtraValue + "," + additionalSubtypeExtraValue,
- false, false)).hashCode();
+ private static int getInputMethodSubtypeId(final String localeString, final String extraValue) {
+ // From the compatibility point of view, the calculation of subtype id has been copied from
+ // {@link InputMethodSubtype} of JellyBean MR2.
+ return Arrays.hashCode(new Object[] {
+ localeString,
+ KEYBOARD_MODE,
+ extraValue,
+ false /* isAuxiliary */,
+ false /* overrideImplicitlyEnabledSubtype */ });
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
index 08a2a8c5a..7a4150def 100644
--- a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
@@ -31,7 +31,7 @@ public final class ApplicationUtils {
// This utility class is not publicly instantiable.
}
- public static int getAcitivityTitleResId(final Context context,
+ public static int getActivityTitleResId(final Context context,
final Class<? extends Activity> cls) {
final ComponentName cn = new ComponentName(context, cls);
try {
@@ -62,4 +62,22 @@ public final class ApplicationUtils {
}
return "";
}
+
+ /**
+ * A utility method to get the application's PackageInfo.versionCode
+ * @return the application's PackageInfo.versionCode
+ */
+ public static int getVersionCode(final Context context) {
+ try {
+ if (context == null) {
+ return 0;
+ }
+ final String packageName = context.getPackageName();
+ final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+ return info.versionCode;
+ } catch (final NameNotFoundException e) {
+ Log.e(TAG, "Could not find version info.", e);
+ }
+ return 0;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
index c2e97a36f..d12aad639 100644
--- a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
+++ b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
@@ -20,7 +20,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
- * This class is a holder of a result of asynchronous computation.
+ * This class is a holder of the result of an asynchronous computation.
*
* @param <E> the type of the result.
*/
@@ -36,9 +36,9 @@ public class AsyncResultHolder<E> {
}
/**
- * Sets the result value to this holder.
+ * Sets the result value of this holder.
*
- * @param result the value which is set.
+ * @param result the value to set.
*/
public void set(final E result) {
synchronized(mLock) {
@@ -54,12 +54,12 @@ public class AsyncResultHolder<E> {
* Causes the current thread to wait unless the value is set or the specified time is elapsed.
*
* @param defaultValue the default value.
- * @param timeOut the time to wait.
- * @return if the result is set until the time limit then the result, otherwise defaultValue.
+ * @param timeOut the maximum time to wait.
+ * @return if the result is set before the time limit then the result, otherwise defaultValue.
*/
public E get(final E defaultValue, final long timeOut) {
try {
- if(mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
+ if (mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
return mResult;
} else {
return defaultValue;
diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
index 066c5fd32..22b9b77d2 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -17,16 +17,11 @@
package com.android.inputmethod.latin.utils;
import com.android.inputmethod.latin.BinaryDictionary;
-import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import android.text.TextUtils;
import android.util.Log;
-import java.util.concurrent.ConcurrentHashMap;
-
public final class AutoCorrectionUtils {
private static final boolean DBG = LatinImeLogger.sDBG;
private static final String TAG = AutoCorrectionUtils.class.getSimpleName();
@@ -36,48 +31,6 @@ public final class AutoCorrectionUtils {
// Purely static class: can't instantiate.
}
- public static boolean isValidWord(final Suggest suggest, final String word,
- final boolean ignoreCase) {
- if (TextUtils.isEmpty(word)) {
- return false;
- }
- final ConcurrentHashMap<String, Dictionary> dictionaries = suggest.getUnigramDictionaries();
- final String lowerCasedWord = word.toLowerCase(suggest.mLocale);
- for (final String key : dictionaries.keySet()) {
- final Dictionary dictionary = dictionaries.get(key);
- // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
- // managing to get null in here. Presumably the language is changing to a language with
- // no main dictionary and the monkey manages to type a whole word before the thread
- // that reads the dictionary is started or something?
- // 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;
- }
-
- public static int getMaxFrequency(final ConcurrentHashMap<String, Dictionary> dictionaries,
- final String word) {
- if (TextUtils.isEmpty(word)) {
- return Dictionary.NOT_A_PROBABILITY;
- }
- int maxFreq = -1;
- for (final String key : dictionaries.keySet()) {
- final Dictionary dictionary = dictionaries.get(key);
- if (null == dictionary) continue;
- final int tempFreq = dictionary.getFrequency(word);
- if (tempFreq >= maxFreq) {
- maxFreq = tempFreq;
- }
- }
- return maxFreq;
- }
-
public static boolean suggestionExceedsAutoCorrectionThreshold(
final SuggestedWordInfo suggestion, final String consideredWord,
final float autoCorrectionThreshold) {
@@ -87,7 +40,7 @@ public final class AutoCorrectionUtils {
final int autoCorrectionSuggestionScore = suggestion.mScore;
// TODO: when the normalized score of the first suggestion is nearly equals to
// the normalized score of the second suggestion, behave less aggressive.
- final float normalizedScore = BinaryDictionary.calcNormalizedScore(
+ final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
consideredWord, suggestion.mWord, autoCorrectionSuggestionScore);
if (DBG) {
Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + ","
@@ -118,9 +71,8 @@ public final class AutoCorrectionUtils {
if (typedWordLength < MINIMUM_SAFETY_NET_CHAR_LENGTH) {
return false;
}
- final int maxEditDistanceOfNativeDictionary =
- (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
- final int distance = BinaryDictionary.editDistance(typedWord, suggestion);
+ final int maxEditDistanceOfNativeDictionary = (typedWordLength / 2) + 1;
+ final int distance = BinaryDictionaryUtils.editDistance(typedWord, suggestion);
if (DBG) {
Log.d(TAG, "Autocorrected edit distance = " + distance
+ ", " + maxEditDistanceOfNativeDictionary);
diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
new file mode 100644
index 000000000..b4658b531
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
@@ -0,0 +1,137 @@
+/*
+ * 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 com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.personalization.PersonalizationHelper;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class BinaryDictionaryUtils {
+ private static final String TAG = BinaryDictionaryUtils.class.getSimpleName();
+
+ private BinaryDictionaryUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ static {
+ JniUtils.loadNativeLibrary();
+ }
+
+ private static native boolean createEmptyDictFileNative(String filePath, long dictVersion,
+ String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray);
+ private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
+ private static native int editDistanceNative(int[] before, int[] after);
+ private static native int setCurrentTimeForTestNative(int currentTime);
+
+ public static DictionaryHeader getHeader(final File dictFile)
+ throws IOException, UnsupportedFormatException {
+ return getHeaderWithOffsetAndLength(dictFile, 0 /* offset */, dictFile.length());
+ }
+
+ public static DictionaryHeader getHeaderWithOffsetAndLength(final File dictFile,
+ final long offset, final long length) throws IOException, UnsupportedFormatException {
+ // dictType is never used for reading the header. Passing an empty string.
+ final BinaryDictionary binaryDictionary = new BinaryDictionary(
+ dictFile.getAbsolutePath(), offset, length,
+ true /* useFullEditDistance */, null /* locale */, "" /* dictType */,
+ false /* isUpdatable */);
+ final DictionaryHeader header = binaryDictionary.getHeader();
+ binaryDictionary.close();
+ if (header == null) {
+ throw new IOException();
+ }
+ return header;
+ }
+
+ public static boolean renameDict(final File dictFile, final File newDictFile) {
+ if (dictFile.isFile()) {
+ return dictFile.renameTo(newDictFile);
+ } else if (dictFile.isDirectory()) {
+ final String dictName = dictFile.getName();
+ final String newDictName = newDictFile.getName();
+ if (newDictFile.exists()) {
+ return false;
+ }
+ for (final File file : dictFile.listFiles()) {
+ if (!file.isFile()) {
+ continue;
+ }
+ final String fileName = file.getName();
+ final String newFileName = fileName.replaceFirst(
+ Pattern.quote(dictName), Matcher.quoteReplacement(newDictName));
+ if (!file.renameTo(new File(dictFile, newFileName))) {
+ return false;
+ }
+ }
+ return dictFile.renameTo(newDictFile);
+ }
+ return false;
+ }
+
+ public static boolean createEmptyDictFile(final String filePath, final long dictVersion,
+ final Locale locale, final Map<String, String> attributeMap) {
+ final String[] keyArray = new String[attributeMap.size()];
+ final String[] valueArray = new String[attributeMap.size()];
+ int index = 0;
+ for (final String key : attributeMap.keySet()) {
+ keyArray[index] = key;
+ valueArray[index] = attributeMap.get(key);
+ index++;
+ }
+ return createEmptyDictFileNative(filePath, dictVersion, locale.toString(), keyArray,
+ valueArray);
+ }
+
+ public static float calcNormalizedScore(final String before, final String after,
+ final int score) {
+ return calcNormalizedScoreNative(StringUtils.toCodePointArray(before),
+ StringUtils.toCodePointArray(after), score);
+ }
+
+ public static int editDistance(final String before, final String after) {
+ if (before == null || after == null) {
+ throw new IllegalArgumentException();
+ }
+ return editDistanceNative(StringUtils.toCodePointArray(before),
+ StringUtils.toCodePointArray(after));
+ }
+
+ /**
+ * Control the current time to be used in the native code. If currentTime >= 0, this method sets
+ * the current time and gets into test mode.
+ * In test mode, set timestamp is used as the current time in the native code.
+ * If currentTime < 0, quit the test mode and returns to using time() to get the current time.
+ *
+ * @param currentTime seconds since the unix epoch
+ * @return current time got in the native code.
+ */
+ @UsedForTesting
+ public static int setCurrentTimeForTest(final int currentTime) {
+ final int currentNativeTimestamp = setCurrentTimeForTestNative(currentTime);
+ PersonalizationHelper.currentTimeChangedForTesting(currentNativeTimestamp);
+ return currentNativeTimestamp;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java
deleted file mode 100644
index ae1fd3f79..000000000
--- a/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2012 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 com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.TreeSet;
-
-/**
- * A TreeSet that is bounded in size and throws everything that's smaller than its limit
- */
-public final class BoundedTreeSet extends TreeSet<SuggestedWordInfo> {
- private final int mCapacity;
- public BoundedTreeSet(final Comparator<SuggestedWordInfo> comparator, final int capacity) {
- super(comparator);
- mCapacity = capacity;
- }
-
- @Override
- public boolean add(final SuggestedWordInfo e) {
- if (size() < mCapacity) return super.add(e);
- if (comparator().compare(e, last()) > 0) return false;
- super.add(e);
- pollLast(); // removes the last element
- return true;
- }
-
- @Override
- public boolean addAll(final Collection<? extends SuggestedWordInfo> e) {
- if (null == e) return false;
- return super.addAll(e);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java b/java/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java
deleted file mode 100644
index 2028298f2..000000000
--- a/java/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
-
-/**
- * This class provides an implementation for the FusionDictionary buffer interface that is backed
- * by a simpled byte array. It allows to create a binary dictionary in memory.
- */
-public final class ByteArrayDictBuffer implements DictBuffer {
- private byte[] mBuffer;
- private int mPosition;
-
- public ByteArrayDictBuffer(final byte[] buffer) {
- mBuffer = buffer;
- mPosition = 0;
- }
-
- @Override
- public int readUnsignedByte() {
- return mBuffer[mPosition++] & 0xFF;
- }
-
- @Override
- public int readUnsignedShort() {
- final int retval = readUnsignedByte();
- return (retval << 8) + readUnsignedByte();
- }
-
- @Override
- public int readUnsignedInt24() {
- final int retval = readUnsignedShort();
- return (retval << 8) + readUnsignedByte();
- }
-
- @Override
- public int readInt() {
- final int retval = readUnsignedShort();
- return (retval << 16) + readUnsignedShort();
- }
-
- @Override
- public int position() {
- return mPosition;
- }
-
- @Override
- public void position(int position) {
- mPosition = position;
- }
-
- @Override
- public void put(final byte b) {
- mBuffer[mPosition++] = b;
- }
-
- @Override
- public int limit() {
- return mBuffer.length - 1;
- }
-
- @Override
- public int capacity() {
- return mBuffer.length;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 3d4404a98..702688f93 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -21,7 +21,7 @@ import android.text.TextUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.settings.SettingsValues;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import java.util.Locale;
@@ -74,7 +74,7 @@ public final class CapsModeUtils {
* @param reqModes The modes to be checked: may be any combination of
* {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
* {@link TextUtils#CAP_MODE_SENTENCES}.
- * @param settingsValues The current settings values.
+ * @param spacingAndPunctuations The current spacing and punctuations settings.
* @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs
*
* @return Returns the actual capitalization modes that can be in effect
@@ -83,7 +83,7 @@ public final class CapsModeUtils {
* {@link TextUtils#CAP_MODE_SENTENCES}.
*/
public static int getCapsMode(final CharSequence cs, final int reqModes,
- final SettingsValues settingsValues, final boolean hasSpaceBefore) {
+ final SpacingAndPunctuations spacingAndPunctuations, final boolean hasSpaceBefore) {
// Quick description of what we want to do:
// CAP_MODE_CHARACTERS is always on.
// CAP_MODE_WORDS is on if there is some whitespace before the cursor.
@@ -139,6 +139,20 @@ public final class CapsModeUtils {
j--;
}
if (j <= 0 || Character.isWhitespace(prevChar)) {
+ if (spacingAndPunctuations.mUsesGermanRules) {
+ // In German typography rules, there is a specific case that the first character
+ // of a new line should not be capitalized if the previous line ends in a comma.
+ boolean hasNewLine = false;
+ while (--j >= 0 && Character.isWhitespace(prevChar)) {
+ if (Constants.CODE_ENTER == prevChar) {
+ hasNewLine = true;
+ }
+ prevChar = cs.charAt(j);
+ }
+ if (Constants.CODE_COMMA == prevChar && hasNewLine) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ }
+ }
// There are only spacing chars between the start of the paragraph and the cursor,
// defined as a isWhitespace() char that is neither a isSpaceChar() nor a tab. Both
// MODE_WORDS and MODE_SENTENCES should be active.
@@ -167,8 +181,7 @@ public final class CapsModeUtils {
// No other language has such a rule as far as I know, instead putting inside the quotation
// mark as the exact thing quoted and handling the surrounding punctuation independently,
// e.g. <<Did he say, "let's go home"?>>
- // Hence, specifically for English, we treat this special case here.
- if (Locale.ENGLISH.getLanguage().equals(settingsValues.mLocale.getLanguage())) {
+ if (spacingAndPunctuations.mUsesAmericanTypography) {
for (; j > 0; j--) {
// Here we look to go over any closing punctuation. This is because in dominant
// variants of English, the final period is placed within double quotes and maybe
@@ -191,7 +204,7 @@ public final class CapsModeUtils {
if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) {
return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
}
- if (settingsValues.mSentenceSeparator != c || j <= 0) {
+ if (!spacingAndPunctuations.isSentenceSeparator(c) || j <= 0) {
return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
}
@@ -241,7 +254,7 @@ public final class CapsModeUtils {
case WORD:
if (Character.isLetter(c)) {
state = WORD;
- } else if (settingsValues.mSentenceSeparator == c) {
+ } else if (spacingAndPunctuations.isSentenceSeparator(c)) {
state = PERIOD;
} else {
return caps;
@@ -257,7 +270,7 @@ public final class CapsModeUtils {
case LETTER:
if (Character.isLetter(c)) {
state = LETTER;
- } else if (settingsValues.mSentenceSeparator == c) {
+ } else if (spacingAndPunctuations.isSentenceSeparator(c)) {
state = PERIOD;
} else {
return noCaps;
diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
index cc25102ce..bbfa0f091 100644
--- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
@@ -102,4 +102,19 @@ public final class CollectionUtils {
public static <E> SparseArray<E> newSparseArray() {
return new SparseArray<E>();
}
+
+ public static <E> ArrayList<E> arrayAsList(final E[] array, final int start, final int end) {
+ if (array == null) {
+ throw new NullPointerException();
+ }
+ if (start < 0 || start > end || end > array.length) {
+ throw new IllegalArgumentException();
+ }
+
+ final ArrayList<E> list = newArrayList(end - start);
+ for (int i = start; i < end; i++) {
+ list.add(array[i]);
+ }
+ return list;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
new file mode 100644
index 000000000..c66007537
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.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.utils;
+
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.ProbabilityInfo;
+import com.android.inputmethod.latin.makedict.WeightedString;
+import com.android.inputmethod.latin.makedict.WordProperty;
+
+import java.util.HashMap;
+
+public class CombinedFormatUtils {
+ public static final String DICTIONARY_TAG = "dictionary";
+ public static final String BIGRAM_TAG = "bigram";
+ public static final String SHORTCUT_TAG = "shortcut";
+ public static final String PROBABILITY_TAG = "f";
+ public static final String HISTORICAL_INFO_TAG = "historicalInfo";
+ public static final String HISTORICAL_INFO_SEPARATOR = ":";
+ public static final String WORD_TAG = "word";
+ public static final String NOT_A_WORD_TAG = "not_a_word";
+ public static final String BLACKLISTED_TAG = "blacklisted";
+
+ public static String formatAttributeMap(final HashMap<String, String> attributeMap) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(DICTIONARY_TAG + "=");
+ if (attributeMap.containsKey(DictionaryHeader.DICTIONARY_ID_KEY)) {
+ builder.append(attributeMap.get(DictionaryHeader.DICTIONARY_ID_KEY));
+ }
+ for (final String key : attributeMap.keySet()) {
+ if (key.equals(DictionaryHeader.DICTIONARY_ID_KEY)) {
+ continue;
+ }
+ final String value = attributeMap.get(key);
+ builder.append("," + key + "=" + value);
+ }
+ builder.append("\n");
+ return builder.toString();
+ }
+
+ public static String formatWordProperty(final WordProperty wordProperty) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(" " + WORD_TAG + "=" + wordProperty.mWord);
+ builder.append(",");
+ builder.append(formatProbabilityInfo(wordProperty.mProbabilityInfo));
+ if (wordProperty.mIsNotAWord) {
+ builder.append("," + NOT_A_WORD_TAG + "=true");
+ }
+ if (wordProperty.mIsBlacklistEntry) {
+ builder.append("," + BLACKLISTED_TAG + "=true");
+ }
+ builder.append("\n");
+ if (wordProperty.mShortcutTargets != null) {
+ for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
+ builder.append(" " + SHORTCUT_TAG + "=" + shortcutTarget.mWord);
+ builder.append(",");
+ builder.append(formatProbabilityInfo(shortcutTarget.mProbabilityInfo));
+ builder.append("\n");
+ }
+ }
+ if (wordProperty.mBigrams != null) {
+ for (final WeightedString bigram : wordProperty.mBigrams) {
+ builder.append(" " + BIGRAM_TAG + "=" + bigram.mWord);
+ builder.append(",");
+ builder.append(formatProbabilityInfo(bigram.mProbabilityInfo));
+ builder.append("\n");
+ }
+ }
+ return builder.toString();
+ }
+
+ public static String formatProbabilityInfo(final ProbabilityInfo probabilityInfo) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(PROBABILITY_TAG + "=" + probabilityInfo.mProbability);
+ if (probabilityInfo.hasHistoricalInfo()) {
+ builder.append(",");
+ builder.append(HISTORICAL_INFO_TAG + "=");
+ builder.append(probabilityInfo.mTimestamp);
+ builder.append(HISTORICAL_INFO_SEPARATOR);
+ builder.append(probabilityInfo.mLevel);
+ builder.append(HISTORICAL_INFO_SEPARATOR);
+ builder.append(probabilityInfo.mCount);
+ }
+ return builder.toString();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
index 72f2cd2d9..87df013a6 100644
--- a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
@@ -16,17 +16,19 @@
package com.android.inputmethod.latin.utils;
+import java.util.Arrays;
+
public final class CoordinateUtils {
private static final int INDEX_X = 0;
private static final int INDEX_Y = 1;
- private static final int ARRAY_SIZE = INDEX_Y + 1;
+ private static final int ELEMENT_SIZE = INDEX_Y + 1;
private CoordinateUtils() {
// This utility class is not publicly instantiable.
}
public static int[] newInstance() {
- return new int[ARRAY_SIZE];
+ return new int[ELEMENT_SIZE];
}
public static int x(final int[] coords) {
@@ -46,4 +48,44 @@ public final class CoordinateUtils {
destination[INDEX_X] = source[INDEX_X];
destination[INDEX_Y] = source[INDEX_Y];
}
+
+ public static int[] newCoordinateArray(final int arraySize) {
+ return new int[ELEMENT_SIZE * arraySize];
+ }
+
+ public static int[] newCoordinateArray(final int arraySize,
+ final int defaultX, final int defaultY) {
+ final int[] result = new int[ELEMENT_SIZE * arraySize];
+ for (int i = 0; i < arraySize; ++i) {
+ setXYInArray(result, i, defaultX, defaultY);
+ }
+ return result;
+ }
+
+ public static int xFromArray(final int[] coordsArray, final int index) {
+ return coordsArray[ELEMENT_SIZE * index + INDEX_X];
+ }
+
+ public static int yFromArray(final int[] coordsArray, final int index) {
+ return coordsArray[ELEMENT_SIZE * index + INDEX_Y];
+ }
+
+ public static int[] coordinateFromArray(final int[] coordsArray, final int index) {
+ final int baseIndex = ELEMENT_SIZE * index;
+ return Arrays.copyOfRange(coordsArray, baseIndex, baseIndex + ELEMENT_SIZE);
+ }
+
+ public static void setXYInArray(final int[] coordsArray, final int index,
+ final int x, final int y) {
+ final int baseIndex = ELEMENT_SIZE * index;
+ coordsArray[baseIndex + INDEX_X] = x;
+ coordsArray[baseIndex + INDEX_Y] = y;
+ }
+
+ public static void setCoordinateInArray(final int[] coordsArray, final int index,
+ final int[] coords) {
+ final int baseIndex = ELEMENT_SIZE * index;
+ coordsArray[baseIndex + INDEX_X] = coords[INDEX_X];
+ coordsArray[baseIndex + INDEX_Y] = coords[INDEX_Y];
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
index 36b927eea..b18a1d83b 100644
--- a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin.utils;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.Constants;
import java.util.ArrayList;
@@ -57,9 +58,9 @@ public final class CsvUtils {
// Note that none of these characters match high or low surrogate characters, so we need not
// take care of matching by code point.
- private static final char COMMA = ',';
- private static final char SPACE = ' ';
- private static final char QUOTE = '"';
+ private static final char COMMA = Constants.CODE_COMMA;
+ private static final char SPACE = Constants.CODE_SPACE;
+ private static final char QUOTE = Constants.CODE_DOUBLE_QUOTE;
@SuppressWarnings("serial")
public static class CsvParseException extends RuntimeException {
diff --git a/java/src/com/android/inputmethod/latin/utils/DialogUtils.java b/java/src/com/android/inputmethod/latin/utils/DialogUtils.java
new file mode 100644
index 000000000..a05c932d0
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/DialogUtils.java
@@ -0,0 +1,34 @@
+/*
+ * 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.content.Context;
+import android.view.ContextThemeWrapper;
+
+import com.android.inputmethod.latin.R;
+
+public final class DialogUtils {
+ private DialogUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static Context getPlatformDialogThemeContext(final Context context) {
+ // Because {@link AlertDialog.Builder.create()} doesn't honor the specified theme with
+ // createThemeContextWrapper=false, the result dialog box has unneeded paddings around it.
+ return new ContextThemeWrapper(context, R.style.platformDialogTheme);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 021bf0825..315913e2f 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -20,15 +20,19 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
+import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.latin.AssetFileAddress;
import com.android.inputmethod.latin.BinaryDictionaryGetter;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Locale;
@@ -278,14 +282,36 @@ public class DictionaryInfoUtils {
BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.getLanguage().toString();
}
- public static FileHeader getDictionaryFileHeaderOrNull(final File file) {
- return BinaryDictIOUtils.getDictionaryFileHeaderOrNull(file, 0, file.length());
+ public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file) {
+ return getDictionaryFileHeaderOrNull(file, 0, file.length());
}
+ private static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
+ final long offset, final long length) {
+ try {
+ final DictionaryHeader header =
+ BinaryDictionaryUtils.getHeaderWithOffsetAndLength(file, offset, length);
+ return header;
+ } catch (UnsupportedFormatException e) {
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns information of the dictionary.
+ *
+ * @param fileAddress the asset dictionary file address.
+ * @return information of the specified dictionary.
+ */
private static DictionaryInfo createDictionaryInfoFromFileAddress(
final AssetFileAddress fileAddress) {
- final FileHeader header = BinaryDictIOUtils.getDictionaryFileHeaderOrNull(
+ final DictionaryHeader header = getDictionaryFileHeaderOrNull(
new File(fileAddress.mFilename), fileAddress.mOffset, fileAddress.mLength);
+ if (header == null) {
+ return null;
+ }
final String id = header.getId();
final Locale locale = LocaleUtils.constructLocaleFromString(header.getLocaleString());
final String description = header.getDescription();
@@ -328,7 +354,7 @@ public class DictionaryInfoUtils {
// Protect against cases of a less-specific dictionary being found, like an
// en dictionary being used for an en_US locale. In this case, the en dictionary
// should be used for en_US but discounted for listing purposes.
- if (!dictionaryInfo.mLocale.equals(locale)) continue;
+ if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) continue;
addOrUpdateDictInfo(dictList, dictionaryInfo);
}
}
@@ -355,4 +381,32 @@ public class DictionaryInfoUtils {
return dictList;
}
+
+ public static boolean looksValidForDictionaryInsertion(final CharSequence text,
+ final SpacingAndPunctuations spacingAndPunctuations) {
+ if (TextUtils.isEmpty(text)) return false;
+ final int length = text.length();
+ // TODO: Make this test "length > Constants.DICTIONARY_MAX_WORD_LENGTH".
+ if (length >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
+ return false;
+ }
+ int i = 0;
+ int digitCount = 0;
+ while (i < length) {
+ final int codePoint = Character.codePointAt(text, i);
+ final int charCount = Character.charCount(codePoint);
+ i += charCount;
+ if (Character.isDigit(codePoint)) {
+ // Count digits: see below
+ digitCount += charCount;
+ continue;
+ }
+ if (!spacingAndPunctuations.isWordCodePoint(codePoint)) return false;
+ }
+ // We reject strings entirely comprised of digits to avoid using PIN codes or credit
+ // card numbers. It would come in handy for word prediction though; a good example is
+ // when writing one's address where the street number is usually quite discriminative,
+ // as well as the postal code.
+ return digitCount < length;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
new file mode 100644
index 000000000..ed502ed3d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Utilities to manage executors.
+ */
+public class ExecutorUtils {
+ private static final ConcurrentHashMap<String, PrioritizedSerialExecutor>
+ sExecutorMap = CollectionUtils.newConcurrentHashMap();
+ /**
+ * Gets the executor for the given dictionary name.
+ */
+ public static PrioritizedSerialExecutor getExecutor(final String dictName) {
+ PrioritizedSerialExecutor executor = sExecutorMap.get(dictName);
+ if (executor == null) {
+ synchronized(sExecutorMap) {
+ executor = new PrioritizedSerialExecutor();
+ sExecutorMap.put(dictName, executor);
+ }
+ }
+ return executor;
+ }
+
+ /**
+ * Shutdowns all executors and removes all executors from the executor map for testing.
+ */
+ @UsedForTesting
+ public static void shutdownAllExecutors() {
+ synchronized(sExecutorMap) {
+ for (final PrioritizedSerialExecutor executor : sExecutorMap.values()) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ executor.shutdown();
+ sExecutorMap.remove(executor);
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/FileUtils.java b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
new file mode 100644
index 000000000..f1106a6c6
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.io.File;
+import java.io.FilenameFilter;
+
+/**
+ * A simple class to help with removing directories recursively.
+ */
+public class FileUtils {
+ public static boolean deleteRecursively(final File path) {
+ if (path.isDirectory()) {
+ final File[] files = path.listFiles();
+ if (files != null) {
+ for (final File child : files) {
+ deleteRecursively(child);
+ }
+ }
+ }
+ return path.delete();
+ }
+
+ public static boolean deleteFilteredFiles(final File dir, final FilenameFilter fileNameFilter) {
+ if (!dir.isDirectory()) {
+ return false;
+ }
+ final File[] files = dir.listFiles(fileNameFilter);
+ if (files == null) {
+ return false;
+ }
+ boolean hasDeletedAllFiles = true;
+ for (final File file : files) {
+ if (!deleteRecursively(file)) {
+ hasDeletedAllFiles = false;
+ }
+ }
+ return hasDeletedAllFiles;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
new file mode 100644
index 000000000..7d937a9d2
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.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.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.latin.InputAttributes;
+import com.android.inputmethod.latin.R;
+
+public final class ImportantNoticeUtils {
+ private static final String TAG = ImportantNoticeUtils.class.getSimpleName();
+
+ // {@link SharedPreferences} name to save the last important notice version that has been
+ // displayed to users.
+ private static final String PREFERENCE_NAME = "important_notice_pref";
+ private static final String KEY_IMPORTANT_NOTICE_VERSION = "important_notice_version";
+ public static final int VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS = 1;
+
+ // Copy of the hidden {@link Settings.Secure#USER_SETUP_COMPLETE} settings key.
+ // The value is zero until each multiuser completes system setup wizard.
+ // Caveat: This is a hidden API.
+ private static final String Settings_Secure_USER_SETUP_COMPLETE = "user_setup_complete";
+ private static final int USER_SETUP_IS_NOT_COMPLETE = 0;
+
+ private ImportantNoticeUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ private static boolean isInSystemSetupWizard(final Context context) {
+ try {
+ final int userSetupComplete = Settings.Secure.getInt(
+ context.getContentResolver(), Settings_Secure_USER_SETUP_COMPLETE);
+ return userSetupComplete == USER_SETUP_IS_NOT_COMPLETE;
+ } catch (final SettingNotFoundException e) {
+ Log.w(TAG, "Can't find settings in Settings.Secure: key="
+ + Settings_Secure_USER_SETUP_COMPLETE);
+ return false;
+ }
+ }
+
+ private static SharedPreferences getImportantNoticePreferences(final Context context) {
+ return context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
+ }
+
+ private static int getCurrentImportantNoticeVersion(final Context context) {
+ return context.getResources().getInteger(R.integer.config_important_notice_version);
+ }
+
+ private static int getLastImportantNoticeVersion(final Context context) {
+ return getImportantNoticePreferences(context).getInt(KEY_IMPORTANT_NOTICE_VERSION, 0);
+ }
+
+ public static int getNextImportantNoticeVersion(final Context context) {
+ return getLastImportantNoticeVersion(context) + 1;
+ }
+
+ private static boolean hasNewImportantNotice(final Context context) {
+ final int lastVersion = getLastImportantNoticeVersion(context);
+ return getCurrentImportantNoticeVersion(context) > lastVersion;
+ }
+
+ public static boolean shouldShowImportantNotice(final Context context,
+ final InputAttributes inputAttributes) {
+ if (inputAttributes == null || inputAttributes.mIsPasswordField) {
+ return false;
+ }
+ if (isInSystemSetupWizard(context)) {
+ return false;
+ }
+ if (!hasNewImportantNotice(context)) {
+ return false;
+ }
+ final String importantNoticeTitle = getNextImportantNoticeTitle(context);
+ if (TextUtils.isEmpty(importantNoticeTitle)) {
+ return false;
+ }
+ return true;
+ }
+
+ public static void updateLastImportantNoticeVersion(final Context context) {
+ getImportantNoticePreferences(context)
+ .edit()
+ .putInt(KEY_IMPORTANT_NOTICE_VERSION, getNextImportantNoticeVersion(context))
+ .apply();
+ }
+
+ public static String getNextImportantNoticeTitle(final Context context) {
+ final int nextVersion = getCurrentImportantNoticeVersion(context);
+ final String[] importantNoticeTitleArray = context.getResources().getStringArray(
+ R.array.important_notice_title_array);
+ if (nextVersion > 0 && nextVersion < importantNoticeTitleArray.length) {
+ return importantNoticeTitleArray[nextVersion];
+ }
+ return null;
+ }
+
+ public static String getNextImportantNoticeContents(final Context context) {
+ final int nextVersion = getNextImportantNoticeVersion(context);
+ final String[] importantNoticeContentsArray = context.getResources().getStringArray(
+ R.array.important_notice_contents_array);
+ if (nextVersion > 0 && nextVersion < importantNoticeContentsArray.length) {
+ return importantNoticeContentsArray[nextVersion];
+ }
+ return null;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
new file mode 100644
index 000000000..764ef72ce
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class JsonUtils {
+ private static final String TAG = JsonUtils.class.getSimpleName();
+
+ private static final String INTEGER_CLASS_NAME = Integer.class.getSimpleName();
+ private static final String STRING_CLASS_NAME = String.class.getSimpleName();
+
+ private static final String EMPTY_STRING = "";
+
+ public static List<Object> jsonStrToList(final String s) {
+ final ArrayList<Object> list = CollectionUtils.newArrayList();
+ final JsonReader reader = new JsonReader(new StringReader(s));
+ try {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ reader.beginObject();
+ while (reader.hasNext()) {
+ final String name = reader.nextName();
+ if (name.equals(INTEGER_CLASS_NAME)) {
+ list.add(reader.nextInt());
+ } else if (name.equals(STRING_CLASS_NAME)) {
+ list.add(reader.nextString());
+ } else {
+ Log.w(TAG, "Invalid name: " + name);
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+ }
+ reader.endArray();
+ return list;
+ } catch (final IOException e) {
+ } finally {
+ close(reader);
+ }
+ return Collections.<Object>emptyList();
+ }
+
+ public static String listToJsonStr(final List<Object> list) {
+ if (list == null || list.isEmpty()) {
+ return EMPTY_STRING;
+ }
+ final StringWriter sw = new StringWriter();
+ final JsonWriter writer = new JsonWriter(sw);
+ try {
+ writer.beginArray();
+ for (final Object o : list) {
+ writer.beginObject();
+ if (o instanceof Integer) {
+ writer.name(INTEGER_CLASS_NAME).value((Integer)o);
+ } else if (o instanceof String) {
+ writer.name(STRING_CLASS_NAME).value((String)o);
+ }
+ writer.endObject();
+ }
+ writer.endArray();
+ return sw.toString();
+ } catch (final IOException e) {
+ } finally {
+ close(writer);
+ }
+ return EMPTY_STRING;
+ }
+
+ private static void close(final Closeable closeable) {
+ try {
+ if (closeable != null) {
+ closeable.close();
+ }
+ } catch (final IOException e) {
+ // Ignore
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
new file mode 100644
index 000000000..5ce977d5e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -0,0 +1,177 @@
+/*
+ * 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.util.Log;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+// Note: this class is used as a parameter type of a native method. You should be careful when you
+// rename this class or field name. See BinaryDictionary#addMultipleDictionaryEntriesNative().
+public final class LanguageModelParam {
+ private static final String TAG = LanguageModelParam.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_TOKEN = false;
+
+ // For now, these probability values are being referred to only when we add new entries to
+ // decaying dynamic binary dictionaries. When these are referred to, what matters is 0 or
+ // non-0. Thus, it's not meaningful to compare 10, 100, and so on.
+ // TODO: Revise the logic in ForgettingCurveUtils in native code.
+ private static final int UNIGRAM_PROBABILITY_FOR_VALID_WORD = 100;
+ private static final int UNIGRAM_PROBABILITY_FOR_OOV_WORD = Dictionary.NOT_A_PROBABILITY;
+ private static final int BIGRAM_PROBABILITY_FOR_VALID_WORD = 10;
+ private static final int BIGRAM_PROBABILITY_FOR_OOV_WORD = Dictionary.NOT_A_PROBABILITY;
+
+ public final String mTargetWord;
+ public final int[] mWord0;
+ public final int[] mWord1;
+ // TODO: this needs to be a list of shortcuts
+ public final int[] mShortcutTarget;
+ public final int mUnigramProbability;
+ public final int mBigramProbability;
+ public final int mShortcutProbability;
+ public final boolean mIsNotAWord;
+ public final boolean mIsBlacklisted;
+ // Time stamp in seconds.
+ public final int mTimestamp;
+
+ // Constructor for unigram. TODO: support shortcuts
+ public LanguageModelParam(final String word, final int unigramProbability,
+ final int timestamp) {
+ this(null /* word0 */, word, unigramProbability, Dictionary.NOT_A_PROBABILITY, timestamp);
+ }
+
+ // Constructor for unigram and bigram.
+ public LanguageModelParam(final String word0, final String word1,
+ final int unigramProbability, final int bigramProbability,
+ final int timestamp) {
+ mTargetWord = word1;
+ mWord0 = (word0 == null) ? null : StringUtils.toCodePointArray(word0);
+ mWord1 = StringUtils.toCodePointArray(word1);
+ mShortcutTarget = null;
+ mUnigramProbability = unigramProbability;
+ mBigramProbability = bigramProbability;
+ mShortcutProbability = Dictionary.NOT_A_PROBABILITY;
+ mIsNotAWord = false;
+ mIsBlacklisted = false;
+ mTimestamp = timestamp;
+ }
+
+ // Process a list of words and return a list of {@link LanguageModelParam} objects.
+ public static ArrayList<LanguageModelParam> createLanguageModelParamsFrom(
+ final ArrayList<String> tokens, final int timestamp,
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator,
+ final SpacingAndPunctuations spacingAndPunctuations) {
+ final ArrayList<LanguageModelParam> languageModelParams =
+ CollectionUtils.newArrayList();
+ final int N = tokens.size();
+ String prevWord = null;
+ for (int i = 0; i < N; ++i) {
+ final String tempWord = tokens.get(i);
+ if (StringUtils.isEmptyStringOrWhiteSpaces(tempWord)) {
+ // just skip this token
+ if (DEBUG_TOKEN) {
+ Log.d(TAG, "--- isEmptyStringOrWhiteSpaces: \"" + tempWord + "\"");
+ }
+ continue;
+ }
+ if (!DictionaryInfoUtils.looksValidForDictionaryInsertion(
+ tempWord, spacingAndPunctuations)) {
+ if (DEBUG_TOKEN) {
+ Log.d(TAG, "--- not looksValidForDictionaryInsertion: \""
+ + tempWord + "\"");
+ }
+ // Sentence terminator found. Split.
+ prevWord = null;
+ continue;
+ }
+ if (DEBUG_TOKEN) {
+ Log.d(TAG, "--- word: \"" + tempWord + "\"");
+ }
+ final LanguageModelParam languageModelParam =
+ detectWhetherVaildWordOrNotAndGetLanguageModelParam(
+ prevWord, tempWord, timestamp, dictionaryFacilitator);
+ if (languageModelParam == null) {
+ continue;
+ }
+ languageModelParams.add(languageModelParam);
+ prevWord = languageModelParam.mTargetWord;
+ }
+ return languageModelParams;
+ }
+
+ private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam(
+ final String prevWord, final String targetWord, final int timestamp,
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
+ final Locale locale = dictionaryFacilitator.getLocale();
+ if (locale == null) {
+ return null;
+ }
+ if (!dictionaryFacilitator.isValidWord(targetWord, true /* ignoreCase */)) {
+ // OOV word.
+ return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
+ false /* isValidWord */, locale);
+ }
+ if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) {
+ return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
+ true /* isValidWord */, locale);
+ }
+ final String lowerCaseTargetWord = targetWord.toLowerCase(locale);
+ if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
+ // Add the lower-cased word.
+ return createAndGetLanguageModelParamOfWord(prevWord, lowerCaseTargetWord,
+ timestamp, true /* isValidWord */, locale);
+ }
+ // Treat the word as an OOV word.
+ return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
+ false /* isValidWord */, locale);
+ }
+
+ private static LanguageModelParam createAndGetLanguageModelParamOfWord(
+ final String prevWord, final String targetWord, final int timestamp,
+ final boolean isValidWord, final Locale locale) {
+ final String word;
+ if (StringUtils.getCapitalizationType(targetWord) == StringUtils.CAPITALIZE_FIRST
+ && prevWord == null && !isValidWord) {
+ word = targetWord.toLowerCase(locale);
+ } else {
+ word = targetWord;
+ }
+ final int unigramProbability = isValidWord ?
+ UNIGRAM_PROBABILITY_FOR_VALID_WORD : UNIGRAM_PROBABILITY_FOR_OOV_WORD;
+ if (prevWord == null) {
+ if (DEBUG) {
+ Log.d(TAG, "--- add unigram: current("
+ + (isValidWord ? "Valid" : "OOV") + ") = " + word);
+ }
+ return new LanguageModelParam(word, unigramProbability, timestamp);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "--- add bigram: prev = " + prevWord + ", current("
+ + (isValidWord ? "Valid" : "OOV") + ") = " + word);
+ }
+ final int bigramProbability = isValidWord ?
+ BIGRAM_PROBABILITY_FOR_VALID_WORD : BIGRAM_PROBABILITY_FOR_OOV_WORD;
+ return new LanguageModelParam(prevWord, word, unigramProbability,
+ bigramProbability, timestamp);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
index e958a7e71..d14ba508b 100644
--- a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
@@ -35,7 +35,7 @@ public final class LatinImeLoggerUtils {
public static void onSeparator(final int code, final int x, final int y) {
// Helper method to log a single code point separator
// TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
- onSeparator(new String(new int[]{code}, 0, 1), x, y);
+ onSeparator(StringUtils.newSingleCodePointString(code), x, y);
}
public static void onSeparator(final String separator, final int x, final int y) {
diff --git a/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
index 44e5d17b4..8469c87b0 100644
--- a/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java
+++ b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
@@ -21,22 +21,22 @@ import android.os.Looper;
import java.lang.ref.WeakReference;
-public class StaticInnerHandlerWrapper<T> extends Handler {
- private final WeakReference<T> mOuterInstanceRef;
+public class LeakGuardHandlerWrapper<T> extends Handler {
+ private final WeakReference<T> mOwnerInstanceRef;
- public StaticInnerHandlerWrapper(final T outerInstance) {
- this(outerInstance, Looper.myLooper());
+ public LeakGuardHandlerWrapper(final T ownerInstance) {
+ this(ownerInstance, Looper.myLooper());
}
- public StaticInnerHandlerWrapper(final T outerInstance, final Looper looper) {
+ public LeakGuardHandlerWrapper(final T ownerInstance, final Looper looper) {
super(looper);
- if (outerInstance == null) {
- throw new NullPointerException("outerInstance is null");
+ if (ownerInstance == null) {
+ throw new NullPointerException("ownerInstance is null");
}
- mOuterInstanceRef = new WeakReference<T>(outerInstance);
+ mOwnerInstanceRef = new WeakReference<T>(ownerInstance);
}
- public T getOuterInstance() {
- return mOuterInstanceRef.get();
+ public T getOwnerInstance() {
+ return mOwnerInstanceRef.get();
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
index 22045aa38..0c55484b4 100644
--- a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
@@ -30,9 +30,6 @@ import java.util.Locale;
* dictionary pack.
*/
public final class LocaleUtils {
- private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
- private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
-
private LocaleUtils() {
// Intentional empty constructor for utility class.
}
@@ -168,12 +165,14 @@ public final class LocaleUtils {
* Creates a locale from a string specification.
*/
public static Locale constructLocaleFromString(final String localeStr) {
- if (localeStr == null)
+ if (localeStr == null) {
return null;
+ }
synchronized (sLocaleCache) {
- if (sLocaleCache.containsKey(localeStr))
- return sLocaleCache.get(localeStr);
- Locale retval = null;
+ Locale retval = sLocaleCache.get(localeStr);
+ if (retval != null) {
+ return retval;
+ }
String[] localeParams = localeStr.split("_", 3);
if (localeParams.length == 1) {
retval = new Locale(localeParams[0]);
@@ -188,38 +187,4 @@ public final class LocaleUtils {
return retval;
}
}
-
- public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
- if (TextUtils.isEmpty(str)) {
- return EMPTY_LT_HASH_MAP;
- }
- final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER);
- final int N = ss.length;
- if (N < 2 || N % 2 != 0) {
- return EMPTY_LT_HASH_MAP;
- }
- final HashMap<String, Long> retval = CollectionUtils.newHashMap();
- for (int i = 0; i < N / 2; ++i) {
- final String localeStr = ss[i * 2];
- final long time = Long.valueOf(ss[i * 2 + 1]);
- retval.put(localeStr, time);
- }
- return retval;
- }
-
- public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) {
- if (map == null || map.isEmpty()) {
- return "";
- }
- final StringBuilder builder = new StringBuilder();
- for (String localeStr : map.keySet()) {
- if (builder.length() > 0) {
- builder.append(LOCALE_AND_TIME_STR_SEPARATER);
- }
- final Long time = map.get(localeStr);
- builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER);
- builder.append(String.valueOf(time));
- }
- return builder.toString();
- }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
index 201a70d42..b10d08af3 100644
--- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
+++ b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
@@ -137,6 +137,7 @@ public class PrioritizedSerialExecutor {
public void shutdown() {
synchronized(mLock) {
mIsShutdown = true;
+ mThreadPoolExecutor.shutdown();
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
index 0f5cd80db..4521ec531 100644
--- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
+++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
@@ -37,12 +37,12 @@ public class RecapitalizeStatus {
CAPS_MODE_ALL_UPPER
};
- private static final int getStringMode(final String string, final String separators) {
+ private static final int getStringMode(final String string, final int[] sortedSeparators) {
if (StringUtils.isIdenticalAfterUpcase(string)) {
return CAPS_MODE_ALL_UPPER;
} else if (StringUtils.isIdenticalAfterDowncase(string)) {
return CAPS_MODE_ALL_LOWER;
- } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, separators)) {
+ } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, sortedSeparators)) {
return CAPS_MODE_FIRST_WORD_UPPER;
} else {
return CAPS_MODE_ORIGINAL_MIXED_CASE;
@@ -60,26 +60,28 @@ public class RecapitalizeStatus {
private int mRotationStyleCurrentIndex;
private boolean mSkipOriginalMixedCaseMode;
private Locale mLocale;
- private String mSeparators;
+ private int[] mSortedSeparators;
private String mStringAfter;
private boolean mIsActive;
+ private static final int[] EMPTY_STORTED_SEPARATORS = {};
+
public RecapitalizeStatus() {
// By default, initialize with dummy values that won't match any real recapitalize.
- initialize(-1, -1, "", Locale.getDefault(), "");
+ initialize(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS);
deactivate();
}
public void initialize(final int cursorStart, final int cursorEnd, final String string,
- final Locale locale, final String separators) {
+ final Locale locale, final int[] sortedSeparators) {
mCursorStartBefore = cursorStart;
mStringBefore = string;
mCursorStartAfter = cursorStart;
mCursorEndAfter = cursorEnd;
mStringAfter = string;
- final int initialMode = getStringMode(mStringBefore, separators);
+ final int initialMode = getStringMode(mStringBefore, sortedSeparators);
mLocale = locale;
- mSeparators = separators;
+ mSortedSeparators = sortedSeparators;
if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) {
mRotationStyleCurrentIndex = 0;
mSkipOriginalMixedCaseMode = false;
@@ -131,7 +133,7 @@ public class RecapitalizeStatus {
mStringAfter = mStringBefore.toLowerCase(mLocale);
break;
case CAPS_MODE_FIRST_WORD_UPPER:
- mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSeparators,
+ mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSortedSeparators,
mLocale);
break;
case CAPS_MODE_ALL_UPPER:
diff --git a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
index 7c6fe93ac..64c9e2cff 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
@@ -34,7 +34,7 @@ public final class ResizableIntArray {
throw new ArrayIndexOutOfBoundsException("length=" + mLength + "; index=" + index);
}
- public void add(final int index, final int val) {
+ public void addAt(final int index, final int val) {
if (index < mLength) {
mArray[index] = val;
} else {
diff --git a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
index 22c92446a..49f4929b4 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
@@ -67,7 +67,8 @@ public final class ResourceUtils {
sBuildKeyValuesDebugString = "[" + TextUtils.join(" ", keyValuePairs) + "]";
}
- public static String getDeviceOverrideValue(final Resources res, final int overrideResId) {
+ public static String getDeviceOverrideValue(final Resources res, final int overrideResId,
+ final String defaultValue) {
final int orientation = res.getConfiguration().orientation;
final String key = overrideResId + "-" + orientation;
if (sDeviceOverrideValueMap.containsKey(key)) {
@@ -86,23 +87,6 @@ public final class ResourceUtils {
return overrideValue;
}
- String defaultValue = null;
- try {
- defaultValue = findDefaultConstant(overrideArray);
- // The defaultValue might be an empty string.
- if (defaultValue == null) {
- Log.w(TAG, "Couldn't find override value nor default value:"
- + " resource="+ res.getResourceEntryName(overrideResId)
- + " build=" + sBuildKeyValuesDebugString);
- } else {
- Log.i(TAG, "Found default value:"
- + " resource="+ res.getResourceEntryName(overrideResId)
- + " build=" + sBuildKeyValuesDebugString
- + " default=" + defaultValue);
- }
- } catch (final DeviceOverridePatternSyntaxError e) {
- Log.w(TAG, "Syntax error, ignored", e);
- }
sDeviceOverrideValueMap.put(key, defaultValue);
return defaultValue;
}
@@ -152,8 +136,7 @@ public final class ResourceUtils {
}
final String condition = conditionConstant.substring(0, posComma);
if (condition.isEmpty()) {
- // Default condition. The default condition should be searched by
- // {@link #findConstantForDefault(String[])}.
+ Log.w(TAG, "Array element has no condition: " + conditionConstant);
continue;
}
try {
@@ -199,24 +182,6 @@ public final class ResourceUtils {
return matchedAll;
}
- @UsedForTesting
- static String findDefaultConstant(final String[] conditionConstantArray)
- throws DeviceOverridePatternSyntaxError {
- if (conditionConstantArray == null) {
- return null;
- }
- for (final String condition : conditionConstantArray) {
- final int posComma = condition.indexOf(',');
- if (posComma < 0) {
- throw new DeviceOverridePatternSyntaxError("Array element has no comma", condition);
- }
- if (posComma == 0) { // condition is empty.
- return condition.substring(posComma + 1);
- }
- }
- return null;
- }
-
public static int getDefaultKeyboardWidth(final Resources res) {
final DisplayMetrics dm = res.getDisplayMetrics();
return dm.widthPixels;
@@ -224,22 +189,23 @@ public final class ResourceUtils {
public static int getDefaultKeyboardHeight(final Resources res) {
final DisplayMetrics dm = res.getDisplayMetrics();
- final String keyboardHeightString = getDeviceOverrideValue(res, R.array.keyboard_heights);
+ final String keyboardHeightInDp = getDeviceOverrideValue(
+ res, R.array.keyboard_heights, null /* defaultValue */);
final float keyboardHeight;
- if (TextUtils.isEmpty(keyboardHeightString)) {
- keyboardHeight = res.getDimension(R.dimen.keyboardHeight);
+ if (TextUtils.isEmpty(keyboardHeightInDp)) {
+ keyboardHeight = res.getDimension(R.dimen.config_default_keyboard_height);
} else {
- keyboardHeight = Float.parseFloat(keyboardHeightString) * dm.density;
+ keyboardHeight = Float.parseFloat(keyboardHeightInDp) * dm.density;
}
final float maxKeyboardHeight = res.getFraction(
- R.fraction.maxKeyboardHeight, dm.heightPixels, dm.heightPixels);
+ R.fraction.config_max_keyboard_height, dm.heightPixels, dm.heightPixels);
float minKeyboardHeight = res.getFraction(
- R.fraction.minKeyboardHeight, dm.heightPixels, dm.heightPixels);
+ R.fraction.config_min_keyboard_height, dm.heightPixels, dm.heightPixels);
if (minKeyboardHeight < 0.0f) {
// Specified fraction was negative, so it should be calculated against display
// width.
minKeyboardHeight = -res.getFraction(
- R.fraction.minKeyboardHeight, dm.widthPixels, dm.widthPixels);
+ R.fraction.config_min_keyboard_height, dm.widthPixels, dm.widthPixels);
}
// Keyboard height will not exceed maxKeyboardHeight and will not be less than
// minKeyboardHeight.
@@ -260,6 +226,10 @@ public final class ResourceUtils {
return dimension >= 0;
}
+ public static float getFloatFromFraction(final Resources res, final int fractionResId) {
+ return res.getFraction(fractionResId, 1, 1);
+ }
+
public static float getFraction(final TypedArray a, final int index, final float defValue) {
final TypedValue value = a.peekValue(index);
if (value == null || !isFractionValue(value)) {
diff --git a/java/src/com/android/inputmethod/latin/utils/RunInLocale.java b/java/src/com/android/inputmethod/latin/utils/RunInLocale.java
index 2c9e3b191..3c632bbc3 100644
--- a/java/src/com/android/inputmethod/latin/utils/RunInLocale.java
+++ b/java/src/com/android/inputmethod/latin/utils/RunInLocale.java
@@ -30,25 +30,23 @@ public abstract class RunInLocale<T> {
* Execute {@link #job(Resources)} method in specified system locale exclusively.
*
* @param res the resources to use.
- * @param newLocale the locale to change to.
+ * @param newLocale the locale to change to. Run in system locale if null.
* @return the value returned from {@link #job(Resources)}.
*/
public T runInLocale(final Resources res, final Locale newLocale) {
synchronized (sLockForRunInLocale) {
- final Configuration conf = res.getConfiguration();
- final Locale oldLocale = conf.locale;
- final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale));
+ final Configuration savedConf = res.getConfiguration();
+ if (newLocale == null || newLocale.equals(savedConf.locale)) {
+ return job(res);
+ }
+ final Configuration newConf = new Configuration();
+ newConf.setTo(savedConf);
+ newConf.setLocale(newLocale);
try {
- if (needsChange) {
- conf.locale = newLocale;
- res.updateConfiguration(conf, null);
- }
+ res.updateConfiguration(newConf, null);
return job(res);
} finally {
- if (needsChange) {
- conf.locale = oldLocale;
- res.updateConfiguration(conf, null);
- }
+ res.updateConfiguration(savedConf, null);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
new file mode 100644
index 000000000..1ca895fdb
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
@@ -0,0 +1,58 @@
+/*
+ * 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/SpannableStringUtils.java b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
index b51fd9377..38164cb36 100644
--- a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
@@ -22,6 +22,7 @@ import android.text.Spanned;
import android.text.SpannedString;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
+import android.text.style.URLSpan;
public final class SpannableStringUtils {
/**
@@ -40,12 +41,17 @@ public final class SpannableStringUtils {
* are out of range in <code>dest</code>.
*/
public static void copyNonParagraphSuggestionSpansFrom(Spanned source, int start, int end,
- Spannable dest, int destoff) {
+ Spannable dest, int destoff) {
Object[] spans = source.getSpans(start, end, SuggestionSpan.class);
for (int i = 0; i < spans.length; i++) {
int fl = source.getSpanFlags(spans[i]);
- if (0 != (fl & Spannable.SPAN_PARAGRAPH)) continue;
+ // We don't care about the PARAGRAPH flag in LatinIME code. However, if this flag
+ // is set, Spannable#setSpan will throw an exception unless the span is on the edge
+ // of a word. But the spans have been split into two by the getText{Before,After}Cursor
+ // methods, so after concatenation they may end in the middle of a word.
+ // Since we don't use them, we can just remove them and avoid crashing.
+ fl &= ~Spannable.SPAN_PARAGRAPH;
int st = source.getSpanStart(spans[i]);
int en = source.getSpanEnd(spans[i]);
@@ -107,4 +113,16 @@ public final class SpannableStringUtils {
return new SpannedString(ss);
}
+
+ public static boolean hasUrlSpans(final CharSequence text,
+ final int startIndex, final int endIndex) {
+ if (!(text instanceof Spanned)) {
+ return false; // Not spanned, so no link
+ }
+ final Spanned spanned = (Spanned)text;
+ // getSpans(x, y) does not return spans that start on x or end on y. x-1, y+1 does the
+ // trick, and works in all cases even if startIndex <= 0 or endIndex >= text.length().
+ final URLSpan[] spans = spanned.getSpans(startIndex - 1, endIndex + 1, URLSpan.class);
+ return null != spans && spans.length > 0;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/StatsUtils.java b/java/src/com/android/inputmethod/latin/utils/StatsUtils.java
new file mode 100644
index 000000000..a059f877b
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/StatsUtils.java
@@ -0,0 +1,53 @@
+/*
+ * 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.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import com.android.inputmethod.latin.settings.Settings;
+
+public final class StatsUtils {
+ private static final String TAG = StatsUtils.class.getSimpleName();
+ private static final StatsUtils sInstance = new StatsUtils();
+
+ public static void onCreateCompleted(final Context context) {
+ sInstance.onCreateCompletedInternal(context);
+ }
+
+ private void onCreateCompletedInternal(final Context context) {
+ mContext = context;
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ final Boolean usePersonalizedDict =
+ prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true);
+ Log.d(TAG, "onCreateCompleted. context: " + context.toString() + "usePersonalizedDict: "
+ + usePersonalizedDict);
+ }
+
+ public static void onDestroy() {
+ sInstance.onDestroyInternal();
+ }
+
+ private void onDestroyInternal() {
+ Log.d(TAG, "onDestroy. context: " + mContext.toString());
+ mContext = null;
+ }
+
+ private Context mContext;
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index a36548392..374badc19 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -16,21 +16,15 @@
package com.android.inputmethod.latin.utils;
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.settings.SettingsValues;
+import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
import android.text.TextUtils;
-import android.util.JsonReader;
-import android.util.JsonWriter;
-import android.util.Log;
-import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.Constants;
+
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
+import java.util.Arrays;
import java.util.Locale;
public final class StringUtils {
@@ -39,6 +33,8 @@ public final class StringUtils {
public static final int CAPITALIZE_FIRST = 1; // First only
public static final int CAPITALIZE_ALL = 2; // All caps
+ private static final String EMPTY_STRING = "";
+
private StringUtils() {
// This utility class is not publicly instantiable.
}
@@ -50,7 +46,7 @@ public final class StringUtils {
public static String newSingleCodePointString(int codePoint) {
if (Character.charCount(codePoint) == 1) {
- // Optimization: avoid creating an temporary array for characters that are
+ // Optimization: avoid creating a temporary array for characters that are
// represented by a single char value
return String.valueOf((char) codePoint);
}
@@ -80,6 +76,20 @@ public final class StringUtils {
return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
}
+ public static String joinCommaSplittableText(final String head, final String tail) {
+ if (TextUtils.isEmpty(head) && TextUtils.isEmpty(tail)) {
+ return EMPTY_STRING;
+ }
+ // Here either head or tail is not null.
+ if (TextUtils.isEmpty(head)) {
+ return tail;
+ }
+ if (TextUtils.isEmpty(tail)) {
+ return head;
+ }
+ return head + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + tail;
+ }
+
public static String appendToCommaSplittableTextIfNotExists(final String text,
final String extraValues) {
if (TextUtils.isEmpty(extraValues)) {
@@ -94,7 +104,7 @@ public final class StringUtils {
public static String removeFromCommaSplittableTextIfExists(final String text,
final String extraValues) {
if (TextUtils.isEmpty(extraValues)) {
- return "";
+ return EMPTY_STRING;
}
final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
if (!containsInArray(text, elements)) {
@@ -162,20 +172,87 @@ public final class StringUtils {
private static final int[] EMPTY_CODEPOINTS = {};
- public static int[] toCodePointArray(final String string) {
- final int length = string.length();
+ public static int[] toCodePointArray(final CharSequence charSequence) {
+ return toCodePointArray(charSequence, 0, charSequence.length());
+ }
+
+ /**
+ * Converts a range of a string to an array of code points.
+ * @param charSequence the source string.
+ * @param startIndex the start index inside the string in java chars, inclusive.
+ * @param endIndex the end index inside the string in java chars, exclusive.
+ * @return a new array of code points. At most endIndex - startIndex, but possibly less.
+ */
+ public static int[] toCodePointArray(final CharSequence charSequence,
+ final int startIndex, final int endIndex) {
+ final int length = charSequence.length();
if (length <= 0) {
return EMPTY_CODEPOINTS;
}
- final int[] codePoints = new int[string.codePointCount(0, length)];
+ final int[] codePoints =
+ new int[Character.codePointCount(charSequence, startIndex, endIndex)];
+ copyCodePointsAndReturnCodePointCount(codePoints, charSequence, startIndex, endIndex,
+ false /* downCase */);
+ return codePoints;
+ }
+
+ /**
+ * Copies the codepoints in a CharSequence to an int array.
+ *
+ * This method assumes there is enough space in the array to store the code points. The size
+ * can be measured with Character#codePointCount(CharSequence, int, int) before passing to this
+ * method. If the int array is too small, an ArrayIndexOutOfBoundsException will be thrown.
+ * Also, this method makes no effort to be thread-safe. Do not modify the CharSequence while
+ * this method is running, or the behavior is undefined.
+ * This method can optionally downcase code points before copying them, but it pays no attention
+ * to locale while doing so.
+ *
+ * @param destination the int array.
+ * @param charSequence the CharSequence.
+ * @param startIndex the start index inside the string in java chars, inclusive.
+ * @param endIndex the end index inside the string in java chars, exclusive.
+ * @param downCase if this is true, code points will be downcased before being copied.
+ * @return the number of copied code points.
+ */
+ public static int copyCodePointsAndReturnCodePointCount(final int[] destination,
+ final CharSequence charSequence, final int startIndex, final int endIndex,
+ final boolean downCase) {
int destIndex = 0;
- for (int index = 0; index < length; index = string.offsetByCodePoints(index, 1)) {
- codePoints[destIndex] = string.codePointAt(index);
+ for (int index = startIndex; index < endIndex;
+ index = Character.offsetByCodePoints(charSequence, index, 1)) {
+ final int codePoint = Character.codePointAt(charSequence, index);
+ // TODO: stop using this, as it's not aware of the locale and does not always do
+ // the right thing.
+ destination[destIndex] = downCase ? Character.toLowerCase(codePoint) : codePoint;
destIndex++;
}
+ return destIndex;
+ }
+
+ public static int[] toSortedCodePointArray(final String string) {
+ final int[] codePoints = toCodePointArray(string);
+ Arrays.sort(codePoints);
return codePoints;
}
+ /**
+ * Construct a String from a code point array
+ *
+ * @param codePoints a code point array that is null terminated when its logical length is
+ * shorter than the array length.
+ * @return a string constructed from the code point array.
+ */
+ public static String getStringFromNullTerminatedCodePointArray(final int[] codePoints) {
+ int stringLength = codePoints.length;
+ for (int i = 0; i < codePoints.length; i++) {
+ if (codePoints[i] == 0) {
+ stringLength = i;
+ break;
+ }
+ }
+ return new String(codePoints, 0 /* offset */, stringLength);
+ }
+
// This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE.
public static int getCapitalizationType(final String text) {
// If the first char is not uppercase, then the word is either all lower case or
@@ -239,65 +316,58 @@ public final class StringUtils {
return true;
}
- @UsedForTesting
- public static boolean looksValidForDictionaryInsertion(final CharSequence text,
- final SettingsValues settings) {
- if (TextUtils.isEmpty(text)) return false;
+ /**
+ * Returns true if all code points in text are whitespace, false otherwise. Empty is true.
+ */
+ // Interestingly enough, U+00A0 NO-BREAK SPACE and U+200B ZERO-WIDTH SPACE are not considered
+ // whitespace, while EN SPACE, EM SPACE and IDEOGRAPHIC SPACES are.
+ public static boolean containsOnlyWhitespace(final String text) {
final int length = text.length();
int i = 0;
- int digitCount = 0;
while (i < length) {
- final int codePoint = Character.codePointAt(text, i);
- final int charCount = Character.charCount(codePoint);
- i += charCount;
- if (Character.isDigit(codePoint)) {
- // Count digits: see below
- digitCount += charCount;
- continue;
+ final int codePoint = text.codePointAt(i);
+ if (!Character.isWhitespace(codePoint)) {
+ return false;
}
- if (!settings.isWordCodePoint(codePoint)) return false;
+ i += Character.charCount(codePoint);
}
- // We reject strings entirely comprised of digits to avoid using PIN codes or credit
- // card numbers. It would come in handy for word prediction though; a good example is
- // when writing one's address where the street number is usually quite discriminative,
- // as well as the postal code.
- return digitCount < length;
+ return true;
}
public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
- final String separators) {
- boolean needCapsNext = true;
+ final int[] sortedSeparators) {
+ boolean needsCapsNext = true;
final int len = text.length();
for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
final int codePoint = text.codePointAt(i);
if (Character.isLetter(codePoint)) {
- if ((needCapsNext && !Character.isUpperCase(codePoint))
- || (!needCapsNext && !Character.isLowerCase(codePoint))) {
+ if ((needsCapsNext && !Character.isUpperCase(codePoint))
+ || (!needsCapsNext && !Character.isLowerCase(codePoint))) {
return false;
}
}
// We need a capital letter next if this is a separator.
- needCapsNext = (-1 != separators.indexOf(codePoint));
+ needsCapsNext = (Arrays.binarySearch(sortedSeparators, codePoint) >= 0);
}
return true;
}
// TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph
// which should be capitalized together in *some* cases.
- public static String capitalizeEachWord(final String text, final String separators,
+ public static String capitalizeEachWord(final String text, final int[] sortedSeparators,
final Locale locale) {
final StringBuilder builder = new StringBuilder();
- boolean needCapsNext = true;
+ boolean needsCapsNext = true;
final int len = text.length();
for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
final String nextChar = text.substring(i, text.offsetByCodePoints(i, 1));
- if (needCapsNext) {
+ if (needsCapsNext) {
builder.append(nextChar.toUpperCase(locale));
} else {
builder.append(nextChar.toLowerCase(locale));
}
// We need a capital letter next if this is a separator.
- needCapsNext = (-1 != separators.indexOf(nextChar.codePointAt(0)));
+ needsCapsNext = (Arrays.binarySearch(sortedSeparators, nextChar.codePointAt(0)) >= 0);
}
return builder.toString();
}
@@ -328,7 +398,7 @@ public final class StringUtils {
boolean hasPeriod = false;
int codePoint = 0;
while (i > 0) {
- codePoint = Character.codePointBefore(text, i);
+ codePoint = Character.codePointBefore(text, i);
if (codePoint < Constants.CODE_PERIOD || codePoint > 'z') {
// Handwavy heuristic to see if that's a URL character. Anything between period
// and z. This includes all lower- and upper-case ascii letters, period,
@@ -367,7 +437,49 @@ public final class StringUtils {
return false;
}
- public static boolean isEmptyStringOrWhiteSpaces(String s) {
+ /**
+ * Examines the string and returns whether we're inside a double quote.
+ *
+ * This is used to decide whether we should put an automatic space before or after a double
+ * quote character. If we're inside a quotation, then we want to close it, so we want a space
+ * after and not before. Otherwise, we want to open the quotation, so we want a space before
+ * and not after. Exception: after a digit, we never want a space because the "inch" or
+ * "minutes" use cases is dominant after digits.
+ * In the practice, we determine whether we are in a quotation or not by finding the previous
+ * double quote character, and looking at whether it's followed by whitespace. If so, that
+ * was a closing quotation mark, so we're not inside a double quote. If it's not followed
+ * by whitespace, then it was an opening quotation mark, and we're inside a quotation.
+ *
+ * @param text the text to examine.
+ * @return whether we're inside a double quote.
+ */
+ public static boolean isInsideDoubleQuoteOrAfterDigit(final CharSequence text) {
+ int i = text.length();
+ if (0 == i) return false;
+ int codePoint = Character.codePointBefore(text, i);
+ if (Character.isDigit(codePoint)) return true;
+ int prevCodePoint = 0;
+ while (i > 0) {
+ codePoint = Character.codePointBefore(text, i);
+ if (Constants.CODE_DOUBLE_QUOTE == codePoint) {
+ // If we see a double quote followed by whitespace, then that
+ // was a closing quote.
+ if (Character.isWhitespace(prevCodePoint)) return false;
+ }
+ if (Character.isWhitespace(codePoint) && Constants.CODE_DOUBLE_QUOTE == prevCodePoint) {
+ // If we see a double quote preceded by whitespace, then that
+ // was an opening quote. No need to continue seeking.
+ return true;
+ }
+ i -= Character.charCount(codePoint);
+ prevCodePoint = codePoint;
+ }
+ // We reached the start of text. If the first char is a double quote, then we're inside
+ // a double quote. Otherwise we're not.
+ return Constants.CODE_DOUBLE_QUOTE == codePoint;
+ }
+
+ public static boolean isEmptyStringOrWhiteSpaces(final String s) {
final int N = codePointCount(s);
for (int i = 0; i < N; ++i) {
if (!Character.isWhitespace(s.codePointAt(i))) {
@@ -378,9 +490,9 @@ public final class StringUtils {
}
@UsedForTesting
- public static String byteArrayToHexString(byte[] bytes) {
+ public static String byteArrayToHexString(final byte[] bytes) {
if (bytes == null || bytes.length == 0) {
- return "";
+ return EMPTY_STRING;
}
final StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
@@ -393,7 +505,7 @@ public final class StringUtils {
* Convert hex string to byte array. The string length must be an even number.
*/
@UsedForTesting
- public static byte[] hexStringToByteArray(String hexString) {
+ public static byte[] hexStringToByteArray(final String hexString) {
if (TextUtils.isEmpty(hexString)) {
return null;
}
@@ -410,66 +522,59 @@ public final class StringUtils {
return bytes;
}
- public static List<Object> jsonStrToList(String s) {
- final ArrayList<Object> retval = CollectionUtils.newArrayList();
- final JsonReader reader = new JsonReader(new StringReader(s));
- try {
- reader.beginArray();
- while(reader.hasNext()) {
- reader.beginObject();
- while (reader.hasNext()) {
- final String name = reader.nextName();
- if (name.equals(Integer.class.getSimpleName())) {
- retval.add(reader.nextInt());
- } else if (name.equals(String.class.getSimpleName())) {
- retval.add(reader.nextString());
- } else {
- Log.w(TAG, "Invalid name: " + name);
- reader.skipValue();
- }
- }
- reader.endObject();
- }
- reader.endArray();
- return retval;
- } catch (IOException e) {
- } finally {
- try {
- reader.close();
- } catch (IOException e) {
+ public static String toUpperCaseOfStringForLocale(final String text,
+ final boolean needsToUpperCase, final Locale locale) {
+ if (text == null || !needsToUpperCase) return text;
+ return text.toUpperCase(locale);
+ }
+
+ public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase,
+ final Locale locale) {
+ if (!Constants.isLetterCode(code) || !needsToUpperCase) return code;
+ final String text = newSingleCodePointString(code);
+ final String casedText = toUpperCaseOfStringForLocale(
+ text, needsToUpperCase, locale);
+ return codePointCount(casedText) == 1
+ ? casedText.codePointAt(0) : CODE_UNSPECIFIED;
+ }
+
+ @UsedForTesting
+ public static class Stringizer<E> {
+ public String stringize(final E element) {
+ return element != null ? element.toString() : "null";
+ }
+
+ @UsedForTesting
+ public final String join(final E[] array) {
+ return joinStringArray(toStringArray(array), null /* delimiter */);
+ }
+
+ @UsedForTesting
+ public final String join(final E[] array, final String delimiter) {
+ return joinStringArray(toStringArray(array), delimiter);
+ }
+
+ protected String[] toStringArray(final E[] array) {
+ final String[] stringArray = new String[array.length];
+ for (int index = 0; index < array.length; index++) {
+ stringArray[index] = stringize(array[index]);
}
+ return stringArray;
}
- return Collections.<Object>emptyList();
- }
- public static String listToJsonStr(List<Object> list) {
- if (list == null || list.isEmpty()) {
- return "";
- }
- final StringWriter sw = new StringWriter();
- final JsonWriter writer = new JsonWriter(sw);
- try {
- writer.beginArray();
- for (final Object o : list) {
- writer.beginObject();
- if (o instanceof Integer) {
- writer.name(Integer.class.getSimpleName()).value((Integer)o);
- } else if (o instanceof String) {
- writer.name(String.class.getSimpleName()).value((String)o);
- }
- writer.endObject();
+ protected String joinStringArray(final String[] stringArray, final String delimiter) {
+ if (stringArray == null) {
+ return "null";
}
- writer.endArray();
- return sw.toString();
- } catch (IOException e) {
- } finally {
- try {
- if (writer != null) {
- writer.close();
- }
- } catch (IOException e) {
+ if (delimiter == null) {
+ return Arrays.toString(stringArray);
+ }
+ final StringBuilder sb = new StringBuilder();
+ for (int index = 0; index < stringArray.length; index++) {
+ sb.append(index == 0 ? "[" : delimiter);
+ sb.append(stringArray[index]);
}
+ return sb + "]";
}
- return "";
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 102a41b4e..b37779bdc 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -25,17 +25,18 @@ import android.os.Build;
import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
-import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
public final class SubtypeLocaleUtils {
- static final String TAG = SubtypeLocaleUtils.class.getSimpleName();
- // This class must be located in the same package as LatinIME.java.
- private static final String RESOURCE_PACKAGE_NAME =
- DictionaryFactory.class.getPackage().getName();
+ private static final String TAG = SubtypeLocaleUtils.class.getSimpleName();
+
+ // This reference class {@link Constants} must be located in the same package as LatinIME.java.
+ private static final String RESOURCE_PACKAGE_NAME = Constants.class.getPackage().getName();
// Special language code to represent "no language".
public static final String NO_LANGUAGE = "zz";
@@ -43,7 +44,8 @@ public final class SubtypeLocaleUtils {
public static final String EMOJI = "emoji";
public static final int UNKNOWN_KEYBOARD_LAYOUT = R.string.subtype_generic;
- private static boolean sInitialized = false;
+ private static volatile boolean sInitialized = false;
+ private static final Object sInitializeLock = new Object();
private static Resources sResources;
private static String[] sPredefinedKeyboardLayoutSet;
// Keyboard layout to its display name map.
@@ -76,9 +78,16 @@ public final class SubtypeLocaleUtils {
}
// Note that this initialization method can be called multiple times.
- public static synchronized void init(final Context context) {
- if (sInitialized) return;
+ public static void init(final Context context) {
+ synchronized (sInitializeLock) {
+ if (sInitialized == false) {
+ initLocked(context);
+ sInitialized = true;
+ }
+ }
+ }
+ private static void initLocked(final Context context) {
final Resources res = context.getResources();
sResources = res;
@@ -121,8 +130,6 @@ public final class SubtypeLocaleUtils {
final String keyboardLayoutSet = keyboardLayoutSetMap[i + 1];
sLocaleAndExtraValueToKeyboardLayoutSetMap.put(key, keyboardLayoutSet);
}
-
- sInitialized = true;
}
public static String[] getPredefinedKeyboardLayoutSet() {
@@ -166,8 +173,18 @@ public final class SubtypeLocaleUtils {
return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
}
+ public static String getSubtypeLanguageDisplayName(final String localeString) {
+ final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+ final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
+ return getSubtypeLocaleDisplayNameInternal(locale.getLanguage(), displayLocale);
+ }
+
private static String getSubtypeLocaleDisplayNameInternal(final String localeString,
final Locale displayLocale) {
+ if (NO_LANGUAGE.equals(localeString)) {
+ // No language subtype should be displayed in system locale.
+ return sResources.getString(R.string.subtype_no_language);
+ }
final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
final String displayName;
if (exceptionalNameResId != null) {
@@ -178,9 +195,6 @@ public final class SubtypeLocaleUtils {
}
};
displayName = getExceptionalName.runInLocale(sResources, displayLocale);
- } else if (NO_LANGUAGE.equals(localeString)) {
- // No language subtype should be displayed in system locale.
- return sResources.getString(R.string.subtype_no_language);
} else {
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
displayName = locale.getDisplayName(displayLocale);
@@ -197,12 +211,14 @@ public final class SubtypeLocaleUtils {
// es_US spanish F Español (EE.UU.) exception
// fr azerty F Français
// fr_CA qwerty F Français (Canada)
+ // fr_CH swiss F Français (Suisse)
// de qwertz F Deutsch
- // zz qwerty F No language (QWERTY) in system locale
+ // de_CH swiss T Deutsch (Schweiz)
+ // zz qwerty F Alphabet (QWERTY) in system locale
// fr qwertz T Français (QWERTZ)
// de qwerty T Deutsch (QWERTY)
// en_US azerty T English (US) (AZERTY) exception
- // zz azerty T No language (AZERTY) in system locale
+ // zz azerty T Alphabet (AZERTY) in system locale
private static String getReplacementString(final InputMethodSubtype subtype,
final Locale displayLocale) {
@@ -289,45 +305,23 @@ public final class SubtypeLocaleUtils {
return keyboardLayoutSet;
}
- // InputMethodSubtype's display name for spacebar text in its locale.
- // isAdditionalSubtype (T=true, F=false)
- // locale layout | Short Middle Full
- // ------ ------- - ---- --------- ----------------------
- // en_US qwerty F En English English (US) exception
- // en_GB qwerty F En English English (UK) exception
- // es_US spanish F Es Español Español (EE.UU.) exception
- // fr azerty F Fr Français Français
- // fr_CA qwerty F Fr Français Français (Canada)
- // de qwertz F De Deutsch Deutsch
- // zz qwerty F QWERTY QWERTY
- // fr qwertz T Fr Français Français
- // de qwerty T De Deutsch Deutsch
- // en_US azerty T En 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 (isNoLanguage(subtype)) {
- return getKeyboardLayoutSetDisplayName(subtype);
- }
- return getSubtypeLocaleDisplayName(subtype.getLocale());
+ // TODO: Get this information from the framework instead of maintaining here by ourselves.
+ // Sorted list of known Right-To-Left language codes.
+ private static final String[] SORTED_RTL_LANGUAGES = {
+ "ar", // Arabic
+ "fa", // Persian
+ "iw", // Hebrew
+ };
+ static {
+ Arrays.sort(SORTED_RTL_LANGUAGES);
}
- // Get InputMethodSubtype's middle display name in its locale.
- public static String getMiddleDisplayName(final InputMethodSubtype subtype) {
- if (isNoLanguage(subtype)) {
- return getKeyboardLayoutSetDisplayName(subtype);
- }
- final Locale locale = getSubtypeLocale(subtype);
- return getSubtypeLocaleDisplayName(locale.getLanguage());
+ public static boolean isRtlLanguage(final Locale locale) {
+ final String language = locale.getLanguage();
+ return Arrays.binarySearch(SORTED_RTL_LANGUAGES, language) >= 0;
}
- // Get InputMethodSubtype's short display name in its locale.
- public static String getShortDisplayName(final InputMethodSubtype subtype) {
- if (isNoLanguage(subtype)) {
- return "";
- }
- final Locale locale = getSubtypeLocale(subtype);
- return StringUtils.capitalizeFirstCodePoint(locale.getLanguage(), locale);
+ public static boolean isRtlLanguage(final InputMethodSubtype 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
new file mode 100644
index 000000000..0b362c48a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
@@ -0,0 +1,76 @@
+/*
+ * 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 com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.TreeSet;
+
+/**
+ * A TreeSet of SuggestedWordInfo that is bounded in size and throws everything that's smaller
+ * than its limit
+ */
+public final class SuggestionResults extends TreeSet<SuggestedWordInfo> {
+ public final Locale mLocale;
+ private final int mCapacity;
+
+ public SuggestionResults(final Locale locale, final int capacity) {
+ this(locale, sSuggestedWordInfoComparator, capacity);
+ }
+
+ public SuggestionResults(final Locale locale, final Comparator<SuggestedWordInfo> comparator,
+ final int capacity) {
+ super(comparator);
+ mLocale = locale;
+ mCapacity = capacity;
+ }
+
+ @Override
+ public boolean add(final SuggestedWordInfo e) {
+ if (size() < mCapacity) return super.add(e);
+ if (comparator().compare(e, last()) > 0) return false;
+ super.add(e);
+ pollLast(); // removes the last element
+ return true;
+ }
+
+ @Override
+ public boolean addAll(final Collection<? extends SuggestedWordInfo> e) {
+ if (null == e) return false;
+ return super.addAll(e);
+ }
+
+ private static final class SuggestedWordInfoComparator
+ implements Comparator<SuggestedWordInfo> {
+ // This comparator ranks the word info with the higher frequency first. That's because
+ // that's the order we want our elements in.
+ @Override
+ public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) {
+ if (o1.mScore > o2.mScore) return -1;
+ if (o1.mScore < o2.mScore) return 1;
+ if (o1.mCodePointCount < o2.mCodePointCount) return -1;
+ if (o1.mCodePointCount > o2.mCodePointCount) return 1;
+ return o1.mWord.compareTo(o2.mWord);
+ }
+ }
+
+ private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator =
+ new SuggestedWordInfoComparator();
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
index afbe2ecad..42ea3c959 100644
--- a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
+++ b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
@@ -22,6 +22,8 @@ import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.util.LruCache;
+import com.android.inputmethod.compat.AppWorkaroundsUtils;
+
public final class TargetPackageInfoGetterTask extends
AsyncTask<String, Void, PackageInfo> {
private static final int MAX_CACHE_ENTRIES = 64; // arbitrary
@@ -37,17 +39,13 @@ public final class TargetPackageInfoGetterTask extends
sCache.remove(packageName);
}
- public interface OnTargetPackageInfoKnownListener {
- public void onTargetPackageInfoKnown(final PackageInfo info);
- }
-
private Context mContext;
- private final OnTargetPackageInfoKnownListener mListener;
+ private final AsyncResultHolder<AppWorkaroundsUtils> mResult;
public TargetPackageInfoGetterTask(final Context context,
- final OnTargetPackageInfoKnownListener listener) {
+ final AsyncResultHolder<AppWorkaroundsUtils> result) {
mContext = context;
- mListener = listener;
+ mResult = result;
}
@Override
@@ -65,6 +63,6 @@ public final class TargetPackageInfoGetterTask extends
@Override
protected void onPostExecute(final PackageInfo info) {
- mListener.onTargetPackageInfoKnown(info);
+ mResult.set(new AppWorkaroundsUtils(info));
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/TextRange.java b/java/src/com/android/inputmethod/latin/utils/TextRange.java
index 48b443ddd..dbf3b5060 100644
--- a/java/src/com/android/inputmethod/latin/utils/TextRange.java
+++ b/java/src/com/android/inputmethod/latin/utils/TextRange.java
@@ -31,6 +31,7 @@ public final class TextRange {
private final int mCursorIndex;
public final CharSequence mWord;
+ public final boolean mHasUrlSpans;
public int getNumberOfCharsInWordBeforeCursor() {
return mCursorIndex - mWordAtCursorStartIndex;
@@ -95,7 +96,7 @@ public final class TextRange {
}
}
if (spanStart == mWordAtCursorStartIndex && spanEnd == mWordAtCursorEndIndex) {
- // If the span does not start and stop here, we ignore it. It probably extends
+ // If the span does not start and stop here, ignore it. It probably extends
// past the start or end of the word, as happens in missing space correction
// or EasyEditSpans put by voice input.
spans[writeIndex++] = spans[readIndex];
@@ -105,7 +106,7 @@ public final class TextRange {
}
public TextRange(final CharSequence textAtCursor, final int wordAtCursorStartIndex,
- final int wordAtCursorEndIndex, final int cursorIndex) {
+ final int wordAtCursorEndIndex, final int cursorIndex, final boolean hasUrlSpans) {
if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex
|| cursorIndex > wordAtCursorEndIndex
|| wordAtCursorEndIndex > textAtCursor.length()) {
@@ -115,6 +116,7 @@ public final class TextRange {
mWordAtCursorStartIndex = wordAtCursorStartIndex;
mWordAtCursorEndIndex = wordAtCursorEndIndex;
mCursorIndex = cursorIndex;
+ mHasUrlSpans = hasUrlSpans;
mWord = mTextAtCursor.subSequence(mWordAtCursorStartIndex, mWordAtCursorEndIndex);
}
} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
index 47ea1ea75..087a7f255 100644
--- a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -22,6 +22,9 @@ import android.graphics.Typeface;
import android.util.SparseArray;
public final class TypefaceUtils {
+ private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
+ private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
+
private TypefaceUtils() {
// This utility class is not publicly instantiable.
}
@@ -31,7 +34,7 @@ public final class TypefaceUtils {
// Working variable for the following method.
private static final Rect sTextHeightBounds = new Rect();
- public static float getCharHeight(final char[] referenceChar, final Paint paint) {
+ private static float getCharHeight(final char[] referenceChar, final Paint paint) {
final int key = getCharGeometryCacheKey(referenceChar[0], paint);
synchronized (sTextHeightCache) {
final Float cachedValue = sTextHeightCache.get(key);
@@ -51,7 +54,7 @@ public final class TypefaceUtils {
// Working variable for the following method.
private static final Rect sTextWidthBounds = new Rect();
- public static float getCharWidth(final char[] referenceChar, final Paint paint) {
+ private static float getCharWidth(final char[] referenceChar, final Paint paint) {
final int key = getCharGeometryCacheKey(referenceChar[0], paint);
synchronized (sTextWidthCache) {
final Float cachedValue = sTextWidthCache.get(key);
@@ -66,11 +69,6 @@ public final class TypefaceUtils {
}
}
- public static float getStringWidth(final String string, final Paint paint) {
- paint.getTextBounds(string, 0, string.length(), sTextWidthBounds);
- return sTextWidthBounds.width();
- }
-
private static int getCharGeometryCacheKey(final char referenceChar, final Paint paint) {
final int labelSize = (int)paint.getTextSize();
final Typeface face = paint.getTypeface();
@@ -86,9 +84,25 @@ public final class TypefaceUtils {
}
}
- public static float getLabelWidth(final String label, final Paint paint) {
- final Rect textBounds = new Rect();
- paint.getTextBounds(label, 0, label.length(), textBounds);
- return textBounds.width();
+ public static float getReferenceCharHeight(final Paint paint) {
+ return getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ public static float getReferenceCharWidth(final Paint paint) {
+ return getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ public static float getReferenceDigitWidth(final Paint paint) {
+ return getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ // Working variable for the following method.
+ private static final Rect sStringWidthBounds = new Rect();
+
+ public static float getStringWidth(final String string, final Paint paint) {
+ synchronized (sStringWidthBounds) {
+ paint.getTextBounds(string, 0, string.length(), sStringWidthBounds);
+ return sStringWidthBounds.width();
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
deleted file mode 100644
index 635afe7cc..000000000
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2012 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.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
-import com.android.inputmethod.latin.makedict.DictDecoder;
-import com.android.inputmethod.latin.makedict.DictEncoder;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
-import com.android.inputmethod.latin.makedict.PendingAttribute;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Reads and writes Binary files for a UserHistoryDictionary.
- *
- * All the methods in this class are static.
- */
-public final class UserHistoryDictIOUtils {
- private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName();
- private static final boolean DEBUG = false;
- private static final String USES_FORGETTING_CURVE_KEY = "USES_FORGETTING_CURVE";
- private static final String USES_FORGETTING_CURVE_VALUE = "1";
- private static final String LAST_UPDATED_TIME_KEY = "date";
-
- public interface OnAddWordListener {
- /**
- * Callback to be notified when a word is added to the dictionary.
- * @param word The added word.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this word.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist).
- * Unspecified if shortcutTarget is null - do not rely on its value.
- */
- public void setUnigram(final String word, final String shortcutTarget, final int frequency,
- final int shortcutFreq);
- public void setBigram(final String word1, final String word2, final int frequency);
- }
-
- @UsedForTesting
- public interface BigramDictionaryInterface {
- public int getFrequency(final String word1, final String word2);
- }
-
- /**
- * Writes dictionary to file.
- */
- public static void writeDictionary(final DictEncoder dictEncoder,
- final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
- final FormatOptions formatOptions) {
- final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams);
- fusionDict.addOptionAttribute(USES_FORGETTING_CURVE_KEY, USES_FORGETTING_CURVE_VALUE);
- fusionDict.addOptionAttribute(LAST_UPDATED_TIME_KEY,
- String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
- try {
- dictEncoder.writeDictionary(fusionDict, formatOptions);
- Log.d(TAG, "end writing");
- } catch (IOException e) {
- Log.e(TAG, "IO exception while writing file", e);
- } catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported format", e);
- }
- }
-
- /**
- * Constructs a new FusionDictionary from BigramDictionaryInterface.
- */
- @UsedForTesting
- static FusionDictionary constructFusionDictionary(
- final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) {
- final FusionDictionary fusionDict = new FusionDictionary(new PtNodeArray(),
- new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
- false));
- int profTotal = 0;
- for (final String word1 : bigrams.keySet()) {
- final HashMap<String, Byte> word1Bigrams = bigrams.getBigrams(word1);
- for (final String word2 : word1Bigrams.keySet()) {
- final int freq = dict.getFrequency(word1, word2);
- if (freq == -1) {
- // don't add this bigram.
- continue;
- }
- if (DEBUG) {
- if (word1 == null) {
- Log.d(TAG, "add unigram: " + word2 + "," + Integer.toString(freq));
- } else {
- Log.d(TAG, "add bigram: " + word1
- + "," + word2 + "," + Integer.toString(freq));
- }
- profTotal++;
- }
- if (word1 == null) { // unigram
- fusionDict.add(word2, freq, null, false /* isNotAWord */);
- } else { // bigram
- if (FusionDictionary.findWordInTree(fusionDict.mRootNodeArray, word1) == null) {
- fusionDict.add(word1, 2, null, false /* isNotAWord */);
- }
- fusionDict.setBigram(word1, word2, freq);
- }
- bigrams.updateBigram(word1, word2, (byte)freq);
- }
- }
- if (DEBUG) {
- Log.d(TAG, "add " + profTotal + "words");
- }
- return fusionDict;
- }
-
- /**
- * Reads dictionary from file.
- */
- public static void readDictionaryBinary(final DictDecoder dictDecoder,
- final OnAddWordListener dict) {
- final TreeMap<Integer, String> unigrams = CollectionUtils.newTreeMap();
- final TreeMap<Integer, Integer> frequencies = CollectionUtils.newTreeMap();
- final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams = CollectionUtils.newTreeMap();
- try {
- dictDecoder.readUnigramsAndBigramsBinary(unigrams, frequencies, bigrams);
- } catch (IOException e) {
- Log.e(TAG, "IO exception while reading file", e);
- } catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported format", e);
- } catch (ArrayIndexOutOfBoundsException e) {
- Log.e(TAG, "ArrayIndexOutOfBoundsException while reading file", e);
- }
- addWordsFromWordMap(unigrams, frequencies, bigrams, dict);
- }
-
- /**
- * Adds all unigrams and bigrams in maps to OnAddWordListener.
- */
- @UsedForTesting
- static void addWordsFromWordMap(final TreeMap<Integer, String> unigrams,
- final TreeMap<Integer, Integer> frequencies,
- final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams,
- final OnAddWordListener to) {
- for (Entry<Integer, String> entry : unigrams.entrySet()) {
- final String word1 = entry.getValue();
- final int unigramFrequency = frequencies.get(entry.getKey());
- to.setUnigram(word1, null /* shortcutTarget */, unigramFrequency, 0 /* shortcutFreq */);
- final ArrayList<PendingAttribute> attrList = bigrams.get(entry.getKey());
- if (attrList != null) {
- for (final PendingAttribute attr : attrList) {
- final String word2 = unigrams.get(attr.mAddress);
- if (word1 == null || word2 == null) {
- Log.e(TAG, "Invalid bigram pair detected: " + word1 + ", " + word2);
- continue;
- }
- to.setBigram(word1, word2,
- BinaryDictIOUtils.reconstructBigramFrequency(unigramFrequency,
- attr.mFrequency));
- }
- }
- }
-
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
deleted file mode 100644
index 1992b2f5d..000000000
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2012 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.util.Log;
-
-import java.util.concurrent.TimeUnit;
-
-public final class UserHistoryForgettingCurveUtils {
- private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
- private static final boolean DEBUG = false;
- private static final int DEFAULT_FC_FREQ = 127;
- private static final int BOOSTED_FC_FREQ = 200;
- private static int FC_FREQ_MAX = DEFAULT_FC_FREQ;
- /* package */ static final int COUNT_MAX = 3;
- private static final int FC_LEVEL_MAX = 3;
- /* package */ static final int ELAPSED_TIME_MAX = 15;
- private static final int ELAPSED_TIME_INTERVAL_HOURS = 6;
- private static final long ELAPSED_TIME_INTERVAL_MILLIS =
- TimeUnit.HOURS.toMillis(ELAPSED_TIME_INTERVAL_HOURS);
- private static final int HALF_LIFE_HOURS = 48;
- private static final int MAX_PUSH_ELAPSED = (FC_LEVEL_MAX + 1) * (ELAPSED_TIME_MAX + 1);
-
- public static void boostMaxFreqForDebug() {
- FC_FREQ_MAX = BOOSTED_FC_FREQ;
- }
-
- public static void resetMaxFreqForDebug() {
- FC_FREQ_MAX = DEFAULT_FC_FREQ;
- }
-
- private UserHistoryForgettingCurveUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static final class ForgettingCurveParams {
- private byte mFc;
- long mLastTouchedTime = 0;
- private final boolean mIsValid;
-
- private void updateLastTouchedTime() {
- mLastTouchedTime = System.currentTimeMillis();
- }
-
- public ForgettingCurveParams(boolean isValid) {
- this(System.currentTimeMillis(), isValid);
- }
-
- private ForgettingCurveParams(long now, boolean isValid) {
- this(pushCount((byte)0, isValid), now, now, isValid);
- }
-
- /** This constructor is called when the user history bigram dictionary is being restored. */
- public ForgettingCurveParams(int fc, long now, long last) {
- // All words with level >= 1 had been saved.
- // Invalid words with level == 0 had been saved.
- // Valid words words with level == 0 had *not* been saved.
- this(fc, now, last, fcToLevel((byte)fc) > 0);
- }
-
- private ForgettingCurveParams(int fc, long now, long last, boolean isValid) {
- mIsValid = isValid;
- mFc = (byte)fc;
- mLastTouchedTime = last;
- updateElapsedTime(now);
- }
-
- public boolean isValid() {
- return mIsValid;
- }
-
- public byte getFc() {
- updateElapsedTime(System.currentTimeMillis());
- return mFc;
- }
-
- public int getFrequency() {
- updateElapsedTime(System.currentTimeMillis());
- return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
- }
-
- public int notifyTypedAgainAndGetFrequency() {
- updateLastTouchedTime();
- // TODO: Check whether this word is valid or not
- mFc = pushCount(mFc, false);
- return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
- }
-
- private void updateElapsedTime(long now) {
- final int elapsedTimeCount =
- (int)((now - mLastTouchedTime) / ELAPSED_TIME_INTERVAL_MILLIS);
- if (elapsedTimeCount <= 0) {
- return;
- }
- if (elapsedTimeCount >= MAX_PUSH_ELAPSED) {
- mLastTouchedTime = now;
- mFc = 0;
- return;
- }
- for (int i = 0; i < elapsedTimeCount; ++i) {
- mLastTouchedTime += ELAPSED_TIME_INTERVAL_MILLIS;
- mFc = pushElapsedTime(mFc);
- }
- }
- }
-
- /* package */ static int fcToElapsedTime(byte fc) {
- return fc & 0x0F;
- }
-
- /* package */ static int fcToCount(byte fc) {
- return (fc >> 4) & 0x03;
- }
-
- /* package */ static int fcToLevel(byte fc) {
- return (fc >> 6) & 0x03;
- }
-
- private static int calcFreq(int elapsedTime, int count, int level) {
- if (level <= 0) {
- // Reserved words, just return -1
- return -1;
- }
- if (count == COUNT_MAX) {
- // Temporary promote because it's frequently typed recently
- ++level;
- }
- final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
- final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
- return MathUtils.SCORE_TABLE[l - 1][et];
- }
-
- /* pakcage */ static byte calcFc(int elapsedTime, int count, int level) {
- final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
- final int c = Math.min(COUNT_MAX, Math.max(0, count));
- final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
- return (byte)(et | (c << 4) | (l << 6));
- }
-
- public static int fcToFreq(byte fc) {
- final int elapsedTime = fcToElapsedTime(fc);
- final int count = fcToCount(fc);
- final int level = fcToLevel(fc);
- return calcFreq(elapsedTime, count, level);
- }
-
- public static byte pushElapsedTime(byte fc) {
- int elapsedTime = fcToElapsedTime(fc);
- int count = fcToCount(fc);
- int level = fcToLevel(fc);
- if (elapsedTime >= ELAPSED_TIME_MAX) {
- // Downgrade level
- elapsedTime = 0;
- count = COUNT_MAX;
- --level;
- } else {
- ++elapsedTime;
- }
- return calcFc(elapsedTime, count, level);
- }
-
- public static byte pushCount(byte fc, boolean isValid) {
- final int elapsedTime = fcToElapsedTime(fc);
- int count = fcToCount(fc);
- int level = fcToLevel(fc);
- if ((elapsedTime == 0 && count >= COUNT_MAX) || (isValid && level == 0)) {
- // Upgrade level
- ++level;
- count = 0;
- if (DEBUG) {
- Log.d(TAG, "Upgrade level.");
- }
- } else {
- ++count;
- }
- return calcFc(0, count, level);
- }
-
- // TODO: isValid should be false for a word whose frequency is 0,
- // or that is not in the dictionary.
- /**
- * Check wheather we should save the bigram to the SQL DB or not
- */
- public static boolean needsToSave(byte fc, boolean isValid, boolean addLevel0Bigram) {
- int level = fcToLevel(fc);
- if (level == 0) {
- if (isValid || !addLevel0Bigram) {
- return false;
- }
- }
- final int elapsedTime = fcToElapsedTime(fc);
- return (elapsedTime < ELAPSED_TIME_MAX - 1 || level > 0);
- }
-
- private static final class MathUtils {
- public static final int[][] SCORE_TABLE = new int[FC_LEVEL_MAX][ELAPSED_TIME_MAX + 1];
- static {
- for (int i = 0; i < FC_LEVEL_MAX; ++i) {
- final float initialFreq;
- if (i >= 2) {
- initialFreq = FC_FREQ_MAX;
- } else if (i == 1) {
- initialFreq = FC_FREQ_MAX / 2;
- } else if (i == 0) {
- initialFreq = FC_FREQ_MAX / 4;
- } else {
- continue;
- }
- for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
- final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
- final float freq = initialFreq
- * (float)Math.pow(initialFreq, elapsedHours / HALF_LIFE_HOURS);
- final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
- SCORE_TABLE[i][j] = intFreq;
- }
- }
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java
index 2beebdfae..6170b4339 100644
--- a/java/src/com/android/inputmethod/research/JsonUtils.java
+++ b/java/src/com/android/inputmethod/research/JsonUtils.java
@@ -91,7 +91,7 @@ import java.util.Map;
jsonWriter.name("willAutoCorrect")
.value(words.mWillAutoCorrect);
jsonWriter.name("isPunctuationSuggestions")
- .value(words.mIsPunctuationSuggestions);
+ .value(words.isPunctuationSuggestions());
jsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
jsonWriter.name("isPrediction").value(words.mIsPrediction);
jsonWriter.name("suggestedWords");
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 6df7c1708..ffdb43c15 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -20,7 +20,7 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.IOException;
@@ -75,9 +75,7 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
// The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams.
public static final int N_GRAM_SIZE = 2;
- // TODO: Remove dependence on Suggest, and pass in Dictionary as a parameter to an appropriate
- // method.
- private final Suggest mSuggest;
+ private final DictionaryFacilitatorForSuggest mDictionaryFacilitator;
@UsedForTesting
private Dictionary mDictionaryForTesting;
private boolean mIsStopping = false;
@@ -89,11 +87,11 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
/* package for test */ int mNumWordsUntilSafeToSample;
public MainLogBuffer(final int wordsBetweenSamples, final int numInitialWordsToIgnore,
- final Suggest suggest) {
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
super(N_GRAM_SIZE + wordsBetweenSamples);
mNumWordsBetweenNGrams = wordsBetweenSamples;
mNumWordsUntilSafeToSample = DEBUG ? 0 : numInitialWordsToIgnore;
- mSuggest = suggest;
+ mDictionaryFacilitator = dictionaryFacilitator;
}
@UsedForTesting
@@ -101,12 +99,14 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
mDictionaryForTesting = dictionary;
}
- private Dictionary getDictionary() {
+ private boolean isValidDictWord(final String word) {
if (mDictionaryForTesting != null) {
- return mDictionaryForTesting;
+ return mDictionaryForTesting.isValidWord(word);
}
- if (mSuggest == null || !mSuggest.hasMainDictionary()) return null;
- return mSuggest.getMainDictionary();
+ if (mDictionaryFacilitator != null) {
+ return mDictionaryFacilitator.isValidMainDictWord(word);
+ }
+ return false;
}
public void setIsStopping() {
@@ -155,8 +155,9 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
}
// Reload the dictionary in case it has changed (e.g., because the user has changed
// languages).
- final Dictionary dictionary = getDictionary();
- if (dictionary == null) {
+ if ((mDictionaryFacilitator == null
+ || !mDictionaryFacilitator.hasInitializedMainDictionary())
+ && mDictionaryForTesting == null) {
// Main dictionary is unavailable. Since we cannot check it, we cannot tell if a
// word is out-of-vocabulary or not. Therefore, we must judge the entire buffer
// contents to potentially pose a privacy risk.
@@ -166,7 +167,6 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
// Check each word in the buffer. If any word poses a privacy threat, we cannot upload
// the complete buffer contents in detail.
int numWordsInLogUnitList = 0;
- final int length = logUnits.size();
for (final LogUnit logUnit : logUnits) {
if (!logUnit.hasOneOrMoreWords()) {
// Digits outside words are a privacy threat.
@@ -178,11 +178,11 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
final String[] words = logUnit.getWordsAsStringArray();
for (final String word : words) {
// Words not in the dictionary are a privacy threat.
- if (ResearchLogger.hasLetters(word) && !(dictionary.isValidWord(word))) {
+ if (ResearchLogger.hasLetters(word) && !isValidDictWord(word)) {
if (DEBUG) {
Log.d(TAG, "\"" + word + "\" NOT SAFE!: hasLetters: "
+ ResearchLogger.hasLetters(word)
- + ", isValid: " + (dictionary.isValidWord(word)));
+ + ", isValid: " + isValidDictWord(word));
}
return PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY;
}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index da9c61103..b1f54c0b4 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -52,14 +52,14 @@ import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputConnection;
-import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
import com.android.inputmethod.research.MotionEventReader.ReplayData;
import com.android.inputmethod.research.ui.SplashScreen;
@@ -102,10 +102,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
private static final boolean DEBUG_REPLAY_AFTER_FEEDBACK = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- // Whether the TextView contents are logged at the end of the session. true will disclose
- // private info.
- private static final boolean LOG_FULL_TEXTVIEW_CONTENTS = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
// Whether the feedback dialog preserves the editable text across invocations. Should be false
// for normal research builds so users do not have to delete the same feedback string they
// entered earlier. Should be true for builds internal to a development team so when the text
@@ -113,7 +109,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// feedback mechanism to generate multiple tests.
private static final boolean FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD = false;
/* package */ static boolean sIsLogging = false;
- private static final int OUTPUT_FORMAT_VERSION = 5;
+ private static final int OUTPUT_FORMAT_VERSION = 6;
// Whether all words should be recorded, leaving unsampled word between bigrams. Useful for
// testing.
/* package for test */ static final boolean IS_LOGGING_EVERYTHING = false
@@ -136,7 +132,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public static final String RESEARCH_KEY_OUTPUT_TEXT = ".research.";
// constants related to specific log points
- private static final String WHITESPACE_SEPARATORS = " \t\n\r";
+ private static final int[] WHITESPACE_SEPARATORS =
+ StringUtils.toSortedCodePointArray(" \t\n\r");
private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
private static final String PREF_RESEARCH_SAVED_CHANNEL = "pref_research_saved_channel";
@@ -168,12 +165,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// U+E001 is in the "private-use area"
/* package for test */ static final String WORD_REPLACEMENT_STRING = "\uE001";
protected static final int SUSPEND_DURATION_IN_MINUTES = 1;
- // set when LatinIME should ignore an onUpdateSelection() callback that
- // arises from operations in this class
- private static boolean sLatinIMEExpectingUpdateSelection = false;
// used to check whether words are not unique
- private Suggest mSuggest;
+ private DictionaryFacilitatorForSuggest mDictionaryFacilitator;
private MainKeyboardView mMainKeyboardView;
// TODO: Check whether a superclass can be used instead of LatinIME.
/* package for test */ LatinIME mLatinIME;
@@ -212,8 +206,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return sInstance;
}
- public void init(final LatinIME latinIME, final KeyboardSwitcher keyboardSwitcher,
- final Suggest suggest) {
+ public void init(final LatinIME latinIME, final KeyboardSwitcher keyboardSwitcher) {
assert latinIME != null;
mLatinIME = latinIME;
mPrefs = PreferenceManager.getDefaultSharedPreferences(latinIME);
@@ -249,7 +242,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
System.currentTimeMillis(), System.nanoTime()), mLatinIME);
final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1);
mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore,
- mSuggest) {
+ mDictionaryFacilitator) {
@Override
protected void publish(final ArrayList<LogUnit> logUnits,
boolean canIncludePrivateData) {
@@ -262,10 +255,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
+ ", cipd: " + canIncludePrivateData);
}
for (final String word : logUnit.getWordsAsStringArray()) {
- final Dictionary dictionary = getDictionary();
+ final boolean isDictionaryWord = mDictionaryFacilitator != null
+ && mDictionaryFacilitator.isValidMainDictWord(word);
mStatistics.recordWordEntered(
- dictionary != null && dictionary.isValidWord(word),
- logUnit.containsUserDeletions());
+ isDictionaryWord, logUnit.containsUserDeletions());
}
}
publishLogUnits(logUnits, mMainResearchLog, canIncludePrivateData);
@@ -663,8 +656,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mInFeedbackDialog = false;
}
- public void initSuggest(final Suggest suggest) {
- mSuggest = suggest;
+ public void initDictionary(final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
+ mDictionaryFacilitator = dictionaryFacilitator;
// MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create
// a new one.
if (mMainLogBuffer != null) {
@@ -672,13 +665,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
}
- private Dictionary getDictionary() {
- if (mSuggest == null) {
- return null;
- }
- return mSuggest.getMainDictionary();
- }
-
private void setIsPasswordView(boolean isPasswordView) {
mIsPasswordView = isPasswordView;
}
@@ -972,11 +958,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
private String scrubWord(String word) {
- final Dictionary dictionary = getDictionary();
- if (dictionary == null) {
- return WORD_REPLACEMENT_STRING;
- }
- if (dictionary.isValidWord(word)) {
+ if (mDictionaryFacilitator != null && mDictionaryFacilitator.isValidMainDictWord(word)) {
return word;
}
return WORD_REPLACEMENT_STRING;
@@ -1126,12 +1108,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
new Object[] { applicationSpecifiedCompletions });
}
- public static boolean getAndClearLatinIMEExpectingUpdateSelection() {
- boolean returnValue = sLatinIMEExpectingUpdateSelection;
- sLatinIMEExpectingUpdateSelection = false;
- return returnValue;
- }
-
/**
* The IME is finishing; it is either being destroyed, or is about to be hidden.
*
@@ -1141,59 +1117,19 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private static final LogStatement LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL =
new LogStatement("LatinIMEOnFinishInputViewInternal", false, false, "isTextTruncated",
"text");
- public static void latinIME_onFinishInputViewInternal(final boolean finishingInput,
- final int savedSelectionStart, final int savedSelectionEnd, final InputConnection ic) {
+ public static void latinIME_onFinishInputViewInternal(final boolean finishingInput) {
// The finishingInput flag is set in InputMethodService. It is true if called from
// doFinishInput(), which can be called as part of doStartInput(). This can happen at times
// when the IME is not closing, such as when powering up. The finishinInput flag is false
// if called from finishViews(), which is called from hideWindow() and onDestroy(). These
// are the situations in which we want to finish up the researchLog.
- if (ic != null && !finishingInput) {
- final boolean isTextTruncated;
- final String text;
- if (LOG_FULL_TEXTVIEW_CONTENTS) {
- // Capture the TextView contents. This will trigger onUpdateSelection(), so we
- // set sLatinIMEExpectingUpdateSelection so that when onUpdateSelection() is called,
- // it can tell that it was generated by the logging code, and not by the user, and
- // therefore keep user-visible state as is.
- ic.beginBatchEdit();
- ic.performContextMenuAction(android.R.id.selectAll);
- CharSequence charSequence = ic.getSelectedText(0);
- if (savedSelectionStart != -1 && savedSelectionEnd != -1) {
- ic.setSelection(savedSelectionStart, savedSelectionEnd);
- }
- ic.endBatchEdit();
- sLatinIMEExpectingUpdateSelection = true;
- if (TextUtils.isEmpty(charSequence)) {
- isTextTruncated = false;
- text = "";
- } else {
- if (charSequence.length() > MAX_INPUTVIEW_LENGTH_TO_CAPTURE) {
- int length = MAX_INPUTVIEW_LENGTH_TO_CAPTURE;
- // do not cut in the middle of a supplementary character
- final char c = charSequence.charAt(length - 1);
- if (Character.isHighSurrogate(c)) {
- length--;
- }
- final CharSequence truncatedCharSequence = charSequence.subSequence(0,
- length);
- isTextTruncated = true;
- text = truncatedCharSequence.toString();
- } else {
- isTextTruncated = false;
- text = charSequence.toString();
- }
- }
- } else {
- isTextTruncated = true;
- text = "";
- }
+ if (!finishingInput) {
final ResearchLogger researchLogger = getInstance();
// Assume that OUTPUT_ENTIRE_BUFFER is only true when we don't care about privacy (e.g.
// during a live user test), so the normal isPotentiallyPrivate and
// isPotentiallyRevealing flags do not apply
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL,
- isTextTruncated, text);
+ true /* isTextTruncated */, "" /* text */);
researchLogger.commitCurrentLogUnit();
getInstance().stop();
}
@@ -1213,9 +1149,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public static void latinIME_onUpdateSelection(final int lastSelectionStart,
final int lastSelectionEnd, final int oldSelStart, final int oldSelEnd,
final int newSelStart, final int newSelEnd, final int composingSpanStart,
- final int composingSpanEnd, final boolean expectingUpdateSelection,
- final boolean expectingUpdateSelectionFromLogger,
- final RichInputConnection connection) {
+ final int composingSpanEnd, final RichInputConnection connection) {
String word = "";
if (connection != null) {
TextRange range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
@@ -1227,8 +1161,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final String scrubbedWord = researchLogger.scrubWord(word);
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONUPDATESELECTION, lastSelectionStart,
lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart, newSelEnd,
- composingSpanStart, composingSpanEnd, expectingUpdateSelection,
- expectingUpdateSelectionFromLogger, scrubbedWord);
+ composingSpanStart, composingSpanEnd, false /* expectingUpdateSelection */,
+ false /* expectingUpdateSelectionFromLogger */, scrubbedWord);
}
/**
@@ -1411,8 +1345,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private static final LogStatement LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD =
new LogStatement("MainKeyboardViewSetKeyboard", false, false, "elementId", "locale",
"orientation", "width", "modeName", "action", "navigateNext",
- "navigatePrevious", "clobberSettingsKey", "passwordInput", "shortcutKeyEnabled",
- "hasShortcutKey", "languageSwitchKeyEnabled", "isMultiLine", "tw", "th",
+ "navigatePrevious", "clobberSettingsKey", "passwordInput",
+ "supportsSwitchingToShortcutIme", "hasShortcutKey", "languageSwitchKeyEnabled",
+ "isMultiLine", "tw", "th",
"keys");
public static void mainKeyboardView_setKeyboard(final Keyboard keyboard,
final int orientation) {
@@ -1425,7 +1360,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
orientation, kid.mWidth, KeyboardId.modeName(kid.mMode), kid.imeAction(),
kid.navigateNext(), kid.navigatePrevious(), kid.mClobberSettingsKey,
- isPasswordView, kid.mShortcutKeyEnabled, kid.mHasShortcutKey,
+ isPasswordView, kid.mSupportsSwitchingToShortcutIme, kid.mHasShortcutKey,
kid.mLanguageSwitchKeyEnabled, kid.isMultiLine(), keyboard.mOccupiedWidth,
keyboard.mOccupiedHeight, keyboard.getKeys());
}