aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod')
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java10
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java28
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java5
-rw-r--r--java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java10
-rw-r--r--java/src/com/android/inputmethod/compat/BuildCompatUtils.java7
-rw-r--r--java/src/com/android/inputmethod/compat/CharacterCompat.java47
-rw-r--r--java/src/com/android/inputmethod/compat/CompatUtils.java2
-rw-r--r--java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java188
-rw-r--r--java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java11
-rw-r--r--java/src/com/android/inputmethod/compat/LocaleSpanCompatUtils.java26
-rw-r--r--java/src/com/android/inputmethod/compat/NotificationCompatUtils.java6
-rw-r--r--java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java36
-rw-r--r--java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java57
-rw-r--r--java/src/com/android/inputmethod/compat/ViewCompatUtils.java7
-rw-r--r--java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtils.java43
-rw-r--r--java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java72
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ActionBatch.java21
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java25
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java12
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java43
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryService.java68
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java6
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java221
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java (renamed from java/src/com/android/inputmethod/annotations/ExternallyReferenced.java)15
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java18
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java204
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java97
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java22
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataParser.java1
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/PrivateLog.java10
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java177
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java12
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/WordListPreference.java184
-rw-r--r--java/src/com/android/inputmethod/event/CombinerChain.java45
-rw-r--r--java/src/com/android/inputmethod/event/DeadKeyCombiner.java265
-rw-r--r--java/src/com/android/inputmethod/event/Event.java33
-rw-r--r--java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java7
-rw-r--r--java/src/com/android/inputmethod/event/MyanmarReordering.java261
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java172
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java44
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java29
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayout.java124
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java156
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java114
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardTheme.java86
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java132
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java290
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java178
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityInfo.java50
-rw-r--r--java/src/com/android/inputmethod/keyboard/TextDecorator.java368
-rw-r--r--java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java262
-rw-r--r--java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java51
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java34
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java146
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java15
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java77
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java79
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java12
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java13
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java23
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java43
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java11
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java50
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java9
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java54
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java46
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java207
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java94
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java1700
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeysCache.java39
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java68
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java123
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java80
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/TimerProxy.java133
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java1
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/UniqueKeysCache.java81
-rw-r--r--java/src/com/android/inputmethod/latin/AssetFileAddress.java7
-rw-r--r--java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java1
-rw-r--r--java/src/com/android/inputmethod/latin/BackupAgent.java30
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java239
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java31
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java43
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java311
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java258
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsContentObserver.java114
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsDictionaryConstants.java48
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsDictionaryUtils.java55
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsManager.java159
-rw-r--r--java/src/com/android/inputmethod/latin/DicTraverseSession.java16
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java91
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java36
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitator.java700
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java661
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java106
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java97
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryStats.java80
-rw-r--r--java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java107
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java384
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java17
-rw-r--r--java/src/com/android/inputmethod/latin/InputPointers.java183
-rw-r--r--java/src/com/android/inputmethod/latin/InputView.java5
-rw-r--r--java/src/com/android/inputmethod/latin/LastComposedWord.java10
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java616
-rw-r--r--java/src/com/android/inputmethod/latin/NgramContext.java291
-rw-r--r--java/src/com/android/inputmethod/latin/PrevWordsInfo.java164
-rw-r--r--java/src/com/android/inputmethod/latin/PunctuationSuggestions.java32
-rw-r--r--java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java16
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java249
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java263
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java227
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java333
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java263
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java206
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java44
-rw-r--r--java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java49
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java108
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java99
-rw-r--r--java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java81
-rw-r--r--java/src/com/android/inputmethod/latin/accounts/AuthUtils.java67
-rw-r--r--java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java189
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java691
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java59
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java40
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java54
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java123
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/NgramProperty.java42
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java10
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/WordProperty.java122
-rw-r--r--java/src/com/android/inputmethod/latin/network/AuthException.java (renamed from java/src/com/android/inputmethod/annotations/UsedForTesting.java)23
-rw-r--r--java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java97
-rw-r--r--java/src/com/android/inputmethod/latin/network/HttpException.java46
-rw-r--r--java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java229
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java54
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java89
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java67
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java37
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java41
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java114
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java124
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java445
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java67
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java7
-rw-r--r--java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java23
-rw-r--r--java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java341
-rw-r--r--java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java469
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettings.java29
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java98
-rw-r--r--java/src/com/android/inputmethod/latin/settings/GestureSettingsFragment.java1
-rw-r--r--java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java61
-rw-r--r--java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java42
-rw-r--r--java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java66
-rw-r--r--java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java18
-rw-r--r--java/src/com/android/inputmethod/latin/settings/RadioButtonPreference.java10
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java109
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsActivity.java17
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java7
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java90
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java7
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java19
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java11
-rw-r--r--java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java55
-rw-r--r--java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java30
-rw-r--r--java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java85
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupActivity.java4
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java11
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java193
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java26
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java88
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java30
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java6
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java420
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java2
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java10
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java212
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java83
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java3
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java85
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java178
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java35
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java195
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java13
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java50
-rw-r--r--java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java16
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java51
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CollectionUtils.java43
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java44
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java91
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java34
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java157
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilter.java58
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java286
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java59
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java142
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FileUtils.java54
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FragmentUtils.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java14
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java187
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageOnSpacebarUtils.java92
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java11
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LocaleUtils.java190
-rw-r--r--java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java (renamed from java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java)50
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java13
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java155
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResourceUtils.java12
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ScriptUtils.java67
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java58
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java57
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java646
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java145
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SuggestionResults.java22
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java106
233 files changed, 11572 insertions, 11767 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 2762a9f25..b0072eebe 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -152,12 +152,16 @@ public final class AccessibilityUtils {
* will occur when a key is typed.
*
* @param suggestedWords the list of suggested auto-correction words
- * @param typedWord the currently typed word
*/
- public void setAutoCorrection(final SuggestedWords suggestedWords, final String typedWord) {
+ public void setAutoCorrection(final SuggestedWords suggestedWords) {
if (suggestedWords.mWillAutoCorrect) {
mAutoCorrectionWord = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
- mTypedWord = typedWord;
+ final SuggestedWords.SuggestedWordInfo typedWordInfo = suggestedWords.mTypedWordInfo;
+ if (null == typedWordInfo) {
+ mTypedWord = null;
+ } else {
+ mTypedWord = typedWordInfo.mWord;
+ }
} else {
mAutoCorrectionWord = null;
mTypedWord = null;
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 7a3510ee1..bbda9f8e2 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -26,9 +26,9 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
import java.util.Locale;
@@ -37,6 +37,8 @@ final class KeyCodeDescriptionMapper {
private static final String SPOKEN_LETTER_RESOURCE_NAME_FORMAT = "spoken_accented_letter_%04X";
private static final String SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT = "spoken_symbol_%04X";
private static final String SPOKEN_EMOJI_RESOURCE_NAME_FORMAT = "spoken_emoji_%04X";
+ private static final String SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX = "spoken_emoticon";
+ private static final String SPOKEN_EMOTICON_CODE_POINT_FORMAT = "_%02X";
// The resource ID of the string spoken for obscured keys
private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot;
@@ -109,7 +111,9 @@ final class KeyCodeDescriptionMapper {
}
if (code == Constants.CODE_OUTPUT_TEXT) {
- return key.getOutputText();
+ final String outputText = key.getOutputText();
+ final String description = getSpokenEmoticonDescription(context, outputText);
+ return TextUtils.isEmpty(description) ? outputText : description;
}
// Just attempt to speak the description.
@@ -340,4 +344,22 @@ final class KeyCodeDescriptionMapper {
}
return resId;
}
+
+ // TODO: Remove this method once TTS supports emoticon verbalization.
+ private static String getSpokenEmoticonDescription(final Context context,
+ final String outputText) {
+ final StringBuilder sb = new StringBuilder(SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX);
+ final int textLength = outputText.length();
+ for (int index = 0; index < textLength; index = outputText.offsetByCodePoints(index, 1)) {
+ final int codePoint = outputText.codePointAt(index);
+ sb.append(String.format(Locale.ROOT, SPOKEN_EMOTICON_CODE_POINT_FORMAT, codePoint));
+ }
+ final String resourceName = sb.toString();
+ final Resources resources = context.getResources();
+ // Note that the resource package name may differ from the context package name.
+ final String resourcePackageName = resources.getResourcePackageName(
+ R.string.spoken_description_unknown);
+ final int resId = resources.getIdentifier(resourceName, "string", resourcePackageName);
+ return (resId == 0) ? null : resources.getString(resId);
+ }
}
diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
index 66b0acb2f..2de71cec9 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
@@ -31,9 +31,9 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.latin.common.CoordinateUtils;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsValues;
-import com.android.inputmethod.latin.utils.CoordinateUtils;
import java.util.List;
@@ -329,9 +329,8 @@ final class KeyboardAccessibilityNodeProvider<KV extends KeyboardView>
if (currentSettings.isWordSeparator(key.getCode())) {
return mAccessibilityUtils.getAutoCorrectionDescription(
keyCodeDescription, shouldObscure);
- } else {
- return keyCodeDescription;
}
+ return keyCodeDescription;
}
/**
diff --git a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
index b84d402fb..e80982fc7 100644
--- a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
+++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
@@ -121,7 +121,7 @@ public final class MainKeyboardAccessibilityDelegate
*/
private void announceKeyboardLanguage(final Keyboard keyboard) {
final String languageText = SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(
- keyboard.mId.mSubtype);
+ keyboard.mId.mSubtype.getRawSubtype());
sendWindowStateChanged(languageText);
}
@@ -269,13 +269,9 @@ public final class MainKeyboardAccessibilityDelegate
eventTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0 /* metaState */);
// Inject a fake down event to {@link PointerTracker} to handle a long press correctly.
tracker.processMotionEvent(downEvent, mKeyDetector);
- // The above fake down event triggers an unnecessary long press timer that should be
- // canceled.
- tracker.cancelLongPressTimer();
downEvent.recycle();
- // Invoke {@link MainKeyboardView#onLongPress(PointerTracker)} as if a long press timeout
- // has passed.
- mKeyboardView.onLongPress(tracker);
+ // Invoke {@link PointerTracker#onLongPressed()} as if a long press timeout has passed.
+ tracker.onLongPressed();
// If {@link Key#hasNoPanelAutoMoreKeys()} is true (such as "0 +" key on the phone layout)
// or a key invokes IME switcher dialog, we should just ignore the next
// {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether
diff --git a/java/src/com/android/inputmethod/compat/BuildCompatUtils.java b/java/src/com/android/inputmethod/compat/BuildCompatUtils.java
index 7d1717bd1..5d56f12ae 100644
--- a/java/src/com/android/inputmethod/compat/BuildCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/BuildCompatUtils.java
@@ -33,11 +33,4 @@ public final class BuildCompatUtils {
public static final int EFFECTIVE_SDK_INT = IS_RELEASE_BUILD
? Build.VERSION.SDK_INT
: Build.VERSION.SDK_INT + 1;
-
- /**
- * API version for L-release.
- */
- // TODO: Substitute this constant reference with Build.VERSION_CODES.L* once the *next* version
- // becomes available.
- public static final int VERSION_CODES_LXX = 21;
}
diff --git a/java/src/com/android/inputmethod/compat/CharacterCompat.java b/java/src/com/android/inputmethod/compat/CharacterCompat.java
new file mode 100644
index 000000000..609fe1638
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/CharacterCompat.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import java.lang.reflect.Method;
+
+public final class CharacterCompat {
+ // Note that Character.isAlphabetic(int), has been introduced in API level 19
+ // (Build.VERSION_CODE.KITKAT).
+ private static final Method METHOD_isAlphabetic = CompatUtils.getMethod(
+ Character.class, "isAlphabetic", int.class);
+
+ private CharacterCompat() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static boolean isAlphabetic(final int code) {
+ if (METHOD_isAlphabetic != null) {
+ return (Boolean)CompatUtils.invoke(null, false, METHOD_isAlphabetic, code);
+ }
+ switch (Character.getType(code)) {
+ case Character.UPPERCASE_LETTER:
+ case Character.LOWERCASE_LETTER:
+ case Character.TITLECASE_LETTER:
+ case Character.MODIFIER_LETTER:
+ case Character.OTHER_LETTER:
+ case Character.LETTER_NUMBER:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java
index 6aa2736c1..5db80190c 100644
--- a/java/src/com/android/inputmethod/compat/CompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/CompatUtils.java
@@ -144,7 +144,7 @@ public final class CompatUtils {
public <T> ToObjectMethodWrapper<T> getMethod(final String name,
final T defaultValue, final Class<?>... parameterTypes) {
- return new ToObjectMethodWrapper<T>(CompatUtils.getMethod(mClass, name, parameterTypes),
+ return new ToObjectMethodWrapper<>(CompatUtils.getMethod(mClass, name, parameterTypes),
defaultValue);
}
diff --git a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
index 5af31795c..01a9e6712 100644
--- a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
@@ -16,13 +16,20 @@
package com.android.inputmethod.compat;
+import android.annotation.TargetApi;
import android.graphics.Matrix;
import android.graphics.RectF;
+import android.os.Build;
+import android.view.inputmethod.CursorAnchorInfo;
-import com.android.inputmethod.annotations.UsedForTesting;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
-@UsedForTesting
-public final class CursorAnchorInfoCompatWrapper {
+/**
+ * A wrapper for {@link CursorAnchorInfo}, which has been introduced in API Level 21. You can use
+ * this wrapper to avoid direct dependency on newly introduced types.
+ */
+public class CursorAnchorInfoCompatWrapper {
/**
* The insertion marker or character bounds have at least one visible region.
@@ -39,123 +46,140 @@ public final class CursorAnchorInfoCompatWrapper {
*/
public static final int FLAG_IS_RTL = 0x04;
- // Note that CursorAnchorInfo has been introduced in API level XX (Build.VERSION_CODE.LXX).
- private static final CompatUtils.ClassWrapper sCursorAnchorInfoClass;
- private static final CompatUtils.ToIntMethodWrapper sGetSelectionStartMethod;
- private static final CompatUtils.ToIntMethodWrapper sGetSelectionEndMethod;
- private static final CompatUtils.ToObjectMethodWrapper<RectF> sGetCharacterBoundsMethod;
- private static final CompatUtils.ToIntMethodWrapper sGetCharacterBoundsFlagsMethod;
- private static final CompatUtils.ToObjectMethodWrapper<CharSequence> sGetComposingTextMethod;
- private static final CompatUtils.ToIntMethodWrapper sGetComposingTextStartMethod;
- private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerBaselineMethod;
- private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerBottomMethod;
- private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerHorizontalMethod;
- private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerTopMethod;
- private static final CompatUtils.ToObjectMethodWrapper<Matrix> sGetMatrixMethod;
- private static final CompatUtils.ToIntMethodWrapper sGetInsertionMarkerFlagsMethod;
-
- private static int INVALID_TEXT_INDEX = -1;
- static {
- sCursorAnchorInfoClass = CompatUtils.getClassWrapper(
- "android.view.inputmethod.CursorAnchorInfo");
- sGetSelectionStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
- "getSelectionStart", INVALID_TEXT_INDEX);
- sGetSelectionEndMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
- "getSelectionEnd", INVALID_TEXT_INDEX);
- sGetCharacterBoundsMethod = sCursorAnchorInfoClass.getMethod(
- "getCharacterBounds", (RectF)null, int.class);
- sGetCharacterBoundsFlagsMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
- "getCharacterBoundsFlags", 0, int.class);
- sGetComposingTextMethod = sCursorAnchorInfoClass.getMethod(
- "getComposingText", (CharSequence)null);
- sGetComposingTextStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
- "getComposingTextStart", INVALID_TEXT_INDEX);
- sGetInsertionMarkerBaselineMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
- "getInsertionMarkerBaseline", 0.0f);
- sGetInsertionMarkerBottomMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
- "getInsertionMarkerBottom", 0.0f);
- sGetInsertionMarkerHorizontalMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
- "getInsertionMarkerHorizontal", 0.0f);
- sGetInsertionMarkerTopMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
- "getInsertionMarkerTop", 0.0f);
- sGetMatrixMethod = sCursorAnchorInfoClass.getMethod("getMatrix", (Matrix)null);
- sGetInsertionMarkerFlagsMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
- "getInsertionMarkerFlags", 0);
- }
-
- @UsedForTesting
- public boolean isAvailable() {
- return sCursorAnchorInfoClass.exists() && mInstance != null;
- }
-
- private Object mInstance;
-
- private CursorAnchorInfoCompatWrapper(final Object instance) {
- mInstance = instance;
+ CursorAnchorInfoCompatWrapper() {
+ // This class is not publicly instantiable.
}
- @UsedForTesting
- public static CursorAnchorInfoCompatWrapper fromObject(final Object instance) {
- if (!sCursorAnchorInfoClass.exists()) {
- return new CursorAnchorInfoCompatWrapper(null);
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Nullable
+ public static CursorAnchorInfoCompatWrapper wrap(@Nullable final CursorAnchorInfo instance) {
+ if (BuildCompatUtils.EFFECTIVE_SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return null;
}
- return new CursorAnchorInfoCompatWrapper(instance);
- }
-
- private static final class FakeHolder {
- static CursorAnchorInfoCompatWrapper sInstance = new CursorAnchorInfoCompatWrapper(null);
- }
-
- @UsedForTesting
- public static CursorAnchorInfoCompatWrapper getFake() {
- return FakeHolder.sInstance;
+ if (instance == null) {
+ return null;
+ }
+ return new RealWrapper(instance);
}
public int getSelectionStart() {
- return sGetSelectionStartMethod.invoke(mInstance);
+ throw new UnsupportedOperationException("not supported.");
}
public int getSelectionEnd() {
- return sGetSelectionEndMethod.invoke(mInstance);
+ throw new UnsupportedOperationException("not supported.");
}
public CharSequence getComposingText() {
- return sGetComposingTextMethod.invoke(mInstance);
+ throw new UnsupportedOperationException("not supported.");
}
public int getComposingTextStart() {
- return sGetComposingTextStartMethod.invoke(mInstance);
+ throw new UnsupportedOperationException("not supported.");
}
public Matrix getMatrix() {
- return sGetMatrixMethod.invoke(mInstance);
+ throw new UnsupportedOperationException("not supported.");
}
+ @SuppressWarnings("unused")
public RectF getCharacterBounds(final int index) {
- return sGetCharacterBoundsMethod.invoke(mInstance, index);
+ throw new UnsupportedOperationException("not supported.");
}
+ @SuppressWarnings("unused")
public int getCharacterBoundsFlags(final int index) {
- return sGetCharacterBoundsFlagsMethod.invoke(mInstance, index);
+ throw new UnsupportedOperationException("not supported.");
}
public float getInsertionMarkerBaseline() {
- return sGetInsertionMarkerBaselineMethod.invoke(mInstance);
+ throw new UnsupportedOperationException("not supported.");
}
public float getInsertionMarkerBottom() {
- return sGetInsertionMarkerBottomMethod.invoke(mInstance);
+ throw new UnsupportedOperationException("not supported.");
}
public float getInsertionMarkerHorizontal() {
- return sGetInsertionMarkerHorizontalMethod.invoke(mInstance);
+ throw new UnsupportedOperationException("not supported.");
}
public float getInsertionMarkerTop() {
- return sGetInsertionMarkerTopMethod.invoke(mInstance);
+ throw new UnsupportedOperationException("not supported.");
}
public int getInsertionMarkerFlags() {
- return sGetInsertionMarkerFlagsMethod.invoke(mInstance);
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static final class RealWrapper extends CursorAnchorInfoCompatWrapper {
+
+ @Nonnull
+ private final CursorAnchorInfo mInstance;
+
+ public RealWrapper(@Nonnull final CursorAnchorInfo info) {
+ mInstance = info;
+ }
+
+ @Override
+ public int getSelectionStart() {
+ return mInstance.getSelectionStart();
+ }
+
+ @Override
+ public int getSelectionEnd() {
+ return mInstance.getSelectionEnd();
+ }
+
+ @Override
+ public CharSequence getComposingText() {
+ return mInstance.getComposingText();
+ }
+
+ @Override
+ public int getComposingTextStart() {
+ return mInstance.getComposingTextStart();
+ }
+
+ @Override
+ public Matrix getMatrix() {
+ return mInstance.getMatrix();
+ }
+
+ @Override
+ public RectF getCharacterBounds(final int index) {
+ return mInstance.getCharacterBounds(index);
+ }
+
+ @Override
+ public int getCharacterBoundsFlags(final int index) {
+ return mInstance.getCharacterBoundsFlags(index);
+ }
+
+ @Override
+ public float getInsertionMarkerBaseline() {
+ return mInstance.getInsertionMarkerBaseline();
+ }
+
+ @Override
+ public float getInsertionMarkerBottom() {
+ return mInstance.getInsertionMarkerBottom();
+ }
+
+ @Override
+ public float getInsertionMarkerHorizontal() {
+ return mInstance.getInsertionMarkerHorizontal();
+ }
+
+ @Override
+ public float getInsertionMarkerTop() {
+ return mInstance.getInsertionMarkerTop();
+ }
+
+ @Override
+ public int getInsertionMarkerFlags() {
+ return mInstance.getInsertionMarkerFlags();
+ }
}
}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
index 365867257..58ad4bd4c 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
@@ -20,11 +20,14 @@ import android.os.Build;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
+import com.android.inputmethod.latin.common.Constants;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import javax.annotation.Nonnull;
+
public final class InputMethodSubtypeCompatUtils {
private static final String TAG = InputMethodSubtypeCompatUtils.class.getSimpleName();
// Note that InputMethodSubtype(int nameId, int iconId, String locale, String mode,
@@ -51,6 +54,8 @@ public final class InputMethodSubtypeCompatUtils {
// This utility class is not publicly instantiable.
}
+ @SuppressWarnings("deprecation")
+ @Nonnull
public static InputMethodSubtype newInputMethodSubtype(int nameId, int iconId, String locale,
String mode, String extraValue, boolean isAuxiliary,
boolean overridesImplicitlyEnabledSubtype, int id) {
@@ -64,6 +69,10 @@ public final class InputMethodSubtypeCompatUtils {
overridesImplicitlyEnabledSubtype, id);
}
+ public static boolean isAsciiCapable(final RichInputMethodSubtype subtype) {
+ return isAsciiCapable(subtype.getRawSubtype());
+ }
+
public static boolean isAsciiCapable(final InputMethodSubtype subtype) {
return isAsciiCapableWithAPI(subtype)
|| subtype.containsExtraValueKey(Constants.Subtype.ExtraValue.ASCII_CAPABLE);
diff --git a/java/src/com/android/inputmethod/compat/LocaleSpanCompatUtils.java b/java/src/com/android/inputmethod/compat/LocaleSpanCompatUtils.java
index f411f181b..58e5a36b6 100644
--- a/java/src/com/android/inputmethod/compat/LocaleSpanCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/LocaleSpanCompatUtils.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.compat;
import android.text.Spannable;
+import android.text.Spanned;
import android.text.style.LocaleSpan;
import android.util.Log;
@@ -127,13 +128,13 @@ public final class LocaleSpanCompatUtils {
final int spanFlag = spannable.getSpanFlags(existingLocaleSpan);
if (spanStart < newStart) {
newStart = spanStart;
- isStartExclusive = ((spanFlag & Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) ==
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ isStartExclusive = ((spanFlag & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (newEnd < spanEnd) {
newEnd = spanEnd;
- isEndExclusive = ((spanFlag & Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) ==
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ isEndExclusive = ((spanFlag & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
existingLocaleSpansToBeMerged.add(existingLocaleSpan);
}
@@ -201,24 +202,17 @@ public final class LocaleSpanCompatUtils {
private static int getSpanFlag(final int originalFlag,
final boolean isStartExclusive, final boolean isEndExclusive) {
- return (originalFlag & ~Spannable.SPAN_POINT_MARK_MASK) |
+ return (originalFlag & ~Spanned.SPAN_POINT_MARK_MASK) |
getSpanPointMarkFlag(isStartExclusive, isEndExclusive);
}
private static int getSpanPointMarkFlag(final boolean isStartExclusive,
final boolean isEndExclusive) {
if (isStartExclusive) {
- if (isEndExclusive) {
- return Spannable.SPAN_EXCLUSIVE_EXCLUSIVE;
- } else {
- return Spannable.SPAN_EXCLUSIVE_INCLUSIVE;
- }
- } else {
- if (isEndExclusive) {
- return Spannable.SPAN_INCLUSIVE_EXCLUSIVE;
- } else {
- return Spannable.SPAN_INCLUSIVE_INCLUSIVE;
- }
+ return isEndExclusive ? Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+ : Spanned.SPAN_EXCLUSIVE_INCLUSIVE;
}
+ return isEndExclusive ? Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+ : Spanned.SPAN_INCLUSIVE_INCLUSIVE;
}
}
diff --git a/java/src/com/android/inputmethod/compat/NotificationCompatUtils.java b/java/src/com/android/inputmethod/compat/NotificationCompatUtils.java
index eb180071e..70ab972c5 100644
--- a/java/src/com/android/inputmethod/compat/NotificationCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/NotificationCompatUtils.java
@@ -71,13 +71,13 @@ public class NotificationCompatUtils {
CompatUtils.invoke(builder, null, METHOD_setPriority, PRIORITY_LOW);
}
+ @SuppressWarnings("deprecation")
public static Notification build(final Notification.Builder builder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// #build was added in API level 16, JELLY_BEAN
return (Notification) CompatUtils.invoke(builder, null, METHOD_build);
- } else {
- // #getNotification was deprecated in API level 16, JELLY_BEAN
- return builder.getNotification();
}
+ // #getNotification was deprecated in API level 16, JELLY_BEAN
+ return builder.getNotification();
}
}
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index c33c01552..4d2925d30 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -23,13 +23,17 @@ import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.common.LocaleUtils;
import com.android.inputmethod.latin.define.DebugFlags;
-import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.Locale;
+
+import javax.annotation.Nullable;
public final class SuggestionSpanUtils {
// Note that SuggestionSpan.FLAG_AUTO_CORRECTION has been introduced
@@ -51,20 +55,22 @@ public final class SuggestionSpanUtils {
// This utility class is not publicly instantiable.
}
+ @UsedForTesting
public static CharSequence getTextWithAutoCorrectionIndicatorUnderline(
final Context context, final String text) {
if (TextUtils.isEmpty(text) || OBJ_FLAG_AUTO_CORRECTION == null) {
return text;
}
final Spannable spannable = new SpannableString(text);
+ // TODO: Set locale if it is feasible.
final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null /* locale */,
- new String[] {} /* suggestions */, OBJ_FLAG_AUTO_CORRECTION,
- SuggestionSpanPickedNotificationReceiver.class);
+ new String[] {} /* suggestions */, OBJ_FLAG_AUTO_CORRECTION, null);
spannable.setSpan(suggestionSpan, 0, text.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
return spannable;
}
+ @UsedForTesting
public static CharSequence getTextWithSuggestionSpan(final Context context,
final String pickedWord, final SuggestedWords suggestedWords) {
if (TextUtils.isEmpty(pickedWord) || suggestedWords.isEmpty()
@@ -86,11 +92,31 @@ public final class SuggestionSpanUtils {
suggestionsList.add(word.toString());
}
}
+ // TODO: Set locale if it is feasible.
final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null /* locale */,
- suggestionsList.toArray(new String[suggestionsList.size()]), 0 /* flags */,
- SuggestionSpanPickedNotificationReceiver.class);
+ suggestionsList.toArray(new String[suggestionsList.size()]), 0 /* flags */, null);
final Spannable spannable = new SpannableString(pickedWord);
spannable.setSpan(suggestionSpan, 0, pickedWord.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
}
+
+ /**
+ * Returns first {@link Locale} found in the given array of {@link SuggestionSpan}.
+ * @param suggestionSpans the array of {@link SuggestionSpan} to be examined.
+ * @return the first {@link Locale} found in {@code suggestionSpans}. {@code null} when not
+ * found.
+ */
+ @UsedForTesting
+ @Nullable
+ public static Locale findFirstLocaleFromSuggestionSpans(
+ final SuggestionSpan[] suggestionSpans) {
+ for (final SuggestionSpan suggestionSpan : suggestionSpans) {
+ final String localeString = suggestionSpan.getLocale();
+ if (TextUtils.isEmpty(localeString)) {
+ continue;
+ }
+ return LocaleUtils.constructLocaleFromString(localeString);
+ }
+ return null;
+ }
}
diff --git a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
deleted file mode 100644
index 1fb597ba6..000000000
--- a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
+++ /dev/null
@@ -1,57 +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.compat;
-
-import android.content.Context;
-import android.provider.UserDictionary.Words;
-
-import java.lang.reflect.Method;
-import java.util.Locale;
-
-public final class UserDictionaryCompatUtils {
- // UserDictionary.Words#addWord(Context, String, int, String, Locale) was introduced
- // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
- private static final Method METHOD_addWord = CompatUtils.getMethod(Words.class, "addWord",
- Context.class, String.class, int.class, String.class, Locale.class);
-
- @SuppressWarnings("deprecation")
- public static void addWord(final Context context, final String word,
- final int freq, final String shortcut, final Locale locale) {
- if (hasNewerAddWord()) {
- CompatUtils.invoke(Words.class, null, METHOD_addWord, context, word, freq, shortcut,
- locale);
- } else {
- // Fall back to the pre-JellyBean method.
- final int localeType;
- if (null == locale) {
- localeType = Words.LOCALE_TYPE_ALL;
- } else {
- final Locale currentLocale = context.getResources().getConfiguration().locale;
- if (locale.equals(currentLocale)) {
- localeType = Words.LOCALE_TYPE_CURRENT;
- } else {
- localeType = Words.LOCALE_TYPE_ALL;
- }
- }
- Words.addWord(context, word, freq, localeType);
- }
- }
-
- public static final boolean hasNewerAddWord() {
- return null != METHOD_addWord;
- }
-}
diff --git a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
index 0f00be133..16260ab6a 100644
--- a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
@@ -31,9 +31,6 @@ public final class ViewCompatUtils {
private static final Method METHOD_setPaddingRelative = CompatUtils.getMethod(
View.class, "setPaddingRelative",
int.class, int.class, int.class, int.class);
- // Note that View.setElevation(float) has been introduced in API level 21.
- private static final Method METHOD_setElevation = CompatUtils.getMethod(
- View.class, "setElevation", float.class);
// Note that View.setTextAlignment(int) has been introduced in API level 17.
private static final Method METHOD_setTextAlignment = CompatUtils.getMethod(
View.class, "setTextAlignment", int.class);
@@ -58,10 +55,6 @@ public final class ViewCompatUtils {
CompatUtils.invoke(view, null, METHOD_setPaddingRelative, start, top, end, bottom);
}
- public static void setElevation(final View view, final float elevation) {
- CompatUtils.invoke(view, null, METHOD_setElevation, elevation);
- }
-
// These TEXT_ALIGNMENT_* constants have been introduced in API 17.
public static final int TEXT_ALIGNMENT_INHERIT = 0;
public static final int TEXT_ALIGNMENT_GRAVITY = 1;
diff --git a/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtils.java
new file mode 100644
index 000000000..0c8e5b77d
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.inputmethodservice.InputMethodService;
+import android.os.Build;
+import android.view.View;
+
+public class ViewOutlineProviderCompatUtils {
+ private ViewOutlineProviderCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public interface InsetsUpdater {
+ public void setInsets(final InputMethodService.Insets insets);
+ }
+
+ private static final InsetsUpdater EMPTY_INSETS_UPDATER = new InsetsUpdater() {
+ @Override
+ public void setInsets(final InputMethodService.Insets insets) {}
+ };
+
+ public static InsetsUpdater setInsetsOutlineProvider(final View view) {
+ if (BuildCompatUtils.EFFECTIVE_SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return EMPTY_INSETS_UPDATER;
+ }
+ return ViewOutlineProviderCompatUtilsLXX.setInsetsOutlineProvider(view);
+ }
+}
diff --git a/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java b/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java
new file mode 100644
index 000000000..5bbb5ce99
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.annotation.TargetApi;
+import android.graphics.Outline;
+import android.inputmethodservice.InputMethodService;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater;
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+class ViewOutlineProviderCompatUtilsLXX {
+ private ViewOutlineProviderCompatUtilsLXX() {
+ // This utility class is not publicly instantiable.
+ }
+
+ static InsetsUpdater setInsetsOutlineProvider(final View view) {
+ final InsetsOutlineProvider provider = new InsetsOutlineProvider(view);
+ view.setOutlineProvider(provider);
+ return provider;
+ }
+
+ private static class InsetsOutlineProvider extends ViewOutlineProvider
+ implements InsetsUpdater {
+ private final View mView;
+ private static final int NO_DATA = -1;
+ private int mLastVisibleTopInsets = NO_DATA;
+
+ public InsetsOutlineProvider(final View view) {
+ mView = view;
+ view.setOutlineProvider(this);
+ }
+
+ @Override
+ public void setInsets(final InputMethodService.Insets insets) {
+ final int visibleTopInsets = insets.visibleTopInsets;
+ if (mLastVisibleTopInsets != visibleTopInsets) {
+ mLastVisibleTopInsets = visibleTopInsets;
+ mView.invalidateOutline();
+ }
+ }
+
+ @Override
+ public void getOutline(final View view, final Outline outline) {
+ if (mLastVisibleTopInsets == NO_DATA) {
+ // Call default implementation.
+ ViewOutlineProvider.BACKGROUND.getOutline(view, outline);
+ return;
+ }
+ // TODO: Revisit this when floating/resize keyboard is supported.
+ outline.setRect(
+ view.getLeft(), mLastVisibleTopInsets, view.getRight(), view.getBottom());
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index 3d294acd7..ee142d845 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -120,9 +120,10 @@ public final class ActionBatch {
if (MetadataDbHelper.STATUS_DOWNLOADING == status) {
// The word list is still downloading. Cancel the download and revert the
// word list status to "available".
- manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
+ manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
- } else if (MetadataDbHelper.STATUS_AVAILABLE != status) {
+ } else if (MetadataDbHelper.STATUS_AVAILABLE != status
+ && MetadataDbHelper.STATUS_RETRYING != status) {
// Should never happen
Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' : " + status
+ " for an upgrade action. Fall back to download.");
@@ -171,6 +172,8 @@ public final class ActionBatch {
final long downloadId = UpdateHandler.registerDownloadRequest(manager, request, db,
mWordList.mId, mWordList.mVersion);
+ Log.i(TAG, String.format("Starting the dictionary download with version:"
+ + " %d and Url: %s", mWordList.mVersion, uri));
DebugLogUtils.l("Starting download of", uri, "with id", downloadId);
PrivateLog.log("Starting download of " + uri + ", id : " + downloadId);
}
@@ -325,8 +328,8 @@ public final class ActionBatch {
mWordList.mId, mWordList.mLocale, mWordList.mDescription,
null == mWordList.mLocalFilename ? "" : mWordList.mLocalFilename,
mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
- mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
- mWordList.mFormatVersion);
+ mWordList.mChecksum, mWordList.mRetryCount, mWordList.mFileSize,
+ mWordList.mVersion, mWordList.mFormatVersion);
PrivateLog.log("Insert 'available' record for " + mWordList.mDescription
+ " and locale " + mWordList.mLocale);
db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values);
@@ -374,9 +377,9 @@ public final class ActionBatch {
final ContentValues values = MetadataDbHelper.makeContentValues(0,
MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_INSTALLED,
mWordList.mId, mWordList.mLocale, mWordList.mDescription,
- "", mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
- mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
- mWordList.mFormatVersion);
+ "", mWordList.mRemoteFilename, mWordList.mLastUpdate,
+ mWordList.mRawChecksum, mWordList.mChecksum, mWordList.mRetryCount,
+ mWordList.mFileSize, mWordList.mVersion, mWordList.mFormatVersion);
PrivateLog.log("Insert 'preinstalled' record for " + mWordList.mDescription
+ " and locale " + mWordList.mLocale);
db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values);
@@ -417,8 +420,8 @@ public final class ActionBatch {
mWordList.mId, mWordList.mLocale, mWordList.mDescription,
oldValues.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN),
mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
- mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
- mWordList.mFormatVersion);
+ mWordList.mChecksum, mWordList.mRetryCount, mWordList.mFileSize,
+ mWordList.mVersion, mWordList.mFormatVersion);
PrivateLog.log("Updating record for " + mWordList.mDescription
+ " and locale " + mWordList.mLocale);
db.update(MetadataDbHelper.METADATA_TABLE_NAME, values,
diff --git a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
index 6d6c8f5c6..0fa72c3fd 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
@@ -122,19 +122,23 @@ public class ButtonSwitcher extends FrameLayout {
mDeleteButton.setTranslationX(STATUS_DELETE == status ? 0 : width);
}
+ // The helper method for {@link AnimatorListenerAdapter}.
+ void animateButtonIfStatusIsEqual(final View newButton, final int newStatus) {
+ if (newStatus != mStatus) return;
+ animateButton(newButton, ANIMATION_IN);
+ }
+
private void animateButtonPosition(final int oldStatus, final int newStatus) {
final View oldButton = getButton(oldStatus);
final View newButton = getButton(newStatus);
if (null != oldButton && null != newButton) {
// Transition between two buttons : animate out, then in
- animateButton(oldButton, ANIMATION_OUT).setListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- if (newStatus != mStatus) return;
- animateButton(newButton, ANIMATION_IN);
- }
- });
+ animateButton(oldButton, ANIMATION_OUT).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(final Animator animation) {
+ animateButtonIfStatusIsEqual(newButton, newStatus);
+ }
+ });
} else if (null != oldButton) {
animateButton(oldButton, ANIMATION_OUT);
} else if (null != newButton) {
@@ -159,9 +163,8 @@ public class ButtonSwitcher extends FrameLayout {
if (ANIMATION_IN == direction) {
button.setClickable(true);
return button.animate().translationX(0);
- } else {
- button.setClickable(false);
- return button.animate().translationX(outerX - innerX);
}
+ button.setClickable(false);
+ return button.animate().translationX(outerX - innerX);
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java b/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
index 3d0e29ed0..3cd822a3c 100644
--- a/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
+++ b/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
@@ -22,6 +22,8 @@ import android.content.SharedPreferences;
public final class CommonPreferences {
private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs";
+ public static final String PREF_FORCE_DOWNLOAD_DICT = "pref_key_force_download_dict";
+
public static SharedPreferences getCommonPreferences(final Context context) {
return context.getSharedPreferences(COMMON_PREFERENCES_NAME, 0);
}
@@ -37,4 +39,14 @@ public final class CommonPreferences {
editor.putBoolean(id, false);
editor.apply();
}
+
+ public static boolean isForceDownloadDict(Context context) {
+ return getCommonPreferences(context).getBoolean(PREF_FORCE_DOWNLOAD_DICT, false);
+ }
+
+ public static void setForceDownloadDict(Context context, boolean forceDownload) {
+ SharedPreferences.Editor editor = getCommonPreferences(context).edit();
+ editor.putBoolean(PREF_FORCE_DOWNLOAD_DICT, forceDownload);
+ editor.apply();
+ }
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
index 1d84e5888..759852025 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
@@ -148,7 +148,7 @@ public class DictionaryDownloadProgressBar extends ProgressBar {
}
}
- private class UpdateHelper implements Runnable {
+ class UpdateHelper implements Runnable {
private int mProgress;
@Override
public void run() {
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
index 8e026171d..836340a75 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
@@ -32,7 +32,7 @@ import java.util.HashMap;
* in case some dictionaries appeared, disappeared, changed states etc.
*/
public class DictionaryListInterfaceState {
- private static class State {
+ static class State {
public boolean mOpen = false;
public int mStatus = MetadataDbHelper.STATUS_UNKNOWN;
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
index f5bd84c8c..659fe5c51 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
@@ -31,6 +31,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.LocaleUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
import java.io.File;
@@ -255,10 +256,9 @@ public final class DictionaryProvider extends ContentProvider {
if (null != dictFiles && dictFiles.size() > 0) {
PrivateLog.log("Returned " + dictFiles.size() + " files");
return new ResourcePathCursor(dictFiles);
- } else {
- PrivateLog.log("No dictionary files for this URL");
- return new ResourcePathCursor(Collections.<WordListInfo>emptyList());
}
+ PrivateLog.log("No dictionary files for this URL");
+ return new ResourcePathCursor(Collections.<WordListInfo>emptyList());
// V2_METADATA and V2_DATAFILE are not supported for query()
default:
return null;
@@ -319,14 +319,13 @@ public final class DictionaryProvider extends ContentProvider {
final AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(
R.raw.empty);
return afd;
- } else {
- final String localFilename =
- wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
- final File f = getContext().getFileStreamPath(localFilename);
- final ParcelFileDescriptor pfd =
- ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
- return new AssetFileDescriptor(pfd, 0, pfd.getStatSize());
}
+ final String localFilename =
+ wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
+ final File f = getContext().getFileStreamPath(localFilename);
+ final ParcelFileDescriptor pfd =
+ ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+ return new AssetFileDescriptor(pfd, 0, pfd.getStatSize());
} catch (FileNotFoundException e) {
// No file : fall through and return null
}
@@ -461,30 +460,32 @@ public final class DictionaryProvider extends ContentProvider {
final String wordlistId = uri.getLastPathSegment();
final String clientId = getClientId(uri);
final ContentValues wordList = getWordlistMetadataForWordlistId(clientId, wordlistId);
- if (null == wordList) return 0;
+ if (null == wordList) {
+ return 0;
+ }
final int status = wordList.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
final int version = wordList.getAsInteger(MetadataDbHelper.VERSION_COLUMN);
if (MetadataDbHelper.STATUS_DELETING == status) {
UpdateHandler.markAsDeleted(getContext(), clientId, wordlistId, version, status);
return 1;
- } else if (MetadataDbHelper.STATUS_INSTALLED == status) {
+ }
+ if (MetadataDbHelper.STATUS_INSTALLED == status) {
final String result = uri.getQueryParameter(QUERY_PARAMETER_DELETE_RESULT);
if (QUERY_PARAMETER_FAILURE.equals(result)) {
- UpdateHandler.markAsBroken(getContext(), clientId, wordlistId, version);
+ if (DEBUG) {
+ Log.d(TAG,
+ "Dictionary is broken, attempting to retry download & installation.");
+ }
+ UpdateHandler.markAsBrokenOrRetrying(getContext(), clientId, wordlistId, version);
}
final String localFilename =
wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
final File f = getContext().getFileStreamPath(localFilename);
// f.delete() returns true if the file was successfully deleted, false otherwise
- if (f.delete()) {
- return 1;
- } else {
- return 0;
- }
- } else {
- Log.e(TAG, "Attempt to delete a file whose status is " + status);
- return 0;
+ return f.delete() ? 1 : 0;
}
+ Log.e(TAG, "Attempt to delete a file whose status is " + status);
+ return 0;
}
/**
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index 41916b614..bbdf2a380 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -22,9 +22,12 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
+import android.util.Log;
import android.widget.Toast;
+import com.android.inputmethod.latin.BinaryDictionaryFileDumper;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.LocaleUtils;
import java.util.Locale;
import java.util.Random;
@@ -32,6 +35,8 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import javax.annotation.Nonnull;
+
/**
* Service that handles background tasks for the dictionary provider.
*
@@ -50,6 +55,8 @@ import java.util.concurrent.TimeUnit;
* to access, and mark the current state as such.
*/
public final class DictionaryService extends Service {
+ private static final String TAG = DictionaryService.class.getSimpleName();
+
/**
* The package name, to use in the intent actions.
*/
@@ -76,19 +83,29 @@ public final class DictionaryService extends Service {
* How often, in milliseconds, we want to update the metadata. This is a
* floor value; actually, it may happen several hours later, or even more.
*/
- private static final long UPDATE_FREQUENCY = TimeUnit.DAYS.toMillis(4);
+ private static final long UPDATE_FREQUENCY_MILLIS = TimeUnit.DAYS.toMillis(4);
/**
* We are waked around midnight, local time. We want to wake between midnight and 6 am,
* roughly. So use a random time between 0 and this delay.
*/
- private static final int MAX_ALARM_DELAY = (int)TimeUnit.HOURS.toMillis(6);
+ private static final int MAX_ALARM_DELAY_MILLIS = (int)TimeUnit.HOURS.toMillis(6);
/**
* How long we consider a "very long time". If no update took place in this time,
* the content provider will trigger an update in the background.
*/
- private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14);
+ private static final long VERY_LONG_TIME_MILLIS = TimeUnit.DAYS.toMillis(14);
+
+ /**
+ * After starting a download, how long we wait before considering it may be stuck. After this
+ * period is elapsed, if the keyboard tries to download again, then we cancel and re-register
+ * the request; if it's within this time, we just leave it be.
+ * It's important to note that we do not re-submit the request merely because the time is up.
+ * This is only to decide whether to cancel the old one and re-requesting when the keyboard
+ * fires a new request for the same data.
+ */
+ public static final long NO_CANCEL_DOWNLOAD_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(30);
/**
* An executor that serializes tasks given to it.
@@ -145,9 +162,14 @@ public final class DictionaryService extends Service {
final int startId) {
final DictionaryService self = this;
if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) {
- // This is a UI action, it can't be run in another thread
- showStartDownloadingToast(this, LocaleUtils.constructLocaleFromString(
- intent.getStringExtra(LOCALE_INTENT_ARGUMENT)));
+ final String localeString = intent.getStringExtra(LOCALE_INTENT_ARGUMENT);
+ if (localeString == null) {
+ Log.e(TAG, "Received " + intent.getAction() + " without locale; skipped");
+ } else {
+ // This is a UI action, it can't be run in another thread
+ showStartDownloadingToast(
+ this, LocaleUtils.constructLocaleFromString(localeString));
+ }
} else {
// If it's a command that does not require UI, arrange for the work to be done on a
// separate thread, so that we can return right away. The executor will spawn a thread
@@ -169,15 +191,28 @@ public final class DictionaryService extends Service {
return Service.START_REDELIVER_INTENT;
}
- private static void dispatchBroadcast(final Context context, final Intent intent) {
+ static void dispatchBroadcast(final Context context, final Intent intent) {
if (DATE_CHANGED_INTENT_ACTION.equals(intent.getAction())) {
+ // Do not force download dictionaries on date change updates.
+ CommonPreferences.setForceDownloadDict(context, false);
// This happens when the date of the device changes. This normally happens
// at midnight local time, but it may happen if the user changes the date
// by hand or something similar happens.
checkTimeAndMaybeSetupUpdateAlarm(context);
} else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) {
// Intent to trigger an update now.
- UpdateHandler.tryUpdate(context, false);
+ UpdateHandler.tryUpdate(context, CommonPreferences.isForceDownloadDict(context));
+ } else if (DictionaryPackConstants.INIT_AND_UPDATE_NOW_INTENT_ACTION.equals(
+ intent.getAction())) {
+ // Enable force download of dictionaries irrespective of wifi or metered connection.
+ CommonPreferences.setForceDownloadDict(context, true);
+
+ // Initialize the client Db.
+ final String mClientId = context.getString(R.string.dictionary_pack_client_id);
+ BinaryDictionaryFileDumper.initializeClientRecordHelper(context, mClientId);
+
+ // Updates the metadata and the download the dictionaries.
+ UpdateHandler.tryUpdate(context, true);
} else {
UpdateHandler.downloadFinished(context, intent);
}
@@ -188,16 +223,16 @@ public final class DictionaryService extends Service {
*/
private static void checkTimeAndMaybeSetupUpdateAlarm(final Context context) {
// Of all clients, if the one that hasn't been updated for the longest
- // is still more recent than UPDATE_FREQUENCY, do nothing.
- if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY)) return;
+ // is still more recent than UPDATE_FREQUENCY_MILLIS, do nothing.
+ if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY_MILLIS)) return;
PrivateLog.log("Date changed - registering alarm");
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
- // Best effort to wake between midnight and MAX_ALARM_DELAY in the morning.
+ // Best effort to wake between midnight and MAX_ALARM_DELAY_MILLIS in the morning.
// It doesn't matter too much if this is very inexact.
final long now = System.currentTimeMillis();
- final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY);
+ final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY_MILLIS);
final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
updateIntent, PendingIntent.FLAG_CANCEL_CURRENT);
@@ -223,18 +258,19 @@ public final class DictionaryService extends Service {
/**
* Refreshes data if it hasn't been refreshed in a very long time.
*
- * This will check the last update time, and if it's been more than VERY_LONG_TIME,
+ * This will check the last update time, and if it's been more than VERY_LONG_TIME_MILLIS,
* update metadata now - and possibly take subsequent update actions.
*/
public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) {
- if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return;
- UpdateHandler.tryUpdate(context, false);
+ if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME_MILLIS)) return;
+ UpdateHandler.tryUpdate(context, CommonPreferences.isForceDownloadDict(context));
}
/**
* Shows a toast informing the user that an automatic dictionary download is starting.
*/
- private static void showStartDownloadingToast(final Context context, final Locale locale) {
+ private static void showStartDownloadingToast(final Context context,
+ @Nonnull final Locale locale) {
final String toastText = String.format(
context.getString(R.string.toast_downloading_suggestions),
locale.getDisplayName());
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java
index 4366348d5..284032beb 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java
@@ -18,7 +18,9 @@ package com.android.inputmethod.dictionarypack;
import com.android.inputmethod.latin.utils.FragmentUtils;
+import android.annotation.TargetApi;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceActivity;
@@ -44,8 +46,8 @@ public final class DictionarySettingsActivity extends PreferenceActivity {
return modIntent;
}
- // TODO: Uncomment the override annotation once we start using SDK version 19.
- // @Override
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ @Override
public boolean isValidFragment(String fragmentName) {
return FragmentUtils.isValidFragment(fragmentName);
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index 11982fa65..88ea4e6c3 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.dictionarypack;
+import com.android.inputmethod.latin.common.LocaleUtils;
+
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -26,12 +28,12 @@ import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.text.TextUtils;
-import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -105,16 +107,27 @@ public final class DictionarySettingsFragment extends PreferenceFragment
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- final String metadataUri =
- MetadataDbHelper.getMetadataUriAsString(getActivity(), mClientId);
- // We only add the "Refresh" button if we have a non-empty URL to refresh from. If the
- // URL is empty, of course we can't refresh so it makes no sense to display this.
- if (!TextUtils.isEmpty(metadataUri)) {
- mUpdateNowMenu =
- menu.add(Menu.NONE, MENU_UPDATE_NOW, 0, R.string.check_for_updates_now);
- mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- refreshNetworkState();
- }
+ new AsyncTask<Void, Void, String>() {
+ @Override
+ protected String doInBackground(Void... params) {
+ return MetadataDbHelper.getMetadataUriAsString(getActivity(), mClientId);
+ }
+
+ @Override
+ protected void onPostExecute(String metadataUri) {
+ // We only add the "Refresh" button if we have a non-empty URL to refresh from. If
+ // the URL is empty, of course we can't refresh so it makes no sense to display
+ // this.
+ if (!TextUtils.isEmpty(metadataUri)) {
+ if (mUpdateNowMenu == null) {
+ mUpdateNowMenu = menu.add(Menu.NONE, MENU_UPDATE_NOW, 0,
+ R.string.check_for_updates_now);
+ mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ }
+ refreshNetworkState();
+ }
+ }
+ }.execute();
}
@Override
@@ -123,18 +136,25 @@ public final class DictionarySettingsFragment extends PreferenceFragment
mChangedSettings = false;
UpdateHandler.registerUpdateEventListener(this);
final Activity activity = getActivity();
- if (!MetadataDbHelper.isClientKnown(activity, mClientId)) {
- Log.i(TAG, "Unknown dictionary pack client: " + mClientId + ". Requesting info.");
- final Intent unknownClientBroadcast =
- new Intent(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT);
- unknownClientBroadcast.putExtra(
- DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA, mClientId);
- activity.sendBroadcast(unknownClientBroadcast);
- }
final IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
getActivity().registerReceiver(mConnectivityChangedReceiver, filter);
refreshNetworkState();
+
+ new Thread("onResume") {
+ @Override
+ public void run() {
+ if (!MetadataDbHelper.isClientKnown(activity, mClientId)) {
+ Log.i(TAG, "Unknown dictionary pack client: " + mClientId
+ + ". Requesting info.");
+ final Intent unknownClientBroadcast =
+ new Intent(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT);
+ unknownClientBroadcast.putExtra(
+ DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA, mClientId);
+ activity.sendBroadcast(unknownClientBroadcast);
+ }
+ }
+ }.start();
}
@Override
@@ -203,25 +223,19 @@ public final class DictionarySettingsFragment extends PreferenceFragment
@Override
public void updateCycleCompleted() {}
- private void refreshNetworkState() {
+ void refreshNetworkState() {
NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
boolean isConnected = null == info ? false : info.isConnected();
if (null != mUpdateNowMenu) mUpdateNowMenu.setEnabled(isConnected);
}
- private void refreshInterface() {
+ void refreshInterface() {
final Activity activity = getActivity();
if (null == activity) return;
- final long lastUpdateDate =
- MetadataDbHelper.getLastUpdateDateForClient(getActivity(), mClientId);
final PreferenceGroup prefScreen = getPreferenceScreen();
final Collection<? extends Preference> prefList =
createInstalledDictSettingsCollection(mClientId);
- final String updateNowSummary = getString(R.string.last_update) + " "
- + DateUtils.formatDateTime(activity, lastUpdateDate,
- DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
-
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -239,14 +253,14 @@ public final class DictionarySettingsFragment extends PreferenceFragment
});
}
- private Preference createErrorMessage(final Activity activity, final int messageResource) {
+ private static Preference createErrorMessage(final Activity activity, final int messageResource) {
final Preference message = new Preference(activity);
message.setTitle(messageResource);
message.setEnabled(false);
return message;
}
- private void removeAnyDictSettings(final PreferenceGroup prefGroup) {
+ static void removeAnyDictSettings(final PreferenceGroup prefGroup) {
for (int i = prefGroup.getPreferenceCount() - 1; i >= 0; --i) {
prefGroup.removePreference(prefGroup.getPreference(i));
}
@@ -276,7 +290,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment
.appendQueryParameter(DictionaryProvider.QUERY_PARAMETER_PROTOCOL_VERSION, "2")
.build();
final Activity activity = getActivity();
- final Cursor cursor = null == activity ? null
+ final Cursor cursor = (null == activity) ? null
: activity.getContentResolver().query(contentUri, null, null, null, null);
if (null == cursor) {
@@ -289,61 +303,57 @@ public final class DictionarySettingsFragment extends PreferenceFragment
final ArrayList<Preference> result = new ArrayList<>();
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<>();
- 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);
- }
- } while (cursor.moveToNext());
- mCurrentPreferenceMap = prefMap;
- return prefMap.values();
}
+ final String systemLocaleString = Locale.getDefault().toString();
+ final TreeMap<String, WordListPreference> prefMap = new TreeMap<>();
+ 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);
+ }
+ } while (cursor.moveToNext());
+ mCurrentPreferenceMap = prefMap;
+ return prefMap.values();
} finally {
cursor.close();
}
@@ -384,8 +394,13 @@ public final class DictionarySettingsFragment extends PreferenceFragment
private void cancelRefresh() {
UpdateHandler.unregisterUpdateEventListener(this);
final Context context = getActivity();
- UpdateHandler.cancelUpdate(context, mClientId);
- stopLoadingAnimation();
+ new Thread("cancelByHand") {
+ @Override
+ public void run() {
+ UpdateHandler.cancelUpdate(context, mClientId);
+ stopLoadingAnimation();
+ }
+ }.start();
}
private void startLoadingAnimation() {
@@ -396,26 +411,28 @@ public final class DictionarySettingsFragment extends PreferenceFragment
if (null != mUpdateNowMenu) mUpdateNowMenu.setTitle(R.string.cancel);
}
- private void stopLoadingAnimation() {
+ void stopLoadingAnimation() {
final View preferenceView = getView();
final Activity activity = getActivity();
if (null == activity) return;
+ final View loadingView = mLoadingView;
+ final MenuItem updateNowMenu = mUpdateNowMenu;
activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mLoadingView.setVisibility(View.GONE);
- preferenceView.setVisibility(View.VISIBLE);
- mLoadingView.startAnimation(AnimationUtils.loadAnimation(
- getActivity(), android.R.anim.fade_out));
- preferenceView.startAnimation(AnimationUtils.loadAnimation(
- getActivity(), android.R.anim.fade_in));
- // The menu is created by the framework asynchronously after the activity,
- // which means it's possible to have the activity running but the menu not
- // created yet - hence the necessity for a null check here.
- if (null != mUpdateNowMenu) {
- mUpdateNowMenu.setTitle(R.string.check_for_updates_now);
- }
+ @Override
+ public void run() {
+ loadingView.setVisibility(View.GONE);
+ preferenceView.setVisibility(View.VISIBLE);
+ loadingView.startAnimation(AnimationUtils.loadAnimation(
+ activity, android.R.anim.fade_out));
+ preferenceView.startAnimation(AnimationUtils.loadAnimation(
+ activity, android.R.anim.fade_in));
+ // The menu is created by the framework asynchronously after the activity,
+ // which means it's possible to have the activity running but the menu not
+ // created yet - hence the necessity for a null check here.
+ if (null != updateNowMenu) {
+ updateNowMenu.setTitle(R.string.check_for_updates_now);
}
- });
+ }
+ });
}
}
diff --git a/java/src/com/android/inputmethod/annotations/ExternallyReferenced.java b/java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java
index ea5f12ce2..6247a15e2 100644
--- a/java/src/com/android/inputmethod/annotations/ExternallyReferenced.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.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,11 +14,16 @@
* limitations under the License.
*/
-package com.android.inputmethod.annotations;
+package com.android.inputmethod.dictionarypack;
/**
- * Denotes that the class, method or field should not be eliminated by ProGuard,
- * because it is externally referenced. (See proguard.flags)
+ * A simple container of download ID and download start date.
*/
-public @interface ExternallyReferenced {
+public class DownloadIdAndStartDate {
+ public final long mId;
+ public final long mStartDate;
+ public DownloadIdAndStartDate(final long id, final long startDate) {
+ mId = id;
+ mStartDate = startDate;
+ }
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java b/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
index d3c0a910f..91ed673ae 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
@@ -24,9 +24,11 @@ import android.view.View;
import android.widget.Button;
import android.widget.TextView;
+import com.android.inputmethod.annotations.ExternallyReferenced;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.LocaleUtils;
-import java.util.Locale;
+import javax.annotation.Nullable;
/**
* This implements the dialog for asking the user whether it's okay to download dictionaries over
@@ -52,22 +54,30 @@ public final class DownloadOverMeteredDialog extends Activity {
setTexts(localeString, size);
}
- private void setTexts(final String localeString, final long size) {
+ private void setTexts(@Nullable final String localeString, final long size) {
final String promptFormat = getString(R.string.should_download_over_metered_prompt);
final String allowButtonFormat = getString(R.string.download_over_metered);
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
- final String language = (null == locale ? "" : locale.getDisplayLanguage());
+ final String language = (null == localeString) ? ""
+ : LocaleUtils.constructLocaleFromString(localeString).getDisplayLanguage();
final TextView prompt = (TextView)findViewById(R.id.download_over_metered_prompt);
prompt.setText(Html.fromHtml(String.format(promptFormat, language)));
final Button allowButton = (Button)findViewById(R.id.allow_button);
allowButton.setText(String.format(allowButtonFormat, ((float)size)/(1024*1024)));
}
+ // This method is externally referenced from layout/download_over_metered.xml using onClick
+ // attribute of Button.
+ @ExternallyReferenced
+ @SuppressWarnings("unused")
public void onClickDeny(final View v) {
UpdateHandler.setDownloadOverMeteredSetting(this, false);
finish();
}
+ // This method is externally referenced from layout/download_over_metered.xml using onClick
+ // attribute of Button.
+ @ExternallyReferenced
+ @SuppressWarnings("unused")
public void onClickAllow(final View v) {
UpdateHandler.setDownloadOverMeteredSetting(this, true);
UpdateHandler.installIfNeverRequested(this, mClientId, mWordListToDownload,
diff --git a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
deleted file mode 100644
index 4f0805c5c..000000000
--- a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
+++ /dev/null
@@ -1,204 +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.dictionarypack;
-
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.text.TextUtils;
-
-import java.util.HashMap;
-import java.util.Locale;
-
-/**
- * A class to help with handling Locales in string form.
- *
- * This file has the same meaning and features (and shares all of its code) with the one with the
- * same name in Latin IME. They need to be kept synchronized; for any update/bugfix to
- * this file, consider also updating/fixing the version in Latin IME.
- */
-public final class LocaleUtils {
- private LocaleUtils() {
- // Intentional empty constructor for utility class.
- }
-
- // Locale match level constants.
- // A higher level of match is guaranteed to have a higher numerical value.
- // Some room is left within constants to add match cases that may arise necessary
- // in the future, for example differentiating between the case where the countries
- // are both present and different, and the case where one of the locales does not
- // specify the countries. This difference is not needed now.
-
- // Nothing matches.
- public static final int LOCALE_NO_MATCH = 0;
- // The languages matches, but the country are different. Or, the reference locale requires a
- // country and the tested locale does not have one.
- public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3;
- // The languages and country match, but the variants are different. Or, the reference locale
- // requires a variant and the tested locale does not have one.
- public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6;
- // The required locale is null or empty so it will accept anything, and the tested locale
- // is non-null and non-empty.
- public static final int LOCALE_ANY_MATCH = 10;
- // The language matches, and the tested locale specifies a country but the reference locale
- // does not require one.
- public static final int LOCALE_LANGUAGE_MATCH = 15;
- // The language and the country match, and the tested locale specifies a variant but the
- // reference locale does not require one.
- public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20;
- // The compared locales are fully identical. This is the best match level.
- public static final int LOCALE_FULL_MATCH = 30;
-
- // The level at which a match is "normally" considered a locale match with standard algorithms.
- // Don't use this directly, use #isMatch to test.
- private static final int LOCALE_MATCH = LOCALE_ANY_MATCH;
-
- // Make this match the maximum match level. If this evolves to have more than 2 digits
- // when written in base 10, also adjust the getMatchLevelSortedString method.
- private static final int MATCH_LEVEL_MAX = 30;
-
- /**
- * Return how well a tested locale matches a reference locale.
- *
- * This will check the tested locale against the reference locale and return a measure of how
- * a well it matches the reference. The general idea is that the tested locale has to match
- * every specified part of the required locale. A full match occur when they are equal, a
- * partial match when the tested locale agrees with the reference locale but is more specific,
- * and a difference when the tested locale does not comply with all requirements from the
- * reference locale.
- * In more detail, if the reference locale specifies at least a language and the testedLocale
- * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the
- * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH
- * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and
- * tested locale agree on the language, but not on the country,
- * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country,
- * and LOCALE_LANGUAGE_MATCH otherwise.
- * If they agree on both the language and the country, but not on the variant,
- * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale
- * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches,
- * LOCALE_FULL_MATCH is returned.
- * Examples:
- * en <=> en_US => LOCALE_LANGUAGE_MATCH
- * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER
- * en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER
- * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH
- * sp_US <=> en_US => LOCALE_NO_MATCH
- * de <=> de => LOCALE_FULL_MATCH
- * en_US <=> en_US => LOCALE_FULL_MATCH
- * "" <=> en_US => LOCALE_ANY_MATCH
- *
- * @param referenceLocale the reference locale to test against.
- * @param testedLocale the locale to test.
- * @return a constant that measures how well the tested locale matches the reference locale.
- */
- public static int getMatchLevel(final String referenceLocale, final String testedLocale) {
- if (TextUtils.isEmpty(referenceLocale)) {
- return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH;
- }
- if (null == testedLocale) return LOCALE_NO_MATCH;
- final String[] referenceParams = referenceLocale.split("_", 3);
- final String[] testedParams = testedLocale.split("_", 3);
- // By spec of String#split, [0] cannot be null and length cannot be 0.
- if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH;
- switch (referenceParams.length) {
- case 1:
- return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH;
- case 2:
- if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (!referenceParams[1].equals(testedParams[1]))
- return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH;
- return LOCALE_FULL_MATCH;
- case 3:
- if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (!referenceParams[1].equals(testedParams[1]))
- return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
- if (!referenceParams[2].equals(testedParams[2]))
- return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
- return LOCALE_FULL_MATCH;
- }
- // It should be impossible to come here
- return LOCALE_NO_MATCH;
- }
-
- /**
- * Return a string that represents this match level, with better matches first.
- *
- * The strings are sorted in lexicographic order: a better match will always be less than
- * a worse match when compared together.
- */
- public static String getMatchLevelSortedString(final int matchLevel) {
- // This works because the match levels are 0~99 (actually 0~30)
- // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel
- return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel);
- }
-
- /**
- * Find out whether a match level should be considered a match.
- *
- * This method takes a match level as returned by the #getMatchLevel method, and returns whether
- * it should be considered a match in the usual sense with standard Locale functions.
- *
- * @param level the match level, as returned by getMatchLevel.
- * @return whether this is a match or not.
- */
- public static boolean isMatch(final int level) {
- return LOCALE_MATCH <= level;
- }
-
- /**
- * Sets the system locale for this process.
- *
- * @param res the resources to use. Pass current resources.
- * @param newLocale the locale to change to.
- * @return the old locale.
- */
- public static Locale setSystemLocale(final Resources res, final Locale newLocale) {
- final Configuration conf = res.getConfiguration();
- final Locale saveLocale = conf.locale;
- conf.locale = newLocale;
- res.updateConfiguration(conf, res.getDisplayMetrics());
- return saveLocale;
- }
-
- private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();
-
- /**
- * Creates a locale from a string specification.
- */
- public static Locale constructLocaleFromString(final String localeStr) {
- if (localeStr == null)
- return null;
- synchronized (sLocaleCache) {
- if (sLocaleCache.containsKey(localeStr))
- return sLocaleCache.get(localeStr);
- Locale retval = null;
- String[] localeParams = localeStr.split("_", 3);
- if (localeParams.length == 1) {
- retval = new Locale(localeParams[0]);
- } else if (localeParams.length == 2) {
- retval = new Locale(localeParams[0], localeParams[1]);
- } else if (localeParams.length == 3) {
- retval = new Locale(localeParams[0], localeParams[1], localeParams[2]);
- }
- if (retval != null) {
- sLocaleCache.put(localeStr, retval);
- }
- return retval;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
index 17dd781d5..a2789cc1a 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -47,10 +47,14 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// used to identify the versions for upgrades. This should never change going forward.
private static final int METADATA_DATABASE_VERSION_WITH_CLIENTID = 6;
// The current database version.
- private static final int CURRENT_METADATA_DATABASE_VERSION = 9;
+ // This MUST be increased every time the dictionary pack metadata URL changes.
+ private static final int CURRENT_METADATA_DATABASE_VERSION = 14;
private final static long NOT_A_DOWNLOAD_ID = -1;
+ // The number of retries allowed when attempting to download a broken dictionary.
+ public static final int DICTIONARY_RETRY_THRESHOLD = 2;
+
public static final String METADATA_TABLE_NAME = "pendingUpdates";
static final String CLIENT_TABLE_NAME = "clients";
public static final String PENDINGID_COLUMN = "pendingid"; // Download Manager ID
@@ -68,7 +72,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
public static final String FORMATVERSION_COLUMN = "formatversion";
public static final String FLAGS_COLUMN = "flags";
public static final String RAW_CHECKSUM_COLUMN = "rawChecksum";
- public static final int COLUMN_COUNT = 14;
+ public static final String RETRY_COUNT_COLUMN = "remainingRetries";
+ public static final int COLUMN_COUNT = 15;
private static final String CLIENT_CLIENT_ID_COLUMN = "clientid";
private static final String CLIENT_METADATA_URI_COLUMN = "uri";
@@ -98,6 +103,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// Deleting: the user marked this word list to be deleted, but it has not been yet because
// Latin IME is not up yet.
public static final int STATUS_DELETING = 5;
+ // Retry: dictionary got corrupted, so an attempt must be done to download & install it again.
+ public static final int STATUS_RETRYING = 6;
// Types, for storing in the TYPE_COLUMN
// This is metadata about what is available.
@@ -124,6 +131,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
+ FORMATVERSION_COLUMN + " INTEGER, "
+ FLAGS_COLUMN + " INTEGER, "
+ RAW_CHECKSUM_COLUMN + " TEXT,"
+ + RETRY_COUNT_COLUMN + " INTEGER, "
+ "PRIMARY KEY (" + WORDLISTID_COLUMN + "," + VERSION_COLUMN + "));";
private static final String METADATA_CREATE_CLIENT_TABLE =
"CREATE TABLE IF NOT EXISTS " + CLIENT_TABLE_NAME + " ("
@@ -140,7 +148,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
STATUS_COLUMN, WORDLISTID_COLUMN, LOCALE_COLUMN, DESCRIPTION_COLUMN,
LOCAL_FILENAME_COLUMN, REMOTE_FILENAME_COLUMN, DATE_COLUMN, CHECKSUM_COLUMN,
FILESIZE_COLUMN, VERSION_COLUMN, FORMATVERSION_COLUMN, FLAGS_COLUMN,
- RAW_CHECKSUM_COLUMN };
+ RAW_CHECKSUM_COLUMN, RETRY_COUNT_COLUMN };
// List of all client table columns.
static final String[] CLIENT_TABLE_COLUMNS = { CLIENT_CLIENT_ID_COLUMN,
CLIENT_METADATA_URI_COLUMN, CLIENT_PENDINGID_COLUMN, FLAGS_COLUMN };
@@ -219,7 +227,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
createClientTable(db);
}
- private void addRawChecksumColumnUnlessPresent(final SQLiteDatabase db, final String clientId) {
+ private static void addRawChecksumColumnUnlessPresent(final SQLiteDatabase db) {
try {
db.execSQL("SELECT " + RAW_CHECKSUM_COLUMN + " FROM "
+ METADATA_TABLE_NAME + " LIMIT 0;");
@@ -230,6 +238,17 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
}
}
+ private static void addRetryCountColumnUnlessPresent(final SQLiteDatabase db) {
+ try {
+ db.execSQL("SELECT " + RETRY_COUNT_COLUMN + " FROM "
+ + METADATA_TABLE_NAME + " LIMIT 0;");
+ } catch (SQLiteException e) {
+ Log.i(TAG, "No " + RETRY_COUNT_COLUMN + " column : creating it");
+ db.execSQL("ALTER TABLE " + METADATA_TABLE_NAME + " ADD COLUMN "
+ + RETRY_COUNT_COLUMN + " INTEGER DEFAULT " + DICTIONARY_RETRY_THRESHOLD + ";");
+ }
+ }
+
/**
* Upgrade the database. Upgrade from version 3 is supported.
* Version 3 has a DB named METADATA_DATABASE_NAME_STEM containing a table METADATA_TABLE_NAME.
@@ -245,6 +264,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
*/
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ // Allow automatic download of dictionaries on upgrading the database.
+ CommonPreferences.setForceDownloadDict(mContext, true);
if (METADATA_DATABASE_INITIAL_VERSION == oldVersion
&& METADATA_DATABASE_VERSION_WITH_CLIENTID <= newVersion
&& CURRENT_METADATA_DATABASE_VERSION >= newVersion) {
@@ -280,7 +301,14 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// strengthen the system against corrupted dictionary files.
// The most secure way to upgrade a database is to just test for the column presence, and
// add it if it's not there.
- addRawChecksumColumnUnlessPresent(db, mClientId);
+ addRawChecksumColumnUnlessPresent(db);
+
+ // A retry count column that did not exist in the previous versions was added that
+ // corresponds to the number of download & installation attempts that have been made
+ // in order to strengthen the system recovery from corrupted dictionary files.
+ // The most secure way to upgrade a database is to just test for the column presence, and
+ // add it if it's not there.
+ addRetryCountColumnUnlessPresent(db);
}
/**
@@ -408,18 +436,18 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
*
* @param context a context instance to open the database on
* @param uri the URI to retrieve the metadata download ID of
- * @return the metadata download ID, or NOT_AN_ID if no download is in progress
+ * @return the download id and start date, or null if the URL is not known
*/
- public static long getMetadataDownloadIdForURI(final Context context,
- final String uri) {
+ public static DownloadIdAndStartDate getMetadataDownloadIdAndStartDateForURI(
+ final Context context, final String uri) {
SQLiteDatabase defaultDb = getDb(context, null);
final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME,
- new String[] { CLIENT_PENDINGID_COLUMN },
+ new String[] { CLIENT_PENDINGID_COLUMN, CLIENT_LAST_UPDATE_DATE_COLUMN },
CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri },
null, null, null, null);
try {
- if (!cursor.moveToFirst()) return UpdateHandler.NOT_AN_ID;
- return cursor.getInt(0); // Only one column, return it
+ if (!cursor.moveToFirst()) return null;
+ return new DownloadIdAndStartDate(cursor.getInt(0), cursor.getLong(1));
} finally {
cursor.close();
}
@@ -452,8 +480,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
public static ContentValues makeContentValues(final int pendingId, final int type,
final int status, final String wordlistId, final String locale,
final String description, final String filename, final String url, final long date,
- final String rawChecksum, final String checksum, final long filesize, final int version,
- final int formatVersion) {
+ final String rawChecksum, final String checksum, final int retryCount,
+ final long filesize, final int version, final int formatVersion) {
final ContentValues result = new ContentValues(COLUMN_COUNT);
result.put(PENDINGID_COLUMN, pendingId);
result.put(TYPE_COLUMN, type);
@@ -465,6 +493,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
result.put(REMOTE_FILENAME_COLUMN, url);
result.put(DATE_COLUMN, date);
result.put(RAW_CHECKSUM_COLUMN, rawChecksum);
+ result.put(RETRY_COUNT_COLUMN, retryCount);
result.put(CHECKSUM_COLUMN, checksum);
result.put(FILESIZE_COLUMN, filesize);
result.put(VERSION_COLUMN, version);
@@ -502,6 +531,9 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
if (null == result.get(DATE_COLUMN)) result.put(DATE_COLUMN, 0);
// Raw checksum unknown unless specified
if (null == result.get(RAW_CHECKSUM_COLUMN)) result.put(RAW_CHECKSUM_COLUMN, "");
+ // Retry column 0 unless specified
+ if (null == result.get(RETRY_COUNT_COLUMN)) result.put(RETRY_COUNT_COLUMN,
+ DICTIONARY_RETRY_THRESHOLD);
// Checksum unknown unless specified
if (null == result.get(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, "");
// No filesize unless specified
@@ -551,6 +583,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
putIntResult(result, cursor, DATE_COLUMN);
putStringResult(result, cursor, RAW_CHECKSUM_COLUMN);
putStringResult(result, cursor, CHECKSUM_COLUMN);
+ putIntResult(result, cursor, RETRY_COUNT_COLUMN);
putIntResult(result, cursor, FILESIZE_COLUMN);
putIntResult(result, cursor, VERSION_COLUMN);
putIntResult(result, cursor, FORMATVERSION_COLUMN);
@@ -676,8 +709,16 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final String id, final int version) {
final Cursor cursor = db.query(METADATA_TABLE_NAME,
METADATA_TABLE_COLUMNS,
- WORDLISTID_COLUMN + "= ? AND " + VERSION_COLUMN + "= ?",
- new String[] { id, Integer.toString(version) }, null, null, null);
+ WORDLISTID_COLUMN + "= ? AND " + VERSION_COLUMN + "= ? AND "
+ + FORMATVERSION_COLUMN + "<= ?",
+ new String[]
+ { id,
+ Integer.toString(version),
+ Integer.toString(UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION)
+ },
+ null /* groupBy */,
+ null /* having */,
+ FORMATVERSION_COLUMN + " DESC"/* orderBy */);
if (null == cursor) {
return null;
}
@@ -706,7 +747,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
return null;
}
try {
- // This is a lookup by primary key, so there can't be more than one result.
+ // Return the first result from the list of results.
return getFirstLineAsContentValues(cursor);
} finally {
cursor.close();
@@ -884,6 +925,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final long downloadId) {
final ContentValues values = new ContentValues();
values.put(CLIENT_PENDINGID_COLUMN, downloadId);
+ values.put(CLIENT_LAST_UPDATE_DATE_COLUMN, System.currentTimeMillis());
final SQLiteDatabase defaultDb = getDb(context, "");
final Cursor cursor = MetadataDbHelper.queryClientIds(context);
if (null == cursor) return;
@@ -1085,4 +1127,27 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final int version) {
markEntryAs(db, id, version, STATUS_DELETING, NOT_A_DOWNLOAD_ID);
}
+
+ /**
+ * Checks retry counts and marks the word list as retrying if retry is possible.
+ *
+ * @param db the metadata database.
+ * @param id the id of the word list.
+ * @param version the version of the word list.
+ * @return {@code true} if the retry is possible.
+ */
+ public static boolean maybeMarkEntryAsRetrying(final SQLiteDatabase db, final String id,
+ final int version) {
+ final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, id, version);
+ int retryCount = values.getAsInteger(MetadataDbHelper.RETRY_COUNT_COLUMN);
+ if (retryCount > 1) {
+ values.put(STATUS_COLUMN, STATUS_RETRYING);
+ values.put(RETRY_COUNT_COLUMN, retryCount - 1);
+ db.update(METADATA_TABLE_NAME, values,
+ WORDLISTID_COLUMN + " = ? AND " + VERSION_COLUMN + " = ?",
+ new String[] { id, Integer.toString(version) });
+ return true;
+ }
+ return false;
+ }
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
index d66b69050..329b9f62e 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.dictionarypack;
+import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -29,9 +30,6 @@ import java.util.List;
* Helper class to easy up manipulation of dictionary pack metadata.
*/
public class MetadataHandler {
- @SuppressWarnings("unused")
- private static final String TAG = "DictionaryProvider:" + MetadataHandler.class.getSimpleName();
-
// The canonical file name for metadata. This is not the name of a real file on the
// device, but a symbolic name used in the database and in metadata handling. It is never
// tested against, only used for human-readability as the file name for the metadata.
@@ -55,6 +53,7 @@ public class MetadataHandler {
final int rawChecksumIndex =
results.getColumnIndex(MetadataDbHelper.RAW_CHECKSUM_COLUMN);
final int checksumIndex = results.getColumnIndex(MetadataDbHelper.CHECKSUM_COLUMN);
+ final int retryCountIndex = results.getColumnIndex(MetadataDbHelper.RETRY_COUNT_COLUMN);
final int localFilenameIndex =
results.getColumnIndex(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
final int remoteFilenameIndex =
@@ -70,6 +69,7 @@ public class MetadataHandler {
results.getLong(fileSizeIndex),
results.getString(rawChecksumIndex),
results.getString(checksumIndex),
+ results.getInt(retryCountIndex),
results.getString(localFilenameIndex),
results.getString(remoteFilenameIndex),
results.getInt(versionIndex),
@@ -102,6 +102,22 @@ public class MetadataHandler {
}
/**
+ * Gets the metadata, for a specific dictionary.
+ *
+ * @param context The context to open files over.
+ * @param clientId the client id for retrieving the database. null for default (deprecated).
+ * @param wordListId the word list ID.
+ * @param version the word list version.
+ * @return the current metaData
+ */
+ public static WordListMetadata getCurrentMetadataForWordList(final Context context,
+ final String clientId, final String wordListId, final int version) {
+ final ContentValues contentValues = MetadataDbHelper.getContentValuesByWordListId(
+ MetadataDbHelper.getDb(context, clientId), wordListId, version);
+ return WordListMetadata.createFromContentValues(contentValues);
+ }
+
+ /**
* Read metadata from a stream.
* @param input The stream to read from.
* @return The read metadata.
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java b/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
index 52290cadc..2b67ae9ff 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
@@ -83,6 +83,7 @@ public class MetadataParser {
Long.parseLong(arguments.get(FILESIZE_FIELD_NAME)),
arguments.get(RAW_CHECKSUM_FIELD_NAME),
arguments.get(CHECKSUM_FIELD_NAME),
+ MetadataDbHelper.DICTIONARY_RETRY_THRESHOLD /* retryCount */,
null,
arguments.get(REMOTE_FILENAME_FIELD_NAME),
Integer.parseInt(arguments.get(VERSION_FIELD_NAME)),
diff --git a/java/src/com/android/inputmethod/dictionarypack/PrivateLog.java b/java/src/com/android/inputmethod/dictionarypack/PrivateLog.java
index 67dd7b9b7..bb64721d5 100644
--- a/java/src/com/android/inputmethod/dictionarypack/PrivateLog.java
+++ b/java/src/com/android/inputmethod/dictionarypack/PrivateLog.java
@@ -43,8 +43,8 @@ public class PrivateLog {
+ COLUMN_DATE + " TEXT,"
+ COLUMN_EVENT + " TEXT);";
- private static final SimpleDateFormat sDateFormat =
- new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US);
+ static final SimpleDateFormat sDateFormat = new SimpleDateFormat(
+ "yyyy/MM/dd HH:mm:ss", Locale.ROOT);
private static PrivateLog sInstance = new PrivateLog();
private static DebugHelper sDebugHelper = null;
@@ -62,9 +62,9 @@ public class PrivateLog {
}
}
- private static class DebugHelper extends SQLiteOpenHelper {
+ static class DebugHelper extends SQLiteOpenHelper {
- private DebugHelper(final Context context) {
+ DebugHelper(final Context context) {
super(context, LOG_DATABASE_NAME, null, LOG_DATABASE_VERSION);
}
@@ -84,7 +84,7 @@ public class PrivateLog {
insert(db, "Upgrade finished");
}
- private static void insert(SQLiteDatabase db, String event) {
+ static void insert(SQLiteDatabase db, String event) {
if (!DEBUG) return;
final ContentValues c = new ContentValues(2);
c.put(COLUMN_DATE, sDateFormat.format(new Date(System.currentTimeMillis())));
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index 6fbca44c5..30ff0b8ee 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -31,7 +31,6 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.ConnectivityManager;
import android.net.Uri;
-import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;
@@ -40,6 +39,8 @@ import com.android.inputmethod.compat.ConnectivityManagerCompatUtils;
import com.android.inputmethod.compat.DownloadManagerCompatUtils;
import com.android.inputmethod.compat.NotificationCompatUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.LocaleUtils;
+import com.android.inputmethod.latin.makedict.FormatSpec;
import com.android.inputmethod.latin.utils.ApplicationUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
@@ -56,10 +57,11 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;
+import javax.annotation.Nullable;
+
/**
* Handler for the update process.
*
@@ -77,7 +79,8 @@ public final class UpdateHandler {
// DownloadManager uses as an ID numbers returned out of an AUTOINCREMENT column
// in SQLite, so it should never return anything < 0.
public static final int NOT_AN_ID = -1;
- public static final int MAXIMUM_SUPPORTED_FORMAT_VERSION = 2;
+ public static final int MAXIMUM_SUPPORTED_FORMAT_VERSION =
+ FormatSpec.MAXIMUM_SUPPORTED_STATIC_VERSION;
// Arbitrary. Probably good if it's a power of 2, and a couple thousand bytes long.
private static final int FILE_COPY_BUFFER_SIZE = 8192;
@@ -252,12 +255,16 @@ public final class UpdateHandler {
res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI));
final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
- cancelUpdateWithDownloadManager(context, metadataUri, manager);
+ if (maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager,
+ DictionaryService.NO_CANCEL_DOWNLOAD_PERIOD_MILLIS)) {
+ // We already have a recent download in progress. Don't register a new download.
+ return;
+ }
final long downloadId;
synchronized (sSharedIdProtector) {
downloadId = manager.enqueue(metadataRequest);
DebugLogUtils.l("Metadata download requested with id", downloadId);
- // If there is already a download in progress, it's been there for a while and
+ // If there is still a download in progress, it's been there for a while and
// there is probably something wrong with download manager. It's best to just
// overwrite the id and request it again. If the old one happens to finish
// anyway, we don't know about its ID any more, so the downloadFinished
@@ -268,21 +275,29 @@ public final class UpdateHandler {
}
/**
- * Cancels downloading a file, if there is one for this URI.
+ * Cancels downloading a file if there is one for this URI and it's too long.
*
* If we are not currently downloading the file at this URI, this is a no-op.
*
* @param context the context to open the database on
* @param metadataUri the URI to cancel
* @param manager an wrapped instance of DownloadManager
+ * @param graceTime if there was a download started less than this many milliseconds, don't
+ * cancel and return true
+ * @return whether the download is still active
*/
- private static void cancelUpdateWithDownloadManager(final Context context,
- final String metadataUri, final DownloadManagerWrapper manager) {
+ private static boolean maybeCancelUpdateAndReturnIfStillRunning(final Context context,
+ final String metadataUri, final DownloadManagerWrapper manager, final long graceTime) {
synchronized (sSharedIdProtector) {
- final long metadataDownloadId =
- MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri);
- if (NOT_AN_ID == metadataDownloadId) return;
- manager.remove(metadataDownloadId);
+ final DownloadIdAndStartDate metadataDownloadIdAndStartDate =
+ MetadataDbHelper.getMetadataDownloadIdAndStartDateForURI(context, metadataUri);
+ if (null == metadataDownloadIdAndStartDate) return false;
+ if (NOT_AN_ID == metadataDownloadIdAndStartDate.mId) return false;
+ if (metadataDownloadIdAndStartDate.mStartDate + graceTime
+ > System.currentTimeMillis()) {
+ return true;
+ }
+ manager.remove(metadataDownloadIdAndStartDate.mId);
writeMetadataDownloadId(context, metadataUri, NOT_AN_ID);
}
// Consider a cancellation as a failure. As such, inform listeners that the download
@@ -290,6 +305,7 @@ public final class UpdateHandler {
for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) {
listener.downloadedMetadata(false);
}
+ return false;
}
/**
@@ -304,7 +320,7 @@ public final class UpdateHandler {
public static void cancelUpdate(final Context context, final String clientId) {
final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId);
- cancelUpdateWithDownloadManager(context, metadataUri, manager);
+ maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager, 0 /* graceTime */);
}
/**
@@ -388,7 +404,7 @@ public final class UpdateHandler {
// If any of these is metadata, we should update the DB
boolean hasMetadata = false;
for (DownloadRecord record : downloadRecords) {
- if (null == record.mAttributes) {
+ if (record.isMetadata()) {
hasMetadata = true;
break;
}
@@ -433,6 +449,8 @@ public final class UpdateHandler {
// download, so we are pretty sure it's alive. It's theoretically possible that it's
// disabled right inbetween the firing of the intent and the control reaching here.
+ boolean dictionaryDownloaded = false;
+
for (final DownloadRecord record : recordList) {
// downloadSuccessful is not final because we may still have exceptions from now on
boolean downloadSuccessful = false;
@@ -447,9 +465,15 @@ public final class UpdateHandler {
final SQLiteDatabase db = MetadataDbHelper.getDb(context, record.mClientId);
publishUpdateWordListCompleted(context, downloadSuccessful, fileId,
db, record.mAttributes, record.mClientId);
+ dictionaryDownloaded = true;
}
}
}
+
+ if (dictionaryDownloaded) {
+ // Disable the force download after downloading the dictionaries.
+ CommonPreferences.setForceDownloadDict(context, false);
+ }
// Now that we're done using it, we can remove this download from DLManager
manager.remove(fileId);
}
@@ -738,19 +762,22 @@ public final class UpdateHandler {
* @return an ordered list of runnables to be called to upgrade.
*/
private static ActionBatch compareMetadataForUpgrade(final Context context,
- final String clientId, List<WordListMetadata> from, List<WordListMetadata> to) {
+ final String clientId, @Nullable final List<WordListMetadata> from,
+ @Nullable final List<WordListMetadata> to) {
final ActionBatch actions = new ActionBatch();
// Upgrade existing word lists
DebugLogUtils.l("Comparing dictionaries");
final Set<String> wordListIds = new TreeSet<>();
// TODO: Can these be null?
- if (null == from) from = new ArrayList<>();
- if (null == to) to = new ArrayList<>();
- for (WordListMetadata wlData : from) wordListIds.add(wlData.mId);
- for (WordListMetadata wlData : to) wordListIds.add(wlData.mId);
+ final List<WordListMetadata> fromList = (from == null) ? new ArrayList<WordListMetadata>()
+ : from;
+ final List<WordListMetadata> toList = (to == null) ? new ArrayList<WordListMetadata>()
+ : to;
+ for (WordListMetadata wlData : fromList) wordListIds.add(wlData.mId);
+ for (WordListMetadata wlData : toList) wordListIds.add(wlData.mId);
for (String id : wordListIds) {
- final WordListMetadata currentInfo = MetadataHandler.findWordListById(from, id);
- final WordListMetadata metadataInfo = MetadataHandler.findWordListById(to, id);
+ final WordListMetadata currentInfo = MetadataHandler.findWordListById(fromList, id);
+ final WordListMetadata metadataInfo = MetadataHandler.findWordListById(toList, id);
// TODO: Remove the following unnecessary check, since we are now doing the filtering
// inside findWordListById.
final WordListMetadata newInfo = null == metadataInfo
@@ -785,6 +812,10 @@ public final class UpdateHandler {
} else {
final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId);
if (newInfo.mVersion == currentInfo.mVersion) {
+ if (TextUtils.equals(newInfo.mRemoteFilename, currentInfo.mRemoteFilename)) {
+ // If the dictionary url hasn't changed, we should preserve the retryCount.
+ newInfo.mRetryCount = currentInfo.mRetryCount;
+ }
// If it's the same id/version, we update the DB with the new values.
// It doesn't matter too much if they didn't change.
actions.add(new ActionBatch.UpdateDataAction(clientId, newInfo));
@@ -797,7 +828,8 @@ public final class UpdateHandler {
actions.add(new ActionBatch.MakeAvailableAction(clientId, newInfo));
if (status == MetadataDbHelper.STATUS_INSTALLED
|| status == MetadataDbHelper.STATUS_DISABLED) {
- actions.add(new ActionBatch.StartDownloadAction(clientId, newInfo, false));
+ actions.add(new ActionBatch.StartDownloadAction(
+ clientId, newInfo, CommonPreferences.isForceDownloadDict(context)));
} else {
// Pass true to ForgetAction: this is indeed an update to a non-installed
// word list, so activate status == AVAILABLE check
@@ -856,8 +888,8 @@ public final class UpdateHandler {
// None of those are expected to happen, but just in case...
if (null == notificationIntent || null == notificationManager) return;
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
- final String language = (null == locale ? "" : locale.getDisplayLanguage());
+ final String language = (null == localeString) ? ""
+ : LocaleUtils.constructLocaleFromString(localeString).getDisplayLanguage();
final String titleFormat = context.getString(R.string.dict_available_notification_title);
final String notificationTitle = String.format(titleFormat, language);
final Notification.Builder builder = new Notification.Builder(context)
@@ -950,8 +982,10 @@ public final class UpdateHandler {
// change the shared preferences. So there is no way for a word list that has been
// auto-installed once to get auto-installed again, and that's what we want.
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.StartDownloadAction(clientId,
- WordListMetadata.createFromContentValues(installCandidate), false));
+ actions.add(new ActionBatch.StartDownloadAction(
+ clientId,
+ WordListMetadata.createFromContentValues(installCandidate),
+ CommonPreferences.isForceDownloadDict(context)));
final String localeString = installCandidate.getAsString(MetadataDbHelper.LOCALE_COLUMN);
// We are in a content provider: we can't do any UI at all. We have to defer the displaying
// itself to the service. Also, we only display this when the user does not have a
@@ -987,17 +1021,19 @@ public final class UpdateHandler {
public static void markAsUsed(final Context context, final String clientId,
final String wordlistId, final int version,
final int status, final boolean allowDownloadOnMeteredData) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- WordListMetadata wordList = MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
+
final ActionBatch actions = new ActionBatch();
if (MetadataDbHelper.STATUS_DISABLED == status
|| MetadataDbHelper.STATUS_DELETING == status) {
- actions.add(new ActionBatch.EnableAction(clientId, wordList));
+ actions.add(new ActionBatch.EnableAction(clientId, wordListMetaData));
} else if (MetadataDbHelper.STATUS_AVAILABLE == status) {
- actions.add(new ActionBatch.StartDownloadAction(clientId, wordList,
- allowDownloadOnMeteredData));
+ boolean forceDownloadDict = CommonPreferences.isForceDownloadDict(context);
+ actions.add(new ActionBatch.StartDownloadAction(clientId, wordListMetaData,
+ forceDownloadDict || allowDownloadOnMeteredData));
} else {
Log.e(TAG, "Unexpected state of the word list for markAsUsed : " + status);
}
@@ -1022,13 +1058,13 @@ public final class UpdateHandler {
// markAsUsed for consistency.
public static void markAsUnused(final Context context, final String clientId,
final String wordlistId, final int version, final int status) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- final WordListMetadata wordList =
- MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.DisableAction(clientId, wordList));
+ actions.add(new ActionBatch.DisableAction(clientId, wordListMetaData));
actions.execute(context, new LogProblemReporter(TAG));
signalNewDictionaryState(context);
}
@@ -1051,14 +1087,14 @@ public final class UpdateHandler {
*/
public static void markAsDeleting(final Context context, final String clientId,
final String wordlistId, final int version, final int status) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- final WordListMetadata wordList =
- MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.DisableAction(clientId, wordList));
- actions.add(new ActionBatch.StartDeleteAction(clientId, wordList));
+ actions.add(new ActionBatch.DisableAction(clientId, wordListMetaData));
+ actions.add(new ActionBatch.StartDeleteAction(clientId, wordListMetaData));
actions.execute(context, new LogProblemReporter(TAG));
signalNewDictionaryState(context);
}
@@ -1076,33 +1112,48 @@ public final class UpdateHandler {
*/
public static void markAsDeleted(final Context context, final String clientId,
final String wordlistId, final int version, final int status) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- final WordListMetadata wordList =
- MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
+
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.FinishDeleteAction(clientId, wordList));
+ actions.add(new ActionBatch.FinishDeleteAction(clientId, wordListMetaData));
actions.execute(context, new LogProblemReporter(TAG));
signalNewDictionaryState(context);
}
/**
- * Marks the word list with the passed id as broken.
- *
- * This effectively deletes the entry from the metadata. It doesn't prevent the same
- * word list to be downloaded again at a later time if the same or a new version is
- * available the next time we download the metadata.
+ * Checks whether the word list should be downloaded again; in which case an download &
+ * installation attempt is made. Otherwise the word list is marked broken.
*
* @param context the context to open the database on.
* @param clientId the id of the client.
- * @param wordlistId the id of the word list to mark as broken.
- * @param version the version of the word list to mark as deleted.
+ * @param wordlistId the id of the word list which is broken.
+ * @param version the version of the broken word list.
*/
- public static void markAsBroken(final Context context, final String clientId,
+ public static void markAsBrokenOrRetrying(final Context context, final String clientId,
final String wordlistId, final int version) {
- // TODO: do this on another thread to avoid blocking the UI.
- MetadataDbHelper.deleteEntry(MetadataDbHelper.getDb(context, clientId),
- wordlistId, version);
+ boolean isRetryPossible = MetadataDbHelper.maybeMarkEntryAsRetrying(
+ MetadataDbHelper.getDb(context, clientId), wordlistId, version);
+
+ if (isRetryPossible) {
+ if (DEBUG) {
+ Log.d(TAG, "Attempting to download & install the wordlist again.");
+ }
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ final ActionBatch actions = new ActionBatch();
+ actions.add(new ActionBatch.StartDownloadAction(
+ clientId, wordListMetaData, CommonPreferences.isForceDownloadDict(context)));
+ actions.execute(context, new LogProblemReporter(TAG));
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Retries for wordlist exhausted, deleting the wordlist from table.");
+ }
+ MetadataDbHelper.deleteEntry(MetadataDbHelper.getDb(context, clientId),
+ wordlistId, version);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java b/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
index 9e510a68b..59f75e4ed 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
@@ -36,6 +36,7 @@ public class WordListMetadata {
public final String mRemoteFilename;
public final int mVersion; // version of this word list
public final int mFlags; // Always 0 in this version, reserved for future use
+ public int mRetryCount;
// The locale is matched against the locale requested by the client. The matching algorithm
// is a standard locale matching with fallback; it is implemented in
@@ -51,8 +52,9 @@ public class WordListMetadata {
public WordListMetadata(final String id, final int type,
final String description, final long lastUpdate, final long fileSize,
- final String rawChecksum, final String checksum, final String localFilename,
- final String remoteFilename, final int version, final int formatVersion,
+ final String rawChecksum, final String checksum, final int retryCount,
+ final String localFilename, final String remoteFilename,
+ final int version, final int formatVersion,
final int flags, final String locale) {
mId = id;
mType = type;
@@ -61,6 +63,7 @@ public class WordListMetadata {
mFileSize = fileSize;
mRawChecksum = rawChecksum;
mChecksum = checksum;
+ mRetryCount = retryCount;
mLocalFilename = localFilename;
mRemoteFilename = remoteFilename;
mVersion = version;
@@ -82,6 +85,7 @@ public class WordListMetadata {
final Long fileSize = values.getAsLong(MetadataDbHelper.FILESIZE_COLUMN);
final String rawChecksum = values.getAsString(MetadataDbHelper.RAW_CHECKSUM_COLUMN);
final String checksum = values.getAsString(MetadataDbHelper.CHECKSUM_COLUMN);
+ final int retryCount = values.getAsInteger(MetadataDbHelper.RETRY_COUNT_COLUMN);
final String localFilename = values.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
final String remoteFilename = values.getAsString(MetadataDbHelper.REMOTE_FILENAME_COLUMN);
final Integer version = values.getAsInteger(MetadataDbHelper.VERSION_COLUMN);
@@ -103,7 +107,8 @@ public class WordListMetadata {
throw new IllegalArgumentException();
}
return new WordListMetadata(id, type, description, lastUpdate, fileSize, rawChecksum,
- checksum, localFilename, remoteFilename, version, formatVersion, flags, locale);
+ checksum, retryCount, localFilename, remoteFilename, version, formatVersion,
+ flags, locale);
}
@Override
@@ -116,6 +121,7 @@ public class WordListMetadata {
sb.append("\nFileSize : ").append(mFileSize);
sb.append("\nRawChecksum : ").append(mRawChecksum);
sb.append("\nChecksum : ").append(mChecksum);
+ sb.append("\nRetryCount: ").append(mRetryCount);
sb.append("\nLocalFilename : ").append(mLocalFilename);
sb.append("\nRemoteFilename : ").append(mRemoteFilename);
sb.append("\nVersion : ").append(mVersion);
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
index aea16af0d..500e39e0e 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
@@ -38,45 +38,39 @@ import java.util.Locale;
* enable or delete it as appropriate for the current state of the word list.
*/
public final class WordListPreference extends Preference {
- static final private String TAG = WordListPreference.class.getSimpleName();
+ private static final String TAG = WordListPreference.class.getSimpleName();
// What to display in the "status" field when we receive unknown data as a status from
// the content provider. Empty string sounds sensible.
- static final private String NO_STATUS_MESSAGE = "";
+ private static final String NO_STATUS_MESSAGE = "";
/// Actions
- static final private int ACTION_UNKNOWN = 0;
- static final private int ACTION_ENABLE_DICT = 1;
- static final private int ACTION_DISABLE_DICT = 2;
- static final private int ACTION_DELETE_DICT = 3;
+ private static final int ACTION_UNKNOWN = 0;
+ private static final int ACTION_ENABLE_DICT = 1;
+ private static final int ACTION_DISABLE_DICT = 2;
+ private static final int ACTION_DELETE_DICT = 3;
// Members
- // The context to get resources
- final Context mContext;
- // The id of the client for which this preference is.
- final String mClientId;
// The metadata word list id and version of this word list.
public final String mWordlistId;
public final int mVersion;
public final Locale mLocale;
public final String mDescription;
+
+ // The id of the client for which this preference is.
+ private final String mClientId;
// The status
private int mStatus;
// The size of the dictionary file
private final int mFilesize;
private final DictionaryListInterfaceState mInterfaceState;
- private final OnWordListPreferenceClick mPreferenceClickHandler =
- new OnWordListPreferenceClick();
- private final OnActionButtonClick mActionButtonClickHandler =
- new OnActionButtonClick();
public WordListPreference(final Context context,
final DictionaryListInterfaceState dictionaryListInterfaceState, final String clientId,
final String wordlistId, final int version, final Locale locale,
final String description, final int status, final int filesize) {
super(context, null);
- mContext = context;
mInterfaceState = dictionaryListInterfaceState;
mClientId = clientId;
mVersion = version;
@@ -116,22 +110,23 @@ public final class WordListPreference extends Preference {
}
private String getSummary(final int status) {
+ final Context context = getContext();
switch (status) {
- // If we are deleting the word list, for the user it's like it's already deleted.
- // It should be reinstallable. Exposing to the user the whole complexity of
- // the delayed deletion process between the dictionary pack and Android Keyboard
- // would only be confusing.
- case MetadataDbHelper.STATUS_DELETING:
- case MetadataDbHelper.STATUS_AVAILABLE:
- return mContext.getString(R.string.dictionary_available);
- case MetadataDbHelper.STATUS_DOWNLOADING:
- return mContext.getString(R.string.dictionary_downloading);
- case MetadataDbHelper.STATUS_INSTALLED:
- return mContext.getString(R.string.dictionary_installed);
- case MetadataDbHelper.STATUS_DISABLED:
- return mContext.getString(R.string.dictionary_disabled);
- default:
- return NO_STATUS_MESSAGE;
+ // If we are deleting the word list, for the user it's like it's already deleted.
+ // It should be reinstallable. Exposing to the user the whole complexity of
+ // the delayed deletion process between the dictionary pack and Android Keyboard
+ // would only be confusing.
+ case MetadataDbHelper.STATUS_DELETING:
+ case MetadataDbHelper.STATUS_AVAILABLE:
+ return context.getString(R.string.dictionary_available);
+ case MetadataDbHelper.STATUS_DOWNLOADING:
+ return context.getString(R.string.dictionary_downloading);
+ case MetadataDbHelper.STATUS_INSTALLED:
+ return context.getString(R.string.dictionary_installed);
+ case MetadataDbHelper.STATUS_DISABLED:
+ return context.getString(R.string.dictionary_disabled);
+ default:
+ return NO_STATUS_MESSAGE;
}
}
@@ -154,7 +149,7 @@ public final class WordListPreference extends Preference {
{ ButtonSwitcher.STATUS_INSTALL, ACTION_ENABLE_DICT }
};
- private int getButtonSwitcherStatus(final int status) {
+ static int getButtonSwitcherStatus(final int status) {
if (status >= sStatusActionList.length) {
Log.e(TAG, "Unknown status " + status);
return ButtonSwitcher.STATUS_NO_BUTTON;
@@ -162,7 +157,7 @@ public final class WordListPreference extends Preference {
return sStatusActionList[status][0];
}
- private static int getActionIdFromStatusAndMenuEntry(final int status) {
+ static int getActionIdFromStatusAndMenuEntry(final int status) {
if (status >= sStatusActionList.length) {
Log.e(TAG, "Unknown status " + status);
return ACTION_UNKNOWN;
@@ -171,9 +166,10 @@ public final class WordListPreference extends Preference {
}
private void disableDict() {
- SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext);
+ final Context context = getContext();
+ final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context);
CommonPreferences.disable(prefs, mWordlistId);
- UpdateHandler.markAsUnused(mContext, mClientId, mWordlistId, mVersion, mStatus);
+ UpdateHandler.markAsUnused(context, mClientId, mWordlistId, mVersion, mStatus);
if (MetadataDbHelper.STATUS_DOWNLOADING == mStatus) {
setStatus(MetadataDbHelper.STATUS_AVAILABLE);
} else if (MetadataDbHelper.STATUS_INSTALLED == mStatus) {
@@ -184,11 +180,13 @@ public final class WordListPreference extends Preference {
Log.e(TAG, "Unexpected state of the word list for disabling " + mStatus);
}
}
+
private void enableDict() {
- SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext);
+ final Context context = getContext();
+ final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context);
CommonPreferences.enable(prefs, mWordlistId);
// Explicit enabling by the user : allow downloading on metered data connection.
- UpdateHandler.markAsUsed(mContext, mClientId, mWordlistId, mVersion, mStatus, true);
+ UpdateHandler.markAsUsed(context, mClientId, mWordlistId, mVersion, mStatus, true);
if (MetadataDbHelper.STATUS_AVAILABLE == mStatus) {
setStatus(MetadataDbHelper.STATUS_DOWNLOADING);
} else if (MetadataDbHelper.STATUS_DISABLED == mStatus
@@ -203,11 +201,13 @@ public final class WordListPreference extends Preference {
Log.e(TAG, "Unexpected state of the word list for enabling " + mStatus);
}
}
+
private void deleteDict() {
- SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext);
+ final Context context = getContext();
+ final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context);
CommonPreferences.disable(prefs, mWordlistId);
setStatus(MetadataDbHelper.STATUS_DELETING);
- UpdateHandler.markAsDeleting(mContext, mClientId, mWordlistId, mVersion, mStatus);
+ UpdateHandler.markAsDeleting(context, mClientId, mWordlistId, mVersion, mStatus);
}
@Override
@@ -225,8 +225,8 @@ public final class WordListPreference extends Preference {
status.setVisibility(showProgressBar ? View.INVISIBLE : View.VISIBLE);
progressBar.setVisibility(showProgressBar ? View.VISIBLE : View.INVISIBLE);
- final ButtonSwitcher buttonSwitcher =
- (ButtonSwitcher)view.findViewById(R.id.wordlist_button_switcher);
+ final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)view.findViewById(
+ R.id.wordlist_button_switcher);
// We need to clear the state of the button switcher, because we reuse views; if we didn't
// reset it would animate from whatever its old state was.
buttonSwitcher.reset(mInterfaceState);
@@ -244,63 +244,67 @@ public final class WordListPreference extends Preference {
// The button is closed.
buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON);
}
- buttonSwitcher.setInternalOnClickListener(mActionButtonClickHandler);
- view.setOnClickListener(mPreferenceClickHandler);
+ buttonSwitcher.setInternalOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ onActionButtonClicked();
+ }
+ });
+ view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ onWordListClicked(v);
+ }
+ });
}
- private class OnWordListPreferenceClick implements View.OnClickListener {
- @Override
- public void onClick(final View v) {
- // Note : v is the preference view
- final ViewParent parent = v.getParent();
- // Just in case something changed in the framework, test for the concrete class
- if (!(parent instanceof ListView)) return;
- final ListView listView = (ListView)parent;
- final int indexToOpen;
- // Close all first, we'll open back any item that needs to be open.
- final boolean wasOpen = mInterfaceState.isOpen(mWordlistId);
- mInterfaceState.closeAll();
- if (wasOpen) {
- // This button being shown. Take note that we don't want to open any button in the
- // loop below.
- indexToOpen = -1;
+ void onWordListClicked(final View v) {
+ // Note : v is the preference view
+ final ViewParent parent = v.getParent();
+ // Just in case something changed in the framework, test for the concrete class
+ if (!(parent instanceof ListView)) return;
+ final ListView listView = (ListView)parent;
+ final int indexToOpen;
+ // Close all first, we'll open back any item that needs to be open.
+ final boolean wasOpen = mInterfaceState.isOpen(mWordlistId);
+ mInterfaceState.closeAll();
+ if (wasOpen) {
+ // This button being shown. Take note that we don't want to open any button in the
+ // loop below.
+ indexToOpen = -1;
+ } else {
+ // This button was not being shown. Open it, and remember the index of this
+ // child as the one to open in the following loop.
+ mInterfaceState.setOpen(mWordlistId, mStatus);
+ indexToOpen = listView.indexOfChild(v);
+ }
+ final int lastDisplayedIndex =
+ listView.getLastVisiblePosition() - listView.getFirstVisiblePosition();
+ // The "lastDisplayedIndex" is actually displayed, hence the <=
+ for (int i = 0; i <= lastDisplayedIndex; ++i) {
+ final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)listView.getChildAt(i)
+ .findViewById(R.id.wordlist_button_switcher);
+ if (i == indexToOpen) {
+ buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus));
} else {
- // This button was not being shown. Open it, and remember the index of this
- // child as the one to open in the following loop.
- mInterfaceState.setOpen(mWordlistId, mStatus);
- indexToOpen = listView.indexOfChild(v);
- }
- final int lastDisplayedIndex =
- listView.getLastVisiblePosition() - listView.getFirstVisiblePosition();
- // The "lastDisplayedIndex" is actually displayed, hence the <=
- for (int i = 0; i <= lastDisplayedIndex; ++i) {
- final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)listView.getChildAt(i)
- .findViewById(R.id.wordlist_button_switcher);
- if (i == indexToOpen) {
- buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus));
- } else {
- buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON);
- }
+ buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON);
}
}
}
- private class OnActionButtonClick implements View.OnClickListener {
- @Override
- public void onClick(final View v) {
- switch (getActionIdFromStatusAndMenuEntry(mStatus)) {
- case ACTION_ENABLE_DICT:
- enableDict();
- break;
- case ACTION_DISABLE_DICT:
- disableDict();
- break;
- case ACTION_DELETE_DICT:
- deleteDict();
- break;
- default:
- Log.e(TAG, "Unknown menu item pressed");
- }
+ void onActionButtonClicked() {
+ switch (getActionIdFromStatusAndMenuEntry(mStatus)) {
+ case ACTION_ENABLE_DICT:
+ enableDict();
+ break;
+ case ACTION_DISABLE_DICT:
+ disableDict();
+ break;
+ case ACTION_DELETE_DICT:
+ deleteDict();
+ break;
+ default:
+ Log.e(TAG, "Unknown menu item pressed");
}
}
}
diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java
index 2d2731f21..d77ece8e6 100644
--- a/java/src/com/android/inputmethod/event/CombinerChain.java
+++ b/java/src/com/android/inputmethod/event/CombinerChain.java
@@ -19,10 +19,9 @@ package com.android.inputmethod.event;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.common.Constants;
import java.util.ArrayList;
-import java.util.HashMap;
import javax.annotation.Nonnull;
@@ -45,13 +44,6 @@ public class CombinerChain {
private SpannableStringBuilder mStateFeedback;
private final ArrayList<Combiner> mCombiners;
- private static final HashMap<String, Class<? extends Combiner>> IMPLEMENTED_COMBINERS =
- new HashMap<>();
- static {
- IMPLEMENTED_COMBINERS.put("MyanmarReordering", MyanmarReordering.class);
- }
- private static final String COMBINER_SPEC_SEPARATOR = ";";
-
/**
* Create an combiner chain.
*
@@ -61,15 +53,11 @@ public class CombinerChain {
* cursor: we'll start after this.
*
* @param initialText The text that has already been combined so far.
- * @param combinerList A list of combiners to be applied in order.
*/
- public CombinerChain(final String initialText, final Combiner... combinerList) {
+ public CombinerChain(final String initialText) {
mCombiners = new ArrayList<>();
// The dead key combiner is always active, and always first
mCombiners.add(new DeadKeyCombiner());
- for (final Combiner combiner : combinerList) {
- mCombiners.add(combiner);
- }
mCombinedText = new StringBuilder(initialText);
mStateFeedback = new SpannableStringBuilder();
}
@@ -97,7 +85,8 @@ public class CombinerChain {
* new event. However it may never be null.
*/
@Nonnull
- public Event processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {
+ public Event processEvent(final ArrayList<Event> previousEvents,
+ @Nonnull final Event newEvent) {
final ArrayList<Event> modifiablePreviousEvents = new ArrayList<>(previousEvents);
Event event = newEvent;
for (final Combiner combiner : mCombiners) {
@@ -145,30 +134,4 @@ public class CombinerChain {
final SpannableStringBuilder s = new SpannableStringBuilder(mCombinedText);
return s.append(mStateFeedback);
}
-
- public static Combiner[] createCombiners(final String spec) {
- if (TextUtils.isEmpty(spec)) {
- return new Combiner[0];
- }
- final String[] combinerDescriptors = spec.split(COMBINER_SPEC_SEPARATOR);
- final Combiner[] combiners = new Combiner[combinerDescriptors.length];
- int i = 0;
- for (final String combinerDescriptor : combinerDescriptors) {
- final Class<? extends Combiner> combinerClass =
- IMPLEMENTED_COMBINERS.get(combinerDescriptor);
- if (null == combinerClass) {
- throw new RuntimeException("Unknown combiner descriptor: " + combinerDescriptor);
- }
- try {
- combiners[i++] = combinerClass.newInstance();
- } catch (InstantiationException e) {
- throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor,
- e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor,
- e);
- }
- }
- return combiners;
- }
}
diff --git a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
index 4f3f4d25f..1a28bb1d5 100644
--- a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
+++ b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
@@ -17,10 +17,11 @@
package com.android.inputmethod.event;
import android.text.TextUtils;
-import android.view.KeyCharacterMap;
+import android.util.SparseIntArray;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.common.Constants;
+import java.text.Normalizer;
import java.util.ArrayList;
import javax.annotation.Nonnull;
@@ -29,9 +30,208 @@ import javax.annotation.Nonnull;
* A combiner that handles dead keys.
*/
public class DeadKeyCombiner implements Combiner {
+
+ private static class Data {
+ // This class data taken from KeyCharacterMap.java.
+
+ /* Characters used to display placeholders for dead keys. */
+ private static final int ACCENT_ACUTE = '\u00B4';
+ private static final int ACCENT_BREVE = '\u02D8';
+ private static final int ACCENT_CARON = '\u02C7';
+ private static final int ACCENT_CEDILLA = '\u00B8';
+ private static final int ACCENT_CIRCUMFLEX = '\u02C6';
+ private static final int ACCENT_COMMA_ABOVE = '\u1FBD';
+ private static final int ACCENT_COMMA_ABOVE_RIGHT = '\u02BC';
+ private static final int ACCENT_DOT_ABOVE = '\u02D9';
+ private static final int ACCENT_DOT_BELOW = Constants.CODE_PERIOD; // approximate
+ private static final int ACCENT_DOUBLE_ACUTE = '\u02DD';
+ private static final int ACCENT_GRAVE = '\u02CB';
+ private static final int ACCENT_HOOK_ABOVE = '\u02C0';
+ private static final int ACCENT_HORN = Constants.CODE_SINGLE_QUOTE; // approximate
+ private static final int ACCENT_MACRON = '\u00AF';
+ private static final int ACCENT_MACRON_BELOW = '\u02CD';
+ private static final int ACCENT_OGONEK = '\u02DB';
+ private static final int ACCENT_REVERSED_COMMA_ABOVE = '\u02BD';
+ private static final int ACCENT_RING_ABOVE = '\u02DA';
+ private static final int ACCENT_STROKE = Constants.CODE_DASH; // approximate
+ private static final int ACCENT_TILDE = '\u02DC';
+ private static final int ACCENT_TURNED_COMMA_ABOVE = '\u02BB';
+ private static final int ACCENT_UMLAUT = '\u00A8';
+ private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8';
+ private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC';
+
+ /* Legacy dead key display characters used in previous versions of the API (before L)
+ * We still support these characters by mapping them to their non-legacy version. */
+ private static final int ACCENT_GRAVE_LEGACY = Constants.CODE_GRAVE_ACCENT;
+ private static final int ACCENT_CIRCUMFLEX_LEGACY = Constants.CODE_CIRCUMFLEX_ACCENT;
+ private static final int ACCENT_TILDE_LEGACY = Constants.CODE_TILDE;
+
+ /**
+ * Maps Unicode combining diacritical to display-form dead key.
+ */
+ static final SparseIntArray sCombiningToAccent = new SparseIntArray();
+ static final SparseIntArray sAccentToCombining = new SparseIntArray();
+ static {
+ // U+0300: COMBINING GRAVE ACCENT
+ addCombining('\u0300', ACCENT_GRAVE);
+ // U+0301: COMBINING ACUTE ACCENT
+ addCombining('\u0301', ACCENT_ACUTE);
+ // U+0302: COMBINING CIRCUMFLEX ACCENT
+ addCombining('\u0302', ACCENT_CIRCUMFLEX);
+ // U+0303: COMBINING TILDE
+ addCombining('\u0303', ACCENT_TILDE);
+ // U+0304: COMBINING MACRON
+ addCombining('\u0304', ACCENT_MACRON);
+ // U+0306: COMBINING BREVE
+ addCombining('\u0306', ACCENT_BREVE);
+ // U+0307: COMBINING DOT ABOVE
+ addCombining('\u0307', ACCENT_DOT_ABOVE);
+ // U+0308: COMBINING DIAERESIS
+ addCombining('\u0308', ACCENT_UMLAUT);
+ // U+0309: COMBINING HOOK ABOVE
+ addCombining('\u0309', ACCENT_HOOK_ABOVE);
+ // U+030A: COMBINING RING ABOVE
+ addCombining('\u030A', ACCENT_RING_ABOVE);
+ // U+030B: COMBINING DOUBLE ACUTE ACCENT
+ addCombining('\u030B', ACCENT_DOUBLE_ACUTE);
+ // U+030C: COMBINING CARON
+ addCombining('\u030C', ACCENT_CARON);
+ // U+030D: COMBINING VERTICAL LINE ABOVE
+ addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE);
+ // U+030E: COMBINING DOUBLE VERTICAL LINE ABOVE
+ //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE);
+ // U+030F: COMBINING DOUBLE GRAVE ACCENT
+ //addCombining('\u030F', ACCENT_DOUBLE_GRAVE);
+ // U+0310: COMBINING CANDRABINDU
+ //addCombining('\u0310', ACCENT_CANDRABINDU);
+ // U+0311: COMBINING INVERTED BREVE
+ //addCombining('\u0311', ACCENT_INVERTED_BREVE);
+ // U+0312: COMBINING TURNED COMMA ABOVE
+ addCombining('\u0312', ACCENT_TURNED_COMMA_ABOVE);
+ // U+0313: COMBINING COMMA ABOVE
+ addCombining('\u0313', ACCENT_COMMA_ABOVE);
+ // U+0314: COMBINING REVERSED COMMA ABOVE
+ addCombining('\u0314', ACCENT_REVERSED_COMMA_ABOVE);
+ // U+0315: COMBINING COMMA ABOVE RIGHT
+ addCombining('\u0315', ACCENT_COMMA_ABOVE_RIGHT);
+ // U+031B: COMBINING HORN
+ addCombining('\u031B', ACCENT_HORN);
+ // U+0323: COMBINING DOT BELOW
+ addCombining('\u0323', ACCENT_DOT_BELOW);
+ // U+0326: COMBINING COMMA BELOW
+ //addCombining('\u0326', ACCENT_COMMA_BELOW);
+ // U+0327: COMBINING CEDILLA
+ addCombining('\u0327', ACCENT_CEDILLA);
+ // U+0328: COMBINING OGONEK
+ addCombining('\u0328', ACCENT_OGONEK);
+ // U+0329: COMBINING VERTICAL LINE BELOW
+ addCombining('\u0329', ACCENT_VERTICAL_LINE_BELOW);
+ // U+0331: COMBINING MACRON BELOW
+ addCombining('\u0331', ACCENT_MACRON_BELOW);
+ // U+0335: COMBINING SHORT STROKE OVERLAY
+ addCombining('\u0335', ACCENT_STROKE);
+ // U+0342: COMBINING GREEK PERISPOMENI
+ //addCombining('\u0342', ACCENT_PERISPOMENI);
+ // U+0344: COMBINING GREEK DIALYTIKA TONOS
+ //addCombining('\u0344', ACCENT_DIALYTIKA_TONOS);
+ // U+0345: COMBINING GREEK YPOGEGRAMMENI
+ //addCombining('\u0345', ACCENT_YPOGEGRAMMENI);
+
+ // One-way mappings to equivalent preferred accents.
+ // U+0340: COMBINING GRAVE TONE MARK
+ sCombiningToAccent.append('\u0340', ACCENT_GRAVE);
+ // U+0341: COMBINING ACUTE TONE MARK
+ sCombiningToAccent.append('\u0341', ACCENT_ACUTE);
+ // U+0343: COMBINING GREEK KORONIS
+ sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE);
+
+ // One-way legacy mappings to preserve compatibility with older applications.
+ // U+0300: COMBINING GRAVE ACCENT
+ sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300');
+ // U+0302: COMBINING CIRCUMFLEX ACCENT
+ sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302');
+ // U+0303: COMBINING TILDE
+ sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303');
+ }
+
+ private static void addCombining(int combining, int accent) {
+ sCombiningToAccent.append(combining, accent);
+ sAccentToCombining.append(accent, combining);
+ }
+
+ // Caution! This may only contain chars, not supplementary code points. It's unlikely
+ // it will ever need to, but if it does we'll have to change this
+ private static final SparseIntArray sNonstandardDeadCombinations = new SparseIntArray();
+ static {
+ // Non-standard decompositions.
+ // Stroke modifier for Finnish multilingual keyboard and others.
+ // U+0110: LATIN CAPITAL LETTER D WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'D', '\u0110');
+ // U+01E4: LATIN CAPITAL LETTER G WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'G', '\u01e4');
+ // U+0126: LATIN CAPITAL LETTER H WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'H', '\u0126');
+ // U+0197: LATIN CAPITAL LETTER I WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'I', '\u0197');
+ // U+0141: LATIN CAPITAL LETTER L WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'L', '\u0141');
+ // U+00D8: LATIN CAPITAL LETTER O WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'O', '\u00d8');
+ // U+0166: LATIN CAPITAL LETTER T WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'T', '\u0166');
+ // U+0111: LATIN SMALL LETTER D WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'd', '\u0111');
+ // U+01E5: LATIN SMALL LETTER G WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'g', '\u01e5');
+ // U+0127: LATIN SMALL LETTER H WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'h', '\u0127');
+ // U+0268: LATIN SMALL LETTER I WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'i', '\u0268');
+ // U+0142: LATIN SMALL LETTER L WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'l', '\u0142');
+ // U+00F8: LATIN SMALL LETTER O WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'o', '\u00f8');
+ // U+0167: LATIN SMALL LETTER T WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 't', '\u0167');
+ }
+
+ private static void addNonStandardDeadCombination(final int deadCodePoint,
+ final int spacingCodePoint, final int result) {
+ final int combination = (deadCodePoint << 16) | spacingCodePoint;
+ sNonstandardDeadCombinations.put(combination, result);
+ }
+
+ public static final int NOT_A_CHAR = 0;
+ public static final int BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION = 16;
+ // Get a non-standard combination
+ public static char getNonstandardCombination(final int deadCodePoint,
+ final int spacingCodePoint) {
+ final int combination = spacingCodePoint |
+ (deadCodePoint << BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION);
+ return (char)sNonstandardDeadCombinations.get(combination, NOT_A_CHAR);
+ }
+ }
+
// TODO: make this a list of events instead
final StringBuilder mDeadSequence = new StringBuilder();
+ @Nonnull
+ private static Event createEventChainFromSequence(final @Nonnull CharSequence text,
+ @Nonnull final Event originalEvent) {
+ int index = text.length();
+ if (index <= 0) {
+ return originalEvent;
+ }
+ Event lastEvent = null;
+ do {
+ final int codePoint = Character.codePointBefore(text, index);
+ lastEvent = Event.createHardwareKeypressEvent(codePoint,
+ originalEvent.mKeyCode, lastEvent, false /* isKeyRepeat */);
+ index -= Character.charCount(codePoint);
+ } while (index > 0);
+ return lastEvent;
+ }
+
@Override
@Nonnull
public Event processEvent(final ArrayList<Event> previousEvents, final Event event) {
@@ -46,32 +246,49 @@ public class DeadKeyCombiner implements Combiner {
// no dead keys at all in the current input, so this combiner has nothing to do and
// simply returns the event as is. The majority of events will go through this path.
return event;
- } else {
- // TODO: Allow combining for several dead chars rather than only the first one.
- // The framework doesn't know how to do this now.
- final int deadCodePoint = mDeadSequence.codePointAt(0);
+ }
+ if (Character.isWhitespace(event.mCodePoint)
+ || event.mCodePoint == mDeadSequence.codePointBefore(mDeadSequence.length())) {
+ // When whitespace or twice the same dead key, we should output the dead sequence as is.
+ final Event resultEvent = createEventChainFromSequence(mDeadSequence.toString(),
+ event);
mDeadSequence.setLength(0);
- final int resultingCodePoint =
- KeyCharacterMap.getDeadChar(deadCodePoint, event.mCodePoint);
- if (0 == resultingCodePoint) {
- // We can't combine both characters. We need to commit the dead key as a separate
- // character, and the next char too unless it's a space (because as a special case,
- // dead key + space should result in only the dead key being committed - that's
- // how dead keys work).
- // If the event is a space, we should commit the dead char alone, but if it's
- // not, we need to commit both.
- // TODO: this is not necessarily triggered by hardware key events, so it's not
- // a good idea to masquerade as one. This should be typed as a software
- // composite event or something.
- return Event.createHardwareKeypressEvent(deadCodePoint, event.mKeyCode,
- Constants.CODE_SPACE == event.mCodePoint ? null : event /* next */,
- false /* isKeyRepeat */);
+ return resultEvent;
+ }
+ if (event.isFunctionalKeyEvent()) {
+ if (Constants.CODE_DELETE == event.mKeyCode) {
+ // Remove the last code point
+ final int trimIndex = mDeadSequence.length() - Character.charCount(
+ mDeadSequence.codePointBefore(mDeadSequence.length()));
+ mDeadSequence.setLength(trimIndex);
+ return Event.createConsumedEvent(event);
+ }
+ return event;
+ }
+ if (event.isDead()) {
+ mDeadSequence.appendCodePoint(event.mCodePoint);
+ return Event.createConsumedEvent(event);
+ }
+ // Combine normally.
+ final StringBuilder sb = new StringBuilder();
+ sb.appendCodePoint(event.mCodePoint);
+ int codePointIndex = 0;
+ while (codePointIndex < mDeadSequence.length()) {
+ final int deadCodePoint = mDeadSequence.codePointAt(codePointIndex);
+ final char replacementSpacingChar =
+ Data.getNonstandardCombination(deadCodePoint, event.mCodePoint);
+ if (Data.NOT_A_CHAR != replacementSpacingChar) {
+ sb.setCharAt(0, replacementSpacingChar);
} else {
- // We could combine the characters.
- return Event.createHardwareKeypressEvent(resultingCodePoint, event.mKeyCode,
- null /* next */, false /* isKeyRepeat */);
+ final int combining = Data.sAccentToCombining.get(deadCodePoint);
+ sb.appendCodePoint(0 == combining ? deadCodePoint : combining);
}
+ codePointIndex += Character.isSupplementaryCodePoint(deadCodePoint) ? 2 : 1;
}
+ final String normalizedString = Normalizer.normalize(sb, Normalizer.Form.NFC);
+ final Event resultEvent = createEventChainFromSequence(normalizedString, event);
+ mDeadSequence.setLength(0);
+ return resultEvent;
}
@Override
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index ef5b04747..e3b1afc53 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -16,9 +16,12 @@
package com.android.inputmethod.event;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.annotations.ExternallyReferenced;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
+
+import javax.annotation.Nonnull;
/**
* Class representing a generic input event as handled by Latin IME.
@@ -55,6 +58,8 @@ public class Event {
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;
+ // An event corresponding to a cursor move
+ final public static int EVENT_TYPE_CURSOR_MOVE = 7;
// 0 is a valid code point, so we use -1 here.
final public static int NOT_A_CODE_POINT = -1;
@@ -133,12 +138,14 @@ public class Event {
}
}
+ @Nonnull
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);
}
+ @Nonnull
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,
@@ -147,6 +154,8 @@ public class Event {
}
// This creates an input event for a dead character. @see {@link #FLAG_DEAD}
+ @ExternallyReferenced
+ @Nonnull
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,
@@ -161,6 +170,7 @@ public class Event {
* @param codePoint the code point.
* @return an event for this code point.
*/
+ @Nonnull
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,
@@ -176,6 +186,7 @@ public class Event {
* @param y the Y coordinate.
* @return an event for this code point and coordinates.
*/
+ @Nonnull
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.
@@ -187,6 +198,7 @@ public class Event {
* Creates an input event representing the manual pick of a suggestion.
* @return an event for this suggestion pick.
*/
+ @Nonnull
public static Event createSuggestionPickedEvent(final SuggestedWordInfo suggestedWordInfo) {
return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord,
NOT_A_CODE_POINT, NOT_A_KEY_CODE,
@@ -202,6 +214,7 @@ public class Event {
* @param keyCode the key code, or NOT_A_KEYCODE if not applicable.
* @return an event for this text.
*/
+ @Nonnull
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,
@@ -212,6 +225,7 @@ public class Event {
* Creates an input event representing the manual pick of a punctuation suggestion.
* @return an event for this suggestion pick.
*/
+ @Nonnull
public static Event createPunctuationSuggestionPickedEvent(
final SuggestedWordInfo suggestedWordInfo) {
final int primaryCode = suggestedWordInfo.mWord.charAt(0);
@@ -222,10 +236,23 @@ public class Event {
}
/**
+ * Creates an input event representing moving the cursor. The relative move amount is stored
+ * in mX.
+ * @param moveAmount the relative move amount.
+ * @return an event for this cursor move.
+ */
+ @Nonnull
+ public static Event createCursorMovedEvent(final int moveAmount) {
+ return new Event(EVENT_TYPE_CURSOR_MOVE, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
+ moveAmount, Constants.NOT_A_COORDINATE, null, FLAG_NONE, null);
+ }
+
+ /**
* Creates an event identical to the passed event, but that has already been consumed.
* @param source the event to copy the properties of.
* @return an identical event marked as consumed.
*/
+ @Nonnull
public static Event createConsumedEvent(final Event source) {
// A consumed event should not input any text at all, so we pass the empty string as text.
return new Event(source.mEventType, source.mText, source.mCodePoint, source.mKeyCode,
@@ -233,6 +260,7 @@ public class Event {
source.mNextEvent);
}
+ @Nonnull
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,
@@ -277,6 +305,7 @@ public class Event {
case EVENT_TYPE_MODE_KEY:
case EVENT_TYPE_NOT_HANDLED:
case EVENT_TYPE_TOGGLE:
+ case EVENT_TYPE_CURSOR_MOVE:
return "";
case EVENT_TYPE_INPUT_KEYPRESS:
return StringUtils.newSingleCodePointString(mCodePoint);
diff --git a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
index c61f45efa..3a4097d7f 100644
--- a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
+++ b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.event;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.common.Constants;
/**
* A hardware event decoder for a hardware qwerty-ish keyboard.
@@ -67,10 +67,9 @@ public class HardwareKeyboardEventDecoder implements HardwareEventDecoder {
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);
}
+ return Event.createHardwareKeypressEvent(Constants.CODE_ENTER, keyCode,
+ null /* next */, isKeyRepeat);
}
// 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.
diff --git a/java/src/com/android/inputmethod/event/MyanmarReordering.java b/java/src/com/android/inputmethod/event/MyanmarReordering.java
deleted file mode 100644
index dcd06c899..000000000
--- a/java/src/com/android/inputmethod/event/MyanmarReordering.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.event;
-
-import com.android.inputmethod.latin.Constants;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-import javax.annotation.Nonnull;
-
-/**
- * A combiner that reorders input for Myanmar.
- */
-public class MyanmarReordering implements Combiner {
- // U+1031 MYANMAR VOWEL SIGN E
- private final static int VOWEL_E = 0x1031; // Code point for vowel E that we need to reorder
- // U+200C ZERO WIDTH NON-JOINER
- // U+200B ZERO WIDTH SPACE
- private final static int ZERO_WIDTH_NON_JOINER = 0x200B; // should be 0x200C
-
- private final ArrayList<Event> mCurrentEvents = new ArrayList<>();
-
- // List of consonants :
- // U+1000 MYANMAR LETTER KA
- // U+1001 MYANMAR LETTER KHA
- // U+1002 MYANMAR LETTER GA
- // U+1003 MYANMAR LETTER GHA
- // U+1004 MYANMAR LETTER NGA
- // U+1005 MYANMAR LETTER CA
- // U+1006 MYANMAR LETTER CHA
- // U+1007 MYANMAR LETTER JA
- // U+1008 MYANMAR LETTER JHA
- // U+1009 MYANMAR LETTER NYA
- // U+100A MYANMAR LETTER NNYA
- // U+100B MYANMAR LETTER TTA
- // U+100C MYANMAR LETTER TTHA
- // U+100D MYANMAR LETTER DDA
- // U+100E MYANMAR LETTER DDHA
- // U+100F MYANMAR LETTER NNA
- // U+1010 MYANMAR LETTER TA
- // U+1011 MYANMAR LETTER THA
- // U+1012 MYANMAR LETTER DA
- // U+1013 MYANMAR LETTER DHA
- // U+1014 MYANMAR LETTER NA
- // U+1015 MYANMAR LETTER PA
- // U+1016 MYANMAR LETTER PHA
- // U+1017 MYANMAR LETTER BA
- // U+1018 MYANMAR LETTER BHA
- // U+1019 MYANMAR LETTER MA
- // U+101A MYANMAR LETTER YA
- // U+101B MYANMAR LETTER RA
- // U+101C MYANMAR LETTER LA
- // U+101D MYANMAR LETTER WA
- // U+101E MYANMAR LETTER SA
- // U+101F MYANMAR LETTER HA
- // U+1020 MYANMAR LETTER LLA
- // U+103F MYANMAR LETTER GREAT SA
- private static boolean isConsonant(final int codePoint) {
- return (codePoint >= 0x1000 && codePoint <= 0x1020) || 0x103F == codePoint;
- }
-
- // List of medials :
- // U+103B MYANMAR CONSONANT SIGN MEDIAL YA
- // U+103C MYANMAR CONSONANT SIGN MEDIAL RA
- // U+103D MYANMAR CONSONANT SIGN MEDIAL WA
- // U+103E MYANMAR CONSONANT SIGN MEDIAL HA
- // U+105E MYANMAR CONSONANT SIGN MON MEDIAL NA
- // U+105F MYANMAR CONSONANT SIGN MON MEDIAL MA
- // U+1060 MYANMAR CONSONANT SIGN MON MEDIAL LA
- // U+1082 MYANMAR CONSONANT SIGN SHAN MEDIAL WA
- private static int[] MEDIAL_LIST = { 0x103B, 0x103C, 0x103D, 0x103E,
- 0x105E, 0x105F, 0x1060, 0x1082};
- private static boolean isMedial(final int codePoint) {
- return Arrays.binarySearch(MEDIAL_LIST, codePoint) >= 0;
- }
-
- private static boolean isConsonantOrMedial(final int codePoint) {
- return isConsonant(codePoint) || isMedial(codePoint);
- }
-
- private Event getLastEvent() {
- final int size = mCurrentEvents.size();
- if (size <= 0) {
- return null;
- }
- return mCurrentEvents.get(size - 1);
- }
-
- private CharSequence getCharSequence() {
- final StringBuilder s = new StringBuilder();
- for (final Event e : mCurrentEvents) {
- s.appendCodePoint(e.mCodePoint);
- }
- return s;
- }
-
- /**
- * Clears the currently combining stream of events and returns the resulting software text
- * event corresponding to the stream. Optionally adds a new event to the cleared stream.
- * @param newEvent the new event to add to the stream. null if none.
- * @return the resulting software text event. Never null.
- */
- private Event clearAndGetResultingEvent(final Event newEvent) {
- final CharSequence combinedText;
- if (mCurrentEvents.size() > 0) {
- combinedText = getCharSequence();
- mCurrentEvents.clear();
- } else {
- combinedText = null;
- }
- if (null != newEvent) {
- mCurrentEvents.add(newEvent);
- }
- return null == combinedText ? Event.createConsumedEvent(newEvent)
- : Event.createSoftwareTextEvent(combinedText, Event.NOT_A_KEY_CODE);
- }
-
- @Override
- @Nonnull
- public Event processEvent(ArrayList<Event> previousEvents, Event newEvent) {
- final int codePoint = newEvent.mCodePoint;
- if (VOWEL_E == codePoint) {
- final Event lastEvent = getLastEvent();
- if (null == lastEvent) {
- mCurrentEvents.add(newEvent);
- return Event.createConsumedEvent(newEvent);
- } else if (isConsonantOrMedial(lastEvent.mCodePoint)) {
- final Event resultingEvent = clearAndGetResultingEvent(null);
- mCurrentEvents.add(Event.createSoftwareKeypressEvent(ZERO_WIDTH_NON_JOINER,
- Event.NOT_A_KEY_CODE,
- Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
- false /* isKeyRepeat */));
- mCurrentEvents.add(newEvent);
- return resultingEvent;
- } else { // VOWEL_E == lastCodePoint. But if that was anything else this is correct too.
- return clearAndGetResultingEvent(newEvent);
- }
- } if (isConsonant(codePoint)) {
- final Event lastEvent = getLastEvent();
- if (null == lastEvent) {
- mCurrentEvents.add(newEvent);
- return Event.createConsumedEvent(newEvent);
- } else if (VOWEL_E == lastEvent.mCodePoint) {
- final int eventSize = mCurrentEvents.size();
- if (eventSize >= 2
- && mCurrentEvents.get(eventSize - 2).mCodePoint == ZERO_WIDTH_NON_JOINER) {
- // We have a ZWJN before a vowel E. We need to remove the ZWNJ and then
- // reorder the vowel with respect to the consonant.
- mCurrentEvents.remove(eventSize - 1);
- mCurrentEvents.remove(eventSize - 2);
- mCurrentEvents.add(newEvent);
- mCurrentEvents.add(lastEvent);
- return Event.createConsumedEvent(newEvent);
- }
- // If there is already a consonant, then we are starting a new syllable.
- for (int i = eventSize - 2; i >= 0; --i) {
- if (isConsonant(mCurrentEvents.get(i).mCodePoint)) {
- return clearAndGetResultingEvent(newEvent);
- }
- }
- // If we come here, we didn't have a consonant so we reorder
- mCurrentEvents.remove(eventSize - 1);
- mCurrentEvents.add(newEvent);
- mCurrentEvents.add(lastEvent);
- return Event.createConsumedEvent(newEvent);
- } else { // lastCodePoint is a consonant/medial. But if it's something else it's fine
- return clearAndGetResultingEvent(newEvent);
- }
- } else if (isMedial(codePoint)) {
- final Event lastEvent = getLastEvent();
- if (null == lastEvent) {
- mCurrentEvents.add(newEvent);
- return Event.createConsumedEvent(newEvent);
- } else if (VOWEL_E == lastEvent.mCodePoint) {
- final int eventSize = mCurrentEvents.size();
- // If there is already a consonant, then we are in the middle of a syllable, and we
- // need to reorder.
- boolean hasConsonant = false;
- for (int i = eventSize - 2; i >= 0; --i) {
- if (isConsonant(mCurrentEvents.get(i).mCodePoint)) {
- hasConsonant = true;
- break;
- }
- }
- if (hasConsonant) {
- mCurrentEvents.remove(eventSize - 1);
- mCurrentEvents.add(newEvent);
- mCurrentEvents.add(lastEvent);
- return Event.createConsumedEvent(newEvent);
- }
- // Otherwise, we just commit everything.
- return clearAndGetResultingEvent(null);
- } else { // lastCodePoint is a consonant/medial. But if it's something else it's fine
- return clearAndGetResultingEvent(newEvent);
- }
- } else if (Constants.CODE_DELETE == newEvent.mKeyCode) {
- final Event lastEvent = getLastEvent();
- final int eventSize = mCurrentEvents.size();
- if (null != lastEvent) {
- if (VOWEL_E == lastEvent.mCodePoint) {
- // We have a VOWEL_E at the end. There are four cases.
- // - The vowel is the only code point in the buffer. Remove it.
- // - The vowel is preceded by a ZWNJ. Remove both vowel E and ZWNJ.
- // - The vowel is preceded by a consonant/medial, remove the consonant/medial.
- // - In all other cases, it's strange, so just remove the last code point.
- if (eventSize <= 1) {
- mCurrentEvents.clear();
- } else { // eventSize >= 2
- final int previousCodePoint = mCurrentEvents.get(eventSize - 2).mCodePoint;
- if (previousCodePoint == ZERO_WIDTH_NON_JOINER) {
- mCurrentEvents.remove(eventSize - 1);
- mCurrentEvents.remove(eventSize - 2);
- } else if (isConsonantOrMedial(previousCodePoint)) {
- mCurrentEvents.remove(eventSize - 2);
- } else {
- mCurrentEvents.remove(eventSize - 1);
- }
- }
- return Event.createConsumedEvent(newEvent);
- } else if (eventSize > 0) {
- mCurrentEvents.remove(eventSize - 1);
- return Event.createConsumedEvent(newEvent);
- }
- }
- }
- // This character is not part of the combining scheme, so we should reset everything.
- if (mCurrentEvents.size() > 0) {
- // If we have events in flight, then add the new event and return the resulting event.
- mCurrentEvents.add(newEvent);
- return clearAndGetResultingEvent(null);
- } else {
- // If we don't have any events in flight, then just pass this one through.
- return newEvent;
- }
- }
-
- @Override
- public CharSequence getCombiningStateFeedback() {
- return getCharSequence();
- }
-
- @Override
- public void reset() {
- mCurrentEvents.clear();
- }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 81ea90a4d..299d1b7c5 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -17,10 +17,10 @@
package com.android.inputmethod.keyboard;
import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED;
-import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT;
-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 static com.android.inputmethod.latin.common.Constants.CODE_OUTPUT_TEXT;
+import static com.android.inputmethod.latin.common.Constants.CODE_SHIFT;
+import static com.android.inputmethod.latin.common.Constants.CODE_SWITCH_ALPHA_SYMBOL;
+import static com.android.inputmethod.latin.common.Constants.CODE_UNSPECIFIED;
import android.content.res.TypedArray;
import android.graphics.Rect;
@@ -36,13 +36,16 @@ import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.keyboard.internal.KeyboardRow;
import com.android.inputmethod.keyboard.internal.MoreKeySpec;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
import java.util.Arrays;
import java.util.Locale;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* Class for describing the position and characteristics of a single key in the keyboard.
*/
@@ -94,18 +97,30 @@ public class Key implements Comparable<Key> {
/** Icon to display instead of a label. Icon takes precedence over a label */
private final int mIconId;
- /** Width of the key, not including the gap */
+ /** Width of the key, excluding the gap */
private final int mWidth;
- /** Height of the key, not including the gap */
+ /** Height of the key, excluding the gap */
private final int mHeight;
- /** X coordinate of the key in the keyboard layout */
+ /**
+ * The combined width in pixels of the horizontal gaps belonging to this key, both to the left
+ * and to the right. I.e., mWidth + mHorizontalGap = total width belonging to the key.
+ */
+ private final int mHorizontalGap;
+ /**
+ * The combined height in pixels of the vertical gaps belonging to this key, both above and
+ * below. I.e., mHeight + mVerticalGap = total height belonging to the key.
+ */
+ private final int mVerticalGap;
+ /** X coordinate of the top-left corner of the key in the keyboard layout, excluding the gap. */
private final int mX;
- /** Y coordinate of the key in the keyboard layout */
+ /** Y coordinate of the top-left corner of the key in the keyboard layout, excluding the gap. */
private final int mY;
/** Hit bounding box of the key */
+ @Nonnull
private final Rect mHitBox = new Rect();
/** More keys. It is guaranteed that this is null or an array of one or more elements */
+ @Nullable
private final MoreKeySpec[] mMoreKeys;
/** More keys column number and flags */
private final int mMoreKeysColumnAndFlags;
@@ -148,8 +163,9 @@ public class Key implements Comparable<Key> {
private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
+ @Nullable
private final KeyVisualAttributes mKeyVisualAttributes;
-
+ @Nullable
private final OptionalAttributes mOptionalAttributes;
private static final class OptionalAttributes {
@@ -171,6 +187,7 @@ public class Key implements Comparable<Key> {
mVisualInsetsRight = visualInsetsRight;
}
+ @Nullable
public static OptionalAttributes newInstance(final String outputText, final int altCode,
final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight) {
if (outputText == null && altCode == CODE_UNSPECIFIED
@@ -194,12 +211,14 @@ public class Key implements Comparable<Key> {
* Constructor for a key on <code>MoreKeyKeyboard</code>, on <code>MoreSuggestions</code>,
* and in a <GridRows/>.
*/
- 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;
+ public Key(@Nullable final String label, final int iconId, final int code,
+ @Nullable final String outputText, @Nullable 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) {
mWidth = width - horizontalGap;
+ mHeight = height - verticalGap;
+ mHorizontalGap = horizontalGap;
+ mVerticalGap = verticalGap;
mHintLabel = hintLabel;
mLabelFlags = labelFlags;
mBackgroundType = backgroundType;
@@ -214,7 +233,7 @@ public class Key implements Comparable<Key> {
mEnabled = (code != CODE_UNSPECIFIED);
mIconId = iconId;
// Horizontal gap is divided equally to both sides of the key.
- mX = x + horizontalGap / 2;
+ mX = x + mHorizontalGap / 2;
mY = y;
mHitBox.set(x, y, x + width + 1, y + height);
mKeyVisualAttributes = null;
@@ -233,20 +252,24 @@ public class Key implements Comparable<Key> {
* @param row the row that this key belongs to. row's x-coordinate will be the right edge of
* this key.
*/
- public Key(final String keySpec, final TypedArray keyAttr, final KeyStyle style,
- final KeyboardParams params, final KeyboardRow row) {
- final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
+ public Key(@Nullable final String keySpec, @Nonnull final TypedArray keyAttr,
+ @Nonnull final KeyStyle style, @Nonnull final KeyboardParams params,
+ @Nonnull final KeyboardRow row) {
+ mHorizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
+ mVerticalGap = params.mVerticalGap;
+
+ final float horizontalGapFloat = mHorizontalGap;
final int rowHeight = row.getRowHeight();
- mHeight = rowHeight - params.mVerticalGap;
+ mHeight = rowHeight - mVerticalGap;
final float keyXPos = row.getKeyX(keyAttr);
final float keyWidth = row.getKeyWidth(keyAttr, keyXPos);
final int keyYPos = row.getKeyY();
// Horizontal gap is divided equally to both sides of the key.
- mX = Math.round(keyXPos + horizontalGap / 2);
+ mX = Math.round(keyXPos + horizontalGapFloat / 2);
mY = keyYPos;
- mWidth = Math.round(keyWidth - horizontalGap);
+ mWidth = Math.round(keyWidth - horizontalGapFloat);
mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1,
keyYPos + rowHeight);
// Update row to have current x coordinate.
@@ -263,8 +286,8 @@ public class Key implements Comparable<Key> {
mLabelFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
| row.getDefaultKeyLabelFlags();
- final boolean needsToUpperCase = needsToUpperCase(mLabelFlags, params.mId.mElementId);
- final Locale locale = params.mId.mLocale;
+ final boolean needsToUpcase = needsToUpcase(mLabelFlags, params.mId.mElementId);
+ final Locale localeForUpcasing = params.mId.getLocale();
int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
@@ -306,7 +329,7 @@ public class Key implements Comparable<Key> {
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);
+ mMoreKeys[i] = new MoreKeySpec(moreKeys[i], needsToUpcase, localeForUpcasing);
}
} else {
mMoreKeys = null;
@@ -326,17 +349,24 @@ public class Key implements Comparable<Key> {
// code point nor as a surrogate pair.
mLabel = new StringBuilder().appendCodePoint(code).toString();
} else {
- mLabel = StringUtils.toUpperCaseOfStringForLocale(
- KeySpecParser.getLabel(keySpec), needsToUpperCase, locale);
+ final String label = KeySpecParser.getLabel(keySpec);
+ mLabel = needsToUpcase
+ ? StringUtils.toTitleCaseOfKeyLabel(label, localeForUpcasing)
+ : label;
}
if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) {
mHintLabel = null;
} else {
- mHintLabel = StringUtils.toUpperCaseOfStringForLocale(style.getString(keyAttr,
- R.styleable.Keyboard_Key_keyHintLabel), needsToUpperCase, locale);
+ final String hintLabel = style.getString(
+ keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
+ mHintLabel = needsToUpcase
+ ? StringUtils.toTitleCaseOfKeyLabel(hintLabel, localeForUpcasing)
+ : hintLabel;
+ }
+ String outputText = KeySpecParser.getOutputText(keySpec);
+ if (needsToUpcase) {
+ outputText = StringUtils.toTitleCaseOfKeyLabel(outputText, localeForUpcasing);
}
- 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)) {
@@ -362,12 +392,14 @@ public class Key implements Comparable<Key> {
mCode = CODE_OUTPUT_TEXT;
}
} else {
- mCode = StringUtils.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale);
+ mCode = needsToUpcase ? StringUtils.toTitleCaseOfKeyCode(code, localeForUpcasing)
+ : code;
}
final int altCodeInAttr = KeySpecParser.parseCode(
style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), CODE_UNSPECIFIED);
- final int altCode = StringUtils.toUpperCaseOfCodeForLocale(
- altCodeInAttr, needsToUpperCase, locale);
+ final int altCode = needsToUpcase
+ ? StringUtils.toTitleCaseOfKeyCode(altCodeInAttr, localeForUpcasing)
+ : altCodeInAttr;
mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode,
disabledIconId, visualInsetsLeft, visualInsetsRight);
mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
@@ -379,7 +411,11 @@ public class Key implements Comparable<Key> {
*
* @param key the original key.
*/
- protected Key(final Key key) {
+ protected Key(@Nonnull final Key key) {
+ this(key, key.mMoreKeys);
+ }
+
+ private Key(@Nonnull final Key key, @Nullable final MoreKeySpec[] moreKeys) {
// Final attributes.
mCode = key.mCode;
mLabel = key.mLabel;
@@ -388,10 +424,12 @@ public class Key implements Comparable<Key> {
mIconId = key.mIconId;
mWidth = key.mWidth;
mHeight = key.mHeight;
+ mHorizontalGap = key.mHorizontalGap;
+ mVerticalGap = key.mVerticalGap;
mX = key.mX;
mY = key.mY;
mHitBox.set(key.mHitBox);
- mMoreKeys = key.mMoreKeys;
+ mMoreKeys = moreKeys;
mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags;
mBackgroundType = key.mBackgroundType;
mActionFlags = key.mActionFlags;
@@ -403,7 +441,16 @@ public class Key implements Comparable<Key> {
mEnabled = key.mEnabled;
}
- private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) {
+ @Nonnull
+ public static Key removeRedundantMoreKeys(@Nonnull final Key key,
+ @Nonnull final MoreKeySpec.LettersOnBaseLayout lettersOnBaseLayout) {
+ final MoreKeySpec[] moreKeys = key.getMoreKeys();
+ final MoreKeySpec[] filteredMoreKeys = MoreKeySpec.removeRedundantMoreKeys(
+ moreKeys, lettersOnBaseLayout);
+ return (filteredMoreKeys == moreKeys) ? key : new Key(key, filteredMoreKeys);
+ }
+
+ private static boolean needsToUpcase(final int labelFlags, final int keyboardElementId) {
if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
switch (keyboardElementId) {
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
@@ -516,14 +563,17 @@ public class Key implements Comparable<Key> {
return mCode;
}
+ @Nullable
public String getLabel() {
return mLabel;
}
+ @Nullable
public String getHintLabel() {
return mHintLabel;
}
+ @Nullable
public MoreKeySpec[] getMoreKeys() {
return mMoreKeys;
}
@@ -582,6 +632,7 @@ public class Key implements Comparable<Key> {
return mKeyVisualAttributes;
}
+ @Nonnull
public final Typeface selectTypeface(final KeyDrawParams params) {
switch (mLabelFlags & LABEL_FLAGS_FONT_MASK) {
case LABEL_FLAGS_FONT_NORMAL:
@@ -658,6 +709,7 @@ public class Key implements Comparable<Key> {
return params.mLetterSize;
}
+ @Nonnull
public Typeface selectPreviewTypeface(final KeyDrawParams params) {
if (previewHasLetterSize()) {
return selectTypeface(params);
@@ -742,6 +794,7 @@ public class Key implements Comparable<Key> {
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0;
}
+ @Nullable
public final String getOutputText() {
final OptionalAttributes attrs = mOptionalAttributes;
return (attrs != null) ? attrs.mOutputText : null;
@@ -756,6 +809,7 @@ public class Key implements Comparable<Key> {
return mIconId;
}
+ @Nullable
public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
final OptionalAttributes attrs = mOptionalAttributes;
final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED;
@@ -767,22 +821,57 @@ public class Key implements Comparable<Key> {
return icon;
}
+ @Nullable
public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) {
return iconSet.getIconDrawable(getIconId());
}
+ /**
+ * Gets the width of the key in pixels, excluding the gap.
+ * @return The width of the key in pixels, excluding the gap.
+ */
public int getWidth() {
return mWidth;
}
+ /**
+ * Gets the height of the key in pixels, excluding the gap.
+ * @return The height of the key in pixels, excluding the gap.
+ */
public int getHeight() {
return mHeight;
}
+ /**
+ * The combined width in pixels of the horizontal gaps belonging to this key, both above and
+ * below. I.e., getWidth() + getHorizontalGap() = total width belonging to the key.
+ * @return Horizontal gap belonging to this key.
+ */
+ public int getHorizontalGap() {
+ return mHorizontalGap;
+ }
+
+ /**
+ * The combined height in pixels of the vertical gaps belonging to this key, both above and
+ * below. I.e., getHeight() + getVerticalGap() = total height belonging to the key.
+ * @return Vertical gap belonging to this key.
+ */
+ public int getVerticalGap() {
+ return mVerticalGap;
+ }
+
+ /**
+ * Gets the x-coordinate of the top-left corner of the key in pixels, excluding the gap.
+ * @return The x-coordinate of the top-left corner of the key in pixels, excluding the gap.
+ */
public int getX() {
return mX;
}
+ /**
+ * Gets the y-coordinate of the top-left corner of the key in pixels, excluding the gap.
+ * @return The y-coordinate of the top-left corner of the key in pixels, excluding the gap.
+ */
public int getY() {
return mY;
}
@@ -825,6 +914,7 @@ public class Key implements Comparable<Key> {
mEnabled = enabled;
}
+ @Nonnull
public Rect getHitBox() {
return mHitBox;
}
@@ -896,8 +986,10 @@ public class Key implements Comparable<Key> {
* @return the background drawable of the key.
* @see android.graphics.drawable.StateListDrawable#setState(int[])
*/
- public final Drawable selectBackgroundDrawable(final Drawable keyBackground,
- final Drawable functionalKeyBackground, final Drawable spacebarBackground) {
+ @Nonnull
+ public final Drawable selectBackgroundDrawable(@Nonnull final Drawable keyBackground,
+ @Nonnull final Drawable functionalKeyBackground,
+ @Nonnull final Drawable spacebarBackground) {
final Drawable background;
if (mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) {
background = functionalKeyBackground;
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 85dfea4e7..7318d4738 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -21,13 +21,16 @@ import android.util.SparseArray;
import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
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.CoordinateUtils;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.CoordinateUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
* consists of rows of keys.
@@ -47,6 +50,7 @@ import java.util.List;
* </pre>
*/
public class Keyboard {
+ @Nonnull
public final KeyboardId mId;
public final int mThemeId;
@@ -78,17 +82,25 @@ public class Keyboard {
public final int mMaxMoreKeysKeyboardColumn;
/** List of keys in this keyboard */
+ @Nonnull
private final List<Key> mSortedKeys;
+ @Nonnull
public final List<Key> mShiftKeys;
+ @Nonnull
public final List<Key> mAltCodeKeysWhileTyping;
+ @Nonnull
public final KeyboardIconsSet mIconsSet;
private final SparseArray<Key> mKeyCache = new SparseArray<>();
+ @Nonnull
private final ProximityInfo mProximityInfo;
+ @Nonnull
+ private final KeyboardLayout mKeyboardLayout;
+
private final boolean mProximityCharsCorrectionEnabled;
- public Keyboard(final KeyboardParams params) {
+ public Keyboard(@Nonnull final KeyboardParams params) {
mId = params.mId;
mThemeId = params.mThemeId;
mOccupiedHeight = params.mOccupiedHeight;
@@ -108,14 +120,15 @@ public class Keyboard {
mAltCodeKeysWhileTyping = Collections.unmodifiableList(params.mAltCodeKeysWhileTyping);
mIconsSet = params.mIconsSet;
- mProximityInfo = new ProximityInfo(params.mId.mLocale.toString(),
- params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
- mMostCommonKeyWidth, mMostCommonKeyHeight, mSortedKeys,
- params.mTouchPositionCorrection);
+ mProximityInfo = new ProximityInfo(params.GRID_WIDTH, params.GRID_HEIGHT,
+ mOccupiedWidth, mOccupiedHeight, mMostCommonKeyWidth, mMostCommonKeyHeight,
+ mSortedKeys, params.mTouchPositionCorrection);
mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
+ mKeyboardLayout = KeyboardLayout.newKeyboardLayout(mSortedKeys, mMostCommonKeyWidth,
+ mMostCommonKeyHeight, mOccupiedWidth, mOccupiedHeight);
}
- protected Keyboard(final Keyboard keyboard) {
+ protected Keyboard(@Nonnull final Keyboard keyboard) {
mId = keyboard.mId;
mThemeId = keyboard.mThemeId;
mOccupiedHeight = keyboard.mOccupiedHeight;
@@ -137,6 +150,7 @@ public class Keyboard {
mProximityInfo = keyboard.mProximityInfo;
mProximityCharsCorrectionEnabled = keyboard.mProximityCharsCorrectionEnabled;
+ mKeyboardLayout = keyboard.mKeyboardLayout;
}
public boolean hasProximityCharsCorrection(final int code) {
@@ -151,20 +165,28 @@ public class Keyboard {
return canAssumeNativeHasProximityCharsInfoOfAllKeys || Character.isLetter(code);
}
+ @Nonnull
public ProximityInfo getProximityInfo() {
return mProximityInfo;
}
+ @Nonnull
+ public KeyboardLayout getKeyboardLayout() {
+ return mKeyboardLayout;
+ }
+
/**
* Return the sorted list of keys of this keyboard.
* The keys are sorted from top-left to bottom-right order.
* The list may contain {@link Key.Spacer} object as well.
* @return the sorted unmodifiable list of {@link Key}s of this keyboard.
*/
+ @Nonnull
public List<Key> getSortedKeys() {
return mSortedKeys;
}
+ @Nullable
public Key getKey(final int code) {
if (code == Constants.CODE_UNSPECIFIED) {
return null;
@@ -186,7 +208,7 @@ public class Keyboard {
}
}
- public boolean hasKey(final Key aKey) {
+ public boolean hasKey(@Nonnull final Key aKey) {
if (mKeyCache.indexOfValue(aKey) >= 0) {
return true;
}
@@ -212,6 +234,7 @@ public class Keyboard {
* @return the list of the nearest keys to the given point. If the given
* point is out of range, then an array of size zero is returned.
*/
+ @Nonnull
public List<Key> getNearestKeys(final int x, final int y) {
// Avoid dead pixels at edges of the keyboard
final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));
@@ -219,7 +242,8 @@ public class Keyboard {
return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
}
- public int[] getCoordinates(final int[] codePoints) {
+ @Nonnull
+ public int[] getCoordinates(@Nonnull final int[] codePoints) {
final int length = codePoints.length;
final int[] coordinates = CoordinateUtils.newCoordinateArray(length);
for (int i = 0; i < length; ++i) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index c565866b7..cdd632bc8 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -16,8 +16,8 @@
package com.android.inputmethod.keyboard;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.InputPointers;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
public interface KeyboardActionListener {
/**
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 3c1167538..a1f7bf0e1 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -16,16 +16,15 @@
package com.android.inputmethod.keyboard;
-import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
import android.text.InputType;
import android.text.TextUtils;
import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.utils.InputTypeUtils;
-import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Arrays;
import java.util.Locale;
@@ -62,8 +61,7 @@ public final class KeyboardId {
public static final int ELEMENT_EMOJI_CATEGORY5 = 15;
public static final int ELEMENT_EMOJI_CATEGORY6 = 16;
- public final InputMethodSubtype mSubtype;
- public final Locale mLocale;
+ public final RichInputMethodSubtype mSubtype;
public final int mWidth;
public final int mHeight;
public final int mMode;
@@ -73,12 +71,12 @@ public final class KeyboardId {
public final boolean mLanguageSwitchKeyEnabled;
public final String mCustomActionLabel;
public final boolean mHasShortcutKey;
+ public final boolean mIsSplitLayout;
private final int mHashCode;
public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) {
mSubtype = params.mSubtype;
- mLocale = SubtypeLocaleUtils.getSubtypeLocale(mSubtype);
mWidth = params.mKeyboardWidth;
mHeight = params.mKeyboardHeight;
mMode = params.mMode;
@@ -89,6 +87,7 @@ public final class KeyboardId {
mCustomActionLabel = (mEditorInfo.actionLabel != null)
? mEditorInfo.actionLabel.toString() : null;
mHasShortcutKey = params.mVoiceInputKeyEnabled;
+ mIsSplitLayout = params.mIsSplitLayoutEnabled;
mHashCode = computeHashCode(this);
}
@@ -108,7 +107,8 @@ public final class KeyboardId {
id.mCustomActionLabel,
id.navigateNext(),
id.navigatePrevious(),
- id.mSubtype
+ id.mSubtype,
+ id.mIsSplitLayout
});
}
@@ -128,7 +128,8 @@ public final class KeyboardId {
&& TextUtils.equals(other.mCustomActionLabel, mCustomActionLabel)
&& other.navigateNext() == navigateNext()
&& other.navigatePrevious() == navigatePrevious()
- && other.mSubtype.equals(mSubtype);
+ && other.mSubtype.equals(mSubtype)
+ && other.mIsSplitLayout == mIsSplitLayout;
}
private static boolean isAlphabetKeyboard(final int elementId) {
@@ -163,6 +164,10 @@ public final class KeyboardId {
return InputTypeUtils.getImeOptionsActionIdFromEditorInfo(mEditorInfo);
}
+ public Locale getLocale() {
+ return mSubtype.getLocale();
+ }
+
@Override
public boolean equals(final Object other) {
return other instanceof KeyboardId && equals((KeyboardId) other);
@@ -175,9 +180,10 @@ public final class KeyboardId {
@Override
public String toString() {
- return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s]",
+ return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s%s]",
elementIdToName(mElementId),
- mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
+ mSubtype.getLocale(),
+ mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
mWidth, mHeight,
modeName(mMode),
actionName(imeAction()),
@@ -187,7 +193,8 @@ public final class KeyboardId {
(passwordInput() ? " passwordInput" : ""),
(mHasShortcutKey ? " hasShortcutKey" : ""),
(mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
- (isMultiLine() ? " isMultiLine" : "")
+ (isMultiLine() ? " isMultiLine" : ""),
+ (mIsSplitLayout ? " isSplitLayout" : "")
);
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayout.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayout.java
new file mode 100644
index 000000000..d0f32078e
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayout.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 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 com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+/**
+ * KeyboardLayout maintains the keyboard layout information.
+ */
+public class KeyboardLayout {
+
+ private final int[] mKeyCodes;
+
+ private final int[] mKeyXCoordinates;
+ private final int[] mKeyYCoordinates;
+
+ private final int[] mKeyWidths;
+ private final int[] mKeyHeights;
+
+ public final int mMostCommonKeyWidth;
+ public final int mMostCommonKeyHeight;
+
+ public final int mKeyboardWidth;
+ public final int mKeyboardHeight;
+
+ public KeyboardLayout(ArrayList<Key> layoutKeys, int mostCommonKeyWidth,
+ int mostCommonKeyHeight, int keyboardWidth, int keyboardHeight) {
+ mMostCommonKeyWidth = mostCommonKeyWidth;
+ mMostCommonKeyHeight = mostCommonKeyHeight;
+ mKeyboardWidth = keyboardWidth;
+ mKeyboardHeight = keyboardHeight;
+
+ mKeyCodes = new int[layoutKeys.size()];
+ mKeyXCoordinates = new int[layoutKeys.size()];
+ mKeyYCoordinates = new int[layoutKeys.size()];
+ mKeyWidths = new int[layoutKeys.size()];
+ mKeyHeights = new int[layoutKeys.size()];
+
+ for (int i = 0; i < layoutKeys.size(); i++) {
+ Key key = layoutKeys.get(i);
+ mKeyCodes[i] = Character.toLowerCase(key.getCode());
+ mKeyXCoordinates[i] = key.getX();
+ mKeyYCoordinates[i] = key.getY();
+ mKeyWidths[i] = key.getWidth();
+ mKeyHeights[i] = key.getHeight();
+ }
+ }
+
+ @UsedForTesting
+ public int[] getKeyCodes() {
+ return mKeyCodes;
+ }
+
+ /**
+ * The x-coordinate for the top-left corner of the keys.
+ *
+ */
+ public int[] getKeyXCoordinates() {
+ return mKeyXCoordinates;
+ }
+
+ /**
+ * The y-coordinate for the top-left corner of the keys.
+ */
+ public int[] getKeyYCoordinates() {
+ return mKeyYCoordinates;
+ }
+
+ /**
+ * The widths of the keys which are smaller than the true hit-area due to the gaps
+ * between keys. The mostCommonKey(Width/Height) represents the true key width/height
+ * including the gaps.
+ */
+ public int[] getKeyWidths() {
+ return mKeyWidths;
+ }
+
+ /**
+ * The heights of the keys which are smaller than the true hit-area due to the gaps
+ * between keys. The mostCommonKey(Width/Height) represents the true key width/height
+ * including the gaps.
+ */
+ public int[] getKeyHeights() {
+ return mKeyHeights;
+ }
+
+ /**
+ * Factory method to create {@link KeyboardLayout} objects.
+ */
+ public static KeyboardLayout newKeyboardLayout(@Nonnull final List<Key> sortedKeys,
+ int mostCommonKeyWidth, int mostCommonKeyHeight,
+ int occupiedWidth, int occupiedHeight) {
+ final ArrayList<Key> layoutKeys = new ArrayList<Key>();
+ for (final Key key : sortedKeys) {
+ if (!ProximityInfo.needsProximityInfo(key)) {
+ continue;
+ }
+ if (key.getCode() != ',') {
+ layoutKeys.add(key);
+ }
+ }
+ return new KeyboardLayout(layoutKeys, mostCommonKeyWidth,
+ mostCommonKeyHeight, occupiedWidth, occupiedHeight);
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index feb79efe9..47013fe9e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -16,8 +16,8 @@
package com.android.inputmethod.keyboard;
-import static com.android.inputmethod.latin.Constants.ImeOption.FORCE_ASCII;
-import static com.android.inputmethod.latin.Constants.ImeOption.NO_SETTINGS_KEY;
+import static com.android.inputmethod.latin.common.Constants.ImeOption.FORCE_ASCII;
+import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_SETTINGS_KEY;
import android.content.Context;
import android.content.res.Resources;
@@ -34,10 +34,10 @@ 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;
+import com.android.inputmethod.keyboard.internal.UniqueKeysCache;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.define.DebugFlags;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
@@ -51,6 +51,9 @@ import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.HashMap;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* This class represents a set of keyboard layouts. Each of them represents a different keyboard
* specific to a keyboard state, such as alphabet, symbols, and so on. Layouts in the same
@@ -60,7 +63,7 @@ import java.util.HashMap;
*/
public final class KeyboardLayoutSet {
private static final String TAG = KeyboardLayoutSet.class.getSimpleName();
- private static final boolean DEBUG_CACHE = DebugFlags.DEBUG_ENABLED;
+ private static final boolean DEBUG_CACHE = false;
private static final String TAG_KEYBOARD_SET = "KeyboardLayoutSet";
private static final String TAG_ELEMENT = "Element";
@@ -69,6 +72,7 @@ public final class KeyboardLayoutSet {
private static final String KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX = "keyboard_layout_set_";
private final Context mContext;
+ @Nonnull
private final Params mParams;
// How many layouts we forcibly keep in cache. This only includes ALPHABET (default) and
@@ -81,7 +85,10 @@ public final class KeyboardLayoutSet {
private static final Keyboard[] sForcibleKeyboardCache = new Keyboard[FORCIBLE_CACHE_SIZE];
private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
new HashMap<>();
- private static final KeysCache sKeysCache = new KeysCache();
+ @Nonnull
+ private static final UniqueKeysCache sUniqueKeysCache = UniqueKeysCache.newInstance();
+ private final static HashMap<InputMethodSubtype, Integer> sScriptIdsForSubtypes =
+ new HashMap<>();
@SuppressWarnings("serial")
public static final class KeyboardLayoutSetException extends RuntimeException {
@@ -96,6 +103,8 @@ public final class KeyboardLayoutSet {
private static final class ElementParams {
int mKeyboardXmlId;
boolean mProximityCharsCorrectionEnabled;
+ boolean mSupportsSplitLayout;
+ boolean mAllowRedundantMoreKeys;
public ElementParams() {}
}
@@ -109,11 +118,17 @@ public final class KeyboardLayoutSet {
boolean mVoiceInputKeyEnabled;
boolean mNoSettingsKey;
boolean mLanguageSwitchKeyEnabled;
- InputMethodSubtype mSubtype;
+ RichInputMethodSubtype mSubtype;
boolean mIsSpellChecker;
int mKeyboardWidth;
int mKeyboardHeight;
int mScriptId = ScriptUtils.SCRIPT_LATIN;
+ // Indicates if the user has enabled the split-layout preference
+ // and the required ProductionFlags are enabled.
+ boolean mIsSplitLayoutEnabledByUser;
+ // Indicates if split layout is actually enabled, taking into account
+ // whether the user has enabled it, and the keyboard layout supports it.
+ boolean mIsSplitLayoutEnabled;
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
new SparseArray<>();
@@ -129,14 +144,26 @@ public final class KeyboardLayoutSet {
private static void clearKeyboardCache() {
sKeyboardCache.clear();
- sKeysCache.clear();
+ sUniqueKeysCache.clear();
+ }
+
+ public static int getScriptId(final Resources resources,
+ @Nonnull final InputMethodSubtype subtype) {
+ final Integer value = sScriptIdsForSubtypes.get(subtype);
+ if (null == value) {
+ final int scriptId = Builder.readScriptId(resources, subtype);
+ sScriptIdsForSubtypes.put(subtype, scriptId);
+ return scriptId;
+ }
+ return value;
}
- KeyboardLayoutSet(final Context context, final Params params) {
+ KeyboardLayoutSet(final Context context, @Nonnull final Params params) {
mContext = context;
mParams = params;
}
+ @Nonnull
public Keyboard getKeyboard(final int baseKeyboardLayoutSetElementId) {
final int keyboardLayoutSetElementId;
switch (mParams.mMode) {
@@ -168,6 +195,9 @@ public final class KeyboardLayoutSet {
// attribute in a keyboard_layout_set XML file. Also each keyboard layout XML resource is
// specified as an elementKeyboard attribute in the file.
// The KeyboardId is an internal key for a Keyboard object.
+
+ mParams.mIsSplitLayoutEnabled = mParams.mIsSplitLayoutEnabledByUser
+ && elementParams.mSupportsSplitLayout;
final KeyboardId id = new KeyboardId(keyboardLayoutSetElementId, mParams);
try {
return getKeyboard(elementParams, id);
@@ -177,6 +207,7 @@ public final class KeyboardLayoutSet {
}
}
+ @Nonnull
private Keyboard getKeyboard(final ElementParams elementParams, final KeyboardId id) {
final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
final Keyboard cachedKeyboard = (ref == null) ? null : ref.get();
@@ -188,10 +219,9 @@ public final class KeyboardLayoutSet {
}
final KeyboardBuilder<KeyboardParams> builder =
- new KeyboardBuilder<>(mContext, new KeyboardParams());
- if (id.isAlphabetKeyboard()) {
- builder.setAutoGenerate(sKeysCache);
- }
+ new KeyboardBuilder<>(mContext, new KeyboardParams(sUniqueKeysCache));
+ sUniqueKeysCache.setEnabled(id.isAlphabetKeyboard());
+ builder.setAllowRedundantMoreKes(elementParams.mAllowRedundantMoreKeys);
final int keyboardXmlId = elementParams.mKeyboardXmlId;
builder.load(keyboardXmlId, id);
if (mParams.mDisableTouchPositionCorrectionDataForTest) {
@@ -232,7 +262,7 @@ public final class KeyboardLayoutSet {
private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo();
- public Builder(final Context context, final EditorInfo ei) {
+ public Builder(final Context context, @Nullable final EditorInfo ei) {
mContext = context;
mPackageName = context.getPackageName();
mResources = context.getResources();
@@ -253,7 +283,7 @@ public final class KeyboardLayoutSet {
return this;
}
- public Builder setSubtype(final InputMethodSubtype subtype) {
+ public Builder setSubtype(@Nonnull final RichInputMethodSubtype subtype) {
final boolean asciiCapable = InputMethodSubtypeCompatUtils.isAsciiCapable(subtype);
// TODO: Consolidate with {@link InputAttributes}.
@SuppressWarnings("deprecation")
@@ -262,12 +292,12 @@ public final class KeyboardLayoutSet {
final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii(
mParams.mEditorInfo.imeOptions)
|| deprecatedForceAscii;
- final InputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable)
- ? SubtypeSwitcher.getInstance().getNoLanguageSubtype()
+ final RichInputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable)
+ ? RichInputMethodSubtype.getNoLanguageSubtype()
: subtype;
mParams.mSubtype = keyboardSubtype;
mParams.mKeyboardLayoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX
- + SubtypeLocaleUtils.getKeyboardLayoutSetName(keyboardSubtype);
+ + keyboardSubtype.getKeyboardLayoutSetName();
return this;
}
@@ -286,31 +316,75 @@ public final class KeyboardLayoutSet {
return this;
}
- public void disableTouchPositionCorrectionData() {
+ public Builder disableTouchPositionCorrectionData() {
mParams.mDisableTouchPositionCorrectionDataForTest = true;
+ return this;
}
- public void setScriptId(final int scriptId) {
- mParams.mScriptId = scriptId;
+ public Builder setSplitLayoutEnabledByUser(final boolean enabled) {
+ mParams.mIsSplitLayoutEnabledByUser = enabled;
+ return this;
+ }
+
+ // Super redux version of reading the script ID for some subtype from Xml.
+ static int readScriptId(final Resources resources, final InputMethodSubtype subtype) {
+ final String layoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX
+ + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
+ final int xmlId = getXmlId(resources, layoutSetName);
+ final XmlResourceParser parser = resources.getXml(xmlId);
+ try {
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ // Bovinate through the XML stupidly searching for TAG_FEATURE, and read
+ // the script Id from it.
+ parser.next();
+ final String tag = parser.getName();
+ if (TAG_FEATURE.equals(tag)) {
+ return readScriptIdFromTagFeature(resources, parser);
+ }
+ }
+ } catch (final IOException | XmlPullParserException e) {
+ throw new RuntimeException(e.getMessage() + " in " + layoutSetName, e);
+ } finally {
+ parser.close();
+ }
+ // If the tag is not found, then the default script is Latin.
+ return ScriptUtils.SCRIPT_LATIN;
+ }
+
+ private static int readScriptIdFromTagFeature(final Resources resources,
+ final XmlPullParser parser) throws IOException, XmlPullParserException {
+ final TypedArray featureAttr = resources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.KeyboardLayoutSet_Feature);
+ try {
+ final int scriptId =
+ featureAttr.getInt(R.styleable.KeyboardLayoutSet_Feature_supportedScript,
+ ScriptUtils.SCRIPT_UNKNOWN);
+ XmlParseUtils.checkEndTag(TAG_FEATURE, parser);
+ return scriptId;
+ } finally {
+ featureAttr.recycle();
+ }
}
public KeyboardLayoutSet build() {
if (mParams.mSubtype == null)
throw new RuntimeException("KeyboardLayoutSet subtype is not specified");
- final String packageName = mResources.getResourcePackageName(
- R.xml.keyboard_layout_set_qwerty);
- final String keyboardLayoutSetName = mParams.mKeyboardLayoutSetName;
- final int xmlId = mResources.getIdentifier(keyboardLayoutSetName, "xml", packageName);
+ final int xmlId = getXmlId(mResources, mParams.mKeyboardLayoutSetName);
try {
parseKeyboardLayoutSet(mResources, xmlId);
- } catch (final IOException e) {
- throw new RuntimeException(e.getMessage() + " in " + keyboardLayoutSetName, e);
- } catch (final XmlPullParserException e) {
- throw new RuntimeException(e.getMessage() + " in " + keyboardLayoutSetName, e);
+ } catch (final IOException | XmlPullParserException e) {
+ throw new RuntimeException(e.getMessage() + " in " + mParams.mKeyboardLayoutSetName,
+ e);
}
return new KeyboardLayoutSet(mContext, mParams);
}
+ private static int getXmlId(final Resources resources, final String keyboardLayoutSetName) {
+ final String packageName = resources.getResourcePackageName(
+ R.xml.keyboard_layout_set_qwerty);
+ return resources.getIdentifier(keyboardLayoutSetName, "xml", packageName);
+ }
+
private void parseKeyboardLayoutSet(final Resources res, final int resId)
throws XmlPullParserException, IOException {
final XmlResourceParser parser = res.getXml(resId);
@@ -340,7 +414,7 @@ public final class KeyboardLayoutSet {
if (TAG_ELEMENT.equals(tag)) {
parseKeyboardLayoutSetElement(parser);
} else if (TAG_FEATURE.equals(tag)) {
- parseKeyboardLayoutSetFeature(parser);
+ mParams.mScriptId = readScriptIdFromTagFeature(mResources, parser);
} else {
throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD_SET);
}
@@ -348,9 +422,8 @@ public final class KeyboardLayoutSet {
final String tag = parser.getName();
if (TAG_KEYBOARD_SET.equals(tag)) {
break;
- } else {
- throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_KEYBOARD_SET);
}
+ throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_KEYBOARD_SET);
}
}
}
@@ -376,27 +449,16 @@ public final class KeyboardLayoutSet {
elementParams.mProximityCharsCorrectionEnabled = a.getBoolean(
R.styleable.KeyboardLayoutSet_Element_enableProximityCharsCorrection,
false);
+ elementParams.mSupportsSplitLayout = a.getBoolean(
+ R.styleable.KeyboardLayoutSet_Element_supportsSplitLayout, false);
+ elementParams.mAllowRedundantMoreKeys = a.getBoolean(
+ R.styleable.KeyboardLayoutSet_Element_allowRedundantMoreKeys, true);
mParams.mKeyboardLayoutSetElementIdToParamsMap.put(elementName, elementParams);
} finally {
a.recycle();
}
}
- private void parseKeyboardLayoutSetFeature(final XmlPullParser parser)
- throws XmlPullParserException, IOException {
- final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.KeyboardLayoutSet_Feature);
- try {
- final int scriptId = a.getInt(
- R.styleable.KeyboardLayoutSet_Feature_supportedScript,
- ScriptUtils.SCRIPT_LATIN);
- XmlParseUtils.checkEndTag(TAG_FEATURE, parser);
- setScriptId(scriptId);
- } finally {
- a.recycle();
- }
- }
-
private static int getKeyboardMode(final EditorInfo editorInfo) {
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 60665f8de..92e5dfceb 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -17,9 +17,7 @@
package com.android.inputmethod.keyboard;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Resources;
-import android.preference.PreferenceManager;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -27,6 +25,7 @@ import android.view.View;
import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
+import com.android.inputmethod.event.Event;
import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
import com.android.inputmethod.keyboard.emoji.EmojiPalettesView;
import com.android.inputmethod.keyboard.internal.KeyboardState;
@@ -35,24 +34,25 @@ import com.android.inputmethod.latin.InputView;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.define.ProductionFlags;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsValues;
+import com.android.inputmethod.latin.utils.CapsModeUtils;
+import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils;
+import com.android.inputmethod.latin.utils.RecapitalizeStatus;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
- private SubtypeSwitcher mSubtypeSwitcher;
- private SharedPreferences mPrefs;
-
private InputView mCurrentInputView;
private View mMainKeyboardFrame;
private MainKeyboardView mKeyboardView;
private EmojiPalettesView mEmojiPalettesView;
private LatinIME mLatinIME;
+ private RichInputMethodManager mRichImm;
private boolean mIsHardwareAcceleratedDrawingEnabled;
private KeyboardState mState;
@@ -75,14 +75,12 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
public static void init(final LatinIME latinIme) {
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(latinIme);
- sInstance.initInternal(latinIme, prefs);
+ sInstance.initInternal(latinIme);
}
- private void initInternal(final LatinIME latinIme, final SharedPreferences prefs) {
+ private void initInternal(final LatinIME latinIme) {
mLatinIME = latinIme;
- mPrefs = prefs;
- mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+ mRichImm = RichInputMethodManager.getInstance();
mState = new KeyboardState(this);
mIsHardwareAcceleratedDrawingEnabled =
InputMethodServiceCompatUtils.enableHardwareAcceleration(mLatinIME);
@@ -90,7 +88,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
public void updateKeyboardTheme() {
final boolean themeUpdated = updateKeyboardThemeAndContextThemeWrapper(
- mLatinIME, KeyboardTheme.getKeyboardTheme(mPrefs));
+ mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */));
if (themeUpdated && mKeyboardView != null) {
mLatinIME.setInputView(onCreateInputView(mIsHardwareAcceleratedDrawingEnabled));
}
@@ -113,18 +111,19 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
mThemeContext, editorInfo);
final Resources res = mThemeContext.getResources();
final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
- final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
+ final int keyboardHeight = ResourceUtils.getKeyboardHeight(res, settingsValues);
builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
- builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
+ builder.setSubtype(mRichImm.getCurrentSubtype());
builder.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey);
builder.setLanguageSwitchKeyEnabled(mLatinIME.shouldShowLanguageSwitchKey());
+ builder.setSplitLayoutEnabledByUser(ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED
+ && settingsValues.mIsSplitKeyboardEnabled);
mKeyboardLayoutSet = builder.build();
try {
mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState);
- mKeyboardTextsSet.setLocale(mSubtypeSwitcher.getCurrentSubtypeLocale(), mThemeContext);
+ mKeyboardTextsSet.setLocale(mRichImm.getCurrentSubtypeLocale(), mThemeContext);
} catch (KeyboardLayoutSetException e) {
Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
- return;
}
}
@@ -160,12 +159,12 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
currentSettingsValues.mKeyPreviewDismissEndXScale,
currentSettingsValues.mKeyPreviewDismissEndYScale,
currentSettingsValues.mKeyPreviewDismissDuration);
- keyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
+ keyboardView.updateShortcutKey(mRichImm.isShortcutImeReady());
final boolean subtypeChanged = (oldKeyboard == null)
- || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
- final int languageOnSpacebarFormatType = mSubtypeSwitcher.getLanguageOnSpacebarFormatType(
- keyboard.mId.mSubtype);
- final boolean hasMultipleEnabledIMEsOrSubtypes = RichInputMethodManager.getInstance()
+ || !keyboard.mId.mSubtype.equals(oldKeyboard.mId.mSubtype);
+ final int languageOnSpacebarFormatType = LanguageOnSpacebarUtils
+ .getLanguageOnSpacebarFormatType(keyboard.mId.mSubtype);
+ final boolean hasMultipleEnabledIMEsOrSubtypes = mRichImm
.hasMultipleEnabledIMEsOrSubtypes(true /* shouldIncludeAuxiliarySubtypes */);
keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, languageOnSpacebarFormatType,
hasMultipleEnabledIMEsOrSubtypes);
@@ -203,42 +202,64 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setAlphabetKeyboard() {
+ if (DEBUG_ACTION) {
+ Log.d(TAG, "setAlphabetKeyboard");
+ }
setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setAlphabetManualShiftedKeyboard() {
+ if (DEBUG_ACTION) {
+ Log.d(TAG, "setAlphabetManualShiftedKeyboard");
+ }
setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED));
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setAlphabetAutomaticShiftedKeyboard() {
+ if (DEBUG_ACTION) {
+ Log.d(TAG, "setAlphabetAutomaticShiftedKeyboard");
+ }
setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED));
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setAlphabetShiftLockedKeyboard() {
+ if (DEBUG_ACTION) {
+ Log.d(TAG, "setAlphabetShiftLockedKeyboard");
+ }
setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED));
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setAlphabetShiftLockShiftedKeyboard() {
+ if (DEBUG_ACTION) {
+ Log.d(TAG, "setAlphabetShiftLockShiftedKeyboard");
+ }
setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED));
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setSymbolsKeyboard() {
+ if (DEBUG_ACTION) {
+ Log.d(TAG, "setSymbolsKeyboard");
+ }
setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
}
private void setMainKeyboardFrame(final SettingsValues settingsValues) {
- mMainKeyboardFrame.setVisibility(
- settingsValues.mHasHardwareKeyboard ? View.GONE : View.VISIBLE);
+ final int visibility = settingsValues.mHasHardwareKeyboard ? View.GONE : View.VISIBLE;
+ mKeyboardView.setVisibility(visibility);
+ // The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}.
+ // @see #getVisibleKeyboardView() and
+ // @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets)
+ mMainKeyboardFrame.setVisibility(visibility);
mEmojiPalettesView.setVisibility(View.GONE);
mEmojiPalettesView.stopEmojiPalettes();
}
@@ -246,8 +267,15 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setEmojiKeyboard() {
+ if (DEBUG_ACTION) {
+ Log.d(TAG, "setEmojiKeyboard");
+ }
final Keyboard keyboard = mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
mMainKeyboardFrame.setVisibility(View.GONE);
+ // The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}.
+ // @see #getVisibleKeyboardView() and
+ // @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets)
+ mKeyboardView.setVisibility(View.GONE);
mEmojiPalettesView.startEmojiPalettes(
mKeyboardTextsSet.getText(KeyboardTextsSet.SWITCH_TO_ALPHA_KEY_LABEL),
mKeyboardView.getKeyVisualAttribute(), keyboard.mIconsSet);
@@ -255,8 +283,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
public void onToggleEmojiKeyboard() {
- if (mKeyboardLayoutSet == null || !isShowingEmojiPalettes()) {
- mLatinIME.startShowingInputView();
+ final boolean needsToLoadKeyboard = (mKeyboardLayoutSet == null);
+ if (needsToLoadKeyboard || !isShowingEmojiPalettes()) {
+ mLatinIME.startShowingInputView(needsToLoadKeyboard);
setEmojiKeyboard();
} else {
mLatinIME.stopShowingInputView();
@@ -267,18 +296,29 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setSymbolsShiftedKeyboard() {
+ if (DEBUG_ACTION) {
+ Log.d(TAG, "setSymbolsShiftedKeyboard");
+ }
setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
}
// Future method for requesting an updating to the shift state.
- public void requestUpdatingShiftState(final int currentAutoCapsState,
- final int currentRecapitalizeState) {
- mState.onUpdateShiftState(currentAutoCapsState, currentRecapitalizeState);
+ @Override
+ public void requestUpdatingShiftState(final int autoCapsFlags, final int recapitalizeMode) {
+ if (DEBUG_ACTION) {
+ Log.d(TAG, "requestUpdatingShiftState: "
+ + " autoCapsFlags=" + CapsModeUtils.flagsToString(autoCapsFlags)
+ + " recapitalizeMode=" + RecapitalizeStatus.modeToString(recapitalizeMode));
+ }
+ mState.onUpdateShiftState(autoCapsFlags, recapitalizeMode);
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void startDoubleTapShiftKeyTimer() {
+ if (DEBUG_TIMER_ACTION) {
+ Log.d(TAG, "startDoubleTapShiftKeyTimer");
+ }
final MainKeyboardView keyboardView = getMainKeyboardView();
if (keyboardView != null) {
keyboardView.startDoubleTapShiftKeyTimer();
@@ -288,6 +328,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void cancelDoubleTapShiftKeyTimer() {
+ if (DEBUG_TIMER_ACTION) {
+ Log.d(TAG, "setAlphabetKeyboard");
+ }
final MainKeyboardView keyboardView = getMainKeyboardView();
if (keyboardView != null) {
keyboardView.cancelDoubleTapShiftKeyTimer();
@@ -297,6 +340,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
// Implements {@link KeyboardState.SwitchActions}.
@Override
public boolean isInDoubleTapShiftKeyTimeout() {
+ if (DEBUG_TIMER_ACTION) {
+ Log.d(TAG, "isInDoubleTapShiftKeyTimeout");
+ }
final MainKeyboardView keyboardView = getMainKeyboardView();
return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout();
}
@@ -304,9 +350,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
/**
* Updates state machine to figure out when to automatically switch back to the previous mode.
*/
- public void onCodeInput(final int code, final int currentAutoCapsState,
+ public void onEvent(final Event event, final int currentAutoCapsState,
final int currentRecapitalizeState) {
- mState.onCodeInput(code, currentAutoCapsState, currentRecapitalizeState);
+ mState.onEvent(event, currentAutoCapsState, currentRecapitalizeState);
}
public boolean isShowingEmojiPalettes() {
@@ -347,7 +393,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
updateKeyboardThemeAndContextThemeWrapper(
- mLatinIME, KeyboardTheme.getKeyboardTheme(mPrefs));
+ mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */));
mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
R.layout.input_view, null);
mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame);
@@ -363,12 +409,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
return mCurrentInputView;
}
- public void onNetworkStateChanged() {
- if (mKeyboardView != null) {
- mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
- }
- }
-
public int getKeyboardShiftMode() {
final Keyboard keyboard = getKeyboard();
if (keyboard == null) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
index 7161d3f26..006d08696 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
@@ -16,14 +16,17 @@
package com.android.inputmethod.keyboard;
+import android.content.Context;
import android.content.SharedPreferences;
+import android.os.Build;
import android.os.Build.VERSION_CODES;
+import android.preference.PreferenceManager;
import android.util.Log;
-import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.BuildCompatUtils;
import com.android.inputmethod.latin.R;
+import java.util.ArrayList;
import java.util.Arrays;
public final class KeyboardTheme implements Comparable<KeyboardTheme> {
@@ -40,7 +43,10 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
public static final int THEME_ID_LXX_DARK = 4;
public static final int DEFAULT_THEME_ID = THEME_ID_KLP;
- private static final KeyboardTheme[] KEYBOARD_THEMES = {
+ private static KeyboardTheme[] AVAILABLE_KEYBOARD_THEMES;
+
+ /* package private for testing */
+ static final KeyboardTheme[] KEYBOARD_THEMES = {
new KeyboardTheme(THEME_ID_ICS, "ICS", R.style.KeyboardTheme_ICS,
// This has never been selected because we support ICS or later.
VERSION_CODES.BASE),
@@ -49,8 +55,9 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
VERSION_CODES.ICE_CREAM_SANDWICH),
new KeyboardTheme(THEME_ID_LXX_LIGHT, "LXXLight", R.style.KeyboardTheme_LXX_Light,
// Default theme for LXX.
- BuildCompatUtils.VERSION_CODES_LXX),
+ Build.VERSION_CODES.LOLLIPOP),
new KeyboardTheme(THEME_ID_LXX_DARK, "LXXDark", R.style.KeyboardTheme_LXX_Dark,
+ // This has never been selected as default theme.
VERSION_CODES.BASE),
};
@@ -62,7 +69,7 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
public final int mThemeId;
public final int mStyleId;
public final String mThemeName;
- private final int mMinApiVersion;
+ public final int mMinApiVersion;
// Note: The themeId should be aligned with "themeId" attribute of Keyboard style
// in values/themes-<style>.xml.
@@ -92,10 +99,11 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
return mThemeId;
}
- @UsedForTesting
- static KeyboardTheme searchKeyboardThemeById(final int themeId) {
+ /* package private for testing */
+ static KeyboardTheme searchKeyboardThemeById(final int themeId,
+ final KeyboardTheme[] availableThemeIds) {
// TODO: This search algorithm isn't optimal if there are many themes.
- for (final KeyboardTheme theme : KEYBOARD_THEMES) {
+ for (final KeyboardTheme theme : availableThemeIds) {
if (theme.mThemeId == themeId) {
return theme;
}
@@ -103,15 +111,16 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
return null;
}
- @UsedForTesting
+ /* package private for testing */
static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs,
- final int sdkVersion) {
+ final int sdkVersion, final KeyboardTheme[] availableThemeArray) {
final String klpThemeIdString = prefs.getString(KLP_KEYBOARD_THEME_KEY, null);
if (klpThemeIdString != null) {
if (sdkVersion <= VERSION_CODES.KITKAT) {
try {
final int themeId = Integer.parseInt(klpThemeIdString);
- final KeyboardTheme theme = searchKeyboardThemeById(themeId);
+ final KeyboardTheme theme = searchKeyboardThemeById(themeId,
+ availableThemeArray);
if (theme != null) {
return theme;
}
@@ -125,25 +134,24 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
prefs.edit().remove(KLP_KEYBOARD_THEME_KEY).apply();
}
// TODO: This search algorithm isn't optimal if there are many themes.
- for (final KeyboardTheme theme : KEYBOARD_THEMES) {
+ for (final KeyboardTheme theme : availableThemeArray) {
if (sdkVersion >= theme.mMinApiVersion) {
return theme;
}
}
- return searchKeyboardThemeById(DEFAULT_THEME_ID);
+ return searchKeyboardThemeById(DEFAULT_THEME_ID, availableThemeArray);
}
public static String getKeyboardThemeName(final int themeId) {
- final KeyboardTheme theme = searchKeyboardThemeById(themeId);
+ final KeyboardTheme theme = searchKeyboardThemeById(themeId, KEYBOARD_THEMES);
return theme.mThemeName;
}
- public static void saveKeyboardThemeId(final String themeIdString,
- final SharedPreferences prefs) {
- saveKeyboardThemeId(themeIdString, prefs, BuildCompatUtils.EFFECTIVE_SDK_INT);
+ public static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs) {
+ saveKeyboardThemeId(themeId, prefs, BuildCompatUtils.EFFECTIVE_SDK_INT);
}
- @UsedForTesting
+ /* package private for testing */
static String getPreferenceKey(final int sdkVersion) {
if (sdkVersion <= VERSION_CODES.KITKAT) {
return KLP_KEYBOARD_THEME_KEY;
@@ -151,26 +159,48 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
return LXX_KEYBOARD_THEME_KEY;
}
- @UsedForTesting
- static void saveKeyboardThemeId(final String themeIdString,
- final SharedPreferences prefs, final int sdkVersion) {
+ /* package private for testing */
+ static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs,
+ final int sdkVersion) {
final String prefKey = getPreferenceKey(sdkVersion);
- prefs.edit().putString(prefKey, themeIdString).apply();
+ prefs.edit().putString(prefKey, Integer.toString(themeId)).apply();
+ }
+
+ public static KeyboardTheme getKeyboardTheme(final Context context) {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ final KeyboardTheme[] availableThemeArray = getAvailableThemeArray(context);
+ return getKeyboardTheme(prefs, BuildCompatUtils.EFFECTIVE_SDK_INT, availableThemeArray);
}
- public static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs) {
- return getKeyboardTheme(prefs, BuildCompatUtils.EFFECTIVE_SDK_INT);
+ /* package private for testing */
+ static KeyboardTheme[] getAvailableThemeArray(final Context context) {
+ if (AVAILABLE_KEYBOARD_THEMES == null) {
+ final int[] availableThemeIdStringArray = context.getResources().getIntArray(
+ R.array.keyboard_theme_ids);
+ final ArrayList<KeyboardTheme> availableThemeList = new ArrayList<>();
+ for (final int id : availableThemeIdStringArray) {
+ final KeyboardTheme theme = searchKeyboardThemeById(id, KEYBOARD_THEMES);
+ if (theme != null) {
+ availableThemeList.add(theme);
+ }
+ }
+ AVAILABLE_KEYBOARD_THEMES = availableThemeList.toArray(
+ new KeyboardTheme[availableThemeList.size()]);
+ Arrays.sort(AVAILABLE_KEYBOARD_THEMES);
+ }
+ return AVAILABLE_KEYBOARD_THEMES;
}
- @UsedForTesting
- static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion) {
+ /* package private for testing */
+ static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion,
+ final KeyboardTheme[] availableThemeArray) {
final String lxxThemeIdString = prefs.getString(LXX_KEYBOARD_THEME_KEY, null);
if (lxxThemeIdString == null) {
- return getDefaultKeyboardTheme(prefs, sdkVersion);
+ return getDefaultKeyboardTheme(prefs, sdkVersion, availableThemeArray);
}
try {
final int themeId = Integer.parseInt(lxxThemeIdString);
- final KeyboardTheme theme = searchKeyboardThemeById(themeId);
+ final KeyboardTheme theme = searchKeyboardThemeById(themeId, availableThemeArray);
if (theme != null) {
return theme;
}
@@ -180,6 +210,6 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
}
// Remove preference that contains unknown or illegal theme id.
prefs.edit().remove(LXX_KEYBOARD_THEME_KEY).apply();
- return getDefaultKeyboardTheme(prefs, sdkVersion);
+ return getDefaultKeyboardTheme(prefs, sdkVersion, availableThemeArray);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 98cd1da54..27e538cb7 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -25,7 +25,6 @@ import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
import android.graphics.Rect;
-import android.graphics.Region;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
@@ -35,12 +34,15 @@ import android.view.View;
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.common.Constants;
import com.android.inputmethod.latin.utils.TypefaceUtils;
import java.util.HashSet;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* A view that renders a virtual {@link Keyboard}.
*
@@ -98,24 +100,28 @@ public class KeyboardView extends View {
private static final float MAX_LABEL_RATIO = 0.90f;
// Main keyboard
+ // TODO: Consider having a dummy keyboard object to make this @Nonnull
+ @Nullable
private Keyboard mKeyboard;
- protected final KeyDrawParams mKeyDrawParams = new KeyDrawParams();
+ @Nonnull
+ private final KeyDrawParams mKeyDrawParams = new KeyDrawParams();
// Drawing
/** True if all keys should be drawn */
private boolean mInvalidateAllKeys;
/** The keys that should be drawn */
private final HashSet<Key> mInvalidatedKeys = new HashSet<>();
- /** The working rectangle variable */
- private final Rect mWorkingRect = new Rect();
+ /** The working rectangle for clipping */
+ private final Rect mClipRect = new Rect();
/** The keyboard bitmap buffer for faster updates */
- /** The clip region to draw keys */
- private final Region mClipRegion = new Region();
private Bitmap mOffscreenBuffer;
/** The canvas for the above mutable keyboard bitmap */
+ @Nonnull
private final Canvas mOffscreenCanvas = new Canvas();
+ @Nonnull
private final Paint mPaint = new Paint();
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
+
public KeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
@@ -159,11 +165,12 @@ public class KeyboardView extends View {
mPaint.setAntiAlias(true);
}
+ @Nullable
public KeyVisualAttributes getKeyVisualAttribute() {
return mKeyVisualAttributes;
}
- private static void blendAlpha(final Paint paint, final int alpha) {
+ private static void blendAlpha(@Nonnull final Paint paint, final int alpha) {
final int color = paint.getColor();
paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE,
Color.red(color), Color.green(color), Color.blue(color));
@@ -182,7 +189,7 @@ public class KeyboardView extends View {
* @see #getKeyboard()
* @param keyboard the keyboard to display in this view
*/
- public void setKeyboard(final Keyboard keyboard) {
+ public void setKeyboard(@Nonnull final Keyboard keyboard) {
mKeyboard = keyboard;
final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
@@ -196,6 +203,7 @@ public class KeyboardView extends View {
* @return the currently attached keyboard
* @see #setKeyboard(Keyboard)
*/
+ @Nullable
public Keyboard getKeyboard() {
return mKeyboard;
}
@@ -204,19 +212,25 @@ public class KeyboardView extends View {
return mVerticalCorrection;
}
+ @Nonnull
+ protected KeyDrawParams getKeyDrawParams() {
+ return mKeyDrawParams;
+ }
+
protected void updateKeyDrawParams(final int keyHeight) {
mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- if (mKeyboard == null) {
+ final Keyboard keyboard = getKeyboard();
+ if (keyboard == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
// The main keyboard expands to the entire this {@link KeyboardView}.
- final int width = mKeyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
- final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
+ final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
+ final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
setMeasuredDimension(width, height);
}
@@ -264,52 +278,45 @@ public class KeyboardView extends View {
}
}
- private void onDrawKeyboard(final Canvas canvas) {
- if (mKeyboard == null) return;
+ private void onDrawKeyboard(@Nonnull final Canvas canvas) {
+ final Keyboard keyboard = getKeyboard();
+ if (keyboard == null) {
+ return;
+ }
- final int width = getWidth();
- final int height = getHeight();
final Paint paint = mPaint;
-
+ final Drawable background = getBackground();
// Calculate clip region and set.
final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty();
final boolean isHardwareAccelerated = canvas.isHardwareAccelerated();
// TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
if (drawAllKeys || isHardwareAccelerated) {
- mClipRegion.set(0, 0, width, height);
- } else {
- mClipRegion.setEmpty();
- for (final Key key : mInvalidatedKeys) {
- if (mKeyboard.hasKey(key)) {
- final int x = key.getX() + getPaddingLeft();
- final int y = key.getY() + getPaddingTop();
- mWorkingRect.set(x, y, x + key.getWidth(), y + key.getHeight());
- mClipRegion.union(mWorkingRect);
- }
- }
- }
- if (!isHardwareAccelerated) {
- canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
- // Draw keyboard background.
- canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
- final Drawable background = getBackground();
- if (background != null) {
+ if (!isHardwareAccelerated && background != null) {
+ // Need to draw keyboard background on {@link #mOffscreenBuffer}.
+ canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
background.draw(canvas);
}
- }
-
- // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
- if (drawAllKeys || isHardwareAccelerated) {
// Draw all keys.
- for (final Key key : mKeyboard.getSortedKeys()) {
+ for (final Key key : keyboard.getSortedKeys()) {
onDrawKey(key, canvas, paint);
}
} else {
- // Draw invalidated keys.
for (final Key key : mInvalidatedKeys) {
- if (mKeyboard.hasKey(key)) {
- onDrawKey(key, canvas, paint);
+ if (!keyboard.hasKey(key)) {
+ continue;
+ }
+ if (background != null) {
+ // Need to redraw key's background on {@link #mOffscreenBuffer}.
+ final int x = key.getX() + getPaddingLeft();
+ final int y = key.getY() + getPaddingTop();
+ mClipRect.set(x, y, x + key.getWidth(), y + key.getHeight());
+ canvas.save();
+ canvas.clipRect(mClipRect);
+ canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+ background.draw(canvas);
+ canvas.restore();
}
+ onDrawKey(key, canvas, paint);
}
}
@@ -317,20 +324,22 @@ public class KeyboardView extends View {
mInvalidateAllKeys = false;
}
- private void onDrawKey(final Key key, final Canvas canvas, final Paint paint) {
+ private void onDrawKey(@Nonnull final Key key, @Nonnull final Canvas canvas,
+ @Nonnull final Paint paint) {
final int keyDrawX = key.getDrawX() + getPaddingLeft();
final int keyDrawY = key.getY() + getPaddingTop();
canvas.translate(keyDrawX, keyDrawY);
- final int keyHeight = mKeyboard.mMostCommonKeyHeight - mKeyboard.mVerticalGap;
final KeyVisualAttributes attr = key.getVisualAttributes();
- final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(keyHeight, attr);
+ final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(key.getHeight(), attr);
params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
if (!key.isSpacer()) {
final Drawable background = key.selectBackgroundDrawable(
mKeyBackground, mFunctionalKeyBackground, mSpacebarBackground);
- onDrawKeyBackground(key, canvas, background);
+ if (background != null) {
+ onDrawKeyBackground(key, canvas, background);
+ }
}
onDrawKeyTopVisuals(key, canvas, paint, params);
@@ -338,8 +347,8 @@ public class KeyboardView extends View {
}
// Draw key background.
- protected void onDrawKeyBackground(final Key key, final Canvas canvas,
- final Drawable background) {
+ protected void onDrawKeyBackground(@Nonnull final Key key, @Nonnull final Canvas canvas,
+ @Nonnull final Drawable background) {
final int keyWidth = key.getDrawWidth();
final int keyHeight = key.getHeight();
final int bgWidth, bgHeight, bgX, bgY;
@@ -371,15 +380,17 @@ public class KeyboardView extends View {
}
// Draw key top visuals.
- protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
- final KeyDrawParams params) {
+ protected void onDrawKeyTopVisuals(@Nonnull final Key key, @Nonnull final Canvas canvas,
+ @Nonnull final Paint paint, @Nonnull final KeyDrawParams params) {
final int keyWidth = key.getDrawWidth();
final int keyHeight = key.getHeight();
final float centerX = keyWidth * 0.5f;
final float centerY = keyHeight * 0.5f;
// Draw key label.
- final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha);
+ final Keyboard keyboard = getKeyboard();
+ final Drawable icon = (keyboard == null) ? null
+ : key.getIcon(keyboard.mIconsSet, params.mAnimAlpha);
float labelX = centerX;
float labelBaseline = centerY;
final String label = key.getLabel();
@@ -498,8 +509,8 @@ public class KeyboardView extends View {
}
// Draw popup hint "..." at the bottom right corner of the key.
- protected void drawKeyPopupHint(final Key key, final Canvas canvas, final Paint paint,
- final KeyDrawParams params) {
+ protected void drawKeyPopupHint(@Nonnull final Key key, @Nonnull final Canvas canvas,
+ @Nonnull final Paint paint, @Nonnull final KeyDrawParams params) {
if (TextUtils.isEmpty(mKeyPopupHintLetter)) {
return;
}
@@ -516,15 +527,15 @@ public class KeyboardView extends View {
canvas.drawText(mKeyPopupHintLetter, hintX, hintY, paint);
}
- protected static void drawIcon(final Canvas canvas, final Drawable icon, final int x,
- final int y, final int width, final int height) {
+ protected static void drawIcon(@Nonnull final Canvas canvas,@Nonnull final Drawable icon,
+ final int x, final int y, final int width, final int height) {
canvas.translate(x, y);
icon.setBounds(0, 0, width, height);
icon.draw(canvas);
canvas.translate(-x, -y);
}
- public Paint newLabelPaint(final Key key) {
+ public Paint newLabelPaint(@Nullable final Key key) {
final Paint paint = new Paint();
paint.setAntiAlias(true);
if (key == null) {
@@ -557,9 +568,10 @@ public class KeyboardView extends View {
* @param key key in the attached {@link Keyboard}.
* @see #invalidateAllKeys
*/
- public void invalidateKey(final Key key) {
- if (mInvalidateAllKeys) return;
- if (key == null) return;
+ public void invalidateKey(@Nullable final Key key) {
+ if (mInvalidateAllKeys || key == null) {
+ return;
+ }
mInvalidatedKeys.add(key);
final int x = key.getX() + getPaddingLeft();
final int y = key.getY() + getPaddingTop();
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 2b16785c2..00d4fa236 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -28,40 +28,44 @@ import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Typeface;
import android.preference.PreferenceManager;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.accessibility.MainKeyboardAccessibilityDelegate;
import com.android.inputmethod.annotations.ExternallyReferenced;
-import com.android.inputmethod.keyboard.internal.DrawingHandler;
import com.android.inputmethod.keyboard.internal.DrawingPreviewPlacerView;
+import com.android.inputmethod.keyboard.internal.DrawingProxy;
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.KeyPreviewView;
-import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
import com.android.inputmethod.keyboard.internal.MoreKeySpec;
import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
import com.android.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview;
import com.android.inputmethod.keyboard.internal.TimerHandler;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.CoordinateUtils;
import com.android.inputmethod.latin.settings.DebugSettings;
-import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.SpacebarLanguageUtils;
+import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils;
import com.android.inputmethod.latin.utils.TypefaceUtils;
+import java.util.Locale;
import java.util.WeakHashMap;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* A view that is responsible for detecting key presses and touch movements.
*
@@ -105,8 +109,8 @@ import java.util.WeakHashMap;
* @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold
* @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
*/
-public final class MainKeyboardView extends KeyboardView implements PointerTracker.DrawingProxy,
- MoreKeysPanel.Controller, DrawingHandler.Callbacks, TimerHandler.Callbacks {
+public final class MainKeyboardView extends KeyboardView implements DrawingProxy,
+ MoreKeysPanel.Controller {
private static final String TAG = MainKeyboardView.class.getSimpleName();
/** Listener for {@link KeyboardActionListener}. */
@@ -147,7 +151,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
// More keys keyboard
private final Paint mBackgroundDimAlphaPaint = new Paint();
- private boolean mNeedsToDimEntireKeyboard;
private final View mMoreKeysKeyboardContainer;
private final View mMoreKeysKeyboardForActionContainer;
private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = new WeakHashMap<>();
@@ -163,11 +166,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private final KeyDetector mKeyDetector;
private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
- private final TimerHandler mKeyTimerHandler;
+ private final TimerHandler mTimerHandler;
private final int mLanguageOnSpacebarHorizontalMargin;
- private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
-
private MainKeyboardAccessibilityDelegate mAccessibilityDelegate;
public MainKeyboardView(final Context context, final AttributeSet attrs) {
@@ -177,7 +178,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
- mDrawingPreviewPlacerView = new DrawingPreviewPlacerView(context, attrs);
+ final DrawingPreviewPlacerView drawingPreviewPlacerView =
+ new DrawingPreviewPlacerView(context, attrs);
final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
@@ -185,7 +187,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
final int gestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0);
- mKeyTimerHandler = new TimerHandler(
+ mTimerHandler = new TimerHandler(
this, ignoreAltCodeKeyTimeout, gestureRecognitionUpdateTime);
final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
@@ -195,7 +197,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mKeyDetector = new KeyDetector(
keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
- PointerTracker.init(mainKeyboardViewAttr, mKeyTimerHandler, this /* DrawingProxy */);
+ PointerTracker.init(mainKeyboardViewAttr, mTimerHandler, this /* DrawingProxy */);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final boolean forceNonDistinctMultitouch = prefs.getBoolean(
@@ -245,15 +247,17 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mGestureFloatingTextDrawingPreview = new GestureFloatingTextDrawingPreview(
mainKeyboardViewAttr);
- mGestureFloatingTextDrawingPreview.setDrawingView(mDrawingPreviewPlacerView);
+ mGestureFloatingTextDrawingPreview.setDrawingView(drawingPreviewPlacerView);
mGestureTrailsDrawingPreview = new GestureTrailsDrawingPreview(mainKeyboardViewAttr);
- mGestureTrailsDrawingPreview.setDrawingView(mDrawingPreviewPlacerView);
+ mGestureTrailsDrawingPreview.setDrawingView(drawingPreviewPlacerView);
mSlidingKeyInputDrawingPreview = new SlidingKeyInputDrawingPreview(mainKeyboardViewAttr);
- mSlidingKeyInputDrawingPreview.setDrawingView(mDrawingPreviewPlacerView);
+ mSlidingKeyInputDrawingPreview.setDrawingView(drawingPreviewPlacerView);
mainKeyboardViewAttr.recycle();
+ mDrawingPreviewPlacerView = drawingPreviewPlacerView;
+
final LayoutInflater inflater = LayoutInflater.from(getContext());
mMoreKeysKeyboardContainer = inflater.inflate(moreKeysKeyboardLayoutId, null);
mMoreKeysKeyboardForActionContainer = inflater.inflate(
@@ -306,17 +310,24 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
animatorToStart.setCurrentPlayTime(startTime);
}
- // Implements {@link TimerHander.Callbacks} method.
- @Override
- public void startWhileTypingFadeinAnimation() {
- cancelAndStartAnimators(
- mAltCodeKeyWhileTypingFadeoutAnimator, mAltCodeKeyWhileTypingFadeinAnimator);
- }
-
+ // Implements {@link DrawingProxy#startWhileTypingAnimation(int)}.
+ /**
+ * Called when a while-typing-animation should be started.
+ * @param fadeInOrOut {@link DrawingProxy#FADE_IN} starts while-typing-fade-in animation.
+ * {@link DrawingProxy#FADE_OUT} starts while-typing-fade-out animation.
+ */
@Override
- public void startWhileTypingFadeoutAnimation() {
- cancelAndStartAnimators(
- mAltCodeKeyWhileTypingFadeinAnimator, mAltCodeKeyWhileTypingFadeoutAnimator);
+ public void startWhileTypingAnimation(final int fadeInOrOut) {
+ switch (fadeInOrOut) {
+ case DrawingProxy.FADE_IN:
+ cancelAndStartAnimators(
+ mAltCodeKeyWhileTypingFadeoutAnimator, mAltCodeKeyWhileTypingFadeinAnimator);
+ break;
+ case DrawingProxy.FADE_OUT:
+ cancelAndStartAnimators(
+ mAltCodeKeyWhileTypingFadeinAnimator, mAltCodeKeyWhileTypingFadeoutAnimator);
+ break;
+ }
}
@ExternallyReferenced
@@ -378,7 +389,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.cancelLongPressTimers();
+ mTimerHandler.cancelLongPressTimers();
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
@@ -450,19 +461,17 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
windowContentView.addView(mDrawingPreviewPlacerView);
}
- // Implements {@link DrawingHandler.Callbacks} method.
+ // Implements {@link DrawingProxy#onKeyPressed(Key,boolean)}.
@Override
- public void dismissAllKeyPreviews() {
- mKeyPreviewChoreographer.dismissAllKeyPreviews();
- PointerTracker.setReleasedKeyGraphicsToAllKeys();
+ public void onKeyPressed(@Nonnull final Key key, final boolean withPreview) {
+ key.onPressed();
+ invalidateKey(key);
+ if (withPreview && !key.noKeyPreview()) {
+ showKeyPreview(key);
+ }
}
- @Override
- public void showKeyPreview(final Key key) {
- // If the key is invalid or has no key preview, we must not show key preview.
- if (key == null || key.noKeyPreview()) {
- return;
- }
+ private void showKeyPreview(@Nonnull final Key key) {
final Keyboard keyboard = getKeyboard();
if (keyboard == null) {
return;
@@ -475,26 +484,36 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
locatePreviewPlacerView();
getLocationInWindow(mOriginCoords);
- mKeyPreviewChoreographer.placeAndShowKeyPreview(key, keyboard.mIconsSet, mKeyDrawParams,
+ mKeyPreviewChoreographer.placeAndShowKeyPreview(key, keyboard.mIconsSet, getKeyDrawParams(),
getWidth(), mOriginCoords, mDrawingPreviewPlacerView, isHardwareAccelerated());
}
- // Implements {@link TimerHandler.Callbacks} method.
- @Override
- public void dismissKeyPreviewWithoutDelay(final Key key) {
+ private void dismissKeyPreviewWithoutDelay(@Nonnull final Key key) {
mKeyPreviewChoreographer.dismissKeyPreview(key, false /* withAnimation */);
- // To redraw key top letter.
invalidateKey(key);
}
+ // Implements {@link DrawingProxy#onKeyReleased(Key,boolean)}.
@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);
+ public void onKeyReleased(@Nonnull final Key key, final boolean withAnimation) {
+ key.onReleased();
+ invalidateKey(key);
+ if (!key.noKeyPreview()) {
+ if (withAnimation) {
+ dismissKeyPreview(key);
+ } else {
+ dismissKeyPreviewWithoutDelay(key);
+ }
+ }
+ }
+
+ private void dismissKeyPreview(@Nonnull final Key key) {
+ if (isHardwareAccelerated()) {
+ mKeyPreviewChoreographer.dismissKeyPreview(key, true /* withAnimation */);
return;
}
- mKeyPreviewChoreographer.dismissKeyPreview(key, true /* withAnimation */);
+ // TODO: Implement preference option to control key preview method and duration.
+ mTimerHandler.postDismissKeyPreview(key, mKeyPreviewDrawParams.getLingerTimeout());
}
public void setSlidingKeyInputPreviewEnabled(final boolean enabled) {
@@ -502,14 +521,13 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
@Override
- public void showSlidingKeyInputPreview(final PointerTracker tracker) {
+ public void showSlidingKeyInputPreview(@Nullable final PointerTracker tracker) {
locatePreviewPlacerView();
- mSlidingKeyInputDrawingPreview.setPreviewPosition(tracker);
- }
-
- @Override
- public void dismissSlidingKeyInputPreview() {
- mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview();
+ if (tracker != null) {
+ mSlidingKeyInputDrawingPreview.setPreviewPosition(tracker);
+ } else {
+ mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview();
+ }
}
private void setGesturePreviewMode(final boolean isGestureTrailEnabled,
@@ -518,20 +536,26 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mGestureTrailsDrawingPreview.setPreviewEnabled(isGestureTrailEnabled);
}
- // Implements {@link DrawingHandler.Callbacks} method.
- @Override
- public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
+ public void showGestureFloatingPreviewText(@Nonnull final SuggestedWords suggestedWords,
+ final boolean dismissDelayed) {
locatePreviewPlacerView();
- mGestureFloatingTextDrawingPreview.setSuggetedWords(suggestedWords);
+ final GestureFloatingTextDrawingPreview gestureFloatingTextDrawingPreview =
+ mGestureFloatingTextDrawingPreview;
+ gestureFloatingTextDrawingPreview.setSuggetedWords(suggestedWords);
+ if (dismissDelayed) {
+ mTimerHandler.postDismissGestureFloatingPreviewText(
+ mGestureFloatingPreviewTextLingerTimeout);
+ }
}
- public void dismissGestureFloatingPreviewText() {
- locatePreviewPlacerView();
- mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
+ // Implements {@link DrawingProxy#dismissGestureFloatingPreviewTextWithoutDelay()}.
+ @Override
+ public void dismissGestureFloatingPreviewTextWithoutDelay() {
+ mGestureFloatingTextDrawingPreview.dismissGestureFloatingPreviewText();
}
@Override
- public void showGestureTrail(final PointerTracker tracker,
+ public void showGestureTrail(@Nonnull final PointerTracker tracker,
final boolean showsFloatingPreviewText) {
locatePreviewPlacerView();
if (showsFloatingPreviewText) {
@@ -566,7 +590,11 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mDrawingPreviewPlacerView.removeAllViews();
}
- private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) {
+ // Implements {@link DrawingProxy@showMoreKeysKeyboard(Key,PointerTracker)}.
+ @Override
+ @Nullable
+ public MoreKeysPanel showMoreKeysKeyboard(@Nonnull final Key key,
+ @Nonnull final PointerTracker tracker) {
final MoreKeySpec[] moreKeys = key.getMoreKeys();
if (moreKeys == null) {
return null;
@@ -582,7 +610,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
&& !key.noKeyPreview() && moreKeys.length == 1
&& mKeyPreviewDrawParams.getVisibleWidth() > 0;
final MoreKeysKeyboard.Builder builder = new MoreKeysKeyboard.Builder(
- context, key, getKeyboard(), isSingleMoreKeyWithPreview,
+ getContext(), key, getKeyboard(), isSingleMoreKeyWithPreview,
mKeyPreviewDrawParams.getVisibleWidth(),
mKeyPreviewDrawParams.getVisibleHeight(), newLabelPaint(key));
moreKeysKeyboard = builder.build();
@@ -595,50 +623,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
(MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view);
moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- return moreKeysKeyboardView;
- }
-
- // Implements {@link TimerHandler.Callbacks} method.
- /**
- * Called when a key is long pressed.
- * @param tracker the pointer tracker which pressed the parent key
- */
- @Override
- public void onLongPress(final PointerTracker tracker) {
- if (isShowingMoreKeysPanel()) {
- return;
- }
- final Key key = tracker.getKey();
- if (key == null) {
- return;
- }
- final KeyboardActionListener listener = mKeyboardActionListener;
- if (key.hasNoPanelAutoMoreKey()) {
- 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, false /* isKeyRepeat */);
- listener.onReleaseKey(moreKeyCode, false /* withSliding */);
- return;
- }
- final int code = key.getCode();
- if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
- // Long pressing the space key invokes IME switcher dialog.
- if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
- tracker.onLongPressed();
- listener.onReleaseKey(code, false /* withSliding */);
- return;
- }
- }
- openMoreKeysPanel(key, tracker);
- }
-
- private void openMoreKeysPanel(final Key key, final PointerTracker tracker) {
- final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext());
- if (moreKeysPanel == null) {
- return;
- }
final int[] lastCoords = CoordinateUtils.newInstance();
tracker.getLastCoordinates(lastCoords);
@@ -656,10 +640,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
// {@code mPreviewVisibleOffset} has been set appropriately in
// {@link KeyboardView#showKeyPreview(PointerTracker)}.
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);
+ moreKeysKeyboardView.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener);
+ return moreKeysKeyboardView;
}
public boolean isInDraggingFinger() {
@@ -672,9 +654,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
locatePreviewPlacerView();
+ // Dismiss another {@link MoreKeysPanel} that may be being showed.
+ onDismissMoreKeysPanel();
+ // Dismiss all key previews that may be being showed.
+ PointerTracker.setReleasedKeyGraphicsToAllKeys();
+ // Dismiss sliding key input preview that may be being showed.
+ mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview();
panel.showInParent(mDrawingPreviewPlacerView);
mMoreKeysPanel = panel;
- dimEntireKeyboard(true /* dimmed */);
}
public boolean isShowingMoreKeysPanel() {
@@ -688,7 +675,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
public void onDismissMoreKeysPanel() {
- dimEntireKeyboard(false /* dimmed */);
if (isShowingMoreKeysPanel()) {
mMoreKeysPanel.removeFromParent();
mMoreKeysPanel = null;
@@ -696,37 +682,37 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
public void startDoubleTapShiftKeyTimer() {
- mKeyTimerHandler.startDoubleTapShiftKeyTimer();
+ mTimerHandler.startDoubleTapShiftKeyTimer();
}
public void cancelDoubleTapShiftKeyTimer() {
- mKeyTimerHandler.cancelDoubleTapShiftKeyTimer();
+ mTimerHandler.cancelDoubleTapShiftKeyTimer();
}
public boolean isInDoubleTapShiftKeyTimeout() {
- return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout();
+ return mTimerHandler.isInDoubleTapShiftKeyTimeout();
}
@Override
- public boolean onTouchEvent(final MotionEvent me) {
+ public boolean onTouchEvent(final MotionEvent event) {
if (getKeyboard() == null) {
return false;
}
if (mNonDistinctMultitouchHelper != null) {
- if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) {
+ if (event.getPointerCount() > 1 && mTimerHandler.isInKeyRepeat()) {
// Key repeating timer will be canceled if 2 or more keys are in action.
- mKeyTimerHandler.cancelKeyRepeatTimers();
+ mTimerHandler.cancelKeyRepeatTimers();
}
// Non distinct multitouch screen support
- mNonDistinctMultitouchHelper.processMotionEvent(me, mKeyDetector);
+ mNonDistinctMultitouchHelper.processMotionEvent(event, mKeyDetector);
return true;
}
- return processMotionEvent(me);
+ return processMotionEvent(event);
}
- public boolean processMotionEvent(final MotionEvent me) {
- final int index = me.getActionIndex();
- final int id = me.getPointerId(index);
+ public boolean processMotionEvent(final MotionEvent event) {
+ final int index = event.getActionIndex();
+ final int id = event.getPointerId(index);
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.
@@ -734,16 +720,15 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
&& PointerTracker.getActivePointerTrackerCount() == 1) {
return true;
}
- tracker.processMotionEvent(me, mKeyDetector);
+ tracker.processMotionEvent(event, mKeyDetector);
return true;
}
public void cancelAllOngoingEvents() {
- mKeyTimerHandler.cancelAllMessages();
- mDrawingHandler.cancelAllMessages();
- dismissAllKeyPreviews();
- dismissGestureFloatingPreviewText();
- dismissSlidingKeyInputPreview();
+ mTimerHandler.cancelAllMessages();
+ PointerTracker.setReleasedKeyGraphicsToAllKeys();
+ mGestureFloatingTextDrawingPreview.dismissGestureFloatingPreviewText();
+ mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview();
PointerTracker.dismissAllMoreKeysPanels();
PointerTracker.cancelAllPointerTrackers();
}
@@ -798,10 +783,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
if (animator == null) {
- mLanguageOnSpacebarFormatType = LanguageOnSpacebarHelper.FORMAT_TYPE_NONE;
+ mLanguageOnSpacebarFormatType = LanguageOnSpacebarUtils.FORMAT_TYPE_NONE;
} else {
if (subtypeChanged
- && languageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) {
+ && languageOnSpacebarFormatType != LanguageOnSpacebarUtils.FORMAT_TYPE_NONE) {
setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE);
if (animator.isStarted()) {
animator.cancel();
@@ -816,24 +801,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
invalidateKey(mSpaceKey);
}
- private void dimEntireKeyboard(final boolean dimmed) {
- final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
- mNeedsToDimEntireKeyboard = dimmed;
- if (needsRedrawing) {
- invalidateAllKeys();
- }
- }
-
- @Override
- protected void onDraw(final Canvas canvas) {
- super.onDraw(canvas);
-
- // Overlay a dark rectangle to dim.
- if (mNeedsToDimEntireKeyboard) {
- canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
- }
- }
-
@Override
protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
final KeyDrawParams params) {
@@ -844,7 +811,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final int code = key.getCode();
if (code == Constants.CODE_SPACE) {
// If input language are explicitly selected.
- if (mLanguageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) {
+ if (mLanguageOnSpacebarFormatType != LanguageOnSpacebarUtils.FORMAT_TYPE_NONE) {
drawLanguageOnSpacebar(key, canvas, paint);
}
// Whether space key needs to show the "..." popup hint for special purposes
@@ -875,16 +842,16 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
// Layout language name on spacebar.
private String layoutLanguageOnSpacebar(final Paint paint,
- final InputMethodSubtype subtype, final int width) {
+ final RichInputMethodSubtype subtype, final int width) {
// Choose appropriate language name to fit into the width.
- if (mLanguageOnSpacebarFormatType == LanguageOnSpacebarHelper.FORMAT_TYPE_FULL_LOCALE) {
- final String fullText = SpacebarLanguageUtils.getFullDisplayName(subtype);
+ if (mLanguageOnSpacebarFormatType == LanguageOnSpacebarUtils.FORMAT_TYPE_FULL_LOCALE) {
+ final String fullText = subtype.getFullDisplayName();
if (fitsTextIntoWidth(width, fullText, paint)) {
return fullText;
}
}
- final String middleText = SpacebarLanguageUtils.getMiddleDisplayName(subtype);
+ final String middleText = subtype.getMiddleDisplayName();
if (fitsTextIntoWidth(width, middleText, paint)) {
return middleText;
}
@@ -893,13 +860,16 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
private void drawLanguageOnSpacebar(final Key key, final Canvas canvas, final Paint paint) {
+ final Keyboard keyboard = getKeyboard();
+ if (keyboard == null) {
+ return;
+ }
final int width = key.getWidth();
final int height = key.getHeight();
paint.setTextAlign(Align.CENTER);
paint.setTypeface(Typeface.DEFAULT);
paint.setTextSize(mLanguageOnSpacebarTextSize);
- final InputMethodSubtype subtype = getKeyboard().mId.mSubtype;
- final String language = layoutLanguageOnSpacebar(paint, subtype, width);
+ final String language = layoutLanguageOnSpacebar(paint, keyboard.mId.mSubtype, width);
// Draw language text with shadow
final float descent = paint.descent();
final float textHeight = -paint.ascent() + descent;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index abcfff8a6..a021e5e2d 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -24,9 +24,11 @@ import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.keyboard.internal.MoreKeySpec;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.utils.TypefaceUtils;
+import javax.annotation.Nonnull;
+
public final class MoreKeysKeyboard extends Keyboard {
private final int mDefaultKeyCoordX;
@@ -328,6 +330,7 @@ public final class MoreKeysKeyboard extends Keyboard {
}
@Override
+ @Nonnull
public MoreKeysKeyboard build() {
final MoreKeysKeyboardParams params = mParams;
final int moreKeyFlags = mParentKey.getMoreKeyLabelFlags();
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 841283b7f..3acc11b59 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -29,9 +29,9 @@ import android.view.ViewGroup;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.accessibility.MoreKeysKeyboardAccessibilityDelegate;
import com.android.inputmethod.keyboard.internal.KeyDrawParams;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.CoordinateUtils;
/**
* A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 49288ade4..9764cb389 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -25,22 +25,27 @@ import android.view.MotionEvent;
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.DrawingProxy;
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.TimerProxy;
import com.android.inputmethod.keyboard.internal.TypingTimeRecorder;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.CoordinateUtils;
+import com.android.inputmethod.latin.common.InputPointers;
import com.android.inputmethod.latin.define.DebugFlags;
import com.android.inputmethod.latin.settings.Settings;
-import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
import java.util.ArrayList;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
public final class PointerTracker implements PointerTrackerQueue.Element,
BatchInputArbiterListener {
private static final String TAG = PointerTracker.class.getSimpleName();
@@ -49,60 +54,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
private static final boolean DEBUG_LISTENER = false;
private static boolean DEBUG_MODE = DebugFlags.DEBUG_ENABLED || DEBUG_EVENT;
- public interface DrawingProxy {
- public void invalidateKey(Key key);
- 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);
- }
-
- public interface TimerProxy {
- public void startTypingStateTimer(Key typedKey);
- public boolean isTypingState();
- 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 startUpdateBatchInputTimer(PointerTracker tracker);
- public void cancelUpdateBatchInputTimer(PointerTracker tracker);
- public void cancelAllUpdateBatchInputTimers();
-
- public static class Adapter implements TimerProxy {
- @Override
- public void startTypingStateTimer(Key typedKey) {}
- @Override
- public boolean isTypingState() { return false; }
- @Override
- public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay) {}
- @Override
- public void startLongPressTimerOf(PointerTracker tracker, int delay) {}
- @Override
- public void cancelLongPressTimerOf(PointerTracker tracker) {}
- @Override
- public void cancelLongPressShiftKeyTimers() {}
- @Override
- public void cancelKeyTimersOf(PointerTracker tracker) {}
- @Override
- public void startDoubleTapShiftKeyTimer() {}
- @Override
- public void cancelDoubleTapShiftKeyTimer() {}
- @Override
- public boolean isInDoubleTapShiftKeyTimeout() { return false; }
- @Override
- public void startUpdateBatchInputTimer(PointerTracker tracker) {}
- @Override
- public void cancelUpdateBatchInputTimer(PointerTracker tracker) {}
- @Override
- public void cancelAllUpdateBatchInputTimers() {}
- }
- }
-
static final class PointerTrackerParams {
public final boolean mKeySelectionByDraggingFinger;
public final int mTouchNoiseThresholdTime;
@@ -163,6 +114,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
// The position and time at which first down event occurred.
private long mDownTime;
+ @Nonnull
private int[] mDownCoordinates = CoordinateUtils.newInstance();
private long mUpTime;
@@ -270,7 +222,7 @@ 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);
- tracker.setReleasedKeyGraphics(tracker.getKey());
+ tracker.setReleasedKeyGraphics(tracker.getKey(), true /* withAnimation */);
}
}
@@ -416,6 +368,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
return mIsInDraggingFinger;
}
+ @Nullable
public Key getKey() {
return mCurrentKey;
}
@@ -429,19 +382,17 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
return mKeyDetector.detectHitKey(x, y);
}
- private void setReleasedKeyGraphics(final Key key) {
- sDrawingProxy.dismissKeyPreview(key);
+ private void setReleasedKeyGraphics(@Nullable final Key key, final boolean withAnimation) {
if (key == null) {
return;
}
- // Even if the key is disabled, update the key release graphics just in case.
- updateReleaseKeyGraphics(key);
+ sDrawingProxy.onKeyReleased(key, withAnimation);
if (key.isShift()) {
for (final Key shiftKey : mKeyboard.mShiftKeys) {
if (shiftKey != key) {
- updateReleaseKeyGraphics(shiftKey);
+ sDrawingProxy.onKeyReleased(shiftKey, false /* withAnimation */);
}
}
}
@@ -450,11 +401,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
final int altCode = key.getAltCode();
final Key altKey = mKeyboard.getKey(altCode);
if (altKey != null) {
- updateReleaseKeyGraphics(altKey);
+ sDrawingProxy.onKeyReleased(altKey, false /* withAnimation */);
}
for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
if (k != key && k.getAltCode() == altCode) {
- updateReleaseKeyGraphics(k);
+ sDrawingProxy.onKeyReleased(k, false /* withAnimation */);
}
}
}
@@ -465,7 +416,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
return sTypingTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime);
}
- private void setPressedKeyGraphics(final Key key, final long eventTime) {
+ private void setPressedKeyGraphics(@Nullable final Key key, final long eventTime) {
if (key == null) {
return;
}
@@ -477,15 +428,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
return;
}
- if (!key.noKeyPreview() && !sInGesture && !needsToSuppressKeyPreviewPopup(eventTime)) {
- sDrawingProxy.showKeyPreview(key);
- }
- updatePressKeyGraphics(key);
+ final boolean noKeyPreview = sInGesture || needsToSuppressKeyPreviewPopup(eventTime);
+ sDrawingProxy.onKeyPressed(key, !noKeyPreview);
if (key.isShift()) {
for (final Key shiftKey : mKeyboard.mShiftKeys) {
if (shiftKey != key) {
- updatePressKeyGraphics(shiftKey);
+ sDrawingProxy.onKeyPressed(shiftKey, false /* withPreview */);
}
}
}
@@ -494,31 +443,21 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
final int altCode = key.getAltCode();
final Key altKey = mKeyboard.getKey(altCode);
if (altKey != null) {
- updatePressKeyGraphics(altKey);
+ sDrawingProxy.onKeyPressed(altKey, false /* withPreview */);
}
for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
if (k != key && k.getAltCode() == altCode) {
- updatePressKeyGraphics(k);
+ sDrawingProxy.onKeyPressed(k, false /* withPreview */);
}
}
}
}
- private static void updateReleaseKeyGraphics(final Key key) {
- key.onReleased();
- sDrawingProxy.invalidateKey(key);
- }
-
- private static void updatePressKeyGraphics(final Key key) {
- key.onPressed();
- sDrawingProxy.invalidateKey(key);
- }
-
public GestureStrokeDrawingPoints getGestureStrokeDrawingPoints() {
return mGestureStrokeDrawingPoints;
}
- public void getLastCoordinates(final int[] outCoords) {
+ public void getLastCoordinates(@Nonnull final int[] outCoords) {
CoordinateUtils.set(outCoords, mLastX, mLastY);
}
@@ -526,7 +465,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
return mDownTime;
}
- public void getDownCoordinates(final int[] outCoords) {
+ public void getDownCoordinates(@Nonnull final int[] outCoords) {
CoordinateUtils.copy(outCoords, mDownCoordinates);
}
@@ -575,7 +514,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
}
sListener.onStartBatchInput();
dismissAllMoreKeysPanels();
- sTimerProxy.cancelLongPressTimerOf(this);
+ sTimerProxy.cancelLongPressTimersOf(this);
}
private void showGestureTrail() {
@@ -765,7 +704,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
private void resetKeySelectionByDraggingFinger() {
mIsInDraggingFinger = false;
mIsInSlidingKeyInput = false;
- sDrawingProxy.dismissSlidingKeyInputPreview();
+ sDrawingProxy.showSlidingKeyInputPreview(null /* tracker */);
}
private void onGestureMoveEvent(final int x, final int y, final long eventTime,
@@ -884,7 +823,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
}
private void processDraggingFingerOutFromOldKey(final Key oldKey) {
- setReleasedKeyGraphics(oldKey);
+ setReleasedKeyGraphics(oldKey, true /* withAnimation */);
callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */);
startKeySelectionByDraggingFinger(oldKey);
sTimerProxy.cancelKeyTimersOf(this);
@@ -927,12 +866,12 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
}
onUpEvent(x, y, eventTime);
cancelTrackingForAction();
- setReleasedKeyGraphics(oldKey);
+ setReleasedKeyGraphics(oldKey, true /* withAnimation */);
} else {
if (!mIsDetectingGesture) {
cancelTrackingForAction();
}
- setReleasedKeyGraphics(oldKey);
+ setReleasedKeyGraphics(oldKey, true /* withAnimation */);
}
}
@@ -960,7 +899,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey);
if (sInGesture) {
mCurrentKey = null;
- setReleasedKeyGraphics(oldKey);
+ setReleasedKeyGraphics(oldKey, true /* withAnimation */);
return;
}
}
@@ -1025,7 +964,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
final int currentRepeatingKeyCode = mCurrentRepeatingKeyCode;
mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
// Release the last pressed key.
- setReleasedKeyGraphics(currentKey);
+ setReleasedKeyGraphics(currentKey, true /* withAnimation */);
if (isShowingMoreKeysPanel()) {
if (!mIsTrackingForActionDisabled) {
@@ -1062,14 +1001,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
}
}
- public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
- setReleasedKeyGraphics(mCurrentKey);
- final int translatedX = panel.translateX(mLastX);
- final int translatedY = panel.translateY(mLastY);
- panel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis());
- mMoreKeysPanel = panel;
- }
-
@Override
public void cancelTrackingForAction() {
if (isShowingMoreKeysPanel()) {
@@ -1082,14 +1013,49 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
return !mIsTrackingForActionDisabled;
}
- public void cancelLongPressTimer() {
- sTimerProxy.cancelLongPressTimerOf(this);
+ public void onLongPressed() {
+ sTimerProxy.cancelLongPressTimersOf(this);
+ if (isShowingMoreKeysPanel()) {
+ return;
+ }
+ final Key key = getKey();
+ if (key == null) {
+ return;
+ }
+ if (key.hasNoPanelAutoMoreKey()) {
+ cancelKeyTracking();
+ final int moreKeyCode = key.getMoreKeys()[0].mCode;
+ sListener.onPressKey(moreKeyCode, 0 /* repeatCont */, true /* isSinglePointer */);
+ sListener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE,
+ Constants.NOT_A_COORDINATE, false /* isKeyRepeat */);
+ sListener.onReleaseKey(moreKeyCode, false /* withSliding */);
+ return;
+ }
+ final int code = key.getCode();
+ if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
+ // Long pressing the space key invokes IME switcher dialog.
+ if (sListener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
+ cancelKeyTracking();
+ sListener.onReleaseKey(code, false /* withSliding */);
+ return;
+ }
+ }
+
+ setReleasedKeyGraphics(key, false /* withAnimation */);
+ final MoreKeysPanel moreKeysPanel = sDrawingProxy.showMoreKeysKeyboard(key, this);
+ if (moreKeysPanel == null) {
+ return;
+ }
+ final int translatedX = moreKeysPanel.translateX(mLastX);
+ final int translatedY = moreKeysPanel.translateY(mLastY);
+ moreKeysPanel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis());
+ mMoreKeysPanel = moreKeysPanel;
}
- public void onLongPressed() {
+ private void cancelKeyTracking() {
resetKeySelectionByDraggingFinger();
cancelTrackingForAction();
- setReleasedKeyGraphics(mCurrentKey);
+ setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */);
sPointerTrackerQueue.remove(this);
}
@@ -1106,7 +1072,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
private void onCancelEventInternal() {
sTimerProxy.cancelKeyTimersOf(this);
- setReleasedKeyGraphics(mCurrentKey);
+ setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */);
resetKeySelectionByDraggingFinger();
dismissMoreKeysPanel();
}
@@ -1152,7 +1118,7 @@ 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();
+ sTimerProxy.cancelLongPressShiftKeyTimer();
if (sInGesture) return;
if (key == null) return;
if (!key.isLongPressEnabled()) return;
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index c19cd671a..b9a5eaefb 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -17,11 +17,10 @@
package com.android.inputmethod.keyboard;
import android.graphics.Rect;
-import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.keyboard.internal.TouchPositionCorrection;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.utils.JniUtils;
import java.util.ArrayList;
@@ -29,6 +28,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import javax.annotation.Nonnull;
+
public class ProximityInfo {
private static final String TAG = ProximityInfo.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -37,6 +38,7 @@ public class ProximityInfo {
public static final int MAX_PROXIMITY_CHARS_SIZE = 16;
/** Number of key widths from current touch point to search for nearest keys. */
private static final float SEARCH_DISTANCE = 1.2f;
+ @Nonnull
private static final List<Key> EMPTY_KEY_LIST = Collections.emptyList();
private static final float DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS = 0.15f;
@@ -50,20 +52,16 @@ public class ProximityInfo {
private final int mKeyboardHeight;
private final int mMostCommonKeyWidth;
private final int mMostCommonKeyHeight;
+ @Nonnull
private final List<Key> mSortedKeys;
+ @Nonnull
private final List<Key>[] mGridNeighbors;
- private final String mLocaleStr;
@SuppressWarnings("unchecked")
- ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight,
- final int minWidth, final int height, final int mostCommonKeyWidth,
- final int mostCommonKeyHeight, final List<Key> sortedKeys,
- final TouchPositionCorrection touchPositionCorrection) {
- if (TextUtils.isEmpty(localeStr)) {
- mLocaleStr = "";
- } else {
- mLocaleStr = localeStr;
- }
+ ProximityInfo(final int gridWidth, final int gridHeight, final int minWidth, final int height,
+ final int mostCommonKeyWidth, final int mostCommonKeyHeight,
+ @Nonnull final List<Key> sortedKeys,
+ @Nonnull final TouchPositionCorrection touchPositionCorrection) {
mGridWidth = gridWidth;
mGridHeight = gridHeight;
mGridSize = mGridWidth * mGridHeight;
@@ -89,16 +87,15 @@ public class ProximityInfo {
}
// TODO: Stop passing proximityCharsArray
- private static native long setProximityInfoNative(String locale,
- int displayWidth, int displayHeight, int gridWidth, int gridHeight,
- int mostCommonKeyWidth, int mostCommonKeyHeight, int[] proximityCharsArray,
- int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, int[] keyWidths,
- int[] keyHeights, int[] keyCharCodes, float[] sweetSpotCenterXs,
+ private static native long setProximityInfoNative(int displayWidth, int displayHeight,
+ int gridWidth, int gridHeight, int mostCommonKeyWidth, int mostCommonKeyHeight,
+ int[] proximityCharsArray, int keyCount, int[] keyXCoordinates, int[] keyYCoordinates,
+ int[] keyWidths, int[] keyHeights, int[] keyCharCodes, float[] sweetSpotCenterXs,
float[] sweetSpotCenterYs, float[] sweetSpotRadii);
private static native void releaseProximityInfoNative(long nativeProximityInfo);
- private static boolean needsProximityInfo(final Key key) {
+ static boolean needsProximityInfo(final Key key) {
// Don't include special keys into ProximityInfo.
return key.getCode() >= Constants.CODE_SPACE;
}
@@ -113,7 +110,8 @@ public class ProximityInfo {
return count;
}
- private long createNativeProximityInfo(final TouchPositionCorrection touchPositionCorrection) {
+ private long createNativeProximityInfo(
+ @Nonnull final TouchPositionCorrection touchPositionCorrection) {
final List<Key>[] gridNeighborKeys = mGridNeighbors;
final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE);
@@ -172,7 +170,7 @@ public class ProximityInfo {
infoIndex++;
}
- if (touchPositionCorrection != null && touchPositionCorrection.isValid()) {
+ if (touchPositionCorrection.isValid()) {
if (DEBUG) {
Log.d(TAG, "touchPositionCorrection: ON");
}
@@ -221,10 +219,10 @@ public class ProximityInfo {
}
// TODO: Stop passing proximityCharsArray
- return setProximityInfoNative(mLocaleStr, mKeyboardMinWidth, mKeyboardHeight,
- mGridWidth, mGridHeight, mMostCommonKeyWidth, mMostCommonKeyHeight,
- proximityCharsArray, keyCount, keyXCoordinates, keyYCoordinates, keyWidths,
- keyHeights, keyCharCodes, sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
+ return setProximityInfoNative(mKeyboardMinWidth, mKeyboardHeight, mGridWidth, mGridHeight,
+ mMostCommonKeyWidth, mMostCommonKeyHeight, proximityCharsArray, keyCount,
+ keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
+ sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
}
public long getNativeProximityInfo() {
@@ -394,10 +392,8 @@ y |---+---+---+---+-v-+-|-+---+---+---+---+---| | thresholdBase and get
}
}
+ @Nonnull
public List<Key> getNearestKeys(final int x, final int y) {
- if (mGridNeighbors == null) {
- return EMPTY_KEY_LIST;
- }
if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) {
int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth);
if (index < mGridSize) {
diff --git a/java/src/com/android/inputmethod/keyboard/TextDecorator.java b/java/src/com/android/inputmethod/keyboard/TextDecorator.java
deleted file mode 100644
index c22717f95..000000000
--- a/java/src/com/android/inputmethod/keyboard/TextDecorator.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.keyboard;
-
-import android.graphics.Matrix;
-import android.graphics.RectF;
-import android.inputmethodservice.InputMethodService;
-import android.os.Message;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.view.inputmethod.CursorAnchorInfo;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
-import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
-
-import javax.annotation.Nonnull;
-
-/**
- * A controller class of the add-to-dictionary indicator (a.k.a. TextDecorator). This class
- * is designed to be independent of UI subsystems such as {@link View}. All the UI related
- * operations are delegated to {@link TextDecoratorUi} via {@link TextDecoratorUiOperator}.
- */
-public class TextDecorator {
- private static final String TAG = TextDecorator.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private static final int INVALID_CURSOR_INDEX = -1;
-
- private static final int MODE_MONITOR = 0;
- private static final int MODE_WAITING_CURSOR_INDEX = 1;
- private static final int MODE_SHOWING_INDICATOR = 2;
-
- private int mMode = MODE_MONITOR;
-
- private String mLastComposingText = null;
- private boolean mHasRtlCharsInLastComposingText = false;
- private RectF mComposingTextBoundsForLastComposingText = new RectF();
-
- private boolean mIsFullScreenMode = false;
- private String mWaitingWord = null;
- private int mWaitingCursorStart = INVALID_CURSOR_INDEX;
- private int mWaitingCursorEnd = INVALID_CURSOR_INDEX;
- private CursorAnchorInfoCompatWrapper mCursorAnchorInfoWrapper = null;
-
- @Nonnull
- private final Listener mListener;
-
- @Nonnull
- private TextDecoratorUiOperator mUiOperator = EMPTY_UI_OPERATOR;
-
- public interface Listener {
- /**
- * Called when the user clicks the indicator to add the word into the dictionary.
- * @param word the word which the user clicked on.
- */
- void onClickComposingTextToAddToDictionary(final String word);
- }
-
- public TextDecorator(final Listener listener) {
- mListener = (listener != null) ? listener : EMPTY_LISTENER;
- }
-
- /**
- * Sets the UI operator for {@link TextDecorator}. Any user visible operations will be
- * delegated to the associated UI operator.
- * @param uiOperator the UI operator to be associated.
- */
- public void setUiOperator(final TextDecoratorUiOperator uiOperator) {
- mUiOperator.disposeUi();
- mUiOperator = uiOperator;
- mUiOperator.setOnClickListener(getOnClickHandler());
- }
-
- private final Runnable mDefaultOnClickHandler = new Runnable() {
- @Override
- public void run() {
- onClickIndicator();
- }
- };
-
- @UsedForTesting
- final Runnable getOnClickHandler() {
- return mDefaultOnClickHandler;
- }
-
- /**
- * Shows the "Add to dictionary" indicator and associates it with associating the given word.
- *
- * @param word the word which should be associated with the indicator. This object will be
- * passed back in {@link Listener#onClickComposingTextToAddToDictionary(String)}.
- * @param selectionStart the cursor index (inclusive) when the indicator should be displayed.
- * @param selectionEnd the cursor index (exclusive) when the indicator should be displayed.
- */
- public void showAddToDictionaryIndicator(final String word, final int selectionStart,
- final int selectionEnd) {
- mWaitingWord = word;
- mWaitingCursorStart = selectionStart;
- mWaitingCursorEnd = selectionEnd;
- mMode = MODE_WAITING_CURSOR_INDEX;
- layoutLater();
- return;
- }
-
- /**
- * Must be called when the input method is about changing to for from the full screen mode.
- * @param fullScreenMode {@code true} if the input method is entering the full screen mode.
- * {@code false} is the input method is finishing the full screen mode.
- */
- public void notifyFullScreenMode(final boolean fullScreenMode) {
- final boolean fullScreenModeChanged = (mIsFullScreenMode != fullScreenMode);
- mIsFullScreenMode = fullScreenMode;
- if (fullScreenModeChanged) {
- layoutLater();
- }
- }
-
- /**
- * Resets previous requests and makes indicator invisible.
- */
- public void reset() {
- mWaitingWord = null;
- mMode = MODE_MONITOR;
- mWaitingCursorStart = INVALID_CURSOR_INDEX;
- mWaitingCursorEnd = INVALID_CURSOR_INDEX;
- cancelLayoutInternalExpectedly("Resetting internal state.");
- }
-
- /**
- * Must be called when the {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}
- * is called.
- *
- * <p>CAVEAT: Currently the input method author is responsible for ignoring
- * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} called in full screen
- * mode.</p>
- * @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}.
- */
- public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
- mCursorAnchorInfoWrapper = info;
- // Do not use layoutLater() to minimize the latency.
- layoutImmediately();
- }
-
- private void cancelLayoutInternalUnexpectedly(final String message) {
- mUiOperator.hideUi();
- Log.d(TAG, message);
- }
-
- private void cancelLayoutInternalExpectedly(final String message) {
- mUiOperator.hideUi();
- if (DEBUG) {
- Log.d(TAG, message);
- }
- }
-
- private void layoutLater() {
- mLayoutInvalidator.invalidateLayout();
- }
-
-
- private void layoutImmediately() {
- // Clear pending layout requests.
- mLayoutInvalidator.cancelInvalidateLayout();
- layoutMain();
- }
-
- private void layoutMain() {
- final CursorAnchorInfoCompatWrapper info = mCursorAnchorInfoWrapper;
-
- if (info == null || !info.isAvailable()) {
- cancelLayoutInternalExpectedly("CursorAnchorInfo isn't available.");
- return;
- }
-
- final Matrix matrix = info.getMatrix();
- if (matrix == null) {
- cancelLayoutInternalUnexpectedly("Matrix is null");
- }
-
- final CharSequence composingText = info.getComposingText();
- if (!TextUtils.isEmpty(composingText)) {
- final int composingTextStart = info.getComposingTextStart();
- final int lastCharRectIndex = composingTextStart + composingText.length() - 1;
- final RectF lastCharRect = info.getCharacterBounds(lastCharRectIndex);
- final int lastCharRectFlags = info.getCharacterBoundsFlags(lastCharRectIndex);
- final boolean hasInvisibleRegionInLastCharRect =
- (lastCharRectFlags & CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION)
- != 0;
- if (lastCharRect == null || matrix == null || hasInvisibleRegionInLastCharRect) {
- mUiOperator.hideUi();
- return;
- }
-
- // Note that the following layout information is fragile, and must be invalidated
- // even when surrounding text next to the composing text is changed because it can
- // affect how the composing text is rendered.
- // TODO: Investigate if we can change the input logic to make the target text
- // composing state so that we can retrieve the character bounds reliably.
- final String composingTextString = composingText.toString();
- final float top = lastCharRect.top;
- final float bottom = lastCharRect.bottom;
- float left = lastCharRect.left;
- float right = lastCharRect.right;
- boolean useRtlLayout = false;
- for (int i = composingText.length() - 1; i >= 0; --i) {
- final int characterIndex = composingTextStart + i;
- final RectF characterBounds = info.getCharacterBounds(characterIndex);
- final int characterBoundsFlags = info.getCharacterBoundsFlags(characterIndex);
- if (characterBounds == null) {
- break;
- }
- if (characterBounds.top != top) {
- break;
- }
- if (characterBounds.bottom != bottom) {
- break;
- }
- if ((characterBoundsFlags & CursorAnchorInfoCompatWrapper.FLAG_IS_RTL) != 0) {
- // This is for both RTL text and bi-directional text. RTL languages usually mix
- // RTL characters with LTR characters and in this case we should display the
- // indicator on the left, while in LTR languages that normally never happens.
- // TODO: Try to come up with a better algorithm.
- useRtlLayout = true;
- }
- left = Math.min(characterBounds.left, left);
- right = Math.max(characterBounds.right, right);
- }
- mLastComposingText = composingTextString;
- mHasRtlCharsInLastComposingText = useRtlLayout;
- mComposingTextBoundsForLastComposingText.set(left, top, right, bottom);
- }
-
- final int selectionStart = info.getSelectionStart();
- final int selectionEnd = info.getSelectionEnd();
- switch (mMode) {
- case MODE_MONITOR:
- mUiOperator.hideUi();
- return;
- case MODE_WAITING_CURSOR_INDEX:
- if (selectionStart != mWaitingCursorStart || selectionEnd != mWaitingCursorEnd) {
- mUiOperator.hideUi();
- return;
- }
- mMode = MODE_SHOWING_INDICATOR;
- break;
- case MODE_SHOWING_INDICATOR:
- if (selectionStart != mWaitingCursorStart || selectionEnd != mWaitingCursorEnd) {
- mUiOperator.hideUi();
- mMode = MODE_MONITOR;
- mWaitingCursorStart = INVALID_CURSOR_INDEX;
- mWaitingCursorEnd = INVALID_CURSOR_INDEX;
- return;
- }
- break;
- default:
- cancelLayoutInternalUnexpectedly("Unexpected internal mode=" + mMode);
- return;
- }
-
- if (!TextUtils.equals(mLastComposingText, mWaitingWord)) {
- cancelLayoutInternalUnexpectedly("mLastComposingText doesn't match mWaitingWord");
- return;
- }
-
- if ((info.getInsertionMarkerFlags() &
- CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION) != 0) {
- mUiOperator.hideUi();
- return;
- }
-
- mUiOperator.layoutUi(matrix, mComposingTextBoundsForLastComposingText,
- mHasRtlCharsInLastComposingText);
- }
-
- private void onClickIndicator() {
- if (mMode != MODE_SHOWING_INDICATOR) {
- return;
- }
- mListener.onClickComposingTextToAddToDictionary(mWaitingWord);
- }
-
- private final LayoutInvalidator mLayoutInvalidator = new LayoutInvalidator(this);
-
- /**
- * Used for managing pending layout tasks for {@link TextDecorator#layoutLater()}.
- */
- private static final class LayoutInvalidator {
- private final HandlerImpl mHandler;
- public LayoutInvalidator(final TextDecorator ownerInstance) {
- mHandler = new HandlerImpl(ownerInstance);
- }
-
- private static final int MSG_LAYOUT = 0;
-
- private static final class HandlerImpl
- extends LeakGuardHandlerWrapper<TextDecorator> {
- public HandlerImpl(final TextDecorator ownerInstance) {
- super(ownerInstance);
- }
-
- @Override
- public void handleMessage(final Message msg) {
- final TextDecorator owner = getOwnerInstance();
- if (owner == null) {
- return;
- }
- switch (msg.what) {
- case MSG_LAYOUT:
- owner.layoutMain();
- break;
- }
- }
- }
-
- /**
- * Puts a layout task into the scheduler. Does nothing if one or more layout tasks are
- * already scheduled.
- */
- public void invalidateLayout() {
- if (!mHandler.hasMessages(MSG_LAYOUT)) {
- mHandler.obtainMessage(MSG_LAYOUT).sendToTarget();
- }
- }
-
- /**
- * Clears the pending layout tasks.
- */
- public void cancelInvalidateLayout() {
- mHandler.removeMessages(MSG_LAYOUT);
- }
- }
-
- private final static Listener EMPTY_LISTENER = new Listener() {
- @Override
- public void onClickComposingTextToAddToDictionary(final String word) {
- }
- };
-
- private final static TextDecoratorUiOperator EMPTY_UI_OPERATOR = new TextDecoratorUiOperator() {
- @Override
- public void disposeUi() {
- }
- @Override
- public void hideUi() {
- }
- @Override
- public void setOnClickListener(Runnable listener) {
- }
- @Override
- public void layoutUi(Matrix matrix, RectF composingTextBounds, boolean useRtlLayout) {
- }
- };
-}
diff --git a/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java b/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java
deleted file mode 100644
index d87dc1bfa..000000000
--- a/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.keyboard;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
-import android.inputmethodservice.InputMethodService;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ViewParent;
-import android.widget.PopupWindow;
-import android.widget.RelativeLayout;
-
-import com.android.inputmethod.latin.R;
-
-/**
- * Used as the UI component of {@link TextDecorator}.
- */
-public final class TextDecoratorUi implements TextDecoratorUiOperator {
- private static final boolean VISUAL_DEBUG = false;
- private static final int VISUAL_DEBUG_HIT_AREA_COLOR = 0x80ff8000;
-
- private final RelativeLayout mLocalRootView;
- private final AddToDictionaryIndicatorView mAddToDictionaryIndicatorView;
- private final PopupWindow mTouchEventWindow;
- private final View mTouchEventWindowClickListenerView;
- private final float mHitAreaMarginInPixels;
- private final RectF mDisplayRect;
-
- /**
- * This constructor is designed to be called from {@link InputMethodService#setInputView(View)}.
- * Other usages are not supported.
- *
- * @param context the context of the input method.
- * @param inputView the view that is passed to {@link InputMethodService#setInputView(View)}.
- */
- public TextDecoratorUi(final Context context, final View inputView) {
- final Resources resources = context.getResources();
- final int hitAreaMarginInDP = resources.getInteger(
- R.integer.text_decorator_hit_area_margin_in_dp);
- mHitAreaMarginInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- hitAreaMarginInDP, resources.getDisplayMetrics());
- final DisplayMetrics displayMetrics = resources.getDisplayMetrics();
- mDisplayRect = new RectF(0.0f, 0.0f, displayMetrics.widthPixels,
- displayMetrics.heightPixels);
-
- mLocalRootView = new RelativeLayout(context);
- mLocalRootView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- // TODO: Use #setBackground(null) for API Level >= 16.
- mLocalRootView.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
-
- final ViewGroup contentView = getContentView(inputView);
- mAddToDictionaryIndicatorView = new AddToDictionaryIndicatorView(context);
- mLocalRootView.addView(mAddToDictionaryIndicatorView);
- if (contentView != null) {
- contentView.addView(mLocalRootView);
- }
-
- // This popup window is used to avoid the limitation that the input method is not able to
- // observe the touch events happening outside of InputMethodService.Insets#touchableRegion.
- // We don't use this popup window for rendering the UI for performance reasons though.
- mTouchEventWindow = new PopupWindow(context);
- if (VISUAL_DEBUG) {
- mTouchEventWindow.setBackgroundDrawable(new ColorDrawable(VISUAL_DEBUG_HIT_AREA_COLOR));
- } else {
- mTouchEventWindow.setBackgroundDrawable(null);
- }
- mTouchEventWindowClickListenerView = new View(context);
- mTouchEventWindow.setContentView(mTouchEventWindowClickListenerView);
- }
-
- @Override
- public void disposeUi() {
- if (mLocalRootView != null) {
- final ViewParent parent = mLocalRootView.getParent();
- if (parent != null && parent instanceof ViewGroup) {
- ((ViewGroup) parent).removeView(mLocalRootView);
- }
- mLocalRootView.removeAllViews();
- }
- if (mTouchEventWindow != null) {
- mTouchEventWindow.dismiss();
- }
- }
-
- @Override
- public void hideUi() {
- mAddToDictionaryIndicatorView.setVisibility(View.GONE);
- mTouchEventWindow.dismiss();
- }
-
- private static final RectF getIndicatorBoundsInScreenCoordinates(final Matrix matrix,
- final RectF composingTextBounds, final boolean showAtLeftSide) {
- final float indicatorSize = composingTextBounds.height();
- final RectF indicatorBounds;
- if (showAtLeftSide) {
- indicatorBounds = new RectF(composingTextBounds.left - indicatorSize,
- composingTextBounds.top, composingTextBounds.left,
- composingTextBounds.top + indicatorSize);
- } else {
- indicatorBounds = new RectF(composingTextBounds.right, composingTextBounds.top,
- composingTextBounds.right + indicatorSize,
- composingTextBounds.top + indicatorSize);
- }
- matrix.mapRect(indicatorBounds);
- return indicatorBounds;
- }
-
- @Override
- public void layoutUi(final Matrix matrix, final RectF composingTextBounds,
- final boolean useRtlLayout) {
- RectF indicatorBoundsInScreenCoordinates = getIndicatorBoundsInScreenCoordinates(matrix,
- composingTextBounds, useRtlLayout /* showAtLeftSide */);
- if (indicatorBoundsInScreenCoordinates.left < mDisplayRect.left ||
- mDisplayRect.right < indicatorBoundsInScreenCoordinates.right) {
- // The indicator is clipped by the screen. Show the indicator at the opposite side.
- indicatorBoundsInScreenCoordinates = getIndicatorBoundsInScreenCoordinates(matrix,
- composingTextBounds, !useRtlLayout /* showAtLeftSide */);
- }
-
- mAddToDictionaryIndicatorView.setBounds(indicatorBoundsInScreenCoordinates);
-
- final RectF hitAreaBoundsInScreenCoordinates = new RectF();
- matrix.mapRect(hitAreaBoundsInScreenCoordinates, composingTextBounds);
- hitAreaBoundsInScreenCoordinates.union(indicatorBoundsInScreenCoordinates);
- hitAreaBoundsInScreenCoordinates.inset(-mHitAreaMarginInPixels, -mHitAreaMarginInPixels);
-
- final int[] originScreen = new int[2];
- mLocalRootView.getLocationOnScreen(originScreen);
- final int viewOriginX = originScreen[0];
- final int viewOriginY = originScreen[1];
- mAddToDictionaryIndicatorView.setX(indicatorBoundsInScreenCoordinates.left - viewOriginX);
- mAddToDictionaryIndicatorView.setY(indicatorBoundsInScreenCoordinates.top - viewOriginY);
- mAddToDictionaryIndicatorView.setVisibility(View.VISIBLE);
-
- if (mTouchEventWindow.isShowing()) {
- mTouchEventWindow.update((int)hitAreaBoundsInScreenCoordinates.left - viewOriginX,
- (int)hitAreaBoundsInScreenCoordinates.top - viewOriginY,
- (int)hitAreaBoundsInScreenCoordinates.width(),
- (int)hitAreaBoundsInScreenCoordinates.height());
- } else {
- mTouchEventWindow.setWidth((int)hitAreaBoundsInScreenCoordinates.width());
- mTouchEventWindow.setHeight((int)hitAreaBoundsInScreenCoordinates.height());
- mTouchEventWindow.showAtLocation(mLocalRootView, Gravity.NO_GRAVITY,
- (int)hitAreaBoundsInScreenCoordinates.left - viewOriginX,
- (int)hitAreaBoundsInScreenCoordinates.top - viewOriginY);
- }
- }
-
- @Override
- public void setOnClickListener(final Runnable listener) {
- mTouchEventWindowClickListenerView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View arg0) {
- listener.run();
- }
- });
- }
-
- private static class IndicatorView extends View {
- private final Path mPath;
- private final Path mTmpPath = new Path();
- private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private final Matrix mMatrix = new Matrix();
- private final int mBackgroundColor;
- private final int mForegroundColor;
- private final RectF mBounds = new RectF();
- public IndicatorView(Context context, final int pathResourceId,
- final int sizeResourceId, final int backgroundColorResourceId,
- final int foregroundColroResourceId) {
- super(context);
- final Resources resources = context.getResources();
- mPath = createPath(resources, pathResourceId, sizeResourceId);
- mBackgroundColor = resources.getColor(backgroundColorResourceId);
- mForegroundColor = resources.getColor(foregroundColroResourceId);
- }
-
- public void setBounds(final RectF rect) {
- mBounds.set(rect);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- mPaint.setColor(mBackgroundColor);
- mPaint.setStyle(Paint.Style.FILL);
- canvas.drawRect(0.0f, 0.0f, mBounds.width(), mBounds.height(), mPaint);
-
- mMatrix.reset();
- mMatrix.postScale(mBounds.width(), mBounds.height());
- mPath.transform(mMatrix, mTmpPath);
- mPaint.setColor(mForegroundColor);
- canvas.drawPath(mTmpPath, mPaint);
- }
-
- private static Path createPath(final Resources resources, final int pathResourceId,
- final int sizeResourceId) {
- final int size = resources.getInteger(sizeResourceId);
- final float normalizationFactor = 1.0f / size;
- final int[] array = resources.getIntArray(pathResourceId);
-
- final Path path = new Path();
- for (int i = 0; i < array.length; i += 2) {
- if (i == 0) {
- path.moveTo(array[i] * normalizationFactor, array[i + 1] * normalizationFactor);
- } else {
- path.lineTo(array[i] * normalizationFactor, array[i + 1] * normalizationFactor);
- }
- }
- path.close();
- return path;
- }
- }
-
- private static ViewGroup getContentView(final View view) {
- final View rootView = view.getRootView();
- if (rootView == null) {
- return null;
- }
-
- final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
- if (windowContentView == null) {
- return null;
- }
- return windowContentView;
- }
-
- private static final class AddToDictionaryIndicatorView extends TextDecoratorUi.IndicatorView {
- public AddToDictionaryIndicatorView(final Context context) {
- super(context, R.array.text_decorator_add_to_dictionary_indicator_path,
- R.integer.text_decorator_add_to_dictionary_indicator_path_size,
- R.color.text_decorator_add_to_dictionary_indicator_background_color,
- R.color.text_decorator_add_to_dictionary_indicator_foreground_color);
- }
- }
-} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java b/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java
deleted file mode 100644
index 9e30e417e..000000000
--- a/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.keyboard;
-
-import android.graphics.Matrix;
-import android.graphics.RectF;
-
-/**
- * This interface defines how UI operations required for {@link TextDecorator} are delegated to
- * the actual UI implementation class.
- */
-public interface TextDecoratorUiOperator {
- /**
- * Called to notify that the UI is ready to be disposed.
- */
- void disposeUi();
-
- /**
- * Called when the UI should become invisible.
- */
- void hideUi();
-
- /**
- * Called to set the new click handler.
- * @param onClickListener the callback object whose {@link Runnable#run()} should be called when
- * the indicator is clicked.
- */
- void setOnClickListener(final Runnable onClickListener);
-
- /**
- * Called when the layout should be updated.
- * @param matrix The matrix that transforms the local coordinates into the screen coordinates.
- * @param composingTextBounds The bounding box of the composing text, in local coordinates.
- * @param useRtlLayout {@code true} if the indicator should be optimized for RTL layout.
- */
- void layoutUi(final Matrix matrix, final RectF composingTextBounds, final boolean useRtlLayout);
-}
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
index 0f9dc855b..a9711aed2 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
@@ -29,7 +29,6 @@ import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.settings.Settings;
@@ -147,7 +146,7 @@ final class EmojiCategory {
mShownCategories.add(properties);
}
- public String getCategoryName(final int categoryId, final int categoryPageId) {
+ public static String getCategoryName(final int categoryId, final int categoryPageId) {
return sCategoryName[categoryId] + "-" + categoryPageId;
}
@@ -271,7 +270,7 @@ final class EmojiCategory {
}
private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) {
- return (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
+ return (((long) categoryId) << Integer.SIZE) | id;
}
public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) {
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
index 925ec6bfb..09313f811 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
@@ -138,6 +138,21 @@ final class EmojiPageKeyboardView extends KeyboardView implements
return mKeyDetector.detectHitKey(x, y);
}
+ void callListenerOnReleaseKey(final Key releasedKey, final boolean withKeyRegistering) {
+ releasedKey.onReleased();
+ invalidateKey(releasedKey);
+ if (withKeyRegistering) {
+ mListener.onReleaseKey(releasedKey);
+ }
+ }
+
+ void callListenerOnPressKey(final Key pressedKey) {
+ mPendingKeyDown = null;
+ pressedKey.onPressed();
+ invalidateKey(pressedKey);
+ mListener.onPressKey(pressedKey);
+ }
+
public void releaseCurrentKey(final boolean withKeyRegistering) {
mHandler.removeCallbacks(mPendingKeyDown);
mPendingKeyDown = null;
@@ -145,11 +160,7 @@ final class EmojiPageKeyboardView extends KeyboardView implements
if (currentKey == null) {
return;
}
- currentKey.onReleased();
- invalidateKey(currentKey);
- if (withKeyRegistering) {
- mListener.onReleaseKey(currentKey);
- }
+ callListenerOnReleaseKey(currentKey, withKeyRegistering);
mCurrentKey = null;
}
@@ -165,10 +176,7 @@ final class EmojiPageKeyboardView extends KeyboardView implements
mPendingKeyDown = new Runnable() {
@Override
public void run() {
- mPendingKeyDown = null;
- key.onPressed();
- invalidateKey(key);
- mListener.onPressKey(key);
+ callListenerOnPressKey(key);
}
};
mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME);
@@ -195,15 +203,11 @@ final class EmojiPageKeyboardView extends KeyboardView implements
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- key.onReleased();
- invalidateKey(key);
- mListener.onReleaseKey(key);
+ callListenerOnReleaseKey(key, true /* withRegistering */);
}
}, KEY_RELEASE_DELAY_TIME);
} else {
- key.onReleased();
- invalidateKey(key);
- mListener.onReleaseKey(key);
+ callListenerOnReleaseKey(key, true /* withRegistering */);
}
return true;
}
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
index e37cd2369..a3b869d73 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
@@ -16,13 +16,12 @@
package com.android.inputmethod.keyboard.emoji;
-import static com.android.inputmethod.latin.Constants.NOT_A_COORDINATE;
+import static com.android.inputmethod.latin.common.Constants.NOT_A_COORDINATE;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
-import android.os.CountDownTimer;
import android.preference.PreferenceManager;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
@@ -47,13 +46,11 @@ import com.android.inputmethod.keyboard.internal.KeyDrawParams;
import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
+import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.utils.ResourceUtils;
-import java.util.concurrent.TimeUnit;
-
/**
* View class to implement Emoji palettes.
* The Emoji keyboard consists of group of views layout/emoji_palettes_view.
@@ -75,9 +72,9 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
private final int mCategoryIndicatorBackgroundResId;
private final int mCategoryPageIndicatorColor;
private final int mCategoryPageIndicatorBackground;
- private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener;
private EmojiPalettesAdapter mEmojiPalettesAdapter;
private final EmojiLayoutParams mEmojiLayoutParams;
+ private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener;
private ImageButton mDeleteKey;
private TextView mAlphabetKeyLeft;
@@ -113,7 +110,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
context, null /* editorInfo */);
final Resources res = context.getResources();
mEmojiLayoutParams = new EmojiLayoutParams(res);
- builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype());
+ builder.setSubtype(RichInputMethodSubtype.getEmojiSubtype());
builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res),
mEmojiLayoutParams.mEmojiKeyboardHeight);
final KeyboardLayoutSet layoutSet = builder.build();
@@ -132,7 +129,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
mCategoryPageIndicatorBackground = emojiPalettesViewAttr.getColor(
R.styleable.EmojiPalettesView_categoryPageIndicatorBackground, 0);
emojiPalettesViewAttr.recycle();
- mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener(context);
+ mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener();
}
@Override
@@ -149,11 +146,14 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
private void addTab(final TabHost host, final int categoryId) {
- final String tabId = mEmojiCategory.getCategoryName(categoryId, 0 /* categoryPageId */);
+ final String tabId = EmojiCategory.getCategoryName(categoryId, 0 /* categoryPageId */);
final TabHost.TabSpec tspec = host.newTabSpec(tabId);
tspec.setContent(R.id.emoji_keyboard_dummy);
final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate(
R.layout.emoji_keyboard_tab_icon, null);
+ // TODO: Replace background color with its own setting rather than using the
+ // category page indicator background as a workaround.
+ iconView.setBackgroundColor(mCategoryPageIndicatorBackground);
iconView.setImageResource(mEmojiCategory.getCategoryTabIcon(categoryId));
iconView.setContentDescription(mEmojiCategory.getAccessibilityDescription(categoryId));
tspec.setIndicator(iconView);
@@ -265,7 +265,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
@Override
public void onPageScrolled(final int position, final float positionOffset,
- final int positionOffsetPixels) {
+ final int positionOffsetPixels) {
mEmojiPalettesAdapter.onPageScrolled();
final Pair<Integer, Integer> newPos =
mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position);
@@ -364,7 +364,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
private static void setupAlphabetKey(final TextView alphabetKey, final String label,
- final KeyDrawParams params) {
+ final KeyDrawParams params) {
alphabetKey.setText(label);
alphabetKey.setTextColor(params.mFunctionalTextColor);
alphabetKey.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mLabelSize);
@@ -372,7 +372,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
public void startEmojiPalettes(final String switchToAlphaLabel,
- final KeyVisualAttributes keyVisualAttr, final KeyboardIconsSet iconSet) {
+ final KeyVisualAttributes keyVisualAttr,
+ final KeyboardIconsSet iconSet) {
final int deleteIconResId = iconSet.getIconResourceId(KeyboardIconsSet.NAME_DELETE_KEY);
if (deleteIconResId != 0) {
mDeleteKey.setImageResource(deleteIconResId);
@@ -398,7 +399,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
public void setKeyboardActionListener(final KeyboardActionListener listener) {
mKeyboardActionListener = listener;
- mDeleteKeyOnTouchListener.setKeyboardActionListener(mKeyboardActionListener);
+ mDeleteKeyOnTouchListener.setKeyboardActionListener(listener);
}
private void updateEmojiCategoryPageIdView() {
@@ -436,45 +437,9 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
private static class DeleteKeyOnTouchListener implements OnTouchListener {
- static final long MAX_REPEAT_COUNT_TIME = TimeUnit.SECONDS.toMillis(30);
- final long mKeyRepeatStartTimeout;
- final long mKeyRepeatInterval;
-
- public DeleteKeyOnTouchListener(Context context) {
- final Resources res = context.getResources();
- 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;
- // TODO: Do the same things done in PointerTracker
- private final CountDownTimer mTimer;
- private int mState = KEY_REPEAT_STATE_INITIALIZED;
- private int mRepeatCount = 0;
-
public void setKeyboardActionListener(final KeyboardActionListener listener) {
mKeyboardActionListener = listener;
}
@@ -482,79 +447,40 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
@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;
+ 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;
}
return false;
}
- private void handleKeyDown() {
- mKeyboardActionListener.onPressKey(
- 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;
- }
-
private void onTouchDown(final View v) {
- mTimer.cancel();
- mRepeatCount = 0;
- handleKeyDown();
+ mKeyboardActionListener.onPressKey(Constants.CODE_DELETE,
+ 0 /* repeatCount */, true /* isSinglePointer */);
v.setPressed(true /* pressed */);
- mState = KEY_REPEAT_STATE_KEY_DOWN;
- mTimer.start();
}
private void onTouchUp(final View v) {
- mTimer.cancel();
- if (mState == KEY_REPEAT_STATE_KEY_DOWN) {
- handleKeyUp();
- }
+ mKeyboardActionListener.onCodeInput(Constants.CODE_DELETE,
+ NOT_A_COORDINATE, NOT_A_COORDINATE, false /* isKeyRepeat */);
+ mKeyboardActionListener.onReleaseKey(Constants.CODE_DELETE, false /* withSliding */);
v.setPressed(false /* pressed */);
- 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.
- 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;
- }
}
}
-}
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
index a194f3dfd..c76a9aca4 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
@@ -19,8 +19,11 @@ package com.android.inputmethod.keyboard.internal;
import android.graphics.Canvas;
import android.view.View;
+import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.keyboard.PointerTracker;
+import javax.annotation.Nonnull;
+
/**
* Abstract base class for previews that are drawn on DrawingPreviewPlacerView, e.g.,
* GestureFloatingTextDrawingPreview, GestureTrailsDrawingPreview, and
@@ -31,7 +34,7 @@ public abstract class AbstractDrawingPreview {
private boolean mPreviewEnabled;
private boolean mHasValidGeometry;
- public void setDrawingView(final DrawingPreviewPlacerView drawingView) {
+ public void setDrawingView(@Nonnull final DrawingPreviewPlacerView drawingView) {
mDrawingView = drawingView;
drawingView.addPreview(this);
}
@@ -51,16 +54,16 @@ public abstract class AbstractDrawingPreview {
}
/**
- * Set {@link MainKeyboardView} geometry and position in the {@link SoftInputWindow}.
+ * Set {@link MainKeyboardView} geometry and position in the window of input method.
* 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
+ * the input method window 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,
+ public void setKeyboardViewGeometry(@Nonnull final int[] originCoords, final int width,
final int height) {
mHasValidGeometry = (width > 0 && height > 0);
}
@@ -71,11 +74,11 @@ public abstract class AbstractDrawingPreview {
* Draws the preview
* @param canvas The canvas where the preview is drawn.
*/
- public abstract void drawPreview(final Canvas canvas);
+ public abstract void drawPreview(@Nonnull final Canvas canvas);
/**
* Set the position of the preview.
* @param tracker The new location of the preview is based on the points in PointerTracker.
*/
- public abstract void setPreviewPosition(final PointerTracker tracker);
+ public abstract void setPreviewPosition(@Nonnull final PointerTracker tracker);
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java b/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java
index cd9875955..77d0e7a90 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java
@@ -16,8 +16,8 @@
package com.android.inputmethod.keyboard.internal;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.InputPointers;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
/**
* This class arbitrates batch input.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java b/java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java
index 6420edd7a..4b355a4ab 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java
@@ -20,8 +20,8 @@ import android.content.res.Resources;
import android.util.DisplayMetrics;
import android.util.Log;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.define.DebugFlags;
// This hack is applied to certain classes of tablets.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
index dce7fc57e..2e2ed52dd 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
@@ -16,8 +16,8 @@
package com.android.inputmethod.keyboard.internal;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
import android.text.TextUtils;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
deleted file mode 100644
index df82becae..000000000
--- a/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
+++ /dev/null
@@ -1,77 +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.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/DrawingPreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java
index a5d47adb3..9c0d7436b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java
@@ -24,7 +24,7 @@ import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
-import com.android.inputmethod.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.common.CoordinateUtils;
import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java
new file mode 100644
index 000000000..06bdfc41b
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java
@@ -0,0 +1,79 @@
+/*
+ * 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.keyboard.Key;
+import com.android.inputmethod.keyboard.MoreKeysPanel;
+import com.android.inputmethod.keyboard.PointerTracker;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public interface DrawingProxy {
+ /**
+ * Called when a key is being pressed.
+ * @param key the {@link Key} that is being pressed.
+ * @param withPreview true if key popup preview should be displayed.
+ */
+ public void onKeyPressed(@Nonnull Key key, boolean withPreview);
+
+ /**
+ * Called when a key is being released.
+ * @param key the {@link Key} that is being released.
+ * @param withAnimation when true, key popup preview should be dismissed with animation.
+ */
+ public void onKeyReleased(@Nonnull Key key, boolean withAnimation);
+
+ /**
+ * Start showing more keys keyboard of a key that is being long pressed.
+ * @param key the {@link Key} that is being long pressed and showing more keys keyboard.
+ * @param tracker the {@link PointerTracker} that detects this long pressing.
+ * @return {@link MoreKeysPanel} that is being shown. null if there is no need to show more keys
+ * keyboard.
+ */
+ @Nullable
+ public MoreKeysPanel showMoreKeysKeyboard(@Nonnull Key key, @Nonnull PointerTracker tracker);
+
+ /**
+ * Start a while-typing-animation.
+ * @param fadeInOrOut {@link #FADE_IN} starts while-typing-fade-in animation.
+ * {@link #FADE_OUT} starts while-typing-fade-out animation.
+ */
+ public void startWhileTypingAnimation(int fadeInOrOut);
+ public static final int FADE_IN = 0;
+ public static final int FADE_OUT = 1;
+
+ /**
+ * Show sliding-key input preview.
+ * @param tracker the {@link PointerTracker} that is currently doing the sliding-key input.
+ * null to dismiss the sliding-key input preview.
+ */
+ public void showSlidingKeyInputPreview(@Nullable PointerTracker tracker);
+
+ /**
+ * Show gesture trails.
+ * @param tracker the {@link PointerTracker} whose gesture trail will be shown.
+ * @param showsFloatingPreviewText when true, a gesture floating preview text will be shown
+ * with this <code>tracker</code>'s trail.
+ */
+ public void showGestureTrail(@Nonnull PointerTracker tracker, boolean showsFloatingPreviewText);
+
+ /**
+ * Dismiss a gesture floating preview text without delay.
+ */
+ public void dismissGestureFloatingPreviewTextWithoutDelay();
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java
index fd84856b7..5443c2a8c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java
@@ -27,7 +27,9 @@ import android.text.TextUtils;
import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.common.CoordinateUtils;
+
+import javax.annotation.Nonnull;
/**
* The class for single gesture preview text. The class for multiple gesture preview text will be
@@ -98,7 +100,7 @@ public class GestureFloatingTextDrawingPreview extends AbstractDrawingPreview {
private final RectF mGesturePreviewRectangle = new RectF();
private int mPreviewTextX;
private int mPreviewTextY;
- private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+ private SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance();
private final int[] mLastPointerCoords = CoordinateUtils.newInstance();
public GestureFloatingTextDrawingPreview(final TypedArray mainKeyboardViewAttr) {
@@ -110,7 +112,11 @@ public class GestureFloatingTextDrawingPreview extends AbstractDrawingPreview {
// Nothing to do here.
}
- public void setSuggetedWords(final SuggestedWords suggestedWords) {
+ public void dismissGestureFloatingPreviewText() {
+ setSuggetedWords(SuggestedWords.getEmptyInstance());
+ }
+
+ public void setSuggetedWords(@Nonnull final SuggestedWords suggestedWords) {
if (!isPreviewEnabled()) {
return;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java
index 7d09e9d2f..07ef00924 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java
@@ -16,7 +16,7 @@
package com.android.inputmethod.keyboard.internal;
-import com.android.inputmethod.latin.utils.ResizableIntArray;
+import com.android.inputmethod.latin.common.ResizableIntArray;
/**
* This class holds drawing points to represent a gesture stroke on the screen.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java
index e49e538aa..3e901114a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java
@@ -18,9 +18,9 @@ package com.android.inputmethod.keyboard.internal;
import android.util.Log;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.InputPointers;
-import com.android.inputmethod.latin.utils.ResizableIntArray;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
+import com.android.inputmethod.latin.common.ResizableIntArray;
/**
* This class holds event points to recognize a gesture stroke.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java
index bf4c4da10..4d998e443 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java
@@ -23,8 +23,8 @@ import android.graphics.Path;
import android.graphics.Rect;
import android.os.SystemClock;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.ResizableIntArray;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.ResizableIntArray;
/**
* This class holds drawing points to represent a gesture trail. The gesture trail may contain
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
index df50efdc1..3ef9ea1dc 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
@@ -20,8 +20,12 @@ import android.graphics.Typeface;
import com.android.inputmethod.latin.utils.ResourceUtils;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
public final class KeyDrawParams {
- public Typeface mTypeface;
+ @Nonnull
+ public Typeface mTypeface = Typeface.DEFAULT;
public int mLetterSize;
public int mLabelSize;
@@ -49,7 +53,7 @@ public final class KeyDrawParams {
public KeyDrawParams() {}
- private KeyDrawParams(final KeyDrawParams copyFrom) {
+ private KeyDrawParams(@Nonnull final KeyDrawParams copyFrom) {
mTypeface = copyFrom.mTypeface;
mLetterSize = copyFrom.mLetterSize;
@@ -77,7 +81,7 @@ public final class KeyDrawParams {
mAnimAlpha = copyFrom.mAnimAlpha;
}
- public void updateParams(final int keyHeight, final KeyVisualAttributes attr) {
+ public void updateParams(final int keyHeight, @Nullable final KeyVisualAttributes attr) {
if (attr == null) {
return;
}
@@ -117,8 +121,9 @@ public final class KeyDrawParams {
attr.mHintLabelOffCenterRatio, mHintLabelOffCenterRatio);
}
+ @Nonnull
public KeyDrawParams mayCloneAndUpdateParams(final int keyHeight,
- final KeyVisualAttributes attr) {
+ @Nullable final KeyVisualAttributes attr) {
if (attr == null) {
return this;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
index 5005b7d7d..448f1b4b1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
@@ -23,12 +23,11 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.common.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:
@@ -69,12 +68,6 @@ public final class KeyPreviewChoreographer {
return mShowingKeyPreviewViews.containsKey(key);
}
- public void dismissAllKeyPreviews() {
- for (final Key key : new HashSet<>(mShowingKeyPreviewViews.keySet())) {
- dismissKeyPreview(key, false /* withAnimation */);
- }
- }
-
public void dismissKeyPreview(final Key key, final boolean withAnimation) {
if (key == null) {
return;
@@ -148,7 +141,7 @@ public final class KeyPreviewChoreographer {
keyPreviewView.setPivotY(previewHeight);
}
- private void showKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
+ void showKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
final boolean withAnimation) {
if (!withAnimation) {
keyPreviewView.setVisibility(View.VISIBLE);
@@ -166,25 +159,25 @@ public final class KeyPreviewChoreographer {
}
public Animator createShowUpAnimator(final Key key, final KeyPreviewView keyPreviewView) {
- final Animator animator = mParams.createShowUpAnimator(keyPreviewView);
- animator.addListener(new AnimatorListenerAdapter() {
+ final Animator showUpAnimator = mParams.createShowUpAnimator(keyPreviewView);
+ showUpAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(final Animator animator) {
showKeyPreview(key, keyPreviewView, false /* withAnimation */);
}
});
- return animator;
+ return showUpAnimator;
}
private Animator createDismissAnimator(final Key key, final KeyPreviewView keyPreviewView) {
- final Animator animator = mParams.createDismissAnimator(keyPreviewView);
- animator.addListener(new AnimatorListenerAdapter() {
+ final Animator dismissAnimator = mParams.createDismissAnimator(keyPreviewView);
+ dismissAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(final Animator animator) {
dismissKeyPreview(key, false /* withAnimation */);
}
});
- return animator;
+ return dismissAnimator;
}
private static class KeyPreviewAnimators extends AnimatorListenerAdapter {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 48ba8e051..3eb62e7a6 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -16,11 +16,14 @@
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 static com.android.inputmethod.latin.common.Constants.CODE_OUTPUT_TEXT;
+import static com.android.inputmethod.latin.common.Constants.CODE_UNSPECIFIED;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
/**
* The string parser of the key specification.
@@ -53,11 +56,11 @@ public final class KeySpecParser {
// Intentional empty constructor for utility class.
}
- private static boolean hasIcon(final String keySpec) {
+ private static boolean hasIcon(@Nonnull final String keySpec) {
return keySpec.startsWith(KeyboardIconsSet.PREFIX_ICON);
}
- private static boolean hasCode(final String keySpec, final int labelEnd) {
+ private static boolean hasCode(@Nonnull final String keySpec, final int labelEnd) {
if (labelEnd <= 0 || labelEnd + 1 >= keySpec.length()) {
return false;
}
@@ -72,7 +75,8 @@ public final class KeySpecParser {
return false;
}
- private static String parseEscape(final String text) {
+ @Nonnull
+ private static String parseEscape(@Nonnull final String text) {
if (text.indexOf(BACKSLASH) < 0) {
return text;
}
@@ -91,7 +95,7 @@ public final class KeySpecParser {
return sb.toString();
}
- private static int indexOfLabelEnd(final String keySpec) {
+ private static int indexOfLabelEnd(@Nonnull final String keySpec) {
final int length = keySpec.length();
if (keySpec.indexOf(BACKSLASH) < 0) {
final int labelEnd = keySpec.indexOf(VERTICAL_BAR);
@@ -116,22 +120,25 @@ public final class KeySpecParser {
return -1;
}
- private static String getBeforeLabelEnd(final String keySpec, final int labelEnd) {
+ @Nonnull
+ private static String getBeforeLabelEnd(@Nonnull final String keySpec, final int labelEnd) {
return (labelEnd < 0) ? keySpec : keySpec.substring(0, labelEnd);
}
- private static String getAfterLabelEnd(final String keySpec, final int labelEnd) {
+ @Nonnull
+ private static String getAfterLabelEnd(@Nonnull final String keySpec, final int labelEnd) {
return keySpec.substring(labelEnd + /* VERTICAL_BAR */1);
}
- private static void checkDoubleLabelEnd(final String keySpec, final int labelEnd) {
+ private static void checkDoubleLabelEnd(@Nonnull 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) {
+ @Nullable
+ public static String getLabel(@Nullable final String keySpec) {
if (keySpec == null) {
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
return null;
@@ -147,7 +154,8 @@ public final class KeySpecParser {
return label;
}
- private static String getOutputTextInternal(final String keySpec, final int labelEnd) {
+ @Nullable
+ private static String getOutputTextInternal(@Nonnull final String keySpec, final int labelEnd) {
if (labelEnd <= 0) {
return null;
}
@@ -155,7 +163,8 @@ public final class KeySpecParser {
return parseEscape(getAfterLabelEnd(keySpec, labelEnd));
}
- public static String getOutputText(final String keySpec) {
+ @Nullable
+ public static String getOutputText(@Nullable final String keySpec) {
if (keySpec == null) {
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
return null;
@@ -184,7 +193,7 @@ public final class KeySpecParser {
return (StringUtils.codePointCount(label) == 1) ? null : label;
}
- public static int getCode(final String keySpec) {
+ public static int getCode(@Nullable final String keySpec) {
if (keySpec == null) {
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
return CODE_UNSPECIFIED;
@@ -211,7 +220,7 @@ public final class KeySpecParser {
return (StringUtils.codePointCount(label) == 1) ? label.codePointAt(0) : CODE_OUTPUT_TEXT;
}
- public static int parseCode(final String text, final int defaultCode) {
+ public static int parseCode(@Nullable final String text, final int defaultCode) {
if (text == null) {
return defaultCode;
}
@@ -226,7 +235,7 @@ public final class KeySpecParser {
return defaultCode;
}
- public static int getIconId(final String keySpec) {
+ public static int getIconId(@Nullable final String keySpec) {
if (keySpec == null) {
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
return KeyboardIconsSet.ICON_UNDEFINED;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
index 7941ddd41..28aa22c16 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
@@ -18,18 +18,22 @@ package com.android.inputmethod.keyboard.internal;
import android.content.res.TypedArray;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
public abstract class KeyStyle {
private final KeyboardTextsSet mTextsSet;
- public abstract String[] getStringArray(TypedArray a, int index);
- public abstract String getString(TypedArray a, int index);
+ public abstract @Nullable String[] getStringArray(TypedArray a, int index);
+ public abstract @Nullable String getString(TypedArray a, int index);
public abstract int getInt(TypedArray a, int index, int defaultValue);
public abstract int getFlags(TypedArray a, int index);
- protected KeyStyle(final KeyboardTextsSet textsSet) {
+ protected KeyStyle(@Nonnull final KeyboardTextsSet textsSet) {
mTextsSet = textsSet;
}
+ @Nullable
protected String parseString(final TypedArray a, final int index) {
if (a.hasValue(index)) {
return mTextsSet.resolveTextReference(a.getString(index));
@@ -37,6 +41,7 @@ public abstract class KeyStyle {
return null;
}
+ @Nullable
protected String[] parseStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) {
final String text = mTextsSet.resolveTextReference(a.getString(index));
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index 5cbb34119..61f98c8ff 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -29,33 +29,42 @@ import org.xmlpull.v1.XmlPullParserException;
import java.util.Arrays;
import java.util.HashMap;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
public final class KeyStylesSet {
private static final String TAG = KeyStylesSet.class.getSimpleName();
private static final boolean DEBUG = false;
+ @Nonnull
private final HashMap<String, KeyStyle> mStyles = new HashMap<>();
+ @Nonnull
private final KeyboardTextsSet mTextsSet;
+ @Nonnull
private final KeyStyle mEmptyKeyStyle;
+ @Nonnull
private static final String EMPTY_STYLE_NAME = "<empty>";
- public KeyStylesSet(final KeyboardTextsSet textsSet) {
+ public KeyStylesSet(@Nonnull final KeyboardTextsSet textsSet) {
mTextsSet = textsSet;
mEmptyKeyStyle = new EmptyKeyStyle(textsSet);
mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle);
}
private static final class EmptyKeyStyle extends KeyStyle {
- EmptyKeyStyle(final KeyboardTextsSet textsSet) {
+ EmptyKeyStyle(@Nonnull final KeyboardTextsSet textsSet) {
super(textsSet);
}
@Override
+ @Nullable
public String[] getStringArray(final TypedArray a, final int index) {
return parseStringArray(a, index);
}
@Override
+ @Nullable
public String getString(final TypedArray a, final int index) {
return parseString(a, index);
}
@@ -76,14 +85,16 @@ public final class KeyStylesSet {
private final String mParentStyleName;
private final SparseArray<Object> mStyleAttributes = new SparseArray<>();
- public DeclaredKeyStyle(final String parentStyleName, final KeyboardTextsSet textsSet,
- final HashMap<String, KeyStyle> styles) {
+ public DeclaredKeyStyle(@Nonnull final String parentStyleName,
+ @Nonnull final KeyboardTextsSet textsSet,
+ @Nonnull final HashMap<String, KeyStyle> styles) {
super(textsSet);
mParentStyleName = parentStyleName;
mStyles = styles;
}
@Override
+ @Nullable
public String[] getStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) {
return parseStringArray(a, index);
@@ -98,6 +109,7 @@ public final class KeyStylesSet {
}
@Override
+ @Nullable
public String getString(final TypedArray a, final int index) {
if (a.hasValue(index)) {
return parseString(a, index);
@@ -176,37 +188,43 @@ public final class KeyStylesSet {
public void parseKeyStyleAttributes(final TypedArray keyStyleAttr, final TypedArray keyAttrs,
final XmlPullParser parser) throws XmlPullParserException {
final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName);
+ if (styleName == null) {
+ throw new XmlParseUtils.ParseException(
+ KeyboardBuilder.TAG_KEY_STYLE + " has no styleName attribute", parser);
+ }
if (DEBUG) {
Log.d(TAG, String.format("<%s styleName=%s />",
KeyboardBuilder.TAG_KEY_STYLE, styleName));
if (mStyles.containsKey(styleName)) {
- Log.d(TAG, "key-style " + styleName + " is overridden at "
+ Log.d(TAG, KeyboardBuilder.TAG_KEY_STYLE + " " + styleName + " is overridden at "
+ parser.getPositionDescription());
}
}
- String parentStyleName = EMPTY_STYLE_NAME;
- if (keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_parentStyle)) {
- parentStyleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_parentStyle);
- if (!mStyles.containsKey(parentStyleName)) {
- throw new XmlParseUtils.ParseException(
- "Unknown parentStyle " + parentStyleName, parser);
- }
+ final String parentStyleInAttr = keyStyleAttr.getString(
+ R.styleable.Keyboard_KeyStyle_parentStyle);
+ if (parentStyleInAttr != null && !mStyles.containsKey(parentStyleInAttr)) {
+ throw new XmlParseUtils.ParseException(
+ "Unknown parentStyle " + parentStyleInAttr, parser);
}
+ final String parentStyleName = (parentStyleInAttr == null) ? EMPTY_STYLE_NAME
+ : parentStyleInAttr;
final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName, mTextsSet, mStyles);
style.readKeyAttributes(keyAttrs);
mStyles.put(styleName, style);
}
+ @Nonnull
public KeyStyle getKeyStyle(final TypedArray keyAttr, final XmlPullParser parser)
throws XmlParseUtils.ParseException {
- if (!keyAttr.hasValue(R.styleable.Keyboard_Key_keyStyle)) {
+ final String styleName = keyAttr.getString(R.styleable.Keyboard_Key_keyStyle);
+ if (styleName == null) {
return mEmptyKeyStyle;
}
- final String styleName = keyAttr.getString(R.styleable.Keyboard_Key_keyStyle);
- if (!mStyles.containsKey(styleName)) {
+ final KeyStyle style = mStyles.get(styleName);
+ if (style == null) {
throw new XmlParseUtils.ParseException("Unknown key style: " + styleName, parser);
}
- return mStyles.get(styleName);
+ return style;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
index c60d587db..6f000d294 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
@@ -23,7 +23,11 @@ import android.util.SparseIntArray;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.ResourceUtils;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
public final class KeyVisualAttributes {
+ @Nullable
public final Typeface mTypeface;
public final float mLetterRatio;
@@ -81,7 +85,8 @@ public final class KeyVisualAttributes {
}
}
- public static KeyVisualAttributes newInstance(final TypedArray keyAttr) {
+ @Nullable
+ public static KeyVisualAttributes newInstance(@Nonnull final TypedArray keyAttr) {
final int indexCount = keyAttr.getIndexCount();
for (int i = 0; i < indexCount; i++) {
final int attrId = keyAttr.getIndex(i);
@@ -93,7 +98,7 @@ public final class KeyVisualAttributes {
return null;
}
- private KeyVisualAttributes(final TypedArray keyAttr) {
+ private KeyVisualAttributes(@Nonnull final TypedArray keyAttr) {
if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyTypeface)) {
mTypeface = Typeface.defaultFromStyle(
keyAttr.getInt(R.styleable.Keyboard_Key_keyTypeface, Typeface.NORMAL));
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index fa4192790..0eabf6cc9 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -32,11 +32,10 @@ import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardTheme;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
-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;
@@ -45,6 +44,9 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Locale;
+
+import javax.annotation.Nonnull;
/**
* Keyboard Building helper.
@@ -137,6 +139,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
private static final int DEFAULT_KEYBOARD_ROWS = 4;
+ @Nonnull
protected final KP mParams;
protected final Context mContext;
protected final Resources mResources;
@@ -147,7 +150,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
private boolean mTopEdge;
private Key mRightEdgeKey = null;
- public KeyboardBuilder(final Context context, final KP params) {
+ public KeyboardBuilder(final Context context, @Nonnull final KP params) {
mContext = context;
final Resources res = context.getResources();
mResources = res;
@@ -158,8 +161,8 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
}
- public void setAutoGenerate(final KeysCache keysCache) {
- mParams.mKeysCache = keysCache;
+ public void setAllowRedundantMoreKes(final boolean enabled) {
+ mParams.mAllowRedundantMoreKeys = enabled;
}
public KeyboardBuilder<KP> load(final int xmlId, final KeyboardId id) {
@@ -188,6 +191,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
mParams.mProximityCharsCorrectionEnabled = enabled;
}
+ @Nonnull
public Keyboard build() {
return new Keyboard(mParams);
}
@@ -277,7 +281,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0);
params.mIconsSet.loadIcons(keyboardAttr);
- params.mTextsSet.setLocale(params.mId.mLocale, mContext);
+ params.mTextsSet.setLocale(params.mId.getLocale(), mContext);
final int resourceId = keyboardAttr.getResourceId(
R.styleable.Keyboard_touchPositionCorrectionData, 0);
@@ -640,7 +644,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
try {
final boolean keyboardLayoutSetMatched = matchString(caseAttr,
R.styleable.Keyboard_Case_keyboardLayoutSet,
- SubtypeLocaleUtils.getKeyboardLayoutSetName(id.mSubtype));
+ id.mSubtype.getKeyboardLayoutSetName());
final boolean keyboardLayoutSetElementMatched = matchTypedValue(caseAttr,
R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId,
KeyboardId.elementIdToName(id.mElementId));
@@ -668,21 +672,22 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
R.styleable.Keyboard_Case_imeAction, id.imeAction());
final boolean isIconDefinedMatched = isIconDefined(caseAttr,
R.styleable.Keyboard_Case_isIconDefined, mParams.mIconsSet);
- final boolean localeCodeMatched = matchString(caseAttr,
- R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
- final boolean languageCodeMatched = matchString(caseAttr,
- R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
- final boolean countryCodeMatched = matchString(caseAttr,
- R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
+ final Locale locale = id.getLocale();
+ final boolean localeCodeMatched = matchLocaleCodes(caseAttr, locale);
+ final boolean languageCodeMatched = matchLanguageCodes(caseAttr, locale);
+ final boolean countryCodeMatched = matchCountryCodes(caseAttr, locale);
+ final boolean splitLayoutMatched = matchBoolean(caseAttr,
+ R.styleable.Keyboard_Case_isSplitLayout, id.mIsSplitLayout);
final boolean selected = keyboardLayoutSetMatched && keyboardLayoutSetElementMatched
&& keyboardThemeMacthed && modeMatched && navigateNextMatched
&& navigatePreviousMatched && passwordInputMatched && clobberSettingsKeyMatched
&& hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
&& isMultiLineMatched && imeActionMatched && isIconDefinedMatched
- && localeCodeMatched && languageCodeMatched && countryCodeMatched;
+ && localeCodeMatched && languageCodeMatched && countryCodeMatched
+ && splitLayoutMatched;
if (DEBUG) {
- startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+ startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
textAttr(caseAttr.getString(
R.styleable.Keyboard_Case_keyboardLayoutSet), "keyboardLayoutSet"),
textAttr(caseAttr.getString(
@@ -707,6 +712,8 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
"languageSwitchKeyEnabled"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_isMultiLine,
"isMultiLine"),
+ booleanAttr(caseAttr, R.styleable.Keyboard_Case_isSplitLayout,
+ "splitLayout"),
textAttr(caseAttr.getString(R.styleable.Keyboard_Case_isIconDefined),
"isIconDefined"),
textAttr(caseAttr.getString(R.styleable.Keyboard_Case_localeCode),
@@ -724,6 +731,18 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
}
}
+ private static boolean matchLocaleCodes(TypedArray caseAttr, final Locale locale) {
+ return matchString(caseAttr, R.styleable.Keyboard_Case_localeCode, locale.toString());
+ }
+
+ private static boolean matchLanguageCodes(TypedArray caseAttr, Locale locale) {
+ return matchString(caseAttr, R.styleable.Keyboard_Case_languageCode, locale.getLanguage());
+ }
+
+ private static boolean matchCountryCodes(TypedArray caseAttr, Locale locale) {
+ return matchString(caseAttr, R.styleable.Keyboard_Case_countryCode, locale.getCountry());
+ }
+
private static boolean matchInteger(final TypedArray a, final int index, final int value) {
// If <case> does not have "index" attribute, that means this <case> is wild-card for
// the attribute.
@@ -833,7 +852,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
mTopEdge = false;
}
- private void endKey(final Key key) {
+ private void endKey(@Nonnull final Key key) {
mParams.onAddKey(key);
if (mLeftEdge) {
key.markAsLeftEdge(mParams);
@@ -846,6 +865,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
}
private void endKeyboard() {
+ mParams.removeRedundantMoreKeys();
// {@link #parseGridRows(XmlPullParser,boolean)} may populate keyboard rows higher than
// previously expected.
final int actualHeight = mCurrentY - mParams.mVerticalGap + mParams.mBottomPadding;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index 62b69dcc9..05b4c7473 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -16,7 +16,7 @@
package com.android.inputmethod.keyboard.internal;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.common.Constants;
import java.util.HashMap;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index e1f302c1e..15a5bd456 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -26,6 +26,9 @@ import com.android.inputmethod.latin.R;
import java.util.HashMap;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
public final class KeyboardIconsSet {
private static final String TAG = KeyboardIconsSet.class.getSimpleName();
@@ -127,6 +130,7 @@ public final class KeyboardIconsSet {
return iconId >= 0 && iconId < ICON_NAMES.length;
}
+ @Nonnull
public static String getIconName(final int iconId) {
return isValidIconId(iconId) ? ICON_NAMES[iconId] : "unknown<" + iconId + ">";
}
@@ -147,6 +151,7 @@ public final class KeyboardIconsSet {
throw new RuntimeException("unknown icon name: " + name);
}
+ @Nullable
public Drawable getIconDrawable(final int iconId) {
if (isValidIconId(iconId)) {
return mIcons[iconId];
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
index 5df9d3ece..738d6a400 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -20,13 +20,16 @@ import android.util.SparseIntArray;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.common.Constants;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
public class KeyboardParams {
public KeyboardId mId;
public int mThemeId;
@@ -46,6 +49,7 @@ public class KeyboardParams {
public int mLeftPadding;
public int mRightPadding;
+ @Nullable
public KeyVisualAttributes mKeyVisualAttributes;
public int mDefaultRowHeight;
@@ -60,20 +64,29 @@ public class KeyboardParams {
public int GRID_HEIGHT;
// Keys are sorted from top-left to bottom-right order.
+ @Nonnull
public final SortedSet<Key> mSortedKeys = new TreeSet<>(ROW_COLUMN_COMPARATOR);
+ @Nonnull
public final ArrayList<Key> mShiftKeys = new ArrayList<>();
+ @Nonnull
public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<>();
+ @Nonnull
public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
+ @Nonnull
public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
+ @Nonnull
public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet);
- public KeysCache mKeysCache;
+ @Nonnull
+ private final UniqueKeysCache mUniqueKeysCache;
+ public boolean mAllowRedundantMoreKeys;
public int mMostCommonKeyHeight = 0;
public int mMostCommonKeyWidth = 0;
public boolean mProximityCharsCorrectionEnabled;
+ @Nonnull
public final TouchPositionCorrection mTouchPositionCorrection =
new TouchPositionCorrection();
@@ -89,14 +102,22 @@ public class KeyboardParams {
}
};
+ public KeyboardParams() {
+ this(UniqueKeysCache.NO_CACHE);
+ }
+
+ public KeyboardParams(@Nonnull final UniqueKeysCache keysCache) {
+ mUniqueKeysCache = keysCache;
+ }
+
protected void clearKeys() {
mSortedKeys.clear();
mShiftKeys.clear();
clearHistogram();
}
- public void onAddKey(final Key newKey) {
- final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
+ public void onAddKey(@Nonnull final Key newKey) {
+ final Key key = mUniqueKeysCache.getUniqueKey(newKey);
final boolean isSpacer = key.isSpacer();
if (isSpacer && key.getWidth() == 0) {
// Ignore zero width {@link Spacer}.
@@ -115,6 +136,23 @@ public class KeyboardParams {
}
}
+ public void removeRedundantMoreKeys() {
+ if (mAllowRedundantMoreKeys) {
+ return;
+ }
+ final MoreKeySpec.LettersOnBaseLayout lettersOnBaseLayout =
+ new MoreKeySpec.LettersOnBaseLayout();
+ for (final Key key : mSortedKeys) {
+ lettersOnBaseLayout.addLetter(key);
+ }
+ final ArrayList<Key> allKeys = new ArrayList<>(mSortedKeys);
+ mSortedKeys.clear();
+ for (final Key key : allKeys) {
+ final Key filteredKey = Key.removeRedundantMoreKeys(key, lettersOnBaseLayout);
+ mSortedKeys.add(mUniqueKeysCache.getUniqueKey(filteredKey));
+ }
+ }
+
private int mMaxHeightCount = 0;
private int mMaxWidthCount = 0;
private final SparseIntArray mHeightHistogram = new SparseIntArray();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index b98ced97c..973e956db 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -19,7 +19,9 @@ package com.android.inputmethod.keyboard.internal;
import android.text.TextUtils;
import android.util.Log;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.event.Event;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
/**
@@ -29,7 +31,7 @@ import com.android.inputmethod.latin.utils.RecapitalizeStatus;
*
* The input events are {@link #onLoadKeyboard(int, int)}, {@link #onSaveKeyboardState()},
* {@link #onPressKey(int,boolean,int,int)}, {@link #onReleaseKey(int,boolean,int,int)},
- * {@link #onCodeInput(int,int,int)}, {@link #onFinishSlidingInput(int,int)},
+ * {@link #onEvent(Event,int,int)}, {@link #onFinishSlidingInput(int,int)},
* {@link #onUpdateShiftState(int,int)}, {@link #onResetKeyboardStateToAlphabet(int,int)}.
*
* The actions are {@link SwitchActions}'s methods.
@@ -37,9 +39,11 @@ import com.android.inputmethod.latin.utils.RecapitalizeStatus;
public final class KeyboardState {
private static final String TAG = KeyboardState.class.getSimpleName();
private static final boolean DEBUG_EVENT = false;
- private static final boolean DEBUG_ACTION = false;
+ private static final boolean DEBUG_INTERNAL_ACTION = false;
public interface SwitchActions {
+ public static final boolean DEBUG_ACTION = false;
+
public void setAlphabetKeyboard();
public void setAlphabetManualShiftedKeyboard();
public void setAlphabetAutomaticShiftedKeyboard();
@@ -52,8 +56,9 @@ public final class KeyboardState {
/**
* Request to call back {@link KeyboardState#onUpdateShiftState(int, int)}.
*/
- public void requestUpdatingShiftState(final int currentAutoCapsState,
- final int currentRecapitalizeState);
+ public void requestUpdatingShiftState(final int autoCapsFlags, final int recapitalizeMode);
+
+ public static final boolean DEBUG_TIMER_ACTION = false;
public void startDoubleTapShiftKeyTimer();
public boolean isInDoubleTapShiftKeyTimeout();
@@ -101,15 +106,17 @@ public final class KeyboardState {
@Override
public String toString() {
- if (!mIsValid) return "INVALID";
+ if (!mIsValid) {
+ return "INVALID";
+ }
if (mIsAlphabetMode) {
- if (mIsAlphabetShiftLocked) return "ALPHABET_SHIFT_LOCKED";
- return "ALPHABET_" + shiftModeToString(mShiftMode);
- } else if (mIsEmojiMode) {
+ return mIsAlphabetShiftLocked ? "ALPHABET_SHIFT_LOCKED"
+ : "ALPHABET_" + shiftModeToString(mShiftMode);
+ }
+ if (mIsEmojiMode) {
return "EMOJI";
- } else {
- return "SYMBOLS_" + shiftModeToString(mShiftMode);
}
+ return "SYMBOLS_" + shiftModeToString(mShiftMode);
}
}
@@ -118,10 +125,9 @@ public final class KeyboardState {
mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
}
- public void onLoadKeyboard(final int currentAutoCapsState,
- final int currentRecapitalizeState) {
+ public void onLoadKeyboard(final int autoCapsFlags, final int recapitalizeMode) {
if (DEBUG_EVENT) {
- Log.d(TAG, "onLoadKeyboard: " + this);
+ Log.d(TAG, "onLoadKeyboard: " + stateToString(autoCapsFlags, recapitalizeMode));
}
// Reset alphabet shift state.
mAlphabetShiftState.setShiftLocked(false);
@@ -129,9 +135,16 @@ public final class KeyboardState {
mPrevSymbolsKeyboardWasShifted = false;
mShiftKeyState.onRelease();
mSymbolKeyState.onRelease();
- onRestoreKeyboardState(currentAutoCapsState, currentRecapitalizeState);
+ if (mSavedKeyboardState.mIsValid) {
+ onRestoreKeyboardState(autoCapsFlags, recapitalizeMode);
+ mSavedKeyboardState.mIsValid = false;
+ } else {
+ // Reset keyboard to alphabet mode.
+ setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
+ }
}
+ // Constants for {@link SavedKeyboardState#mShiftMode} and {@link #setShifted(int)}.
private static final int UNSHIFT = 0;
private static final int MANUAL_SHIFT = 1;
private static final int AUTOMATIC_SHIFT = 2;
@@ -155,39 +168,35 @@ public final class KeyboardState {
}
}
- private void onRestoreKeyboardState(final int currentAutoCapsState,
- final int currentRecapitalizeState) {
+ private void onRestoreKeyboardState(final int autoCapsFlags, final int recapitalizeMode) {
final SavedKeyboardState state = mSavedKeyboardState;
if (DEBUG_EVENT) {
- Log.d(TAG, "onRestoreKeyboardState: saved=" + state + " " + this);
- }
- if (!state.mIsValid || state.mIsAlphabetMode) {
- setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState);
- } else if (state.mIsEmojiMode) {
- setEmojiKeyboard();
- } else {
- if (state.mShiftMode == MANUAL_SHIFT) {
- setSymbolsShiftedKeyboard();
- } else {
- setSymbolsKeyboard();
- }
+ Log.d(TAG, "onRestoreKeyboardState: saved=" + state
+ + " " + stateToString(autoCapsFlags, recapitalizeMode));
}
-
- if (!state.mIsValid) return;
- state.mIsValid = false;
-
+ mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked;
if (state.mIsAlphabetMode) {
+ setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
setShiftLocked(state.mIsAlphabetShiftLocked);
if (!state.mIsAlphabetShiftLocked) {
setShifted(state.mShiftMode);
}
+ return;
+ }
+ if (state.mIsEmojiMode) {
+ setEmojiKeyboard();
+ return;
+ }
+ // Symbol mode
+ if (state.mShiftMode == MANUAL_SHIFT) {
+ setSymbolsShiftedKeyboard();
} else {
- mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked;
+ setSymbolsKeyboard();
}
}
private void setShifted(final int shiftMode) {
- if (DEBUG_ACTION) {
+ if (DEBUG_INTERNAL_ACTION) {
Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this);
}
if (!mIsAlphabetMode) return;
@@ -226,7 +235,7 @@ public final class KeyboardState {
}
private void setShiftLocked(final boolean shiftLocked) {
- if (DEBUG_ACTION) {
+ if (DEBUG_INTERNAL_ACTION) {
Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this);
}
if (!mIsAlphabetMode) return;
@@ -240,10 +249,10 @@ public final class KeyboardState {
mAlphabetShiftState.setShiftLocked(shiftLocked);
}
- private void toggleAlphabetAndSymbols(final int currentAutoCapsState,
- final int currentRecapitalizeState) {
- if (DEBUG_ACTION) {
- Log.d(TAG, "toggleAlphabetAndSymbols: " + this);
+ private void toggleAlphabetAndSymbols(final int autoCapsFlags, final int recapitalizeMode) {
+ if (DEBUG_INTERNAL_ACTION) {
+ Log.d(TAG, "toggleAlphabetAndSymbols: "
+ + stateToString(autoCapsFlags, recapitalizeMode));
}
if (mIsAlphabetMode) {
mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked();
@@ -255,7 +264,7 @@ public final class KeyboardState {
mPrevSymbolsKeyboardWasShifted = false;
} else {
mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
- setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState);
+ setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
if (mPrevMainKeyboardWasShiftLocked) {
setShiftLocked(true);
}
@@ -265,15 +274,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(final int currentAutoCapsState,
- final int currentRecapitalizeState) {
- if (DEBUG_ACTION) {
- Log.d(TAG, "resetKeyboardStateToAlphabet: " + this);
+ private void resetKeyboardStateToAlphabet(final int autoCapsFlags, final int recapitalizeMode) {
+ if (DEBUG_INTERNAL_ACTION) {
+ Log.d(TAG, "resetKeyboardStateToAlphabet: "
+ + stateToString(autoCapsFlags, recapitalizeMode));
}
if (mIsAlphabetMode) return;
mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
- setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState);
+ setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
if (mPrevMainKeyboardWasShiftLocked) {
setShiftLocked(true);
}
@@ -288,10 +297,9 @@ public final class KeyboardState {
}
}
- private void setAlphabetKeyboard(final int currentAutoCapsState,
- final int currentRecapitalizeState) {
- if (DEBUG_ACTION) {
- Log.d(TAG, "setAlphabetKeyboard");
+ private void setAlphabetKeyboard(final int autoCapsFlags, final int recapitalizeMode) {
+ if (DEBUG_INTERNAL_ACTION) {
+ Log.d(TAG, "setAlphabetKeyboard: " + stateToString(autoCapsFlags, recapitalizeMode));
}
mSwitchActions.setAlphabetKeyboard();
@@ -300,11 +308,11 @@ public final class KeyboardState {
mIsSymbolShifted = false;
mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
mSwitchState = SWITCH_STATE_ALPHA;
- mSwitchActions.requestUpdatingShiftState(currentAutoCapsState, currentRecapitalizeState);
+ mSwitchActions.requestUpdatingShiftState(autoCapsFlags, recapitalizeMode);
}
private void setSymbolsKeyboard() {
- if (DEBUG_ACTION) {
+ if (DEBUG_INTERNAL_ACTION) {
Log.d(TAG, "setSymbolsKeyboard");
}
mSwitchActions.setSymbolsKeyboard();
@@ -317,7 +325,7 @@ public final class KeyboardState {
}
private void setSymbolsShiftedKeyboard() {
- if (DEBUG_ACTION) {
+ if (DEBUG_INTERNAL_ACTION) {
Log.d(TAG, "setSymbolsShiftedKeyboard");
}
mSwitchActions.setSymbolsShiftedKeyboard();
@@ -330,7 +338,7 @@ public final class KeyboardState {
}
private void setEmojiKeyboard() {
- if (DEBUG_ACTION) {
+ if (DEBUG_INTERNAL_ACTION) {
Log.d(TAG, "setEmojiKeyboard");
}
mIsAlphabetMode = false;
@@ -342,11 +350,12 @@ public final class KeyboardState {
mSwitchActions.setEmojiKeyboard();
}
- public void onPressKey(final int code, final boolean isSinglePointer,
- final int currentAutoCapsState, final int currentRecapitalizeState) {
+ public void onPressKey(final int code, final boolean isSinglePointer, final int autoCapsFlags,
+ final int recapitalizeMode) {
if (DEBUG_EVENT) {
- Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code) + " single="
- + isSinglePointer + " autoCaps=" + currentAutoCapsState + " " + this);
+ Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code)
+ + " single=" + isSinglePointer
+ + " " + stateToString(autoCapsFlags, recapitalizeMode));
}
if (code != Constants.CODE_SHIFT) {
// Because the double tap shift key timer is to detect two consecutive shift key press,
@@ -358,7 +367,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(currentAutoCapsState, currentRecapitalizeState);
+ onPressSymbol(autoCapsFlags, recapitalizeMode);
} else {
mShiftKeyState.onOtherKeyPressed();
mSymbolKeyState.onOtherKeyPressed();
@@ -371,7 +380,7 @@ public final class KeyboardState {
// off because, for example, we may be in the #1 state within the manual temporary
// shifted mode.
if (!isSinglePointer && mIsAlphabetMode
- && currentAutoCapsState != TextUtils.CAP_MODE_CHARACTERS) {
+ && autoCapsFlags != TextUtils.CAP_MODE_CHARACTERS) {
final boolean needsToResetAutoCaps = mAlphabetShiftState.isAutomaticShifted()
|| (mAlphabetShiftState.isManualShifted() && mShiftKeyState.isReleasing());
if (needsToResetAutoCaps) {
@@ -381,34 +390,35 @@ public final class KeyboardState {
}
}
- public void onReleaseKey(final int code, final boolean withSliding,
- final int currentAutoCapsState, final int currentRecapitalizeState) {
+ public void onReleaseKey(final int code, final boolean withSliding, final int autoCapsFlags,
+ final int recapitalizeMode) {
if (DEBUG_EVENT) {
Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code)
- + " sliding=" + withSliding + " " + this);
+ + " sliding=" + withSliding
+ + " " + stateToString(autoCapsFlags, recapitalizeMode));
}
if (code == Constants.CODE_SHIFT) {
- onReleaseShift(withSliding, currentAutoCapsState, currentRecapitalizeState);
+ onReleaseShift(withSliding, autoCapsFlags, recapitalizeMode);
} else if (code == Constants.CODE_CAPSLOCK) {
setShiftLocked(!mAlphabetShiftState.isShiftLocked());
} else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
- onReleaseSymbol(withSliding, currentAutoCapsState, currentRecapitalizeState);
+ onReleaseSymbol(withSliding, autoCapsFlags, recapitalizeMode);
}
}
- private void onPressSymbol(final int currentAutoCapsState,
- final int currentRecapitalizeState) {
- toggleAlphabetAndSymbols(currentAutoCapsState, currentRecapitalizeState);
+ private void onPressSymbol(final int autoCapsFlags,
+ final int recapitalizeMode) {
+ toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode);
mSymbolKeyState.onPress();
mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
}
- private void onReleaseSymbol(final boolean withSliding, final int currentAutoCapsState,
- final int currentRecapitalizeState) {
+ private void onReleaseSymbol(final boolean withSliding, final int autoCapsFlags,
+ final int recapitalizeMode) {
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(currentAutoCapsState, currentRecapitalizeState);
+ toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode);
} 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
@@ -418,23 +428,23 @@ public final class KeyboardState {
mSymbolKeyState.onRelease();
}
- public void onUpdateShiftState(final int autoCaps, final int recapitalizeMode) {
+ public void onUpdateShiftState(final int autoCapsFlags, final int recapitalizeMode) {
if (DEBUG_EVENT) {
- Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + ", recapitalizeMode="
- + recapitalizeMode + " " + this);
+ Log.d(TAG, "onUpdateShiftState: " + stateToString(autoCapsFlags, recapitalizeMode));
}
mRecapitalizeMode = recapitalizeMode;
- updateAlphabetShiftState(autoCaps, recapitalizeMode);
+ updateAlphabetShiftState(autoCapsFlags, recapitalizeMode);
}
// 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(final int currentAutoCapsState,
- final int currentRecapitalizeState) {
+ public void onResetKeyboardStateToAlphabet(final int autoCapsFlags,
+ final int recapitalizeMode) {
if (DEBUG_EVENT) {
- Log.d(TAG, "onResetKeyboardStateToAlphabet: " + this);
+ Log.d(TAG, "onResetKeyboardStateToAlphabet: "
+ + stateToString(autoCapsFlags, recapitalizeMode));
}
- resetKeyboardStateToAlphabet(currentAutoCapsState, currentRecapitalizeState);
+ resetKeyboardStateToAlphabet(autoCapsFlags, recapitalizeMode);
}
private void updateShiftStateForRecapitalize(final int recapitalizeMode) {
@@ -452,7 +462,7 @@ public final class KeyboardState {
}
}
- private void updateAlphabetShiftState(final int autoCaps, final int recapitalizeMode) {
+ private void updateAlphabetShiftState(final int autoCapsFlags, final int recapitalizeMode) {
if (!mIsAlphabetMode) return;
if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != recapitalizeMode) {
// We are recapitalizing. Match the keyboard to the current recapitalize state.
@@ -465,7 +475,7 @@ public final class KeyboardState {
return;
}
if (!mAlphabetShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) {
- if (mShiftKeyState.isReleasing() && autoCaps != Constants.TextUtils.CAP_MODE_OFF) {
+ if (mShiftKeyState.isReleasing() && autoCapsFlags != Constants.TextUtils.CAP_MODE_OFF) {
// Only when shift key is releasing, automatic temporary upper case will be set.
setShifted(AUTOMATIC_SHIFT);
} else {
@@ -525,8 +535,8 @@ public final class KeyboardState {
}
}
- private void onReleaseShift(final boolean withSliding, final int currentAutoCapsState,
- final int currentRecapitalizeState) {
+ private void onReleaseShift(final boolean withSliding, final int autoCapsFlags,
+ final int recapitalizeMode) {
if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
// We are recapitalizing. We should match the keyboard state to the recapitalize
// state in priority.
@@ -549,8 +559,7 @@ public final class KeyboardState {
// After chording input, automatic shift state may have been changed depending on
// what characters were input.
mShiftKeyState.onRelease();
- mSwitchActions.requestUpdatingShiftState(currentAutoCapsState,
- currentRecapitalizeState);
+ mSwitchActions.requestUpdatingShiftState(autoCapsFlags, recapitalizeMode);
return;
} else if (mAlphabetShiftState.isShiftLockShifted() && withSliding) {
// In shift locked state, shift has been pressed and slid out to other key.
@@ -587,21 +596,20 @@ public final class KeyboardState {
mShiftKeyState.onRelease();
}
- public void onFinishSlidingInput(final int currentAutoCapsState,
- final int currentRecapitalizeState) {
+ public void onFinishSlidingInput(final int autoCapsFlags, final int recapitalizeMode) {
if (DEBUG_EVENT) {
- Log.d(TAG, "onFinishSlidingInput: " + this);
+ Log.d(TAG, "onFinishSlidingInput: " + stateToString(autoCapsFlags, recapitalizeMode));
}
// Switch back to the previous keyboard mode if the user cancels sliding input.
switch (mSwitchState) {
case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
- toggleAlphabetAndSymbols(currentAutoCapsState, currentRecapitalizeState);
+ toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode);
break;
case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
toggleShiftInSymbols();
break;
case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT:
- setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState);
+ setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
break;
}
}
@@ -610,11 +618,11 @@ public final class KeyboardState {
return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
}
- public void onCodeInput(final int code, final int currentAutoCapsState,
- final int currentRecapitalizeState) {
+ public void onEvent(final Event event, final int autoCapsFlags, final int recapitalizeMode) {
+ final int code = event.isFunctionalKeyEvent() ? event.mKeyCode : event.mCodePoint;
if (DEBUG_EVENT) {
- Log.d(TAG, "onCodeInput: code=" + Constants.printableCode(code)
- + " autoCaps=" + currentAutoCapsState + " " + this);
+ Log.d(TAG, "onEvent: code=" + Constants.printableCode(code)
+ + " " + stateToString(autoCapsFlags, recapitalizeMode));
}
switch (mSwitchState) {
@@ -650,7 +658,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(currentAutoCapsState, currentRecapitalizeState);
+ toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode);
mPrevSymbolsKeyboardWasShifted = false;
}
break;
@@ -658,11 +666,11 @@ public final class KeyboardState {
// If the code is a letter, update keyboard shift state.
if (Constants.isLetterCode(code)) {
- updateAlphabetShiftState(currentAutoCapsState, currentRecapitalizeState);
+ updateAlphabetShiftState(autoCapsFlags, recapitalizeMode);
} else if (code == Constants.CODE_EMOJI) {
setEmojiKeyboard();
} else if (code == Constants.CODE_ALPHA_FROM_EMOJI) {
- setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState);
+ setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
}
}
@@ -695,4 +703,9 @@ public final class KeyboardState {
+ " symbol=" + mSymbolKeyState
+ " switch=" + switchStateToString(mSwitchState) + "]";
}
+
+ private String stateToString(final int autoCapsFlags, final int recapitalizeMode) {
+ return this + " autoCapsFlags=" + CapsModeUtils.flagsToString(autoCapsFlags)
+ + " recapitalizeMode=" + RecapitalizeStatus.modeToString(recapitalizeMode);
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index cd6abeed3..0aaf6b401 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -21,53 +21,46 @@ 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.common.Constants;
import com.android.inputmethod.latin.utils.RunInLocale;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
-import java.util.HashMap;
import java.util.Locale;
+// TODO: Make this an immutable class.
public final class KeyboardTextsSet {
public static final String PREFIX_TEXT = "!text/";
+ private static final String PREFIX_RESOURCE = "!string/";
public static final String SWITCH_TO_ALPHA_KEY_LABEL = "keylabel_to_alpha";
private static final char BACKSLASH = Constants.CODE_BACKSLASH;
- private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
+ private static final int MAX_REFERENCE_INDIRECTION = 10;
+ private Resources mResources;
+ private Locale mResourceLocale;
+ private String mResourcePackageName;
private String[] mTextsTable;
- // Resource name to text map.
- private HashMap<String, String> mResourceNameToTextsMap = new HashMap<>();
public void setLocale(final Locale locale, final Context context) {
- mTextsTable = KeyboardTextsTable.getTextsTable(locale);
final Resources res = context.getResources();
- final int referenceId = context.getApplicationInfo().labelRes;
- final String resourcePackageName = res.getResourcePackageName(referenceId);
- final RunInLocale<Void> job = new RunInLocale<Void>() {
- @Override
- protected Void job(final Resources resource) {
- loadStringResourcesInternal(res, RESOURCE_NAMES, resourcePackageName);
- return null;
- }
- };
// Null means the current system locale.
- job.runInLocale(res,
- SubtypeLocaleUtils.NO_LANGUAGE.equals(locale.toString()) ? null : locale);
+ final String resourcePackageName = res.getResourcePackageName(
+ context.getApplicationInfo().labelRes);
+ setLocale(locale, res, resourcePackageName);
}
@UsedForTesting
- void loadStringResourcesInternal(final Resources res, final String[] resourceNames,
+ public void setLocale(final Locale locale, final Resources res,
final String resourcePackageName) {
- for (final String resName : resourceNames) {
- final int resId = res.getIdentifier(resName, "string", resourcePackageName);
- mResourceNameToTextsMap.put(resName, res.getString(resId));
- }
+ mResources = res;
+ // Null means the current system locale.
+ mResourceLocale = SubtypeLocaleUtils.NO_LANGUAGE.equals(locale.toString()) ? null : locale;
+ mResourcePackageName = resourcePackageName;
+ mTextsTable = KeyboardTextsTable.getTextsTable(locale);
}
public String getText(final String name) {
- final String text = mResourceNameToTextsMap.get(name);
- return (text != null) ? text : KeyboardTextsTable.getText(name, mTextsTable);
+ return KeyboardTextsTable.getText(name, mTextsTable);
}
private static int searchTextNameEnd(final String text, final int start) {
@@ -93,13 +86,14 @@ public final class KeyboardTextsSet {
StringBuilder sb;
do {
level++;
- if (level >= MAX_STRING_REFERENCE_INDIRECTION) {
- throw new RuntimeException("Too many " + PREFIX_TEXT + "name indirection: " + text);
+ if (level >= MAX_REFERENCE_INDIRECTION) {
+ throw new RuntimeException("Too many " + PREFIX_TEXT + " or " + PREFIX_RESOURCE +
+ " reference indirection: " + text);
}
- final int prefixLen = PREFIX_TEXT.length();
+ final int prefixLength = PREFIX_TEXT.length();
final int size = text.length();
- if (size < prefixLen) {
+ if (size < prefixLength) {
break;
}
@@ -110,10 +104,12 @@ public final class KeyboardTextsSet {
if (sb == null) {
sb = new StringBuilder(text.substring(0, pos));
}
- final int end = searchTextNameEnd(text, pos + prefixLen);
- final String name = text.substring(pos + prefixLen, end);
- sb.append(getText(name));
- pos = end - 1;
+ pos = expandReference(text, pos, PREFIX_TEXT, sb);
+ } else if (text.startsWith(PREFIX_RESOURCE, pos)) {
+ if (sb == null) {
+ sb = new StringBuilder(text.substring(0, pos));
+ }
+ pos = expandReference(text, pos, PREFIX_RESOURCE, sb);
} else if (c == BACKSLASH) {
if (sb != null) {
// Append both escape character and escaped character.
@@ -132,18 +128,24 @@ public final class KeyboardTextsSet {
return TextUtils.isEmpty(text) ? null : text;
}
- // These texts' name should be aligned with the @string/<name> in
- // values*/strings-action-keys.xml.
- static final String[] RESOURCE_NAMES = {
- // Labels for action.
- "label_go_key",
- "label_send_key",
- "label_next_key",
- "label_done_key",
- "label_search_key",
- "label_previous_key",
- // Other labels.
- "label_pause_key",
- "label_wait_key",
- };
+ private int expandReference(final String text, final int pos, final String prefix,
+ final StringBuilder sb) {
+ final int prefixLength = prefix.length();
+ final int end = searchTextNameEnd(text, pos + prefixLength);
+ final String name = text.substring(pos + prefixLength, end);
+ if (prefix.equals(PREFIX_TEXT)) {
+ sb.append(getText(name));
+ } else { // PREFIX_RESOURCE
+ final String resourcePackageName = mResourcePackageName;
+ final RunInLocale<String> getTextJob = new RunInLocale<String>() {
+ @Override
+ protected String job(final Resources res) {
+ final int resId = res.getIdentifier(name, "string", resourcePackageName);
+ return res.getString(resId);
+ }
+ };
+ sb.append(getTextJob.runInLocale(mResources, mResourceLocale));
+ }
+ return end - 1;
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
index 31bc549ca..b50c0a86a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
@@ -83,24 +83,24 @@ public final class KeyboardTextsTable {
private static final String[] NAMES = {
// /* index:histogram */ "name",
- /* 0:32 */ "morekeys_a",
- /* 1:32 */ "morekeys_o",
- /* 2:30 */ "morekeys_u",
- /* 3:30 */ "keylabel_to_alpha",
- /* 4:29 */ "morekeys_e",
- /* 5:28 */ "morekeys_i",
- /* 6:23 */ "morekeys_c",
- /* 7:23 */ "double_quotes",
- /* 8:22 */ "morekeys_n",
- /* 9:22 */ "single_quotes",
- /* 10:20 */ "morekeys_s",
- /* 11:17 */ "keyspec_currency",
- /* 12:14 */ "morekeys_y",
- /* 13:13 */ "morekeys_d",
- /* 14:12 */ "morekeys_z",
+ /* 0:33 */ "morekeys_a",
+ /* 1:33 */ "morekeys_o",
+ /* 2:32 */ "morekeys_e",
+ /* 3:31 */ "morekeys_u",
+ /* 4:31 */ "keylabel_to_alpha",
+ /* 5:30 */ "morekeys_i",
+ /* 6:25 */ "morekeys_n",
+ /* 7:25 */ "morekeys_c",
+ /* 8:23 */ "double_quotes",
+ /* 9:22 */ "morekeys_s",
+ /* 10:22 */ "single_quotes",
+ /* 11:19 */ "keyspec_currency",
+ /* 12:17 */ "morekeys_y",
+ /* 13:16 */ "morekeys_z",
+ /* 14:14 */ "morekeys_d",
/* 15:10 */ "morekeys_t",
/* 16:10 */ "morekeys_l",
- /* 17: 9 */ "morekeys_g",
+ /* 17:10 */ "morekeys_g",
/* 18: 9 */ "single_angle_quotes",
/* 19: 9 */ "double_angle_quotes",
/* 20: 8 */ "morekeys_r",
@@ -136,121 +136,129 @@ public final class KeyboardTextsTable {
/* 50: 5 */ "additional_morekeys_symbols_8",
/* 51: 5 */ "additional_morekeys_symbols_9",
/* 52: 5 */ "additional_morekeys_symbols_0",
- /* 53: 4 */ "morekeys_nordic_row2_11",
- /* 54: 4 */ "morekeys_punctuation",
- /* 55: 4 */ "keyspec_tablet_comma",
- /* 56: 3 */ "keyspec_swiss_row1_11",
- /* 57: 3 */ "keyspec_swiss_row2_10",
- /* 58: 3 */ "keyspec_swiss_row2_11",
- /* 59: 3 */ "morekeys_swiss_row1_11",
- /* 60: 3 */ "morekeys_swiss_row2_10",
- /* 61: 3 */ "morekeys_swiss_row2_11",
- /* 62: 3 */ "morekeys_star",
- /* 63: 3 */ "keyspec_left_parenthesis",
- /* 64: 3 */ "keyspec_right_parenthesis",
- /* 65: 3 */ "keyspec_left_square_bracket",
- /* 66: 3 */ "keyspec_right_square_bracket",
- /* 67: 3 */ "keyspec_left_curly_bracket",
- /* 68: 3 */ "keyspec_right_curly_bracket",
- /* 69: 3 */ "keyspec_less_than",
- /* 70: 3 */ "keyspec_greater_than",
- /* 71: 3 */ "keyspec_less_than_equal",
- /* 72: 3 */ "keyspec_greater_than_equal",
- /* 73: 3 */ "keyspec_left_double_angle_quote",
- /* 74: 3 */ "keyspec_right_double_angle_quote",
- /* 75: 3 */ "keyspec_left_single_angle_quote",
- /* 76: 3 */ "keyspec_right_single_angle_quote",
- /* 77: 3 */ "keyspec_comma",
- /* 78: 3 */ "morekeys_tablet_comma",
- /* 79: 3 */ "keyhintlabel_period",
- /* 80: 3 */ "morekeys_tablet_period",
- /* 81: 3 */ "morekeys_question",
- /* 82: 2 */ "morekeys_h",
- /* 83: 2 */ "morekeys_w",
- /* 84: 2 */ "morekeys_east_slavic_row2_2",
- /* 85: 2 */ "morekeys_cyrillic_u",
- /* 86: 2 */ "morekeys_cyrillic_en",
- /* 87: 2 */ "morekeys_cyrillic_ghe",
- /* 88: 2 */ "morekeys_cyrillic_o",
- /* 89: 2 */ "morekeys_cyrillic_i",
- /* 90: 2 */ "keyspec_south_slavic_row1_6",
- /* 91: 2 */ "keyspec_south_slavic_row2_11",
- /* 92: 2 */ "keyspec_south_slavic_row3_1",
- /* 93: 2 */ "keyspec_south_slavic_row3_8",
- /* 94: 2 */ "morekeys_tablet_punctuation",
- /* 95: 2 */ "keyspec_spanish_row2_10",
- /* 96: 2 */ "morekeys_bullet",
- /* 97: 2 */ "morekeys_left_parenthesis",
- /* 98: 2 */ "morekeys_right_parenthesis",
- /* 99: 2 */ "morekeys_arabic_diacritics",
- /* 100: 2 */ "keyhintlabel_tablet_comma",
- /* 101: 2 */ "keyspec_period",
- /* 102: 2 */ "morekeys_period",
- /* 103: 2 */ "keyspec_tablet_period",
+ /* 53: 5 */ "morekeys_tablet_period",
+ /* 54: 4 */ "morekeys_nordic_row2_11",
+ /* 55: 4 */ "morekeys_punctuation",
+ /* 56: 4 */ "keyspec_tablet_comma",
+ /* 57: 4 */ "keyspec_period",
+ /* 58: 4 */ "morekeys_period",
+ /* 59: 4 */ "keyspec_tablet_period",
+ /* 60: 3 */ "keyspec_swiss_row1_11",
+ /* 61: 3 */ "keyspec_swiss_row2_10",
+ /* 62: 3 */ "keyspec_swiss_row2_11",
+ /* 63: 3 */ "morekeys_swiss_row1_11",
+ /* 64: 3 */ "morekeys_swiss_row2_10",
+ /* 65: 3 */ "morekeys_swiss_row2_11",
+ /* 66: 3 */ "morekeys_star",
+ /* 67: 3 */ "keyspec_left_parenthesis",
+ /* 68: 3 */ "keyspec_right_parenthesis",
+ /* 69: 3 */ "keyspec_left_square_bracket",
+ /* 70: 3 */ "keyspec_right_square_bracket",
+ /* 71: 3 */ "keyspec_left_curly_bracket",
+ /* 72: 3 */ "keyspec_right_curly_bracket",
+ /* 73: 3 */ "keyspec_less_than",
+ /* 74: 3 */ "keyspec_greater_than",
+ /* 75: 3 */ "keyspec_less_than_equal",
+ /* 76: 3 */ "keyspec_greater_than_equal",
+ /* 77: 3 */ "keyspec_left_double_angle_quote",
+ /* 78: 3 */ "keyspec_right_double_angle_quote",
+ /* 79: 3 */ "keyspec_left_single_angle_quote",
+ /* 80: 3 */ "keyspec_right_single_angle_quote",
+ /* 81: 3 */ "keyspec_comma",
+ /* 82: 3 */ "morekeys_tablet_comma",
+ /* 83: 3 */ "keyhintlabel_period",
+ /* 84: 3 */ "morekeys_question",
+ /* 85: 2 */ "morekeys_h",
+ /* 86: 2 */ "morekeys_w",
+ /* 87: 2 */ "morekeys_east_slavic_row2_2",
+ /* 88: 2 */ "morekeys_cyrillic_u",
+ /* 89: 2 */ "morekeys_cyrillic_en",
+ /* 90: 2 */ "morekeys_cyrillic_ghe",
+ /* 91: 2 */ "morekeys_cyrillic_o",
+ /* 92: 2 */ "morekeys_cyrillic_i",
+ /* 93: 2 */ "keyspec_south_slavic_row1_6",
+ /* 94: 2 */ "keyspec_south_slavic_row2_11",
+ /* 95: 2 */ "keyspec_south_slavic_row3_1",
+ /* 96: 2 */ "keyspec_south_slavic_row3_8",
+ /* 97: 2 */ "morekeys_tablet_punctuation",
+ /* 98: 2 */ "keyspec_spanish_row2_10",
+ /* 99: 2 */ "morekeys_bullet",
+ /* 100: 2 */ "morekeys_left_parenthesis",
+ /* 101: 2 */ "morekeys_right_parenthesis",
+ /* 102: 2 */ "morekeys_arabic_diacritics",
+ /* 103: 2 */ "keyhintlabel_tablet_comma",
/* 104: 2 */ "keyhintlabel_tablet_period",
/* 105: 2 */ "keyspec_symbols_question",
/* 106: 2 */ "keyspec_symbols_semicolon",
/* 107: 2 */ "keyspec_symbols_percent",
/* 108: 2 */ "morekeys_symbols_semicolon",
/* 109: 2 */ "morekeys_symbols_percent",
- /* 110: 1 */ "morekeys_v",
- /* 111: 1 */ "morekeys_j",
- /* 112: 1 */ "morekeys_q",
- /* 113: 1 */ "morekeys_x",
- /* 114: 1 */ "keyspec_q",
- /* 115: 1 */ "keyspec_w",
- /* 116: 1 */ "keyspec_y",
- /* 117: 1 */ "keyspec_x",
- /* 118: 1 */ "morekeys_east_slavic_row2_11",
- /* 119: 1 */ "morekeys_cyrillic_ka",
- /* 120: 1 */ "morekeys_cyrillic_a",
- /* 121: 1 */ "morekeys_currency_dollar",
- /* 122: 1 */ "morekeys_plus",
- /* 123: 1 */ "morekeys_less_than",
- /* 124: 1 */ "morekeys_greater_than",
- /* 125: 1 */ "morekeys_exclamation",
- /* 126: 0 */ "morekeys_currency_generic",
- /* 127: 0 */ "morekeys_symbols_1",
- /* 128: 0 */ "morekeys_symbols_2",
- /* 129: 0 */ "morekeys_symbols_3",
- /* 130: 0 */ "morekeys_symbols_4",
- /* 131: 0 */ "morekeys_symbols_5",
- /* 132: 0 */ "morekeys_symbols_6",
- /* 133: 0 */ "morekeys_symbols_7",
- /* 134: 0 */ "morekeys_symbols_8",
- /* 135: 0 */ "morekeys_symbols_9",
- /* 136: 0 */ "morekeys_symbols_0",
- /* 137: 0 */ "morekeys_am_pm",
- /* 138: 0 */ "keyspec_settings",
- /* 139: 0 */ "keyspec_shortcut",
- /* 140: 0 */ "keyspec_action_next",
- /* 141: 0 */ "keyspec_action_previous",
- /* 142: 0 */ "keylabel_to_more_symbol",
- /* 143: 0 */ "keylabel_tablet_to_more_symbol",
- /* 144: 0 */ "keylabel_to_phone_numeric",
- /* 145: 0 */ "keylabel_to_phone_symbols",
- /* 146: 0 */ "keylabel_time_am",
- /* 147: 0 */ "keylabel_time_pm",
- /* 148: 0 */ "keyspec_popular_domain",
- /* 149: 0 */ "morekeys_popular_domain",
- /* 150: 0 */ "keyspecs_left_parenthesis_more_keys",
- /* 151: 0 */ "keyspecs_right_parenthesis_more_keys",
- /* 152: 0 */ "single_laqm_raqm",
- /* 153: 0 */ "single_raqm_laqm",
- /* 154: 0 */ "double_laqm_raqm",
- /* 155: 0 */ "double_raqm_laqm",
- /* 156: 0 */ "single_lqm_rqm",
- /* 157: 0 */ "single_9qm_lqm",
- /* 158: 0 */ "single_9qm_rqm",
- /* 159: 0 */ "single_rqm_9qm",
- /* 160: 0 */ "double_lqm_rqm",
- /* 161: 0 */ "double_9qm_lqm",
- /* 162: 0 */ "double_9qm_rqm",
- /* 163: 0 */ "double_rqm_9qm",
- /* 164: 0 */ "morekeys_single_quote",
- /* 165: 0 */ "morekeys_double_quote",
- /* 166: 0 */ "morekeys_tablet_double_quote",
- /* 167: 0 */ "keyspec_emoji_action_key",
+ /* 110: 2 */ "label_go_key",
+ /* 111: 2 */ "label_send_key",
+ /* 112: 2 */ "label_next_key",
+ /* 113: 2 */ "label_done_key",
+ /* 114: 2 */ "label_search_key",
+ /* 115: 2 */ "label_previous_key",
+ /* 116: 2 */ "label_pause_key",
+ /* 117: 2 */ "label_wait_key",
+ /* 118: 1 */ "morekeys_v",
+ /* 119: 1 */ "morekeys_j",
+ /* 120: 1 */ "morekeys_q",
+ /* 121: 1 */ "morekeys_x",
+ /* 122: 1 */ "keyspec_q",
+ /* 123: 1 */ "keyspec_w",
+ /* 124: 1 */ "keyspec_y",
+ /* 125: 1 */ "keyspec_x",
+ /* 126: 1 */ "morekeys_east_slavic_row2_11",
+ /* 127: 1 */ "morekeys_cyrillic_ka",
+ /* 128: 1 */ "morekeys_cyrillic_a",
+ /* 129: 1 */ "morekeys_currency_dollar",
+ /* 130: 1 */ "morekeys_plus",
+ /* 131: 1 */ "morekeys_less_than",
+ /* 132: 1 */ "morekeys_greater_than",
+ /* 133: 1 */ "morekeys_exclamation",
+ /* 134: 0 */ "morekeys_currency_generic",
+ /* 135: 0 */ "morekeys_symbols_1",
+ /* 136: 0 */ "morekeys_symbols_2",
+ /* 137: 0 */ "morekeys_symbols_3",
+ /* 138: 0 */ "morekeys_symbols_4",
+ /* 139: 0 */ "morekeys_symbols_5",
+ /* 140: 0 */ "morekeys_symbols_6",
+ /* 141: 0 */ "morekeys_symbols_7",
+ /* 142: 0 */ "morekeys_symbols_8",
+ /* 143: 0 */ "morekeys_symbols_9",
+ /* 144: 0 */ "morekeys_symbols_0",
+ /* 145: 0 */ "morekeys_am_pm",
+ /* 146: 0 */ "keyspec_settings",
+ /* 147: 0 */ "keyspec_shortcut",
+ /* 148: 0 */ "keyspec_action_next",
+ /* 149: 0 */ "keyspec_action_previous",
+ /* 150: 0 */ "keylabel_to_more_symbol",
+ /* 151: 0 */ "keylabel_tablet_to_more_symbol",
+ /* 152: 0 */ "keylabel_to_phone_numeric",
+ /* 153: 0 */ "keylabel_to_phone_symbols",
+ /* 154: 0 */ "keylabel_time_am",
+ /* 155: 0 */ "keylabel_time_pm",
+ /* 156: 0 */ "keyspec_popular_domain",
+ /* 157: 0 */ "morekeys_popular_domain",
+ /* 158: 0 */ "keyspecs_left_parenthesis_more_keys",
+ /* 159: 0 */ "keyspecs_right_parenthesis_more_keys",
+ /* 160: 0 */ "single_laqm_raqm",
+ /* 161: 0 */ "single_raqm_laqm",
+ /* 162: 0 */ "double_laqm_raqm",
+ /* 163: 0 */ "double_raqm_laqm",
+ /* 164: 0 */ "single_lqm_rqm",
+ /* 165: 0 */ "single_9qm_lqm",
+ /* 166: 0 */ "single_9qm_rqm",
+ /* 167: 0 */ "single_rqm_9qm",
+ /* 168: 0 */ "double_lqm_rqm",
+ /* 169: 0 */ "double_9qm_lqm",
+ /* 170: 0 */ "double_9qm_rqm",
+ /* 171: 0 */ "double_rqm_9qm",
+ /* 172: 0 */ "morekeys_single_quote",
+ /* 173: 0 */ "morekeys_double_quote",
+ /* 174: 0 */ "morekeys_tablet_double_quote",
+ /* 175: 0 */ "keyspec_emoji_action_key",
};
private static final String EMPTY = "";
@@ -258,17 +266,16 @@ public final class KeyboardTextsTable {
/* Default texts */
private static final String[] TEXTS_DEFAULT = {
/* morekeys_a ~ */
- EMPTY, EMPTY, EMPTY,
+ EMPTY, EMPTY, EMPTY, EMPTY,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
/* keylabel_to_alpha */ "ABC",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
EMPTY, EMPTY, EMPTY,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_lqm_rqm",
- /* morekeys_n */ EMPTY,
- /* single_quotes */ "!text/single_lqm_rqm",
/* morekeys_s */ EMPTY,
+ /* single_quotes */ "!text/single_lqm_rqm",
/* keyspec_currency */ "$",
/* morekeys_y ~ */
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
@@ -291,10 +298,16 @@ public final class KeyboardTextsTable {
// Label for "switch to symbols" key.
/* keylabel_to_symbol */ "?123",
/* additional_morekeys_symbols_1 ~ */
- EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
- /* ~ morekeys_nordic_row2_11 */
+ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+ /* ~ additional_morekeys_symbols_0 */
+ /* morekeys_tablet_period */ "!text/morekeys_tablet_punctuation",
+ /* morekeys_nordic_row2_11 */ EMPTY,
/* morekeys_punctuation */ "!autoColumnOrder!8,\\,,?,!,#,!text/keyspec_right_parenthesis,!text/keyspec_left_parenthesis,/,;,',@,:,-,\",+,\\%,&",
/* keyspec_tablet_comma */ ",",
+ // Period key
+ /* keyspec_period */ ".",
+ /* morekeys_period */ "!text/morekeys_punctuation",
+ /* keyspec_tablet_period */ ".",
/* keyspec_swiss_row1_11 ~ */
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
/* ~ morekeys_swiss_row2_11 */
@@ -328,7 +341,6 @@ public final class KeyboardTextsTable {
/* keyspec_comma */ ",",
/* morekeys_tablet_comma */ EMPTY,
/* keyhintlabel_period */ EMPTY,
- /* morekeys_tablet_period */ "!text/morekeys_tablet_punctuation",
// U+00BF: "¿" INVERTED QUESTION MARK
/* morekeys_question */ "\u00BF",
/* morekeys_h ~ */
@@ -345,19 +357,23 @@ public final class KeyboardTextsTable {
/* morekeys_bullet */ "\u266A,\u2665,\u2660,\u2666,\u2663",
/* morekeys_left_parenthesis */ "!fixedColumnOrder!3,!text/keyspecs_left_parenthesis_more_keys",
/* morekeys_right_parenthesis */ "!fixedColumnOrder!3,!text/keyspecs_right_parenthesis_more_keys",
- /* morekeys_arabic_diacritics */ EMPTY,
- /* keyhintlabel_tablet_comma */ EMPTY,
- // Period key
- /* keyspec_period */ ".",
- /* morekeys_period */ "!text/morekeys_punctuation",
- /* keyspec_tablet_period */ ".",
- /* keyhintlabel_tablet_period */ EMPTY,
+ /* morekeys_arabic_diacritics ~ */
+ EMPTY, EMPTY, EMPTY,
+ /* ~ keyhintlabel_tablet_period */
/* keyspec_symbols_question */ "?",
/* keyspec_symbols_semicolon */ ";",
/* keyspec_symbols_percent */ "%",
/* morekeys_symbols_semicolon */ EMPTY,
// U+2030: "‰" PER MILLE SIGN
/* morekeys_symbols_percent */ "\u2030",
+ /* label_go_key */ "!string/label_go_key",
+ /* label_send_key */ "!string/label_send_key",
+ /* label_next_key */ "!string/label_next_key",
+ /* label_done_key */ "!string/label_done_key",
+ /* label_search_key */ "!string/label_search_key",
+ /* label_previous_key */ "!string/label_previous_key",
+ /* label_pause_key */ "!string/label_pause_key",
+ /* label_wait_key */ "!string/label_wait_key",
/* morekeys_v ~ */
EMPTY, EMPTY, EMPTY, EMPTY,
/* ~ morekeys_x */
@@ -488,13 +504,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -503,6 +512,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -511,13 +527,11 @@ public final class KeyboardTextsTable {
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_i */ "\u00ED,\u00EC,\u00EF,\u00EE,\u012F,\u012B,\u0133",
- /* morekeys_c */ null,
- /* double_quotes */ null,
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
/* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes ~ */
- null, null, null,
+ /* morekeys_c ~ */
+ null, null, null, null, null,
/* ~ keyspec_currency */
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+0133: "ij" LATIN SMALL LIGATURE IJ
@@ -527,7 +541,7 @@ public final class KeyboardTextsTable {
/* Locale ar: Arabic */
private static final String[] TEXTS_ar = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
@@ -535,9 +549,9 @@ public final class KeyboardTextsTable {
// U+0628: "ب" ARABIC LETTER BEH
// U+062C: "ج" ARABIC LETTER JEEM
/* keylabel_to_alpha */ "\u0623\u200C\u0628\u200C\u062C",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_cyrillic_soft_sign */
// U+0661: "١" ARABIC-INDIC DIGIT ONE
/* keyspec_symbols_1 */ "\u0661",
@@ -574,14 +588,17 @@ public final class KeyboardTextsTable {
// U+066B: "٫" ARABIC DECIMAL SEPARATOR
// U+066C: "٬" ARABIC THOUSANDS SEPARATOR
/* additional_morekeys_symbols_0 */ "0,\u066B,\u066C",
+ /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics",
/* morekeys_nordic_row2_11 */ null,
/* morekeys_punctuation */ null,
// 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,
+ /* keyspec_period */ null,
+ /* morekeys_period */ "!text/morekeys_arabic_diacritics",
+ /* keyspec_tablet_period ~ */
+ null, null, null, null, null, null, null,
/* ~ morekeys_swiss_row2_11 */
// U+2605: "★" BLACK STAR
// U+066D: "٭" ARABIC FIVE POINTED STAR
@@ -611,7 +628,6 @@ public final class KeyboardTextsTable {
/* 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 ~ */
@@ -643,9 +659,6 @@ public final class KeyboardTextsTable {
// Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
/* morekeys_arabic_diacritics */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
/* keyhintlabel_tablet_comma */ "\u061F",
- /* keyspec_period */ null,
- /* morekeys_period */ "!text/morekeys_arabic_diacritics",
- /* keyspec_tablet_period */ null,
/* keyhintlabel_tablet_period */ "\u0651",
/* keyspec_symbols_question */ "\u061F",
/* keyspec_symbols_semicolon */ "\u061B",
@@ -658,8 +671,11 @@ public final class KeyboardTextsTable {
/* Locale az_AZ: Azerbaijani (Azerbaijan) */
private static final String[] TEXTS_az_AZ = {
+ // This is the same as Turkish
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- /* morekeys_a */ "\u00E2",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ /* morekeys_a */ "\u00E2,\u00E4,\u00E1",
// U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+0153: "œ" LATIN SMALL LIGATURE OE
@@ -669,6 +685,9 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D",
+ // U+0259: "ə" LATIN SMALL LETTER SCHWA
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ /* morekeys_e */ "\u0259,\u00E9",
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -676,8 +695,6 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
/* keylabel_to_alpha */ null,
- // U+0259: "ə" LATIN SMALL LETTER SCHWA
- /* morekeys_e */ "\u0259",
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -686,20 +703,27 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u0148,\u00F1",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes ~ */
- null, null, null,
- /* ~ single_quotes */
+ /* double_quotes */ null,
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u015F,\u00DF,\u015B,\u0161",
- /* keyspec_currency ~ */
- null, null, null, null, null, null,
+ /* single_quotes */ null,
+ /* keyspec_currency */ null,
+ // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+ /* morekeys_y */ "\u00FD",
+ // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+ /* morekeys_z */ "\u017E",
+ /* morekeys_d ~ */
+ null, null, null,
/* ~ morekeys_l */
// U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
/* morekeys_g */ "\u011F",
@@ -708,21 +732,21 @@ public final class KeyboardTextsTable {
/* Locale be_BY: Belarusian (Belarus) */
private static final String[] TEXTS_be_BY = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_lqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_k */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* morekeys_cyrillic_ie */ "\u0451",
@@ -744,33 +768,50 @@ public final class KeyboardTextsTable {
/* Locale bg: Bulgarian */
private static final String[] TEXTS_bg = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
// single_quotes of Bulgarian is default single_quotes_right_left.
/* double_quotes */ "!text/double_9qm_lqm",
};
+ /* Locale bn_BD: Bengali (Bangladesh) */
+ private static final String[] TEXTS_bn_BD = {
+ /* morekeys_a ~ */
+ null, null, null, null,
+ /* ~ morekeys_u */
+ // Label for "switch to alphabetic" key.
+ // U+0995: "क" BENGALI LETTER KA
+ // U+0996: "ख" BENGALI LETTER KHA
+ // U+0997: "ग" BENGALI LETTER GA
+ /* keylabel_to_alpha */ "\u0995\u0996\u0997",
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // U+09F3: "৳" BENGALI RUPEE SIGN
+ /* keyspec_currency */ "\u09F3",
+ };
+
/* Locale bn_IN: Bengali (India) */
private static final String[] TEXTS_bn_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0995: "क" BENGALI LETTER KA
// U+0996: "ख" BENGALI LETTER KHA
// U+0997: "ग" BENGALI LETTER GA
/* keylabel_to_alpha */ "\u0995\u0996\u0997",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
};
@@ -798,13 +839,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F2,\u00F3,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
@@ -813,6 +847,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E8,\u00E9,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -820,16 +861,15 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes ~ */
- null, null, null, null, null, null, null,
+ /* double_quotes ~ */
+ null, null, null, null, null, null, null, null,
/* ~ morekeys_t */
// U+00B7: "·" MIDDLE DOT
// U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
@@ -837,14 +877,14 @@ public final class KeyboardTextsTable {
/* morekeys_g ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, 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+00B7: "·" MIDDLE DOT
/* morekeys_punctuation */ "!autoColumnOrder!9,\\,,?,!,\u00B7,#,),(,/,;,',@,:,-,\",+,\\%,&",
/* keyspec_tablet_comma ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null,
/* ~ keyspec_south_slavic_row3_8 */
/* morekeys_tablet_punctuation */ "!autoColumnOrder!8,\\,,',\u00B7,#,),(,/,;,@,:,-,\",+,\\%,&",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
@@ -871,14 +911,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+011B: "ě" LATIN SMALL LETTER E WITH CARON
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
@@ -888,6 +920,14 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u011B,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -895,30 +935,30 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u00EC,\u012F,\u012B",
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u0148,\u00F1,\u0144",
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
/* morekeys_c */ "\u010D,\u00E7,\u0107",
/* double_quotes */ "!text/double_9qm_lqm",
- // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u0148,\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
/* morekeys_s */ "\u0161,\u00DF,\u015B",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u010F",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
/* morekeys_z */ "\u017E,\u017A,\u017C",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u010F",
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
/* morekeys_t */ "\u0165",
/* morekeys_l */ null,
@@ -931,20 +971,27 @@ public final class KeyboardTextsTable {
/* Locale da: Danish */
private static final String[] TEXTS_da = {
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
// U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
// U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
// U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
// U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* morekeys_a */ "\u00E1,\u00E4,\u00E0,\u00E2,\u00E3,\u0101",
+ /* morekeys_a */ "\u00E5,\u00E6,\u00E1,\u00E4,\u00E0,\u00E2,\u00E3,\u0101",
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
// U+0153: "œ" LATIN SMALL LIGATURE OE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* morekeys_o */ "\u00F3,\u00F4,\u00F2,\u00F5,\u0153,\u014D",
+ /* morekeys_o */ "\u00F8,\u00F6,\u00F3,\u00F4,\u00F2,\u00F5,\u0153,\u014D",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ /* morekeys_e */ "\u00E9,\u00EB",
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
@@ -952,29 +999,26 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- /* morekeys_e */ "\u00E9,\u00EB",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
/* morekeys_i */ "\u00ED,\u00EF",
- /* morekeys_c */ null,
- /* double_quotes */ "!text/double_9qm_lqm",
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
/* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
+ /* morekeys_c */ null,
+ /* double_quotes */ "!text/double_9qm_lqm",
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u00DF,\u015B,\u0161",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
+ /* morekeys_z */ null,
// U+00F0: "ð" LATIN SMALL LETTER ETH
/* morekeys_d */ "\u00F0",
- /* morekeys_z */ null,
/* morekeys_t */ null,
// U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
/* morekeys_l */ "\u0142",
@@ -994,8 +1038,8 @@ public final class KeyboardTextsTable {
/* morekeys_nordic_row2_10 */ "\u00E4",
/* keyspec_east_slavic_row1_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,
- /* ~ additional_morekeys_symbols_0 */
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_tablet_period */
// U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
/* morekeys_nordic_row2_11 */ "\u00F6",
};
@@ -1020,6 +1064,12 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F6,%,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u00F8,\u014D",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0117",
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -1027,23 +1077,17 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FC,%,\u00FB,\u00F9,\u00FA,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0117",
/* morekeys_i */ null,
- /* morekeys_c */ null,
- /* double_quotes */ "!text/double_9qm_lqm",
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
/* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
+ /* morekeys_c */ null,
+ /* double_quotes */ "!text/double_9qm_lqm",
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u00DF,\u015B,\u0161",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency ~ */
null, null, null, null, null, null, null,
/* ~ morekeys_g */
@@ -1052,8 +1096,8 @@ public final class KeyboardTextsTable {
/* morekeys_r ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
- /* ~ keyspec_tablet_comma */
+ null, null, null, null, null, null, null, null, null, null,
+ /* ~ keyspec_tablet_period */
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
/* keyspec_swiss_row1_11 */ "\u00FC",
// U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
@@ -1071,7 +1115,7 @@ public final class KeyboardTextsTable {
/* Locale el: Greek */
private static final String[] TEXTS_el = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0391: "Α" GREEK CAPITAL LETTER ALPHA
@@ -1100,6 +1144,12 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
/* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u0153,\u00F8,\u014D,\u00F5",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113",
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
@@ -1107,24 +1157,17 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
/* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u012B,\u00EC",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u00F1",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
/* morekeys_c */ "\u00E7",
/* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* morekeys_n */ "\u00F1",
- /* single_quotes */ null,
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
/* morekeys_s */ "\u00DF",
};
@@ -1154,6 +1197,15 @@ public final class KeyboardTextsTable {
// U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D,\u0151,\u00BA",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ /* morekeys_e */ "\u00E9,\u011B,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
@@ -1166,15 +1218,6 @@ public final class KeyboardTextsTable {
// U+00B5: "µ" MICRO SIGN
/* morekeys_u */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B,\u0169,\u0171,\u0173,\u00B5",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* morekeys_e */ "\u00E9,\u011B,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -1185,12 +1228,6 @@ public final class KeyboardTextsTable {
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u0129,\u00EC,\u012F,\u012B,\u0131,\u0133",
- // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
- // U+010D: "č" LATIN SMALL LETTER C WITH CARON
- // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE
- /* morekeys_c */ "\u0107,\u010D,\u00E7,\u010B",
- /* double_quotes */ null,
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
// U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
@@ -1198,27 +1235,33 @@ public final class KeyboardTextsTable {
// U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
// U+014B: "ŋ" LATIN SMALL LETTER ENG
/* morekeys_n */ "\u00F1,\u0144,\u0146,\u0148,\u0149,\u014B",
- /* single_quotes */ null,
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE
+ /* morekeys_c */ "\u0107,\u010D,\u00E7,\u010B",
+ /* double_quotes */ null,
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
/* morekeys_s */ "\u00DF,\u0161,\u015B,\u0219,\u015F",
+ /* single_quotes */ null,
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
// U+00FE: "þ" LATIN SMALL LETTER THORN
/* morekeys_y */ "y,\u00FD,\u0177,\u00FF,\u00FE",
- // U+00F0: "ð" LATIN SMALL LETTER ETH
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
- /* morekeys_d */ "\u00F0,\u010F,\u0111",
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
/* morekeys_z */ "\u017A,\u017C,\u017E",
+ // U+00F0: "ð" LATIN SMALL LETTER ETH
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ /* morekeys_d */ "\u00F0,\u010F,\u0111",
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
// U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
@@ -1248,6 +1291,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null,
/* ~ morekeys_question */
// U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX
// U+0127: "ħ" LATIN SMALL LETTER H WITH STROKE
@@ -1260,8 +1304,9 @@ public final class KeyboardTextsTable {
// U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
/* keyspec_spanish_row2_10 */ "\u0135",
/* morekeys_bullet ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~ morekeys_symbols_percent */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null,
+ /* ~ label_wait_key */
// U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
/* morekeys_v */ "w,\u0175",
/* morekeys_j */ null,
@@ -1300,13 +1345,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
@@ -1315,6 +1353,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -1322,18 +1367,18 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes ~ */
+ /* double_quotes ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null,
/* ~ morekeys_nordic_row2_11 */
// U+00A1: "¡" INVERTED EXCLAMATION MARK
// U+00BF: "¿" INVERTED QUESTION MARK
@@ -1361,6 +1406,15 @@ public final class KeyboardTextsTable {
// U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
/* morekeys_o */ "\u00F6,\u00F5,\u00F2,\u00F3,\u00F4,\u0153,\u0151,\u00F8",
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ /* morekeys_e */ "\u0113,\u00E8,\u0117,\u00E9,\u00EA,\u00EB,\u0119,\u011B",
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
// U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
@@ -1371,15 +1425,6 @@ public final class KeyboardTextsTable {
// U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
/* morekeys_u */ "\u00FC,\u016B,\u0173,\u00F9,\u00FA,\u00FB,\u016F,\u0171",
/* keylabel_to_alpha */ null,
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- /* morekeys_e */ "\u0113,\u00E8,\u0117,\u00E9,\u00EA,\u00EB,\u0119,\u011B",
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
@@ -1388,31 +1433,31 @@ public final class KeyboardTextsTable {
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
/* morekeys_i */ "\u012B,\u00EC,\u012F,\u00ED,\u00EE,\u00EF,\u0131",
+ // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u0146,\u00F1,\u0144",
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
/* morekeys_c */ "\u010D,\u00E7,\u0107",
/* double_quotes */ "!text/double_9qm_lqm",
- // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u0146,\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
/* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u010F",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
/* morekeys_z */ "\u017E,\u017C,\u017A",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u010F",
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
/* morekeys_t */ "\u0163,\u0165",
@@ -1466,13 +1511,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
@@ -1481,6 +1519,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -1488,20 +1533,19 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
};
/* Locale fa: Persian */
private static final String[] TEXTS_fa = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0627: "ا" ARABIC LETTER ALEF
@@ -1509,9 +1553,9 @@ public final class KeyboardTextsTable {
// U+0628: "ب" ARABIC LETTER BEH
// U+067E: "پ" ARABIC LETTER PEH
/* keylabel_to_alpha */ "\u0627\u200C\u0628\u200C\u067E",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+FDFC: "﷼" RIAL SIGN
/* keyspec_currency */ "\uFDFC",
/* morekeys_y ~ */
@@ -1553,6 +1597,7 @@ public final class KeyboardTextsTable {
// U+066B: "٫" ARABIC DECIMAL SEPARATOR
// U+066C: "٬" ARABIC THOUSANDS SEPARATOR
/* additional_morekeys_symbols_0 */ "0,\u066B,\u066C",
+ /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics",
/* morekeys_nordic_row2_11 */ null,
/* morekeys_punctuation */ null,
// U+060C: "،" ARABIC COMMA
@@ -1561,8 +1606,10 @@ public final class KeyboardTextsTable {
// 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,
+ /* keyspec_period */ null,
+ /* morekeys_period */ "!text/morekeys_arabic_diacritics",
+ /* keyspec_tablet_period ~ */
+ null, null, null, null, null, null, null,
/* ~ morekeys_swiss_row2_11 */
// U+2605: "★" BLACK STAR
// U+066D: "٭" ARABIC FIVE POINTED STAR
@@ -1586,7 +1633,6 @@ public final class KeyboardTextsTable {
/* 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 ~ */
@@ -1618,9 +1664,6 @@ public final class KeyboardTextsTable {
// Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
/* morekeys_arabic_diacritics */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
/* keyhintlabel_tablet_comma */ "\u061F",
- /* keyspec_period */ null,
- /* morekeys_period */ "!text/morekeys_arabic_diacritics",
- /* keyspec_tablet_period */ null,
/* keyhintlabel_tablet_period */ "\u064B",
/* keyspec_symbols_question */ "\u061F",
/* keyspec_symbols_semicolon */ "\u061B",
@@ -1629,8 +1672,9 @@ public final class KeyboardTextsTable {
/* morekeys_symbols_semicolon */ ";",
// U+2030: "‰" PER MILLE SIGN
/* morekeys_symbols_percent */ "\\%,\u2030",
- /* morekeys_v ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* label_go_key ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null,
/* ~ morekeys_plus */
// U+2264: "≤" LESS-THAN OR EQUAL TO
// U+2265: "≥" GREATER-THAN EQUAL TO
@@ -1644,13 +1688,16 @@ public final class KeyboardTextsTable {
/* Locale fi: Finnish */
private static final String[] TEXTS_fi = {
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
// U+00E6: "æ" LATIN SMALL LETTER AE
// U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
// U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
// U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* morekeys_a */ "\u00E6,\u00E0,\u00E1,\u00E2,\u00E3,\u0101",
+ /* morekeys_a */ "\u00E4,\u00E5,\u00E6,\u00E0,\u00E1,\u00E2,\u00E3,\u0101",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
@@ -1658,25 +1705,26 @@ public final class KeyboardTextsTable {
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
// U+0153: "œ" LATIN SMALL LIGATURE OE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* morekeys_o */ "\u00F8,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u014D",
+ /* morekeys_o */ "\u00F6,\u00F8,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u014D",
+ /* morekeys_e */ null,
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
/* morekeys_u */ "\u00FC",
/* keylabel_to_alpha ~ */
- null, null, null, null, null, null, null,
- /* ~ single_quotes */
+ null, null, null, null, null,
+ /* ~ double_quotes */
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
/* morekeys_s */ "\u0161,\u00DF,\u015B",
- /* keyspec_currency ~ */
+ /* single_quotes ~ */
null, null, null,
- /* ~ morekeys_d */
+ /* ~ morekeys_y */
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
/* morekeys_z */ "\u017E,\u017A,\u017C",
- /* morekeys_t ~ */
- null, null, null, null, null, null, null, null,
+ /* morekeys_d ~ */
+ null, null, null, null, null, null, null, null, null,
/* ~ morekeys_cyrillic_ie */
// U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
/* keyspec_nordic_row1_11 */ "\u00E5",
@@ -1688,8 +1736,8 @@ public final class KeyboardTextsTable {
/* morekeys_nordic_row2_10 */ "\u00F8",
/* keyspec_east_slavic_row1_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,
- /* ~ additional_morekeys_symbols_0 */
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_tablet_period */
// U+00E6: "æ" LATIN SMALL LETTER AE
/* morekeys_nordic_row2_11 */ "\u00E6",
};
@@ -1716,13 +1764,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F4,\u0153,%,\u00F6,\u00F2,\u00F3,\u00F5,\u00F8,\u014D,\u00BA",
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00F9,\u00FB,%,\u00FC,\u00FA,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -1731,6 +1772,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,%,\u0119,\u0117,\u0113",
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00F9,\u00FB,%,\u00FC,\u00FA,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -1738,20 +1786,22 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00EE,%,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ /* morekeys_n */ null,
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,%,\u0107,\u010D",
/* double_quotes ~ */
- null, null, null, null, null,
+ null, null, null, null,
/* ~ keyspec_currency */
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "%,\u00FF",
- /* morekeys_d ~ */
+ /* morekeys_z ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~ keyspec_tablet_comma */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null,
+ /* ~ keyspec_tablet_period */
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
/* keyspec_swiss_row1_11 */ "\u00E8",
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
@@ -1789,13 +1839,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
@@ -1804,6 +1847,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -1811,29 +1861,28 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
};
/* Locale hi: Hindi */
private static final String[] TEXTS_hi = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0915: "क" DEVANAGARI LETTER KA
// U+0916: "ख" DEVANAGARI LETTER KHA
// U+0917: "ग" DEVANAGARI LETTER GA
/* keylabel_to_alpha */ "\u0915\u0916\u0917",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
/* morekeys_y ~ */
@@ -1872,6 +1921,40 @@ public final class KeyboardTextsTable {
/* additional_morekeys_symbols_8 */ "8",
/* additional_morekeys_symbols_9 */ "9",
/* additional_morekeys_symbols_0 */ "0",
+ /* morekeys_tablet_period */ "!autoColumnOrder!8,\\,,.,',#,),(,/,;,@,:,-,\",+,\\%,&",
+ /* morekeys_nordic_row2_11 ~ */
+ null, null, null,
+ /* ~ keyspec_tablet_comma */
+ // U+0964: "।" DEVANAGARI DANDA
+ /* keyspec_period */ "\u0964",
+ /* morekeys_period */ "!autoColumnOrder!9,\\,,.,?,!,#,),(,/,;,',@,:,-,\",+,\\%,&",
+ /* keyspec_tablet_period */ "\u0964",
+ };
+
+ /* Locale hi_ZZ: Hindi (ZZ) */
+ private static final String[] TEXTS_hi_ZZ = {
+ /* morekeys_a ~ */
+ null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ single_quotes */
+ // U+20B9: "₹" INDIAN RUPEE SIGN
+ /* keyspec_currency */ "\u20B9",
+ /* morekeys_y ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null,
+ /* ~ morekeys_symbols_percent */
+ /* label_go_key */ "Go",
+ /* label_send_key */ "Send",
+ /* label_next_key */ "Next",
+ /* label_done_key */ "Done",
+ /* label_search_key */ "Search",
+ /* label_previous_key */ "Prev",
+ /* label_pause_key */ "Pause",
+ /* label_wait_key */ "Wait",
};
/* Locale hr: Croatian */
@@ -1879,27 +1962,27 @@ public final class KeyboardTextsTable {
/* morekeys_a ~ */
null, null, null, null, null, null,
/* ~ morekeys_i */
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
/* morekeys_c */ "\u010D,\u0107,\u00E7",
/* double_quotes */ "!text/double_9qm_rqm",
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_rqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
/* morekeys_s */ "\u0161,\u015B,\u00DF",
+ /* single_quotes */ "!text/single_9qm_rqm",
/* keyspec_currency */ null,
/* morekeys_y */ null,
- // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
- /* morekeys_d */ "\u0111",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
/* morekeys_z */ "\u017E,\u017A,\u017C",
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ /* morekeys_d */ "\u0111",
/* morekeys_t ~ */
null, null, null,
/* ~ morekeys_g */
@@ -1928,14 +2011,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F6,\u0151,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u0171,\u00FB,\u00F9,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -1944,6 +2019,14 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u0171,\u00FB,\u00F9,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -1951,12 +2034,13 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u00EC,\u012F,\u012B",
+ /* morekeys_n */ null,
/* morekeys_c */ null,
/* double_quotes */ "!text/double_9qm_rqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_rqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null,
/* ~ morekeys_g */
/* single_angle_quotes */ "!text/single_raqm_laqm",
/* double_angle_quotes */ "!text/double_raqm_laqm",
@@ -1965,19 +2049,21 @@ public final class KeyboardTextsTable {
/* Locale hy_AM: Armenian (Armenia) */
private static final String[] TEXTS_hy_AM = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0531: "Ա" ARMENIAN CAPITAL LETTER AYB
// U+0532: "Բ" ARMENIAN CAPITAL LETTER BEN
// U+0533: "Գ" ARMENIAN CAPITAL LETTER GIM
/* keylabel_to_alpha */ "\u0531\u0532\u0533",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
- /* ~ morekeys_nordic_row2_11 */
+ null, null, null,
+ /* ~ additional_morekeys_symbols_0 */
+ /* morekeys_tablet_period */ "!text/morekeys_punctuation",
+ /* morekeys_nordic_row2_11 */ null,
// U+055E: "՞" ARMENIAN QUESTION MARK
// U+055C: "՜" ARMENIAN EXCLAMATION MARK
// U+055A: "՚" ARMENIAN APOSTROPHE
@@ -1990,6 +2076,10 @@ public final class KeyboardTextsTable {
// U+055F: "՟" ARMENIAN ABBREVIATION MARK
/* morekeys_punctuation */ "!autoColumnOrder!8,\\,,\u055E,\u055C,.,\u055A,\u0559,?,!,\u055D,\u055B,\u058A,\u00BB,\u00AB,\u055F,;,:",
/* keyspec_tablet_comma */ "\u055D",
+ // U+0589: "։" ARMENIAN FULL STOP
+ /* keyspec_period */ "\u0589",
+ /* morekeys_period */ null,
+ /* keyspec_tablet_period */ "\u0589",
/* 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,
@@ -2002,21 +2092,14 @@ public final class KeyboardTextsTable {
/* keyspec_comma */ "\u055D",
/* morekeys_tablet_comma */ null,
/* keyhintlabel_period */ null,
- /* 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,
- /* ~ keyhintlabel_tablet_comma */
- // U+0589: "։" ARMENIAN FULL STOP
- /* keyspec_period */ "\u0589",
- /* morekeys_period */ null,
- /* keyspec_tablet_period */ "\u0589",
- /* keyhintlabel_tablet_period ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null,
/* ~ morekeys_greater_than */
// U+055C: "՜" ARMENIAN EXCLAMATION MARK
// U+00A1: "¡" INVERTED EXCLAMATION MARK
@@ -2043,13 +2126,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
@@ -2058,6 +2134,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00EB,\u00E8,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
@@ -2065,18 +2148,18 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EE,\u00EC,\u012F,\u012B",
+ /* morekeys_n */ null,
/* morekeys_c */ null,
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
- /* single_quotes */ "!text/single_9qm_lqm",
/* morekeys_s */ null,
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
+ /* morekeys_z */ null,
// U+00F0: "ð" LATIN SMALL LETTER ETH
/* morekeys_d */ "\u00F0",
- /* morekeys_z */ null,
// U+00FE: "þ" LATIN SMALL LETTER THORN
/* morekeys_t */ "\u00FE",
};
@@ -2103,13 +2186,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F2,\u00F3,\u00F4,\u00F6,\u00F5,\u0153,\u00F8,\u014D,\u00BA",
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00F9,\u00FA,\u00FB,\u00FC,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -2118,6 +2194,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0119,\u0117,\u0113",
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00F9,\u00FA,\u00FB,\u00FC,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
@@ -2125,12 +2208,12 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00EC,\u00ED,\u00EE,\u00EF,\u012F,\u012B",
- /* morekeys_c ~ */
+ /* morekeys_n ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
- /* ~ keyspec_tablet_comma */
+ null, null, null, null, null, null, null, null, null,
+ /* ~ keyspec_tablet_period */
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
/* keyspec_swiss_row1_11 */ "\u00FC",
// U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
@@ -2148,27 +2231,26 @@ public final class KeyboardTextsTable {
/* Locale iw: Hebrew */
private static final String[] TEXTS_iw = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+05D0: "א" HEBREW LETTER ALEF
// U+05D1: "ב" HEBREW LETTER BET
// U+05D2: "ג" HEBREW LETTER GIMEL
/* keylabel_to_alpha */ "\u05D0\u05D1\u05D2",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_rqm_9qm",
- /* morekeys_n */ null,
- /* single_quotes */ "!text/single_rqm_9qm",
/* morekeys_s */ null,
+ /* single_quotes */ "!text/single_rqm_9qm",
// U+20AA: "₪" NEW SHEQEL SIGN
/* keyspec_currency */ "\u20AA",
/* morekeys_y ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null,
/* ~ morekeys_swiss_row2_11 */
// U+2605: "★" BLACK STAR
/* morekeys_star */ "\u2605",
@@ -2198,6 +2280,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null,
/* ~ morekeys_currency_dollar */
// U+00B1: "±" PLUS-MINUS SIGN
// U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
@@ -2207,34 +2290,34 @@ public final class KeyboardTextsTable {
/* Locale ka_GE: Georgian (Georgia) */
private static final String[] TEXTS_ka_GE = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+10D0: "ა" GEORGIAN LETTER AN
// U+10D1: "ბ" GEORGIAN LETTER BAN
// U+10D2: "გ" GEORGIAN LETTER GAN
/* keylabel_to_alpha */ "\u10D0\u10D1\u10D2",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_lqm",
};
/* Locale kk: Kazakh */
private static final String[] TEXTS_kk = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
+ null, null,
/* ~ morekeys_k */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* morekeys_cyrillic_ie */ "\u0451",
@@ -2255,7 +2338,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_w */
// U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
/* morekeys_east_slavic_row2_2 */ "\u0456",
@@ -2270,7 +2353,8 @@ public final class KeyboardTextsTable {
/* morekeys_cyrillic_o */ "\u04E9",
/* morekeys_cyrillic_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null,
/* ~ keyspec_x */
// U+04BB: "һ" CYRILLIC SMALL LETTER SHHA
/* morekeys_east_slavic_row2_11 */ "\u04BB",
@@ -2283,14 +2367,14 @@ public final class KeyboardTextsTable {
/* Locale km_KH: Khmer (Cambodia) */
private static final String[] TEXTS_km_KH = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+1780: "ក" KHMER LETTER KA
// U+1781: "ខ" KHMER LETTER KHA
// U+1782: "គ" KHMER LETTER KO
/* keylabel_to_alpha */ "\u1780\u1781\u1782",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
@@ -2298,7 +2382,8 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null,
/* ~ morekeys_cyrillic_a */
// U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL
/* morekeys_currency_dollar */ "\u17DB,\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
@@ -2307,16 +2392,16 @@ public final class KeyboardTextsTable {
/* Locale kn_IN: Kannada (India) */
private static final String[] TEXTS_kn_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0C85: "ಅ" KANNADA LETTER A
// U+0C86: "ಆ" KANNADA LETTER AA
// U+0C87: "ಇ" KANNADA LETTER I
/* keylabel_to_alpha */ "\u0C85\u0C86\u0C87",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
};
@@ -2324,16 +2409,16 @@ public final class KeyboardTextsTable {
/* Locale ky: Kirghiz */
private static final String[] TEXTS_ky = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
+ null, null,
/* ~ morekeys_k */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* morekeys_cyrillic_ie */ "\u0451",
@@ -2354,7 +2439,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_east_slavic_row2_2 */
// U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
/* morekeys_cyrillic_u */ "\u04AF",
@@ -2368,16 +2453,16 @@ public final class KeyboardTextsTable {
/* Locale lo_LA: Lao (Laos) */
private static final String[] TEXTS_lo_LA = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0E81: "ກ" LAO LETTER KO
// U+0E82: "ຂ" LAO LETTER KHO SUNG
// U+0E84: "ຄ" LAO LETTER KHO TAM
/* keylabel_to_alpha */ "\u0E81\u0E82\u0E84",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20AD: "₭" KIP SIGN
/* keyspec_currency */ "\u20AD",
};
@@ -2403,6 +2488,15 @@ public final class KeyboardTextsTable {
// U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
/* morekeys_o */ "\u00F6,\u00F5,\u00F2,\u00F3,\u00F4,\u0153,\u0151,\u00F8",
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ /* morekeys_e */ "\u0117,\u0119,\u0113,\u00E8,\u00E9,\u00EA,\u00EB,\u011B",
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
// U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
@@ -2414,15 +2508,6 @@ public final class KeyboardTextsTable {
// U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
/* morekeys_u */ "\u016B,\u0173,\u00FC,\u016B,\u00F9,\u00FA,\u00FB,\u016F,\u0171",
/* keylabel_to_alpha */ null,
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- /* morekeys_e */ "\u0117,\u0119,\u0113,\u00E8,\u00E9,\u00EA,\u00EB,\u011B",
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -2431,31 +2516,31 @@ public final class KeyboardTextsTable {
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
/* morekeys_i */ "\u012F,\u012B,\u00EC,\u00ED,\u00EE,\u00EF,\u0131",
+ // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u0146,\u00F1,\u0144",
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
/* morekeys_c */ "\u010D,\u00E7,\u0107",
/* double_quotes */ "!text/double_9qm_lqm",
- // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u0146,\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
/* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u010F",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
/* morekeys_z */ "\u017E,\u017C,\u017A",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u010F",
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
/* morekeys_t */ "\u0163,\u0165",
@@ -2498,6 +2583,15 @@ public final class KeyboardTextsTable {
// U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
/* morekeys_o */ "\u00F2,\u00F3,\u00F4,\u00F5,\u00F6,\u0153,\u0151,\u00F8",
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ /* morekeys_e */ "\u0113,\u0117,\u00E8,\u00E9,\u00EA,\u00EB,\u0119,\u011B",
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
// U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -2508,15 +2602,6 @@ public final class KeyboardTextsTable {
// U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
/* morekeys_u */ "\u016B,\u0173,\u00F9,\u00FA,\u00FB,\u00FC,\u016F,\u0171",
/* keylabel_to_alpha */ null,
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- /* morekeys_e */ "\u0113,\u0117,\u00E8,\u00E9,\u00EA,\u00EB,\u0119,\u011B",
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -2525,31 +2610,31 @@ public final class KeyboardTextsTable {
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
/* morekeys_i */ "\u012B,\u012F,\u00EC,\u00ED,\u00EE,\u00EF,\u0131",
+ // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u0146,\u00F1,\u0144",
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
/* morekeys_c */ "\u010D,\u00E7,\u0107",
/* double_quotes */ "!text/double_9qm_lqm",
- // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u0146,\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
/* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u010F",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
/* morekeys_z */ "\u017E,\u017C,\u017A",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u010F",
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
/* morekeys_t */ "\u0163,\u0165",
@@ -2574,21 +2659,21 @@ public final class KeyboardTextsTable {
/* Locale mk: Macedonian */
private static final String[] TEXTS_mk = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_lqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_k */
// U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE
/* morekeys_cyrillic_ie */ "\u0450",
@@ -2597,7 +2682,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null,
/* ~ morekeys_cyrillic_o */
// U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
/* morekeys_cyrillic_i */ "\u045D",
@@ -2614,14 +2699,14 @@ public final class KeyboardTextsTable {
/* Locale ml_IN: Malayalam (India) */
private static final String[] TEXTS_ml_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0D05: "അ" MALAYALAM LETTER A
/* keylabel_to_alpha */ "\u0D05",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
};
@@ -2629,16 +2714,16 @@ public final class KeyboardTextsTable {
/* Locale mn_MN: Mongolian (Mongolia) */
private static final String[] TEXTS_mn_MN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20AE: "₮" TUGRIK SIGN
/* keyspec_currency */ "\u20AE",
};
@@ -2646,16 +2731,16 @@ public final class KeyboardTextsTable {
/* Locale mr_IN: Marathi (India) */
private static final String[] TEXTS_mr_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0915: "क" DEVANAGARI LETTER KA
// U+0916: "ख" DEVANAGARI LETTER KHA
// U+0917: "ग" DEVANAGARI LETTER GA
/* keylabel_to_alpha */ "\u0915\u0916\u0917",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
/* morekeys_y ~ */
@@ -2696,68 +2781,26 @@ public final class KeyboardTextsTable {
/* additional_morekeys_symbols_0 */ "0",
};
- /* Locale my_MM: Burmese (Myanmar) */
- private static final String[] TEXTS_my_MM = {
- /* morekeys_a ~ */
- null, null, null,
- /* ~ morekeys_u */
- // Label for "switch to alphabetic" key.
- // U+1000: "က" MYANMAR LETTER KA
- // U+1001: "ခ" MYANMAR LETTER KHA
- // U+1002: "ဂ" MYANMAR LETTER GA
- /* keylabel_to_alpha */ "\u1000\u1001\u1002",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
- /* ~ morekeys_nordic_row2_11 */
- /* morekeys_punctuation */ "!autoColumnOrder!9,\u104A,.,?,!,#,),(,/,;,...,',@,:,-,\",+,\\%,&",
- // U+104A: "၊" MYANMAR SIGN LITTLE SECTION
- // U+104B: "။" MYANMAR SIGN SECTION
- /* keyspec_tablet_comma */ "\u104A",
- /* keyspec_swiss_row1_11 ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
- /* ~ keyspec_comma */
- /* morekeys_tablet_comma */ "\\,",
- /* keyhintlabel_period */ "\u104A",
- /* morekeys_tablet_period ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~ keyspec_south_slavic_row3_8 */
- /* morekeys_tablet_punctuation */ "!autoColumnOrder!8,.,',#,),(,/,;,@,...,:,-,\",+,\\%,&",
- /* keyspec_spanish_row2_10 ~ */
- null, null, null, null, null, null,
- /* ~ keyhintlabel_tablet_comma */
- /* keyspec_period */ "\u104B",
- /* 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+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+00E6: "æ" LATIN SMALL LETTER AE
// U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
// U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
// U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* morekeys_a */ "\u00E0,\u00E4,\u00E1,\u00E2,\u00E3,\u0101",
+ /* morekeys_a */ "\u00E5,\u00E6,\u00E4,\u00E0,\u00E1,\u00E2,\u00E3,\u0101",
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
// U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
- // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
// U+0153: "œ" LATIN SMALL LIGATURE OE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* morekeys_o */ "\u00F4,\u00F2,\u00F3,\u00F6,\u00F5,\u0153,\u014D",
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
- /* keylabel_to_alpha */ null,
+ /* morekeys_o */ "\u00F8,\u00F6,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u014D",
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -2766,13 +2809,20 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
- /* morekeys_i */ null,
- /* morekeys_c */ null,
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
+ /* keylabel_to_alpha ~ */
+ null, null, null, null,
+ /* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_rqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_rqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_cyrillic_ie */
// U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
/* keyspec_nordic_row1_11 */ "\u00E5",
@@ -2784,8 +2834,8 @@ public final class KeyboardTextsTable {
/* morekeys_nordic_row2_10 */ "\u00F6",
/* keyspec_east_slavic_row1_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,
- /* ~ additional_morekeys_symbols_0 */
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_tablet_period */
// U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
/* morekeys_nordic_row2_11 */ "\u00E4",
};
@@ -2793,16 +2843,16 @@ public final class KeyboardTextsTable {
/* Locale ne_NP: Nepali (Nepal) */
private static final String[] TEXTS_ne_NP = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0915: "क" DEVANAGARI LETTER KA
// U+0916: "ख" DEVANAGARI LETTER KHA
// U+0917: "ग" DEVANAGARI LETTER GA
/* keylabel_to_alpha */ "\u0915\u0916\u0917",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
/* keyspec_currency */ "\u0930\u0941.",
/* morekeys_y ~ */
@@ -2841,6 +2891,14 @@ public final class KeyboardTextsTable {
/* additional_morekeys_symbols_8 */ "8",
/* additional_morekeys_symbols_9 */ "9",
/* additional_morekeys_symbols_0 */ "0",
+ /* morekeys_tablet_period */ "!autoColumnOrder!8,.,\\,,',#,),(,/,;,@,:,-,\",+,\\%,&",
+ /* morekeys_nordic_row2_11 ~ */
+ null, null, null,
+ /* ~ keyspec_tablet_comma */
+ // U+0964: "।" DEVANAGARI DANDA
+ /* keyspec_period */ "\u0964",
+ /* morekeys_period */ "!autoColumnOrder!9,.,\\,,?,!,#,),(,/,;,',@,:,-,\",+,\\%,&",
+ /* keyspec_tablet_period */ "\u0964",
};
/* Locale nl: Dutch */
@@ -2863,13 +2921,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
@@ -2878,6 +2929,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00EB,\u00EA,\u00E8,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -2886,13 +2944,13 @@ public final class KeyboardTextsTable {
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B,\u0133",
- /* morekeys_c */ null,
- /* double_quotes */ "!text/double_9qm_rqm",
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
/* morekeys_n */ "\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_rqm",
+ /* morekeys_c */ null,
+ /* double_quotes */ "!text/double_9qm_rqm",
/* morekeys_s */ null,
+ /* single_quotes */ "!text/single_9qm_rqm",
/* keyspec_currency */ null,
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_y */ "\u0133",
@@ -2919,8 +2977,6 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D",
- /* morekeys_u */ null,
- /* keylabel_to_alpha */ null,
// U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
@@ -2929,27 +2985,29 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u0119,\u00E8,\u00E9,\u00EA,\u00EB,\u0117,\u0113",
- /* morekeys_i */ null,
+ /* morekeys_u ~ */
+ null, null, null,
+ /* ~ morekeys_i */
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u0144,\u00F1",
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u0107,\u00E7,\u010D",
/* double_quotes */ "!text/double_9qm_rqm",
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* morekeys_n */ "\u0144,\u00F1",
- /* single_quotes */ "!text/single_9qm_rqm",
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u015B,\u00DF,\u0161",
- /* keyspec_currency ~ */
- null, null, null,
- /* ~ morekeys_d */
+ /* single_quotes */ "!text/single_9qm_rqm",
+ /* keyspec_currency */ null,
+ /* morekeys_y */ null,
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
/* morekeys_z */ "\u017C,\u017A,\u017E",
+ /* morekeys_d */ null,
/* morekeys_t */ null,
// U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
/* morekeys_l */ "\u0142",
@@ -2976,13 +3034,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F5,\u00F4,\u00F2,\u00F6,\u0153,\u00F8,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
@@ -2991,6 +3042,13 @@ public final class KeyboardTextsTable {
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
/* morekeys_e */ "\u00E9,\u00EA,\u00E8,\u0119,\u0117,\u0113,\u00EB",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -2998,6 +3056,7 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EE,\u00EC,\u00EF,\u012F,\u012B",
+ /* morekeys_n */ null,
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
@@ -3019,19 +3078,19 @@ public final class KeyboardTextsTable {
/* Locale ro: Romanian */
private static final String[] TEXTS_ro = {
+ // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
// U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
// U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
// U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
// U+00E6: "æ" LATIN SMALL LETTER AE
// U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
// U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
- /* morekeys_a */ "\u00E2,\u00E3,\u0103,\u00E0,\u00E1,\u00E4,\u00E6,\u00E5,\u0101",
+ /* morekeys_a */ "\u0103,\u00E2,\u00E3,\u00E0,\u00E1,\u00E4,\u00E6,\u00E5,\u0101",
/* morekeys_o ~ */
null, null, null, null,
- /* ~ morekeys_e */
+ /* ~ keylabel_to_alpha */
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -3039,18 +3098,18 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ /* morekeys_n */ null,
/* morekeys_c */ null,
/* double_quotes */ "!text/double_9qm_rqm",
- /* morekeys_n */ null,
- /* single_quotes */ "!text/single_9qm_rqm",
// U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u0219,\u00DF,\u015B,\u0161",
+ /* single_quotes */ "!text/single_9qm_rqm",
/* keyspec_currency ~ */
null, null, null, null,
- /* ~ morekeys_z */
+ /* ~ morekeys_d */
// U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW
/* morekeys_t */ "\u021B",
};
@@ -3058,21 +3117,21 @@ public final class KeyboardTextsTable {
/* Locale ru: Russian */
private static final String[] TEXTS_ru = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_lqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_k */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* morekeys_cyrillic_ie */ "\u0451",
@@ -3094,15 +3153,15 @@ public final class KeyboardTextsTable {
/* Locale si_LK: Sinhalese (Sri Lanka) */
private static final String[] TEXTS_si_LK = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0D85: "අ" SINHALA LETTER AYANNA
// U+0D86: "ආ" SINHALA LETTER AAYANNA
/* keylabel_to_alpha */ "\u0D85,\u0D86",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+0DBB/U+0DD4: "රු" SINHALA LETTER RAYANNA/SINHALA VOWEL SIGN KETTI PAA-PILLA
/* keyspec_currency */ "\u0DBB\u0DD4",
};
@@ -3128,6 +3187,15 @@ public final class KeyboardTextsTable {
// U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
/* morekeys_o */ "\u00F4,\u00F3,\u00F6,\u00F2,\u00F5,\u0153,\u0151,\u00F8",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ /* morekeys_e */ "\u00E9,\u011B,\u0113,\u0117,\u00E8,\u00EA,\u00EB,\u0119",
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
@@ -3138,15 +3206,6 @@ public final class KeyboardTextsTable {
// U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
/* morekeys_u */ "\u00FA,\u016F,\u00FC,\u016B,\u0173,\u00F9,\u00FB,\u0171",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- /* morekeys_e */ "\u00E9,\u011B,\u0113,\u0117,\u00E8,\u00EA,\u00EB,\u0119",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
@@ -3155,32 +3214,32 @@ public final class KeyboardTextsTable {
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
/* morekeys_i */ "\u00ED,\u012B,\u012F,\u00EC,\u00EE,\u00EF,\u0131",
- // U+010D: "č" LATIN SMALL LETTER C WITH CARON
- // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
- /* morekeys_c */ "\u010D,\u00E7,\u0107",
- /* double_quotes */ "!text/double_9qm_lqm",
// U+0148: "ň" LATIN SMALL LETTER N WITH CARON
// U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
/* morekeys_n */ "\u0148,\u0146,\u00F1,\u0144",
- /* single_quotes */ "!text/single_9qm_lqm",
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ /* morekeys_c */ "\u010D,\u00E7,\u0107",
+ /* double_quotes */ "!text/double_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
/* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u010F",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
/* morekeys_z */ "\u017E,\u017C,\u017A",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u010F",
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
/* morekeys_t */ "\u0165,\u0163",
@@ -3205,22 +3264,21 @@ public final class KeyboardTextsTable {
/* Locale sl: Slovenian */
private static final String[] TEXTS_sl = {
/* morekeys_a ~ */
- null, null, null, null, null, null,
- /* ~ morekeys_i */
+ null, null, null, null, null, null, null,
+ /* ~ morekeys_n */
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
/* morekeys_c */ "\u010D,\u0107",
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
- /* single_quotes */ "!text/single_9qm_lqm",
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u0161",
+ /* single_quotes */ "!text/single_9qm_lqm",
/* keyspec_currency */ null,
/* morekeys_y */ null,
- // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
- /* morekeys_d */ "\u0111",
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
/* morekeys_z */ "\u017E",
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ /* morekeys_d */ "\u0111",
/* morekeys_t ~ */
null, null, null,
/* ~ morekeys_g */
@@ -3231,7 +3289,7 @@ public final class KeyboardTextsTable {
/* Locale sr: Serbian */
private static final String[] TEXTS_sr = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// END: More keys definitions for Serbian (Cyrillic)
// Label for "switch to alphabetic" key.
@@ -3239,14 +3297,14 @@ public final class KeyboardTextsTable {
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
+ /* morekeys_s */ null,
/* single_quotes */ "!text/single_9qm_lqm",
- /* morekeys_s ~ */
- null, null, null, null, null, null, null, null,
+ /* keyspec_currency ~ */
+ null, null, null, null, null, null, null,
/* ~ morekeys_g */
/* single_angle_quotes */ "!text/single_raqm_laqm",
/* double_angle_quotes */ "!text/double_raqm_laqm",
@@ -3259,7 +3317,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null,
/* ~ morekeys_cyrillic_o */
// U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
/* morekeys_cyrillic_i */ "\u045D",
@@ -3291,20 +3349,75 @@ public final class KeyboardTextsTable {
/* keyspec_south_slavic_row3_8 */ "\u0452",
};
+ /* Locale sr_ZZ: Serbian (ZZ) */
+ private static final String[] TEXTS_sr_ZZ = {
+ /* morekeys_a */ null,
+ /* morekeys_o */ null,
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ /* morekeys_e */ "\u00E8",
+ /* morekeys_u */ null,
+ /* keylabel_to_alpha */ null,
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ /* morekeys_i */ "\u00EC",
+ /* morekeys_n */ null,
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ /* morekeys_c */ "\u010D,\u0107,%",
+ /* double_quotes */ null,
+ // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+ /* morekeys_s */ "\u0161,%",
+ /* single_quotes ~ */
+ null, null, null,
+ /* ~ morekeys_y */
+ // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+ /* morekeys_z */ "\u017E,%",
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ /* morekeys_d */ "\u0111,%",
+ /* morekeys_t ~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null,
+ /* ~ morekeys_symbols_percent */
+ /* label_go_key */ "Idi",
+ /* label_send_key */ "\u0160alji",
+ /* label_next_key */ "Sled",
+ /* label_done_key */ "Gotov",
+ /* label_search_key */ "Tra\u017Ei",
+ /* label_previous_key */ "Preth",
+ /* label_pause_key */ "Pauza",
+ /* label_wait_key */ "\u010Cekaj",
+ };
+
/* Locale sv: Swedish */
private static final String[] TEXTS_sv = {
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING
+ // U+00E6: "æ" LATIN SMALL LETTER AE
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
// U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
// U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
// U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
- /* morekeys_a */ "\u00E1,\u00E0,\u00E2,\u0105,\u00E3",
+ /* morekeys_a */ "\u00E4,\u00E5,\u00E6,\u00E1,\u00E0,\u00E2,\u0105,\u00E3",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
// U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
// U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
- /* morekeys_o */ "\u00F3,\u00F2,\u00F4,\u00F5,\u014D",
+ /* morekeys_o */ "\u00F6,\u00F8,\u0153,\u00F3,\u00F2,\u00F4,\u00F5,\u014D",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119",
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -3312,43 +3425,37 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FC,\u00FA,\u00F9,\u00FB,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
/* morekeys_i */ "\u00ED,\u00EC,\u00EE,\u00EF",
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ /* morekeys_n */ "\u0144,\u00F1,\u0148",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
/* double_quotes */ null,
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
- /* morekeys_n */ "\u0144,\u00F1,\u0148",
- /* single_quotes */ null,
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
/* morekeys_s */ "\u015B,\u0161,\u015F,\u00DF",
+ /* single_quotes */ null,
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* morekeys_y */ "\u00FD,\u00FF",
- // U+00F0: "ð" LATIN SMALL LETTER ETH
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- /* morekeys_d */ "\u00F0,\u010F",
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
/* morekeys_z */ "\u017A,\u017E,\u017C",
+ // U+00F0: "ð" LATIN SMALL LETTER ETH
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ /* morekeys_d */ "\u00F0,\u010F",
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
// U+00FE: "þ" LATIN SMALL LETTER THORN
/* morekeys_t */ "\u0165,\u00FE",
@@ -3372,8 +3479,8 @@ public final class KeyboardTextsTable {
/* morekeys_nordic_row2_10 */ "\u00F8,\u0153",
/* keyspec_east_slavic_row1_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,
- /* ~ additional_morekeys_symbols_0 */
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~ morekeys_tablet_period */
// U+00E6: "æ" LATIN SMALL LETTER AE
/* morekeys_nordic_row2_11 */ "\u00E6",
};
@@ -3399,6 +3506,12 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
/* morekeys_o */ "\u00F4,\u00F6,\u00F2,\u00F3,\u0153,\u00F8,\u014D,\u00F5",
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113",
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -3406,28 +3519,21 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113",
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
/* morekeys_i */ "\u00EE,\u00EF,\u00ED,\u012B,\u00EC",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u00F1",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
/* morekeys_c */ "\u00E7",
/* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* morekeys_n */ "\u00F1",
- /* single_quotes */ null,
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
/* morekeys_s */ "\u00DF",
- /* keyspec_currency ~ */
- null, null, null, null, null, null,
+ /* single_quotes ~ */
+ null, null, null, null, null, null, null,
/* ~ morekeys_l */
/* morekeys_g */ "g\'",
};
@@ -3435,16 +3541,16 @@ public final class KeyboardTextsTable {
/* Locale ta_IN: Tamil (India) */
private static final String[] TEXTS_ta_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0BA4: "த" TAMIL LETTER TA
// U+0BAE/U+0BBF: "மி" TAMIL LETTER MA/TAMIL VOWEL SIGN I
// U+0BB4/U+0BCD: "ழ்" TAMIL LETTER LLLA/TAMIL SIGN VIRAMA
/* keylabel_to_alpha */ "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+0BF9: "௹" TAMIL RUPEE SIGN
/* keyspec_currency */ "\u0BF9",
};
@@ -3452,16 +3558,16 @@ public final class KeyboardTextsTable {
/* Locale ta_LK: Tamil (Sri Lanka) */
private static final String[] TEXTS_ta_LK = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0BA4: "த" TAMIL LETTER TA
// U+0BAE/U+0BBF: "மி" TAMIL LETTER MA/TAMIL VOWEL SIGN I
// U+0BB4/U+0BCD: "ழ்" TAMIL LETTER LLLA/TAMIL SIGN VIRAMA
/* keylabel_to_alpha */ "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+0DBB/U+0DD4: "රු" SINHALA LETTER RAYANNA/SINHALA VOWEL SIGN KETTI PAA-PILLA
/* keyspec_currency */ "\u0DBB\u0DD4",
};
@@ -3469,7 +3575,7 @@ public final class KeyboardTextsTable {
/* Locale ta_SG: Tamil (Singapore) */
private static final String[] TEXTS_ta_SG = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0BA4: "த" TAMIL LETTER TA
@@ -3481,16 +3587,16 @@ public final class KeyboardTextsTable {
/* Locale te_IN: Telugu (India) */
private static final String[] TEXTS_te_IN = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0C05: "అ" TELUGU LETTER A
// U+0C06: "ఆ" TELUGU LETTER AA
// U+0C07: "ఇ" TELUGU LETTER I
/* keylabel_to_alpha */ "\u0C05\u0C06\u0C07",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+20B9: "₹" INDIAN RUPEE SIGN
/* keyspec_currency */ "\u20B9",
};
@@ -3498,16 +3604,16 @@ public final class KeyboardTextsTable {
/* Locale th: Thai */
private static final String[] TEXTS_th = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0E01: "ก" THAI CHARACTER KO KAI
// U+0E02: "ข" THAI CHARACTER KHO KHAI
// U+0E04: "ค" THAI CHARACTER KHO KHWAI
/* keylabel_to_alpha */ "\u0E01\u0E02\u0E04",
- /* morekeys_e ~ */
- null, null, null, null, null, null, null,
- /* ~ morekeys_s */
+ /* morekeys_i ~ */
+ null, null, null, null, null, null,
+ /* ~ single_quotes */
// U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
/* keyspec_currency */ "\u0E3F",
};
@@ -3535,13 +3641,6 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA",
- // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
- // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
- // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
- // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
- // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
- /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
- /* keylabel_to_alpha */ null,
// U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
// U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
// U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
@@ -3550,6 +3649,13 @@ public final class KeyboardTextsTable {
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
// U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
/* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B",
+ /* keylabel_to_alpha */ null,
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
@@ -3557,20 +3663,21 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ /* morekeys_n */ "\u00F1,\u0144",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
- /* morekeys_n */ "\u00F1,\u0144",
};
/* Locale tr: Turkish */
private static final String[] TEXTS_tr = {
// U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
- /* morekeys_a */ "\u00E2",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ /* morekeys_a */ "\u00E2,\u00E4,\u00E1",
// U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
// U+0153: "œ" LATIN SMALL LIGATURE OE
@@ -3580,6 +3687,9 @@ public final class KeyboardTextsTable {
// U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
/* morekeys_o */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D",
+ // U+0259: "ə" LATIN SMALL LETTER SCHWA
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ /* morekeys_e */ "\u0259,\u00E9",
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
@@ -3587,7 +3697,6 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
/* keylabel_to_alpha */ null,
- /* morekeys_e */ null,
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
@@ -3596,20 +3705,27 @@ public final class KeyboardTextsTable {
// U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
/* morekeys_i */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u0148,\u00F1",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
// U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
// U+010D: "č" LATIN SMALL LETTER C WITH CARON
/* morekeys_c */ "\u00E7,\u0107,\u010D",
- /* double_quotes ~ */
- null, null, null,
- /* ~ single_quotes */
+ /* double_quotes */ null,
// U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
/* morekeys_s */ "\u015F,\u00DF,\u015B,\u0161",
- /* keyspec_currency ~ */
- null, null, null, null, null, null,
+ /* single_quotes */ null,
+ /* keyspec_currency */ null,
+ // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+ /* morekeys_y */ "\u00FD",
+ // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+ /* morekeys_z */ "\u017E",
+ /* morekeys_d ~ */
+ null, null, null,
/* ~ morekeys_l */
// U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
/* morekeys_g */ "\u011F",
@@ -3618,20 +3734,19 @@ public final class KeyboardTextsTable {
/* Locale uk: Ukrainian */
private static final String[] TEXTS_uk = {
/* morekeys_a ~ */
- null, null, null,
+ null, null, null, null,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
/* keylabel_to_alpha */ "\u0410\u0411\u0412",
- /* morekeys_e ~ */
+ /* morekeys_i ~ */
null, null, null,
/* ~ morekeys_c */
/* double_quotes */ "!text/double_9qm_lqm",
- /* morekeys_n */ null,
- /* single_quotes */ "!text/single_9qm_lqm",
/* morekeys_s */ null,
+ /* single_quotes */ "!text/single_9qm_lqm",
// U+20B4: "₴" HRYVNIA SIGN
/* keyspec_currency */ "\u20B4",
/* morekeys_y ~ */
@@ -3651,7 +3766,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null,
/* ~ morekeys_w */
// U+0457: "ї" CYRILLIC SMALL LETTER YI
/* morekeys_east_slavic_row2_2 */ "\u0457",
@@ -3661,6 +3776,66 @@ public final class KeyboardTextsTable {
/* morekeys_cyrillic_ghe */ "\u0491",
};
+ /* Locale uz_UZ: Uzbek (Uzbekistan) */
+ private static final String[] TEXTS_uz_UZ = {
+ // This is the same as Turkish
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ /* morekeys_a */ "\u00E2,\u00E4,\u00E1",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+ /* morekeys_o */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D",
+ // U+0259: "ə" LATIN SMALL LETTER SCHWA
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ /* morekeys_e */ "\u0259,\u00E9",
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
+ /* keylabel_to_alpha */ null,
+ // U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+ /* morekeys_i */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u0148,\u00F1",
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ /* morekeys_c */ "\u00E7,\u0107,\u010D",
+ /* double_quotes */ null,
+ // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+ // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+ // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+ // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+ /* morekeys_s */ "\u015F,\u00DF,\u015B,\u0161",
+ /* single_quotes */ null,
+ /* keyspec_currency */ null,
+ // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+ /* morekeys_y */ "\u00FD",
+ // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+ /* morekeys_z */ "\u017E",
+ /* morekeys_d ~ */
+ null, null, null,
+ /* ~ morekeys_l */
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ /* morekeys_g */ "\u011F",
+ };
+
/* Locale vi: Vietnamese */
private static final String[] TEXTS_vi = {
// U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
@@ -3699,6 +3874,18 @@ public final class KeyboardTextsTable {
// U+1EE1: "ỡ" LATIN SMALL LETTER O WITH HORN AND TILDE
// U+1EE3: "ợ" LATIN SMALL LETTER O WITH HORN AND DOT BELOW
/* morekeys_o */ "\u00F2,\u00F3,\u1ECF,\u00F5,\u1ECD,\u00F4,\u1ED3,\u1ED1,\u1ED5,\u1ED7,\u1ED9,\u01A1,\u1EDD,\u1EDB,\u1EDF,\u1EE1,\u1EE3",
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+1EBB: "ẻ" LATIN SMALL LETTER E WITH HOOK ABOVE
+ // U+1EBD: "ẽ" LATIN SMALL LETTER E WITH TILDE
+ // U+1EB9: "ẹ" LATIN SMALL LETTER E WITH DOT BELOW
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+1EC1: "ề" LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE
+ // U+1EBF: "ế" LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE
+ // U+1EC3: "ể" LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+ // U+1EC5: "ễ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE
+ // U+1EC7: "ệ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+ /* morekeys_e */ "\u00E8,\u00E9,\u1EBB,\u1EBD,\u1EB9,\u00EA,\u1EC1,\u1EBF,\u1EC3,\u1EC5,\u1EC7",
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+1EE7: "ủ" LATIN SMALL LETTER U WITH HOOK ABOVE
@@ -3712,27 +3899,15 @@ public final class KeyboardTextsTable {
// U+1EF1: "ự" LATIN SMALL LETTER U WITH HORN AND DOT BELOW
/* morekeys_u */ "\u00F9,\u00FA,\u1EE7,\u0169,\u1EE5,\u01B0,\u1EEB,\u1EE9,\u1EED,\u1EEF,\u1EF1",
/* keylabel_to_alpha */ null,
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+1EBB: "ẻ" LATIN SMALL LETTER E WITH HOOK ABOVE
- // U+1EBD: "ẽ" LATIN SMALL LETTER E WITH TILDE
- // U+1EB9: "ẹ" LATIN SMALL LETTER E WITH DOT BELOW
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+1EC1: "ề" LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE
- // U+1EBF: "ế" LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE
- // U+1EC3: "ể" LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
- // U+1EC5: "ễ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE
- // U+1EC7: "ệ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW
- /* morekeys_e */ "\u00E8,\u00E9,\u1EBB,\u1EBD,\u1EB9,\u00EA,\u1EC1,\u1EBF,\u1EC3,\u1EC5,\u1EC7",
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+1EC9: "ỉ" LATIN SMALL LETTER I WITH HOOK ABOVE
// U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE
// U+1ECB: "ị" LATIN SMALL LETTER I WITH DOT BELOW
/* morekeys_i */ "\u00EC,\u00ED,\u1EC9,\u0129,\u1ECB",
- /* morekeys_c ~ */
+ /* morekeys_n ~ */
null, null, null, null, null,
- /* ~ morekeys_s */
+ /* ~ single_quotes */
// U+20AB: "₫" DONG SIGN
/* keyspec_currency */ "\u20AB",
// U+1EF3: "ỳ" LATIN SMALL LETTER Y WITH GRAVE
@@ -3741,6 +3916,7 @@ public final class KeyboardTextsTable {
// U+1EF9: "ỹ" LATIN SMALL LETTER Y WITH TILDE
// U+1EF5: "ỵ" LATIN SMALL LETTER Y WITH DOT BELOW
/* morekeys_y */ "\u1EF3,\u00FD,\u1EF7,\u1EF9,\u1EF5",
+ /* morekeys_z */ null,
// U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
/* morekeys_d */ "\u0111",
};
@@ -3766,6 +3942,12 @@ public final class KeyboardTextsTable {
// U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
// U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
/* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u0153,\u00F8,\u014D,\u00F5",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113",
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
// U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
@@ -3773,24 +3955,17 @@ public final class KeyboardTextsTable {
// U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
/* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
/* keylabel_to_alpha */ null,
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113",
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
// U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
// U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
/* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u012B,\u00EC",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* morekeys_n */ "\u00F1",
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
/* morekeys_c */ "\u00E7",
/* double_quotes */ null,
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* morekeys_n */ "\u00F1",
- /* single_quotes */ null,
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
/* morekeys_s */ "\u00DF",
};
@@ -3821,6 +3996,16 @@ public final class KeyboardTextsTable {
// U+0153: "œ" LATIN SMALL LIGATURE OE
// U+00BA: "º" MASCULINE ORDINAL INDICATOR
/* morekeys_o */ "\u00F2,\u00F3,\u00F4,\u00F5,\u00F6,\u00F8,\u014D,\u014F,\u0151,\u0153,\u00BA",
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ // U+0115: "ĕ" LATIN SMALL LETTER E WITH BREVE
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113,\u0115,\u0117,\u0119,\u011B",
// U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
// U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
// U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
@@ -3833,16 +4018,6 @@ public final class KeyboardTextsTable {
// U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
/* morekeys_u */ "\u00F9,\u00FA,\u00FB,\u00FC,\u0169,\u016B,\u016D,\u016F,\u0171,\u0173",
/* keylabel_to_alpha */ null,
- // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
- // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
- // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
- // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
- // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
- // U+0115: "ĕ" LATIN SMALL LETTER E WITH BREVE
- // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
- // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
- /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113,\u0115,\u0117,\u0119,\u011B",
// U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
// U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
// U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
@@ -3854,13 +4029,6 @@ public final class KeyboardTextsTable {
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_i */ "\u00EC,\u00ED,\u00EE,\u00EF,\u0129,\u012B,\u012D,\u012F,\u0131,\u0133",
- // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
- // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
- // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE
- // U+010D: "č" LATIN SMALL LETTER C WITH CARON
- /* morekeys_c */ "\u00E7,\u0107,\u0109,\u010B,\u010D",
- /* double_quotes */ null,
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
// U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
// U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
@@ -3868,7 +4036,13 @@ public final class KeyboardTextsTable {
// U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
// U+014B: "ŋ" LATIN SMALL LETTER ENG
/* morekeys_n */ "\u00F1,\u0144,\u0146,\u0148,\u0149,\u014B",
- /* single_quotes */ null,
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
+ // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ /* morekeys_c */ "\u00E7,\u0107,\u0109,\u010B,\u010D",
+ /* double_quotes */ null,
// U+00DF: "ß" LATIN SMALL LETTER SHARP S
// U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
// U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
@@ -3876,20 +4050,21 @@ public final class KeyboardTextsTable {
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
// U+017F: "ſ" LATIN SMALL LETTER LONG S
/* morekeys_s */ "\u00DF,\u015B,\u015D,\u015F,\u0161,\u017F",
+ /* single_quotes */ null,
/* keyspec_currency */ null,
// U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
// U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
// U+0133: "ij" LATIN SMALL LIGATURE IJ
/* morekeys_y */ "\u00FD,\u0177,\u00FF,\u0133",
- // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
- // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
- // U+00F0: "ð" LATIN SMALL LETTER ETH
- /* morekeys_d */ "\u010F,\u0111,\u00F0",
// U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
// U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
// U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
/* morekeys_z */ "\u017A,\u017C,\u017E",
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ // U+00F0: "ð" LATIN SMALL LETTER ETH
+ /* morekeys_d */ "\u010F,\u0111,\u00F0",
// U+00FE: "þ" LATIN SMALL LETTER THORN
// U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
// U+0165: "ť" LATIN SMALL LETTER T WITH CARON
@@ -3920,6 +4095,7 @@ public final class KeyboardTextsTable {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null,
/* ~ morekeys_question */
// U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX
/* morekeys_h */ "\u0125",
@@ -3927,7 +4103,8 @@ public final class KeyboardTextsTable {
/* morekeys_w */ "\u0175",
/* morekeys_east_slavic_row2_2 ~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null,
/* ~ morekeys_v */
// U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
/* morekeys_j */ "\u0135",
@@ -3935,72 +4112,75 @@ public final class KeyboardTextsTable {
private static final Object[] LOCALES_AND_TEXTS = {
// "locale", TEXT_ARRAY, /* numberOfNonNullText/lengthOf_TEXT_ARRAY localeName */
- "DEFAULT", TEXTS_DEFAULT, /* 168/168 DEFAULT */
+ "DEFAULT", TEXTS_DEFAULT, /* 176/176 DEFAULT */
"af" , TEXTS_af, /* 7/ 13 Afrikaans */
"ar" , TEXTS_ar, /* 55/110 Arabic */
- "az_AZ" , TEXTS_az_AZ, /* 8/ 18 Azerbaijani (Azerbaijan) */
+ "az_AZ" , TEXTS_az_AZ, /* 11/ 18 Azerbaijani (Azerbaijan) */
"be_BY" , TEXTS_be_BY, /* 9/ 32 Belarusian (Belarus) */
- "bg" , TEXTS_bg, /* 2/ 8 Bulgarian */
+ "bg" , TEXTS_bg, /* 2/ 9 Bulgarian */
+ "bn_BD" , TEXTS_bn_BD, /* 2/ 12 Bengali (Bangladesh) */
"bn_IN" , TEXTS_bn_IN, /* 2/ 12 Bengali (India) */
- "ca" , TEXTS_ca, /* 11/ 96 Catalan */
+ "ca" , TEXTS_ca, /* 11/ 99 Catalan */
"cs" , TEXTS_cs, /* 17/ 21 Czech */
- "da" , TEXTS_da, /* 19/ 54 Danish */
- "de" , TEXTS_de, /* 16/ 62 German */
- "el" , TEXTS_el, /* 1/ 4 Greek */
- "en" , TEXTS_en, /* 8/ 11 English */
- "eo" , TEXTS_eo, /* 26/118 Esperanto */
- "es" , TEXTS_es, /* 8/ 55 Spanish */
+ "da" , TEXTS_da, /* 19/ 55 Danish */
+ "de" , TEXTS_de, /* 16/ 66 German */
+ "el" , TEXTS_el, /* 1/ 5 Greek */
+ "en" , TEXTS_en, /* 8/ 10 English */
+ "eo" , TEXTS_eo, /* 26/126 Esperanto */
+ "es" , TEXTS_es, /* 8/ 56 Spanish */
"et_EE" , TEXTS_et_EE, /* 22/ 27 Estonian (Estonia) */
- "eu_ES" , TEXTS_eu_ES, /* 7/ 9 Basque (Spain) */
- "fa" , TEXTS_fa, /* 58/125 Persian */
- "fi" , TEXTS_fi, /* 10/ 54 Finnish */
- "fr" , TEXTS_fr, /* 13/ 62 French */
- "gl_ES" , TEXTS_gl_ES, /* 7/ 9 Gallegan (Spain) */
- "hi" , TEXTS_hi, /* 23/ 53 Hindi */
+ "eu_ES" , TEXTS_eu_ES, /* 7/ 8 Basque (Spain) */
+ "fa" , TEXTS_fa, /* 58/133 Persian */
+ "fi" , TEXTS_fi, /* 10/ 55 Finnish */
+ "fr" , TEXTS_fr, /* 13/ 66 French */
+ "gl_ES" , TEXTS_gl_ES, /* 7/ 8 Gallegan (Spain) */
+ "hi" , TEXTS_hi, /* 27/ 60 Hindi */
+ "hi_ZZ" , TEXTS_hi_ZZ, /* 9/118 Hindi (ZZ) */
"hr" , TEXTS_hr, /* 9/ 20 Croatian */
"hu" , TEXTS_hu, /* 9/ 20 Hungarian */
- "hy_AM" , TEXTS_hy_AM, /* 9/126 Armenian (Armenia) */
+ "hy_AM" , TEXTS_hy_AM, /* 9/134 Armenian (Armenia) */
"is" , TEXTS_is, /* 10/ 16 Icelandic */
- "it" , TEXTS_it, /* 11/ 62 Italian */
- "iw" , TEXTS_iw, /* 20/123 Hebrew */
- "ka_GE" , TEXTS_ka_GE, /* 3/ 10 Georgian (Georgia) */
- "kk" , TEXTS_kk, /* 15/121 Kazakh */
- "km_KH" , TEXTS_km_KH, /* 2/122 Khmer (Cambodia) */
+ "it" , TEXTS_it, /* 11/ 66 Italian */
+ "iw" , TEXTS_iw, /* 20/131 Hebrew */
+ "ka_GE" , TEXTS_ka_GE, /* 3/ 11 Georgian (Georgia) */
+ "kk" , TEXTS_kk, /* 15/129 Kazakh */
+ "km_KH" , TEXTS_km_KH, /* 2/130 Khmer (Cambodia) */
"kn_IN" , TEXTS_kn_IN, /* 2/ 12 Kannada (India) */
- "ky" , TEXTS_ky, /* 10/ 89 Kirghiz */
+ "ky" , TEXTS_ky, /* 10/ 92 Kirghiz */
"lo_LA" , TEXTS_lo_LA, /* 2/ 12 Lao (Laos) */
"lt" , TEXTS_lt, /* 18/ 22 Lithuanian */
"lv" , TEXTS_lv, /* 18/ 22 Latvian */
- "mk" , TEXTS_mk, /* 9/ 94 Macedonian */
+ "mk" , TEXTS_mk, /* 9/ 97 Macedonian */
"ml_IN" , TEXTS_ml_IN, /* 2/ 12 Malayalam (India) */
"mn_MN" , TEXTS_mn_MN, /* 2/ 12 Mongolian (Mongolia) */
"mr_IN" , TEXTS_mr_IN, /* 23/ 53 Marathi (India) */
- "my_MM" , TEXTS_my_MM, /* 8/104 Burmese (Myanmar) */
- "nb" , TEXTS_nb, /* 11/ 54 Norwegian Bokmål */
- "ne_NP" , TEXTS_ne_NP, /* 23/ 53 Nepali (Nepal) */
+ "nb" , TEXTS_nb, /* 11/ 55 Norwegian Bokmål */
+ "ne_NP" , TEXTS_ne_NP, /* 27/ 60 Nepali (Nepal) */
"nl" , TEXTS_nl, /* 9/ 13 Dutch */
"pl" , TEXTS_pl, /* 10/ 17 Polish */
- "pt" , TEXTS_pt, /* 6/ 7 Portuguese */
+ "pt" , TEXTS_pt, /* 6/ 8 Portuguese */
"rm" , TEXTS_rm, /* 1/ 2 Raeto-Romance */
"ro" , TEXTS_ro, /* 6/ 16 Romanian */
"ru" , TEXTS_ru, /* 9/ 32 Russian */
"si_LK" , TEXTS_si_LK, /* 2/ 12 Sinhalese (Sri Lanka) */
"sk" , TEXTS_sk, /* 20/ 22 Slovak */
"sl" , TEXTS_sl, /* 8/ 20 Slovenian */
- "sr" , TEXTS_sr, /* 11/ 94 Serbian */
- "sv" , TEXTS_sv, /* 21/ 54 Swedish */
+ "sr" , TEXTS_sr, /* 11/ 97 Serbian */
+ "sr_ZZ" , TEXTS_sr_ZZ, /* 14/118 Serbian (ZZ) */
+ "sv" , TEXTS_sv, /* 21/ 55 Swedish */
"sw" , TEXTS_sw, /* 9/ 18 Swahili */
"ta_IN" , TEXTS_ta_IN, /* 2/ 12 Tamil (India) */
"ta_LK" , TEXTS_ta_LK, /* 2/ 12 Tamil (Sri Lanka) */
- "ta_SG" , TEXTS_ta_SG, /* 1/ 4 Tamil (Singapore) */
+ "ta_SG" , TEXTS_ta_SG, /* 1/ 5 Tamil (Singapore) */
"te_IN" , TEXTS_te_IN, /* 2/ 12 Telugu (India) */
"th" , TEXTS_th, /* 2/ 12 Thai */
- "tl" , TEXTS_tl, /* 7/ 9 Tagalog */
- "tr" , TEXTS_tr, /* 7/ 18 Turkish */
- "uk" , TEXTS_uk, /* 11/ 88 Ukrainian */
- "vi" , TEXTS_vi, /* 8/ 14 Vietnamese */
- "zu" , TEXTS_zu, /* 8/ 11 Zulu */
- "zz" , TEXTS_zz, /* 19/112 Alphabet */
+ "tl" , TEXTS_tl, /* 7/ 8 Tagalog */
+ "tr" , TEXTS_tr, /* 11/ 18 Turkish */
+ "uk" , TEXTS_uk, /* 11/ 91 Ukrainian */
+ "uz_UZ" , TEXTS_uz_UZ, /* 11/ 18 Uzbek (Uzbekistan) */
+ "vi" , TEXTS_vi, /* 8/ 15 Vietnamese */
+ "zu" , TEXTS_zu, /* 8/ 10 Zulu */
+ "zz" , TEXTS_zz, /* 19/120 Alphabet */
};
static {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
deleted file mode 100644
index 7743d4744..000000000
--- a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
+++ /dev/null
@@ -1,39 +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.keyboard.internal;
-
-import com.android.inputmethod.keyboard.Key;
-
-import java.util.HashMap;
-
-public final class KeysCache {
- private final HashMap<Key, Key> mMap = new HashMap<>();
-
- public void clear() {
- mMap.clear();
- }
-
- public Key get(final Key key) {
- final Key existingKey = mMap.get(key);
- if (existingKey != null) {
- // Reuse the existing element that equals to "key" without adding "key" to the map.
- return existingKey;
- }
- mMap.put(key, key);
- return key;
- }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java b/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java
deleted file mode 100644
index 6400a2440..000000000
--- a/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.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/MatrixUtils.java b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java
index c1f374964..d927cc362 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java
@@ -28,7 +28,8 @@ import java.util.Arrays;
*/
@UsedForTesting
public class MatrixUtils {
- private static final String TAG = MatrixUtils.class.getSimpleName();
+ static final String TAG = MatrixUtils.class.getSimpleName();
+
public static class MatrixOperationFailedException extends Exception {
private static final long serialVersionUID = 4384485606788583829L;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
index 625a0c283..0bd42fc13 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -17,17 +17,21 @@
package com.android.inputmethod.keyboard.internal;
import android.text.TextUtils;
+import android.util.SparseIntArray;
+import com.android.inputmethod.compat.CharacterCompat;
import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.define.DebugFlags;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.CollectionUtils;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.HashSet;
import java.util.Locale;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* The more key specification object. The more keys are an array of {@link MoreKeySpec}.
*
@@ -42,18 +46,22 @@ import java.util.Locale;
// TODO: Should extend the key specification object.
public final class MoreKeySpec {
public final int mCode;
+ @Nullable
public final String mLabel;
+ @Nullable
public final String mOutputText;
public final int mIconId;
- public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, final Locale locale) {
- if (TextUtils.isEmpty(moreKeySpec)) {
+ public MoreKeySpec(@Nonnull final String moreKeySpec, boolean needsToUpperCase,
+ @Nonnull final Locale locale) {
+ if (moreKeySpec.isEmpty()) {
throw new KeySpecParser.KeySpecParserError("Empty more key spec");
}
- mLabel = StringUtils.toUpperCaseOfStringForLocale(
- KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale);
- final int code = StringUtils.toUpperCaseOfCodeForLocale(
- KeySpecParser.getCode(moreKeySpec), needsToUpperCase, locale);
+ final String label = KeySpecParser.getLabel(moreKeySpec);
+ mLabel = needsToUpperCase ? StringUtils.toTitleCaseOfKeyLabel(label, locale) : label;
+ final int codeInSpec = KeySpecParser.getCode(moreKeySpec);
+ final int code = needsToUpperCase ? StringUtils.toTitleCaseOfKeyCode(codeInSpec, locale)
+ : codeInSpec;
if (code == Constants.CODE_UNSPECIFIED) {
// Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
// upper case representation ("SS").
@@ -61,14 +69,16 @@ public final class MoreKeySpec {
mOutputText = mLabel;
} else {
mCode = code;
- mOutputText = StringUtils.toUpperCaseOfStringForLocale(
- KeySpecParser.getOutputText(moreKeySpec), needsToUpperCase, locale);
+ final String outputText = KeySpecParser.getOutputText(moreKeySpec);
+ mOutputText = needsToUpperCase
+ ? StringUtils.toTitleCaseOfKeyLabel(outputText, locale) : outputText;
}
mIconId = KeySpecParser.getIconId(moreKeySpec);
}
+ @Nonnull
public Key buildKey(final int x, final int y, final int labelFlags,
- final KeyboardParams params) {
+ @Nonnull 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);
@@ -79,14 +89,18 @@ public final class MoreKeySpec {
int hashCode = 1;
hashCode = 31 + mCode;
hashCode = hashCode * 31 + mIconId;
- hashCode = hashCode * 31 + (mLabel == null ? 0 : mLabel.hashCode());
- hashCode = hashCode * 31 + (mOutputText == null ? 0 : mOutputText.hashCode());
+ final String label = mLabel;
+ hashCode = hashCode * 31 + (label == null ? 0 : label.hashCode());
+ final String outputText = mOutputText;
+ hashCode = hashCode * 31 + (outputText == null ? 0 : outputText.hashCode());
return hashCode;
}
@Override
public boolean equals(final Object o) {
- if (this == o) return true;
+ if (this == o) {
+ return true;
+ }
if (o instanceof MoreKeySpec) {
final MoreKeySpec other = (MoreKeySpec)o;
return mCode == other.mCode
@@ -105,12 +119,56 @@ public final class MoreKeySpec {
: Constants.printableCode(mCode));
if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
return output;
- } else {
- return label + "|" + output;
}
+ return label + "|" + output;
+ }
+
+ public static class LettersOnBaseLayout {
+ private final SparseIntArray mCodes = new SparseIntArray();
+ private final HashSet<String> mTexts = new HashSet<>();
+
+ public void addLetter(@Nonnull final Key key) {
+ final int code = key.getCode();
+ if (CharacterCompat.isAlphabetic(code)) {
+ mCodes.put(code, 0);
+ } else if (code == Constants.CODE_OUTPUT_TEXT) {
+ mTexts.add(key.getOutputText());
+ }
+ }
+
+ public boolean contains(@Nonnull final MoreKeySpec moreKey) {
+ final int code = moreKey.mCode;
+ if (CharacterCompat.isAlphabetic(code) && mCodes.indexOfKey(code) >= 0) {
+ return true;
+ } else if (code == Constants.CODE_OUTPUT_TEXT && mTexts.contains(moreKey.mOutputText)) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ @Nullable
+ public static MoreKeySpec[] removeRedundantMoreKeys(@Nullable final MoreKeySpec[] moreKeys,
+ @Nonnull final LettersOnBaseLayout lettersOnBaseLayout) {
+ if (moreKeys == null) {
+ return null;
+ }
+ final ArrayList<MoreKeySpec> filteredMoreKeys = new ArrayList<>();
+ for (final MoreKeySpec moreKey : moreKeys) {
+ if (!lettersOnBaseLayout.contains(moreKey)) {
+ filteredMoreKeys.add(moreKey);
+ }
+ }
+ final int size = filteredMoreKeys.size();
+ if (size == moreKeys.length) {
+ return moreKeys;
+ }
+ if (size == 0) {
+ return null;
+ }
+ return filteredMoreKeys.toArray(new MoreKeySpec[size]);
}
- private static final boolean DEBUG = DebugFlags.DEBUG_ENABLED;
// Constants for parsing.
private static final char COMMA = Constants.CODE_COMMA;
private static final char BACKSLASH = Constants.CODE_BACKSLASH;
@@ -128,7 +186,8 @@ public final class MoreKeySpec {
* @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) {
+ @Nullable
+ public static String[] splitKeySpecs(@Nullable final String text) {
if (TextUtils.isEmpty(text)) {
return null;
}
@@ -170,9 +229,11 @@ public final class MoreKeySpec {
return list.toArray(new String[list.size()]);
}
+ @Nonnull
private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private static String[] filterOutEmptyString(final String[] array) {
+ @Nonnull
+ private static String[] filterOutEmptyString(@Nullable final String[] array) {
if (array == null) {
return EMPTY_STRING_ARRAY;
}
@@ -193,8 +254,8 @@ public final class MoreKeySpec {
return out.toArray(new String[out.size()]);
}
- public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs,
- final String[] additionalMoreKeySpecs) {
+ public static String[] insertAdditionalMoreKeys(@Nullable final String[] moreKeySpecs,
+ @Nullable final String[] additionalMoreKeySpecs) {
final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
final int moreKeysCount = moreKeys.length;
@@ -228,11 +289,6 @@ public final class 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]);
@@ -240,11 +296,6 @@ public final class MoreKeySpec {
} 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]);
@@ -259,7 +310,7 @@ public final class MoreKeySpec {
}
}
- public static int getIntValue(final String[] moreKeys, final String key,
+ public static int getIntValue(@Nullable final String[] moreKeys, final String key,
final int defaultValue) {
if (moreKeys == null) {
return defaultValue;
@@ -286,7 +337,7 @@ public final class MoreKeySpec {
return value;
}
- public static boolean getBooleanValue(final String[] moreKeys, final String key) {
+ public static boolean getBooleanValue(@Nullable final String[] moreKeys, final String key) {
if (moreKeys == null) {
return false;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
index 3a9aa81a3..8a375c620 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
@@ -22,7 +22,7 @@ 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.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.common.CoordinateUtils;
public final class NonDistinctMultitouchHelper {
private static final String TAG = NonDistinctMultitouchHelper.class.getSimpleName();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 8e89e61ea..556d74f4b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -95,7 +95,7 @@ public final class PointerTrackerQueue {
public void releaseAllPointersOlderThan(final Element pointer, final long eventTime) {
synchronized (mExpandableArrayOfActivePointers) {
if (DEBUG) {
- Log.d(TAG, "releaseAllPoniterOlderThan: " + pointer + " " + this);
+ Log.d(TAG, "releaseAllPointerOlderThan: " + pointer + " " + this);
}
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
final int arraySize = mArraySize;
@@ -144,9 +144,9 @@ public final class PointerTrackerQueue {
synchronized (mExpandableArrayOfActivePointers) {
if (DEBUG) {
if (pointer == null) {
- Log.d(TAG, "releaseAllPoniters: " + this);
+ Log.d(TAG, "releaseAllPointers: " + this);
} else {
- Log.d(TAG, "releaseAllPoniterExcept: " + pointer + " " + this);
+ Log.d(TAG, "releaseAllPointerExcept: " + pointer + " " + this);
}
}
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java
index ef4c74d61..73a6f9516 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java
@@ -23,7 +23,7 @@ import android.graphics.Path;
import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.common.CoordinateUtils;
/**
* Draw rubber band preview graphics during sliding key input.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java
index ec7b9b024..91f3558eb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java
@@ -22,31 +22,27 @@ 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.common.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);
- }
+import javax.annotation.Nonnull;
+public final class TimerHandler extends LeakGuardHandlerWrapper<DrawingProxy>
+ 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_LONGPRESS_SHIFT_KEY = 3;
private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 4;
private static final int MSG_UPDATE_BATCH_INPUT = 5;
+ private static final int MSG_DISMISS_KEY_PREVIEW = 6;
+ private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 7;
private final int mIgnoreAltCodeKeyTimeout;
private final int mGestureRecognitionUpdateTime;
- public TimerHandler(final Callbacks ownerInstance, final int ignoreAltCodeKeyTimeout,
- final int gestureRecognitionUpdateTime) {
+ public TimerHandler(@Nonnull final DrawingProxy ownerInstance,
+ final int ignoreAltCodeKeyTimeout, final int gestureRecognitionUpdateTime) {
super(ownerInstance);
mIgnoreAltCodeKeyTimeout = ignoreAltCodeKeyTimeout;
mGestureRecognitionUpdateTime = gestureRecognitionUpdateTime;
@@ -54,32 +50,40 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> imple
@Override
public void handleMessage(final Message msg) {
- final Callbacks callbacks = getOwnerInstance();
- if (callbacks == null) {
+ final DrawingProxy drawingProxy = getOwnerInstance();
+ if (drawingProxy == null) {
return;
}
- final PointerTracker tracker = (PointerTracker) msg.obj;
switch (msg.what) {
case MSG_TYPING_STATE_EXPIRED:
- callbacks.startWhileTypingFadeinAnimation();
+ drawingProxy.startWhileTypingAnimation(DrawingProxy.FADE_IN);
break;
case MSG_REPEAT_KEY:
- tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
+ final PointerTracker tracker1 = (PointerTracker) msg.obj;
+ tracker1.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
break;
case MSG_LONGPRESS_KEY:
case MSG_LONGPRESS_SHIFT_KEY:
cancelLongPressTimers();
- callbacks.onLongPress(tracker);
+ final PointerTracker tracker2 = (PointerTracker) msg.obj;
+ tracker2.onLongPressed();
break;
case MSG_UPDATE_BATCH_INPUT:
- tracker.updateBatchInputByTimer(SystemClock.uptimeMillis());
- startUpdateBatchInputTimer(tracker);
+ final PointerTracker tracker3 = (PointerTracker) msg.obj;
+ tracker3.updateBatchInputByTimer(SystemClock.uptimeMillis());
+ startUpdateBatchInputTimer(tracker3);
+ break;
+ case MSG_DISMISS_KEY_PREVIEW:
+ drawingProxy.onKeyReleased((Key) msg.obj, false /* withAnimation */);
+ break;
+ case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
+ drawingProxy.dismissGestureFloatingPreviewTextWithoutDelay();
break;
}
}
@Override
- public void startKeyRepeatTimerOf(final PointerTracker tracker, final int repeatCount,
+ public void startKeyRepeatTimerOf(@Nonnull final PointerTracker tracker, final int repeatCount,
final int delay) {
final Key key = tracker.getKey();
if (key == null || delay == 0) {
@@ -103,7 +107,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> imple
}
@Override
- public void startLongPressTimerOf(final PointerTracker tracker, final int delay) {
+ public void startLongPressTimerOf(@Nonnull final PointerTracker tracker, final int delay) {
final Key key = tracker.getKey();
if (key == null) {
return;
@@ -116,13 +120,13 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> imple
}
@Override
- public void cancelLongPressTimerOf(final PointerTracker tracker) {
+ public void cancelLongPressTimersOf(@Nonnull final PointerTracker tracker) {
removeMessages(MSG_LONGPRESS_KEY, tracker);
removeMessages(MSG_LONGPRESS_SHIFT_KEY, tracker);
}
@Override
- public void cancelLongPressShiftKeyTimers() {
+ public void cancelLongPressShiftKeyTimer() {
removeMessages(MSG_LONGPRESS_SHIFT_KEY);
}
@@ -132,15 +136,15 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> imple
}
@Override
- public void startTypingStateTimer(final Key typedKey) {
+ public void startTypingStateTimer(@Nonnull 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) {
+ final DrawingProxy drawingProxy = getOwnerInstance();
+ if (drawingProxy == null) {
return;
}
@@ -148,7 +152,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> imple
final int typedCode = typedKey.getCode();
if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) {
if (isTyping) {
- callbacks.startWhileTypingFadeinAnimation();
+ drawingProxy.startWhileTypingAnimation(DrawingProxy.FADE_IN);
}
return;
}
@@ -158,7 +162,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> imple
if (isTyping) {
return;
}
- callbacks.startWhileTypingFadeoutAnimation();
+ drawingProxy.startWhileTypingAnimation(DrawingProxy.FADE_OUT);
}
@Override
@@ -183,9 +187,9 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> imple
}
@Override
- public void cancelKeyTimersOf(final PointerTracker tracker) {
+ public void cancelKeyTimersOf(@Nonnull final PointerTracker tracker) {
cancelKeyRepeatTimerOf(tracker);
- cancelLongPressTimerOf(tracker);
+ cancelLongPressTimersOf(tracker);
}
public void cancelAllKeyTimers() {
@@ -194,7 +198,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> imple
}
@Override
- public void startUpdateBatchInputTimer(final PointerTracker tracker) {
+ public void startUpdateBatchInputTimer(@Nonnull final PointerTracker tracker) {
if (mGestureRecognitionUpdateTime <= 0) {
return;
}
@@ -204,7 +208,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> imple
}
@Override
- public void cancelUpdateBatchInputTimer(final PointerTracker tracker) {
+ public void cancelUpdateBatchInputTimer(@Nonnull final PointerTracker tracker) {
removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
}
@@ -213,8 +217,18 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> imple
removeMessages(MSG_UPDATE_BATCH_INPUT);
}
+ public void postDismissKeyPreview(@Nonnull final Key key, final long delay) {
+ sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, key), delay);
+ }
+
+ public void postDismissGestureFloatingPreviewText(final long delay) {
+ sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
+ }
+
public void cancelAllMessages() {
cancelAllKeyTimers();
cancelAllUpdateBatchInputTimers();
+ removeMessages(MSG_DISMISS_KEY_PREVIEW);
+ removeMessages(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TimerProxy.java b/java/src/com/android/inputmethod/keyboard/internal/TimerProxy.java
new file mode 100644
index 000000000..0ce3de8d9
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/TimerProxy.java
@@ -0,0 +1,133 @@
+/*
+ * 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.keyboard.Key;
+import com.android.inputmethod.keyboard.PointerTracker;
+
+import javax.annotation.Nonnull;
+
+public interface TimerProxy {
+ /**
+ * Start a timer to detect if a user is typing keys.
+ * @param typedKey the key that is typed.
+ */
+ public void startTypingStateTimer(@Nonnull Key typedKey);
+
+ /**
+ * Check if a user is key typing.
+ * @return true if a user is in typing.
+ */
+ public boolean isTypingState();
+
+ /**
+ * Start a timer to simulate repeated key presses while a user keep pressing a key.
+ * @param tracker the {@link PointerTracker} that points the key to be repeated.
+ * @param repeatCount the number of times that the key is repeating. Starting from 1.
+ * @param delay the interval delay to the next key repeat, in millisecond.
+ */
+ public void startKeyRepeatTimerOf(@Nonnull PointerTracker tracker, int repeatCount, int delay);
+
+ /**
+ * Start a timer to detect a long pressed key.
+ * If a key pointed by <code>tracker</code> is a shift key, start another timer to detect
+ * long pressed shift key.
+ * @param tracker the {@link PointerTracker} that starts long pressing.
+ * @param delay the delay to fire the long press timer, in millisecond.
+ */
+ public void startLongPressTimerOf(@Nonnull PointerTracker tracker, int delay);
+
+ /**
+ * Cancel timers for detecting a long pressed key and a long press shift key.
+ * @param tracker cancel long press timers of this {@link PointerTracker}.
+ */
+ public void cancelLongPressTimersOf(@Nonnull PointerTracker tracker);
+
+ /**
+ * Cancel a timer for detecting a long pressed shift key.
+ */
+ public void cancelLongPressShiftKeyTimer();
+
+ /**
+ * Cancel timers for detecting repeated key press, long pressed key, and long pressed shift key.
+ * @param tracker the {@link PointerTracker} that starts timers to be canceled.
+ */
+ public void cancelKeyTimersOf(@Nonnull PointerTracker tracker);
+
+ /**
+ * Start a timer to detect double tapped shift key.
+ */
+ public void startDoubleTapShiftKeyTimer();
+
+ /**
+ * Cancel a timer of detecting double tapped shift key.
+ */
+ public void cancelDoubleTapShiftKeyTimer();
+
+ /**
+ * Check if a timer of detecting double tapped shift key is running.
+ * @return true if detecting double tapped shift key is on going.
+ */
+ public boolean isInDoubleTapShiftKeyTimeout();
+
+ /**
+ * Start a timer to fire updating batch input while <code>tracker</code> is on hold.
+ * @param tracker the {@link PointerTracker} that stops moving.
+ */
+ public void startUpdateBatchInputTimer(@Nonnull PointerTracker tracker);
+
+ /**
+ * Cancel a timer of firing updating batch input.
+ * @param tracker the {@link PointerTracker} that resumes moving or ends gesture input.
+ */
+ public void cancelUpdateBatchInputTimer(@Nonnull PointerTracker tracker);
+
+ /**
+ * Cancel all timers of firing updating batch input.
+ */
+ public void cancelAllUpdateBatchInputTimers();
+
+ public static class Adapter implements TimerProxy {
+ @Override
+ public void startTypingStateTimer(@Nonnull Key typedKey) {}
+ @Override
+ public boolean isTypingState() { return false; }
+ @Override
+ public void startKeyRepeatTimerOf(@Nonnull PointerTracker tracker, int repeatCount,
+ int delay) {}
+ @Override
+ public void startLongPressTimerOf(@Nonnull PointerTracker tracker, int delay) {}
+ @Override
+ public void cancelLongPressTimersOf(@Nonnull PointerTracker tracker) {}
+ @Override
+ public void cancelLongPressShiftKeyTimer() {}
+ @Override
+ public void cancelKeyTimersOf(@Nonnull PointerTracker tracker) {}
+ @Override
+ public void startDoubleTapShiftKeyTimer() {}
+ @Override
+ public void cancelDoubleTapShiftKeyTimer() {}
+ @Override
+ public boolean isInDoubleTapShiftKeyTimeout() { return false; }
+ @Override
+ public void startUpdateBatchInputTimer(@Nonnull PointerTracker tracker) {}
+ @Override
+ public void cancelUpdateBatchInputTimer(@Nonnull PointerTracker tracker) {}
+ @Override
+ public void cancelAllUpdateBatchInputTimers() {}
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
index fef97cc11..d8f0114e1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
@@ -80,6 +80,7 @@ public final class TouchPositionCorrection {
return mRadii.length;
}
+ @SuppressWarnings({ "static-method", "unused" })
public float getX(final int row) {
return 0.0f;
// Touch position correction data for X coordinate is obsolete.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/UniqueKeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/UniqueKeysCache.java
new file mode 100644
index 000000000..5b329dce4
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/UniqueKeysCache.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.internal;
+
+import com.android.inputmethod.keyboard.Key;
+
+import java.util.HashMap;
+
+import javax.annotation.Nonnull;
+
+public abstract class UniqueKeysCache {
+ public abstract void setEnabled(boolean enabled);
+ public abstract void clear();
+ public abstract @Nonnull Key getUniqueKey(@Nonnull Key key);
+
+ @Nonnull
+ public static final UniqueKeysCache NO_CACHE = new UniqueKeysCache() {
+ @Override
+ public void setEnabled(boolean enabled) {}
+
+ @Override
+ public void clear() {}
+
+ @Override
+ public Key getUniqueKey(Key key) { return key; }
+ };
+
+ @Nonnull
+ public static UniqueKeysCache newInstance() {
+ return new UniqueKeysCacheImpl();
+ }
+
+ private static final class UniqueKeysCacheImpl extends UniqueKeysCache {
+ private final HashMap<Key, Key> mCache;
+
+ private boolean mEnabled;
+
+ UniqueKeysCacheImpl() {
+ mCache = new HashMap<>();
+ }
+
+ @Override
+ public void setEnabled(final boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ @Override
+ public void clear() {
+ mCache.clear();
+ }
+
+ @Override
+ public Key getUniqueKey(final Key key) {
+ if (!mEnabled) {
+ return key;
+ }
+ final Key existingKey = mCache.get(key);
+ if (existingKey != null) {
+ // Reuse the existing object that equals to "key" without adding "key" to
+ // the cache.
+ return existingKey;
+ }
+ mCache.put(key, key);
+ return key;
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
index fd6c24dfe..f8d02d6ea 100644
--- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -16,7 +16,7 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.latin.utils.FileUtils;
+import com.android.inputmethod.latin.common.FileUtils;
import java.io.File;
@@ -62,4 +62,9 @@ public final class AssetFileAddress {
public void deleteUnderlyingFile() {
FileUtils.deleteRecursively(new File(mFilename));
}
+
+ @Override
+ public String toString() {
+ return String.format("%s (offset=%d, length=%d)", mFilename, mOffset, mLength);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index eb8b34ccd..60d257362 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -22,6 +22,7 @@ import android.os.Vibrator;
import android.view.HapticFeedbackConstants;
import android.view.View;
+import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.settings.SettingsValues;
/**
diff --git a/java/src/com/android/inputmethod/latin/BackupAgent.java b/java/src/com/android/inputmethod/latin/BackupAgent.java
index 1f044618a..b2d92b30c 100644
--- a/java/src/com/android/inputmethod/latin/BackupAgent.java
+++ b/java/src/com/android/inputmethod/latin/BackupAgent.java
@@ -17,15 +17,41 @@
package com.android.inputmethod.latin;
import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupDataInput;
import android.app.backup.SharedPreferencesBackupHelper;
+import android.content.SharedPreferences;
+import android.os.ParcelFileDescriptor;
+
+import com.android.inputmethod.latin.settings.LocalSettingsConstants;
+
+import java.io.IOException;
/**
- * Backs up the Latin IME shared preferences.
+ * Backup/restore agent for LatinIME.
+ * Currently it backs up the default shared preferences.
*/
public final class BackupAgent extends BackupAgentHelper {
+ private static final String PREF_SUFFIX = "_preferences";
+
@Override
public void onCreate() {
addHelper("shared_pref", new SharedPreferencesBackupHelper(this,
- getPackageName() + "_preferences"));
+ getPackageName() + PREF_SUFFIX));
+ }
+
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+ throws IOException {
+ // Let the restore operation go through
+ super.onRestore(data, appVersionCode, newState);
+
+ // Remove the preferences that we don't want restored.
+ final SharedPreferences.Editor prefEditor = getSharedPreferences(
+ getPackageName() + PREF_SUFFIX, MODE_PRIVATE).edit();
+ for (final String key : LocalSettingsConstants.PREFS_TO_SKIP_RESTORING) {
+ prefEditor.remove(key);
+ }
+ // Flush the changes to disk.
+ prefEditor.commit();
}
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 693e1cdcc..9a3ac674e 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -21,8 +21,12 @@ 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.common.ComposedData;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.FileUtils;
+import com.android.inputmethod.latin.common.InputPointers;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.makedict.FormatSpec;
import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
@@ -30,10 +34,8 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.makedict.WordProperty;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-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 com.android.inputmethod.latin.utils.WordInputEventForPersonalization;
import java.io.File;
import java.util.ArrayList;
@@ -42,6 +44,8 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
+import javax.annotation.Nonnull;
+
/**
* Implements a static, compacted, binary dictionary of standard words.
*/
@@ -53,6 +57,9 @@ public final class BinaryDictionary extends Dictionary {
// Must be equal to CONFIDENCE_TO_AUTO_COMMIT in native/jni/src/defines.h
private static final int CONFIDENCE_TO_AUTO_COMMIT = 1000000;
+ public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
+ public static final int MAX_PREV_WORD_COUNT_FOR_N_GRAM = 3;
+
@UsedForTesting
public static final String UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT";
@UsedForTesting
@@ -67,9 +74,9 @@ public final class BinaryDictionary extends Dictionary {
// Format to get unigram flags from native side via getWordPropertyNative().
private static final int FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT = 5;
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;
+ private static final int FORMAT_WORD_PROPERTY_IS_POSSIBLY_OFFENSIVE_INDEX = 1;
+ private static final int FORMAT_WORD_PROPERTY_HAS_NGRAMS_INDEX = 2;
+ private static final int FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX = 3; // DEPRECATED
private static final int FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX = 4;
// Format to get probability and historical info from native side via getWordPropertyNative().
@@ -83,7 +90,6 @@ public final class BinaryDictionary extends Dictionary {
public static final String DIR_NAME_SUFFIX_FOR_RECORD_MIGRATION = ".migrating";
private long mNativeDict;
- private final Locale mLocale;
private final long mDictSize;
private final String mDictFilePath;
private final boolean mUseFullEditDistance;
@@ -117,8 +123,7 @@ public final class BinaryDictionary extends Dictionary {
public BinaryDictionary(final String filename, final long offset, final long length,
final boolean useFullEditDistance, final Locale locale, final String dictType,
final boolean isUpdatable) {
- super(dictType);
- mLocale = locale;
+ super(dictType, locale);
mDictSize = length;
mDictFilePath = filename;
mIsUpdatable = isUpdatable;
@@ -138,8 +143,7 @@ public final class BinaryDictionary extends Dictionary {
public BinaryDictionary(final String filename, final boolean useFullEditDistance,
final Locale locale, final String dictType, final long formatVersion,
final Map<String, String> attributeMap) {
- super(dictType);
- mLocale = locale;
+ super(dictType, locale);
mDictSize = 0;
mDictFilePath = filename;
// On memory dictionary is always updatable.
@@ -180,36 +184,41 @@ public final class BinaryDictionary extends Dictionary {
boolean[] isBeginningOfSentenceArray, int[] word);
private static native void getWordPropertyNative(long dict, int[] word,
boolean isBeginningOfSentence, int[] outCodePoints, boolean[] outFlags,
- int[] outProbabilityInfo, ArrayList<int[]> outBigramTargets,
- ArrayList<int[]> outBigramProbabilityInfo, ArrayList<int[]> outShortcutTargets,
- ArrayList<Integer> outShortcutProbabilities);
+ int[] outProbabilityInfo, ArrayList<int[][]> outNgramPrevWordsArray,
+ ArrayList<boolean[]> outNgramPrevWordIsBeginningOfSentenceArray,
+ ArrayList<int[]> outNgramTargets, ArrayList<int[]> outNgramProbabilityInfo,
+ ArrayList<int[]> outShortcutTargets, ArrayList<Integer> outShortcutProbabilities);
private static native int getNextWordNative(long dict, int token, int[] outCodePoints,
boolean[] outIsBeginningOfSentence);
private static native void getSuggestionsNative(long dict, long proximityInfo,
long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions,
int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray,
- int[] outputSuggestionCount, int[] outputCodePoints, int[] outputScores,
- int[] outputIndices, int[] outputTypes, int[] outputAutoCommitFirstWordConfidence,
- float[] inOutLanguageWeight);
+ int prevWordCount, int[] outputSuggestionCount, int[] outputCodePoints,
+ int[] outputScores, int[] outputIndices, int[] outputTypes,
+ int[] outputAutoCommitFirstWordConfidence,
+ float[] inOutWeightOfLangModelVsSpatialModel);
private static native boolean addUnigramEntryNative(long dict, int[] word, int probability,
int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence,
- boolean isNotAWord, boolean isBlacklisted, int timestamp);
+ boolean isNotAWord, boolean isPossiblyOffensive, int timestamp);
private static native boolean removeUnigramEntryNative(long dict, int[] word);
private static native boolean addNgramEntryNative(long dict,
int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray,
int[] word, int probability, int timestamp);
private static native boolean removeNgramEntryNative(long dict,
int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray, int[] word);
- private static native int addMultipleDictionaryEntriesNative(long dict,
- LanguageModelParam[] languageModelParams, int startIndex);
+ private static native boolean updateEntriesForWordWithNgramContextNative(long dict,
+ int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray,
+ int[] word, boolean isValidWord, int count, int timestamp);
+ private static native int updateEntriesForInputEventsNative(long dict,
+ WordInputEventForPersonalization[] inputEvents, int startIndex);
private static native String getPropertyNative(long dict, String query);
private static native boolean isCorruptedNative(long dict);
private static native boolean migrateNative(long dict, String dictFilePath,
long newFormatVersion);
// TODO: Move native dict into session
- private final void loadDictionary(final String path, final long startOffset,
+ private void loadDictionary(final String path, final long startOffset,
final long length, final boolean isUpdatable) {
mHasUpdated = false;
mNativeDict = openNative(path, startOffset, length, isUpdatable);
@@ -256,23 +265,25 @@ public final class BinaryDictionary extends Dictionary {
}
@Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData,
+ final NgramContext ngramContext, final long proximityInfoHandle,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int sessionId, final float[] inOutLanguageWeight) {
+ final int sessionId, final float weightForLocale,
+ final float[] inOutWeightOfLangModelVsSpatialModel) {
if (!isValidDictionary()) {
return null;
}
final DicTraverseSession session = getTraverseSession(sessionId);
Arrays.fill(session.mInputCodePoints, Constants.NOT_A_CODE);
- prevWordsInfo.outputToArray(session.mPrevWordCodePointArrays,
+ ngramContext.outputToArray(session.mPrevWordCodePointArrays,
session.mIsBeginningOfSentenceArray);
- final InputPointers inputPointers = composer.getInputPointers();
- final boolean isGesture = composer.isBatchMode();
+ final InputPointers inputPointers = composedData.mInputPointers;
+ final boolean isGesture = composedData.mIsBatchMode;
final int inputSize;
if (!isGesture) {
- inputSize = composer.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
- session.mInputCodePoints);
+ inputSize =
+ composedData.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
+ session.mInputCodePoints);
if (inputSize < 0) {
return null;
}
@@ -283,41 +294,45 @@ public final class BinaryDictionary extends Dictionary {
session.mNativeSuggestOptions.setIsGesture(isGesture);
session.mNativeSuggestOptions.setBlockOffensiveWords(
settingsValuesForSuggestion.mBlockPotentiallyOffensive);
- session.mNativeSuggestOptions.setSpaceAwareGestureEnabled(
- settingsValuesForSuggestion.mSpaceAwareGestureEnabled);
- session.mNativeSuggestOptions.setAdditionalFeaturesOptions(
- settingsValuesForSuggestion.mAdditionalFeaturesSettingValues);
- if (inOutLanguageWeight != null) {
- session.mInputOutputLanguageWeight[0] = inOutLanguageWeight[0];
+ session.mNativeSuggestOptions.setWeightForLocale(weightForLocale);
+ if (inOutWeightOfLangModelVsSpatialModel != null) {
+ session.mInputOutputWeightOfLangModelVsSpatialModel[0] =
+ inOutWeightOfLangModelVsSpatialModel[0];
} else {
- session.mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT;
+ session.mInputOutputWeightOfLangModelVsSpatialModel[0] =
+ Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL;
}
// TOOD: Pass multiple previous words information for n-gram.
- getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
+ getSuggestionsNative(mNativeDict, proximityInfoHandle,
getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(),
inputPointers.getYCoordinates(), inputPointers.getTimes(),
inputPointers.getPointerIds(), session.mInputCodePoints, inputSize,
session.mNativeSuggestOptions.getOptions(), session.mPrevWordCodePointArrays,
- session.mIsBeginningOfSentenceArray, session.mOutputSuggestionCount,
- session.mOutputCodePoints, session.mOutputScores, session.mSpaceIndices,
- session.mOutputTypes, session.mOutputAutoCommitFirstWordConfidence,
- session.mInputOutputLanguageWeight);
- if (inOutLanguageWeight != null) {
- inOutLanguageWeight[0] = session.mInputOutputLanguageWeight[0];
+ session.mIsBeginningOfSentenceArray, ngramContext.getPrevWordCount(),
+ session.mOutputSuggestionCount, session.mOutputCodePoints, session.mOutputScores,
+ session.mSpaceIndices, session.mOutputTypes,
+ session.mOutputAutoCommitFirstWordConfidence,
+ session.mInputOutputWeightOfLangModelVsSpatialModel);
+ if (inOutWeightOfLangModelVsSpatialModel != null) {
+ inOutWeightOfLangModelVsSpatialModel[0] =
+ session.mInputOutputWeightOfLangModelVsSpatialModel[0];
}
final int count = session.mOutputSuggestionCount[0];
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
for (int j = 0; j < count; ++j) {
- final int start = j * Constants.DICTIONARY_MAX_WORD_LENGTH;
+ final int start = j * DICTIONARY_MAX_WORD_LENGTH;
int len = 0;
- while (len < Constants.DICTIONARY_MAX_WORD_LENGTH
+ while (len < DICTIONARY_MAX_WORD_LENGTH
&& session.mOutputCodePoints[start + len] != 0) {
++len;
}
if (len > 0) {
suggestions.add(new SuggestedWordInfo(
new String(session.mOutputCodePoints, start, len),
- session.mOutputScores[j], session.mOutputTypes[j], this /* sourceDict */,
+ "" /* prevWordsContext */,
+ (int)(session.mOutputScores[j] * weightForLocale),
+ session.mOutputTypes[j],
+ this /* sourceDict */,
session.mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
session.mOutputAutoCommitFirstWordConfidence[0]));
}
@@ -340,31 +355,34 @@ public final class BinaryDictionary extends Dictionary {
@Override
public int getFrequency(final String word) {
- if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
- int[] codePoints = StringUtils.toCodePointArray(word);
+ if (TextUtils.isEmpty(word)) {
+ return NOT_A_PROBABILITY;
+ }
+ final int[] codePoints = StringUtils.toCodePointArray(word);
return getProbabilityNative(mNativeDict, codePoints);
}
@Override
public int getMaxFrequencyOfExactMatches(final String word) {
- if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
- int[] codePoints = StringUtils.toCodePointArray(word);
+ if (TextUtils.isEmpty(word)) {
+ return NOT_A_PROBABILITY;
+ }
+ final int[] codePoints = StringUtils.toCodePointArray(word);
return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints);
}
@UsedForTesting
- public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
- return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY;
+ public boolean isValidNgram(final NgramContext ngramContext, final String word) {
+ return getNgramProbability(ngramContext, word) != NOT_A_PROBABILITY;
}
- public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) {
- if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
+ public int getNgramProbability(final NgramContext ngramContext, final String word) {
+ if (!ngramContext.isValid() || TextUtils.isEmpty(word)) {
return NOT_A_PROBABILITY;
}
- final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
- final boolean[] isBeginningOfSentenceArray =
- new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
- prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
+ final int[][] prevWordCodePointArrays = new int[ngramContext.getPrevWordCount()][];
+ final boolean[] isBeginningOfSentenceArray = new boolean[ngramContext.getPrevWordCount()];
+ ngramContext.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
final int[] wordCodePoints = StringUtils.toCodePointArray(word);
return getNgramProbabilityNative(mNativeDict, prevWordCodePointArrays,
isBeginningOfSentenceArray, wordCodePoints);
@@ -375,25 +393,28 @@ public final class BinaryDictionary extends Dictionary {
return null;
}
final int[] codePoints = StringUtils.toCodePointArray(word);
- final int[] outCodePoints = new int[Constants.DICTIONARY_MAX_WORD_LENGTH];
+ final int[] outCodePoints = new int[DICTIONARY_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 = new ArrayList<>();
- final ArrayList<int[]> outBigramProbabilityInfo = new ArrayList<>();
+ final ArrayList<int[][]> outNgramPrevWordsArray = new ArrayList<>();
+ final ArrayList<boolean[]> outNgramPrevWordIsBeginningOfSentenceArray =
+ new ArrayList<>();
+ final ArrayList<int[]> outNgramTargets = new ArrayList<>();
+ final ArrayList<int[]> outNgramProbabilityInfo = new ArrayList<>();
final ArrayList<int[]> outShortcutTargets = new ArrayList<>();
final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>();
getWordPropertyNative(mNativeDict, codePoints, isBeginningOfSentence, outCodePoints,
- outFlags, outProbabilityInfo, outBigramTargets, outBigramProbabilityInfo,
- outShortcutTargets, outShortcutProbabilities);
+ outFlags, outProbabilityInfo, outNgramPrevWordsArray,
+ outNgramPrevWordIsBeginningOfSentenceArray, outNgramTargets,
+ outNgramProbabilityInfo, 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],
+ outFlags[FORMAT_WORD_PROPERTY_IS_POSSIBLY_OFFENSIVE_INDEX],
+ outFlags[FORMAT_WORD_PROPERTY_HAS_NGRAMS_INDEX],
outFlags[FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX], outProbabilityInfo,
- outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
- outShortcutProbabilities);
+ outNgramPrevWordsArray, outNgramPrevWordIsBeginningOfSentenceArray,
+ outNgramTargets, outNgramProbabilityInfo);
}
public static class GetNextWordPropertyResult {
@@ -411,7 +432,7 @@ public final class BinaryDictionary extends Dictionary {
* If token is 0, this method newly starts iterating the dictionary.
*/
public GetNextWordPropertyResult getNextWordProperty(final int token) {
- final int[] codePoints = new int[Constants.DICTIONARY_MAX_WORD_LENGTH];
+ final int[] codePoints = new int[DICTIONARY_MAX_WORD_LENGTH];
final boolean[] isBeginningOfSentence = new boolean[1];
final int nextToken = getNextWordNative(mNativeDict, token, codePoints,
isBeginningOfSentence);
@@ -421,18 +442,16 @@ public final class BinaryDictionary extends Dictionary {
}
// Add a unigram entry to binary dictionary with unigram attributes in native code.
- public boolean addUnigramEntry(final String word, final int probability,
- final String shortcutTarget, final int shortcutProbability,
- final boolean isBeginningOfSentence, final boolean isNotAWord,
- final boolean isBlacklisted, final int timestamp) {
+ public boolean addUnigramEntry(
+ final String word, final int probability, final boolean isBeginningOfSentence,
+ final boolean isNotAWord, final boolean isPossiblyOffensive, final int timestamp) {
if (word == null || (word.isEmpty() && !isBeginningOfSentence)) {
return false;
}
final int[] codePoints = StringUtils.toCodePointArray(word);
- final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
- StringUtils.toCodePointArray(shortcutTarget) : null;
- if (!addUnigramEntryNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
- shortcutProbability, isBeginningOfSentence, isNotAWord, isBlacklisted, timestamp)) {
+ if (!addUnigramEntryNative(mNativeDict, codePoints, probability,
+ null /* shortcutTargetCodePoints */, 0 /* shortcutProbability */,
+ isBeginningOfSentence, isNotAWord, isPossiblyOffensive, timestamp)) {
return false;
}
mHasUpdated = true;
@@ -453,15 +472,14 @@ public final class BinaryDictionary extends Dictionary {
}
// Add an n-gram entry to the binary dictionary with timestamp in native code.
- public boolean addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
+ public boolean addNgramEntry(final NgramContext ngramContext, final String word,
final int probability, final int timestamp) {
- if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
+ if (!ngramContext.isValid() || TextUtils.isEmpty(word)) {
return false;
}
- final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
- final boolean[] isBeginningOfSentenceArray =
- new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
- prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
+ final int[][] prevWordCodePointArrays = new int[ngramContext.getPrevWordCount()][];
+ final boolean[] isBeginningOfSentenceArray = new boolean[ngramContext.getPrevWordCount()];
+ ngramContext.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
final int[] wordCodePoints = StringUtils.toCodePointArray(word);
if (!addNgramEntryNative(mNativeDict, prevWordCodePointArrays,
isBeginningOfSentenceArray, wordCodePoints, probability, timestamp)) {
@@ -471,35 +489,38 @@ public final class BinaryDictionary extends Dictionary {
return true;
}
- // Remove an n-gram entry from the binary dictionary in native code.
- public boolean removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) {
- if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
+ // Update entries for the word occurrence with the ngramContext.
+ public boolean updateEntriesForWordWithNgramContext(@Nonnull final NgramContext ngramContext,
+ final String word, final boolean isValidWord, final int count, final int timestamp) {
+ if (TextUtils.isEmpty(word)) {
return false;
}
- final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
- final boolean[] isBeginningOfSentenceArray =
- new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
- prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
+ final int[][] prevWordCodePointArrays = new int[ngramContext.getPrevWordCount()][];
+ final boolean[] isBeginningOfSentenceArray = new boolean[ngramContext.getPrevWordCount()];
+ ngramContext.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
final int[] wordCodePoints = StringUtils.toCodePointArray(word);
- if (!removeNgramEntryNative(mNativeDict, prevWordCodePointArrays,
- isBeginningOfSentenceArray, wordCodePoints)) {
+ if (!updateEntriesForWordWithNgramContextNative(mNativeDict, prevWordCodePointArrays,
+ isBeginningOfSentenceArray, wordCodePoints, isValidWord, count, timestamp)) {
return false;
}
mHasUpdated = true;
return true;
}
- public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
- if (!isValidDictionary()) return;
- int processedParamCount = 0;
- while (processedParamCount < languageModelParams.length) {
+ @UsedForTesting
+ public void updateEntriesForInputEvents(final WordInputEventForPersonalization[] inputEvents) {
+ if (!isValidDictionary()) {
+ return;
+ }
+ int processedEventCount = 0;
+ while (processedEventCount < inputEvents.length) {
if (needsToRunGC(true /* mindsBlockByGC */)) {
flushWithGC();
}
- processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict,
- languageModelParams, processedParamCount);
+ processedEventCount = updateEntriesForInputEventsNative(mNativeDict, inputEvents,
+ processedEventCount);
mHasUpdated = true;
- if (processedParamCount <= 0) {
+ if (processedEventCount <= 0) {
return;
}
}
@@ -517,7 +538,9 @@ public final class BinaryDictionary extends Dictionary {
// Flush to dict file if the dictionary has been updated.
public boolean flush() {
- if (!isValidDictionary()) return false;
+ if (!isValidDictionary()) {
+ return false;
+ }
if (mHasUpdated) {
if (!flushNative(mNativeDict, mDictFilePath)) {
return false;
@@ -537,7 +560,9 @@ public final class BinaryDictionary extends Dictionary {
// Run GC and flush to dict file.
public boolean flushWithGC() {
- if (!isValidDictionary()) return false;
+ if (!isValidDictionary()) {
+ return false;
+ }
if (!flushWithGCNative(mNativeDict, mDictFilePath)) {
return false;
}
@@ -552,7 +577,9 @@ public final class BinaryDictionary extends Dictionary {
* @return whether GC is needed to run or not.
*/
public boolean needsToRunGC(final boolean mindsBlockByGC) {
- if (!isValidDictionary()) return false;
+ if (!isValidDictionary()) {
+ return false;
+ }
return needsToRunGCNative(mNativeDict, mindsBlockByGC);
}
@@ -596,8 +623,10 @@ public final class BinaryDictionary extends Dictionary {
}
@UsedForTesting
- public String getPropertyForTest(final String query) {
- if (!isValidDictionary()) return "";
+ public String getPropertyForGettingStats(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 10b1f1b77..bc62f3ae3 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -29,6 +29,7 @@ import android.util.Log;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
import com.android.inputmethod.dictionarypack.MD5Calculator;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils.DictionaryInfo;
import com.android.inputmethod.latin.utils.FileTransforms;
@@ -67,6 +68,11 @@ public final class BinaryDictionaryFileDumper {
private static final byte[] MAGIC_NUMBER_VERSION_2 =
new byte[] { (byte)0x9B, (byte)0xC1, (byte)0x3A, (byte)0xFE };
+ private static final boolean SHOULD_VERIFY_MAGIC_NUMBER =
+ DecoderSpecificConstants.SHOULD_VERIFY_MAGIC_NUMBER;
+ private static final boolean SHOULD_VERIFY_CHECKSUM =
+ DecoderSpecificConstants.SHOULD_VERIFY_CHECKSUM;
+
private static final String DICTIONARY_PROJECTION[] = { "id" };
private static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt";
@@ -302,13 +308,18 @@ public final class BinaryDictionaryFileDumper {
checkMagicAndCopyFileTo(bufferedInputStream, bufferedOutputStream);
bufferedOutputStream.flush();
bufferedOutputStream.close();
- final String actualRawChecksum = MD5Calculator.checksum(
- new BufferedInputStream(new FileInputStream(outputFile)));
- Log.i(TAG, "Computed checksum for downloaded dictionary. Expected = " + rawChecksum
- + " ; actual = " + actualRawChecksum);
- if (!TextUtils.isEmpty(rawChecksum) && !rawChecksum.equals(actualRawChecksum)) {
- throw new IOException("Could not decode the file correctly : checksum differs");
+
+ if (SHOULD_VERIFY_CHECKSUM) {
+ final String actualRawChecksum = MD5Calculator.checksum(
+ new BufferedInputStream(new FileInputStream(outputFile)));
+ Log.i(TAG, "Computed checksum for downloaded dictionary. Expected = "
+ + rawChecksum + " ; actual = " + actualRawChecksum);
+ if (!TextUtils.isEmpty(rawChecksum) && !rawChecksum.equals(actualRawChecksum)) {
+ throw new IOException(
+ "Could not decode the file correctly : checksum differs");
+ }
}
+
final File finalFile = new File(finalFileName);
finalFile.delete();
if (!outputFile.renameTo(finalFile)) {
@@ -444,9 +455,11 @@ public final class BinaryDictionaryFileDumper {
if (readMagicNumberSize < length) {
throw new IOException("Less bytes to read than the magic number length");
}
- if (!Arrays.equals(MAGIC_NUMBER_VERSION_2, magicNumberBuffer)) {
- if (!Arrays.equals(MAGIC_NUMBER_VERSION_1, magicNumberBuffer)) {
- throw new IOException("Wrong magic number for downloaded file");
+ if (SHOULD_VERIFY_MAGIC_NUMBER) {
+ if (!Arrays.equals(MAGIC_NUMBER_VERSION_2, magicNumberBuffer)) {
+ if (!Arrays.equals(MAGIC_NUMBER_VERSION_1, magicNumberBuffer)) {
+ throw new IOException("Wrong magic number for downloaded file");
+ }
}
}
output.write(magicNumberBuffer);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 867c18686..f4300c462 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -21,11 +21,12 @@ import android.content.SharedPreferences;
import android.content.res.AssetFileDescriptor;
import android.util.Log;
+import com.android.inputmethod.latin.common.LocaleUtils;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
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.DictionaryInfoUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
import java.io.File;
import java.io.IOException;
@@ -54,6 +55,9 @@ final public class BinaryDictionaryGetter {
*/
private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs";
+ private static final boolean SHOULD_USE_DICT_VERSION =
+ DecoderSpecificConstants.SHOULD_USE_DICT_VERSION;
+
// Name of the category for the main dictionary
public static final String MAIN_DICTIONARY_CATEGORY = "main";
public static final String ID_CATEGORY_SEPARATOR = ":";
@@ -87,10 +91,15 @@ final public class BinaryDictionaryGetter {
*/
public static AssetFileAddress loadFallbackResource(final Context context,
final int fallbackResId) {
- final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId);
+ AssetFileDescriptor afd = null;
+ try {
+ afd = context.getResources().openRawResourceFd(fallbackResId);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Resource not found: " + fallbackResId, e);
+ return null;
+ }
if (afd == null) {
- Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId="
- + fallbackResId);
+ Log.e(TAG, "Resource cannot be opened: " + fallbackResId);
return null;
}
try {
@@ -99,8 +108,7 @@ final public class BinaryDictionaryGetter {
} finally {
try {
afd.close();
- } catch (IOException e) {
- // Ignored
+ } catch (IOException ignored) {
}
}
}
@@ -121,12 +129,11 @@ final public class BinaryDictionaryGetter {
// reason some dictionaries have been installed BUT the dictionary pack can't be
// found anymore it's safer to actually supply installed dictionaries.
return true;
- } else {
- // The default is true here for the same reasons as above. We got the dictionary
- // pack but if we don't have any settings for it it means the user has never been
- // to the settings yet. So by default, the main dictionaries should be on.
- return mDictPreferences.getBoolean(dictId, true);
}
+ // The default is true here for the same reasons as above. We got the dictionary
+ // pack but if we don't have any settings for it it means the user has never been
+ // to the settings yet. So by default, the main dictionaries should be on.
+ return mDictPreferences.getBoolean(dictId, true);
}
}
@@ -224,7 +231,11 @@ 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 file) {
+ private static boolean hackCanUseDictionaryFile(final File file) {
+ if (!SHOULD_USE_DICT_VERSION) {
+ return true;
+ }
+
try {
// Read the version of the file
final DictionaryHeader header = BinaryDictionaryUtils.getHeader(file);
@@ -264,7 +275,8 @@ final public class BinaryDictionaryGetter {
public static ArrayList<AssetFileAddress> getDictionaryFiles(final Locale locale,
final Context context) {
- final boolean hasDefaultWordList = DictionaryFactory.isDictionaryAvailable(context, locale);
+ final boolean hasDefaultWordList = DictionaryInfoUtils.isDictionaryAvailable(
+ context, locale);
BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context,
hasDefaultWordList);
final File[] cachedWordLists = getCachedWordLists(locale.toString(), context);
@@ -276,7 +288,7 @@ final public class BinaryDictionaryGetter {
// cachedWordLists may not be null, see doc for getCachedDictionaryList
for (final File f : cachedWordLists) {
final String wordListId = DictionaryInfoUtils.getWordListIdFromFileName(f.getName());
- final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f);
+ final boolean canUse = f.canRead() && hackCanUseDictionaryFile(f);
if (canUse && DictionaryInfoUtils.isMainWordListId(wordListId)) {
foundMainDict = true;
}
@@ -285,7 +297,8 @@ final public class BinaryDictionaryGetter {
final AssetFileAddress afa = AssetFileAddress.makeFromFileName(f.getPath());
if (null != afa) fileList.add(afa);
} else {
- Log.e(TAG, "Found a cached dictionary file but cannot read or use it");
+ Log.e(TAG, "Found a cached dictionary file for " + locale.toString()
+ + " but cannot read or use it");
}
}
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
deleted file mode 100644
index 43af66eb7..000000000
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ /dev/null
@@ -1,311 +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;
-
-public final class Constants {
- public static final class Color {
- /**
- * The alpha value for fully opaque.
- */
- public final static int ALPHA_OPAQUE = 255;
- }
-
- public static final class ImeOption {
- /**
- * The private IME option used to indicate that no microphone should be shown for a given
- * text field. For instance, this is specified by the search dialog when the dialog is
- * already showing a voice search button.
- *
- * @deprecated Use {@link ImeOption#NO_MICROPHONE} with package name prefixed.
- */
- @SuppressWarnings("dep-ann")
- public static final String NO_MICROPHONE_COMPAT = "nm";
-
- /**
- * The private IME option used to indicate that no microphone should be shown for a given
- * text field. For instance, this is specified by the search dialog when the dialog is
- * already showing a voice search button.
- */
- public static final String NO_MICROPHONE = "noMicrophoneKey";
-
- /**
- * The private IME option used to indicate that no settings key should be shown for a given
- * text field.
- */
- public static final String NO_SETTINGS_KEY = "noSettingsKey";
-
- /**
- * The private IME option used to indicate that the given text field needs ASCII code points
- * input.
- *
- * @deprecated Use EditorInfo#IME_FLAG_FORCE_ASCII.
- */
- @SuppressWarnings("dep-ann")
- public static final String FORCE_ASCII = "forceAscii";
-
- private ImeOption() {
- // This utility class is not publicly instantiable.
- }
- }
-
- public static final class Subtype {
- /**
- * The subtype mode used to indicate that the subtype is a keyboard.
- */
- public static final String KEYBOARD_MODE = "keyboard";
-
- public static final class ExtraValue {
- /**
- * 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 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 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 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 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 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";
-
- /**
- * The subtype extra value used to specify the combining rules.
- */
- public static final String COMBINING_RULES = "CombiningRules";
-
- private ExtraValue() {
- // This utility class is not publicly instantiable.
- }
- }
-
- private Subtype() {
- // This utility class is not publicly instantiable.
- }
- }
-
- public static final class TextUtils {
- /**
- * Capitalization mode for {@link android.text.TextUtils#getCapsMode}: don't capitalize
- * characters. This value may be used with
- * {@link android.text.TextUtils#CAP_MODE_CHARACTERS},
- * {@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() {
- // This utility class is not publicly instantiable.
- }
- }
-
- 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;
- public static final int EXTERNAL_KEYBOARD_COORDINATE = -4;
-
- // A hint on how many characters to cache from the TextView. A good value of this is given by
- // how many characters we need to be able to almost always find the caps mode.
- public static final int EDITOR_CONTENTS_CACHE_SIZE = 1024;
- // How many characters we accept for the recapitalization functionality. This needs to be
- // large enough for all reasonable purposes, but avoid purposeful attacks. 100k sounds about
- // right for this.
- public static final int MAX_CHARACTERS_FOR_RECAPITALIZATION = 1024 * 100;
-
- // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
- public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
-
- // (MAX_PREV_WORD_COUNT_FOR_N_GRAM + 1)-gram is supported in Java side. Needs to modify
- // MAX_PREV_WORD_COUNT_FOR_N_GRAM in native/jni/src/defines.h for suggestions.
- public static final int MAX_PREV_WORD_COUNT_FOR_N_GRAM = 2;
-
- // 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 final String WORD_SEPARATOR = " ";
-
- public static boolean isValidCoordinate(final int coordinate) {
- // Detect {@link NOT_A_COORDINATE}, {@link SUGGESTION_STRIP_COORDINATE},
- // and {@link SPELL_CHECKER_COORDINATE}.
- return coordinate >= 0;
- }
-
- /**
- * Custom request code used in
- * {@link com.android.inputmethod.keyboard.KeyboardActionListener#onCustomRequest(int)}.
- */
- // The code to show input method picker.
- public static final int CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER = 1;
-
- /**
- * Some common keys code. Must be positive.
- */
- public static final int CODE_ENTER = '\n';
- 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_DASH = '-';
- public static final int CODE_SINGLE_QUOTE = '\'';
- public static final int CODE_DOUBLE_QUOTE = '"';
- 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 = '%';
- public static final int CODE_CLOSING_PARENTHESIS = ')';
- public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
- public static final int CODE_CLOSING_CURLY_BRACKET = '}';
- public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
- public static final int CODE_INVERTED_QUESTION_MARK = 0xBF; // ¿
- public static final int CODE_INVERTED_EXCLAMATION_MARK = 0xA1; // ¡
-
- public static final String REGEXP_PERIOD = "\\.";
- public static final String STRING_SPACE = " ";
- public static final String STRING_PERIOD_AND_SPACE = ". ";
-
- /**
- * Special keys code. Must be negative.
- * These should be aligned with {@link KeyboardCodesSet#ID_TO_NAME},
- * {@link KeyboardCodesSet#DEFAULT}, and {@link KeyboardCodesSet#RTL}.
- */
- public static final int CODE_SHIFT = -1;
- public static final int CODE_CAPSLOCK = -2;
- public static final int CODE_SWITCH_ALPHA_SYMBOL = -3;
- public static final int CODE_OUTPUT_TEXT = -4;
- public static final int CODE_DELETE = -5;
- public static final int CODE_SETTINGS = -6;
- public static final int CODE_SHORTCUT = -7;
- public static final int CODE_ACTION_NEXT = -8;
- public static final int CODE_ACTION_PREVIOUS = -9;
- 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 = -15;
-
- public static boolean isLetterCode(final int code) {
- return code >= CODE_SPACE;
- }
-
- public static String printableCode(final int code) {
- switch (code) {
- case CODE_SHIFT: return "shift";
- case CODE_CAPSLOCK: return "capslock";
- case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";
- case CODE_OUTPUT_TEXT: return "text";
- case CODE_DELETE: return "delete";
- case CODE_SETTINGS: return "settings";
- case CODE_SHORTCUT: return "shortcut";
- case CODE_ACTION_NEXT: return "actionNext";
- case CODE_ACTION_PREVIOUS: return "actionPrevious";
- case CODE_LANGUAGE_SWITCH: return "languageSwitch";
- case CODE_EMOJI: return "emoji";
- case CODE_SHIFT_ENTER: return "shiftEnter";
- case CODE_ALPHA_FROM_EMOJI: return "alpha";
- case CODE_UNSPECIFIED: return "unspec";
- case CODE_TAB: return "tab";
- case CODE_ENTER: return "enter";
- case CODE_SPACE: return "space";
- default:
- if (code < CODE_SPACE) return String.format("\\u%02X", code);
- if (code < 0x100) return String.format("%c", code);
- if (code < 0x10000) return String.format("\\u%04X", code);
- return String.format("\\U%05X", code);
- }
- }
-
- 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 162a209e3..15a14e5af 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -16,34 +16,26 @@
package com.android.inputmethod.latin;
-import android.content.ContentResolver;
import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
import android.net.Uri;
-import android.os.SystemClock;
-import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
-import android.text.TextUtils;
import android.util.Log;
-import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.annotations.ExternallyReferenced;
+import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.personalization.AccountUtils;
-import com.android.inputmethod.latin.utils.ExecutorUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
-public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
-
- private static final String[] PROJECTION = {BaseColumns._ID, Contacts.DISPLAY_NAME};
- private static final String[] PROJECTION_ID_ONLY = {BaseColumns._ID};
+import javax.annotation.Nullable;
+public class ContactsBinaryDictionary extends ExpandableBinaryDictionary
+ implements ContactsChangedListener {
private static final String TAG = ContactsBinaryDictionary.class.getSimpleName();
private static final String NAME = "contacts";
@@ -51,72 +43,39 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private static final boolean DEBUG_DUMP = false;
/**
- * Frequency for contacts information into the dictionary
- */
- private static final int FREQUENCY_FOR_CONTACTS = 40;
- private static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90;
-
- /** The maximum number of contacts that this dictionary supports. */
- private static final int MAX_CONTACT_COUNT = 10000;
-
- private static final int INDEX_NAME = 1;
-
- /** The number of contacts in the most recent dictionary rebuild. */
- private int mContactCountAtLastRebuild = 0;
-
- /** The hash code of ArrayList of contacts names in the most recent dictionary rebuild. */
- private int mHashCodeAtLastRebuild = 0;
-
- private ContentObserver mObserver;
-
- /**
* Whether to use "firstname lastname" in bigram predictions.
*/
private final boolean mUseFirstLastBigrams;
+ private final ContactsManager mContactsManager;
protected ContactsBinaryDictionary(final Context context, final Locale locale,
final File dictFile, final String name) {
super(context, getDictName(name, locale, dictFile), locale, Dictionary.TYPE_CONTACTS,
dictFile);
- mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale);
- registerObserver(context);
+ mUseFirstLastBigrams = ContactsDictionaryUtils.useFirstLastBigramsForLocale(locale);
+ mContactsManager = new ContactsManager(context);
+ mContactsManager.registerForUpdates(this /* listener */);
reloadDictionaryIfRequired();
}
- @UsedForTesting
+ // Note: This method is called by {@link DictionaryFacilitator} using Java reflection.
+ @ExternallyReferenced
public static ContactsBinaryDictionary getDictionary(final Context context, final Locale locale,
- final File dictFile, final String dictNamePrefix) {
+ final File dictFile, final String dictNamePrefix, @Nullable final String account) {
return new ContactsBinaryDictionary(context, locale, dictFile, dictNamePrefix + NAME);
}
- private synchronized void registerObserver(final Context context) {
- if (mObserver != null) return;
- ContentResolver cres = context.getContentResolver();
- cres.registerContentObserver(Contacts.CONTENT_URI, true, mObserver =
- new ContentObserver(null) {
- @Override
- public void onChange(boolean self) {
- ExecutorUtils.getExecutor("Check Contacts").execute(new Runnable() {
- @Override
- public void run() {
- if (haveContentsChanged()) {
- setNeedsToRecreate();
- }
- }
- });
- }
- });
- }
-
@Override
public synchronized void close() {
- if (mObserver != null) {
- mContext.getContentResolver().unregisterContentObserver(mObserver);
- mObserver = null;
- }
+ mContactsManager.close();
super.close();
}
+ /**
+ * Typically called whenever the dictionary is created for the first time or
+ * recreated when we think that there are updates to the dictionary.
+ * This is called asynchronously.
+ */
@Override
public void loadInitialContentsLocked() {
loadDeviceAccountsEmailAddressesLocked();
@@ -125,6 +84,9 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
loadDictionaryForUriLocked(Contacts.CONTENT_URI);
}
+ /**
+ * Loads device accounts to the dictionary.
+ */
private void loadDeviceAccountsEmailAddressesLocked() {
final List<String> accountVocabulary =
AccountUtils.getDeviceAccountsEmailAddresses(mContext);
@@ -136,80 +98,25 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
Log.d(TAG, "loadAccountVocabulary: " + word);
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addUnigramLocked(word, FREQUENCY_FOR_CONTACTS, null /* shortcut */,
- 0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */,
+ addUnigramLocked(word, ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS,
+ false /* isNotAWord */, false /* isPossiblyOffensive */,
BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
}
+ /**
+ * Loads data within content providers to the dictionary.
+ */
private void loadDictionaryForUriLocked(final Uri uri) {
- Cursor cursor = null;
- try {
- cursor = mContext.getContentResolver().query(uri, PROJECTION, null, null, null);
- if (null == cursor) {
- return;
- }
- if (cursor.moveToFirst()) {
- mContactCountAtLastRebuild = 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();
- }
- }
- }
-
- private boolean useFirstLastBigramsForLocale(final Locale locale) {
- // TODO: Add firstname/lastname bigram rules for other languages.
- if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
- return true;
- }
- return false;
- }
-
- private void addWordsLocked(final Cursor cursor) {
- int count = 0;
- final ArrayList<String> names = new ArrayList<>();
- while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) {
- String name = cursor.getString(INDEX_NAME);
- if (isValidName(name)) {
- names.add(name);
- addNameLocked(name);
- ++count;
- } else {
- if (DEBUG_DUMP) {
- Log.d(TAG, "Invalid name: " + name);
- }
- }
- cursor.moveToNext();
+ final ArrayList<String> validNames = mContactsManager.getValidNames(uri);
+ for (final String name : validNames) {
+ addNameLocked(name);
}
- mHashCodeAtLastRebuild = names.hashCode();
- }
-
- private int getContactCount() {
- // TODO: consider switching to a rawQuery("select count(*)...") on the database if
- // performance is a bottleneck.
- Cursor cursor = null;
- try {
- 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();
- }
+ if (uri.equals(Contacts.CONTENT_URI)) {
+ // Since we were able to add content successfully, update the local
+ // state of the manager.
+ mContactsManager.updateLocalState(validNames);
}
- return 0;
}
/**
@@ -218,11 +125,12 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
*/
private void addNameLocked(final String name) {
int len = StringUtils.codePointCount(name);
- PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ NgramContext ngramContext = NgramContext.getEmptyPrevWordsContext(
+ BinaryDictionary.MAX_PREV_WORD_COUNT_FOR_N_GRAM);
// TODO: Better tokenization for non-Latin writing systems
for (int i = 0; i < len; i++) {
if (Character.isLetter(name.codePointAt(i))) {
- int end = getWordEndPosition(name, len, i);
+ int end = ContactsDictionaryUtils.getWordEndPosition(name, len, i);
String word = name.substring(i, end);
if (DEBUG_DUMP) {
Log.d(TAG, "addName word = " + word);
@@ -233,93 +141,29 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
final int wordLen = StringUtils.codePointCount(word);
if (wordLen <= MAX_WORD_LENGTH && wordLen > 1) {
if (DEBUG) {
- Log.d(TAG, "addName " + name + ", " + word + ", " + prevWordsInfo);
+ Log.d(TAG, "addName " + name + ", " + word + ", " + ngramContext);
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addUnigramLocked(word, FREQUENCY_FOR_CONTACTS,
- null /* shortcut */, 0 /* shortcutFreq */, false /* isNotAWord */,
- false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
- if (!prevWordsInfo.isValid() && mUseFirstLastBigrams) {
+ addUnigramLocked(word,
+ ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS, false /* isNotAWord */,
+ false /* isPossiblyOffensive */,
+ BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+ if (ngramContext.isValid() && mUseFirstLastBigrams) {
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addNgramEntryLocked(prevWordsInfo, word, FREQUENCY_FOR_CONTACTS_BIGRAM,
+ addNgramEntryLocked(ngramContext,
+ word,
+ ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS_BIGRAM,
BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
- prevWordsInfo = prevWordsInfo.getNextPrevWordsInfo(
- new PrevWordsInfo.WordInfo(word));
- }
- }
- }
- }
-
- /**
- * Returns the index of the last letter in the word, starting from position startIndex.
- */
- private static int getWordEndPosition(final String string, final int len,
- final int startIndex) {
- int end;
- int cp = 0;
- for (end = startIndex + 1; end < len; end += Character.charCount(cp)) {
- cp = string.codePointAt(end);
- if (!(cp == Constants.CODE_DASH || cp == Constants.CODE_SINGLE_QUOTE
- || Character.isLetter(cp))) {
- break;
- }
- }
- return end;
- }
-
- private boolean haveContentsChanged() {
- final long startTime = SystemClock.uptimeMillis();
- final int contactCount = getContactCount();
- if (contactCount > MAX_CONTACT_COUNT) {
- // If there are too many contacts then return false. In this rare case it is impossible
- // to include all of them anyways and the cost of rebuilding the dictionary is too high.
- // TODO: Sort and check only the MAX_CONTACT_COUNT most recent contacts?
- return false;
- }
- if (contactCount != mContactCountAtLastRebuild) {
- if (DEBUG) {
- Log.d(TAG, "Contact count changed: " + mContactCountAtLastRebuild + " to "
- + contactCount);
- }
- return true;
- }
- // 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.
- final Cursor cursor = mContext.getContentResolver().query(Contacts.CONTENT_URI, PROJECTION,
- null, null, null);
- if (null == cursor) {
- return false;
- }
- final ArrayList<String> names = new ArrayList<>();
- try {
- if (cursor.moveToFirst()) {
- while (!cursor.isAfterLast()) {
- String name = cursor.getString(INDEX_NAME);
- if (isValidName(name)) {
- names.add(name);
- }
- cursor.moveToNext();
+ ngramContext = ngramContext.getNextNgramContext(
+ new NgramContext.WordInfo(word));
}
}
- if (names.hashCode() != mHashCodeAtLastRebuild) {
- return true;
- }
- } finally {
- cursor.close();
}
- if (DEBUG) {
- Log.d(TAG, "No contacts changed. (runtime = " + (SystemClock.uptimeMillis() - startTime)
- + " ms)");
- }
- return false;
}
- private static boolean isValidName(final String name) {
- if (name != null && -1 == name.indexOf(Constants.CODE_COMMERCIAL_AT)) {
- return true;
- }
- return false;
+ @Override
+ public void onContactsChange() {
+ setNeedsToRecreate();
}
}
diff --git a/java/src/com/android/inputmethod/latin/ContactsContentObserver.java b/java/src/com/android/inputmethod/latin/ContactsContentObserver.java
new file mode 100644
index 000000000..5eb9b16d1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ContactsContentObserver.java
@@ -0,0 +1,114 @@
+/*
+ * 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.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.SystemClock;
+import android.provider.ContactsContract.Contacts;
+import android.util.Log;
+
+import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener;
+import com.android.inputmethod.latin.utils.ExecutorUtils;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A content observer that listens to updates to content provider {@link Contacts#CONTENT_URI}.
+ */
+public class ContactsContentObserver implements Runnable {
+ private static final String TAG = ContactsContentObserver.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ private static AtomicBoolean sRunning = new AtomicBoolean(false);
+
+ private final Context mContext;
+ private final ContactsManager mManager;
+
+ private ContentObserver mContentObserver;
+ private ContactsChangedListener mContactsChangedListener;
+
+ public ContactsContentObserver(final ContactsManager manager, final Context context) {
+ mManager = manager;
+ mContext = context;
+ }
+
+ public void registerObserver(final ContactsChangedListener listener) {
+ if (DEBUG) {
+ Log.d(TAG, "Registered Contacts Content Observer");
+ }
+ mContactsChangedListener = listener;
+ mContentObserver = new ContentObserver(null /* handler */) {
+ @Override
+ public void onChange(boolean self) {
+ ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD)
+ .execute(ContactsContentObserver.this);
+ }
+ };
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ contentResolver.registerContentObserver(Contacts.CONTENT_URI, true, mContentObserver);
+ }
+
+ @Override
+ public void run() {
+ if (!sRunning.compareAndSet(false /* expect */, true /* update */)) {
+ if (DEBUG) {
+ Log.d(TAG, "run() : Already running. Don't waste time checking again.");
+ }
+ return;
+ }
+ if (haveContentsChanged()) {
+ if (DEBUG) {
+ Log.d(TAG, "run() : Contacts have changed. Notifying listeners.");
+ }
+ mContactsChangedListener.onContactsChange();
+ }
+ sRunning.set(false);
+ }
+
+ boolean haveContentsChanged() {
+ final long startTime = SystemClock.uptimeMillis();
+ final int contactCount = mManager.getContactCount();
+ if (contactCount > ContactsDictionaryConstants.MAX_CONTACT_COUNT) {
+ // If there are too many contacts then return false. In this rare case it is impossible
+ // to include all of them anyways and the cost of rebuilding the dictionary is too high.
+ // TODO: Sort and check only the MAX_CONTACT_COUNT most recent contacts?
+ return false;
+ }
+ if (contactCount != mManager.getContactCountAtLastRebuild()) {
+ if (DEBUG) {
+ Log.d(TAG, "Contact count changed: " + mManager.getContactCountAtLastRebuild()
+ + " to " + contactCount);
+ }
+ return true;
+ }
+ final ArrayList<String> names = mManager.getValidNames(Contacts.CONTENT_URI);
+ if (names.hashCode() != mManager.getHashCodeAtLastRebuild()) {
+ return true;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "No contacts changed. (runtime = " + (SystemClock.uptimeMillis() - startTime)
+ + " ms)");
+ }
+ return false;
+ }
+
+ public void unregister() {
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionaryConstants.java b/java/src/com/android/inputmethod/latin/ContactsDictionaryConstants.java
new file mode 100644
index 000000000..8d8faca58
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ContactsDictionaryConstants.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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.provider.BaseColumns;
+import android.provider.ContactsContract.Contacts;
+
+/**
+ * Constants related to Contacts Content Provider.
+ */
+public class ContactsDictionaryConstants {
+ /**
+ * Projections for {@link Contacts.CONTENT_URI}
+ */
+ public static final String[] PROJECTION = { BaseColumns._ID, Contacts.DISPLAY_NAME };
+ public static final String[] PROJECTION_ID_ONLY = { BaseColumns._ID };
+
+ /**
+ * Frequency for contacts information into the dictionary
+ */
+ public static final int FREQUENCY_FOR_CONTACTS = 40;
+ public static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90;
+
+ /**
+ * The maximum number of contacts that this dictionary supports.
+ */
+ public static final int MAX_CONTACT_COUNT = 10000;
+
+ /**
+ * Index of the column for 'name' in content providers:
+ * Contacts & ContactsContract.Profile.
+ */
+ public static final int NAME_INDEX = 1;
+}
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionaryUtils.java b/java/src/com/android/inputmethod/latin/ContactsDictionaryUtils.java
new file mode 100644
index 000000000..b77388434
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ContactsDictionaryUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.latin.common.Constants;
+
+import java.util.Locale;
+
+/**
+ * Utility methods related contacts dictionary.
+ */
+public class ContactsDictionaryUtils {
+
+ /**
+ * Returns the index of the last letter in the word, starting from position startIndex.
+ */
+ public static int getWordEndPosition(final String string, final int len,
+ final int startIndex) {
+ int end;
+ int cp = 0;
+ for (end = startIndex + 1; end < len; end += Character.charCount(cp)) {
+ cp = string.codePointAt(end);
+ if (cp != Constants.CODE_DASH && cp != Constants.CODE_SINGLE_QUOTE
+ && !Character.isLetter(cp)) {
+ break;
+ }
+ }
+ return end;
+ }
+
+ /**
+ * Returns true if the locale supports using first name and last name as bigrams.
+ */
+ public static boolean useFirstLastBigramsForLocale(final Locale locale) {
+ // TODO: Add firstname/lastname bigram rules for other languages.
+ if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ContactsManager.java b/java/src/com/android/inputmethod/latin/ContactsManager.java
new file mode 100644
index 000000000..1fadc6f6f
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ContactsManager.java
@@ -0,0 +1,159 @@
+/*
+ * 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.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.util.Log;
+
+import com.android.inputmethod.latin.common.Constants;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Manages all interactions with Contacts DB.
+ *
+ * The manager provides an API for listening to meaning full updates by keeping a
+ * measure of the current state of the content provider.
+ */
+public class ContactsManager {
+ private static final String TAG = ContactsManager.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /**
+ * Interface to implement for classes interested in getting notified for updates
+ * to Contacts content provider.
+ */
+ public static interface ContactsChangedListener {
+ public void onContactsChange();
+ }
+
+ /**
+ * The number of contacts observed in the most recent instance of
+ * contacts content provider.
+ */
+ private AtomicInteger mContactCountAtLastRebuild = new AtomicInteger(0);
+
+ /**
+ * The hash code of list of valid contacts names in the most recent dictionary
+ * rebuild.
+ */
+ private AtomicInteger mHashCodeAtLastRebuild = new AtomicInteger(0);
+
+ private final Context mContext;
+ private final ContactsContentObserver mObserver;
+
+ public ContactsManager(final Context context) {
+ mContext = context;
+ mObserver = new ContactsContentObserver(this /* ContactsManager */, context);
+ }
+
+ // TODO: This was synchronized in previous version. Why?
+ public void registerForUpdates(final ContactsChangedListener listener) {
+ mObserver.registerObserver(listener);
+ }
+
+ public int getContactCountAtLastRebuild() {
+ return mContactCountAtLastRebuild.get();
+ }
+
+ public int getHashCodeAtLastRebuild() {
+ return mHashCodeAtLastRebuild.get();
+ }
+
+ /**
+ * Returns all the valid names in the Contacts DB. Callers should also
+ * call {@link #updateLocalState(ArrayList)} after they are done with result
+ * so that the manager can cache local state for determining updates.
+ */
+ public ArrayList<String> getValidNames(final Uri uri) {
+ final ArrayList<String> names = new ArrayList<>();
+ // 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.
+ final Cursor cursor = mContext.getContentResolver().query(uri,
+ ContactsDictionaryConstants.PROJECTION, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ while (!cursor.isAfterLast()) {
+ final String name = cursor.getString(
+ ContactsDictionaryConstants.NAME_INDEX);
+ if (isValidName(name)) {
+ names.add(name);
+ }
+ cursor.moveToNext();
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return names;
+ }
+
+ /**
+ * Returns the number of contacts in contacts content provider.
+ */
+ public int getContactCount() {
+ // TODO: consider switching to a rawQuery("select count(*)...") on the database if
+ // performance is a bottleneck.
+ Cursor cursor = null;
+ try {
+ cursor = mContext.getContentResolver().query(Contacts.CONTENT_URI,
+ ContactsDictionaryConstants.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;
+ }
+
+ private static boolean isValidName(final String name) {
+ if (name != null && -1 == name.indexOf(Constants.CODE_COMMERCIAL_AT)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Updates the local state of the manager. This should be called when the callers
+ * are done with all the updates of the content provider successfully.
+ */
+ public void updateLocalState(final ArrayList<String> names) {
+ mContactCountAtLastRebuild.set(getContactCount());
+ mHashCodeAtLastRebuild.set(names.hashCode());
+ }
+
+ /**
+ * Performs any necessary cleanup.
+ */
+ public void close() {
+ mObserver.unregister();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index b341f623e..6816f129a 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -16,7 +16,8 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.latin.settings.NativeSuggestOptions;
+import com.android.inputmethod.latin.common.NativeSuggestOptions;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import com.android.inputmethod.latin.utils.JniUtils;
import java.util.Locale;
@@ -27,20 +28,21 @@ public final class DicTraverseSession {
}
// Must be equal to MAX_RESULTS in native/jni/src/defines.h
private static final int MAX_RESULTS = 18;
- public final int[] mInputCodePoints = new int[Constants.DICTIONARY_MAX_WORD_LENGTH];
+ public final int[] mInputCodePoints =
+ new int[DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH];
public final int[][] mPrevWordCodePointArrays =
- new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
+ new int[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
public final boolean[] mIsBeginningOfSentenceArray =
- new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ new boolean[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
public final int[] mOutputSuggestionCount = new int[1];
public final int[] mOutputCodePoints =
- new int[Constants.DICTIONARY_MAX_WORD_LENGTH * MAX_RESULTS];
+ new int[DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH * MAX_RESULTS];
public final int[] mSpaceIndices = new int[MAX_RESULTS];
public final int[] mOutputScores = new int[MAX_RESULTS];
public final int[] mOutputTypes = new int[MAX_RESULTS];
// Only one result is ever used
public final int[] mOutputAutoCommitFirstWordConfidence = new int[1];
- public final float[] mInputOutputLanguageWeight = new float[1];
+ public final float[] mInputOutputWeightOfLangModelVsSpatialModel = new float[1];
public final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
@@ -70,7 +72,7 @@ public final class DicTraverseSession {
mNativeDicTraverseSession, dictionary, previousWord, previousWordLength);
}
- private final long createNativeDicTraverseSession(String locale, long dictSize) {
+ private static long createNativeDicTraverseSession(String locale, long dictSize) {
return setDicTraverseSessionNative(locale, dictSize);
}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 560ced9c4..16dcb3208 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -17,11 +17,14 @@
package com.android.inputmethod.latin;
import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.common.ComposedData;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Arrays;
+import java.util.HashSet;
/**
* Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key
@@ -29,25 +32,24 @@ import java.util.ArrayList;
*/
public abstract class Dictionary {
public static final int NOT_A_PROBABILITY = -1;
- public static final float NOT_A_LANGUAGE_WEIGHT = -1.0f;
+ public static final float NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL = -1.0f;
// The following types do not actually come from real dictionary instances, so we create
// corresponding instances.
public static final String TYPE_USER_TYPED = "user_typed";
- public static final Dictionary DICTIONARY_USER_TYPED = new PhonyDictionary(TYPE_USER_TYPED);
+ public static final PhonyDictionary DICTIONARY_USER_TYPED = new PhonyDictionary(TYPE_USER_TYPED);
public static final String TYPE_APPLICATION_DEFINED = "application_defined";
- public static final Dictionary DICTIONARY_APPLICATION_DEFINED =
+ public static final PhonyDictionary DICTIONARY_APPLICATION_DEFINED =
new PhonyDictionary(TYPE_APPLICATION_DEFINED);
public static final String TYPE_HARDCODED = "hardcoded"; // punctuation signs and such
- public static final Dictionary DICTIONARY_HARDCODED =
+ public static final PhonyDictionary DICTIONARY_HARDCODED =
new PhonyDictionary(TYPE_HARDCODED);
// Spawned by resuming suggestions. Comes from a span that was in the TextView.
public static final String TYPE_RESUMED = "resumed";
- public static final Dictionary DICTIONARY_RESUMED =
- new PhonyDictionary(TYPE_RESUMED);
+ public static final PhonyDictionary DICTIONARY_RESUMED = new PhonyDictionary(TYPE_RESUMED);
// The following types of dictionary have actual functional instances. We don't need final
// phony dictionary instances for them.
@@ -57,33 +59,44 @@ public abstract class Dictionary {
public static final String TYPE_USER = "user";
// User history dictionary internal to LatinIME.
public static final String TYPE_USER_HISTORY = "history";
- // Personalization dictionary.
- public static final String TYPE_PERSONALIZATION = "personalization";
- // Contextual dictionary.
- public static final String TYPE_CONTEXTUAL = "contextual";
public final String mDictType;
+ // The locale for this dictionary. May be null if unknown (phony dictionary for example).
+ public final Locale mLocale;
- public Dictionary(final String dictType) {
+ /**
+ * Set out of the dictionary types listed above that are based on data specific to the user,
+ * e.g., the user's contacts.
+ */
+ private static final HashSet<String> sUserSpecificDictionaryTypes = new HashSet<>(Arrays.asList(
+ TYPE_USER_TYPED,
+ TYPE_USER,
+ TYPE_CONTACTS,
+ TYPE_USER_HISTORY));
+
+ public Dictionary(final String dictType, final Locale locale) {
mDictType = dictType;
+ mLocale = locale;
}
/**
- * Searches for suggestions for a given context. For the moment the context is only the
- * previous word.
- * @param composer the key sequence to match with coordinate info, as a WordComposer
- * @param prevWordsInfo the information of previous words.
- * @param proximityInfo the object for key proximity. May be ignored by some implementations.
+ * Searches for suggestions for a given context.
+ * @param composedData the key sequence to match with coordinate info
+ * @param ngramContext the context for n-gram.
+ * @param proximityInfoHandle the handle for key proximity. Is ignored by some implementations.
* @param settingsValuesForSuggestion the settings values used for the suggestion.
* @param sessionId the session id.
- * @param inOutLanguageWeight the language weight used for generating suggestions.
- * inOutLanguageWeight is a float array that has only one element. This can be updated when the
- * different language weight is used.
+ * @param weightForLocale the weight given to this locale, to multiply the output scores for
+ * multilingual input.
+ * @param inOutWeightOfLangModelVsSpatialModel the weight of the language model as a ratio of
+ * the spatial model, used for generating suggestions. inOutWeightOfLangModelVsSpatialModel is
+ * a float array that has only one element. This can be updated when a different value is used.
* @return the list of suggestions (possibly null if none)
*/
- abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ abstract public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData,
+ final NgramContext ngramContext, final long proximityInfoHandle,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int sessionId, final float[] inOutLanguageWeight);
+ final int sessionId, final float weightForLocale,
+ final float[] inOutWeightOfLangModelVsSpatialModel);
/**
* Checks if the given word has to be treated as a valid word. Please note that some
@@ -100,10 +113,18 @@ public abstract class Dictionary {
*/
abstract public boolean isInDictionary(final String word);
+ /**
+ * Get the frequency of the word.
+ * @param word the word to get the frequency of.
+ */
public int getFrequency(final String word) {
return NOT_A_PROBABILITY;
}
+ /**
+ * Get the maximum frequency of the word.
+ * @param word the word to get the maximum frequency of.
+ */
public int getMaxFrequencyOfExactMatches(final String word) {
return NOT_A_PROBABILITY;
}
@@ -156,20 +177,30 @@ public abstract class Dictionary {
}
/**
+ * Whether this dictionary is based on data specific to the user, e.g., the user's contacts.
+ * @return Whether this dictionary is specific to the user.
+ */
+ public boolean isUserSpecific() {
+ return sUserSpecificDictionaryTypes.contains(mDictType);
+ }
+
+ /**
* Not a true dictionary. A placeholder used to indicate suggestions that don't come from any
* real dictionary.
*/
- private static class PhonyDictionary extends Dictionary {
- // This class is not publicly instantiable.
- private PhonyDictionary(final String type) {
- super(type);
+ @UsedForTesting
+ static class PhonyDictionary extends Dictionary {
+ @UsedForTesting
+ PhonyDictionary(final String type) {
+ super(type, null);
}
@Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData,
+ final NgramContext ngramContext, final long proximityInfoHandle,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int sessionId, final float[] inOutLanguageWeight) {
+ final int sessionId, final float weightForLocale,
+ final float[] inOutWeightOfLangModelVsSpatialModel) {
return null;
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 2b4c54d48..96575f629 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -18,13 +18,14 @@ package com.android.inputmethod.latin;
import android.util.Log;
-import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.common.ComposedData;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -34,13 +35,14 @@ public final class DictionaryCollection extends Dictionary {
private final String TAG = DictionaryCollection.class.getSimpleName();
protected final CopyOnWriteArrayList<Dictionary> mDictionaries;
- public DictionaryCollection(final String dictType) {
- super(dictType);
+ public DictionaryCollection(final String dictType, final Locale locale) {
+ super(dictType, locale);
mDictionaries = new CopyOnWriteArrayList<>();
}
- public DictionaryCollection(final String dictType, final Dictionary... dictionaries) {
- super(dictType);
+ public DictionaryCollection(final String dictType, final Locale locale,
+ final Dictionary... dictionaries) {
+ super(dictType, locale);
if (null == dictionaries) {
mDictionaries = new CopyOnWriteArrayList<>();
} else {
@@ -49,30 +51,32 @@ public final class DictionaryCollection extends Dictionary {
}
}
- public DictionaryCollection(final String dictType, final Collection<Dictionary> dictionaries) {
- super(dictType);
+ public DictionaryCollection(final String dictType, final Locale locale,
+ final Collection<Dictionary> dictionaries) {
+ super(dictType, locale);
mDictionaries = new CopyOnWriteArrayList<>(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
@Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData,
+ final NgramContext ngramContext, final long proximityInfoHandle,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int sessionId, final float[] inOutLanguageWeight) {
+ final int sessionId, final float weightForLocale,
+ final float[] inOutWeightOfLangModelVsSpatialModel) {
final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries;
if (dictionaries.isEmpty()) return null;
// To avoid creating unnecessary objects, we get the list out of the first
// dictionary and add the rest to it if not null, hence the get(0)
- ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
- prevWordsInfo, proximityInfo, settingsValuesForSuggestion, sessionId,
- inOutLanguageWeight);
+ ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composedData,
+ ngramContext, proximityInfoHandle, settingsValuesForSuggestion, sessionId,
+ weightForLocale, inOutWeightOfLangModelVsSpatialModel);
if (null == suggestions) suggestions = new ArrayList<>();
final int length = dictionaries.size();
for (int i = 1; i < length; ++ i) {
- final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
- prevWordsInfo, proximityInfo, settingsValuesForSuggestion, sessionId,
- inOutLanguageWeight);
+ final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(
+ composedData, ngramContext, proximityInfoHandle, settingsValuesForSuggestion,
+ sessionId, weightForLocale, inOutWeightOfLangModelVsSpatialModel);
if (null != sugg) suggestions.addAll(sugg);
}
return suggestions;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index fd1f51dd6..d5dff10db 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -17,644 +17,160 @@
package com.android.inputmethod.latin;
import android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.PrevWordsInfo.WordInfo;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.personalization.ContextualDictionary;
-import com.android.inputmethod.latin.personalization.PersonalizationDataChunk;
-import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.common.ComposedData;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
-import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
-import com.android.inputmethod.latin.utils.DistracterFilter;
-import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary;
-import com.android.inputmethod.latin.utils.ExecutorUtils;
-import com.android.inputmethod.latin.utils.LanguageModelParam;
import com.android.inputmethod.latin.utils.SuggestionResults;
import java.io.File;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
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 DictionaryFacilitator {
- public static final String TAG = DictionaryFacilitator.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 boolean mIsUserDictEnabled = false;
- private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
- // To synchronize assigning mDictionaries to ensure closing dictionaries.
- private final Object mLock = new Object();
- private final DistracterFilter mDistracterFilter;
-
- private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS =
- new String[] {
- Dictionary.TYPE_MAIN,
- Dictionary.TYPE_USER_HISTORY,
- Dictionary.TYPE_PERSONALIZATION,
- Dictionary.TYPE_USER,
- Dictionary.TYPE_CONTACTS,
- Dictionary.TYPE_CONTEXTUAL
- };
-
- public static final Map<String, Class<? extends ExpandableBinaryDictionary>>
- DICT_TYPE_TO_CLASS = new HashMap<>();
-
- static {
- DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class);
- DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_PERSONALIZATION, PersonalizationDictionary.class);
- DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class);
- DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class);
- DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTEXTUAL, ContextualDictionary.class);
- }
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Interface that facilitates interaction with different kinds of dictionaries. Provides APIs to
+ * instantiate and select the correct dictionaries (based on language or account), update entries
+ * and fetch suggestions. Currently AndroidSpellCheckerService and LatinIME both use
+ * DictionaryFacilitator as a client for interacting with dictionaries.
+ */
+public interface DictionaryFacilitator {
- private static final String DICT_FACTORY_METHOD_NAME = "getDictionary";
- private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
- new Class[] { Context.class, Locale.class, File.class, String.class };
+ public static final String[] ALL_DICTIONARY_TYPES = new String[] {
+ Dictionary.TYPE_MAIN,
+ Dictionary.TYPE_USER_HISTORY,
+ Dictionary.TYPE_USER,
+ Dictionary.TYPE_CONTACTS};
- private static final String[] SUB_DICT_TYPES =
- Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS, 1 /* start */,
- DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length);
+ public static final String[] DYNAMIC_DICTIONARY_TYPES = new String[] {
+ Dictionary.TYPE_USER_HISTORY,
+ Dictionary.TYPE_USER,
+ Dictionary.TYPE_CONTACTS};
/**
- * Class contains dictionaries for a locale.
+ * {@link Dictionary#TYPE_USER} is deprecated, except for the spelling service.
*/
- private static class Dictionaries {
- public final Locale mLocale;
- private Dictionary mMainDict;
- public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
- new ConcurrentHashMap<>();
-
- public Dictionaries() {
- mLocale = null;
- }
-
- public Dictionaries(final Locale locale, final Dictionary mainDict,
- final Map<String, ExpandableBinaryDictionary> subDicts) {
- mLocale = locale;
- // Main dictionary can be asynchronously loaded.
- setMainDict(mainDict);
- for (final Map.Entry<String, ExpandableBinaryDictionary> entry : subDicts.entrySet()) {
- setSubDict(entry.getKey(), entry.getValue());
- }
- }
-
- private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) {
- if (dict != null) {
- mSubDictMap.put(dictType, dict);
- }
- }
-
- public void setMainDict(final Dictionary mainDict) {
- // Close old dictionary if exists. Main dictionary can be assigned multiple times.
- final Dictionary oldDict = mMainDict;
- mMainDict = mainDict;
- if (oldDict != null && mainDict != oldDict) {
- oldDict.close();
- }
- }
-
- public Dictionary getDict(final String dictType) {
- if (Dictionary.TYPE_MAIN.equals(dictType)) {
- return mMainDict;
- } else {
- return getSubDict(dictType);
- }
- }
-
- public ExpandableBinaryDictionary getSubDict(final String dictType) {
- return mSubDictMap.get(dictType);
- }
-
- public boolean hasDict(final String dictType) {
- if (Dictionary.TYPE_MAIN.equals(dictType)) {
- return mMainDict != null;
- } else {
- return mSubDictMap.containsKey(dictType);
- }
- }
-
- public void closeDict(final String dictType) {
- final Dictionary dict;
- if (Dictionary.TYPE_MAIN.equals(dictType)) {
- dict = mMainDict;
- } else {
- dict = mSubDictMap.remove(dictType);
- }
- if (dict != null) {
- dict.close();
- }
- }
- }
+ public static final String[] DICTIONARY_TYPES_FOR_SPELLING = new String[] {
+ Dictionary.TYPE_MAIN,
+ Dictionary.TYPE_USER_HISTORY,
+ Dictionary.TYPE_USER,
+ Dictionary.TYPE_CONTACTS};
- public interface DictionaryInitializationListener {
- public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
- }
+ /**
+ * {@link Dictionary#TYPE_USER} is deprecated, except for the spelling service.
+ */
+ public static final String[] DICTIONARY_TYPES_FOR_SUGGESTIONS = new String[] {
+ Dictionary.TYPE_MAIN,
+ Dictionary.TYPE_USER_HISTORY,
+ Dictionary.TYPE_CONTACTS};
- public DictionaryFacilitator() {
- mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER;
- }
+ /**
+ * Returns whether this facilitator is exactly for this locale.
+ *
+ * @param locale the locale to test against
+ */
+ boolean isForLocale(final Locale locale);
- public DictionaryFacilitator(final DistracterFilter distracterFilter) {
- mDistracterFilter = distracterFilter;
- }
+ /**
+ * Returns whether this facilitator is exactly for this account.
+ *
+ * @param account the account to test against.
+ */
+ boolean isForAccount(@Nullable final String account);
- public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
- mDistracterFilter.updateEnabledSubtypes(enabledSubtypes);
+ interface DictionaryInitializationListener {
+ void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
}
- public Locale getLocale() {
- return mDictionaries.mLocale;
- }
+ /**
+ * Called every time {@link LatinIME} starts on a new text field.
+ * Dot not affect {@link AndroidSpellCheckerService}.
+ *
+ * WARNING: The service methods that call start/finish are very spammy.
+ */
+ void onStartInput();
- private static ExpandableBinaryDictionary getSubDict(final String dictType,
- final Context context, final Locale locale, final File dictFile,
- final String dictNamePrefix) {
- final Class<? extends ExpandableBinaryDictionary> dictClass =
- DICT_TYPE_TO_CLASS.get(dictType);
- if (dictClass == null) {
- return null;
- }
- try {
- final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME,
- DICT_FACTORY_METHOD_ARG_TYPES);
- final Object dict = factoryMethod.invoke(null /* obj */,
- new Object[] { context, locale, dictFile, dictNamePrefix });
- return (ExpandableBinaryDictionary) dict;
- } catch (final NoSuchMethodException | SecurityException | IllegalAccessException
- | IllegalArgumentException | InvocationTargetException e) {
- Log.e(TAG, "Cannot create dictionary: " + dictType, e);
- return null;
- }
- }
+ /**
+ * Called every time the {@link LatinIME} finishes with the current text field.
+ * May be followed by {@link #onStartInput} again in another text field,
+ * or it may be done for a while.
+ * Dot not affect {@link AndroidSpellCheckerService}.
+ *
+ * WARNING: The service methods that call start/finish are very spammy.
+ */
+ void onFinishInput();
- public void resetDictionaries(final Context context, final Locale newLocale,
- final boolean useContactsDict, final boolean usePersonalizedDicts,
- final boolean forceReloadMainDictionary,
- final DictionaryInitializationListener listener) {
- resetDictionariesWithDictNamePrefix(context, newLocale, useContactsDict,
- usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */);
- }
+ boolean isActive();
- public void resetDictionariesWithDictNamePrefix(final Context context, final Locale newLocale,
- final boolean useContactsDict, final boolean usePersonalizedDicts,
- final boolean forceReloadMainDictionary,
- final DictionaryInitializationListener listener,
- final String dictNamePrefix) {
- final boolean localeHasBeenChanged = !newLocale.equals(mDictionaries.mLocale);
- // We always try to have the main dictionary. Other dictionaries can be unused.
- final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary;
- // TODO: Make subDictTypesToUse configurable by resource or a static final list.
- final HashSet<String> subDictTypesToUse = new HashSet<>();
- if (useContactsDict) {
- subDictTypesToUse.add(Dictionary.TYPE_CONTACTS);
- }
- subDictTypesToUse.add(Dictionary.TYPE_USER);
- if (usePersonalizedDicts) {
- subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
- subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION);
- subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL);
- }
-
- final Dictionary newMainDict;
- if (reloadMainDictionary) {
- // The main dictionary will be asynchronously loaded.
- newMainDict = null;
- } else {
- newMainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
- }
-
- final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
- for (final String dictType : SUB_DICT_TYPES) {
- if (!subDictTypesToUse.contains(dictType)) {
- // This dictionary will not be used.
- continue;
- }
- final ExpandableBinaryDictionary dict;
- if (!localeHasBeenChanged && mDictionaries.hasDict(dictType)) {
- // Continue to use current dictionary.
- dict = mDictionaries.getSubDict(dictType);
- } else {
- // Start to use new dictionary.
- dict = getSubDict(dictType, context, newLocale, null /* dictFile */,
- dictNamePrefix);
- }
- subDicts.put(dictType, dict);
- }
-
- // Replace Dictionaries.
- final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict, subDicts);
- final Dictionaries oldDictionaries;
- synchronized (mLock) {
- oldDictionaries = mDictionaries;
- mDictionaries = newDictionaries;
- mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context);
- if (reloadMainDictionary) {
- asyncReloadMainDictionary(context, newLocale, listener);
- }
- }
- if (listener != null) {
- listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
- }
- // Clean up old dictionaries.
- if (reloadMainDictionary) {
- oldDictionaries.closeDict(Dictionary.TYPE_MAIN);
- }
- for (final String dictType : SUB_DICT_TYPES) {
- if (localeHasBeenChanged || !subDictTypesToUse.contains(dictType)) {
- oldDictionaries.closeDict(dictType);
- }
- }
- oldDictionaries.mSubDictMap.clear();
- }
+ Locale getLocale();
- 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(hasInitializedMainDictionary());
- }
- latchForWaitingLoadingMainDictionary.countDown();
- }
- });
- }
+ void resetDictionaries(
+ final Context context,
+ final Locale newLocale,
+ final boolean useContactsDict,
+ final boolean usePersonalizedDicts,
+ final boolean forceReloadMainDictionary,
+ @Nullable final String account,
+ final String dictNamePrefix,
+ @Nullable final DictionaryInitializationListener listener);
@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;
- final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
-
- for (final String dictType : dictionaryTypes) {
- if (dictType.equals(Dictionary.TYPE_MAIN)) {
- mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context, locale);
- } else {
- final File dictFile = dictionaryFiles.get(dictType);
- final ExpandableBinaryDictionary dict = getSubDict(
- dictType, context, locale, dictFile, "" /* dictNamePrefix */);
- if (additionalDictAttributes.containsKey(dictType)) {
- dict.clearAndFlushDictionaryWithAdditionalAttributes(
- additionalDictAttributes.get(dictType));
- }
- if (dict == null) {
- throw new RuntimeException("Unknown dictionary type: " + dictType);
- }
- dict.reloadDictionaryIfRequired();
- dict.waitAllTasksForTests();
- subDicts.put(dictType, dict);
- }
- }
- mDictionaries = new Dictionaries(locale, mainDictionary, subDicts);
- }
+ void resetDictionariesForTesting(
+ final Context context,
+ final Locale locale,
+ final ArrayList<String> dictionaryTypes,
+ final HashMap<String, File> dictionaryFiles,
+ final Map<String, Map<String, String>> additionalDictAttributes,
+ @Nullable final String account);
- public void closeDictionaries() {
- final Dictionaries dictionaries;
- synchronized (mLock) {
- dictionaries = mDictionaries;
- mDictionaries = new Dictionaries();
- }
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- dictionaries.closeDict(dictType);
- }
- mDistracterFilter.close();
- }
+ void closeDictionaries();
@UsedForTesting
- public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
- return mDictionaries.getSubDict(dictName);
- }
+ ExpandableBinaryDictionary getSubDictForTesting(final String dictName);
- // The main dictionary could have been loaded asynchronously. Don't cache the return value
- // of this method.
- public boolean hasInitializedMainDictionary() {
- final Dictionary mainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
- return mainDict != null && mainDict.isInitialized();
- }
+ // The main dictionaries are loaded asynchronously. Don't cache the return value
+ // of these methods.
+ boolean hasAtLeastOneInitializedMainDictionary();
- public boolean hasPersonalizationDictionary() {
- return mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION);
- }
+ boolean hasAtLeastOneUninitializedMainDictionary();
- public void flushPersonalizationDictionary() {
- final ExpandableBinaryDictionary personalizationDict =
- mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
- if (personalizationDict != null) {
- personalizationDict.asyncFlushBinaryDictionary();
- }
- }
-
- public void waitForLoadingMainDictionary(final long timeout, final TimeUnit unit)
- throws InterruptedException {
- mLatchForWaitingLoadingMainDictionary.await(timeout, unit);
- }
+ void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
+ throws InterruptedException;
@UsedForTesting
- public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
- throws InterruptedException {
- waitForLoadingMainDictionary(timeout, unit);
- final Map<String, ExpandableBinaryDictionary> dictMap = mDictionaries.mSubDictMap;
- for (final ExpandableBinaryDictionary dict : dictMap.values()) {
- dict.waitAllTasksForTests();
- }
- }
-
- public boolean isUserDictionaryEnabled() {
- return mIsUserDictEnabled;
- }
+ void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
+ throws InterruptedException;
- public void addWordToUserDictionary(final Context context, final String word) {
- final Locale locale = getLocale();
- if (locale == null) {
- return;
- }
- UserBinaryDictionary.addWordToUserDictionary(context, locale, word);
- }
+ void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
+ @Nonnull final NgramContext ngramContext, final long timeStampInSeconds,
+ final boolean blockPotentiallyOffensive);
- public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
- final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds,
- final boolean blockPotentiallyOffensive) {
- final Dictionaries dictionaries = mDictionaries;
- final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
- PrevWordsInfo prevWordsInfoForCurrentWord = prevWordsInfo;
- for (int i = 0; i < words.length; i++) {
- final String currentWord = words[i];
- final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
- addWordToUserHistory(dictionaries, prevWordsInfoForCurrentWord, currentWord,
- wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive);
- prevWordsInfoForCurrentWord =
- prevWordsInfoForCurrentWord.getNextPrevWordsInfo(new WordInfo(currentWord));
- }
- }
-
- private void addWordToUserHistory(final Dictionaries dictionaries,
- final PrevWordsInfo prevWordsInfo, final String word, final boolean wasAutoCapitalized,
- final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
- final ExpandableBinaryDictionary userHistoryDictionary =
- dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
- if (userHistoryDictionary == null) {
- return;
- }
- final int maxFreq = getFrequency(word);
- if (maxFreq == 0 && blockPotentiallyOffensive) {
- return;
- }
- final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
- final String secondWord;
- if (wasAutoCapitalized) {
- if (isValidWord(word, false /* ignoreCase */)
- && !isValidWord(lowerCasedWord, 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 = word;
- } 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 = lowerCasedWord;
- }
- } 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.hasDict(Dictionary.TYPE_MAIN) ?
- dictionaries.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
- 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 = lowerCasedWord;
- } else {
- secondWord = word;
- }
- }
- // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
- // We don't add words with 0-frequency (assuming they would be profanity etc.).
- final boolean isValid = maxFreq > 0;
- UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWordsInfo, secondWord,
- isValid, timeStampInSeconds,
- new DistracterFilterCheckingIsInDictionary(
- mDistracterFilter, userHistoryDictionary));
- }
-
- private void removeWord(final String dictName, final String word) {
- final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName);
- if (dictionary != null) {
- dictionary.removeUnigramEntryDynamically(word);
- }
- }
-
- public void removeWordFromPersonalizedDicts(final String word) {
- removeWord(Dictionary.TYPE_USER_HISTORY, word);
- removeWord(Dictionary.TYPE_PERSONALIZATION, word);
- removeWord(Dictionary.TYPE_CONTEXTUAL, word);
- }
+ void unlearnFromUserHistory(final String word,
+ @Nonnull final NgramContext ngramContext, final long timeStampInSeconds,
+ final int eventType);
// TODO: Revise the way to fusion suggestion results.
- public SuggestionResults getSuggestionResults(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
- final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) {
- final Dictionaries dictionaries = mDictionaries;
- final SuggestionResults suggestionResults = new SuggestionResults(
- dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS,
- prevWordsInfo.mPrevWordsInfo[0].mIsBeginningOfSentence);
- final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT };
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaries.getDict(dictType);
- if (null == dictionary) continue;
- final ArrayList<SuggestedWordInfo> dictionarySuggestions =
- dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
- settingsValuesForSuggestion, sessionId, languageWeight);
- if (null == dictionarySuggestions) continue;
- suggestionResults.addAll(dictionarySuggestions);
- if (null != suggestionResults.mRawSuggestions) {
- suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
- }
- }
- return suggestionResults;
- }
-
- 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);
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaries.getDict(dictType);
- // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
- // would be immutable once it's finished initializing, but concretely a null test is
- // probably good enough for the time being.
- if (null == dictionary) continue;
- if (dictionary.isValidWord(word)
- || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
- return true;
- }
- }
- return false;
- }
-
- private int getFrequencyInternal(final String word,
- final boolean isGettingMaxFrequencyOfExactMatches) {
- if (TextUtils.isEmpty(word)) {
- return Dictionary.NOT_A_PROBABILITY;
- }
- int maxFreq = Dictionary.NOT_A_PROBABILITY;
- final Dictionaries dictionaries = mDictionaries;
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaries.getDict(dictType);
- if (dictionary == null) continue;
- final int tempFreq;
- if (isGettingMaxFrequencyOfExactMatches) {
- tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
- } else {
- tempFreq = dictionary.getFrequency(word);
- }
- if (tempFreq >= maxFreq) {
- maxFreq = tempFreq;
- }
- }
- return maxFreq;
- }
+ @Nonnull SuggestionResults getSuggestionResults(final ComposedData composedData,
+ final NgramContext ngramContext, @Nonnull final Keyboard keyboard,
+ final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId,
+ final int inputStyle);
- public int getFrequency(final String word) {
- return getFrequencyInternal(word, false /* isGettingMaxFrequencyOfExactMatches */);
- }
+ boolean isValidSpellingWord(final String word);
- public int getMaxFrequencyOfExactMatches(final String word) {
- return getFrequencyInternal(word, true /* isGettingMaxFrequencyOfExactMatches */);
- }
+ boolean isValidSuggestionWord(final String word);
- private void clearSubDictionary(final String dictName) {
- final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName);
- if (dictionary != null) {
- dictionary.clear();
- }
- }
+ void clearUserHistoryDictionary(final Context context);
- public void clearUserHistoryDictionary() {
- clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
- }
+ String dump(final Context context);
- // This method gets called only when the IME receives a notification to remove the
- // personalization dictionary.
- public void clearPersonalizationDictionary() {
- clearSubDictionary(Dictionary.TYPE_PERSONALIZATION);
- }
+ void dumpDictionaryForDebug(final String dictName);
- public void clearContextualDictionary() {
- clearSubDictionary(Dictionary.TYPE_CONTEXTUAL);
- }
-
- public void addEntriesToPersonalizationDictionary(
- final PersonalizationDataChunk personalizationDataChunk,
- final SpacingAndPunctuations spacingAndPunctuations,
- final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
- final ExpandableBinaryDictionary personalizationDict =
- mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
- if (personalizationDict == null) {
- if (callback != null) {
- callback.onFinished();
- }
- return;
- }
- final ArrayList<LanguageModelParam> languageModelParams =
- LanguageModelParam.createLanguageModelParamsFrom(
- personalizationDataChunk.mTokens,
- personalizationDataChunk.mTimestampInSeconds,
- this /* dictionaryFacilitator */, spacingAndPunctuations,
- new DistracterFilterCheckingIsInDictionary(
- mDistracterFilter, personalizationDict));
- if (languageModelParams == null || languageModelParams.isEmpty()) {
- if (callback != null) {
- callback.onFinished();
- }
- return;
- }
- personalizationDict.addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
- }
-
- public void addPhraseToContextualDictionary(final String[] phrase, final int probability,
- final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) {
- final ExpandableBinaryDictionary contextualDict =
- mDictionaries.getSubDict(Dictionary.TYPE_CONTEXTUAL);
- if (contextualDict == null) {
- return;
- }
- PrevWordsInfo prevWordsInfo = PrevWordsInfo.BEGINNING_OF_SENTENCE;
- for (int i = 0; i < phrase.length; i++) {
- final String[] subPhrase = Arrays.copyOfRange(phrase, i /* start */, phrase.length);
- final String subPhraseStr = TextUtils.join(Constants.WORD_SEPARATOR, subPhrase);
- contextualDict.addUnigramEntryWithCheckingDistracter(
- subPhraseStr, probability, null /* shortcutTarget */,
- Dictionary.NOT_A_PROBABILITY /* shortcutFreq */,
- false /* isNotAWord */, false /* isBlacklisted */,
- BinaryDictionary.NOT_A_VALID_TIMESTAMP,
- DistracterFilter.EMPTY_DISTRACTER_FILTER);
- contextualDict.addNgramEntry(prevWordsInfo, subPhraseStr,
- bigramProbabilityForPhrases, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
-
- if (i < phrase.length - 1) {
- contextualDict.addUnigramEntryWithCheckingDistracter(
- phrase[i], probability, null /* shortcutTarget */,
- Dictionary.NOT_A_PROBABILITY /* shortcutFreq */,
- false /* isNotAWord */, false /* isBlacklisted */,
- BinaryDictionary.NOT_A_VALID_TIMESTAMP,
- DistracterFilter.EMPTY_DISTRACTER_FILTER);
- contextualDict.addNgramEntry(prevWordsInfo, phrase[i],
- bigramProbabilityForWords, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
- }
- prevWordsInfo =
- prevWordsInfo.getNextPrevWordsInfo(new PrevWordsInfo.WordInfo(phrase[i]));
- }
- }
-
- public void dumpDictionaryForDebug(final String dictName) {
- final ExpandableBinaryDictionary dictToDump = mDictionaries.getSubDict(dictName);
- if (dictToDump == null) {
- Log.e(TAG, "Cannot dump " + dictName + ". "
- + "The dictionary is not being used for suggestion or cannot be dumped.");
- return;
- }
- dictToDump.dumpAllWordsForDebug();
- }
+ @Nonnull List<DictionaryStats> getDictionaryStats(final Context context);
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
new file mode 100644
index 000000000..9ce92da9e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
@@ -0,0 +1,661 @@
+/*
+7 * 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.Keyboard;
+import com.android.inputmethod.latin.NgramContext.WordInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.common.ComposedData;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
+import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
+import com.android.inputmethod.latin.utils.ExecutorUtils;
+import com.android.inputmethod.latin.utils.SuggestionResults;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Facilitates interaction with different kinds of dictionaries. Provides APIs
+ * to instantiate and select the correct dictionaries (based on language or account),
+ * update entries and fetch suggestions.
+ *
+ * Currently AndroidSpellCheckerService and LatinIME both use DictionaryFacilitator as
+ * a client for interacting with dictionaries.
+ */
+public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
+ // TODO: Consolidate dictionaries in native code.
+ public static final String TAG = DictionaryFacilitatorImpl.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 DictionaryGroup mDictionaryGroup = new DictionaryGroup();
+ private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
+ // To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
+ private final Object mLock = new Object();
+
+ public static final Map<String, Class<? extends ExpandableBinaryDictionary>>
+ DICT_TYPE_TO_CLASS = new HashMap<>();
+
+ static {
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class);
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class);
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class);
+ }
+
+ private static final String DICT_FACTORY_METHOD_NAME = "getDictionary";
+ private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
+ new Class[] { Context.class, Locale.class, File.class, String.class, String.class };
+
+ @Override
+ public boolean isForLocale(final Locale locale) {
+ return locale != null && locale.equals(mDictionaryGroup.mLocale);
+ }
+
+ /**
+ * Returns whether this facilitator is exactly for this account.
+ *
+ * @param account the account to test against.
+ */
+ public boolean isForAccount(@Nullable final String account) {
+ return TextUtils.equals(mDictionaryGroup.mAccount, account);
+ }
+
+ /**
+ * A group of dictionaries that work together for a single language.
+ */
+ private static class DictionaryGroup {
+ // TODO: Add null analysis annotations.
+ // TODO: Run evaluation to determine a reasonable value for these constants. The current
+ // values are ad-hoc and chosen without any particular care or methodology.
+ public static final float WEIGHT_FOR_MOST_PROBABLE_LANGUAGE = 1.0f;
+ public static final float WEIGHT_FOR_GESTURING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.95f;
+ public static final float WEIGHT_FOR_TYPING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.6f;
+
+ /**
+ * The locale associated with the dictionary group.
+ */
+ @Nullable public final Locale mLocale;
+
+ /**
+ * The user account associated with the dictionary group.
+ */
+ @Nullable public final String mAccount;
+
+ @Nullable private Dictionary mMainDict;
+ // Confidence that the most probable language is actually the language the user is
+ // typing in. For now, this is simply the number of times a word from this language
+ // has been committed in a row.
+ private int mConfidence = 0;
+
+ public float mWeightForTypingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
+ public float mWeightForGesturingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
+ public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
+ new ConcurrentHashMap<>();
+
+ public DictionaryGroup() {
+ this(null /* locale */, null /* mainDict */, null /* account */,
+ Collections.<String, ExpandableBinaryDictionary>emptyMap() /* subDicts */);
+ }
+
+ public DictionaryGroup(@Nullable final Locale locale,
+ @Nullable final Dictionary mainDict,
+ @Nullable final String account,
+ final Map<String, ExpandableBinaryDictionary> subDicts) {
+ mLocale = locale;
+ mAccount = account;
+ // The main dictionary can be asynchronously loaded.
+ setMainDict(mainDict);
+ for (final Map.Entry<String, ExpandableBinaryDictionary> entry : subDicts.entrySet()) {
+ setSubDict(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) {
+ if (dict != null) {
+ mSubDictMap.put(dictType, dict);
+ }
+ }
+
+ public void setMainDict(final Dictionary mainDict) {
+ // Close old dictionary if exists. Main dictionary can be assigned multiple times.
+ final Dictionary oldDict = mMainDict;
+ mMainDict = mainDict;
+ if (oldDict != null && mainDict != oldDict) {
+ oldDict.close();
+ }
+ }
+
+ public Dictionary getDict(final String dictType) {
+ if (Dictionary.TYPE_MAIN.equals(dictType)) {
+ return mMainDict;
+ }
+ return getSubDict(dictType);
+ }
+
+ public ExpandableBinaryDictionary getSubDict(final String dictType) {
+ return mSubDictMap.get(dictType);
+ }
+
+ public boolean hasDict(final String dictType, @Nullable final String account) {
+ if (Dictionary.TYPE_MAIN.equals(dictType)) {
+ return mMainDict != null;
+ }
+ if (Dictionary.TYPE_USER_HISTORY.equals(dictType) &&
+ !TextUtils.equals(account, mAccount)) {
+ // If the dictionary type is user history, & if the account doesn't match,
+ // return immediately. If the account matches, continue looking it up in the
+ // sub dictionary map.
+ return false;
+ }
+ return mSubDictMap.containsKey(dictType);
+ }
+
+ public void closeDict(final String dictType) {
+ final Dictionary dict;
+ if (Dictionary.TYPE_MAIN.equals(dictType)) {
+ dict = mMainDict;
+ } else {
+ dict = mSubDictMap.remove(dictType);
+ }
+ if (dict != null) {
+ dict.close();
+ }
+ }
+ }
+
+ public DictionaryFacilitatorImpl() {
+ }
+
+ @Override
+ public void onStartInput() {
+ }
+
+ @Override
+ public void onFinishInput() {
+ }
+
+ @Override
+ public boolean isActive() {
+ return mDictionaryGroup.mLocale != null;
+ }
+
+ @Override
+ public Locale getLocale() {
+ return mDictionaryGroup.mLocale;
+ }
+
+ @Nullable
+ private static ExpandableBinaryDictionary getSubDict(final String dictType,
+ final Context context, final Locale locale, final File dictFile,
+ final String dictNamePrefix, @Nullable final String account) {
+ final Class<? extends ExpandableBinaryDictionary> dictClass =
+ DICT_TYPE_TO_CLASS.get(dictType);
+ if (dictClass == null) {
+ return null;
+ }
+ try {
+ final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME,
+ DICT_FACTORY_METHOD_ARG_TYPES);
+ final Object dict = factoryMethod.invoke(null /* obj */,
+ new Object[] { context, locale, dictFile, dictNamePrefix, account });
+ return (ExpandableBinaryDictionary) dict;
+ } catch (final NoSuchMethodException | SecurityException | IllegalAccessException
+ | IllegalArgumentException | InvocationTargetException e) {
+ Log.e(TAG, "Cannot create dictionary: " + dictType, e);
+ return null;
+ }
+ }
+
+ @Nullable
+ static DictionaryGroup findDictionaryGroupWithLocale(final DictionaryGroup dictionaryGroup,
+ final Locale locale) {
+ return locale.equals(dictionaryGroup.mLocale) ? dictionaryGroup : null;
+ }
+
+ @Override
+ public void resetDictionaries(
+ final Context context,
+ final Locale newLocale,
+ final boolean useContactsDict,
+ final boolean usePersonalizedDicts,
+ final boolean forceReloadMainDictionary,
+ @Nullable final String account,
+ final String dictNamePrefix,
+ @Nullable final DictionaryInitializationListener listener) {
+ final HashMap<Locale, ArrayList<String>> existingDictionariesToCleanup = new HashMap<>();
+ // TODO: Make subDictTypesToUse configurable by resource or a static final list.
+ final HashSet<String> subDictTypesToUse = new HashSet<>();
+ subDictTypesToUse.add(Dictionary.TYPE_USER);
+ if (useContactsDict) {
+ subDictTypesToUse.add(Dictionary.TYPE_CONTACTS);
+ }
+ if (usePersonalizedDicts) {
+ subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
+ }
+
+ // Gather all dictionaries. We'll remove them from the list to clean up later.
+ final ArrayList<String> dictTypeForLocale = new ArrayList<>();
+ existingDictionariesToCleanup.put(newLocale, dictTypeForLocale);
+ final DictionaryGroup currentDictionaryGroupForLocale =
+ findDictionaryGroupWithLocale(mDictionaryGroup, newLocale);
+ if (currentDictionaryGroupForLocale != null) {
+ for (final String dictType : DYNAMIC_DICTIONARY_TYPES) {
+ if (currentDictionaryGroupForLocale.hasDict(dictType, account)) {
+ dictTypeForLocale.add(dictType);
+ }
+ }
+ if (currentDictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
+ dictTypeForLocale.add(Dictionary.TYPE_MAIN);
+ }
+ }
+
+ final DictionaryGroup dictionaryGroupForLocale =
+ findDictionaryGroupWithLocale(mDictionaryGroup, newLocale);
+ final ArrayList<String> dictTypesToCleanupForLocale =
+ existingDictionariesToCleanup.get(newLocale);
+ final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale);
+
+ final Dictionary mainDict;
+ if (forceReloadMainDictionary || noExistingDictsForThisLocale
+ || !dictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
+ mainDict = null;
+ } else {
+ mainDict = dictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN);
+ dictTypesToCleanupForLocale.remove(Dictionary.TYPE_MAIN);
+ }
+
+ final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
+ for (final String subDictType : subDictTypesToUse) {
+ final ExpandableBinaryDictionary subDict;
+ if (noExistingDictsForThisLocale
+ || !dictionaryGroupForLocale.hasDict(subDictType, account)) {
+ // Create a new dictionary.
+ subDict = getSubDict(subDictType, context, newLocale, null /* dictFile */,
+ dictNamePrefix, account);
+ } else {
+ // Reuse the existing dictionary, and don't close it at the end
+ subDict = dictionaryGroupForLocale.getSubDict(subDictType);
+ dictTypesToCleanupForLocale.remove(subDictType);
+ }
+ subDicts.put(subDictType, subDict);
+ }
+ DictionaryGroup newDictionaryGroup =
+ new DictionaryGroup(newLocale, mainDict, account, subDicts);
+
+ // Replace Dictionaries.
+ final DictionaryGroup oldDictionaryGroup;
+ synchronized (mLock) {
+ oldDictionaryGroup = mDictionaryGroup;
+ mDictionaryGroup = newDictionaryGroup;
+ if (hasAtLeastOneUninitializedMainDictionary()) {
+ asyncReloadUninitializedMainDictionaries(context, newLocale, listener);
+ }
+ }
+ if (listener != null) {
+ listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
+ }
+
+ // Clean up old dictionaries.
+ for (final Locale localeToCleanUp : existingDictionariesToCleanup.keySet()) {
+ final ArrayList<String> dictTypesToCleanUp =
+ existingDictionariesToCleanup.get(localeToCleanUp);
+ final DictionaryGroup dictionarySetToCleanup =
+ findDictionaryGroupWithLocale(oldDictionaryGroup, localeToCleanUp);
+ for (final String dictType : dictTypesToCleanUp) {
+ dictionarySetToCleanup.closeDict(dictType);
+ }
+ }
+ }
+
+ private void asyncReloadUninitializedMainDictionaries(final Context context,
+ final Locale locale, final DictionaryInitializationListener listener) {
+ final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
+ mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary;
+ ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
+ @Override
+ public void run() {
+ doReloadUninitializedMainDictionaries(
+ context, locale, listener, latchForWaitingLoadingMainDictionary);
+ }
+ });
+ }
+
+ void doReloadUninitializedMainDictionaries(final Context context, final Locale locale,
+ final DictionaryInitializationListener listener,
+ final CountDownLatch latchForWaitingLoadingMainDictionary) {
+ final DictionaryGroup dictionaryGroup =
+ findDictionaryGroupWithLocale(mDictionaryGroup, locale);
+ if (null == dictionaryGroup) {
+ // This should never happen, but better safe than crashy
+ Log.w(TAG, "Expected a dictionary group for " + locale + " but none found");
+ return;
+ }
+ final Dictionary mainDict =
+ DictionaryFactory.createMainDictionaryFromManager(context, locale);
+ synchronized (mLock) {
+ if (locale.equals(dictionaryGroup.mLocale)) {
+ dictionaryGroup.setMainDict(mainDict);
+ } else {
+ // Dictionary facilitator has been reset for another locale.
+ mainDict.close();
+ }
+ }
+ if (listener != null) {
+ listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
+ }
+ 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,
+ @Nullable final String account) {
+ Dictionary mainDictionary = null;
+ final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
+
+ for (final String dictType : dictionaryTypes) {
+ if (dictType.equals(Dictionary.TYPE_MAIN)) {
+ mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context,
+ locale);
+ } else {
+ final File dictFile = dictionaryFiles.get(dictType);
+ final ExpandableBinaryDictionary dict = getSubDict(
+ dictType, context, locale, dictFile, "" /* dictNamePrefix */, account);
+ if (additionalDictAttributes.containsKey(dictType)) {
+ dict.clearAndFlushDictionaryWithAdditionalAttributes(
+ additionalDictAttributes.get(dictType));
+ }
+ if (dict == null) {
+ throw new RuntimeException("Unknown dictionary type: " + dictType);
+ }
+ dict.reloadDictionaryIfRequired();
+ dict.waitAllTasksForTests();
+ subDicts.put(dictType, dict);
+ }
+ }
+ mDictionaryGroup = new DictionaryGroup(locale, mainDictionary, account, subDicts);
+ }
+
+ public void closeDictionaries() {
+ final DictionaryGroup dictionaryGroupToClose;
+ synchronized (mLock) {
+ dictionaryGroupToClose = mDictionaryGroup;
+ mDictionaryGroup = new DictionaryGroup();
+ }
+ for (final String dictType : ALL_DICTIONARY_TYPES) {
+ dictionaryGroupToClose.closeDict(dictType);
+ }
+ }
+
+ @UsedForTesting
+ public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
+ return mDictionaryGroup.getSubDict(dictName);
+ }
+
+ // The main dictionaries are loaded asynchronously. Don't cache the return value
+ // of these methods.
+ public boolean hasAtLeastOneInitializedMainDictionary() {
+ final Dictionary mainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN);
+ if (mainDict != null && mainDict.isInitialized()) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean hasAtLeastOneUninitializedMainDictionary() {
+ final Dictionary mainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN);
+ if (mainDict == null || !mainDict.isInitialized()) {
+ return true;
+ }
+ return false;
+ }
+
+ public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
+ throws InterruptedException {
+ mLatchForWaitingLoadingMainDictionaries.await(timeout, unit);
+ }
+
+ @UsedForTesting
+ public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
+ throws InterruptedException {
+ waitForLoadingMainDictionaries(timeout, unit);
+ for (final ExpandableBinaryDictionary dict : mDictionaryGroup.mSubDictMap.values()) {
+ dict.waitAllTasksForTests();
+ }
+ }
+
+ public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
+ @Nonnull final NgramContext ngramContext, final long timeStampInSeconds,
+ final boolean blockPotentiallyOffensive) {
+ final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
+ NgramContext ngramContextForCurrentWord = ngramContext;
+ for (int i = 0; i < words.length; i++) {
+ final String currentWord = words[i];
+ final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
+ addWordToUserHistory(mDictionaryGroup, ngramContextForCurrentWord, currentWord,
+ wasCurrentWordAutoCapitalized, (int) timeStampInSeconds,
+ blockPotentiallyOffensive);
+ ngramContextForCurrentWord =
+ ngramContextForCurrentWord.getNextNgramContext(new WordInfo(currentWord));
+ }
+ }
+
+ private void addWordToUserHistory(final DictionaryGroup dictionaryGroup,
+ final NgramContext ngramContext, final String word, final boolean wasAutoCapitalized,
+ final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
+ final ExpandableBinaryDictionary userHistoryDictionary =
+ dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY);
+ if (userHistoryDictionary == null || !isForLocale(userHistoryDictionary.mLocale)) {
+ return;
+ }
+ final int maxFreq = getFrequency(word);
+ if (maxFreq == 0 && blockPotentiallyOffensive) {
+ return;
+ }
+ final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
+ final String secondWord;
+ if (wasAutoCapitalized) {
+ if (isValidSuggestionWord(word) && !isValidSuggestionWord(lowerCasedWord)) {
+ // 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 = word;
+ } 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 = lowerCasedWord;
+ }
+ } 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 = dictionaryGroup.hasDict(Dictionary.TYPE_MAIN,
+ null /* account */) ?
+ dictionaryGroup.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
+ 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 = lowerCasedWord;
+ } else {
+ secondWord = word;
+ }
+ }
+ // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
+ // We don't add words with 0-frequency (assuming they would be profanity etc.).
+ final boolean isValid = maxFreq > 0;
+ UserHistoryDictionary.addToDictionary(userHistoryDictionary, ngramContext, secondWord,
+ isValid, timeStampInSeconds);
+ }
+
+ private void removeWord(final String dictName, final String word) {
+ final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName);
+ if (dictionary != null) {
+ dictionary.removeUnigramEntryDynamically(word);
+ }
+ }
+
+ @Override
+ public void unlearnFromUserHistory(final String word,
+ @Nonnull final NgramContext ngramContext, final long timeStampInSeconds,
+ final int eventType) {
+ // TODO: Decide whether or not to remove the word on EVENT_BACKSPACE.
+ if (eventType != Constants.EVENT_BACKSPACE) {
+ removeWord(Dictionary.TYPE_USER_HISTORY, word);
+ }
+ }
+
+ // TODO: Revise the way to fusion suggestion results.
+ @Override
+ @Nonnull public SuggestionResults getSuggestionResults(ComposedData composedData,
+ NgramContext ngramContext, @Nonnull final Keyboard keyboard,
+ SettingsValuesForSuggestion settingsValuesForSuggestion, int sessionId,
+ int inputStyle) {
+ long proximityInfoHandle = keyboard.getProximityInfo().getNativeProximityInfo();
+ final SuggestionResults suggestionResults = new SuggestionResults(
+ SuggestedWords.MAX_SUGGESTIONS, ngramContext.isBeginningOfSentenceContext(),
+ false /* firstSuggestionExceedsConfidenceThreshold */);
+ final float[] weightOfLangModelVsSpatialModel =
+ new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL };
+ for (final String dictType : DICTIONARY_TYPES_FOR_SUGGESTIONS) {
+ final Dictionary dictionary = mDictionaryGroup.getDict(dictType);
+ if (null == dictionary) continue;
+ final float weightForLocale = composedData.mIsBatchMode
+ ? mDictionaryGroup.mWeightForGesturingInLocale
+ : mDictionaryGroup.mWeightForTypingInLocale;
+ final ArrayList<SuggestedWordInfo> dictionarySuggestions =
+ dictionary.getSuggestions(composedData, ngramContext,
+ proximityInfoHandle, settingsValuesForSuggestion, sessionId,
+ weightForLocale, weightOfLangModelVsSpatialModel);
+ if (null == dictionarySuggestions) continue;
+ suggestionResults.addAll(dictionarySuggestions);
+ if (null != suggestionResults.mRawSuggestions) {
+ suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
+ }
+ }
+ return suggestionResults;
+ }
+
+ public boolean isValidSpellingWord(final String word) {
+ return isValidWord(word, DICTIONARY_TYPES_FOR_SPELLING);
+ }
+
+ public boolean isValidSuggestionWord(final String word) {
+ return isValidWord(word, DICTIONARY_TYPES_FOR_SUGGESTIONS);
+ }
+
+ private boolean isValidWord(final String word, final String[] dictionariesToCheck) {
+ if (TextUtils.isEmpty(word)) {
+ return false;
+ }
+ if (mDictionaryGroup.mLocale == null) {
+ return false;
+ }
+ for (final String dictType : dictionariesToCheck) {
+ final Dictionary dictionary = mDictionaryGroup.getDict(dictType);
+ // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
+ // would be immutable once it's finished initializing, but concretely a null test is
+ // probably good enough for the time being.
+ if (null == dictionary) continue;
+ if (dictionary.isValidWord(word)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int getFrequency(final String word) {
+ if (TextUtils.isEmpty(word)) {
+ return Dictionary.NOT_A_PROBABILITY;
+ }
+ int maxFreq = Dictionary.NOT_A_PROBABILITY;
+ for (final String dictType : ALL_DICTIONARY_TYPES) {
+ final Dictionary dictionary = mDictionaryGroup.getDict(dictType);
+ if (dictionary == null) continue;
+ final int tempFreq = dictionary.getFrequency(word);
+ if (tempFreq >= maxFreq) {
+ maxFreq = tempFreq;
+ }
+ }
+ return maxFreq;
+ }
+
+ private void clearSubDictionary(final String dictName) {
+ final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName);
+ if (dictionary != null) {
+ dictionary.clear();
+ }
+ }
+
+ @Override
+ public void clearUserHistoryDictionary(final Context context) {
+ clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
+ }
+
+ @Override
+ public void dumpDictionaryForDebug(final String dictName) {
+ final ExpandableBinaryDictionary dictToDump = mDictionaryGroup.getSubDict(dictName);
+ if (dictToDump == null) {
+ Log.e(TAG, "Cannot dump " + dictName + ". "
+ + "The dictionary is not being used for suggestion or cannot be dumped.");
+ return;
+ }
+ dictToDump.dumpAllWordsForDebug();
+ }
+
+ @Override
+ @Nonnull public List<DictionaryStats> getDictionaryStats(final Context context) {
+ final ArrayList<DictionaryStats> statsOfEnabledSubDicts = new ArrayList<>();
+ for (final String dictType : DYNAMIC_DICTIONARY_TYPES) {
+ final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictType);
+ if (dictionary == null) continue;
+ statsOfEnabledSubDicts.add(dictionary.getDictionaryStats());
+ }
+ return statsOfEnabledSubDicts;
+ }
+
+ @Override
+ public String dump(final Context context) {
+ return "";
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
new file mode 100644
index 000000000..cbaf6ea4e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * Cache for dictionary facilitators of multiple locales.
+ * This class automatically creates and releases up to 3 facilitator instances using LRU policy.
+ */
+public class DictionaryFacilitatorLruCache {
+ private static final String TAG = "DictionaryFacilitatorLruCache";
+ private static final int WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS = 1000;
+ private static final int MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT = 5;
+
+ private final Context mContext;
+ private final String mDictionaryNamePrefix;
+ private final Object mLock = new Object();
+ private final DictionaryFacilitator mDictionaryFacilitator;
+ private boolean mUseContactsDictionary;
+ private Locale mLocale;
+
+ public DictionaryFacilitatorLruCache(final Context context, final String dictionaryNamePrefix) {
+ mContext = context;
+ mDictionaryNamePrefix = dictionaryNamePrefix;
+ mDictionaryFacilitator = DictionaryFacilitatorProvider.getDictionaryFacilitator(
+ true /* isNeededForSpellChecking */);
+ }
+
+ private static void waitForLoadingMainDictionary(
+ final DictionaryFacilitator dictionaryFacilitator) {
+ for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) {
+ try {
+ dictionaryFacilitator.waitForLoadingMainDictionaries(
+ WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+ return;
+ } catch (final InterruptedException e) {
+ Log.i(TAG, "Interrupted during waiting for loading main dictionary.", e);
+ if (i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT - 1) {
+ Log.i(TAG, "Retry", e);
+ } else {
+ Log.w(TAG, "Give up retrying. Retried "
+ + MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT + " times.", e);
+ }
+ }
+ }
+ }
+
+ private void resetDictionariesForLocaleLocked() {
+ // Nothing to do if the locale is null. This would be the case before any get() calls.
+ if (mLocale != null) {
+ // Note: Given that personalized dictionaries are not used here; we can pass null account.
+ mDictionaryFacilitator.resetDictionaries(mContext, mLocale,
+ mUseContactsDictionary, false /* usePersonalizedDicts */,
+ false /* forceReloadMainDictionary */, null /* account */,
+ mDictionaryNamePrefix, null /* listener */);
+ }
+ }
+
+ public void setUseContactsDictionary(final boolean useContactsDictionary) {
+ synchronized (mLock) {
+ if (mUseContactsDictionary == useContactsDictionary) {
+ // The value has not been changed.
+ return;
+ }
+ mUseContactsDictionary = useContactsDictionary;
+ resetDictionariesForLocaleLocked();
+ waitForLoadingMainDictionary(mDictionaryFacilitator);
+ }
+ }
+
+ public DictionaryFacilitator get(final Locale locale) {
+ synchronized (mLock) {
+ if (!mDictionaryFacilitator.isForLocale(locale)) {
+ mLocale = locale;
+ resetDictionariesForLocaleLocked();
+ }
+ waitForLoadingMainDictionary(mDictionaryFacilitator);
+ return mDictionaryFacilitator;
+ }
+ }
+
+ public void closeDictionaries() {
+ synchronized (mLock) {
+ mDictionaryFacilitator.closeDictionaries();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 59de4f82a..49608d830 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -19,10 +19,8 @@ package com.android.inputmethod.latin;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
import android.util.Log;
-import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
import java.io.File;
@@ -43,14 +41,13 @@ public final class DictionaryFactory {
* locale. If none is found, it falls back to the built-in dictionary - if any.
* @param context application context for reading resources
* @param locale the locale for which to create the dictionary
- * @param useFullEditDistance whether to use the full edit distance in suggestions
* @return an initialized instance of DictionaryCollection
*/
public static DictionaryCollection createMainDictionaryFromManager(final Context context,
- final Locale locale, final boolean useFullEditDistance) {
+ final Locale locale) {
if (null == locale) {
Log.e(TAG, "No locale defined for dictionary");
- return new DictionaryCollection(Dictionary.TYPE_MAIN,
+ return new DictionaryCollection(Dictionary.TYPE_MAIN, locale,
createReadOnlyBinaryDictionary(context, locale));
}
@@ -61,7 +58,7 @@ public final class DictionaryFactory {
for (final AssetFileAddress f : assetFileList) {
final ReadOnlyBinaryDictionary readOnlyBinaryDictionary =
new ReadOnlyBinaryDictionary(f.mFilename, f.mOffset, f.mLength,
- useFullEditDistance, locale, Dictionary.TYPE_MAIN);
+ false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
if (readOnlyBinaryDictionary.isValidDictionary()) {
dictList.add(readOnlyBinaryDictionary);
} else {
@@ -75,7 +72,7 @@ public final class DictionaryFactory {
// If the list is empty, that means we should not use any dictionary (for example, the user
// explicitly disabled the main dictionary), so the following is okay. dictList is never
// null, but if for some reason it is, DictionaryCollection handles it gracefully.
- return new DictionaryCollection(Dictionary.TYPE_MAIN, dictList);
+ return new DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList);
}
/**
@@ -83,7 +80,7 @@ public final class DictionaryFactory {
* @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) {
+ public static void killDictionary(final Context context, final AssetFileAddress f) {
if (f.pointsToPhysicalFile()) {
f.deleteUnderlyingFile();
// Warn the dictionary provider if the dictionary came from there.
@@ -101,49 +98,33 @@ public final class DictionaryFactory {
}
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);
- }
+ // 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
- * locale. If none is found, it falls back to the built-in dictionary, if any.
- * @param context application context for reading resources
- * @param locale the locale for which to create the dictionary
- * @return an initialized instance of DictionaryCollection
- */
- public static DictionaryCollection createMainDictionaryFromManager(final Context context,
- final Locale locale) {
- return createMainDictionaryFromManager(context, locale, false /* useFullEditDistance */);
- }
-
- /**
* Initializes a read-only binary dictionary from a raw resource file
* @param context application context for reading resources
* @param locale the locale to use for the resource
* @return an initialized instance of ReadOnlyBinaryDictionary
*/
- protected static ReadOnlyBinaryDictionary createReadOnlyBinaryDictionary(final Context context,
+ private static ReadOnlyBinaryDictionary createReadOnlyBinaryDictionary(final Context context,
final Locale locale) {
AssetFileDescriptor afd = null;
try {
@@ -177,36 +158,4 @@ public final class DictionaryFactory {
}
}
}
-
- /**
- * Create a dictionary from passed data. This is intended for unit tests only.
- * @param dictionaryList the list of files to read, with their offsets and lengths
- * @param useFullEditDistance whether to use the full edit distance in suggestions
- * @return the created dictionary, or null.
- */
- @UsedForTesting
- public static Dictionary createDictionaryForTest(final AssetFileAddress[] dictionaryList,
- final boolean useFullEditDistance, Locale locale) {
- final DictionaryCollection dictionaryCollection =
- new DictionaryCollection(Dictionary.TYPE_MAIN);
- for (final AssetFileAddress address : dictionaryList) {
- final ReadOnlyBinaryDictionary readOnlyBinaryDictionary = new ReadOnlyBinaryDictionary(
- address.mFilename, address.mOffset, address.mLength, useFullEditDistance,
- locale, Dictionary.TYPE_MAIN);
- dictionaryCollection.addDictionary(readOnlyBinaryDictionary);
- }
- return dictionaryCollection;
- }
-
- /**
- * Find out whether a dictionary is available for this locale.
- * @param context the context on which to check resources.
- * @param locale the locale to check for.
- * @return whether a (non-placeholder) dictionary is available or not.
- */
- public static boolean isDictionaryAvailable(Context context, Locale locale) {
- final Resources res = context.getResources();
- return 0 != DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
- res, locale);
- }
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryStats.java b/java/src/com/android/inputmethod/latin/DictionaryStats.java
new file mode 100644
index 000000000..a6b37aa8f
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryStats.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.util.Locale;
+
+public class DictionaryStats {
+ public static final int NOT_AN_ENTRY_COUNT = -1;
+
+ public final Locale mLocale;
+ public final String mDictName;
+ public final String mDictFilePath;
+ public final long mDictFileSize;
+ public final int mContentVersion;
+
+ public DictionaryStats(final Locale locale, final String dictName, final File dictFile,
+ final int contentVersion) {
+ mLocale = locale;
+ mDictName = dictName;
+ mDictFilePath = (dictFile == null) ? null : dictFile.getName();
+ mDictFileSize = (dictFile == null || !dictFile.exists()) ? 0 : dictFile.length();
+ mContentVersion = contentVersion;
+ }
+
+ public String getFileSizeString() {
+ if (mDictFileSize == 0) {
+ return "0";
+ }
+ BigDecimal bytes = new BigDecimal(mDictFileSize);
+ BigDecimal kb = bytes.divide(new BigDecimal(1024), 2, BigDecimal.ROUND_HALF_UP);
+ if (kb.longValue() == 0) {
+ return bytes.toString() + " bytes";
+ }
+ BigDecimal mb = kb.divide(new BigDecimal(1024), 2, BigDecimal.ROUND_HALF_UP);
+ if (mb.longValue() == 0) {
+ return kb.toString() + " kb";
+ }
+ return mb.toString() + " Mb";
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder(mDictName);
+ if (mDictName.equals(Dictionary.TYPE_MAIN)) {
+ builder.append(" (");
+ builder.append(mContentVersion);
+ builder.append(")");
+ }
+ builder.append(": ");
+ builder.append(mDictFilePath);
+ builder.append(" / ");
+ builder.append(getFileSizeString());
+ return builder.toString();
+ }
+
+ public static String toString(final Iterable<DictionaryStats> stats) {
+ final StringBuilder builder = new StringBuilder("LM Stats");
+ for (DictionaryStats stat : stats) {
+ builder.append("\n ");
+ builder.append(stat.toString());
+ }
+ return builder.toString();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java b/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java
new file mode 100644
index 000000000..9b271116d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java
@@ -0,0 +1,107 @@
+/*
+ * 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.util.Log;
+import android.view.KeyEvent;
+
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.latin.settings.Settings;
+
+/**
+ * A class for detecting Emoji-Alt physical key.
+ */
+final class EmojiAltPhysicalKeyDetector {
+ private static final String TAG = "EmojiAltPhysicalKeyDetector";
+
+ private final RichInputConnection mRichInputConnection;
+
+ // True if the Alt key has been used as a modifier. In this case the Alt key up isn't
+ // recognized as an emoji key.
+ private boolean mAltHasBeenUsedAsAModifier;
+
+ public EmojiAltPhysicalKeyDetector(final RichInputConnection richInputConnection) {
+ mRichInputConnection = richInputConnection;
+ }
+
+ /**
+ * Record a down key event.
+ * @param keyEvent a down key event.
+ */
+ public void onKeyDown(final KeyEvent keyEvent) {
+ if (isAltKey(keyEvent)) {
+ mAltHasBeenUsedAsAModifier = false;
+ }
+ if (containsAltModifier(keyEvent)) {
+ mAltHasBeenUsedAsAModifier = true;
+ }
+ }
+
+ /**
+ * Determine whether an up key event is a special key up or not.
+ * @param keyEvent an up key event.
+ */
+ public void onKeyUp(final KeyEvent keyEvent) {
+ if (keyEvent.isCanceled()) {
+ // This key up event was a part of key combinations and should be ignored.
+ return;
+ }
+ if (!isAltKey(keyEvent)) {
+ mAltHasBeenUsedAsAModifier |= containsAltModifier(keyEvent);
+ return;
+ }
+ if (containsAltModifier(keyEvent)) {
+ mAltHasBeenUsedAsAModifier = true;
+ return;
+ }
+ if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) {
+ return;
+ }
+ if (mAltHasBeenUsedAsAModifier) {
+ return;
+ }
+ if (!mRichInputConnection.isConnected()) {
+ Log.w(TAG, "onKeyUp() : No connection to text view");
+ return;
+ }
+ onEmojiAltKeyDetected();
+ }
+
+ private static void onEmojiAltKeyDetected() {
+ KeyboardSwitcher.getInstance().onToggleEmojiKeyboard();
+ }
+
+ private static boolean isAltKey(final KeyEvent keyEvent) {
+ final int keyCode = keyEvent.getKeyCode();
+ return keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT;
+ }
+
+ private static boolean containsAltModifier(final KeyEvent keyEvent) {
+ final int metaState = keyEvent.getMetaState();
+ // TODO: Support multiple keyboards. Take device id into account.
+ switch (keyEvent.getKeyCode()) {
+ case KeyEvent.KEYCODE_ALT_LEFT:
+ // Return true if Left-Alt is pressed with Right-Alt pressed.
+ return (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
+ case KeyEvent.KEYCODE_ALT_RIGHT:
+ // Return true if Right-Alt is pressed with Left-Alt pressed.
+ return (metaState & KeyEvent.META_ALT_LEFT_ON) != 0;
+ default:
+ return (metaState & (KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON)) != 0;
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index c11a220a4..1ef7061fb 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -20,35 +20,41 @@ import android.content.Context;
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.common.ComposedData;
+import com.android.inputmethod.latin.common.FileUtils;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.makedict.FormatSpec;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.makedict.WordProperty;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
+import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.CombinedFormatUtils;
-import com.android.inputmethod.latin.utils.DistracterFilter;
import com.android.inputmethod.latin.utils.ExecutorUtils;
-import com.android.inputmethod.latin.utils.FileUtils;
-import com.android.inputmethod.latin.utils.LanguageModelParam;
+import com.android.inputmethod.latin.utils.WordInputEventForPersonalization;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
-import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* Abstract base class for an expandable dictionary that can be created and updated dynamically
* during runtime. When updated it automatically generates a new binary dictionary to handle future
* queries in native code. This binary dictionary is written to internal storage.
+ *
+ * A class that extends this abstract class must have a static factory method named
+ * getDictionary(Context context, Locale locale, File dictFile, String dictNamePrefix)
*/
abstract public class ExpandableBinaryDictionary extends Dictionary {
private static final boolean DEBUG = false;
@@ -61,16 +67,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
- 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;
+ protected static final int MAX_WORD_LENGTH =
+ DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH;
private static final int DICTIONARY_FORMAT_VERSION = FormatSpec.VERSION4;
+ private static final WordProperty[] DEFAULT_WORD_PROPERTIES_FOR_SYNC =
+ new WordProperty[0] /* default */;
+
/** The application context. */
protected final Context mContext;
@@ -86,9 +93,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
private final String mDictName;
- /** Dictionary locale */
- private final Locale mLocale;
-
/** Dictionary file */
private final File mDictFile;
@@ -110,14 +114,14 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
protected abstract void loadInitialContentsLocked();
- private boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) {
+ static boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) {
return formatVersion == FormatSpec.VERSION4;
}
- private boolean needsToMigrateDictionary(final int formatVersion) {
+ private static boolean needsToMigrateDictionary(final int formatVersion) {
// When we bump up the dictionary format version, the old version should be added to here
// for supporting migration. Note that native code has to support reading such formats.
- return formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING;
+ return formatVersion == FormatSpec.VERSION402;
}
public boolean isValidDictionaryLocked() {
@@ -137,10 +141,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
public ExpandableBinaryDictionary(final Context context, final String dictName,
final Locale locale, final String dictType, final File dictFile) {
- super(dictType);
+ super(dictType, locale);
mDictName = dictName;
mContext = context;
- mLocale = locale;
mDictFile = getDictFile(context, dictName, dictFile);
mBinaryDictionary = null;
mIsReloading = new AtomicBoolean();
@@ -163,32 +166,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
asyncExecuteTaskWithLock(mLock.writeLock(), task);
}
- private void asyncExecuteTaskWithLock(final Lock lock, final Runnable task) {
- asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, task);
- }
-
- private void asyncPreCheckAndExecuteTaskWithWriteLock(
- final Callable<Boolean> preCheckTask, final Runnable task) {
- asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask, task);
-
- }
-
- // Execute task with lock when the result of preCheckTask is true or preCheckTask is null.
- private void asyncPreCheckAndExecuteTaskWithLock(final Lock lock,
- final Callable<Boolean> preCheckTask, final Runnable task) {
- ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
+ private static void asyncExecuteTaskWithLock(final Lock lock, final Runnable task) {
+ ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
@Override
public void run() {
- if (preCheckTask != null) {
- try {
- if (!preCheckTask.call().booleanValue()) {
- return;
- }
- } catch (final Exception e) {
- Log.e(TAG, "The pre check task throws an exception.", e);
- return;
- }
- }
lock.lock();
try {
task.run();
@@ -199,6 +180,18 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
});
}
+ @Nullable
+ BinaryDictionary getBinaryDictionary() {
+ return mBinaryDictionary;
+ }
+
+ void closeBinaryDictionary() {
+ if (mBinaryDictionary != null) {
+ mBinaryDictionary.close();
+ mBinaryDictionary = null;
+ }
+ }
+
/**
* Closes and cleans up the binary dictionary.
*/
@@ -207,10 +200,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
public void run() {
- if (mBinaryDictionary != null) {
- mBinaryDictionary.close();
- mBinaryDictionary = null;
- }
+ closeBinaryDictionary();
}
});
}
@@ -224,10 +214,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
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;
}
@@ -240,14 +226,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
});
}
- private void removeBinaryDictionaryLocked() {
- if (mBinaryDictionary != null) {
- mBinaryDictionary.close();
- }
+ void removeBinaryDictionaryLocked() {
+ closeBinaryDictionary();
if (mDictFile.exists() && !FileUtils.deleteRecursively(mDictFile)) {
Log.e(TAG, "Can't remove a file: " + mDictFile.getName());
}
- mBinaryDictionary = null;
}
private void openBinaryDictionaryLocked() {
@@ -256,7 +239,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
true /* useFullEditDistance */, mLocale, mDictType, true /* isUpdatable */);
}
- private void createOnMemoryBinaryDictionaryLocked() {
+ void createOnMemoryBinaryDictionaryLocked() {
mBinaryDictionary = new BinaryDictionary(
mDictFile.getAbsolutePath(), true /* useFullEditDistance */, mLocale, mDictType,
DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
@@ -275,11 +258,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/**
* Check whether GC is needed and run GC if required.
*/
- protected void runGCIfRequired(final boolean mindsBlockByGC) {
+ public void runGCIfRequired(final boolean mindsBlockByGC) {
asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
public void run() {
- if (mBinaryDictionary == null) {
+ if (getBinaryDictionary() == null) {
return;
}
runGCIfRequiredLocked(mindsBlockByGC);
@@ -293,40 +276,38 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
}
+ private void updateDictionaryWithWriteLock(@Nonnull final Runnable updateTask) {
+ reloadDictionaryIfRequired();
+ final Runnable task = new Runnable() {
+ @Override
+ public void run() {
+ if (getBinaryDictionary() == null) {
+ return;
+ }
+ runGCIfRequiredLocked(true /* mindsBlockByGC */);
+ updateTask.run();
+ }
+ };
+ asyncExecuteTaskWithWriteLock(task);
+ }
+
/**
* Adds unigram information of a word to the dictionary. May overwrite an existing entry.
*/
- public void addUnigramEntryWithCheckingDistracter(final String word, final int frequency,
- final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
- final boolean isBlacklisted, final int timestamp,
- final DistracterFilter distracterFilter) {
- reloadDictionaryIfRequired();
- asyncPreCheckAndExecuteTaskWithWriteLock(
- new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- return !distracterFilter.isDistracterToWordsInDictionaries(
- PrevWordsInfo.EMPTY_PREV_WORDS_INFO, word, mLocale);
- }
- },
- new Runnable() {
- @Override
- public void run() {
- if (mBinaryDictionary == null) {
- return;
- }
- runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addUnigramLocked(word, frequency, shortcutTarget, shortcutFreq,
- isNotAWord, isBlacklisted, timestamp);
- }
- });
+ public void addUnigramEntry(final String word, final int frequency,
+ final boolean isNotAWord, final boolean isPossiblyOffensive, final int timestamp) {
+ updateDictionaryWithWriteLock(new Runnable() {
+ @Override
+ public void run() {
+ addUnigramLocked(word, frequency, isNotAWord, isPossiblyOffensive, timestamp);
+ }
+ });
}
protected void addUnigramLocked(final String word, final int frequency,
- final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
- final boolean isBlacklisted, final int timestamp) {
- if (!mBinaryDictionary.addUnigramEntry(word, frequency, shortcutTarget, shortcutFreq,
- false /* isBeginningOfSentence */, isNotAWord, isBlacklisted, timestamp)) {
+ final boolean isNotAWord, final boolean isPossiblyOffensive, final int timestamp) {
+ if (!mBinaryDictionary.addUnigramEntry(word, frequency,
+ false /* isBeginningOfSentence */, isNotAWord, isPossiblyOffensive, timestamp)) {
Log.e(TAG, "Cannot add unigram entry. word: " + word);
}
}
@@ -339,11 +320,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
public void run() {
- if (mBinaryDictionary == null) {
+ final BinaryDictionary binaryDictionary = getBinaryDictionary();
+ if (binaryDictionary == null) {
return;
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- if (!mBinaryDictionary.removeUnigramEntry(word)) {
+ if (!binaryDictionary.removeUnigramEntry(word)) {
if (DEBUG) {
Log.i(TAG, "Cannot remove unigram entry: " + word);
}
@@ -355,75 +337,85 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/**
* Adds n-gram information of a word to the dictionary. May overwrite an existing entry.
*/
- public void addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
+ public void addNgramEntry(@Nonnull final NgramContext ngramContext, final String word,
final int frequency, final int timestamp) {
reloadDictionaryIfRequired();
asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
public void run() {
- if (mBinaryDictionary == null) {
+ if (getBinaryDictionary() == null) {
return;
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addNgramEntryLocked(prevWordsInfo, word, frequency, timestamp);
+ addNgramEntryLocked(ngramContext, word, frequency, timestamp);
}
});
}
- protected void addNgramEntryLocked(final PrevWordsInfo prevWordsInfo, final String word,
+ protected void addNgramEntryLocked(@Nonnull final NgramContext ngramContext, final String word,
final int frequency, final int timestamp) {
- if (!mBinaryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp)) {
+ if (!mBinaryDictionary.addNgramEntry(ngramContext, word, frequency, timestamp)) {
if (DEBUG) {
Log.i(TAG, "Cannot add n-gram entry.");
- Log.i(TAG, " PrevWordsInfo: " + prevWordsInfo + ", word: " + word);
+ Log.i(TAG, " NgramContext: " + ngramContext + ", word: " + word);
}
}
}
/**
- * Dynamically remove the n-gram entry in the dictionary.
+ * Update dictionary for the word with the ngramContext.
*/
- @UsedForTesting
- public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word) {
- reloadDictionaryIfRequired();
- asyncExecuteTaskWithWriteLock(new Runnable() {
+ public void updateEntriesForWord(@Nonnull final NgramContext ngramContext,
+ final String word, final boolean isValidWord, final int count, final int timestamp) {
+ updateDictionaryWithWriteLock(new Runnable() {
@Override
public void run() {
- if (mBinaryDictionary == null) {
+ final BinaryDictionary binaryDictionary = getBinaryDictionary();
+ if (binaryDictionary == null) {
return;
}
- runGCIfRequiredLocked(true /* mindsBlockByGC */);
- if (!mBinaryDictionary.removeNgramEntry(prevWordsInfo, word)) {
+ if (!binaryDictionary.updateEntriesForWordWithNgramContext(ngramContext, word,
+ isValidWord, count, timestamp)) {
if (DEBUG) {
- Log.i(TAG, "Cannot remove n-gram entry.");
- Log.i(TAG, " PrevWordsInfo: " + prevWordsInfo + ", word: " + word);
+ Log.e(TAG, "Cannot update counter. word: " + word
+ + " context: " + ngramContext.toString());
}
}
}
});
}
- public interface AddMultipleDictionaryEntriesCallback {
+ /**
+ * Used by Sketch.
+ * {@see https://cs.corp.google.com/#android/vendor/unbundled_google/packages/LatinIMEGoogle/tools/sketch/ime-simulator/src/com/android/inputmethod/sketch/imesimulator/ImeSimulator.java&q=updateEntriesForInputEventsCallback&l=286}
+ */
+ @UsedForTesting
+ public interface UpdateEntriesForInputEventsCallback {
public void onFinished();
}
/**
- * Dynamically add multiple entries to the dictionary.
+ * Dynamically update entries according to input events.
+ *
+ * Used by Sketch.
+ * {@see https://cs.corp.google.com/#android/vendor/unbundled_google/packages/LatinIMEGoogle/tools/sketch/ime-simulator/src/com/android/inputmethod/sketch/imesimulator/ImeSimulator.java&q=updateEntriesForInputEventsCallback&l=286}
*/
- public void addMultipleDictionaryEntriesDynamically(
- final ArrayList<LanguageModelParam> languageModelParams,
- final AddMultipleDictionaryEntriesCallback callback) {
+ @UsedForTesting
+ public void updateEntriesForInputEvents(
+ @Nonnull final ArrayList<WordInputEventForPersonalization> inputEvents,
+ final UpdateEntriesForInputEventsCallback callback) {
reloadDictionaryIfRequired();
asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
public void run() {
try {
- if (mBinaryDictionary == null) {
+ final BinaryDictionary binaryDictionary = getBinaryDictionary();
+ if (binaryDictionary == null) {
return;
}
- mBinaryDictionary.addMultipleDictionaryEntries(
- languageModelParams.toArray(
- new LanguageModelParam[languageModelParams.size()]));
+ binaryDictionary.updateEntriesForInputEvents(
+ inputEvents.toArray(
+ new WordInputEventForPersonalization[inputEvents.size()]));
} finally {
if (callback != null) {
callback.onFinished();
@@ -434,10 +426,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
@Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData,
+ final NgramContext ngramContext, final long proximityInfoHandle,
final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId,
- final float[] inOutLanguageWeight) {
+ final float weightForLocale, final float[] inOutWeightOfLangModelVsSpatialModel) {
reloadDictionaryIfRequired();
boolean lockAcquired = false;
try {
@@ -448,8 +440,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return null;
}
final ArrayList<SuggestedWordInfo> suggestions =
- mBinaryDictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
- settingsValuesForSuggestion, sessionId, inOutLanguageWeight);
+ mBinaryDictionary.getSuggestions(composedData, ngramContext,
+ proximityInfoHandle, settingsValuesForSuggestion, sessionId,
+ weightForLocale, inOutWeightOfLangModelVsSpatialModel);
if (mBinaryDictionary.isCorrupted()) {
Log.i(TAG, "Dictionary (" + mDictName +") is corrupted. "
+ "Remove and regenerate it.");
@@ -519,16 +512,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
- protected boolean isValidNgramLocked(final PrevWordsInfo prevWordsInfo, final String word) {
- if (mBinaryDictionary == null) return false;
- return mBinaryDictionary.isValidNgram(prevWordsInfo, word);
- }
-
/**
* Loads the current binary dictionary from internal storage. Assumes the dictionary file
* exists.
*/
- private void loadBinaryDictionaryLocked() {
+ void loadBinaryDictionaryLocked() {
if (DBG_STRESS_TEST) {
// Test if this class does not cause problems when it takes long time to load binary
// dictionary.
@@ -537,6 +525,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
Thread.sleep(15000);
Log.w(TAG, "End stress in loading");
} catch (InterruptedException e) {
+ Log.w("Interrupted while loading: " + mDictName, e);
}
}
final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
@@ -556,7 +545,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/**
* Create a new binary dictionary and load initial contents.
*/
- private void createNewDictionaryLocked() {
+ void createNewDictionaryLocked() {
removeBinaryDictionaryLocked();
createOnMemoryBinaryDictionaryLocked();
loadInitialContentsLocked();
@@ -572,6 +561,14 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
mNeedsToRecreate = true;
}
+ void clearNeedsToRecreate() {
+ mNeedsToRecreate = false;
+ }
+
+ boolean isNeededToRecreate() {
+ return mNeedsToRecreate;
+ }
+
/**
* Load the current binary dictionary from internal storage. If the dictionary file doesn't
* exists or needs to be regenerated, the new dictionary file will be asynchronously generated.
@@ -593,36 +590,40 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/**
* Reloads the dictionary. Access is controlled on a per dictionary file basis.
*/
- private final void asyncReloadDictionary() {
- if (mIsReloading.compareAndSet(false, true)) {
- asyncExecuteTaskWithWriteLock(new Runnable() {
- @Override
- public void run() {
- try {
- if (!mDictFile.exists() || mNeedsToRecreate) {
- // If the dictionary file does not exist or contents have been updated,
- // generate a new one.
+ private void asyncReloadDictionary() {
+ final AtomicBoolean isReloading = mIsReloading;
+ if (!isReloading.compareAndSet(false, true)) {
+ return;
+ }
+ final File dictFile = mDictFile;
+ asyncExecuteTaskWithWriteLock(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (!dictFile.exists() || isNeededToRecreate()) {
+ // If the dictionary file does not exist or contents have been updated,
+ // generate a new one.
+ createNewDictionaryLocked();
+ } else if (getBinaryDictionary() == null) {
+ // Otherwise, load the existing dictionary.
+ loadBinaryDictionaryLocked();
+ final BinaryDictionary binaryDictionary = getBinaryDictionary();
+ if (binaryDictionary != null && !(isValidDictionaryLocked()
+ // TODO: remove the check below
+ && matchesExpectedBinaryDictFormatVersionForThisType(
+ binaryDictionary.getFormatVersion()))) {
+ // Binary dictionary or its format version is not valid. Regenerate
+ // the dictionary file. createNewDictionaryLocked will remove the
+ // existing files if appropriate.
createNewDictionaryLocked();
- } else if (mBinaryDictionary == null) {
- // Otherwise, load the existing dictionary.
- loadBinaryDictionaryLocked();
- 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. createNewDictionaryLocked will remove the
- // existing files if appropriate.
- createNewDictionaryLocked();
- }
}
- mNeedsToRecreate = false;
- } finally {
- mIsReloading.set(false);
}
+ clearNeedsToRecreate();
+ } finally {
+ isReloading.set(false);
}
- });
- }
+ }
+ });
}
/**
@@ -632,22 +633,37 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
public void run() {
- if (mBinaryDictionary == null) {
+ final BinaryDictionary binaryDictionary = getBinaryDictionary();
+ if (binaryDictionary == null) {
return;
}
- if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
- mBinaryDictionary.flushWithGC();
+ if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
+ binaryDictionary.flushWithGC();
} else {
- mBinaryDictionary.flush();
+ binaryDictionary.flush();
}
}
});
}
+ public DictionaryStats getDictionaryStats() {
+ reloadDictionaryIfRequired();
+ final String dictName = mDictName;
+ final File dictFile = mDictFile;
+ final AsyncResultHolder<DictionaryStats> result = new AsyncResultHolder<>();
+ asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
+ @Override
+ public void run() {
+ result.set(new DictionaryStats(mLocale, dictName, dictFile, 0));
+ }
+ });
+ return result.get(null /* defaultValue */, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
+ }
+
@UsedForTesting
public void waitAllTasksForTests() {
final CountDownLatch countDownLatch = new CountDownLatch(1);
- ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
+ asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
public void run() {
countDownLatch.countDown();
@@ -669,31 +685,71 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
public void dumpAllWordsForDebug() {
reloadDictionaryIfRequired();
+ final String tag = TAG;
+ final String dictName = mDictName;
asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
@Override
public void run() {
- Log.d(TAG, "Dump dictionary: " + mDictName);
+ Log.d(tag, "Dump dictionary: " + dictName + " for " + mLocale);
+ final BinaryDictionary binaryDictionary = getBinaryDictionary();
+ if (binaryDictionary == null) {
+ return;
+ }
try {
- final DictionaryHeader header = mBinaryDictionary.getHeader();
- Log.d(TAG, "Format version: " + mBinaryDictionary.getFormatVersion());
- Log.d(TAG, CombinedFormatUtils.formatAttributeMap(
+ final DictionaryHeader header = binaryDictionary.getHeader();
+ Log.d(tag, "Format version: " + binaryDictionary.getFormatVersion());
+ Log.d(tag, CombinedFormatUtils.formatAttributeMap(
header.mDictionaryOptions.mAttributes));
} catch (final UnsupportedFormatException e) {
- Log.d(TAG, "Cannot fetch header information.", e);
+ Log.d(tag, "Cannot fetch header information.", e);
}
int token = 0;
do {
final BinaryDictionary.GetNextWordPropertyResult result =
- mBinaryDictionary.getNextWordProperty(token);
+ binaryDictionary.getNextWordProperty(token);
final WordProperty wordProperty = result.mWordProperty;
if (wordProperty == null) {
- Log.d(TAG, " dictionary is empty.");
+ Log.d(tag, " dictionary is empty.");
break;
}
- Log.d(TAG, wordProperty.toString());
+ Log.d(tag, wordProperty.toString());
token = result.mNextToken;
} while (token != 0);
}
});
}
+
+ /**
+ * Returns dictionary content required for syncing.
+ */
+ public WordProperty[] getWordPropertiesForSyncing() {
+ reloadDictionaryIfRequired();
+ final AsyncResultHolder<WordProperty[]> result = new AsyncResultHolder<>();
+ asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
+ @Override
+ public void run() {
+ final ArrayList<WordProperty> wordPropertyList = new ArrayList<>();
+ final BinaryDictionary binaryDictionary = getBinaryDictionary();
+ if (binaryDictionary == null) {
+ return;
+ }
+ int token = 0;
+ do {
+ // TODO: We need a new API that returns *new* un-synced data.
+ final BinaryDictionary.GetNextWordPropertyResult nextWordPropertyResult =
+ binaryDictionary.getNextWordProperty(token);
+ final WordProperty wordProperty = nextWordPropertyResult.mWordProperty;
+ if (wordProperty == null) {
+ break;
+ }
+ wordPropertyList.add(wordProperty);
+ token = nextWordPropertyResult.mNextToken;
+ } while (token != 0);
+ result.set(wordPropertyList.toArray(new WordProperty[wordPropertyList.size()]));
+ }
+ });
+ // TODO: Figure out the best timeout duration for this API.
+ return result.get(DEFAULT_WORD_PROPERTIES_FOR_SYNC,
+ TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index fecb0ef94..37effeead 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -16,15 +16,16 @@
package com.android.inputmethod.latin;
-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.common.Constants.ImeOption.NO_FLOATING_GESTURE_PREVIEW;
+import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE;
+import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT;
import android.text.InputType;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.utils.InputTypeUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -42,6 +43,12 @@ public final class InputAttributes {
final public boolean mApplicationSpecifiedCompletionOn;
final public boolean mShouldInsertSpacesAutomatically;
final public boolean mShouldShowVoiceInputKey;
+ /**
+ * Whether the floating gesture preview should be disabled. If true, this should override the
+ * corresponding keyboard settings preference, always suppressing the floating preview text.
+ * {@link com.android.inputmethod.latin.settings.SettingsValues#mGestureFloatingPreviewTextEnabled}
+ */
+ final public boolean mDisableGestureFloatingPreviewText;
final public boolean mIsGeneralTextInput;
final private int mInputType;
final private EditorInfo mEditorInfo;
@@ -77,6 +84,7 @@ public final class InputAttributes {
mApplicationSpecifiedCompletionOn = false;
mShouldInsertSpacesAutomatically = false;
mShouldShowVoiceInputKey = false;
+ mDisableGestureFloatingPreviewText = false;
mIsGeneralTextInput = false;
return;
}
@@ -109,6 +117,9 @@ public final class InputAttributes {
|| hasNoMicrophoneKeyOption();
mShouldShowVoiceInputKey = !noMicrophone;
+ mDisableGestureFloatingPreviewText = InputAttributes.inPrivateImeOptions(
+ mPackageNameForPrivateImeOptions, NO_FLOATING_GESTURE_PREVIEW, editorInfo);
+
// If it's a browser edit field and auto correct is not ON explicitly, then
// disable auto correction, but keep suggestions on.
// If NO_SUGGESTIONS is set, don't do prediction.
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
deleted file mode 100644
index 790e0d830..000000000
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ /dev/null
@@ -1,183 +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;
-
-import android.util.Log;
-import android.util.SparseIntArray;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.define.DebugFlags;
-import com.android.inputmethod.latin.utils.ResizableIntArray;
-
-// 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;
- private final ResizableIntArray mPointerIds;
- private final ResizableIntArray mTimes;
-
- public InputPointers(int defaultCapacity) {
- mDefaultCapacity = defaultCapacity;
- mXCoordinates = new ResizableIntArray(defaultCapacity);
- mYCoordinates = new ResizableIntArray(defaultCapacity);
- mPointerIds = new ResizableIntArray(defaultCapacity);
- mTimes = new ResizableIntArray(defaultCapacity);
- }
-
- 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 (DebugFlags.DEBUG_ENABLED || DEBUG_TIME) {
- fillWithLastTimeUntil(index);
- }
- mTimes.addAt(index, time);
- }
-
- @UsedForTesting
- void addPointer(int x, int y, int pointerId, int time) {
- mXCoordinates.add(x);
- mYCoordinates.add(y);
- mPointerIds.add(pointerId);
- mTimes.add(time);
- }
-
- public void set(InputPointers ip) {
- mXCoordinates.set(ip.mXCoordinates);
- mYCoordinates.set(ip.mYCoordinates);
- mPointerIds.set(ip.mPointerIds);
- mTimes.set(ip.mTimes);
- }
-
- public void copy(InputPointers ip) {
- mXCoordinates.copy(ip.mXCoordinates);
- mYCoordinates.copy(ip.mYCoordinates);
- mPointerIds.copy(ip.mPointerIds);
- mTimes.copy(ip.mTimes);
- }
-
- /**
- * 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.
- * @param times the source {@link ResizableIntArray} to read the event times from.
- * @param xCoordinates the source {@link ResizableIntArray} to read the x-coordinates from.
- * @param yCoordinates the source {@link ResizableIntArray} to read the y-coordinates from.
- * @param startPos the starting index of the data in {@code times} and etc.
- * @param length the number of data to be appended.
- */
- public void append(int pointerId, ResizableIntArray times, ResizableIntArray xCoordinates,
- ResizableIntArray yCoordinates, int startPos, int length) {
- if (length == 0) {
- return;
- }
- mXCoordinates.append(xCoordinates, startPos, length);
- mYCoordinates.append(yCoordinates, startPos, length);
- mPointerIds.fill(pointerId, mPointerIds.getLength(), length);
- mTimes.append(times, startPos, length);
- }
-
- /**
- * Shift to the left by elementCount, discarding elementCount pointers at the start.
- * @param elementCount how many elements to shift.
- */
- public void shift(final int elementCount) {
- mXCoordinates.shift(elementCount);
- mYCoordinates.shift(elementCount);
- mPointerIds.shift(elementCount);
- mTimes.shift(elementCount);
- }
-
- public void reset() {
- final int defaultCapacity = mDefaultCapacity;
- mXCoordinates.reset(defaultCapacity);
- mYCoordinates.reset(defaultCapacity);
- mPointerIds.reset(defaultCapacity);
- mTimes.reset(defaultCapacity);
- }
-
- public int getPointerSize() {
- return mXCoordinates.getLength();
- }
-
- public int[] getXCoordinates() {
- return mXCoordinates.getPrimitiveArray();
- }
-
- public int[] getYCoordinates() {
- return mYCoordinates.getPrimitiveArray();
- }
-
- public int[] getPointerIds() {
- return mPointerIds.getPrimitiveArray();
- }
-
- public int[] getTimes() {
- if (DebugFlags.DEBUG_ENABLED || DEBUG_TIME) {
- if (!isValidTimeStamps()) {
- throw new RuntimeException("Time stamps are invalid.");
- }
- }
- return mTimes.getPrimitiveArray();
- }
-
- @Override
- public String toString() {
- return "size=" + getPointerSize() + " id=" + mPointerIds + " time=" + mTimes
- + " x=" + mXCoordinates + " y=" + mYCoordinates;
- }
-
- private boolean isValidTimeStamps() {
- final int[] times = mTimes.getPrimitiveArray();
- 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 < 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 7fa935413..f3a8ca169 100644
--- a/java/src/com/android/inputmethod/latin/InputView.java
+++ b/java/src/com/android/inputmethod/latin/InputView.java
@@ -139,7 +139,10 @@ public final class InputView extends FrameLayout {
return y - mEventReceivingRect.top;
}
- // Callback when a {@link MotionEvent} is forwarded.
+ /**
+ * Callback when a {@link MotionEvent} is forwarded.
+ * @param me the motion event to be forwarded.
+ */
protected void onForwardingEvent(final MotionEvent me) {}
// Returns true if a {@link MotionEvent} is needed to be forwarded to
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 8cbf8379b..426d33e6d 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -19,6 +19,8 @@ package com.android.inputmethod.latin;
import android.text.TextUtils;
import com.android.inputmethod.event.Event;
+import com.android.inputmethod.latin.common.InputPointers;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import java.util.ArrayList;
@@ -48,10 +50,10 @@ public final class LastComposedWord {
public final String mTypedWord;
public final CharSequence mCommittedWord;
public final String mSeparatorString;
- public final PrevWordsInfo mPrevWordsInfo;
+ public final NgramContext mNgramContext;
public final int mCapitalizedMode;
public final InputPointers mInputPointers =
- new InputPointers(Constants.DICTIONARY_MAX_WORD_LENGTH);
+ new InputPointers(DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH);
private boolean mActive;
@@ -64,7 +66,7 @@ public final class LastComposedWord {
public LastComposedWord(final ArrayList<Event> events,
final InputPointers inputPointers, final String typedWord,
final CharSequence committedWord, final String separatorString,
- final PrevWordsInfo prevWordsInfo, final int capitalizedMode) {
+ final NgramContext ngramContext, final int capitalizedMode) {
if (inputPointers != null) {
mInputPointers.copy(inputPointers);
}
@@ -73,7 +75,7 @@ public final class LastComposedWord {
mCommittedWord = committedWord;
mSeparatorString = separatorString;
mActive = true;
- mPrevWordsInfo = prevWordsInfo;
+ mNgramContext = ngramContext;
mCapitalizedMode = capitalizedMode;
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d57db8e9a..330be377b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -16,9 +16,9 @@
package com.android.inputmethod.latin;
-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.common.Constants.ImeOption.FORCE_ASCII;
+import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE;
+import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
@@ -31,13 +31,11 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManager;
-import android.net.ConnectivityManager;
import android.os.Debug;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.text.InputType;
-import android.text.TextUtils;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
@@ -46,19 +44,17 @@ import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
-import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
-import android.widget.TextView;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
+import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils;
+import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
import com.android.inputmethod.event.Event;
import com.android.inputmethod.event.HardwareEventDecoder;
@@ -69,32 +65,29 @@ import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.MainKeyboardView;
-import com.android.inputmethod.keyboard.TextDecoratorUi;
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.CoordinateUtils;
+import com.android.inputmethod.latin.common.InputPointers;
import com.android.inputmethod.latin.define.DebugFlags;
import com.android.inputmethod.latin.define.ProductionFlags;
import com.android.inputmethod.latin.inputlogic.InputLogic;
-import com.android.inputmethod.latin.personalization.ContextualDictionaryUpdater;
-import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever;
-import com.android.inputmethod.latin.personalization.PersonalizationDictionaryUpdater;
import com.android.inputmethod.latin.personalization.PersonalizationHelper;
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.touchinputconsumer.GestureConsumer;
import com.android.inputmethod.latin.utils.ApplicationUtils;
-import com.android.inputmethod.latin.utils.CapsModeUtils;
-import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.CursorAnchorInfoUtils;
import com.android.inputmethod.latin.utils.DialogUtils;
-import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatchesAndSuggestions;
import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import com.android.inputmethod.latin.utils.StatsUtils;
+import com.android.inputmethod.latin.utils.StatsUtilsManager;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.latin.utils.ViewLayoutUtils;
@@ -105,6 +98,8 @@ import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
+import javax.annotation.Nonnull;
+
/**
* Input method implementation for Qwerty'ish keyboard.
*/
@@ -112,17 +107,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
SuggestionStripView.Listener, SuggestionStripViewAccessor,
DictionaryFacilitator.DictionaryInitializationListener,
ImportantNoticeDialog.ImportantNoticeDialogListener {
- private static final String TAG = LatinIME.class.getSimpleName();
+ static final String TAG = LatinIME.class.getSimpleName();
private static final boolean TRACE = false;
- private static boolean DEBUG = false;
private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100;
-
- private static final int PENDING_IMS_CALLBACK_DURATION = 800;
-
- private static final int DELAY_WAIT_FOR_DICTIONARY_LOAD = 2000; // 2s
-
private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2;
+ private static final int PENDING_IMS_CALLBACK_DURATION_MILLIS = 800;
+ static final long DELAY_WAIT_FOR_DICTIONARY_LOAD_MILLIS = TimeUnit.SECONDS.toMillis(2);
+ static final long DELAY_DEALLOCATE_MEMORY_MILLIS = TimeUnit.SECONDS.toMillis(10);
/**
* The name of the scheme used by the Package Manager to warn of a new package installation,
@@ -130,22 +122,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
*/
private static final String SCHEME_PACKAGE = "package";
- private final Settings mSettings;
+ final Settings mSettings;
private final DictionaryFacilitator mDictionaryFacilitator =
- new DictionaryFacilitator(
- new DistracterFilterCheckingExactMatchesAndSuggestions(this /* context */));
- // TODO: Move from LatinIME.
- private final PersonalizationDictionaryUpdater mPersonalizationDictionaryUpdater =
- new PersonalizationDictionaryUpdater(this /* context */, mDictionaryFacilitator);
- private final ContextualDictionaryUpdater mContextualDictionaryUpdater =
- new ContextualDictionaryUpdater(this /* context */, mDictionaryFacilitator,
- new Runnable() {
- @Override
- public void run() {
- mHandler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
- }
- });
- private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
+ DictionaryFacilitatorProvider.getDictionaryFacilitator(
+ false /* isNeededForSpellChecking */);
+ final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
this /* SuggestionStripViewAccessor */, mDictionaryFacilitator);
// 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.
@@ -153,14 +134,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: Move these {@link View}s to {@link KeyboardSwitcher}.
private View mInputView;
+ private InsetsUpdater mInsetsUpdater;
private SuggestionStripView mSuggestionStripView;
- private TextView mExtractEditText;
private RichInputMethodManager mRichImm;
@UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
- private final SubtypeSwitcher mSubtypeSwitcher;
private final SubtypeState mSubtypeState = new SubtypeState();
- private final SpecialKeyDetector mSpecialKeyDetector;
+ private final EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector =
+ new EmojiAltPhysicalKeyDetector(mInputLogic.mConnection);
+ private StatsUtilsManager mStatsUtilsManager;
// Working variable for {@link #startShowingInputView()} and
// {@link #onEvaluateInputViewShown()}.
private boolean mIsExecutingStartShowingInputView;
@@ -176,6 +158,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private final boolean mIsHardwareAcceleratedDrawingEnabled;
+ private GestureConsumer mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
+
public final UIHandler mHandler = new UIHandler(this);
public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> {
@@ -188,20 +172,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6;
private static final int MSG_RESET_CACHES = 7;
private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8;
+ private static final int MSG_DEALLOCATE_MEMORY = 9;
+ private static final int MSG_RESUME_SUGGESTIONS_FOR_START_INPUT = 10;
// Update this when adding new messages
- private static final int MSG_LAST = MSG_WAIT_FOR_DICTIONARY_LOAD;
+ private static final int MSG_LAST = MSG_RESUME_SUGGESTIONS_FOR_START_INPUT;
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_UNUSED = 0;
- private static final int ARG1_FALSE = 0;
private static final int ARG1_TRUE = 1;
private int mDelayInMillisecondsToUpdateSuggestions;
private int mDelayInMillisecondsToUpdateShiftState;
- public UIHandler(final LatinIME ownerInstance) {
+ public UIHandler(@Nonnull final LatinIME ownerInstance) {
super(ownerInstance);
}
@@ -245,20 +230,26 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
break;
case MSG_RESUME_SUGGESTIONS:
latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
- latinIme.mSettings.getCurrent(),
- msg.arg1 == ARG1_TRUE /* shouldIncludeResumedWordInSuggestions */,
+ latinIme.mSettings.getCurrent(), false /* forStartInput */,
+ latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId());
+ break;
+ case MSG_RESUME_SUGGESTIONS_FOR_START_INPUT:
+ latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
+ latinIme.mSettings.getCurrent(), true /* forStartInput */,
latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId());
break;
case MSG_REOPEN_DICTIONARIES:
// We need to re-evaluate the currently composing word in case the script has
// changed.
postWaitForDictionaryLoad();
- latinIme.resetSuggest();
+ latinIme.resetDictionaryFacilitatorIfNecessary();
break;
case MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED:
+ final SuggestedWords suggestedWords = (SuggestedWords) msg.obj;
latinIme.mInputLogic.onUpdateTailBatchInputCompleted(
latinIme.mSettings.getCurrent(),
- (SuggestedWords) msg.obj, latinIme.mKeyboardSwitcher);
+ suggestedWords, latinIme.mKeyboardSwitcher);
+ latinIme.onTailBatchInputResultShown(suggestedWords);
break;
case MSG_RESET_CACHES:
final SettingsValues settingsValues = latinIme.mSettings.getCurrent();
@@ -275,6 +266,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
case MSG_WAIT_FOR_DICTIONARY_LOAD:
Log.i(TAG, "Timeout waiting for dictionary load");
break;
+ case MSG_DEALLOCATE_MEMORY:
+ latinIme.deallocateMemory();
+ break;
}
}
@@ -287,8 +281,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES));
}
- public void postResumeSuggestions(final boolean shouldIncludeResumedWordInSuggestions,
- final boolean shouldDelay) {
+ private void postResumeSuggestionsInternal(final boolean shouldDelay,
+ final boolean forStartInput) {
final LatinIME latinIme = getOwnerInstance();
if (latinIme == null) {
return;
@@ -297,17 +291,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
removeMessages(MSG_RESUME_SUGGESTIONS);
+ removeMessages(MSG_RESUME_SUGGESTIONS_FOR_START_INPUT);
+ final int message = forStartInput ? MSG_RESUME_SUGGESTIONS_FOR_START_INPUT
+ : MSG_RESUME_SUGGESTIONS;
if (shouldDelay) {
- sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS,
- shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
- 0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions);
+ sendMessageDelayed(obtainMessage(message),
+ mDelayInMillisecondsToUpdateSuggestions);
} else {
- sendMessage(obtainMessage(MSG_RESUME_SUGGESTIONS,
- shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
- 0 /* ignored */));
+ sendMessage(obtainMessage(message));
}
}
+ public void postResumeSuggestions(final boolean shouldDelay) {
+ postResumeSuggestionsInternal(shouldDelay, false /* forStartInput */);
+ }
+
+ public void postResumeSuggestionsForStartInput(final boolean shouldDelay) {
+ postResumeSuggestionsInternal(shouldDelay, true /* forStartInput */);
+ }
+
public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
removeMessages(MSG_RESET_CACHES);
sendMessage(obtainMessage(MSG_RESET_CACHES, tryResumeSuggestions ? 1 : 0,
@@ -316,7 +318,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void postWaitForDictionaryLoad() {
sendMessageDelayed(obtainMessage(MSG_WAIT_FOR_DICTIONARY_LOAD),
- DELAY_WAIT_FOR_DICTIONARY_LOAD);
+ DELAY_WAIT_FOR_DICTIONARY_LOAD_MILLIS);
}
public void cancelWaitForDictionaryLoad() {
@@ -345,6 +347,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mDelayInMillisecondsToUpdateShiftState);
}
+ public void postDeallocateMemory() {
+ sendMessageDelayed(obtainMessage(MSG_DEALLOCATE_MEMORY),
+ DELAY_DEALLOCATE_MEMORY_MILLIS);
+ }
+
+ public void cancelDeallocateMemory() {
+ removeMessages(MSG_DEALLOCATE_MEMORY);
+ }
+
+ public boolean hasPendingDeallocateMemory() {
+ return hasMessages(MSG_DEALLOCATE_MEMORY);
+ }
+
@UsedForTesting
public void removeAllMessages() {
for (int i = 0; i <= MSG_LAST; ++i) {
@@ -442,7 +457,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mPendingSuccessiveImsCallback = false;
resetPendingImsCallback();
sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
- PENDING_IMS_CALLBACK_DURATION);
+ PENDING_IMS_CALLBACK_DURATION_MILLIS);
}
final LatinIME latinIme = getOwnerInstance();
if (latinIme != null) {
@@ -450,6 +465,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
latinIme.onStartInputViewInternal(editorInfo, restarting);
mAppliedEditorInfo = editorInfo;
}
+ cancelDeallocateMemory();
}
}
@@ -463,6 +479,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
latinIme.onFinishInputViewInternal(finishingInput);
mAppliedEditorInfo = null;
}
+ if (!hasPendingDeallocateMemory()) {
+ postDeallocateMemory();
+ }
}
}
@@ -516,9 +535,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public LatinIME() {
super();
mSettings = Settings.getInstance();
- mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mKeyboardSwitcher = KeyboardSwitcher.getInstance();
- mSpecialKeyDetector = new SpecialKeyDetector(this);
+ mStatsUtilsManager = StatsUtilsManager.getInstance();
mIsHardwareAcceleratedDrawingEnabled =
InputMethodServiceCompatUtils.enableHardwareAcceleration(this);
Log.i(TAG, "Hardware accelerated drawing: " + mIsHardwareAcceleratedDrawingEnabled);
@@ -530,28 +548,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
DebugFlags.init(PreferenceManager.getDefaultSharedPreferences(this));
RichInputMethodManager.init(this);
mRichImm = RichInputMethodManager.getInstance();
- SubtypeSwitcher.init(this);
KeyboardSwitcher.init(this);
AudioAndHapticFeedbackManager.init(this);
AccessibilityUtils.init(this);
- StatsUtils.init(this);
-
+ mStatsUtilsManager.onCreate(this /* context */, mDictionaryFacilitator);
super.onCreate();
mHandler.onCreate();
- DEBUG = DebugFlags.DEBUG_ENABLED;
- // TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}.
+ // TODO: Resolve mutual dependencies of {@link #loadSettings()} and
+ // {@link #resetDictionaryFacilitatorIfNecessary()}.
loadSettings();
- resetSuggest();
+ resetDictionaryFacilitatorIfNecessary();
- // Register to receive ringer mode change and network state change.
- // Also receive installation and removal of a dictionary pack.
+ // Register to receive ringer mode change.
final IntentFilter filter = new IntentFilter();
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
- registerReceiver(mConnectivityAndRingerModeChangeReceiver, filter);
+ registerReceiver(mRingerModeChangeReceiver, filter);
+ // Register to receive installation and removal of a dictionary pack.
final IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -566,15 +581,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter);
- DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this);
-
- StatsUtils.onCreate(mSettings.getCurrent());
+ StatsUtils.onCreate(mSettings.getCurrent(), mRichImm);
}
// Has to be package-visible for unit tests
@UsedForTesting
void loadSettings() {
- final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale();
+ final Locale locale = mRichImm.getCurrentSubtypeLocale();
final EditorInfo editorInfo = getCurrentInputEditorInfo();
final InputAttributes inputAttributes = new InputAttributes(
editorInfo, isFullscreenMode(), getPackageName());
@@ -585,30 +598,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// been displayed. Opening dictionaries never affects responsivity as dictionaries are
// asynchronously loaded.
if (!mHandler.hasPendingReopenDictionaries()) {
- resetSuggestForLocale(locale);
+ resetDictionaryFacilitator(locale);
}
- mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList(
- true /* allowsImplicitlySelectedSubtypes */));
refreshPersonalizationDictionarySession(currentSettingsValues);
- StatsUtils.onLoadSettings(currentSettingsValues);
+ resetDictionaryFacilitatorIfNecessary();
+ mStatsUtilsManager.onLoadSettings(this /* context */, currentSettingsValues);
}
private void refreshPersonalizationDictionarySession(
final SettingsValues currentSettingsValues) {
- mPersonalizationDictionaryUpdater.onLoadSettings(
- currentSettingsValues.mUsePersonalizedDicts,
- mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes());
- mContextualDictionaryUpdater.onLoadSettings(currentSettingsValues.mUsePersonalizedDicts);
- final boolean shouldKeepUserHistoryDictionaries;
- if (currentSettingsValues.mUsePersonalizedDicts) {
- shouldKeepUserHistoryDictionaries = true;
- } else {
- shouldKeepUserHistoryDictionaries = false;
- }
- if (!shouldKeepUserHistoryDictionaries) {
+ if (!currentSettingsValues.mUsePersonalizedDicts) {
// Remove user history dictionaries.
PersonalizationHelper.removeAllUserHistoryDictionaries(this);
- mDictionaryFacilitator.clearUserHistoryDictionary();
+ mDictionaryFacilitator.clearUserHistoryDictionary(this);
}
}
@@ -621,44 +623,49 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
if (mHandler.hasPendingWaitForDictionaryLoad()) {
mHandler.cancelWaitForDictionaryLoad();
- mHandler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */,
- false /* shouldDelay */);
+ mHandler.postResumeSuggestions(false /* shouldDelay */);
}
}
- private void resetSuggest() {
- final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- final String switcherLocaleStr = switcherSubtypeLocale.toString();
+ void resetDictionaryFacilitatorIfNecessary() {
+ final Locale subtypeSwitcherLocale = mRichImm.getCurrentSubtypeLocale();
final Locale subtypeLocale;
- if (TextUtils.isEmpty(switcherLocaleStr)) {
+ if (subtypeSwitcherLocale == null) {
// 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
// case, we are about to go down but we still don't know it, however the system tells
- // us there is no current subtype so the locale is the empty string. Take the best
- // possible guess instead -- it's bound to have no consequences, and we have no way
- // of knowing anyway.
+ // us there is no current subtype.
Log.e(TAG, "System is reporting no current subtype.");
subtypeLocale = getResources().getConfiguration().locale;
} else {
- subtypeLocale = switcherSubtypeLocale;
+ subtypeLocale = subtypeSwitcherLocale;
+ }
+ if (mDictionaryFacilitator.isForLocale(subtypeLocale)
+ && mDictionaryFacilitator.isForAccount(mSettings.getCurrent().mAccount)) {
+ return;
}
- resetSuggestForLocale(subtypeLocale);
+ resetDictionaryFacilitator(subtypeLocale);
}
/**
- * Reset suggest by loading dictionaries for the locale and the current settings values.
+ * Reset the facilitator by loading dictionaries for the given locale and
+ * the current settings values.
*
* @param locale the locale
*/
- private void resetSuggestForLocale(final Locale locale) {
+ // TODO: make sure the current settings always have the right locales, and read from them.
+ private void resetDictionaryFacilitator(final Locale locale) {
final SettingsValues settingsValues = mSettings.getCurrent();
mDictionaryFacilitator.resetDictionaries(this /* context */, locale,
settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
- false /* forceReloadMainDictionary */, this);
+ false /* forceReloadMainDictionary */,
+ settingsValues.mAccount, "" /* dictNamePrefix */,
+ this /* DictionaryInitializationListener */);
if (settingsValues.mAutoCorrectionEnabledPerUserSettings) {
mInputLogic.mSuggest.setAutoCorrectionThreshold(
settingsValues.mAutoCorrectionThreshold);
}
+ mInputLogic.mSuggest.setPlausibilityThreshold(settingsValues.mPlausibilityThreshold);
}
/**
@@ -668,19 +675,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final SettingsValues settingsValues = mSettings.getCurrent();
mDictionaryFacilitator.resetDictionaries(this /* context */,
mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict,
- settingsValues.mUsePersonalizedDicts, true /* forceReloadMainDictionary */, this);
+ settingsValues.mUsePersonalizedDicts,
+ true /* forceReloadMainDictionary */,
+ settingsValues.mAccount, "" /* dictNamePrefix */,
+ this /* DictionaryInitializationListener */);
}
@Override
public void onDestroy() {
mDictionaryFacilitator.closeDictionaries();
- mPersonalizationDictionaryUpdater.onDestroy();
- mContextualDictionaryUpdater.onDestroy();
mSettings.onDestroy();
- unregisterReceiver(mConnectivityAndRingerModeChangeReceiver);
+ unregisterReceiver(mRingerModeChangeReceiver);
unregisterReceiver(mDictionaryPackInstallReceiver);
unregisterReceiver(mDictionaryDumpBroadcastReceiver);
- StatsUtils.onDestroy();
+ mStatsUtilsManager.onDestroy(this /* context */);
super.onDestroy();
}
@@ -688,7 +696,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void recycle() {
unregisterReceiver(mDictionaryPackInstallReceiver);
unregisterReceiver(mDictionaryDumpBroadcastReceiver);
- unregisterReceiver(mConnectivityAndRingerModeChangeReceiver);
+ unregisterReceiver(mRingerModeChangeReceiver);
mInputLogic.recycle();
}
@@ -715,15 +723,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
cleanupInternalStateForFinishInput();
}
}
- // TODO: Remove this test.
- if (!conf.locale.equals(mPersonalizationDictionaryUpdater.getLocale())) {
- refreshPersonalizationDictionarySession(settingsValues);
- }
super.onConfigurationChanged(conf);
}
@Override
public View onCreateInputView() {
+ StatsUtils.onCreateInputView();
return mKeyboardSwitcher.onCreateInputView(mIsHardwareAcceleratedDrawingEnabled);
}
@@ -731,60 +736,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void setInputView(final View view) {
super.setInputView(view);
mInputView = view;
+ mInsetsUpdater = ViewOutlineProviderCompatUtils.setInsetsOutlineProvider(view);
+ updateSoftInputWindowLayoutParameters();
mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view);
if (hasSuggestionStripView()) {
mSuggestionStripView.setListener(this, view);
}
- mInputLogic.setTextDecoratorUi(new TextDecoratorUi(this, view));
- }
-
- @Override
- public void setExtractView(final View view) {
- final TextView prevExtractEditText = mExtractEditText;
- super.setExtractView(view);
- TextView nextExtractEditText = null;
- if (view != null) {
- final View extractEditText = view.findViewById(android.R.id.inputExtractEditText);
- if (extractEditText instanceof TextView) {
- nextExtractEditText = (TextView)extractEditText;
- }
- }
- if (prevExtractEditText == nextExtractEditText) {
- return;
- }
- if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK && prevExtractEditText != null) {
- prevExtractEditText.getViewTreeObserver().removeOnPreDrawListener(
- mExtractTextViewPreDrawListener);
- }
- mExtractEditText = nextExtractEditText;
- if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK && mExtractEditText != null) {
- mExtractEditText.getViewTreeObserver().addOnPreDrawListener(
- mExtractTextViewPreDrawListener);
- }
- }
-
- private final ViewTreeObserver.OnPreDrawListener mExtractTextViewPreDrawListener =
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- onExtractTextViewPreDraw();
- return true;
- }
- };
-
- private void onExtractTextViewPreDraw() {
- if (!ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK || !isFullscreenMode()
- || mExtractEditText == null) {
- return;
- }
- final CursorAnchorInfo info = CursorAnchorInfoUtils.getCursorAnchorInfo(mExtractEditText);
- mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info));
}
@Override
public void setCandidatesView(final View view) {
// To ensure that CandidatesView will never be set.
- return;
}
@Override
@@ -795,11 +757,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
mHandler.onStartInputView(editorInfo, restarting);
+ mStatsUtilsManager.onStartInputView();
}
@Override
public void onFinishInputView(final boolean finishingInput) {
+ StatsUtils.onFinishInputView();
mHandler.onFinishInputView(finishingInput);
+ mStatsUtilsManager.onFinishInputView();
+ mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
}
@Override
@@ -811,20 +777,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) {
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
- mSubtypeSwitcher.onSubtypeChanged(subtype);
+ InputMethodSubtype oldSubtype = mRichImm.getCurrentSubtype().getRawSubtype();
+ StatsUtils.onSubtypeChanged(oldSubtype, subtype);
+ mRichImm.onSubtypeChanged(subtype);
mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype),
mSettings.getCurrent());
loadKeyboard();
}
- private void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) {
+ void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) {
super.onStartInput(editorInfo, restarting);
}
@SuppressWarnings("deprecation")
- private void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) {
+ void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) {
super.onStartInputView(editorInfo, restarting);
- mRichImm.clearSubtypeCaches();
+
+ mDictionaryFacilitator.onStartInput();
+ // Switch to the null consumer to handle cases leading to early exit below, for which we
+ // also wouldn't be consuming gesture data.
+ mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
+ mRichImm.refreshSubtypeCaches();
final KeyboardSwitcher switcher = mKeyboardSwitcher;
switcher.updateKeyboardTheme();
final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
@@ -839,7 +812,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
return;
}
- if (DEBUG) {
+ if (DebugFlags.DEBUG_ENABLED) {
Log.d(TAG, "onStartInputView: editorInfo:"
+ String.format("inputType=0x%08x imeOptions=0x%08x",
editorInfo.inputType, editorInfo.imeOptions));
@@ -867,6 +840,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
+ // Update to a gesture consumer with the current editor and IME state.
+ mGestureConsumer = GestureConsumer.newInstance(editorInfo,
+ mInputLogic.getPrivateCommandPerformer(),
+ mRichImm.getCurrentSubtypeLocale(),
+ switcher.getKeyboard());
+
// Forward this event to the accessibility utilities, if enabled.
final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
if (accessUtils.isTouchExplorationEnabled()) {
@@ -875,9 +854,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo);
final boolean isDifferentTextField = !restarting || inputTypeChanged;
- if (isDifferentTextField) {
- mSubtypeSwitcher.updateParametersOnStartInputView();
- }
+
+ StatsUtils.onStartInputView(editorInfo.inputType,
+ Settings.getInstance().getCurrent().mDisplayOrientation,
+ !isDifferentTextField);
// The EditorInfo might have a flag that affects fullscreen mode.
// Note: This call should be done by InputMethodService?
@@ -897,15 +877,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// span, so we should reset our state unconditionally, even if restarting is true.
// We also tell the input logic about the combining rules for the current subtype, so
// it can adjust its combiners if needed.
- mInputLogic.startInput(mSubtypeSwitcher.getCombiningRulesExtraValueOfCurrentSubtype(),
+ mInputLogic.startInput(mRichImm.getCombiningRulesExtraValueOfCurrentSubtype(),
currentSettingsValues);
- // Note: the following does a round-trip IPC on the main thread: be careful
- final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- if (null != currentLocale && !currentLocale.equals(suggest.getLocale())) {
- // TODO: Do this automatically.
- resetSuggest();
- }
+ resetDictionaryFacilitatorIfNecessary();
// TODO[IL]: Can the following be moved to InputLogic#startInput?
if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess(
@@ -919,11 +894,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// mLastSelection{Start,End} are reset later in this method, no need to do it here
needToCallLoadKeyboardLater = true;
} else {
- // When rotating, initialSelStart and initialSelEnd sometimes are lying. Make a best
- // effort to work around this bug.
+ // When rotating, and when input is starting again in a field from where the focus
+ // didn't move (the keyboard having been closed with the back key),
+ // initialSelStart and initialSelEnd sometimes are lying. Make a best effort to
+ // work around this bug.
mInputLogic.mConnection.tryFixLyingCursorPosition();
- mHandler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */,
- true /* shouldDelay */);
+ mHandler.postResumeSuggestionsForStartInput(true /* shouldDelay */);
needToCallLoadKeyboardLater = false;
}
} else {
@@ -943,6 +919,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
suggest.setAutoCorrectionThreshold(
currentSettingsValues.mAutoCorrectionThreshold);
}
+ suggest.setPlausibilityThreshold(currentSettingsValues.mPlausibilityThreshold);
switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(),
getCurrentRecapitalizeState());
@@ -970,7 +947,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip();
mainKeyboardView.setMainDictionaryAvailability(
- mDictionaryFacilitator.hasInitializedMainDictionary());
+ mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary());
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setSlidingKeyInputPreviewEnabled(
@@ -980,8 +957,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
currentSettingsValues.mGestureTrailEnabled,
currentSettingsValues.mGestureFloatingPreviewTextEnabled);
- // Contextual dictionary should be updated for the current application.
- mContextualDictionaryUpdater.onStartInputView(editorInfo.packageName);
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
}
@@ -994,45 +969,50 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- private void onFinishInputInternal() {
+ void onFinishInputInternal() {
super.onFinishInput();
+ mDictionaryFacilitator.onFinishInput();
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
mainKeyboardView.closing();
}
}
- private void onFinishInputViewInternal(final boolean finishingInput) {
+ void onFinishInputViewInternal(final boolean finishingInput) {
super.onFinishInputView(finishingInput);
cleanupInternalStateForFinishInput();
}
private void cleanupInternalStateForFinishInput() {
- mKeyboardSwitcher.deallocateMemory();
// Remove pending messages related to update suggestions
mHandler.cancelUpdateSuggestionStrip();
// Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
mInputLogic.finishInput();
}
+ protected void deallocateMemory() {
+ mKeyboardSwitcher.deallocateMemory();
+ }
+
@Override
public void onUpdateSelection(final int oldSelStart, final int oldSelEnd,
final int newSelStart, final int newSelEnd,
final int composingSpanStart, final int composingSpanEnd) {
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
composingSpanStart, composingSpanEnd);
- if (DEBUG) {
+ if (DebugFlags.DEBUG_ENABLED) {
Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd
+ ", nss=" + newSelStart + ", nse=" + newSelEnd
+ ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd);
}
- // This call happens when we have a hardware keyboard as well as when we don't. While we
- // don't support hardware keyboards yet we should avoid doing the processing associated
- // with cursor movement when we have a hardware keyboard since we are not in charge.
+ // This call happens whether our view is displayed or not, but if it's not then we should
+ // not attempt recorrection. This is true even with a hardware keyboard connected: if the
+ // view is not displayed we have no means of showing suggestions anyway, and if it is then
+ // we want to show suggestions anyway.
final SettingsValues settingsValues = mSettings.getCurrent();
- if ((!settingsValues.mHasHardwareKeyboard || ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED)
+ if (isInputViewShown()
&& mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
settingsValues)) {
mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
@@ -1040,15 +1020,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- // We cannot mark this method as @Override until new SDK becomes publicly available.
- // @Override
- public void onUpdateCursorAnchorInfo(final CursorAnchorInfo info) {
- if (!ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK || isFullscreenMode()) {
- return;
- }
- mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info));
- }
-
/**
* This is called when the user has clicked on the extracted text view,
* when running in fullscreen mode. The default implementation hides
@@ -1098,7 +1069,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions) {
- if (DEBUG) {
+ if (DebugFlags.DEBUG_ENABLED) {
Log.i(TAG, "Received completions:");
if (applicationSpecifiedCompletions != null) {
for (int i = 0; i < applicationSpecifiedCompletions.length; i++) {
@@ -1121,9 +1092,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
SuggestedWords.getFromApplicationSpecifiedCompletions(
applicationSpecifiedCompletions);
final SuggestedWords suggestedWords = new SuggestedWords(applicationSuggestedWords,
- null /* rawSuggestions */, false /* typedWordValid */, false /* willAutoCorrect */,
+ null /* rawSuggestions */,
+ null /* typedWord */,
+ false /* typedWordValid */,
+ false /* willAutoCorrect */,
false /* isObsoleteSuggestions */,
- SuggestedWords.INPUT_STYLE_APPLICATION_SPECIFIED /* inputStyle */);
+ SuggestedWords.INPUT_STYLE_APPLICATION_SPECIFIED /* inputStyle */,
+ SuggestedWords.NOT_A_SEQUENCE_NUMBER);
// When in fullscreen mode, show completions generated by the application forcibly
setSuggestedWords(suggestedWords);
}
@@ -1131,6 +1106,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onComputeInsets(final InputMethodService.Insets outInsets) {
super.onComputeInsets(outInsets);
+ // This method may be called before {@link #setInputView(View)}.
+ if (mInputView == null) {
+ return;
+ }
final SettingsValues settingsValues = mSettings.getCurrent();
final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView();
if (visibleKeyboardView == null || !hasSuggestionStripView()) {
@@ -1141,8 +1120,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (hasHardwareKeyboard && visibleKeyboardView.getVisibility() == View.GONE) {
// If there is a hardware keyboard and a visible software keyboard view has been hidden,
// no visual element will be shown on the screen.
- outInsets.touchableInsets = inputHeight;
+ outInsets.contentTopInsets = inputHeight;
outInsets.visibleTopInsets = inputHeight;
+ mInsetsUpdater.setInsets(outInsets);
return;
}
final int suggestionsHeight = (!mKeyboardSwitcher.isShowingEmojiPalettes()
@@ -1150,7 +1130,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
? mSuggestionStripView.getHeight() : 0;
final int visibleTopY = inputHeight - visibleKeyboardView.getHeight() - suggestionsHeight;
mSuggestionStripView.setMoreSuggestionsHeight(visibleTopY);
- // Need to set touchable region only if a keyboard view is being shown.
+ // Need to set expanded touchable region only if a keyboard view is being shown.
if (visibleKeyboardView.isShown()) {
final int touchLeft = 0;
final int touchTop = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY;
@@ -1163,14 +1143,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
outInsets.contentTopInsets = visibleTopY;
outInsets.visibleTopInsets = visibleTopY;
+ mInsetsUpdater.setInsets(outInsets);
}
- public void startShowingInputView() {
+ public void startShowingInputView(final boolean needsToLoadKeyboard) {
mIsExecutingStartShowingInputView = true;
// This {@link #showWindow(boolean)} will eventually call back
// {@link #onEvaluateInputViewShown()}.
showWindow(true /* showInput */);
mIsExecutingStartShowingInputView = false;
+ if (needsToLoadKeyboard) {
+ loadKeyboard();
+ }
}
public void stopShowingInputView() {
@@ -1178,6 +1162,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
@Override
+ public boolean onShowInputRequested(final int flags, final boolean configChange) {
+ if (Settings.getInstance().getCurrent().mHasHardwareKeyboard) {
+ return true;
+ }
+ return super.onShowInputRequested(flags, configChange);
+ }
+
+ @Override
public boolean onEvaluateInputViewShown() {
if (mIsExecutingStartShowingInputView) {
return true;
@@ -1207,8 +1199,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void updateFullscreenMode() {
+ super.updateFullscreenMode();
+ updateSoftInputWindowLayoutParameters();
+ }
+
+ private void updateSoftInputWindowLayoutParameters() {
// Override layout parameters to expand {@link SoftInputWindow} to the entire screen.
- // See {@link InputMethodService#setinputView(View) and
+ // See {@link InputMethodService#setinputView(View)} and
// {@link SoftInputWindow#updateWidthHeight(WindowManager.LayoutParams)}.
final Window window = getWindow().getWindow();
ViewLayoutUtils.updateLayoutHeightOf(window, LayoutParams.MATCH_PARENT);
@@ -1227,22 +1224,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ViewLayoutUtils.updateLayoutGravityOf(inputArea, Gravity.BOTTOM);
ViewLayoutUtils.updateLayoutHeightOf(mInputView, layoutHeight);
}
- super.updateFullscreenMode();
- mInputLogic.onUpdateFullscreenMode(isFullscreenMode());
}
- private int getCurrentAutoCapsState() {
+ int getCurrentAutoCapsState() {
return mInputLogic.getCurrentAutoCapsState(mSettings.getCurrent());
}
- private int getCurrentRecapitalizeState() {
+ int getCurrentRecapitalizeState() {
return mInputLogic.getCurrentRecapitalizeState();
}
- public Locale getCurrentSubtypeLocale() {
- return mSubtypeSwitcher.getCurrentSubtypeLocale();
- }
-
/**
* @param codePoints code points to get coordinates for.
* @return x,y coordinates for this keyboard, as a flattened array.
@@ -1256,24 +1247,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return keyboard.getCoordinates(codePoints);
}
- // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is
- // pressed.
- @Override
- public void addWordToUserDictionary(final String word) {
- if (TextUtils.isEmpty(word)) {
- // Probably never supposed to happen, but just in case.
- return;
- }
- final String wordToEdit;
- if (CapsModeUtils.isAutoCapsMode(mInputLogic.mLastComposedWord.mCapitalizedMode)) {
- wordToEdit = word.toLowerCase(getCurrentSubtypeLocale());
- } else {
- wordToEdit = word;
- }
- mDictionaryFacilitator.addWordToUserDictionary(this /* context */, wordToEdit);
- mInputLogic.onAddWordToUserDictionary();
- }
-
// Callback for the {@link SuggestionStripView}, to call when the important notice strip is
// pressed.
@Override
@@ -1284,7 +1257,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Implement {@link ImportantNoticeDialog.ImportantNoticeDialogListener}
@Override
public void onClickSettingsOfImportantNoticeDialog(final int nextVersion) {
- launchSettings();
+ launchSettings(SettingsActivity.EXTRA_ENTRY_VALUE_NOTICE_DIALOG);
}
// Implement {@link ImportantNoticeDialog.ImportantNoticeDialogListener}
@@ -1328,48 +1301,56 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeState.switchSubtype(token, mRichImm);
}
+ // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
+ // alphabetic shift and shift while in symbol layout and get rid of this method.
+ private int getCodePointForKeyboard(final int codePoint) {
+ if (Constants.CODE_SHIFT == codePoint) {
+ final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();
+ if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
+ return codePoint;
+ }
+ return Constants.CODE_SYMBOL_SHIFT;
+ }
+ return codePoint;
+ }
+
// Implementation of {@link KeyboardActionListener}.
@Override
public void onCodeInput(final int codePoint, final int x, final int y,
final boolean isKeyRepeat) {
+ // TODO: this processing does not belong inside LatinIME, the caller should be doing this.
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
// x and y include some padding, but everything down the line (especially native
// code) needs the coordinates in the keyboard frame.
// TODO: We should reconsider which coordinate system should be used to represent
// keyboard event. Also we should pull this up -- LatinIME has no business doing
- // this transformation, it should be done already before calling onCodeInput.
+ // this transformation, it should be done already before calling onEvent.
final int keyX = mainKeyboardView.getKeyX(x);
final int keyY = mainKeyboardView.getKeyY(y);
- final int codeToSend;
- if (Constants.CODE_SHIFT == codePoint) {
- // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
- // alphabetic shift and shift while in symbol layout.
- final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();
- if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
- codeToSend = codePoint;
- } else {
- codeToSend = Constants.CODE_SYMBOL_SHIFT;
- }
- } else {
- codeToSend = codePoint;
- }
- if (Constants.CODE_SHORTCUT == codePoint) {
- mSubtypeSwitcher.switchToShortcutIME(this);
- // Still call the *#onCodeInput methods for readability.
+ final Event event = createSoftwareKeypressEvent(getCodePointForKeyboard(codePoint),
+ keyX, keyY, isKeyRepeat);
+ onEvent(event);
+ }
+
+ // This method is public for testability of LatinIME, but also in the future it should
+ // completely replace #onCodeInput.
+ public void onEvent(@Nonnull final Event event) {
+ if (Constants.CODE_SHORTCUT == event.mKeyCode) {
+ mRichImm.switchToShortcutIme(this);
}
- final Event event = createSoftwareKeypressEvent(codeToSend, keyX, keyY, isKeyRepeat);
final InputTransaction completeInputTransaction =
mInputLogic.onCodeInput(mSettings.getCurrent(), event,
mKeyboardSwitcher.getKeyboardShiftMode(),
mKeyboardSwitcher.getCurrentKeyboardScriptId(), mHandler);
updateStateAfterInputTransaction(completeInputTransaction);
- mKeyboardSwitcher.onCodeInput(codePoint, getCurrentAutoCapsState(),
- getCurrentRecapitalizeState());
+ mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
}
// A helper method to split the code point and the key code. Ultimately, they should not be
// squashed into the same variable, and this method should be removed.
- private static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
+ // public for testing, as we don't want to copy the same logic into test code
+ @Nonnull
+ public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
final int keyY, final boolean isKeyRepeat) {
final int keyCode;
final int codePoint;
@@ -1387,44 +1368,59 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onTextInput(final String rawText) {
// TODO: have the keyboard pass the correct key code when we need it.
- final Event event = Event.createSoftwareTextEvent(rawText, Event.NOT_A_KEY_CODE);
+ final Event event = Event.createSoftwareTextEvent(rawText, Constants.CODE_OUTPUT_TEXT);
final InputTransaction completeInputTransaction =
mInputLogic.onTextInput(mSettings.getCurrent(), event,
mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
updateStateAfterInputTransaction(completeInputTransaction);
- mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT, getCurrentAutoCapsState(),
- getCurrentRecapitalizeState());
+ mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
}
@Override
public void onStartBatchInput() {
mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler);
+ mGestureConsumer.onGestureStarted(
+ mRichImm.getCurrentSubtypeLocale(),
+ mKeyboardSwitcher.getKeyboard());
}
@Override
public void onUpdateBatchInput(final InputPointers batchPointers) {
- mInputLogic.onUpdateBatchInput(mSettings.getCurrent(), batchPointers, mKeyboardSwitcher);
+ mInputLogic.onUpdateBatchInput(batchPointers);
}
@Override
public void onEndBatchInput(final InputPointers batchPointers) {
mInputLogic.onEndBatchInput(batchPointers);
+ mGestureConsumer.onGestureCompleted(batchPointers);
}
@Override
public void onCancelBatchInput() {
mInputLogic.onCancelBatchInput(mHandler);
+ mGestureConsumer.onGestureCanceled();
+ }
+
+ /**
+ * To be called after the InputLogic has gotten a chance to act on the suggested words by the
+ * IME for the full gesture, possibly updating the TextView to reflect the first suggestion.
+ * <p>
+ * This method must be run on the UI Thread.
+ * @param suggestedWords suggested words by the IME for the full gesture.
+ */
+ public void onTailBatchInputResultShown(final SuggestedWords suggestedWords) {
+ mGestureConsumer.onImeSuggestionsProcessed(suggestedWords,
+ mInputLogic.getComposingStart(), mInputLogic.getComposingLength(),
+ mDictionaryFacilitator);
}
// This method must run on the UI Thread.
- private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
+ void showGesturePreviewAndSuggestionStrip(@Nonnull final SuggestedWords suggestedWords,
final boolean dismissGestureFloatingPreviewText) {
showSuggestionStrip(suggestedWords);
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
- mainKeyboardView.showGestureFloatingPreviewText(suggestedWords);
- if (dismissGestureFloatingPreviewText) {
- mainKeyboardView.dismissGestureFloatingPreviewText();
- }
+ mainKeyboardView.showGestureFloatingPreviewText(suggestedWords,
+ dismissGestureFloatingPreviewText /* dismissDelayed */);
}
// Called from PointerTracker through the KeyboardActionListener interface
@@ -1446,22 +1442,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return null != mSuggestionStripView;
}
- @Override
- public boolean isShowingAddToDictionaryHint() {
- return hasSuggestionStripView() && mSuggestionStripView.isShowingAddToDictionaryHint();
- }
-
- @Override
- public void dismissAddToDictionaryHint() {
- if (!hasSuggestionStripView()) {
- return;
- }
- mSuggestionStripView.dismissAddToDictionaryHint();
- }
-
private void setSuggestedWords(final SuggestedWords suggestedWords) {
final SettingsValues currentSettingsValues = mSettings.getCurrent();
- mInputLogic.setSuggestedWords(suggestedWords, currentSettingsValues, mHandler);
+ mInputLogic.setSuggestedWords(suggestedWords);
// TODO: Modify this when we support suggestions with hard keyboard
if (!hasSuggestionStripView()) {
return;
@@ -1471,7 +1454,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
final boolean shouldShowImportantNotice =
- ImportantNoticeUtils.shouldShowImportantNotice(this);
+ ImportantNoticeUtils.shouldShowImportantNotice(this, currentSettingsValues);
final boolean shouldShowSuggestionCandidates =
currentSettingsValues.mInputAttributes.mShouldShowSuggestions
&& currentSettingsValues.isSuggestionsEnabledPerUserSettings();
@@ -1489,7 +1472,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final boolean isEmptyApplicationSpecifiedCompletions =
currentSettingsValues.isApplicationSpecifiedCompletionsOn()
&& suggestedWords.isEmpty();
- final boolean noSuggestionsFromDictionaries = (SuggestedWords.EMPTY == suggestedWords)
+ final boolean noSuggestionsFromDictionaries = suggestedWords.isEmpty()
|| suggestedWords.isPunctuationSuggestions()
|| isEmptyApplicationSpecifiedCompletions;
final boolean isBeginningOfSentencePrediction = (suggestedWords.mInputStyle
@@ -1507,7 +1490,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// We should clear the contextual strip if there is no suggestion from dictionaries.
|| noSuggestionsFromDictionaries) {
mSuggestionStripView.setSuggestions(suggestedWords,
- SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype()));
+ mRichImm.getCurrentSubtype().isRtlSubtype());
}
}
@@ -1516,26 +1499,23 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final OnGetSuggestedWordsCallback callback) {
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
if (keyboard == null) {
- callback.onGetSuggestedWords(SuggestedWords.EMPTY);
+ callback.onGetSuggestedWords(SuggestedWords.getEmptyInstance());
return;
}
- mInputLogic.getSuggestedWords(mSettings.getCurrent(), keyboard.getProximityInfo(),
+ mInputLogic.getSuggestedWords(mSettings.getCurrent(), keyboard,
mKeyboardSwitcher.getKeyboardShiftMode(), inputStyle, sequenceNumber, callback);
}
@Override
- public void showSuggestionStrip(final SuggestedWords sourceSuggestedWords) {
- final SuggestedWords suggestedWords =
- sourceSuggestedWords.isEmpty() ? SuggestedWords.EMPTY : sourceSuggestedWords;
- if (SuggestedWords.EMPTY == suggestedWords) {
+ public void showSuggestionStrip(final SuggestedWords suggestedWords) {
+ if (suggestedWords.isEmpty()) {
setNeutralSuggestionStrip();
} else {
setSuggestedWords(suggestedWords);
}
// 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);
+ AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords);
}
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
@@ -1550,25 +1530,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
updateStateAfterInputTransaction(completeInputTransaction);
}
- @Override
- public void showAddToDictionaryHint(final String word) {
- if (!hasSuggestionStripView()) {
- return;
- }
- mSuggestionStripView.showAddToDictionaryHint(word);
- }
-
// 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();
final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled
- ? SuggestedWords.EMPTY : currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
+ ? SuggestedWords.getEmptyInstance()
+ : currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
setSuggestedWords(neutralSuggestions);
}
- // TODO: Make this private
// Outside LatinIME, only used by the {@link InputTestsBase} test suite.
@UsedForTesting
void loadKeyboard() {
@@ -1676,7 +1648,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Hooks for hardware keyboard
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
- mSpecialKeyDetector.onKeyDown(keyEvent);
+ // TODO: This should be processed in {@link InputLogic}.
+ mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent);
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
return super.onKeyDown(keyCode, keyEvent);
}
@@ -1697,7 +1670,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) {
- mSpecialKeyDetector.onKeyUp(keyEvent);
+ // TODO: This should be processed in {@link InputLogic}.
+ mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent);
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
return super.onKeyUp(keyCode, keyEvent);
}
@@ -1713,21 +1687,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// boolean onKeyLongPress(final int keyCode, final KeyEvent event);
// boolean onKeyMultiple(final int keyCode, final int count, final KeyEvent event);
- // receive ringer mode change and network state change.
- private final BroadcastReceiver mConnectivityAndRingerModeChangeReceiver =
- new BroadcastReceiver() {
+ // receive ringer mode change.
+ private final BroadcastReceiver mRingerModeChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
- if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
- mSubtypeSwitcher.onNetworkStateChanged(intent);
- } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+ if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged();
}
}
};
- private void launchSettings() {
+ void launchSettings(final String extraEntryValue) {
mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR);
requestHideSelf(0);
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
@@ -1740,6 +1711,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra(SettingsActivity.EXTRA_SHOW_HOME_AS_UP, false);
+ intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY, extraEntryValue);
startActivity(intent);
}
@@ -1751,6 +1723,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
languageSelectionTitle,
getString(ApplicationUtils.getActivityTitleResId(this, SettingsActivity.class))
};
+ final String imeId = mRichImm.getInputMethodIdOfThisIme();
final OnClickListener listener = new OnClickListener() {
@Override
public void onClick(DialogInterface di, int position) {
@@ -1758,7 +1731,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
switch (position) {
case 0:
final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
- mRichImm.getInputMethodIdOfThisIme(),
+ imeId,
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -1766,7 +1739,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
startActivity(intent);
break;
case 1:
- launchSettings();
+ launchSettings(SettingsActivity.EXTRA_ENTRY_VALUE_LONG_PRESS_COMMA);
break;
}
}
@@ -1798,45 +1771,45 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
dialog.show();
}
- // TODO: can this be removed somehow without breaking the tests?
@UsedForTesting
- /* package for test */ SuggestedWords getSuggestedWordsForTest() {
+ SuggestedWords getSuggestedWordsForTest() {
// You may not use this method for anything else than debug
- return DEBUG ? mInputLogic.mSuggestedWords : null;
+ return DebugFlags.DEBUG_ENABLED ? mInputLogic.mSuggestedWords : null;
}
// DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
@UsedForTesting
- /* package for test */ void waitForLoadingDictionaries(final long timeout, final TimeUnit unit)
+ void waitForLoadingDictionaries(final long timeout, final TimeUnit unit)
throws InterruptedException {
mDictionaryFacilitator.waitForLoadingDictionariesForTesting(timeout, unit);
}
// DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
@UsedForTesting
- /* package for test */ void replaceDictionariesForTest(final Locale locale) {
+ void replaceDictionariesForTest(final Locale locale) {
final SettingsValues settingsValues = mSettings.getCurrent();
mDictionaryFacilitator.resetDictionaries(this, locale,
settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
- false /* forceReloadMainDictionary */, this /* listener */);
+ false /* forceReloadMainDictionary */,
+ settingsValues.mAccount, "", /* dictionaryNamePrefix */
+ this /* DictionaryInitializationListener */);
}
// DO NOT USE THIS for any other purpose than testing.
@UsedForTesting
- /* package for test */ void clearPersonalizedDictionariesForTest() {
- mDictionaryFacilitator.clearUserHistoryDictionary();
- mDictionaryFacilitator.clearPersonalizationDictionary();
+ void clearPersonalizedDictionariesForTest() {
+ mDictionaryFacilitator.clearUserHistoryDictionary(this);
}
@UsedForTesting
- /* package for test */ List<InputMethodSubtype> getEnabledSubtypesForTest() {
+ List<InputMethodSubtype> getEnabledSubtypesForTest() {
return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList(
true /* allowsImplicitlySelectedSubtypes */) : new ArrayList<InputMethodSubtype>();
}
public void dumpDictionaryForDebug(final String dictName) {
- if (mDictionaryFacilitator.getLocale() == null) {
- resetSuggest();
+ if (!mDictionaryFacilitator.isActive()) {
+ resetDictionaryFacilitatorIfNecessary();
}
mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
}
@@ -1862,6 +1835,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
p.println(" Keyboard mode = " + keyboardMode);
final SettingsValues settingsValues = mSettings.getCurrent();
p.println(settingsValues.dump());
+ p.println(mDictionaryFacilitator.dump(this /* context */));
// TODO: Dump all settings values
}
diff --git a/java/src/com/android/inputmethod/latin/NgramContext.java b/java/src/com/android/inputmethod/latin/NgramContext.java
new file mode 100644
index 000000000..9682fb8a4
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/NgramContext.java
@@ -0,0 +1,291 @@
+/*
+ * 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.text.TextUtils;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.common.StringUtils;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Class to represent information of previous words. This class is used to add n-gram entries
+ * into binary dictionaries, to get predictions, and to get suggestions.
+ */
+public class NgramContext {
+ @Nonnull
+ public static final NgramContext EMPTY_PREV_WORDS_INFO =
+ new NgramContext(WordInfo.EMPTY_WORD_INFO);
+ @Nonnull
+ public static final NgramContext BEGINNING_OF_SENTENCE =
+ new NgramContext(WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO);
+
+ public static final String BEGINNING_OF_SENTENCE_TAG = "<S>";
+
+ public static final String CONTEXT_SEPARATOR = " ";
+
+ public static NgramContext getEmptyPrevWordsContext(int maxPrevWordCount) {
+ return new NgramContext(maxPrevWordCount, WordInfo.EMPTY_WORD_INFO);
+ }
+
+ /**
+ * Word information used to represent previous words information.
+ */
+ public static class WordInfo {
+ @Nonnull
+ public static final WordInfo EMPTY_WORD_INFO = new WordInfo(null);
+ @Nonnull
+ public static final WordInfo BEGINNING_OF_SENTENCE_WORD_INFO = new WordInfo();
+
+ // This is an empty char sequence when mIsBeginningOfSentence is true.
+ public final CharSequence mWord;
+ // TODO: Have sentence separator.
+ // Whether the current context is beginning of sentence or not. This is true when composing
+ // at the beginning of an input field or composing a word after a sentence separator.
+ public final boolean mIsBeginningOfSentence;
+
+ // Beginning of sentence.
+ private WordInfo() {
+ mWord = "";
+ mIsBeginningOfSentence = true;
+ }
+
+ public WordInfo(final CharSequence word) {
+ mWord = word;
+ mIsBeginningOfSentence = false;
+ }
+
+ public boolean isValid() {
+ return mWord != null;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(new Object[] { mWord, mIsBeginningOfSentence } );
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof WordInfo)) return false;
+ final WordInfo wordInfo = (WordInfo)o;
+ if (mWord == null || wordInfo.mWord == null) {
+ return mWord == wordInfo.mWord
+ && mIsBeginningOfSentence == wordInfo.mIsBeginningOfSentence;
+ }
+ return TextUtils.equals(mWord, wordInfo.mWord)
+ && mIsBeginningOfSentence == wordInfo.mIsBeginningOfSentence;
+ }
+ }
+
+ // The words immediately before the considered word. EMPTY_WORD_INFO element means we don't
+ // have any context for that previous word including the "beginning of sentence context" - we
+ // just don't know what to predict using the information. An example of that is after a comma.
+ // For simplicity of implementation, elements may also be EMPTY_WORD_INFO transiently after the
+ // WordComposer was reset and before starting a new composing word, but we should never be
+ // calling getSuggetions* in this situation.
+ private final WordInfo[] mPrevWordsInfo;
+ private final int mPrevWordsCount;
+
+ private final int mMaxPrevWordCount;
+
+ // Construct from the previous word information.
+ public NgramContext(final WordInfo... prevWordsInfo) {
+ this(DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM, prevWordsInfo);
+ }
+
+ public NgramContext(final int maxPrevWordCount, final WordInfo... prevWordsInfo) {
+ mPrevWordsInfo = prevWordsInfo;
+ mPrevWordsCount = prevWordsInfo.length;
+ mMaxPrevWordCount = maxPrevWordCount;
+ }
+
+ /**
+ * Create next prevWordsInfo using current prevWordsInfo.
+ */
+ @Nonnull
+ public NgramContext getNextNgramContext(final WordInfo wordInfo) {
+ final int nextPrevWordCount = Math.min(mMaxPrevWordCount, mPrevWordsCount + 1);
+ final WordInfo[] prevWordsInfo = new WordInfo[nextPrevWordCount];
+ prevWordsInfo[0] = wordInfo;
+ System.arraycopy(mPrevWordsInfo, 0, prevWordsInfo, 1, nextPrevWordCount - 1);
+ return new NgramContext(mMaxPrevWordCount, prevWordsInfo);
+ }
+
+
+ /**
+ * Extracts the previous words context.
+ *
+ * @return a String with the previous words separated by white space.
+ */
+ public String extractPrevWordsContext() {
+ final ArrayList<String> terms = new ArrayList<>();
+ for (int i = mPrevWordsInfo.length - 1; i >= 0; --i) {
+ if (mPrevWordsInfo[i] != null && mPrevWordsInfo[i].isValid()) {
+ final NgramContext.WordInfo wordInfo = mPrevWordsInfo[i];
+ if (wordInfo.mIsBeginningOfSentence) {
+ terms.add(BEGINNING_OF_SENTENCE_TAG);
+ } else {
+ final String term = wordInfo.mWord.toString();
+ if (!term.isEmpty()) {
+ terms.add(term);
+ }
+ }
+ }
+ }
+ return TextUtils.join(CONTEXT_SEPARATOR, terms);
+ }
+
+ /**
+ * Extracts the previous words context.
+ *
+ * @return a String array with the previous words.
+ */
+ public String[] extractPrevWordsContextArray() {
+ final ArrayList<String> prevTermList = new ArrayList<>();
+ for (int i = mPrevWordsInfo.length - 1; i >= 0; --i) {
+ if (mPrevWordsInfo[i] != null && mPrevWordsInfo[i].isValid()) {
+ final NgramContext.WordInfo wordInfo = mPrevWordsInfo[i];
+ if (wordInfo.mIsBeginningOfSentence) {
+ prevTermList.add(BEGINNING_OF_SENTENCE_TAG);
+ } else {
+ final String term = wordInfo.mWord.toString();
+ if (!term.isEmpty()) {
+ prevTermList.add(term);
+ }
+ }
+ }
+ }
+ final String[] contextStringArray = prevTermList.toArray(new String[prevTermList.size()]);
+ return contextStringArray;
+ }
+
+ public boolean isValid() {
+ return mPrevWordsCount > 0 && mPrevWordsInfo[0].isValid();
+ }
+
+ public boolean isBeginningOfSentenceContext() {
+ return mPrevWordsCount > 0 && mPrevWordsInfo[0].mIsBeginningOfSentence;
+ }
+
+ // n is 1-indexed.
+ // TODO: Remove
+ public CharSequence getNthPrevWord(final int n) {
+ if (n <= 0 || n > mPrevWordsCount) {
+ return null;
+ }
+ return mPrevWordsInfo[n - 1].mWord;
+ }
+
+ // n is 1-indexed.
+ @UsedForTesting
+ public boolean isNthPrevWordBeginningOfSentence(final int n) {
+ if (n <= 0 || n > mPrevWordsCount) {
+ return false;
+ }
+ return mPrevWordsInfo[n - 1].mIsBeginningOfSentence;
+ }
+
+ public void outputToArray(final int[][] codePointArrays,
+ final boolean[] isBeginningOfSentenceArray) {
+ for (int i = 0; i < mPrevWordsCount; i++) {
+ final WordInfo wordInfo = mPrevWordsInfo[i];
+ if (wordInfo == null || !wordInfo.isValid()) {
+ codePointArrays[i] = new int[0];
+ isBeginningOfSentenceArray[i] = false;
+ continue;
+ }
+ codePointArrays[i] = StringUtils.toCodePointArray(wordInfo.mWord);
+ isBeginningOfSentenceArray[i] = wordInfo.mIsBeginningOfSentence;
+ }
+ }
+
+ public int getPrevWordCount() {
+ return mPrevWordsCount;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashValue = 0;
+ for (final WordInfo wordInfo : mPrevWordsInfo) {
+ if (wordInfo == null || !WordInfo.EMPTY_WORD_INFO.equals(wordInfo)) {
+ break;
+ }
+ hashValue ^= wordInfo.hashCode();
+ }
+ return hashValue;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof NgramContext)) return false;
+ final NgramContext prevWordsInfo = (NgramContext)o;
+
+ final int minLength = Math.min(mPrevWordsCount, prevWordsInfo.mPrevWordsCount);
+ for (int i = 0; i < minLength; i++) {
+ if (!mPrevWordsInfo[i].equals(prevWordsInfo.mPrevWordsInfo[i])) {
+ return false;
+ }
+ }
+ final WordInfo[] longerWordsInfo;
+ final int longerWordsInfoCount;
+ if (mPrevWordsCount > prevWordsInfo.mPrevWordsCount) {
+ longerWordsInfo = mPrevWordsInfo;
+ longerWordsInfoCount = mPrevWordsCount;
+ } else {
+ longerWordsInfo = prevWordsInfo.mPrevWordsInfo;
+ longerWordsInfoCount = prevWordsInfo.mPrevWordsCount;
+ }
+ for (int i = minLength; i < longerWordsInfoCount; i++) {
+ if (longerWordsInfo[i] != null
+ && !WordInfo.EMPTY_WORD_INFO.equals(longerWordsInfo[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer builder = new StringBuffer();
+ for (int i = 0; i < mPrevWordsCount; i++) {
+ final WordInfo wordInfo = mPrevWordsInfo[i];
+ builder.append("PrevWord[");
+ builder.append(i);
+ builder.append("]: ");
+ if (wordInfo == null) {
+ builder.append("null. ");
+ continue;
+ }
+ if (!wordInfo.isValid()) {
+ builder.append("Empty. ");
+ continue;
+ }
+ builder.append(wordInfo.mWord);
+ builder.append(", isBeginningOfSentence: ");
+ builder.append(wordInfo.mIsBeginningOfSentence);
+ builder.append(". ");
+ }
+ return builder.toString();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
deleted file mode 100644
index db877ab7a..000000000
--- a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.text.TextUtils;
-
-import com.android.inputmethod.latin.utils.StringUtils;
-
-import java.util.Arrays;
-
-/**
- * Class to represent information of previous words. This class is used to add n-gram entries
- * into binary dictionaries, to get predictions, and to get suggestions.
- */
-public class PrevWordsInfo {
- public static final PrevWordsInfo EMPTY_PREV_WORDS_INFO =
- new PrevWordsInfo(WordInfo.EMPTY_WORD_INFO);
- public static final PrevWordsInfo BEGINNING_OF_SENTENCE =
- new PrevWordsInfo(WordInfo.BEGINNING_OF_SENTENCE);
-
- /**
- * Word information used to represent previous words information.
- */
- public static class WordInfo {
- public static final WordInfo EMPTY_WORD_INFO = new WordInfo(null);
- public static final WordInfo BEGINNING_OF_SENTENCE = new WordInfo();
-
- // This is an empty char sequence when mIsBeginningOfSentence is true.
- public final CharSequence mWord;
- // TODO: Have sentence separator.
- // Whether the current context is beginning of sentence or not. This is true when composing
- // at the beginning of an input field or composing a word after a sentence separator.
- public final boolean mIsBeginningOfSentence;
-
- // Beginning of sentence.
- public WordInfo() {
- mWord = "";
- mIsBeginningOfSentence = true;
- }
-
- public WordInfo(final CharSequence word) {
- mWord = word;
- mIsBeginningOfSentence = false;
- }
-
- public boolean isValid() {
- return mWord != null;
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(new Object[] { mWord, mIsBeginningOfSentence } );
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof WordInfo)) return false;
- final WordInfo wordInfo = (WordInfo)o;
- if (mWord == null || wordInfo.mWord == null) {
- return mWord == wordInfo.mWord
- && mIsBeginningOfSentence == wordInfo.mIsBeginningOfSentence;
- }
- return TextUtils.equals(mWord, wordInfo.mWord)
- && mIsBeginningOfSentence == wordInfo.mIsBeginningOfSentence;
- }
- }
-
- // The words immediately before the considered word. EMPTY_WORD_INFO element means we don't
- // have any context for that previous word including the "beginning of sentence context" - we
- // just don't know what to predict using the information. An example of that is after a comma.
- // For simplicity of implementation, elements may also be EMPTY_WORD_INFO transiently after the
- // WordComposer was reset and before starting a new composing word, but we should never be
- // calling getSuggetions* in this situation.
- public WordInfo[] mPrevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
-
- // Construct from the previous word information.
- public PrevWordsInfo(final WordInfo prevWordInfo) {
- mPrevWordsInfo[0] = prevWordInfo;
- }
-
- // Construct from WordInfo array. n-th element represents (n+1)-th previous word's information.
- public PrevWordsInfo(final WordInfo[] prevWordsInfo) {
- for (int i = 0; i < Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM; i++) {
- mPrevWordsInfo[i] =
- (prevWordsInfo.length > i) ? prevWordsInfo[i] : WordInfo.EMPTY_WORD_INFO;
- }
- }
-
- // Create next prevWordsInfo using current prevWordsInfo.
- public PrevWordsInfo getNextPrevWordsInfo(final WordInfo wordInfo) {
- final WordInfo[] prevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
- prevWordsInfo[0] = wordInfo;
- for (int i = 1; i < prevWordsInfo.length; i++) {
- prevWordsInfo[i] = mPrevWordsInfo[i - 1];
- }
- return new PrevWordsInfo(prevWordsInfo);
- }
-
- public boolean isValid() {
- return mPrevWordsInfo[0].isValid();
- }
-
- public void outputToArray(final int[][] codePointArrays,
- final boolean[] isBeginningOfSentenceArray) {
- for (int i = 0; i < mPrevWordsInfo.length; i++) {
- final WordInfo wordInfo = mPrevWordsInfo[i];
- if (wordInfo == null || !wordInfo.isValid()) {
- codePointArrays[i] = new int[0];
- isBeginningOfSentenceArray[i] = false;
- continue;
- }
- codePointArrays[i] = StringUtils.toCodePointArray(wordInfo.mWord);
- isBeginningOfSentenceArray[i] = wordInfo.mIsBeginningOfSentence;
- }
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(mPrevWordsInfo);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof PrevWordsInfo)) return false;
- final PrevWordsInfo prevWordsInfo = (PrevWordsInfo)o;
- return Arrays.equals(mPrevWordsInfo, prevWordsInfo.mPrevWordsInfo);
- }
-
- @Override
- public String toString() {
- final StringBuffer builder = new StringBuffer();
- for (int i = 0; i < mPrevWordsInfo.length; i++) {
- final WordInfo wordInfo = mPrevWordsInfo[i];
- builder.append("PrevWord[");
- builder.append(i);
- builder.append("]: ");
- if (wordInfo == null || !wordInfo.isValid()) {
- builder.append("Empty. ");
- continue;
- }
- builder.append(wordInfo.mWord);
- builder.append(", isBeginningOfSentence: ");
- builder.append(wordInfo.mIsBeginningOfSentence);
- builder.append(". ");
- }
- return builder.toString();
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
index 56014cbad..e2c562174 100644
--- a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
@@ -17,11 +17,14 @@
package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.internal.KeySpecParser;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
+import javax.annotation.Nullable;
+
/**
* The extended {@link SuggestedWords} class to represent punctuation suggestions.
*
@@ -32,10 +35,12 @@ public final class PunctuationSuggestions extends SuggestedWords {
private PunctuationSuggestions(final ArrayList<SuggestedWordInfo> punctuationsList) {
super(punctuationsList,
null /* rawSuggestions */,
+ null /* typedWord */,
false /* typedWordValid */,
false /* hasAutoCorrectionCandidate */,
false /* isObsoleteSuggestions */,
- INPUT_STYLE_NONE /* inputStyle */);
+ INPUT_STYLE_NONE /* inputStyle */,
+ SuggestedWords.NOT_A_SEQUENCE_NUMBER);
}
/**
@@ -46,17 +51,21 @@ public final class PunctuationSuggestions extends SuggestedWords {
* @return The {@link PunctuationSuggestions} object.
*/
public static PunctuationSuggestions newPunctuationSuggestions(
- final String[] punctuationSpecs) {
- final ArrayList<SuggestedWordInfo> puncuationsList = new ArrayList<>();
- for (final String puncSpec : punctuationSpecs) {
- puncuationsList.add(newHardCodedWordInfo(puncSpec));
+ @Nullable final String[] punctuationSpecs) {
+ if (punctuationSpecs == null || punctuationSpecs.length == 0) {
+ return new PunctuationSuggestions(new ArrayList<SuggestedWordInfo>(0));
+ }
+ final ArrayList<SuggestedWordInfo> punctuationList =
+ new ArrayList<>(punctuationSpecs.length);
+ for (String spec : punctuationSpecs) {
+ punctuationList.add(newHardCodedWordInfo(spec));
}
- return new PunctuationSuggestions(puncuationsList);
+ return new PunctuationSuggestions(punctuationList);
}
/**
* {@inheritDoc}
- * Note that {@link super#getWord(int)} returns a punctuation key specification text.
+ * Note that {@link SuggestedWords#getWord(int)} returns a punctuation key specification text.
* The suggested punctuation should be gotten by parsing the key specification.
*/
@Override
@@ -70,7 +79,7 @@ public final class PunctuationSuggestions extends SuggestedWords {
/**
* {@inheritDoc}
- * Note that {@link super#getWord(int)} returns a punctuation key specification text.
+ * Note that {@link SuggestedWords#getWord(int)} returns a punctuation key specification text.
* The displayed text should be gotten by parsing the key specification.
*/
@Override
@@ -82,7 +91,7 @@ public final class PunctuationSuggestions extends SuggestedWords {
/**
* {@inheritDoc}
* Note that {@link #getWord(int)} returns a suggested punctuation. We should create a
- * {@link SuggestedWordInfo} object that represents a hard coded word.
+ * {@link SuggestedWords.SuggestedWordInfo} object that represents a hard coded word.
*/
@Override
public SuggestedWordInfo getInfo(final int index) {
@@ -105,7 +114,8 @@ public final class PunctuationSuggestions extends SuggestedWords {
}
private static SuggestedWordInfo newHardCodedWordInfo(final String keySpec) {
- return new SuggestedWordInfo(keySpec, SuggestedWordInfo.MAX_SCORE,
+ return new SuggestedWordInfo(keySpec, "" /* prevWordsContext */,
+ SuggestedWordInfo.MAX_SCORE,
SuggestedWordInfo.KIND_HARDCODED,
Dictionary.DICTIONARY_HARDCODED,
SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
index 5d4fc5861..7b1a53a6e 100644
--- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
@@ -16,8 +16,8 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.common.ComposedData;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import java.util.ArrayList;
@@ -40,7 +40,7 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
public ReadOnlyBinaryDictionary(final String filename, final long offset, final long length,
final boolean useFullEditDistance, final Locale locale, final String dictType) {
- super(dictType);
+ super(dictType, locale);
mBinaryDictionary = new BinaryDictionary(filename, offset, length, useFullEditDistance,
locale, dictType, false /* isUpdatable */);
}
@@ -50,14 +50,16 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
}
@Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData,
+ final NgramContext ngramContext, final long proximityInfoHandle,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int sessionId, final float[] inOutLanguageWeight) {
+ final int sessionId, final float weightForLocale,
+ final float[] inOutWeightOfLangModelVsSpatialModel) {
if (mLock.readLock().tryLock()) {
try {
- return mBinaryDictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
- settingsValuesForSuggestion, sessionId, inOutLanguageWeight);
+ return mBinaryDictionary.getSuggestions(composedData, ngramContext,
+ proximityInfoHandle, settingsValuesForSuggestion, sessionId,
+ weightForLocale, inOutWeightOfLangModelVsSpatialModel);
} finally {
mLock.readLock().unlock();
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 744b0321a..08e8fe346 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -16,13 +16,14 @@
package com.android.inputmethod.latin;
-import android.graphics.Color;
+import static com.android.inputmethod.latin.define.DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH;
+
import android.inputmethodservice.InputMethodService;
import android.os.Build;
+import android.os.Bundle;
import android.text.SpannableStringBuilder;
-import android.text.Spanned;
import android.text.TextUtils;
-import android.text.style.BackgroundColorSpan;
+import android.text.style.CharacterStyle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
@@ -33,16 +34,20 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import com.android.inputmethod.compat.InputConnectionCompatUtils;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.UnicodeSurrogate;
+import com.android.inputmethod.latin.common.StringUtils;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
+import com.android.inputmethod.latin.inputlogic.PrivateCommandPerformer;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
-import com.android.inputmethod.latin.utils.PrevWordsInfoUtils;
+import com.android.inputmethod.latin.utils.NgramContextUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
import com.android.inputmethod.latin.utils.SpannableStringUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
-import java.util.Arrays;
+import javax.annotation.Nonnull;
/**
* Enrichment class for InputConnection to simplify interaction and add functionality.
@@ -52,15 +57,15 @@ import java.util.Arrays;
* all the time to find out what text is in the buffer, when we need it to determine caps mode
* for example.
*/
-public final class RichInputConnection {
+public final class RichInputConnection implements PrivateCommandPerformer {
private static final String TAG = RichInputConnection.class.getSimpleName();
private static final boolean DBG = false;
private static final boolean DEBUG_PREVIOUS_TEXT = false;
private static final boolean DEBUG_BATCH_NESTING = false;
// Provision for long words and separators between the words.
- private static final int LOOKBACK_CHARACTER_NUM = Constants.DICTIONARY_MAX_WORD_LENGTH
- * (Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM + 1) /* words */
- + Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM /* separators */;
+ private static final int LOOKBACK_CHARACTER_NUM = DICTIONARY_MAX_WORD_LENGTH
+ * (DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM + 1) /* words */
+ + DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM /* separators */;
private static final int INVALID_CURSOR_POSITION = -1;
/**
@@ -88,26 +93,25 @@ public final class RichInputConnection {
private final StringBuilder mComposingText = new StringBuilder();
/**
- * This variable is a temporary object used in
- * {@link #commitTextWithBackgroundColor(CharSequence, int, int)} to avoid object creation.
+ * This variable is a temporary object used in {@link #commitText(CharSequence,int)}
+ * to avoid object creation.
*/
private SpannableStringBuilder mTempObjectForCommitText = new SpannableStringBuilder();
- /**
- * This variable is used to track whether the last committed text had the background color or
- * not.
- * TODO: Omit this flag if possible.
- */
- private boolean mLastCommittedTextHasBackgroundColor = false;
private final InputMethodService mParent;
InputConnection mIC;
int mNestLevel;
+
public RichInputConnection(final InputMethodService parent) {
mParent = parent;
mIC = null;
mNestLevel = 0;
}
+ public boolean isConnected() {
+ return mIC != null;
+ }
+
private void checkConsistencyForDebug() {
final ExtractedTextRequest r = new ExtractedTextRequest();
r.hintMaxChars = 0;
@@ -143,15 +147,14 @@ public final class RichInputConnection {
public void beginBatchEdit() {
if (++mNestLevel == 1) {
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) {
+ if (isConnected()) {
mIC.beginBatchEdit();
}
} else {
if (DBG) {
throw new RuntimeException("Nest level too deep");
- } else {
- Log.e(TAG, "Nest level too deep : " + mNestLevel);
}
+ Log.e(TAG, "Nest level too deep : " + mNestLevel);
}
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
@@ -159,7 +162,7 @@ public final class RichInputConnection {
public void endBatchEdit() {
if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead
- if (--mNestLevel == 0 && null != mIC) {
+ if (--mNestLevel == 0 && isConnected()) {
mIC.endBatchEdit();
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
@@ -191,7 +194,7 @@ public final class RichInputConnection {
Log.d(TAG, "Will try to retrieve text later.");
return false;
}
- if (null != mIC && shouldFinishComposition) {
+ if (isConnected() && shouldFinishComposition) {
mIC.finishComposingText();
}
return true;
@@ -207,8 +210,9 @@ public final class RichInputConnection {
mIC = mParent.getCurrentInputConnection();
// Call upon the inputconnection directly since our own method is using the cache, and
// we want to refresh it.
- final CharSequence textBeforeCursor = null == mIC ? null :
- mIC.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
+ final CharSequence textBeforeCursor = isConnected()
+ ? mIC.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0)
+ : null;
if (null == textBeforeCursor) {
// For some reason the app thinks we are not connected to it. This looks like a
// framework bug... Fall back to ground state and return false.
@@ -237,39 +241,18 @@ public final class RichInputConnection {
// it works, but it's wrong and should be fixed.
mCommittedTextBeforeComposingText.append(mComposingText);
mComposingText.setLength(0);
- // TODO: Clear this flag in setComposingRegion() and setComposingText() as well if needed.
- mLastCommittedTextHasBackgroundColor = false;
- if (null != mIC) {
+ if (isConnected()) {
mIC.finishComposingText();
}
}
/**
- * Synonym of {@code commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT}.
+ * Calls {@link InputConnection#commitText(CharSequence, int)}.
+ *
* @param text The text to commit. This may include styles.
- * See {@link InputConnection#commitText(CharSequence, int)}.
* @param newCursorPosition The new cursor position around the text.
- * See {@link InputConnection#commitText(CharSequence, int)}.
*/
public void commitText(final CharSequence text, final int newCursorPosition) {
- commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT, text.length());
- }
-
- /**
- * Calls {@link InputConnection#commitText(CharSequence, int)} with the given background color.
- * @param text The text to commit. This may include styles.
- * See {@link InputConnection#commitText(CharSequence, int)}.
- * @param newCursorPosition The new cursor position around the text.
- * See {@link InputConnection#commitText(CharSequence, int)}.
- * @param color The background color to be attached. Set {@link Color#TRANSPARENT} to disable
- * the background color. Note that this method specifies {@link BackgroundColorSpan} with
- * {@link Spanned#SPAN_COMPOSING} flag, meaning that the background color persists until
- * {@link #finishComposingText()} is called.
- * @param coloredTextLength the length of text, in Java chars, which should be rendered with
- * the given background color.
- */
- public void commitTextWithBackgroundColor(final CharSequence text, final int newCursorPosition,
- final int color, final int coloredTextLength) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
mCommittedTextBeforeComposingText.append(text);
@@ -279,46 +262,34 @@ public final class RichInputConnection {
mExpectedSelStart += text.length() - mComposingText.length();
mExpectedSelEnd = mExpectedSelStart;
mComposingText.setLength(0);
- mLastCommittedTextHasBackgroundColor = false;
- if (null != mIC) {
- if (color == Color.TRANSPARENT) {
- mIC.commitText(text, newCursorPosition);
- } else {
- mTempObjectForCommitText.clear();
- mTempObjectForCommitText.append(text);
- final BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(color);
- final int spanLength = Math.min(coloredTextLength, text.length());
- mTempObjectForCommitText.setSpan(backgroundColorSpan, 0, spanLength,
- Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- mIC.commitText(mTempObjectForCommitText, newCursorPosition);
- mLastCommittedTextHasBackgroundColor = true;
+ if (isConnected()) {
+ mTempObjectForCommitText.clear();
+ mTempObjectForCommitText.append(text);
+ final CharacterStyle[] spans = mTempObjectForCommitText.getSpans(
+ 0, text.length(), CharacterStyle.class);
+ for (final CharacterStyle span : spans) {
+ final int spanStart = mTempObjectForCommitText.getSpanStart(span);
+ final int spanEnd = mTempObjectForCommitText.getSpanEnd(span);
+ final int spanFlags = mTempObjectForCommitText.getSpanFlags(span);
+ // We have to adjust the end of the span to include an additional character.
+ // This is to avoid splitting a unicode surrogate pair.
+ // See com.android.inputmethod.latin.common.Constants.UnicodeSurrogate
+ // See https://b.corp.google.com/issues/19255233
+ if (0 < spanEnd && spanEnd < mTempObjectForCommitText.length()) {
+ final char spanEndChar = mTempObjectForCommitText.charAt(spanEnd - 1);
+ final char nextChar = mTempObjectForCommitText.charAt(spanEnd);
+ if (UnicodeSurrogate.isLowSurrogate(spanEndChar)
+ && UnicodeSurrogate.isHighSurrogate(nextChar)) {
+ mTempObjectForCommitText.setSpan(span, spanStart, spanEnd + 1, spanFlags);
+ }
+ }
}
+ mIC.commitText(mTempObjectForCommitText, newCursorPosition);
}
}
- /**
- * Removes the background color from the highlighted text if necessary. Should be called while
- * there is no on-going composing text.
- *
- * <p>CAVEAT: This method internally calls {@link InputConnection#finishComposingText()}.
- * Be careful of any unexpected side effects.</p>
- */
- public void removeBackgroundColorFromHighlightedTextIfNecessary() {
- // TODO: We haven't yet full tested if we really need to check this flag or not. Omit this
- // flag if everything works fine without this condition.
- if (!mLastCommittedTextHasBackgroundColor) {
- return;
- }
- if (mComposingText.length() > 0) {
- Log.e(TAG, "clearSpansWithComposingFlags should be called when composing text is " +
- "empty. mComposingText=" + mComposingText);
- return;
- }
- finishComposingText();
- }
-
public CharSequence getSelectedText(final int flags) {
- return (null == mIC) ? null : mIC.getSelectedText(flags);
+ return isConnected() ? mIC.getSelectedText(flags) : null;
}
public boolean canDeleteCharacters() {
@@ -343,16 +314,17 @@ public final class RichInputConnection {
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 (!isConnected()) {
+ return Constants.TextUtils.CAP_MODE_OFF;
+ }
if (!TextUtils.isEmpty(mComposingText)) {
if (hasSpaceBefore) {
// If we have some composing text and a space before, then we should have
// MODE_CHARACTERS and MODE_WORDS on.
return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & inputType;
- } else {
- // We have some composing text - we should be in MODE_CHARACTERS only.
- return TextUtils.CAP_MODE_CHARACTERS & inputType;
}
+ // We have some composing text - we should be in MODE_CHARACTERS only.
+ return TextUtils.CAP_MODE_CHARACTERS & inputType;
}
// TODO: this will generally work, but there may be cases where the buffer contains SOME
// information but not enough to determine the caps mode accurately. This may happen after
@@ -367,7 +339,9 @@ public final class RichInputConnection {
}
// This never calls InputConnection#getCapsMode - in fact, it's a static method that
// never blocks or initiates IPC.
- return CapsModeUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType,
+ // TODO: don't call #toString() here. Instead, all accesses to
+ // mCommittedTextBeforeComposingText should be done on the main thread.
+ return CapsModeUtils.getCapsMode(mCommittedTextBeforeComposingText.toString(), inputType,
spacingAndPunctuations, hasSpaceBefore);
}
@@ -402,12 +376,12 @@ public final class RichInputConnection {
return s;
}
mIC = mParent.getCurrentInputConnection();
- return (null == mIC) ? null : mIC.getTextBeforeCursor(n, flags);
+ return isConnected() ? mIC.getTextBeforeCursor(n, flags) : null;
}
public CharSequence getTextAfterCursor(final int n, final int flags) {
mIC = mParent.getCurrentInputConnection();
- return (null == mIC) ? null : mIC.getTextAfterCursor(n, flags);
+ return isConnected() ? mIC.getTextAfterCursor(n, flags) : null;
}
public void deleteSurroundingText(final int beforeLength, final int afterLength) {
@@ -434,7 +408,7 @@ public final class RichInputConnection {
mExpectedSelEnd -= mExpectedSelStart;
mExpectedSelStart = 0;
}
- if (null != mIC) {
+ if (isConnected()) {
mIC.deleteSurroundingText(beforeLength, afterLength);
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
@@ -442,7 +416,7 @@ public final class RichInputConnection {
public void performEditorAction(final int actionId) {
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) {
+ if (isConnected()) {
mIC.performEditorAction(actionId);
}
}
@@ -494,7 +468,7 @@ public final class RichInputConnection {
break;
}
}
- if (null != mIC) {
+ if (isConnected()) {
mIC.sendKeyEvent(keyEvent);
}
}
@@ -517,7 +491,7 @@ public final class RichInputConnection {
mCommittedTextBeforeComposingText.append(
textBeforeCursor.subSequence(0, indexOfStartOfComposingText));
}
- if (null != mIC) {
+ if (isConnected()) {
mIC.setComposingRegion(start, end);
}
}
@@ -531,7 +505,7 @@ public final class RichInputConnection {
mComposingText.append(text);
// TODO: support values of newCursorPosition != 1. At this time, this is never called with
// newCursorPosition != 1.
- if (null != mIC) {
+ if (isConnected()) {
mIC.setComposingText(text, newCursorPosition);
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
@@ -556,7 +530,7 @@ public final class RichInputConnection {
}
mExpectedSelStart = start;
mExpectedSelEnd = end;
- if (null != mIC) {
+ if (isConnected()) {
final boolean isIcValid = mIC.setSelection(start, end);
if (!isIcValid) {
return false;
@@ -570,7 +544,7 @@ public final class RichInputConnection {
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
// This has no effect on the text field and does not change its content. It only makes
// TextView flash the text for a second based on indices contained in the argument.
- if (null != mIC) {
+ if (isConnected()) {
mIC.commitCorrection(correctionInfo);
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
@@ -586,18 +560,19 @@ public final class RichInputConnection {
mExpectedSelStart += text.length() - mComposingText.length();
mExpectedSelEnd = mExpectedSelStart;
mComposingText.setLength(0);
- if (null != mIC) {
+ if (isConnected()) {
mIC.commitCompletion(completionInfo);
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@SuppressWarnings("unused")
- public PrevWordsInfo getPrevWordsInfoFromNthPreviousWord(
+ @Nonnull
+ public NgramContext getNgramContextFromNthPreviousWord(
final SpacingAndPunctuations spacingAndPunctuations, final int n) {
mIC = mParent.getCurrentInputConnection();
- if (null == mIC) {
- return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ if (!isConnected()) {
+ return NgramContext.EMPTY_PREV_WORDS_INFO;
}
final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
if (DEBUG_PREVIOUS_TEXT && null != prev) {
@@ -618,14 +593,10 @@ public final class RichInputConnection {
}
}
}
- return PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
+ return NgramContextUtils.getNgramContextFromNthPreviousWord(
prev, spacingAndPunctuations, n);
}
- private static boolean isSeparator(final int code, final int[] sortedSeparators) {
- return Arrays.binarySearch(sortedSeparators, code) >= 0;
- }
-
private static boolean isPartOfCompositionForScript(final int codePoint,
final SpacingAndPunctuations spacingAndPunctuations, final int scriptId) {
// We always consider word connectors part of compositions.
@@ -645,7 +616,7 @@ public final class RichInputConnection {
public TextRange getWordRangeAtCursor(final SpacingAndPunctuations spacingAndPunctuations,
final int scriptId) {
mIC = mParent.getCurrentInputConnection();
- if (mIC == null) {
+ if (!isConnected()) {
return null;
}
final CharSequence before = mIC.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE,
@@ -740,17 +711,19 @@ public final class RichInputConnection {
return TextUtils.equals(text, beforeText);
}
- public boolean revertDoubleSpacePeriod() {
+ public boolean revertDoubleSpacePeriod(final SpacingAndPunctuations spacingAndPunctuations) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
// Here we test whether we indeed have a period and a space before us. This should not
// be needed, but it's there just in case something went wrong.
final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0);
- if (!TextUtils.equals(Constants.STRING_PERIOD_AND_SPACE, textBeforeCursor)) {
+ if (!TextUtils.equals(spacingAndPunctuations.mSentenceSeparatorAndSpace,
+ textBeforeCursor)) {
// Theoretically we should not be coming here if there isn't ". " before the
// cursor, but the application may be changing the text while we are typing, so
// anything goes. We should not crash.
- Log.d(TAG, "Tried to revert double-space combo but we didn't find "
- + "\"" + Constants.STRING_PERIOD_AND_SPACE + "\" just before the cursor.");
+ Log.d(TAG, "Tried to revert double-space combo but we didn't find \""
+ + spacingAndPunctuations.mSentenceSeparatorAndSpace
+ + "\" just before the cursor.");
return false;
}
// Double-space results in ". ". A backspace to cancel this should result in a single
@@ -847,17 +820,32 @@ public final class RichInputConnection {
/**
* Try to get the text from the editor to expose lies the framework may have been
- * telling us. Concretely, when the device rotates, the frameworks tells us about where the
- * cursor used to be initially in the editor at the time it first received the focus; this
+ * telling us. Concretely, when the device rotates and when the keyboard reopens in the same
+ * text field after having been closed with the back key, the frameworks tells us about where
+ * the cursor used to be initially in the editor at the time it first received the focus; this
* may be completely different from the place it is upon rotation. Since we don't have any
* means to get the real value, try at least to ask the text view for some characters and
* detect the most damaging cases: when the cursor position is declared to be much smaller
* than it really is.
*/
public void tryFixLyingCursorPosition() {
+ mIC = mParent.getCurrentInputConnection();
final CharSequence textBeforeCursor = getTextBeforeCursor(
Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
- if (null == textBeforeCursor) {
+ final CharSequence selectedText = isConnected() ? mIC.getSelectedText(0 /* flags */) : null;
+ if (null == textBeforeCursor ||
+ (!TextUtils.isEmpty(selectedText) && mExpectedSelEnd == mExpectedSelStart)) {
+ // If textBeforeCursor is null, we have no idea what kind of text field we have or if
+ // thinking about the "cursor position" actually makes any sense. In this case we
+ // remember a meaningless cursor position. Contrast this with an empty string, which is
+ // valid and should mean the cursor is at the start of the text.
+ // Also, if we expect we don't have a selection but we DO have non-empty selected text,
+ // then the framework lied to us about the cursor position. In this case, we should just
+ // revert to the most basic behavior possible for the next action (backspace in
+ // particular comes to mind), so we remember a meaningless cursor position which should
+ // result in degraded behavior from the next input.
+ // Interestingly, in either case, chances are any action the user takes next will result
+ // in a call to onUpdateSelection, which should set things right.
mExpectedSelStart = mExpectedSelEnd = Constants.NOT_A_CURSOR_POSITION;
} else {
final int textLength = textBeforeCursor.length();
@@ -880,6 +868,15 @@ public final class RichInputConnection {
}
}
+ @Override
+ public boolean performPrivateCommand(final String action, final Bundle data) {
+ mIC = mParent.getCurrentInputConnection();
+ if (!isConnected()) {
+ return false;
+ }
+ return mIC.performPrivateCommand(action, data);
+ }
+
public int getExpectedSelectionStart() {
return mExpectedSelStart;
}
@@ -920,8 +917,6 @@ public final class RichInputConnection {
}
}
- private boolean mCursorAnchorInfoMonitorEnabled = false;
-
/**
* Requests the editor to call back {@link InputMethodManager#updateCursorAnchorInfo}.
* @param enableMonitor {@code true} to request the editor to call back the method whenever the
@@ -936,22 +931,10 @@ public final class RichInputConnection {
public boolean requestCursorUpdates(final boolean enableMonitor,
final boolean requestImmediateCallback) {
mIC = mParent.getCurrentInputConnection();
- final boolean scheduled;
- if (null != mIC) {
- scheduled = InputConnectionCompatUtils.requestCursorUpdates(mIC, enableMonitor,
- requestImmediateCallback);
- } else {
- scheduled = false;
+ if (!isConnected()) {
+ return false;
}
- mCursorAnchorInfoMonitorEnabled = (scheduled && enableMonitor);
- return scheduled;
- }
-
- /**
- * @return {@code true} if the application reported that the monitor mode of
- * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} is currently enabled.
- */
- public boolean isCursorAnchorInfoMonitorEnabled() {
- return mCursorAnchorInfoMonitorEnabled;
+ return InputConnectionCompatUtils.requestCursorUpdates(
+ mIC, enableMonitor, requestImmediateCallback);
}
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 7cf4eff92..ef946c8bc 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -16,10 +16,12 @@
package com.android.inputmethod.latin;
-import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE;
+import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
import android.content.Context;
import android.content.SharedPreferences;
+import android.inputmethodservice.InputMethodService;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
@@ -28,20 +30,31 @@ import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
/**
* Enrichment class for InputMethodManager to simplify interaction and add functionality.
*/
-public final class RichInputMethodManager {
+// non final for easy mocking.
+public class RichInputMethodManager {
private static final String TAG = RichInputMethodManager.class.getSimpleName();
+ private static final boolean DEBUG = false;
private RichInputMethodManager() {
// This utility class is not publicly instantiable.
@@ -49,12 +62,12 @@ public final class RichInputMethodManager {
private static final RichInputMethodManager sInstance = new RichInputMethodManager();
+ private Context mContext;
private InputMethodManagerCompatWrapper mImmWrapper;
private InputMethodInfoCache mInputMethodInfoCache;
- final HashMap<InputMethodInfo, List<InputMethodSubtype>>
- mSubtypeListCacheWithImplicitlySelectedSubtypes = new HashMap<>();
- final HashMap<InputMethodInfo, List<InputMethodSubtype>>
- mSubtypeListCacheWithoutImplicitlySelectedSubtypes = new HashMap<>();
+ private RichInputMethodSubtype mCurrentRichInputMethodSubtype;
+ private InputMethodInfo mShortcutInputMethodInfo;
+ private InputMethodSubtype mShortcutSubtype;
private static final int INDEX_NOT_FOUND = -1;
@@ -82,20 +95,24 @@ public final class RichInputMethodManager {
return;
}
mImmWrapper = new InputMethodManagerCompatWrapper(context);
+ mContext = context;
mInputMethodInfoCache = new InputMethodInfoCache(
mImmWrapper.mImm, context.getPackageName());
// Initialize additional subtypes.
SubtypeLocaleUtils.init(context);
- final InputMethodSubtype[] additionalSubtypes = getAdditionalSubtypes(context);
- setAdditionalInputMethodSubtypes(additionalSubtypes);
+ final InputMethodSubtype[] additionalSubtypes = getAdditionalSubtypes();
+ mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
+ getInputMethodIdOfThisIme(), additionalSubtypes);
+
+ // Initialize the current input method subtype and the shortcut IME.
+ refreshSubtypeCaches();
}
- public InputMethodSubtype[] getAdditionalSubtypes(final Context context) {
- SubtypeLocaleUtils.init(context);
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ public InputMethodSubtype[] getAdditionalSubtypes() {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(
- prefs, context.getResources());
+ prefs, mContext.getResources());
return AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes);
}
@@ -212,33 +229,57 @@ public final class RichInputMethodManager {
private final InputMethodManager mImm;
private final String mImePackageName;
- private InputMethodInfo mCachedValue;
+ private InputMethodInfo mCachedThisImeInfo;
+ private final HashMap<InputMethodInfo, List<InputMethodSubtype>>
+ mCachedSubtypeListWithImplicitlySelected;
+ private final HashMap<InputMethodInfo, List<InputMethodSubtype>>
+ mCachedSubtypeListOnlyExplicitlySelected;
public InputMethodInfoCache(final InputMethodManager imm, final String imePackageName) {
mImm = imm;
mImePackageName = imePackageName;
+ mCachedSubtypeListWithImplicitlySelected = new HashMap<>();
+ mCachedSubtypeListOnlyExplicitlySelected = new HashMap<>();
}
- public synchronized InputMethodInfo get() {
- if (mCachedValue != null) {
- return mCachedValue;
+ public synchronized InputMethodInfo getInputMethodOfThisIme() {
+ if (mCachedThisImeInfo != null) {
+ return mCachedThisImeInfo;
}
for (final InputMethodInfo imi : mImm.getInputMethodList()) {
if (imi.getPackageName().equals(mImePackageName)) {
- mCachedValue = imi;
+ mCachedThisImeInfo = imi;
return imi;
}
}
throw new RuntimeException("Input method id for " + mImePackageName + " not found.");
}
+ public synchronized List<InputMethodSubtype> getEnabledInputMethodSubtypeList(
+ final InputMethodInfo imi, final boolean allowsImplicitlySelectedSubtypes) {
+ final HashMap<InputMethodInfo, List<InputMethodSubtype>> cache =
+ allowsImplicitlySelectedSubtypes
+ ? mCachedSubtypeListWithImplicitlySelected
+ : mCachedSubtypeListOnlyExplicitlySelected;
+ final List<InputMethodSubtype> cachedList = cache.get(imi);
+ if (cachedList != null) {
+ return cachedList;
+ }
+ final List<InputMethodSubtype> result = mImm.getEnabledInputMethodSubtypeList(
+ imi, allowsImplicitlySelectedSubtypes);
+ cache.put(imi, result);
+ return result;
+ }
+
public synchronized void clear() {
- mCachedValue = null;
+ mCachedThisImeInfo = null;
+ mCachedSubtypeListWithImplicitlySelected.clear();
+ mCachedSubtypeListOnlyExplicitlySelected.clear();
}
}
public InputMethodInfo getInputMethodInfoOfThisIme() {
- return mInputMethodInfoCache.get();
+ return mInputMethodInfoCache.getInputMethodOfThisIme();
}
public String getInputMethodIdOfThisIme() {
@@ -246,24 +287,20 @@ public final class RichInputMethodManager {
}
public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) {
- return checkIfSubtypeBelongsToImeAndEnabled(getInputMethodInfoOfThisIme(), subtype);
+ return checkIfSubtypeBelongsToList(subtype,
+ getEnabledInputMethodSubtypeList(
+ getInputMethodInfoOfThisIme(),
+ true /* allowsImplicitlySelectedSubtypes */));
}
public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
final InputMethodSubtype subtype) {
final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype);
- final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(
- subtype, getMyEnabledInputMethodSubtypeList(
- false /* allowsImplicitlySelectedSubtypes */));
+ final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(subtype,
+ getMyEnabledInputMethodSubtypeList(false /* allowsImplicitlySelectedSubtypes */));
return subtypeEnabled && !subtypeExplicitlyEnabled;
}
- public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi,
- final InputMethodSubtype subtype) {
- return checkIfSubtypeBelongsToList(subtype, getEnabledInputMethodSubtypeList(imi,
- true /* allowsImplicitlySelectedSubtypes */));
- }
-
private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype,
final List<InputMethodSubtype> subtypes) {
return getSubtypeIndexInList(subtype, subtypes) != INDEX_NOT_FOUND;
@@ -281,26 +318,40 @@ public final class RichInputMethodManager {
return INDEX_NOT_FOUND;
}
- public boolean checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype) {
- return getSubtypeIndexInIme(subtype, getInputMethodInfoOfThisIme()) != INDEX_NOT_FOUND;
+ public void onSubtypeChanged(@Nonnull final InputMethodSubtype newSubtype) {
+ updateCurrentSubtype(newSubtype);
+ updateShortcutIme();
+ if (DEBUG) {
+ Log.w(TAG, "onSubtypeChanged: " + mCurrentRichInputMethodSubtype.getNameForLogging());
+ }
}
- private static int getSubtypeIndexInIme(final InputMethodSubtype subtype,
- final InputMethodInfo imi) {
- final int count = imi.getSubtypeCount();
- for (int index = 0; index < count; index++) {
- final InputMethodSubtype ims = imi.getSubtypeAt(index);
- if (ims.equals(subtype)) {
- return index;
- }
+ private static RichInputMethodSubtype sForcedSubtypeForTesting = null;
+
+ @UsedForTesting
+ static void forceSubtype(@Nonnull final InputMethodSubtype subtype) {
+ sForcedSubtypeForTesting = RichInputMethodSubtype.getRichInputMethodSubtype(subtype);
+ }
+
+ @Nonnull
+ public Locale getCurrentSubtypeLocale() {
+ if (null != sForcedSubtypeForTesting) {
+ return sForcedSubtypeForTesting.getLocale();
}
- return INDEX_NOT_FOUND;
+ return getCurrentSubtype().getLocale();
}
- public InputMethodSubtype getCurrentInputMethodSubtype(
- final InputMethodSubtype defaultSubtype) {
- final InputMethodSubtype currentSubtype = mImmWrapper.mImm.getCurrentInputMethodSubtype();
- return (currentSubtype != null) ? currentSubtype : defaultSubtype;
+ @Nonnull
+ public RichInputMethodSubtype getCurrentSubtype() {
+ if (null != sForcedSubtypeForTesting) {
+ return sForcedSubtypeForTesting;
+ }
+ return mCurrentRichInputMethodSubtype;
+ }
+
+
+ public String getCombiningRulesExtraValueOfCurrentSubtype() {
+ return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype().getRawSubtype());
}
public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) {
@@ -343,7 +394,6 @@ public final class RichInputMethodManager {
// subtypes should be counted as well.
if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
++filteredImisCount;
- continue;
}
}
@@ -388,27 +438,19 @@ public final class RichInputMethodManager {
getInputMethodIdOfThisIme(), subtypes);
// Clear the cache so that we go read the {@link InputMethodInfo} of this IME and list of
// subtypes again next time.
- clearSubtypeCaches();
+ refreshSubtypeCaches();
}
private List<InputMethodSubtype> getEnabledInputMethodSubtypeList(final InputMethodInfo imi,
final boolean allowsImplicitlySelectedSubtypes) {
- final HashMap<InputMethodInfo, List<InputMethodSubtype>> cache =
- allowsImplicitlySelectedSubtypes
- ? mSubtypeListCacheWithImplicitlySelectedSubtypes
- : mSubtypeListCacheWithoutImplicitlySelectedSubtypes;
- final List<InputMethodSubtype> cachedList = cache.get(imi);
- if (null != cachedList) return cachedList;
- final List<InputMethodSubtype> result = mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
+ return mInputMethodInfoCache.getEnabledInputMethodSubtypeList(
imi, allowsImplicitlySelectedSubtypes);
- cache.put(imi, result);
- return result;
}
- public void clearSubtypeCaches() {
- mSubtypeListCacheWithImplicitlySelectedSubtypes.clear();
- mSubtypeListCacheWithoutImplicitlySelectedSubtypes.clear();
+ public void refreshSubtypeCaches() {
mInputMethodInfoCache.clear();
+ updateCurrentSubtype(mImmWrapper.mImm.getCurrentInputMethodSubtype());
+ updateShortcutIme();
}
public boolean shouldOfferSwitchingToNextInputMethod(final IBinder binder,
@@ -421,4 +463,109 @@ public final class RichInputMethodManager {
}
return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder);
}
+
+ public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
+ final Locale systemLocale = mContext.getResources().getConfiguration().locale;
+ final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
+ final InputMethodManager inputMethodManager = 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);
+ }
+ for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) {
+ if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty()
+ && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void updateCurrentSubtype(@Nullable final InputMethodSubtype subtype) {
+ mCurrentRichInputMethodSubtype = RichInputMethodSubtype.getRichInputMethodSubtype(subtype);
+ }
+
+ private void updateShortcutIme() {
+ if (DEBUG) {
+ Log.d(TAG, "Update shortcut IME from : "
+ + (mShortcutInputMethodInfo == null
+ ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
+ + (mShortcutSubtype == null ? "<null>" : (
+ mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
+ }
+ final RichInputMethodSubtype richSubtype = mCurrentRichInputMethodSubtype;
+ final boolean implicitlyEnabledSubtype = checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
+ richSubtype.getRawSubtype());
+ final Locale systemLocale = mContext.getResources().getConfiguration().locale;
+ LanguageOnSpacebarUtils.onSubtypeChanged(
+ richSubtype, implicitlyEnabledSubtype, systemLocale);
+ LanguageOnSpacebarUtils.setEnabledSubtypes(getMyEnabledInputMethodSubtypeList(
+ true /* allowsImplicitlySelectedSubtypes */));
+
+ // TODO: Update an icon for shortcut IME
+ final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
+ getInputMethodManager().getShortcutInputMethodsAndSubtypes();
+ mShortcutInputMethodInfo = null;
+ mShortcutSubtype = null;
+ for (final InputMethodInfo imi : shortcuts.keySet()) {
+ final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
+ // TODO: Returns the first found IMI for now. Should handle all shortcuts as
+ // appropriate.
+ mShortcutInputMethodInfo = imi;
+ // TODO: Pick up the first found subtype for now. Should handle all subtypes
+ // as appropriate.
+ mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null;
+ break;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Update shortcut IME to : "
+ + (mShortcutInputMethodInfo == null
+ ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
+ + (mShortcutSubtype == null ? "<null>" : (
+ mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
+ }
+ }
+
+ public void switchToShortcutIme(final InputMethodService context) {
+ if (mShortcutInputMethodInfo == null) {
+ return;
+ }
+
+ final String imiId = mShortcutInputMethodInfo.getId();
+ switchToTargetIME(imiId, mShortcutSubtype, context);
+ }
+
+ private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
+ final InputMethodService context) {
+ final IBinder token = context.getWindow().getWindow().getAttributes().token;
+ if (token == null) {
+ return;
+ }
+ final InputMethodManager imm = getInputMethodManager();
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ imm.setInputMethodAndSubtype(token, imiId, subtype);
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ public boolean isShortcutImeReady() {
+ if (mShortcutInputMethodInfo == null) {
+ return false;
+ }
+ if (mShortcutSubtype == null) {
+ return true;
+ }
+ return true;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java
new file mode 100644
index 000000000..9d7849ffc
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java
@@ -0,0 +1,227 @@
+/*
+ * 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 static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
+
+import android.util.Log;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.LocaleUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+import java.util.Locale;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Enrichment class for InputMethodSubtype to enable concurrent multi-lingual input.
+ *
+ * Right now, this returns the extra value of its primary subtype.
+ */
+// non final for easy mocking.
+public class RichInputMethodSubtype {
+ private static final String TAG = RichInputMethodSubtype.class.getSimpleName();
+
+ @Nonnull
+ private final InputMethodSubtype mSubtype;
+ @Nonnull
+ private final Locale mLocale;
+
+ public RichInputMethodSubtype(@Nonnull final InputMethodSubtype subtype) {
+ mSubtype = subtype;
+ mLocale = LocaleUtils.constructLocaleFromString(mSubtype.getLocale());
+ }
+
+ // Extra values are determined by the primary subtype. This is probably right, but
+ // we may have to revisit this later.
+ public String getExtraValueOf(@Nonnull final String key) {
+ return mSubtype.getExtraValueOf(key);
+ }
+
+ // The mode is also determined by the primary subtype.
+ public String getMode() {
+ return mSubtype.getMode();
+ }
+
+ public boolean isNoLanguage() {
+ return SubtypeLocaleUtils.NO_LANGUAGE.equals(mSubtype.getLocale());
+ }
+
+ public String getNameForLogging() {
+ return toString();
+ }
+
+ // InputMethodSubtype's display name for spacebar text in its locale.
+ // isAdditionalSubtype (T=true, F=false)
+ // locale layout | Middle Full
+ // ------ ------- - --------- ----------------------
+ // en_US qwerty F English English (US) exception
+ // en_GB qwerty F English English (UK) exception
+ // es_US spanish F Español Español (EE.UU.) exception
+ // fr azerty F Français Français
+ // fr_CA qwerty F Français Français (Canada)
+ // fr_CH swiss F Français Français (Suisse)
+ // de qwertz F Deutsch Deutsch
+ // de_CH swiss T Deutsch Deutsch (Schweiz)
+ // zz qwerty F QWERTY QWERTY
+ // fr qwertz T Français Français
+ // de qwerty T Deutsch Deutsch
+ // en_US azerty T English English (US)
+ // zz azerty T AZERTY AZERTY
+ // Get the RichInputMethodSubtype's full display name in its locale.
+ @Nonnull
+ public String getFullDisplayName() {
+ if (isNoLanguage()) {
+ return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
+ }
+ return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(mSubtype.getLocale());
+ }
+
+ // Get the RichInputMethodSubtype's middle display name in its locale.
+ @Nonnull
+ public String getMiddleDisplayName() {
+ if (isNoLanguage()) {
+ return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
+ }
+ return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(mSubtype.getLocale());
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (!(o instanceof RichInputMethodSubtype)) {
+ return false;
+ }
+ final RichInputMethodSubtype other = (RichInputMethodSubtype)o;
+ return mSubtype.equals(other.mSubtype) && mLocale.equals(other.mLocale);
+ }
+
+ @Override
+ public int hashCode() {
+ return mSubtype.hashCode() + mLocale.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "Multi-lingual subtype: " + mSubtype + ", " + mLocale;
+ }
+
+ @Nonnull
+ public Locale getLocale() {
+ return mLocale;
+ }
+
+ public boolean isRtlSubtype() {
+ // The subtype is considered RTL if the language of the main subtype is RTL.
+ return LocaleUtils.isRtlLanguage(mLocale);
+ }
+
+ // TODO: remove this method
+ @Nonnull
+ public InputMethodSubtype getRawSubtype() { return mSubtype; }
+
+ @Nonnull
+ public String getKeyboardLayoutSetName() {
+ return SubtypeLocaleUtils.getKeyboardLayoutSetName(mSubtype);
+ }
+
+ public static RichInputMethodSubtype getRichInputMethodSubtype(
+ @Nullable final InputMethodSubtype subtype) {
+ if (subtype == null) {
+ return getNoLanguageSubtype();
+ } else {
+ return new RichInputMethodSubtype(subtype);
+ }
+ }
+
+ // Dummy no language QWERTY subtype. See {@link R.xml.method}.
+ 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;
+ @Nonnull
+ private static final RichInputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
+ new RichInputMethodSubtype(InputMethodSubtypeCompatUtils.newInputMethodSubtype(
+ R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
+ SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
+ EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE,
+ false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
+ SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE));
+ // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
+ // Dummy Emoji subtype. See {@link R.xml.method}.
+ private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0;
+ private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE =
+ "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
+ + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+ @Nonnull
+ private static final RichInputMethodSubtype DUMMY_EMOJI_SUBTYPE = new RichInputMethodSubtype(
+ InputMethodSubtypeCompatUtils.newInputMethodSubtype(
+ R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
+ SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
+ EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE,
+ false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
+ SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE));
+ private static RichInputMethodSubtype sNoLanguageSubtype;
+ private static RichInputMethodSubtype sEmojiSubtype;
+
+ @Nonnull
+ public static RichInputMethodSubtype getNoLanguageSubtype() {
+ RichInputMethodSubtype noLanguageSubtype = sNoLanguageSubtype;
+ if (noLanguageSubtype == null) {
+ final InputMethodSubtype rawNoLanguageSubtype = RichInputMethodManager.getInstance()
+ .findSubtypeByLocaleAndKeyboardLayoutSet(
+ SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
+ if (rawNoLanguageSubtype != null) {
+ noLanguageSubtype = new RichInputMethodSubtype(rawNoLanguageSubtype);
+ }
+ }
+ if (noLanguageSubtype != null) {
+ sNoLanguageSubtype = noLanguageSubtype;
+ return noLanguageSubtype;
+ }
+ 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;
+ }
+
+ @Nonnull
+ public static RichInputMethodSubtype getEmojiSubtype() {
+ RichInputMethodSubtype emojiSubtype = sEmojiSubtype;
+ if (emojiSubtype == null) {
+ final InputMethodSubtype rawEmojiSubtype = RichInputMethodManager.getInstance()
+ .findSubtypeByLocaleAndKeyboardLayoutSet(
+ SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
+ if (rawEmojiSubtype != null) {
+ emojiSubtype = new RichInputMethodSubtype(rawEmojiSubtype);
+ }
+ }
+ if (emojiSubtype != null) {
+ sEmojiSubtype = emojiSubtype;
+ return emojiSubtype;
+ }
+ 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/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
deleted file mode 100644
index a725e1611..000000000
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2010 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 static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.REQ_NETWORK_CONNECTIVITY;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.inputmethodservice.InputMethodService;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.AsyncTask;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.inputmethod.InputMethodInfo;
-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.define.DebugFlags;
-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 = DebugFlags.DEBUG_ENABLED;
- private static final String TAG = SubtypeSwitcher.class.getSimpleName();
-
- private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
-
- private /* final */ RichInputMethodManager mRichImm;
- private /* final */ Resources mResources;
-
- 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 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 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;
- }
-
- public static void init(final Context context) {
- SubtypeLocaleUtils.init(context);
- RichInputMethodManager.init(context);
- sInstance.initialize(context);
- }
-
- private SubtypeSwitcher() {
- // Intentional empty constructor for singleton.
- }
-
- private void initialize(final Context context) {
- if (mResources != null) {
- return;
- }
- mResources = context.getResources();
- mRichImm = RichInputMethodManager.getInstance();
- ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(
- Context.CONNECTIVITY_SERVICE);
-
- final NetworkInfo info = connectivityManager.getActiveNetworkInfo();
- mIsNetworkConnected = (info != null && info.isConnected());
-
- onSubtypeChanged(getCurrentSubtype());
- updateParametersOnStartInputView();
- }
-
- /**
- * Update parameters which are changed outside LatinIME. This parameters affect UI so that they
- * should be updated every time onStartInputView is called.
- */
- public void updateParametersOnStartInputView() {
- final List<InputMethodSubtype> enabledSubtypesOfThisIme =
- mRichImm.getMyEnabledInputMethodSubtypeList(true);
- mLanguageOnSpacebarHelper.updateEnabledSubtypes(enabledSubtypesOfThisIme);
- updateShortcutIME();
- }
-
- private void updateShortcutIME() {
- if (DBG) {
- Log.d(TAG, "Update shortcut IME from : "
- + (mShortcutInputMethodInfo == null
- ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
- + (mShortcutSubtype == null ? "<null>" : (
- mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
- }
- // TODO: Update an icon for shortcut IME
- final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
- mRichImm.getInputMethodManager().getShortcutInputMethodsAndSubtypes();
- mShortcutInputMethodInfo = null;
- mShortcutSubtype = null;
- for (final InputMethodInfo imi : shortcuts.keySet()) {
- final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
- // TODO: Returns the first found IMI for now. Should handle all shortcuts as
- // appropriate.
- mShortcutInputMethodInfo = imi;
- // TODO: Pick up the first found subtype for now. Should handle all subtypes
- // as appropriate.
- mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null;
- break;
- }
- if (DBG) {
- Log.d(TAG, "Update shortcut IME to : "
- + (mShortcutInputMethodInfo == null
- ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
- + (mShortcutSubtype == null ? "<null>" : (
- mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
- }
- }
-
- // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
- public void onSubtypeChanged(final InputMethodSubtype newSubtype) {
- if (DBG) {
- Log.w(TAG, "onSubtypeChanged: "
- + SubtypeLocaleUtils.getSubtypeNameForLogging(newSubtype));
- }
-
- final Locale newLocale = SubtypeLocaleUtils.getSubtypeLocale(newSubtype);
- final Locale systemLocale = mResources.getConfiguration().locale;
- final boolean sameLocale = systemLocale.equals(newLocale);
- final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage());
- final boolean implicitlyEnabled =
- mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype);
- mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(
- sameLocale || (sameLanguage && implicitlyEnabled));
-
- updateShortcutIME();
- }
-
- ////////////////////////////
- // Shortcut IME functions //
- ////////////////////////////
-
- public void switchToShortcutIME(final InputMethodService context) {
- if (mShortcutInputMethodInfo == null) {
- return;
- }
-
- final String imiId = mShortcutInputMethodInfo.getId();
- switchToTargetIME(imiId, mShortcutSubtype, context);
- }
-
- private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
- final InputMethodService context) {
- final IBinder token = context.getWindow().getWindow().getAttributes().token;
- if (token == null) {
- return;
- }
- final InputMethodManager imm = mRichImm.getInputMethodManager();
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- imm.setInputMethodAndSubtype(token, imiId, subtype);
- return null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- public boolean isShortcutImeEnabled() {
- updateShortcutIME();
- if (mShortcutInputMethodInfo == null) {
- return false;
- }
- if (mShortcutSubtype == null) {
- return true;
- }
- return mRichImm.checkIfSubtypeBelongsToImeAndEnabled(
- mShortcutInputMethodInfo, mShortcutSubtype);
- }
-
- public boolean isShortcutImeReady() {
- updateShortcutIME();
- if (mShortcutInputMethodInfo == null) {
- return false;
- }
- if (mShortcutSubtype == null) {
- return true;
- }
- if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) {
- return mIsNetworkConnected;
- }
- return true;
- }
-
- public void onNetworkStateChanged(final Intent intent) {
- final boolean noConnection = intent.getBooleanExtra(
- ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
- mIsNetworkConnected = !noConnection;
-
- KeyboardSwitcher.getInstance().onNetworkStateChanged();
- }
-
- //////////////////////////////////
- // Subtype Switching functions //
- //////////////////////////////////
-
- 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<>();
- 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);
- }
- for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) {
- if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty()
- && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) {
- return false;
- }
- }
- return true;
- }
-
- private static InputMethodSubtype sForcedSubtypeForTesting = null;
- @UsedForTesting
- void forceSubtype(final InputMethodSubtype subtype) {
- sForcedSubtypeForTesting = subtype;
- }
-
- public Locale getCurrentSubtypeLocale() {
- if (null != sForcedSubtypeForTesting) {
- return LocaleUtils.constructLocaleFromString(sForcedSubtypeForTesting.getLocale());
- }
- return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype());
- }
-
- public InputMethodSubtype getCurrentSubtype() {
- if (null != sForcedSubtypeForTesting) {
- return sForcedSubtypeForTesting;
- }
- return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype());
- }
-
- public InputMethodSubtype getNoLanguageSubtype() {
- if (mNoLanguageSubtype == null) {
- mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
- SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
- }
- if (mNoLanguageSubtype != null) {
- return mNoLanguageSubtype;
- }
- 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;
- }
-
- public InputMethodSubtype getEmojiSubtype() {
- if (mEmojiSubtype == null) {
- mEmojiSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
- SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
- }
- if (mEmojiSubtype != null) {
- return mEmojiSubtype;
- }
- 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;
- }
-
- public String getCombiningRulesExtraValueOfCurrentSubtype() {
- return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype());
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index b03818c1d..7ccefd2dd 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -18,18 +18,25 @@ package com.android.inputmethod.latin;
import android.text.TextUtils;
-import com.android.inputmethod.keyboard.ProximityInfo;
+import static com.android.inputmethod.latin.define.DecoderSpecificConstants.SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION;
+import static com.android.inputmethod.latin.define.DecoderSpecificConstants.SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION;
+
+import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.define.DebugFlags;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.SuggestionResults;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Locale;
+import javax.annotation.Nonnull;
+
/**
* This class loads a dictionary and provides a list of suggestions for a given sequence of
* characters. This includes corrections and completions.
@@ -49,34 +56,55 @@ public final class Suggest {
private static final boolean DBG = DebugFlags.DEBUG_ENABLED;
private final DictionaryFacilitator mDictionaryFacilitator;
+ private static final int MAXIMUM_AUTO_CORRECT_LENGTH_FOR_GERMAN = 12;
+ private static final HashMap<String, Integer> sLanguageToMaximumAutoCorrectionWithSpaceLength =
+ new HashMap<>();
+ static {
+ // TODO: should we add Finnish here?
+ // TODO: This should not be hardcoded here but be written in the dictionary header
+ sLanguageToMaximumAutoCorrectionWithSpaceLength.put(Locale.GERMAN.getLanguage(),
+ MAXIMUM_AUTO_CORRECT_LENGTH_FOR_GERMAN);
+ }
+
private float mAutoCorrectionThreshold;
+ private float mPlausibilityThreshold;
public Suggest(final DictionaryFacilitator dictionaryFacilitator) {
mDictionaryFacilitator = dictionaryFacilitator;
}
- public Locale getLocale() {
- return mDictionaryFacilitator.getLocale();
- }
-
+ /**
+ * Set the normalized-score threshold for a suggestion to be considered strong enough that we
+ * will auto-correct to this.
+ * @param threshold the threshold
+ */
public void setAutoCorrectionThreshold(final float threshold) {
mAutoCorrectionThreshold = threshold;
}
+ /**
+ * Set the normalized-score threshold for what we consider a "plausible" suggestion, in
+ * the same dimension as the auto-correction threshold.
+ * @param threshold the threshold
+ */
+ public void setPlausibilityThreshold(final float threshold) {
+ mPlausibilityThreshold = threshold;
+ }
+
public interface OnGetSuggestedWordsCallback {
public void onGetSuggestedWords(final SuggestedWords suggestedWords);
}
public void getSuggestedWords(final WordComposer wordComposer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final Keyboard keyboard,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
final boolean isCorrectionEnabled, final int inputStyle, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
if (wordComposer.isBatchMode()) {
- getSuggestedWordsForBatchInput(wordComposer, prevWordsInfo, proximityInfo,
+ getSuggestedWordsForBatchInput(wordComposer, ngramContext, keyboard,
settingsValuesForSuggestion, inputStyle, sequenceNumber, callback);
} else {
- getSuggestedWordsForNonBatchInput(wordComposer, prevWordsInfo, proximityInfo,
+ getSuggestedWordsForNonBatchInput(wordComposer, ngramContext, keyboard,
settingsValuesForSuggestion, inputStyle, isCorrectionEnabled,
sequenceNumber, callback);
}
@@ -84,7 +112,7 @@ public final class Suggest {
private static ArrayList<SuggestedWordInfo> getTransformedSuggestedWordInfoList(
final WordComposer wordComposer, final SuggestionResults results,
- final int trailingSingleQuotesCount) {
+ final int trailingSingleQuotesCount, final Locale defaultLocale) {
final boolean shouldMakeSuggestionsAllUpperCase = wordComposer.isAllUpperCase()
&& !wordComposer.isResumed();
final boolean isOnlyFirstCharCapitalized =
@@ -96,16 +124,19 @@ public final class Suggest {
|| 0 != trailingSingleQuotesCount) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
+ final Locale wordLocale = wordInfo.mSourceDict.mLocale;
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, results.mLocale, shouldMakeSuggestionsAllUpperCase,
- isOnlyFirstCharCapitalized, trailingSingleQuotesCount);
+ wordInfo, null == wordLocale ? defaultLocale : wordLocale,
+ shouldMakeSuggestionsAllUpperCase, isOnlyFirstCharCapitalized,
+ trailingSingleQuotesCount);
suggestionsContainer.set(i, transformedWordInfo);
}
}
return suggestionsContainer;
}
- private static String getWhitelistedWordOrNull(final ArrayList<SuggestedWordInfo> suggestions) {
+ private static SuggestedWordInfo getWhitelistedWordInfoOrNull(
+ @Nonnull final ArrayList<SuggestedWordInfo> suggestions) {
if (suggestions.isEmpty()) {
return null;
}
@@ -113,75 +144,124 @@ public final class Suggest {
if (!firstSuggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
return null;
}
- return firstSuggestedWordInfo.mWord;
+ return firstSuggestedWordInfo;
}
// Retrieves suggestions for non-batch input (typing, recorrection, predictions...)
// and calls the callback function with the suggestions.
private void getSuggestedWordsForNonBatchInput(final WordComposer wordComposer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final Keyboard keyboard,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
final int inputStyleIfNotPrediction, final boolean isCorrectionEnabled,
final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
- final String typedWord = wordComposer.getTypedWord();
- final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(typedWord);
+ final String typedWordString = wordComposer.getTypedWord();
+ final int trailingSingleQuotesCount =
+ StringUtils.getTrailingSingleQuotesCount(typedWordString);
final String consideredWord = trailingSingleQuotesCount > 0
- ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
- : typedWord;
+ ? typedWordString.substring(0, typedWordString.length() - trailingSingleQuotesCount)
+ : typedWordString;
final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- wordComposer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion,
- SESSION_ID_TYPING);
+ wordComposer.getComposedDataSnapshot(), ngramContext, keyboard,
+ settingsValuesForSuggestion, SESSION_ID_TYPING, inputStyleIfNotPrediction);
+ final Locale locale = mDictionaryFacilitator.getLocale();
final ArrayList<SuggestedWordInfo> suggestionsContainer =
getTransformedSuggestedWordInfoList(wordComposer, suggestionResults,
- trailingSingleQuotesCount);
- final boolean didRemoveTypedWord =
- SuggestedWordInfo.removeDups(wordComposer.getTypedWord(), suggestionsContainer);
+ trailingSingleQuotesCount, locale);
- final String whitelistedWord = getWhitelistedWordOrNull(suggestionsContainer);
+ boolean foundInDictionary = false;
+ Dictionary sourceDictionaryOfRemovedWord = null;
+ for (final SuggestedWordInfo info : suggestionsContainer) {
+ // Search for the best dictionary, defined as the first one with the highest match
+ // quality we can find.
+ if (!foundInDictionary && typedWordString.equals(info.mWord)) {
+ // Use this source if the old match had lower quality than this match
+ sourceDictionaryOfRemovedWord = info.mSourceDict;
+ foundInDictionary = true;
+ break;
+ }
+ }
+
+ final int firstOcurrenceOfTypedWordInSuggestions =
+ SuggestedWordInfo.removeDups(typedWordString, suggestionsContainer);
+
+ final SuggestedWordInfo whitelistedWordInfo =
+ getWhitelistedWordInfoOrNull(suggestionsContainer);
+ final String whitelistedWord = whitelistedWordInfo == null
+ ? null : whitelistedWordInfo.mWord;
final boolean resultsArePredictions = !wordComposer.isComposingWord();
- // We allow auto-correction if we have a whitelisted word, or if the word had more than
- // one char and was not suggested.
- final boolean allowsToBeAutoCorrected = (null != whitelistedWord)
- || (consideredWord.length() > 1 && !didRemoveTypedWord);
+ // We allow auto-correction if whitelisting is not required or the word is whitelisted,
+ // or if the word had more than one char and was not suggested.
+ final boolean allowsToBeAutoCorrected =
+ (SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION || whitelistedWord != null)
+ || (consideredWord.length() > 1 && (sourceDictionaryOfRemovedWord == null));
final boolean hasAutoCorrection;
- // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
- // any attempt to do auto-correction is already shielded with a test for this flag; at the
- // 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 || resultsArePredictions
- || suggestionResults.isEmpty() || wordComposer.hasDigits()
- || wordComposer.isMostlyCaps() || wordComposer.isResumed()
- || !mDictionaryFacilitator.hasInitializedMainDictionary()
+ // If correction is not enabled, we never auto-correct. This is for example for when
+ // the setting "Auto-correction" is "off": we still suggest, but we don't auto-correct.
+ if (!isCorrectionEnabled
+ // If the word does not allow to be auto-corrected, then we don't auto-correct.
+ || !allowsToBeAutoCorrected
+ // If we are doing prediction, then we never auto-correct of course
+ || resultsArePredictions
+ // If we don't have suggestion results, we can't evaluate the first suggestion
+ // for auto-correction
+ || suggestionResults.isEmpty()
+ // If the word has digits, we never auto-correct because it's likely the word
+ // was type with a lot of care
+ || wordComposer.hasDigits()
+ // If the word is mostly caps, we never auto-correct because this is almost
+ // certainly intentional (and careful input)
+ || wordComposer.isMostlyCaps()
+ // We never auto-correct when suggestions are resumed because it would be unexpected
+ || wordComposer.isResumed()
+ // 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 types in English with no dictionary and has a "Will" in their contact
+ // list, "will" would always auto-correct to "Will" which is unwanted. Hence, no
+ // main dict => no auto-correct. Also, it would probably get obnoxious quickly.
+ // TODO: now that we have personalization, we may want to re-evaluate this decision
+ || !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()
+ // If the first suggestion is a shortcut we never auto-correct to it, regardless
+ // of how strong it is (whitelist entries are not KIND_SHORTCUT but KIND_WHITELIST).
+ // TODO: we may want to have shortcut-only entries auto-correct in the future.
|| suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) {
- // If we don't have a main dictionary, we never want to auto-correct. The reason for
- // this is, the user may have a contact whose name happens to match a valid word in
- // their language, and it will unexpectedly auto-correct. For example, if the user
- // types in English with no dictionary and has a "Will" in their contact list, "will"
- // would always auto-correct to "Will" which is unwanted. Hence, no main dict => no
- // auto-correct.
- // Also, shortcuts should never auto-correct unless they are whitelist entries.
- // TODO: we may want to have shortcut-only entries auto-correct in the future.
hasAutoCorrection = false;
} else {
- hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
- suggestionResults.first(), consideredWord, mAutoCorrectionThreshold);
+ final SuggestedWordInfo firstSuggestion = suggestionResults.first();
+ if (suggestionResults.mFirstSuggestionExceedsConfidenceThreshold
+ && firstOcurrenceOfTypedWordInSuggestions != 0) {
+ hasAutoCorrection = true;
+ } else if (!AutoCorrectionUtils.suggestionExceedsThreshold(
+ firstSuggestion, consideredWord, mAutoCorrectionThreshold)) {
+ // Score is too low for autocorrect
+ hasAutoCorrection = false;
+ } else {
+ // We have a high score, so we need to check if this suggestion is in the correct
+ // form to allow auto-correcting to it in this language. For details of how this
+ // is determined, see #isAllowedByAutoCorrectionWithSpaceFilter.
+ // TODO: this should not have its own logic here but be handled by the dictionary.
+ hasAutoCorrection = isAllowedByAutoCorrectionWithSpaceFilter(firstSuggestion);
+ }
}
- if (!TextUtils.isEmpty(typedWord)) {
- suggestionsContainer.add(0, new SuggestedWordInfo(typedWord,
- SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED,
- Dictionary.DICTIONARY_USER_TYPED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
+ final SuggestedWordInfo typedWordInfo = new SuggestedWordInfo(typedWordString,
+ "" /* prevWordsContext */, SuggestedWordInfo.MAX_SCORE,
+ SuggestedWordInfo.KIND_TYPED,
+ null == sourceDictionaryOfRemovedWord ? Dictionary.DICTIONARY_USER_TYPED
+ : sourceDictionaryOfRemovedWord,
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
+ if (!TextUtils.isEmpty(typedWordString)) {
+ suggestionsContainer.add(0, typedWordInfo);
}
final ArrayList<SuggestedWordInfo> suggestionsList;
if (DBG && !suggestionsContainer.isEmpty()) {
- suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, suggestionsContainer);
+ suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWordString,
+ suggestionsContainer);
} else {
suggestionsList = suggestionsContainer;
}
@@ -194,12 +274,12 @@ public final class Suggest {
} else {
inputStyle = inputStyleIfNotPrediction;
}
+
+ final boolean isTypedWordValid = firstOcurrenceOfTypedWordInSuggestions > -1
+ || (!resultsArePredictions && !allowsToBeAutoCorrected);
callback.onGetSuggestedWords(new SuggestedWords(suggestionsList,
- suggestionResults.mRawSuggestions,
- // 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.
- !resultsArePredictions && !allowsToBeAutoCorrected /* typedWordValid */,
+ suggestionResults.mRawSuggestions, typedWordInfo,
+ isTypedWordValid,
hasAutoCorrection /* willAutoCorrect */,
false /* isObsoleteSuggestions */, inputStyle, sequenceNumber));
}
@@ -207,13 +287,15 @@ public final class Suggest {
// Retrieves suggestions for the batch input
// and calls the callback function with the suggestions.
private void getSuggestedWordsForBatchInput(final WordComposer wordComposer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
+ final NgramContext ngramContext, final Keyboard keyboard,
final SettingsValuesForSuggestion settingsValuesForSuggestion,
final int inputStyle, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- wordComposer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion,
- SESSION_ID_GESTURE);
+ wordComposer.getComposedDataSnapshot(), ngramContext, keyboard,
+ settingsValuesForSuggestion, SESSION_ID_GESTURE, inputStyle);
+ // For transforming words that don't come from a dictionary, because it's our best bet
+ final Locale locale = mDictionaryFacilitator.getLocale();
final ArrayList<SuggestedWordInfo> suggestionsContainer =
new ArrayList<>(suggestionResults);
final int suggestionsCount = suggestionsContainer.size();
@@ -222,22 +304,25 @@ public final class Suggest {
if (isFirstCharCapitalized || isAllUpperCase) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
+ final Locale wordlocale = wordInfo.mSourceDict.mLocale;
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized,
- 0 /* trailingSingleQuotesCount */);
+ wordInfo, null == wordlocale ? locale : wordlocale, isAllUpperCase,
+ isFirstCharCapitalized, 0 /* trailingSingleQuotesCount */);
suggestionsContainer.set(i, transformedWordInfo);
}
}
- if (suggestionsContainer.size() > 1 && TextUtils.equals(suggestionsContainer.get(0).mWord,
- wordComposer.getRejectedBatchModeSuggestion())) {
+ if (SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION
+ && suggestionsContainer.size() > 1
+ && TextUtils.equals(suggestionsContainer.get(0).mWord,
+ wordComposer.getRejectedBatchModeSuggestion())) {
final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
suggestionsContainer.add(1, rejected);
}
SuggestedWordInfo.removeDups(null /* typedWord */, suggestionsContainer);
// For some reason some suggestions with MIN_VALUE are making their way here.
- // TODO: Find a more robust way to detect distractors.
+ // TODO: Find a more robust way to detect distracters.
for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
suggestionsContainer.remove(i);
@@ -248,8 +333,12 @@ public final class Suggest {
// (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
// Note that because this method is never used to get predictions, there is no need to
// modify inputType such in getSuggestedWordsForNonBatchInput.
+ final SuggestedWordInfo pseudoTypedWordInfo = suggestionsContainer.isEmpty() ? null
+ : suggestionsContainer.get(0);
+
callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer,
suggestionResults.mRawSuggestions,
+ pseudoTypedWordInfo,
true /* typedWordValid */,
false /* willAutoCorrect */,
false /* isObsoleteSuggestions */,
@@ -283,6 +372,41 @@ public final class Suggest {
return suggestionsList;
}
+ /**
+ * Computes whether this suggestion should be blocked or not in this language
+ *
+ * This function implements a filter that avoids auto-correcting to suggestions that contain
+ * spaces that are above a certain language-dependent character limit. In languages like German
+ * where it's possible to concatenate many words, it often happens our dictionary does not
+ * have the longer words. In this case, we offer a lot of unhelpful suggestions that contain
+ * one or several spaces. Ideally we should understand what the user wants and display useful
+ * suggestions by improving the dictionary and possibly having some specific logic. Until
+ * that's possible we should avoid displaying unhelpful suggestions. But it's hard to tell
+ * whether a suggestion is useful or not. So at least for the time being we block
+ * auto-correction when the suggestion is long and contains a space, which should avoid the
+ * worst damage.
+ * This function is implementing that filter. If the language enforces no such limit, then it
+ * always returns true. If the suggestion contains no space, it also returns true. Otherwise,
+ * it checks the length against the language-specific limit.
+ *
+ * @param info the suggestion info
+ * @return whether it's fine to auto-correct to this.
+ */
+ private static boolean isAllowedByAutoCorrectionWithSpaceFilter(final SuggestedWordInfo info) {
+ final Locale locale = info.mSourceDict.mLocale;
+ if (null == locale) {
+ return true;
+ }
+ final Integer maximumLengthForThisLanguage =
+ sLanguageToMaximumAutoCorrectionWithSpaceLength.get(locale.getLanguage());
+ if (null == maximumLengthForThisLanguage) {
+ // This language does not enforce a maximum length to auto-correction
+ return true;
+ }
+ return info.mWord.length() <= maximumLengthForThisLanguage
+ || -1 == info.mWord.indexOf(Constants.CODE_SPACE);
+ }
+
/* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo(
final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
final boolean isOnlyFirstCharCapitalized, final int trailingSingleQuotesCount) {
@@ -302,7 +426,8 @@ public final class Suggest {
for (int i = quotesToAppend - 1; i >= 0; --i) {
sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE);
}
- return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKindAndFlags,
+ return new SuggestedWordInfo(sb.toString(), wordInfo.mPrevWordsContext,
+ wordInfo.mScore, wordInfo.mKindAndFlags,
wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord,
wordInfo.mAutoCommitFirstWordConfidence);
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 1d221b77f..bcd4d5f69 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -20,13 +20,16 @@ import android.text.TextUtils;
import android.view.inputmethod.CompletionInfo;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.define.DebugFlags;
-import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
public class SuggestedWords {
public static final int INDEX_OF_TYPED_WORD = 0;
public static final int INDEX_OF_AUTO_CORRECTION = 1;
@@ -45,11 +48,14 @@ public class SuggestedWords {
public static final int MAX_SUGGESTIONS = 18;
private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = new ArrayList<>(0);
- public static final SuggestedWords EMPTY = new SuggestedWords(
- EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, false /* typedWordValid */,
- false /* willAutoCorrect */, false /* isObsoleteSuggestions */, INPUT_STYLE_NONE);
-
- public final String mTypedWord;
+ @Nonnull
+ private static final SuggestedWords EMPTY = new SuggestedWords(
+ EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, null /* typedWord */,
+ false /* typedWordValid */, false /* willAutoCorrect */,
+ false /* isObsoleteSuggestions */, INPUT_STYLE_NONE, NOT_A_SEQUENCE_NUMBER);
+
+ @Nullable
+ public final SuggestedWordInfo mTypedWordInfo;
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",
@@ -60,35 +66,14 @@ public class SuggestedWords {
// INPUT_STYLE_* constants above.
public final int mInputStyle;
public final int mSequenceNumber; // Sequence number for auto-commit.
+ @Nonnull
protected final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList;
+ @Nullable
public final ArrayList<SuggestedWordInfo> mRawSuggestions;
- public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList,
- final ArrayList<SuggestedWordInfo> rawSuggestions,
- final boolean typedWordValid,
- final boolean willAutoCorrect,
- final boolean isObsoleteSuggestions,
- final int inputStyle) {
- this(suggestedWordInfoList, rawSuggestions, typedWordValid, willAutoCorrect,
- isObsoleteSuggestions, inputStyle, NOT_A_SEQUENCE_NUMBER);
- }
-
- public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList,
- final ArrayList<SuggestedWordInfo> rawSuggestions,
- final boolean typedWordValid,
- final boolean willAutoCorrect,
- final boolean isObsoleteSuggestions,
- final int inputStyle,
- final int sequenceNumber) {
- this(suggestedWordInfoList, rawSuggestions,
- (suggestedWordInfoList.isEmpty() || isPrediction(inputStyle)) ? null
- : suggestedWordInfoList.get(INDEX_OF_TYPED_WORD).mWord,
- typedWordValid, willAutoCorrect, isObsoleteSuggestions, inputStyle, sequenceNumber);
- }
-
- public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList,
- final ArrayList<SuggestedWordInfo> rawSuggestions,
- final String typedWord,
+ public SuggestedWords(@Nonnull final ArrayList<SuggestedWordInfo> suggestedWordInfoList,
+ @Nullable final ArrayList<SuggestedWordInfo> rawSuggestions,
+ @Nullable final SuggestedWordInfo typedWordInfo,
final boolean typedWordValid,
final boolean willAutoCorrect,
final boolean isObsoleteSuggestions,
@@ -101,7 +86,7 @@ public class SuggestedWords {
mIsObsoleteSuggestions = isObsoleteSuggestions;
mInputStyle = inputStyle;
mSequenceNumber = sequenceNumber;
- mTypedWord = typedWord;
+ mTypedWordInfo = typedWordInfo;
}
public boolean isEmpty() {
@@ -113,6 +98,27 @@ public class SuggestedWords {
}
/**
+ * Get suggested word to show as suggestions to UI.
+ *
+ * @param shouldShowLxxSuggestionUi true if showing suggestion UI introduced in LXX and later.
+ * @return the count of suggested word to show as suggestions to UI.
+ */
+ public int getWordCountToShow(final boolean shouldShowLxxSuggestionUi) {
+ if (isPrediction() || !shouldShowLxxSuggestionUi) {
+ return size();
+ }
+ return size() - /* typed word */ 1;
+ }
+
+ /**
+ * Get {@link SuggestedWordInfo} object for the typed word.
+ * @return The {@link SuggestedWordInfo} object for the typed word.
+ */
+ public SuggestedWordInfo getTypedWordInfo() {
+ return mTypedWordInfo;
+ }
+
+ /**
* Get suggested word at <code>index</code>.
* @param index The index of the suggested word.
* @return The suggested word.
@@ -142,6 +148,15 @@ public class SuggestedWords {
return mSuggestedWordInfoList.get(index);
}
+ /**
+ * Gets the suggestion index from the suggestions list.
+ * @param suggestedWordInfo The {@link SuggestedWordInfo} to find the index.
+ * @return The position of the suggestion in the suggestion list.
+ */
+ public int indexOf(SuggestedWordInfo suggestedWordInfo) {
+ return mSuggestedWordInfoList.indexOf(suggestedWordInfo);
+ }
+
public String getDebugString(final int pos) {
if (!DebugFlags.DEBUG_ENABLED) {
return null;
@@ -187,17 +202,20 @@ public class SuggestedWords {
return result;
}
+ @Nonnull
+ public static final SuggestedWords getEmptyInstance() {
+ return SuggestedWords.EMPTY;
+ }
+
// Should get rid of the first one (what the user typed previously) from suggestions
// and replace it with what the user currently typed.
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
- final String typedWord, final SuggestedWords previousSuggestions) {
+ @Nonnull final SuggestedWordInfo typedWordInfo,
+ @Nonnull final SuggestedWords previousSuggestions) {
final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>();
final HashSet<String> alreadySeen = new HashSet<>();
- suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
- SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- alreadySeen.add(typedWord.toString());
+ suggestionsList.add(typedWordInfo);
+ alreadySeen.add(typedWordInfo.mWord);
final int previousSize = previousSuggestions.size();
for (int index = 1; index < previousSize; index++) {
final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(index);
@@ -217,7 +235,8 @@ public class SuggestedWords {
return candidate.isEligibleForAutoCommit() ? candidate : null;
}
- public static final class SuggestedWordInfo {
+ // non-final for testability.
+ public static class SuggestedWordInfo {
public static final int NOT_AN_INDEX = -1;
public static final int NOT_A_CONFIDENCE = -1;
public static final int MAX_SCORE = Integer.MAX_VALUE;
@@ -240,14 +259,17 @@ public class SuggestedWords {
public static final int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
public static final int KIND_FLAG_EXACT_MATCH = 0x40000000;
public static final int KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION = 0x20000000;
+ public static final int KIND_FLAG_APPROPRIATE_FOR_AUTO_CORRECTION = 0x10000000;
public final String mWord;
+ public final String mPrevWordsContext;
// 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 mKindAndFlags;
public final int mCodePointCount;
+ @Deprecated
public final Dictionary mSourceDict;
// For auto-commit. This keeps track of the index inside the touch coordinates array
// passed to native code to get suggestions for a gesture that corresponds to the first
@@ -261,6 +283,7 @@ public class SuggestedWords {
/**
* Create a new suggested word info.
* @param word The string to suggest.
+ * @param prevWordsContext previous words context.
* @param score A measure of how likely this suggestion is.
* @param kindAndFlags The kind of suggestion, as one of the above KIND_* constants with
* flags.
@@ -268,10 +291,12 @@ public class SuggestedWords {
* @param indexOfTouchPointOfSecondWord See mIndexOfTouchPointOfSecondWord.
* @param autoCommitFirstWordConfidence See mAutoCommitFirstWordConfidence.
*/
- public SuggestedWordInfo(final String word, final int score, final int kindAndFlags,
+ public SuggestedWordInfo(final String word, final String prevWordsContext,
+ final int score, final int kindAndFlags,
final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord,
final int autoCommitFirstWordConfidence) {
mWord = word;
+ mPrevWordsContext = prevWordsContext;
mApplicationSpecifiedCompletionInfo = null;
mScore = score;
mKindAndFlags = kindAndFlags;
@@ -288,6 +313,7 @@ public class SuggestedWords {
*/
public SuggestedWordInfo(final CompletionInfo applicationSpecifiedCompletion) {
mWord = applicationSpecifiedCompletion.getText().toString();
+ mPrevWordsContext = "";
mApplicationSpecifiedCompletionInfo = applicationSpecifiedCompletion;
mScore = SuggestedWordInfo.MAX_SCORE;
mKindAndFlags = SuggestedWordInfo.KIND_APP_DEFINED;
@@ -321,6 +347,10 @@ public class SuggestedWords {
return (mKindAndFlags & KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION) != 0;
}
+ public boolean isAprapreateForAutoCorrection() {
+ return (mKindAndFlags & KIND_FLAG_APPROPRIATE_FOR_AUTO_CORRECTION) != 0;
+ }
+
public void setDebugString(final String str) {
if (null == str) throw new NullPointerException("Debug info is null");
mDebugString = str;
@@ -330,6 +360,15 @@ public class SuggestedWords {
return mDebugString;
}
+ public String getWord() {
+ return mWord;
+ }
+
+ @Deprecated
+ public Dictionary getSourceDictionary() {
+ return mSourceDict;
+ }
+
public int codePointAt(int i) {
return mWord.codePointAt(i);
}
@@ -338,43 +377,49 @@ public class SuggestedWords {
public String toString() {
if (TextUtils.isEmpty(mDebugString)) {
return mWord;
- } else {
- return mWord + " (" + mDebugString + ")";
}
+ return mWord + " (" + mDebugString + ")";
}
- // This will always remove the higher index if a duplicate is found.
- public static boolean removeDups(final String typedWord,
- ArrayList<SuggestedWordInfo> candidates) {
+ /**
+ * This will always remove the higher index if a duplicate is found.
+ *
+ * @return position of typed word in the candidate list
+ */
+ public static int removeDups(
+ @Nullable final String typedWord,
+ @Nonnull final ArrayList<SuggestedWordInfo> candidates) {
if (candidates.isEmpty()) {
- return false;
+ return -1;
}
- final boolean didRemoveTypedWord;
+ int firstOccurrenceOfWord = -1;
if (!TextUtils.isEmpty(typedWord)) {
- didRemoveTypedWord = removeSuggestedWordInfoFrom(typedWord, candidates,
- -1 /* startIndexExclusive */);
- } else {
- didRemoveTypedWord = false;
+ firstOccurrenceOfWord = removeSuggestedWordInfoFromList(
+ typedWord, candidates, -1 /* startIndexExclusive */);
}
for (int i = 0; i < candidates.size(); ++i) {
- removeSuggestedWordInfoFrom(candidates.get(i).mWord, candidates,
- i /* startIndexExclusive */);
+ removeSuggestedWordInfoFromList(
+ candidates.get(i).mWord, candidates, i /* startIndexExclusive */);
}
- return didRemoveTypedWord;
+ return firstOccurrenceOfWord;
}
- private static boolean removeSuggestedWordInfoFrom(final String word,
- final ArrayList<SuggestedWordInfo> candidates, final int startIndexExclusive) {
- boolean didRemove = false;
+ private static int removeSuggestedWordInfoFromList(
+ @Nonnull final String word,
+ @Nonnull final ArrayList<SuggestedWordInfo> candidates,
+ final int startIndexExclusive) {
+ int firstOccurrenceOfWord = -1;
for (int i = startIndexExclusive + 1; i < candidates.size(); ++i) {
final SuggestedWordInfo previous = candidates.get(i);
if (word.equals(previous.mWord)) {
- didRemove = true;
+ if (firstOccurrenceOfWord == -1) {
+ firstOccurrenceOfWord = i;
+ }
candidates.remove(i);
--i;
}
}
- return didRemove;
+ return firstOccurrenceOfWord;
}
}
@@ -387,47 +432,6 @@ public class SuggestedWords {
return isPrediction(mInputStyle);
}
- // SuggestedWords is an immutable object, as much as possible. We must not just remove
- // words from the member ArrayList as some other parties may expect the object to never change.
- // This is only ever called by recorrection at the moment, hence the ForRecorrection moniker.
- public SuggestedWords getSuggestedWordsExcludingTypedWordForRecorrection() {
- final ArrayList<SuggestedWordInfo> newSuggestions = new ArrayList<>();
- String typedWord = null;
- for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
- final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
- if (!info.isKindOf(SuggestedWordInfo.KIND_TYPED)) {
- 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, null /* rawSuggestions */, typedWord,
- true /* typedWordValid */, false /* willAutoCorrect */, mIsObsoleteSuggestions,
- SuggestedWords.INPUT_STYLE_RECORRECTION, NOT_A_SEQUENCE_NUMBER);
- }
-
- // Creates a new SuggestedWordInfo from the currently suggested words that removes all but the
- // last word of all suggestions, separated by a space. This is necessary because when we commit
- // a multiple-word suggestion, the IME only retains the last word as the composing word, and
- // we should only suggest replacements for this last word.
- // TODO: make this work with languages without spaces.
- public SuggestedWords getSuggestedWordsForLastWordOfPhraseGesture() {
- final ArrayList<SuggestedWordInfo> newSuggestions = new ArrayList<>();
- for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
- final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
- final int indexOfLastSpace = info.mWord.lastIndexOf(Constants.CODE_SPACE) + 1;
- final String lastWord = info.mWord.substring(indexOfLastSpace);
- newSuggestions.add(new SuggestedWordInfo(lastWord, info.mScore, info.mKindAndFlags,
- info.mSourceDict, SuggestedWordInfo.NOT_AN_INDEX,
- SuggestedWordInfo.NOT_A_CONFIDENCE));
- }
- return new SuggestedWords(newSuggestions, null /* rawSuggestions */, mTypedWordValid,
- mWillAutoCorrect, mIsObsoleteSuggestions, INPUT_STYLE_TAIL_BATCH);
- }
-
/**
* @return the {@link SuggestedWordInfo} which corresponds to the word that is originally
* typed by the user. Otherwise returns {@code null}. Note that gesture input is not
diff --git a/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
deleted file mode 100644
index 08785f3d9..000000000
--- a/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
+++ /dev/null
@@ -1,44 +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;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.text.style.SuggestionSpan;
-import android.util.Log;
-
-import com.android.inputmethod.latin.define.DebugFlags;
-
-public final class SuggestionSpanPickedNotificationReceiver extends BroadcastReceiver {
- private static final boolean DBG = DebugFlags.DEBUG_ENABLED;
- private static final String TAG =
- SuggestionSpanPickedNotificationReceiver.class.getSimpleName();
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(intent.getAction())) {
- if (DBG) {
- final String before = intent.getStringExtra(
- SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE);
- final String after = intent.getStringExtra(
- SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER);
- Log.d(TAG, "Received notification picked: " + before + "," + after);
- }
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
index 123ab208c..2a69d3650 100644
--- a/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
@@ -17,16 +17,20 @@
package com.android.inputmethod.latin;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.Process;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
-import com.android.inputmethod.compat.IntentCompatUtils;
+import com.android.inputmethod.dictionarypack.CommonPreferences;
+import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager;
+import com.android.inputmethod.latin.setup.SetupActivity;
import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils;
/**
@@ -50,10 +54,6 @@ import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils;
* receiver and it checks whether the setup wizard's icon should be appeared or not on the launcher
* depending on which partition this IME is installed.
*
- * When a multiuser account has been created, {@link Intent#ACTION_USER_INITIALIZE} is received
- * by this receiver and it checks the whether the setup wizard's icon should be appeared or not on
- * the launcher depending on which partition this IME is installed.
- *
* When the system locale has been changed, {@link Intent#ACTION_LOCALE_CHANGED} is received by
* this receiver and the {@link KeyboardLayoutSet}'s cache is cleared.
*/
@@ -69,26 +69,25 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
// subtypes when the package is replaced.
RichInputMethodManager.init(context);
final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
- final InputMethodSubtype[] additionalSubtypes = richImm.getAdditionalSubtypes(context);
+ final InputMethodSubtype[] additionalSubtypes = richImm.getAdditionalSubtypes();
richImm.setAdditionalInputMethodSubtypes(additionalSubtypes);
- LauncherIconVisibilityManager.updateSetupWizardIconVisibility(context);
+ toggleAppIcon(context);
+ downloadLatestDictionaries(context);
} else if (Intent.ACTION_BOOT_COMPLETED.equals(intentAction)) {
Log.i(TAG, "Boot has been completed");
- LauncherIconVisibilityManager.updateSetupWizardIconVisibility(context);
- } else if (IntentCompatUtils.is_ACTION_USER_INITIALIZE(intentAction)) {
- Log.i(TAG, "User initialize");
- LauncherIconVisibilityManager.updateSetupWizardIconVisibility(context);
+ toggleAppIcon(context);
} else if (Intent.ACTION_LOCALE_CHANGED.equals(intentAction)) {
Log.i(TAG, "System locale changed");
KeyboardLayoutSet.onSystemLocaleChanged();
}
// The process that hosts this broadcast receiver is invoked and remains alive even after
- // 1) the package has been re-installed, 2) the device has just booted,
+ // 1) the package has been re-installed,
+ // 2) the device has just booted,
// 3) a new user has been created.
// There is no good reason to keep the process alive if this IME isn't a current IME.
- final InputMethodManager imm =
- (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ final InputMethodManager imm = (InputMethodManager)
+ context.getSystemService(Context.INPUT_METHOD_SERVICE);
// Called to check whether this IME has been triggered by the current user or not
final boolean isInputMethodManagerValidForUserOfThisProcess =
!imm.getInputMethodList().isEmpty();
@@ -100,4 +99,24 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
Process.killProcess(myPid);
}
}
+
+ private void downloadLatestDictionaries(Context context) {
+ final Intent updateIntent = new Intent(
+ DictionaryPackConstants.INIT_AND_UPDATE_NOW_INTENT_ACTION);
+ context.sendBroadcast(updateIntent);
+ }
+
+ private static void toggleAppIcon(final Context context) {
+ final int appInfoFlags = context.getApplicationInfo().flags;
+ final boolean isSystemApp = (appInfoFlags & ApplicationInfo.FLAG_SYSTEM) > 0;
+ if (Log.isLoggable(TAG, Log.INFO)) {
+ Log.i(TAG, "toggleAppIcon() : FLAG_SYSTEM = " + isSystemApp);
+ }
+ context.getPackageManager().setComponentEnabledSetting(
+ new ComponentName(context, SetupActivity.class),
+ isSystemApp
+ ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 21014b378..fe24ccfc2 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -16,26 +16,25 @@
package com.android.inputmethod.latin;
-import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
-import android.os.Build;
import android.provider.UserDictionary.Words;
import android.text.TextUtils;
import android.util.Log;
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.compat.UserDictionaryCompatUtils;
+import com.android.inputmethod.annotations.ExternallyReferenced;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.io.File;
import java.util.Arrays;
import java.util.Locale;
+import javax.annotation.Nullable;
+
/**
* An expandable dictionary that stores the words in the user dictionary provider into a binary
* dictionary file to use it from native code.
@@ -47,36 +46,26 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
private static final String USER_DICTIONARY_ALL_LANGUAGES = "";
private static final int HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY = 250;
private static final int LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY = 160;
- // Shortcut frequency is 0~15, with 15 = whitelist. We don't want user dictionary entries
- // to auto-correct, so we set this to the highest frequency that won't, i.e. 14.
- private static final int USER_DICT_SHORTCUT_FREQUENCY = 14;
- private static final String[] PROJECTION_QUERY_WITH_SHORTCUT = new String[] {
- Words.WORD,
- Words.SHORTCUT,
- Words.FREQUENCY,
- };
- private static final String[] PROJECTION_QUERY_WITHOUT_SHORTCUT = new String[] {
- Words.WORD,
- Words.FREQUENCY,
- };
+ private static final String[] PROJECTION_QUERY = new String[] {Words.WORD, Words.FREQUENCY};
private static final String NAME = "userunigram";
private ContentObserver mObserver;
- final private String mLocale;
+ final private String mLocaleString;
final private boolean mAlsoUseMoreRestrictiveLocales;
protected UserBinaryDictionary(final Context context, final Locale locale,
- final boolean alsoUseMoreRestrictiveLocales, final File dictFile, final String name) {
+ final boolean alsoUseMoreRestrictiveLocales,
+ final File dictFile, final String name) {
super(context, getDictName(name, locale, dictFile), locale, Dictionary.TYPE_USER, dictFile);
if (null == locale) throw new NullPointerException(); // Catch the error earlier
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;
+ mLocaleString = USER_DICTIONARY_ALL_LANGUAGES;
} else {
- mLocale = localeStr;
+ mLocaleString = localeStr;
}
mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales;
ContentResolver cres = context.getContentResolver();
@@ -101,10 +90,13 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
reloadDictionaryIfRequired();
}
- @UsedForTesting
- public static UserBinaryDictionary getDictionary(final Context context, final Locale locale,
- final File dictFile, final String dictNamePrefix) {
- return new UserBinaryDictionary(context, locale, false /* alsoUseMoreRestrictiveLocales */,
+ // Note: This method is called by {@link DictionaryFacilitator} using Java reflection.
+ @ExternallyReferenced
+ public static UserBinaryDictionary getDictionary(
+ final Context context, final Locale locale, final File dictFile,
+ final String dictNamePrefix, @Nullable final String account) {
+ return new UserBinaryDictionary(
+ context, locale, false /* alsoUseMoreRestrictiveLocales */,
dictFile, dictNamePrefix + NAME);
}
@@ -124,7 +116,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
// This is correct for locale processing.
// For this example, we'll look at the "en_US_POSIX" case.
final String[] localeElements =
- TextUtils.isEmpty(mLocale) ? new String[] {} : mLocale.split("_", 3);
+ TextUtils.isEmpty(mLocaleString) ? new String[] {} : mLocaleString.split("_", 3);
final int length = localeElements.length;
final StringBuilder request = new StringBuilder("(locale is NULL)");
@@ -167,24 +159,12 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
requestArguments = localeElements;
}
final String requestString = request.toString();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- try {
- addWordsFromProjectionLocked(PROJECTION_QUERY_WITH_SHORTCUT, requestString,
- requestArguments);
- } catch (IllegalArgumentException e) {
- // This may happen on some non-compliant devices where the declared API is JB+ but
- // the SHORTCUT column is not present for some reason.
- addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString,
- requestArguments);
- }
- } else {
- addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString,
- requestArguments);
- }
+ addWordsFromProjectionLocked(PROJECTION_QUERY, requestString, requestArguments);
}
private void addWordsFromProjectionLocked(final String[] query, String request,
- final String[] requestArguments) throws IllegalArgumentException {
+ final String[] requestArguments)
+ throws IllegalArgumentException {
Cursor cursor = null;
try {
cursor = mContext.getContentResolver().query(
@@ -201,69 +181,33 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
}
}
- public static boolean isEnabled(final Context context) {
- final ContentResolver cr = context.getContentResolver();
- final ContentProviderClient client = cr.acquireContentProviderClient(Words.CONTENT_URI);
- if (client != null) {
- client.release();
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Adds a word to the user dictionary and makes it persistent.
- *
- * @param context the context
- * @param locale the locale
- * @param word the word to add. If the word is capitalized, then the dictionary will
- * recognize it as a capitalized word when searched.
- */
- public static void addWordToUserDictionary(final Context context, final Locale locale,
- final String word) {
- // Update the user dictionary provider
- UserDictionaryCompatUtils.addWord(context, word,
- HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY, null, locale);
- }
-
- private int scaleFrequencyFromDefaultToLatinIme(final int defaultFrequency) {
+ private static int scaleFrequencyFromDefaultToLatinIme(final int defaultFrequency) {
// The default frequency for the user dictionary is 250 for historical reasons.
// Latin IME considers a good value for the default user dictionary frequency
// is about 160 considering the scale we use. So we are scaling down the values.
if (defaultFrequency > Integer.MAX_VALUE / LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY) {
return (defaultFrequency / HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY)
* LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY;
- } else {
- return (defaultFrequency * LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY)
- / HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY;
}
+ return (defaultFrequency * LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY)
+ / HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY;
}
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()) {
final int indexWord = cursor.getColumnIndex(Words.WORD);
- final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(Words.SHORTCUT) : 0;
final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
while (!cursor.isAfterLast()) {
final String word = cursor.getString(indexWord);
- final String shortcut = hasShortcutColumn ? cursor.getString(indexShortcut) : null;
final int frequency = cursor.getInt(indexFrequency);
final int adjustedFrequency = scaleFrequencyFromDefaultToLatinIme(frequency);
// Safeguard against adding really long words.
if (word.length() <= MAX_WORD_LENGTH) {
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addUnigramLocked(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 */);
- addUnigramLocked(shortcut, adjustedFrequency, word,
- USER_DICT_SHORTCUT_FREQUENCY, true /* isNotAWord */,
- false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
- }
+ addUnigramLocked(word, adjustedFrequency, false /* isNotAWord */,
+ false /* isPossiblyOffensive */,
+ BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
cursor.moveToNext();
}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 32d1fe372..8803edc88 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,11 +16,17 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.event.CombinerChain;
import com.android.inputmethod.event.Event;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.common.ComposedData;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.CoordinateUtils;
+import com.android.inputmethod.latin.common.InputPointers;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.define.DebugFlags;
-import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import java.util.ArrayList;
import java.util.Collections;
@@ -31,7 +37,7 @@ import javax.annotation.Nonnull;
* A place to store the currently composing word with information such as adjacent key codes as well
*/
public final class WordComposer {
- private static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
+ private static final int MAX_WORD_LENGTH = DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH;
private static final boolean DBG = DebugFlags.DEBUG_ENABLED;
public static final int CAPS_MODE_OFF = 0;
@@ -48,7 +54,7 @@ public final class WordComposer {
// The list of events that served to compose this string.
private final ArrayList<Event> mEvents;
private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
- private String mAutoCorrection;
+ private SuggestedWordInfo mAutoCorrection;
private boolean mIsResumed;
private boolean mIsBatchMode;
// A memory of the last rejected batch mode suggestion, if any. This goes like this: the user
@@ -87,6 +93,10 @@ public final class WordComposer {
refreshTypedWordCache();
}
+ public ComposedData getComposedDataSnapshot() {
+ return new ComposedData(getInputPointers(), isBatchMode(), mTypedWordCache.toString());
+ }
+
/**
* Restart the combiners, possibly with a new spec.
* @param combiningSpec The spec string for combining. This is found in the extra value.
@@ -95,8 +105,7 @@ public final class WordComposer {
final String nonNullCombiningSpec = null == combiningSpec ? "" : combiningSpec;
if (!nonNullCombiningSpec.equals(mCombiningSpec)) {
mCombinerChain = new CombinerChain(
- mCombinerChain.getComposingWordWithCombiningFeedback().toString(),
- CombinerChain.createCombiners(nonNullCombiningSpec));
+ mCombinerChain.getComposingWordWithCombiningFeedback().toString());
mCombiningSpec = nonNullCombiningSpec;
}
}
@@ -127,43 +136,10 @@ public final class WordComposer {
* Number of keystrokes in the composing word.
* @return the number of keystrokes
*/
- // This may be made public if need be, but right now it's not used anywhere
- /* package for tests */ int size() {
+ public int size() {
return mCodePointSize;
}
- /**
- * 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) {
- // This method can be called on a separate thread and mTypedWordCache can change while we
- // are executing this method.
- final String typedWord = mTypedWordCache.toString();
- // lastIndex is exclusive
- final int lastIndex = typedWord.length()
- - StringUtils.getTrailingSingleQuotesCount(typedWord);
- if (lastIndex <= 0) {
- // The string is empty or contains only single quotes.
- return 0;
- }
-
- // 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(typedWord, 0, lastIndex);
- if (codePointSize > destination.length) {
- return -1;
- }
- return StringUtils.copyCodePointsAndReturnCodePointCount(destination, typedWord, 0,
- lastIndex, true /* downCase */);
- }
-
public boolean isSingleLetter() {
return size() == 1;
}
@@ -182,7 +158,7 @@ public final class WordComposer {
* @return the processed event. Never null, but may be marked as consumed.
*/
@Nonnull
- public Event processEvent(final Event event) {
+ public Event processEvent(@Nonnull final Event event) {
final Event processedEvent = mCombinerChain.processEvent(mEvents, event);
// The retained state of the combiner chain may have changed while processing the event,
// so we need to update our cache.
@@ -256,31 +232,33 @@ 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 actualMoveAmount = 0;
int cursorPos = mCursorPositionWithinWord;
// 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.
- while (actualMoveAmountWithinWord < expectedMoveAmount && cursorPos < mCodePointSize) {
- actualMoveAmountWithinWord += Character.charCount(codePoints[cursorPos]);
+ while (actualMoveAmount < expectedMoveAmount && cursorPos < codePoints.length) {
+ actualMoveAmount += Character.charCount(codePoints[cursorPos]);
++cursorPos;
}
} else {
// Moving the cursor backward for the expected amount or until the start of the word
// has been reached, whichever comes first.
- while (actualMoveAmountWithinWord > expectedMoveAmount && cursorPos > 0) {
+ while (actualMoveAmount > expectedMoveAmount && cursorPos > 0) {
--cursorPos;
- actualMoveAmountWithinWord -= Character.charCount(codePoints[cursorPos]);
+ actualMoveAmount -= Character.charCount(codePoints[cursorPos]);
}
}
// If the actual and expected amounts differ, we crossed the start or the end of the word
// so the result would not be inside the composing word.
- if (actualMoveAmountWithinWord != expectedMoveAmount) return false;
+ if (actualMoveAmount != expectedMoveAmount) {
+ return false;
+ }
mCursorPositionWithinWord = cursorPos;
+ mCombinerChain.applyProcessedEvent(mCombinerChain.processEvent(
+ mEvents, Event.createCursorMovedEvent(cursorPos)));
return true;
}
@@ -353,9 +331,8 @@ public final class WordComposer {
if (size() <= 1) {
return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
|| mCapitalizedMode == CAPS_MODE_MANUAL_SHIFT_LOCKED;
- } else {
- return mCapsCount == size();
}
+ return mCapsCount == size();
}
public boolean wasShiftedNoLock() {
@@ -418,14 +395,14 @@ public final class WordComposer {
/**
* Sets the auto-correction for this word.
*/
- public void setAutoCorrection(final String correction) {
- mAutoCorrection = correction;
+ public void setAutoCorrection(final SuggestedWordInfo autoCorrection) {
+ mAutoCorrection = autoCorrection;
}
/**
* @return the auto-correction for this word, or null if none.
*/
- public String getAutoCorrectionOrNull() {
+ public SuggestedWordInfo getAutoCorrectionOrNull() {
return mAutoCorrection;
}
@@ -439,13 +416,13 @@ public final class WordComposer {
// `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
// committedWord should contain suggestion spans if applicable.
public LastComposedWord commitWord(final int type, final CharSequence committedWord,
- final String separatorString, final PrevWordsInfo prevWordsInfo) {
+ final String separatorString, final NgramContext ngramContext) {
// Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK
// or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
// the last composed word to ensure this does not happen.
final LastComposedWord lastComposedWord = new LastComposedWord(mEvents,
mInputPointers, mTypedWordCache.toString(), committedWord, separatorString,
- prevWordsInfo, mCapitalizedMode);
+ ngramContext, mCapitalizedMode);
mInputPointers.reset();
if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
&& type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) {
@@ -491,4 +468,14 @@ public final class WordComposer {
public String getRejectedBatchModeSuggestion() {
return mRejectedBatchModeSuggestion;
}
+
+ @UsedForTesting
+ void addInputPointerForTest(int index, int keyX, int keyY) {
+ mInputPointers.addPointerAt(index, keyX, keyY, 0, 0);
+ }
+
+ @UsedForTesting
+ void setTypedWordCacheForTests(String typedWordCacheForTests) {
+ mTypedWordCache = typedWordCacheForTests;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java b/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java
new file mode 100644
index 000000000..00bcecf52
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.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.latin.accounts;
+
+import android.accounts.AccountManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.settings.LocalSettingsConstants;
+
+/**
+ * {@link BroadcastReceiver} for {@link AccountManager#LOGIN_ACCOUNTS_CHANGED_ACTION}.
+ */
+public class AccountsChangedReceiver extends BroadcastReceiver {
+ static final String TAG = "AccountsChangedReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(intent.getAction())) {
+ Log.w(TAG, "Received unknown broadcast: " + intent);
+ return;
+ }
+
+ // Ideally the account preference could live in a different preferences file
+ // that wasn't being backed up and restored, however the preference fragments
+ // currently only deal with the default shared preferences which is why
+ // separating this out into a different file is not trivial currently.
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ final String currentAccount = prefs.getString(
+ LocalSettingsConstants.PREF_ACCOUNT_NAME, null);
+ removeUnknownAccountFromPreference(prefs, getAccountsForLogin(context), currentAccount);
+ }
+
+ /**
+ * Helper method to help test this receiver.
+ */
+ @UsedForTesting
+ protected String[] getAccountsForLogin(Context context) {
+ return LoginAccountUtils.getAccountsForLogin(context);
+ }
+
+ /**
+ * Removes the currentAccount from preferences if it's not found
+ * in the list of current accounts.
+ */
+ private static void removeUnknownAccountFromPreference(final SharedPreferences prefs,
+ final String[] accounts, final String currentAccount) {
+ if (currentAccount == null) {
+ return;
+ }
+ for (final String account : accounts) {
+ if (TextUtils.equals(currentAccount, account)) {
+ return;
+ }
+ }
+ Log.i(TAG, "The current account was removed from the system: " + currentAccount);
+ prefs.edit()
+ .remove(LocalSettingsConstants.PREF_ACCOUNT_NAME)
+ .apply();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/accounts/AuthUtils.java b/java/src/com/android/inputmethod/latin/accounts/AuthUtils.java
new file mode 100644
index 000000000..31aba3631
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/accounts/AuthUtils.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.accounts;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+
+import java.io.IOException;
+
+/**
+ * Utility class that handles generation/invalidation of auth tokens in the app.
+ */
+public class AuthUtils {
+ private final AccountManager mAccountManager;
+
+ public AuthUtils(Context context) {
+ mAccountManager = AccountManager.get(context);
+ }
+
+ /**
+ * @see AccountManager#invalidateAuthToken(String, String)
+ */
+ public void invalidateAuthToken(final String accountType, final String authToken) {
+ mAccountManager.invalidateAuthToken(accountType, authToken);
+ }
+
+ /**
+ * @see AccountManager#getAuthToken(
+ * Account, String, Bundle, boolean, AccountManagerCallback, Handler)
+ */
+ public AccountManagerFuture<Bundle> getAuthToken(final Account account,
+ final String authTokenType, final Bundle options, final boolean notifyAuthFailure,
+ final AccountManagerCallback<Bundle> callback, final Handler handler) {
+ return mAccountManager.getAuthToken(account, authTokenType, options, notifyAuthFailure,
+ callback, handler);
+ }
+
+ /**
+ * @see AccountManager#blockingGetAuthToken(Account, String, boolean)
+ */
+ public String blockingGetAuthToken(final Account account, final String authTokenType,
+ final boolean notifyAuthFailure) throws OperationCanceledException,
+ AuthenticatorException, IOException {
+ return mAccountManager.blockingGetAuthToken(account, authTokenType, notifyAuthFailure);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
deleted file mode 100644
index a87785b1a..000000000
--- a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
+++ /dev/null
@@ -1,189 +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.debug;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnClickListener;
-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.DictionaryHeader;
-import com.android.inputmethod.latin.utils.DialogUtils;
-import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Locale;
-
-/**
- * A class to read a local file as a dictionary for debugging purposes.
- */
-public class ExternalDictionaryGetterForDebug {
- private static final String SOURCE_FOLDER = Environment.getExternalStorageDirectory().getPath()
- + "/Download";
-
- private static String[] findDictionariesInTheDownloadedFolder() {
- final File[] files = new File(SOURCE_FOLDER).listFiles();
- final ArrayList<String> eligibleList = new ArrayList<>();
- for (File f : files) {
- final DictionaryHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(f);
- if (null == header) continue;
- eligibleList.add(f.getName());
- }
- return eligibleList.toArray(new String[0]);
- }
-
- public static void chooseAndInstallDictionary(final Context context) {
- final String[] fileNames = findDictionariesInTheDownloadedFolder();
- if (0 == fileNames.length) {
- showNoFileDialog(context);
- } else if (1 == fileNames.length) {
- askInstallFile(context, SOURCE_FOLDER, fileNames[0], null /* completeRunnable */);
- } else {
- showChooseFileDialog(context, fileNames);
- }
- }
-
- private static void showNoFileDialog(final Context context) {
- new AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(context))
- .setMessage(R.string.read_external_dictionary_no_files_message)
- .setPositiveButton(android.R.string.ok, new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- dialog.dismiss();
- }
- }).create().show();
- }
-
- private static void showChooseFileDialog(final Context context, final String[] fileNames) {
- 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) {
- askInstallFile(context, SOURCE_FOLDER, fileNames[which],
- null /* completeRunnable */);
- }
- })
- .create().show();
- }
-
- /**
- * Shows a dialog which offers the user to install the external dictionary.
- */
- 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 DictionaryHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file);
- final StringBuilder message = new StringBuilder();
- final String locale = header.getLocaleString();
- for (String key : header.mDictionaryOptions.mAttributes.keySet()) {
- message.append(key + " = " + header.mDictionaryOptions.mAttributes.get(key));
- message.append("\n");
- }
- final String languageName = LocaleUtils.constructLocaleFromString(locale)
- .getDisplayName(Locale.getDefault());
- final String title = String.format(
- context.getString(R.string.read_external_dictionary_confirm_install_message),
- languageName);
- new AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(context))
- .setTitle(title)
- .setMessage(message)
- .setNegativeButton(android.R.string.cancel, new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- dialog.dismiss();
- if (completeRunnable != null) {
- completeRunnable.run();
- }
- }
- }).setPositiveButton(android.R.string.ok, new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- installFile(context, file, header);
- dialog.dismiss();
- if (completeRunnable != null) {
- completeRunnable.run();
- }
- }
- }).setOnCancelListener(new OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- // Canceled by the user by hitting the back key
- if (completeRunnable != null) {
- completeRunnable.run();
- }
- }
- }).create().show();
- }
-
- private static void installFile(final Context context, final File file,
- final DictionaryHeader header) {
- BufferedOutputStream outputStream = null;
- File tempFile = null;
- try {
- final String locale = header.getLocaleString();
- // Create the id for a main dictionary for this locale
- final String id = BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY
- + BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale;
- final String finalFileName = DictionaryInfoUtils.getCacheFileName(id, locale, context);
- final String tempFileName = BinaryDictionaryGetter.getTempFileName(id, context);
- tempFile = new File(tempFileName);
- tempFile.delete();
- outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));
- final BufferedInputStream bufferedStream = new BufferedInputStream(
- new FileInputStream(file));
- BinaryDictionaryFileDumper.checkMagicAndCopyFileTo(bufferedStream, outputStream);
- outputStream.flush();
- final File finalFile = new File(finalFileName);
- finalFile.delete();
- if (!tempFile.renameTo(finalFile)) {
- throw new IOException("Can't move the file to its final name");
- }
- } catch (IOException e) {
- // There was an error: show a dialog
- new AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(context))
- .setTitle(R.string.read_external_dictionary_error)
- .setMessage(e.toString())
- .setPositiveButton(android.R.string.ok, new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- dialog.dismiss();
- }
- }).create().show();
- return;
- } finally {
- try {
- if (null != outputStream) outputStream.close();
- if (null != tempFile) tempFile.delete();
- } catch (IOException e) {
- // Don't do anything
- }
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index fdab7f25f..5b3b28d75 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin.inputlogic;
import android.graphics.Color;
-import android.inputmethodservice.InputMethodService;
import android.os.SystemClock;
import android.text.SpannableString;
import android.text.Spanned;
@@ -28,32 +27,28 @@ import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.inputmethod.CorrectionInfo;
-import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
-import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
import com.android.inputmethod.compat.SuggestionSpanUtils;
import com.android.inputmethod.event.Event;
import com.android.inputmethod.event.InputTransaction;
+import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.keyboard.TextDecorator;
-import com.android.inputmethod.keyboard.TextDecoratorUiOperator;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LastComposedWord;
import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.RichInputConnection;
import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.define.DebugFlags;
-import com.android.inputmethod.latin.define.ProductionFlags;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
@@ -61,13 +56,15 @@ import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.TextRange;
import java.util.ArrayList;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
+import javax.annotation.Nonnull;
+
/**
* This class manages the input logic.
*/
@@ -75,7 +72,7 @@ public final class InputLogic {
private static final String TAG = InputLogic.class.getSimpleName();
// TODO : Remove this member when we can.
- private final LatinIME mLatinIME;
+ final LatinIME mLatinIME;
private final SuggestionStripViewAccessor mSuggestionStripViewAccessor;
// Never null.
@@ -85,18 +82,10 @@ public final class InputLogic {
// Current space state of the input method. This can be any of the above constants.
private int mSpaceState;
// Never null
- public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+ public SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance();
public final Suggest mSuggest;
private final DictionaryFacilitator mDictionaryFacilitator;
- private final TextDecorator mTextDecorator = new TextDecorator(new TextDecorator.Listener() {
- @Override
- public void onClickComposingTextToAddToDictionary(final String word) {
- mLatinIME.addWordToUserDictionary(word);
- mLatinIME.dismissAddToDictionaryHint();
- }
- });
-
public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
// This has package visibility so it can be accessed from InputLogicHandler.
/* package */ final WordComposer mWordComposer;
@@ -144,13 +133,20 @@ public final class InputLogic {
*/
public void startInput(final String combiningSpec, final SettingsValues settingsValues) {
mEnteredText = null;
+ if (!mWordComposer.getTypedWord().isEmpty()) {
+ // For messaging apps that offer send button, the IME does not get the opportunity
+ // to capture the last word. This block should capture those uncommitted words.
+ // The timestamp at which it is captured is not accurate but close enough.
+ StatsUtils.onWordCommitUserTyped(
+ mWordComposer.getTypedWord(), mWordComposer.isBatchMode());
+ }
mWordComposer.restartCombining(combiningSpec);
resetComposingState(true /* alsoResetLastComposedWord */);
mDeleteCount = 0;
mSpaceState = SpaceState.NONE;
mRecapitalizeStatus.disable(); // Do not perform recapitalize until the cursor is moved once
mCurrentlyPressedHardwareKeys.clear();
- mSuggestedWords = SuggestedWords.EMPTY;
+ mSuggestedWords = SuggestedWords.getEmptyInstance();
// In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying
// so we try using some heuristics to find out about these and fix them.
mConnection.tryFixLyingCursorPosition();
@@ -161,13 +157,9 @@ public final class InputLogic {
mInputLogicHandler.reset();
}
- if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK) {
- // AcceptTypedWord feature relies on CursorAnchorInfo.
- if (settingsValues.mShouldShowUiToAcceptTypedWord) {
- mConnection.requestCursorUpdates(true /* enableMonitor */,
- true /* requestImmediateCallback */);
- }
- mTextDecorator.reset();
+ if (settingsValues.mShouldShowLxxSuggestionUi) {
+ mConnection.requestCursorUpdates(true /* enableMonitor */,
+ true /* requestImmediateCallback */);
}
}
@@ -204,6 +196,8 @@ public final class InputLogic {
public void finishInput() {
if (mWordComposer.isComposingWord()) {
mConnection.finishComposingText();
+ StatsUtils.onWordCommitUserTyped(
+ mWordComposer.getTypedWord(), mWordComposer.isBatchMode());
}
resetComposingState(true /* alsoResetLastComposedWord */);
mInputLogicHandler.reset();
@@ -231,9 +225,7 @@ public final class InputLogic {
* @return the complete transaction object
*/
public InputTransaction onTextInput(final SettingsValues settingsValues, final Event event,
- final int keyboardShiftMode,
- // TODO: remove this argument
- final LatinIME.UIHandler handler) {
+ final int keyboardShiftMode, final LatinIME.UIHandler handler) {
final String rawText = event.getTextToCommit().toString();
final InputTransaction inputTransaction = new InputTransaction(settingsValues, event,
SystemClock.uptimeMillis(), mSpaceState,
@@ -247,9 +239,10 @@ public final class InputLogic {
handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_TYPING);
final String text = performSpecificTldProcessingOnTextInput(rawText);
if (SpaceState.PHANTOM == mSpaceState) {
- promotePhantomSpace(settingsValues);
+ insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues);
}
mConnection.commitText(text, 1);
+ StatsUtils.onWordCommitUserTyped(mEnteredText, mWordComposer.isBatchMode());
mConnection.endBatchEdit();
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.NONE;
@@ -260,20 +253,6 @@ public final class InputLogic {
}
/**
- * Determines whether "Touch again to save" should be shown or not.
- * @param suggestionInfo the suggested word chosen by the user.
- * @return {@code true} if we should show the "Touch again to save" hint.
- */
- private boolean shouldShowAddToDictionaryHint(final SuggestedWordInfo suggestionInfo) {
- // 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).
- return (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_TYPED)
- || suggestionInfo.isKindOf(SuggestedWordInfo.KIND_OOV_CORRECTION))
- && !mDictionaryFacilitator.isValidWord(suggestionInfo.mWord, true /* ignoreCase */)
- && mDictionaryFacilitator.isUserDictionaryEnabled();
- }
-
- /**
* A suggestion was picked from the suggestion strip.
* @param settingsValues the current values of the settings.
* @param suggestionInfo the suggestion info.
@@ -285,12 +264,14 @@ public final class InputLogic {
// interface
public InputTransaction onPickSuggestionManually(final SettingsValues settingsValues,
final SuggestedWordInfo suggestionInfo, final int keyboardShiftState,
- // TODO: remove these arguments
final int currentKeyboardScriptId, 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()) {
+ // We still want to log a suggestion click.
+ StatsUtils.onPickSuggestionManually(
+ mSuggestedWords, suggestionInfo, mDictionaryFacilitator);
// Word separators are suggested before the user inputs something.
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
final Event event = Event.createPunctuationSuggestionPickedEvent(suggestionInfo);
@@ -312,7 +293,7 @@ public final class InputLogic {
final int firstChar = Character.codePointAt(suggestion, 0);
if (!settingsValues.isWordSeparator(firstChar)
|| settingsValues.isUsuallyPrecededBySpace(firstChar)) {
- promotePhantomSpace(settingsValues);
+ insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues);
}
}
@@ -321,7 +302,7 @@ public final class InputLogic {
// however need to reset the suggestion strip right away, because we know we can't take
// the risk of calling commitCompletion twice because we don't know how the app will react.
if (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_APP_DEFINED)) {
- mSuggestedWords = SuggestedWords.EMPTY;
+ mSuggestedWords = SuggestedWords.getEmptyInstance();
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
resetComposingState(true /* alsoResetLastComposedWord */);
@@ -330,7 +311,6 @@ public final class InputLogic {
return inputTransaction;
}
- final boolean shouldShowAddToDictionaryHint = shouldShowAddToDictionaryHint(suggestionInfo);
commitChosenWord(settingsValues, suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
LastComposedWord.NOT_A_SEPARATOR);
mConnection.endBatchEdit();
@@ -340,13 +320,14 @@ public final class InputLogic {
mSpaceState = SpaceState.PHANTOM;
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
- if (shouldShowAddToDictionaryHint) {
- mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
- } else {
- // If we're not showing the "Touch again to save", then update the suggestion strip.
- // That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE.
- handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
- }
+ // If we're not showing the "Touch again to save", then update the suggestion strip.
+ // That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE.
+ handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
+
+ StatsUtils.onPickSuggestionManually(
+ mSuggestedWords, suggestionInfo, mDictionaryFacilitator);
+ StatsUtils.onWordCommitSuggestionPickedManually(
+ suggestionInfo.mWord, mWordComposer.isBatchMode());
return inputTransaction;
}
@@ -416,14 +397,8 @@ public final class InputLogic {
// The cursor has been moved : we now accept to perform recapitalization
mRecapitalizeStatus.enable();
- // We moved the cursor and need to invalidate the indicator right now.
- mTextDecorator.reset();
- // Remaining background color that was used for the add-to-dictionary indicator should be
- // removed.
- mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
// We moved the cursor. If we are touching a word, we need to resume suggestion.
- mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */,
- true /* shouldDelay */);
+ mLatinIME.mHandler.postResumeSuggestions(true /* shouldDelay */);
// Stop the last recapitalization, if started.
mRecapitalizeStatus.stop();
return true;
@@ -442,9 +417,8 @@ public final class InputLogic {
* {@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 these arguments
+ public InputTransaction onCodeInput(final SettingsValues settingsValues,
+ @Nonnull final Event event, final int keyboardShiftMode,
final int currentKeyboardScriptId, final LatinIME.UIHandler handler) {
final Event processedEvent = mWordComposer.processEvent(event);
final InputTransaction inputTransaction = new InputTransaction(settingsValues,
@@ -491,17 +465,14 @@ public final class InputLogic {
}
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 */);
+ SuggestedWords.getEmptyInstance(), false /* dismissGestureFloatingPreviewText */);
handler.cancelUpdateSuggestionStrip();
++mAutoCommitSequenceNumber;
mConnection.beginBatchEdit();
- if (!mWordComposer.isComposingWord()) {
- mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
- } else {
+ if (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 batch input at the current cursor position.
@@ -557,30 +528,7 @@ public final class InputLogic {
* 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(Constants.WORD_SEPARATOR, 2);
- batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord);
- promotePhantomSpace(settingsValues);
- mConnection.commitText(commitParts[0], 0);
- mSpaceState = SpaceState.PHANTOM;
- keyboardSwitcher.requestUpdatingShiftState(
- getCurrentAutoCapsState(settingsValues), getCurrentRecapitalizeState());
- mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode(
- settingsValues, keyboardSwitcher.getKeyboardShiftMode()));
- ++mAutoCommitSequenceNumber;
- }
- }
- }
+ public void onUpdateBatchInput(final InputPointers batchPointers) {
mInputLogicHandler.onUpdateBatchInput(batchPointers, mAutoCommitSequenceNumber);
}
@@ -589,27 +537,25 @@ public final class InputLogic {
++mAutoCommitSequenceNumber;
}
- // TODO: remove this argument
public void onCancelBatchInput(final LatinIME.UIHandler handler) {
mInputLogicHandler.onCancelBatchInput();
handler.showGesturePreviewAndSuggestionStrip(
- SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
+ SuggestedWords.getEmptyInstance(), true /* dismissGestureFloatingPreviewText */);
}
// TODO: on the long term, this method should become private, but it will be difficult.
// Especially, how do we deal with InputMethodService.onDisplayCompletions?
- public void setSuggestedWords(final SuggestedWords suggestedWords,
- final SettingsValues settingsValues, final LatinIME.UIHandler handler) {
- if (SuggestedWords.EMPTY != suggestedWords) {
- final String autoCorrection;
+ public void setSuggestedWords(final SuggestedWords suggestedWords) {
+ if (!suggestedWords.isEmpty()) {
+ final SuggestedWordInfo suggestedWordInfo;
if (suggestedWords.mWillAutoCorrect) {
- autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+ suggestedWordInfo = suggestedWords.getInfo(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 = suggestedWords.mTypedWord;
+ suggestedWordInfo = suggestedWords.mTypedWordInfo;
}
- mWordComposer.setAutoCorrection(autoCorrection);
+ mWordComposer.setAutoCorrection(suggestedWordInfo);
}
mSuggestedWords = suggestedWords;
final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect;
@@ -666,7 +612,6 @@ public final class InputLogic {
* @param inputTransaction The transaction in progress.
*/
private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction,
- // TODO: remove these arguments
final int currentKeyboardScriptId, final LatinIME.UIHandler handler) {
switch (event.mKeyCode) {
case Constants.CODE_DELETE:
@@ -683,7 +628,7 @@ public final class InputLogic {
break;
case Constants.CODE_CAPSLOCK:
// Note: Changing keyboard to shift lock state is handled in
- // {@link KeyboardSwitcher#onCodeInput(int)}.
+ // {@link KeyboardSwitcher#onEvent(Event)}.
break;
case Constants.CODE_SYMBOL_SHIFT:
// Note: Calling back to the keyboard on the symbol Shift key is handled in
@@ -711,14 +656,13 @@ public final class InputLogic {
break;
case Constants.CODE_EMOJI:
// Note: Switching emoji keyboard is being handled in
- // {@link KeyboardState#onCodeInput(int,int)}.
+ // {@link KeyboardState#onEvent(Event,int)}.
break;
case Constants.CODE_ALPHA_FROM_EMOJI:
// Note: Switching back from Emoji keyboard to the main keyboard is being
- // handled in {@link KeyboardState#onCodeInput(int,int)}.
+ // handled in {@link KeyboardState#onEvent(Event,int)}.
break;
case Constants.CODE_SHIFT_ENTER:
- // TODO: remove this object
final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_ENTER,
event.mKeyCode, event.mX, event.mY, event.isKeyRepeat());
handleNonSpecialCharacterEvent(tmpEvent, inputTransaction, handler);
@@ -742,7 +686,6 @@ public final class InputLogic {
*/
private void handleNonFunctionalEvent(final Event event,
final InputTransaction inputTransaction,
- // TODO: remove this argument
final LatinIME.UIHandler handler) {
inputTransaction.setDidAffectContents();
switch (event.mCodePoint) {
@@ -788,18 +731,7 @@ public final class InputLogic {
*/
private void handleNonSpecialCharacterEvent(final Event event,
final InputTransaction inputTransaction,
- // TODO: remove this argument
final LatinIME.UIHandler handler) {
- if (!mWordComposer.isComposingWord()) {
- mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
- // In case the "add to dictionary" hint was still displayed.
- // TODO: Do we really need to check if we have composing text here?
- if (mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) {
- mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
- mTextDecorator.reset();
- }
- }
-
final int codePoint = event.mCodePoint;
mSpaceState = SpaceState.NONE;
if (inputTransaction.mSettingsValues.isWordSeparator(codePoint)
@@ -843,7 +775,7 @@ public final class InputLogic {
// Sanity check
throw new RuntimeException("Should not be composing here");
}
- promotePhantomSpace(settingsValues);
+ insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues);
}
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
@@ -904,7 +836,6 @@ public final class InputLogic {
* @param inputTransaction The transaction in progress.
*/
private void handleSeparatorEvent(final Event event, final InputTransaction inputTransaction,
- // TODO: remove this argument
final LatinIME.UIHandler handler) {
final int codePoint = event.mCodePoint;
final SettingsValues settingsValues = inputTransaction.mSettingsValues;
@@ -954,12 +885,13 @@ public final class InputLogic {
}
if (needsPrecedingSpace) {
- promotePhantomSpace(settingsValues);
+ insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues);
}
if (tryPerformDoubleSpacePeriod(event, inputTransaction)) {
mSpaceState = SpaceState.DOUBLE;
inputTransaction.setRequiresUpdateSuggestions();
+ StatsUtils.onDoubleSpacePeriod();
} else if (swapWeakSpace && trySwapSwapperAndSpace(event, inputTransaction)) {
mSpaceState = SpaceState.SWAP_PUNCTUATION;
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
@@ -1011,7 +943,6 @@ public final class InputLogic {
* @param inputTransaction The transaction in progress.
*/
private void handleBackspaceEvent(final Event event, final InputTransaction inputTransaction,
- // TODO: remove this argument, put it into settingsValues
final int currentKeyboardScriptId) {
mSpaceState = SpaceState.NONE;
mDeleteCount++;
@@ -1041,10 +972,13 @@ public final class InputLogic {
mWordComposer.reset();
mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
if (!TextUtils.isEmpty(rejectedSuggestion)) {
- mDictionaryFacilitator.removeWordFromPersonalizedDicts(rejectedSuggestion);
+ unlearnWord(rejectedSuggestion, inputTransaction.mSettingsValues,
+ Constants.EVENT_REJECTION);
}
+ StatsUtils.onBackspaceWordDelete(rejectedSuggestion.length());
} else {
mWordComposer.applyProcessedEvent(event);
+ StatsUtils.onBackspacePressed(1);
}
if (mWordComposer.isComposingWord()) {
setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
@@ -1054,7 +988,24 @@ public final class InputLogic {
inputTransaction.setRequiresUpdateSuggestions();
} else {
if (mLastComposedWord.canRevertCommit()) {
+ final String lastComposedWord = mLastComposedWord.mTypedWord;
revertCommit(inputTransaction, inputTransaction.mSettingsValues);
+ StatsUtils.onRevertAutoCorrect();
+ StatsUtils.onWordCommitUserTyped(lastComposedWord, mWordComposer.isBatchMode());
+ // Restart suggestions when backspacing into a reverted word. This is required for
+ // the final corrected word to be learned, as learning only occurs when suggestions
+ // are active.
+ //
+ // Note: restartSuggestionsOnWordTouchedByCursor is already called for normal
+ // (non-revert) backspace handling.
+ if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings()
+ && inputTransaction.mSettingsValues.mSpacingAndPunctuations
+ .mCurrentLanguageHasSpaces
+ && !mConnection.isCursorFollowedByWordCharacter(
+ inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
+ restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
+ false /* forStartInput */, currentKeyboardScriptId);
+ }
return;
}
if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
@@ -1062,6 +1013,7 @@ public final class InputLogic {
// This is triggered on backspace after a key that inputs multiple characters,
// like the smiley key or the .com key.
mConnection.deleteSurroundingText(mEnteredText.length(), 0);
+ StatsUtils.onDeleteMultiCharInput(mEnteredText.length());
mEnteredText = null;
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
// In addition we know that spaceState is false, and that we should not be
@@ -1070,21 +1022,26 @@ public final class InputLogic {
}
if (SpaceState.DOUBLE == inputTransaction.mSpaceState) {
cancelDoubleSpacePeriodCountdown();
- if (mConnection.revertDoubleSpacePeriod()) {
+ if (mConnection.revertDoubleSpacePeriod(
+ inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
// No need to reset mSpaceState, it has already be done (that's why we
// receive it as a parameter)
inputTransaction.setRequiresUpdateSuggestions();
mWordComposer.setCapitalizedModeAtStartComposingTime(
WordComposer.CAPS_MODE_OFF);
+ StatsUtils.onRevertDoubleSpacePeriod();
return;
}
} else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
if (mConnection.revertSwapPunctuation()) {
+ StatsUtils.onRevertSwapPunctuation();
// Likewise
return;
}
}
+ boolean hasUnlearnedWordBeingDeleted = false;
+
// 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()) {
@@ -1094,25 +1051,36 @@ public final class InputLogic {
mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
mConnection.getExpectedSelectionEnd());
mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ StatsUtils.onBackspaceSelectedText(numCharsDeleted);
} else {
// There is no selection, just delete one character.
- if (Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) {
- // This should never happen.
- Log.e(TAG, "Backspace when we don't know the selection position");
- }
- if (inputTransaction.mSettingsValues.isBeforeJellyBean() ||
- inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()) {
- // There are two possible reasons to send a key event: either the field has
+ if (inputTransaction.mSettingsValues.isBeforeJellyBean()
+ || inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()
+ || Constants.NOT_A_CURSOR_POSITION
+ == mConnection.getExpectedSelectionEnd()) {
+ // There are three possible reasons to send a key event: either the field has
// type TYPE_NULL, in which case the keyboard should send events, or we are
- // running in backward compatibility mode. Before Jelly bean, the keyboard
- // would simulate a hardware keyboard event on pressing enter or delete. This
- // is bad for many reasons (there are race conditions with commits) but some
- // applications are relying on this behavior so we continue to support it for
- // older apps, so we retain this behavior if the app has target SDK < JellyBean.
+ // running in backward compatibility mode, or we don't know the cursor position.
+ // Before Jelly bean, the keyboard would simulate a hardware keyboard event on
+ // pressing enter or delete. This is bad for many reasons (there are race
+ // conditions with commits) but some applications are relying on this behavior
+ // so we continue to support it for older apps, so we retain this behavior if
+ // the app has target SDK < JellyBean.
+ // As for the case where we don't know the cursor position, it can happen
+ // because of bugs in the framework. But the framework should know, so the next
+ // best thing is to leave it to whatever it thinks is best.
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ int totalDeletedLength = 1;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
+ // If this is an accelerated (i.e., double) deletion, then we need to
+ // consider unlearning here because we may have already reached
+ // the previous word, and will lose it after next deletion.
+ hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
+ inputTransaction.mSettingsValues, currentKeyboardScriptId);
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ totalDeletedLength++;
}
+ StatsUtils.onBackspacePressed(totalDeletedLength);
} else {
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursor == Constants.NOT_A_CODE) {
@@ -1123,32 +1091,81 @@ public final class InputLogic {
// catch it and have their broken interface react. If you need the keyboard
// to do this, you're doing it wrong -- please fix your app.
mConnection.deleteSurroundingText(1, 0);
+ // TODO: Add a new StatsUtils method onBackspaceWhenNoText()
return;
}
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDelete, 0);
+ int totalDeletedLength = lengthToDelete;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
+ // If this is an accelerated (i.e., double) deletion, then we need to
+ // consider unlearning here because we may have already reached
+ // the previous word, and will lose it after next deletion.
+ hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
+ inputTransaction.mSettingsValues, currentKeyboardScriptId);
final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
codePointBeforeCursorToDeleteAgain) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
+ totalDeletedLength += lengthToDeleteAgain;
}
}
+ StatsUtils.onBackspacePressed(totalDeletedLength);
}
}
- if (inputTransaction.mSettingsValues
- .isSuggestionsEnabledPerUserSettings()
+ if (!hasUnlearnedWordBeingDeleted) {
+ // Consider unlearning the word being deleted (if we have not done so already).
+ unlearnWordBeingDeleted(
+ inputTransaction.mSettingsValues, currentKeyboardScriptId);
+ }
+ if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings()
&& inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mCurrentLanguageHasSpaces
&& !mConnection.isCursorFollowedByWordCharacter(
inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
- true /* shouldIncludeResumedWordInSuggestions */, currentKeyboardScriptId);
+ false /* forStartInput */, currentKeyboardScriptId);
+ }
+ }
+ }
+
+ boolean unlearnWordBeingDeleted(
+ final SettingsValues settingsValues,final int currentKeyboardScriptId) {
+ // If we just started backspacing to delete a previous word (but have not
+ // entered the composing state yet), unlearn the word.
+ // TODO: Consider tracking whether or not this word was typed by the user.
+ if (!mConnection.hasSelection()
+ && settingsValues.isSuggestionsEnabledPerUserSettings()
+ && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
+ && !mConnection.isCursorFollowedByWordCharacter(
+ settingsValues.mSpacingAndPunctuations)) {
+ final TextRange range = mConnection.getWordRangeAtCursor(
+ settingsValues.mSpacingAndPunctuations,
+ currentKeyboardScriptId);
+ if (range == null) {
+ // Happens if we don't have an input connection at all.
+ return false;
+ }
+ final String wordBeingDeleted = range.mWord.toString();
+ if (!wordBeingDeleted.isEmpty()) {
+ unlearnWord(wordBeingDeleted, settingsValues,
+ Constants.EVENT_BACKSPACE);
+ return true;
}
}
+ return false;
+ }
+
+ void unlearnWord(final String word, final SettingsValues settingsValues, final int eventType) {
+ final NgramContext ngramContext = mConnection.getNgramContextFromNthPreviousWord(
+ settingsValues.mSpacingAndPunctuations, 2);
+ final long timeStampInSeconds = TimeUnit.MILLISECONDS.toSeconds(
+ System.currentTimeMillis());
+ mDictionaryFacilitator.unlearnFromUserHistory(
+ word, ngramContext, timeStampInSeconds, eventType);
}
/**
@@ -1253,7 +1270,9 @@ public final class InputLogic {
if (null == lastTwo) return false;
final int length = lastTwo.length();
if (length < 2) return false;
- if (lastTwo.charAt(length - 1) != Constants.CODE_SPACE) return false;
+ if (lastTwo.charAt(length - 1) != Constants.CODE_SPACE) {
+ return false;
+ }
// We know there is a space in pos -1, and we have at least two chars. If we have only two
// chars, isSurrogatePairs can't return true as charAt(1) is a space, so this is fine.
final int firstCodePoint =
@@ -1336,7 +1355,7 @@ public final class InputLogic {
}
private void performAdditionToUserHistoryDictionary(final SettingsValues settingsValues,
- final String suggestion, final PrevWordsInfo prevWordsInfo) {
+ final String suggestion, @Nonnull final NgramContext ngramContext) {
// If correction is not enabled, we don't add words to the user history dictionary.
// That's to avoid unintended additions in some sensitive fields, or fields that
// expect to receive non-words.
@@ -1348,7 +1367,7 @@ public final class InputLogic {
final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
System.currentTimeMillis());
mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized,
- prevWordsInfo, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
+ ngramContext, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
}
public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
@@ -1360,7 +1379,7 @@ public final class InputLogic {
+ "requested!");
}
// Clear the suggestions strip.
- mSuggestionStripViewAccessor.showSuggestionStrip(SuggestedWords.EMPTY);
+ mSuggestionStripViewAccessor.showSuggestionStrip(SuggestedWords.getEmptyInstance());
return;
}
@@ -1374,14 +1393,20 @@ public final class InputLogic {
new OnGetSuggestedWordsCallback() {
@Override
public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
- final String typedWord = mWordComposer.getTypedWord();
+ final String typedWordString = mWordComposer.getTypedWord();
+ final SuggestedWordInfo typedWordInfo = new SuggestedWordInfo(
+ typedWordString, "" /* prevWordsContext */,
+ SuggestedWordInfo.MAX_SCORE,
+ SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE);
// 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) {
+ if (suggestedWords.size() > 1 || typedWordString.length() <= 1) {
holder.set(suggestedWords);
} else {
- holder.set(retrieveOlderSuggestions(typedWord, mSuggestedWords));
+ holder.set(retrieveOlderSuggestions(typedWordInfo, mSuggestedWords));
}
}
}
@@ -1400,12 +1425,12 @@ public final class InputLogic {
* do nothing.
*
* @param settingsValues the current values of the settings.
- * @param shouldIncludeResumedWordInSuggestions whether to include the word on which we resume
- * suggestions in the suggestion list.
+ * @param forStartInput whether we're doing this in answer to starting the input (as opposed
+ * to a cursor move, for example). In ICS, there is a platform bug that we need to work
+ * around only when we come here at input start time.
*/
- // TODO: make this private.
public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues,
- final boolean shouldIncludeResumedWordInSuggestions,
+ final boolean forStartInput,
// TODO: remove this argument, put it into settingsValues
final int currentKeyboardScriptId) {
// HACK: We may want to special-case some apps that exhibit bad behavior in case of
@@ -1452,15 +1477,14 @@ public final class InputLogic {
final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor();
if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return;
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
- final String typedWord = range.mWord.toString();
- if (shouldIncludeResumedWordInSuggestions) {
- suggestions.add(new SuggestedWordInfo(typedWord,
- SuggestedWords.MAX_SUGGESTIONS + 1,
- SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- }
- if (!isResumableWord(settingsValues, typedWord)) {
+ final String typedWordString = range.mWord.toString();
+ final SuggestedWordInfo typedWordInfo = new SuggestedWordInfo(typedWordString,
+ "" /* prevWordsContext */, SuggestedWords.MAX_SUGGESTIONS + 1,
+ SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
+ suggestions.add(typedWordInfo);
+ if (!isResumableWord(settingsValues, typedWordString)) {
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
return;
}
@@ -1468,9 +1492,9 @@ public final class InputLogic {
for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) {
for (final String s : span.getSuggestions()) {
++i;
- if (!TextUtils.equals(s, typedWord)) {
+ if (!TextUtils.equals(s, typedWordString)) {
suggestions.add(new SuggestedWordInfo(s,
- SuggestedWords.MAX_SUGGESTIONS - i,
+ "" /* prevWordsContext */, SuggestedWords.MAX_SUGGESTIONS - i,
SuggestedWordInfo.KIND_RESUMED, Dictionary.DICTIONARY_RESUMED,
SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
SuggestedWordInfo.NOT_A_CONFIDENCE
@@ -1478,47 +1502,25 @@ public final class InputLogic {
}
}
}
- final int[] codePoints = StringUtils.toCodePointArray(typedWord);
- // 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.
- final PrevWordsInfo prevWordsInfo = getPrevWordsInfoFromNthPreviousWordForSuggestion(
- settingsValues.mSpacingAndPunctuations,
- 0 == numberOfCharsInWordBeforeCursor ? 1 : 2);
+ final int[] codePoints = StringUtils.toCodePointArray(typedWordString);
mWordComposer.setComposingWord(codePoints,
mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
mWordComposer.setCursorPositionWithinWord(
- typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
- mConnection.maybeMoveTheCursorAroundAndRestoreToWorkaroundABug();
+ typedWordString.codePointCount(0, numberOfCharsInWordBeforeCursor));
+ if (forStartInput) {
+ mConnection.maybeMoveTheCursorAroundAndRestoreToWorkaroundABug();
+ }
mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor());
- if (suggestions.size() <= (shouldIncludeResumedWordInSuggestions ? 1 : 0)) {
+ if (suggestions.size() <= 1) {
// If there weren't any suggestion spans on this word, suggestions#size() will be 1
// if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we
// have no useful suggestions, so we will try to compute some for it instead.
mInputLogicHandler.getSuggestedWords(Suggest.SESSION_ID_TYPING,
SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
@Override
- public void onGetSuggestedWords(
- final SuggestedWords suggestedWordsIncludingTypedWord) {
- final SuggestedWords suggestedWords;
- if (suggestedWordsIncludingTypedWord.size() > 1
- && !shouldIncludeResumedWordInSuggestions) {
- // We were able to compute new suggestions for this word.
- // Remove the typed word, since we don't want to display it in this
- // case. The #getSuggestedWordsExcludingTypedWordForRecorrection()
- // method sets willAutoCorrect to false.
- suggestedWords = suggestedWordsIncludingTypedWord
- .getSuggestedWordsExcludingTypedWordForRecorrection();
- } 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);
+ public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
+ doShowSuggestionsAndClearAutoCorrectionIndicator(suggestedWords);
}});
} else {
// We found suggestion spans in the word. We'll create the SuggestedWords out of
@@ -1526,14 +1528,18 @@ public final class InputLogic {
// 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 */,
+ null /* rawSuggestions */, typedWordInfo, false /* typedWordValid */,
false /* willAutoCorrect */, false /* isObsoleteSuggestions */,
SuggestedWords.INPUT_STYLE_RECORRECTION, SuggestedWords.NOT_A_SEQUENCE_NUMBER);
- mIsAutoCorrectionIndicatorOn = false;
- mLatinIME.mHandler.showSuggestionStrip(suggestedWords);
+ doShowSuggestionsAndClearAutoCorrectionIndicator(suggestedWords);
}
}
+ void doShowSuggestionsAndClearAutoCorrectionIndicator(final SuggestedWords suggestedWords) {
+ mIsAutoCorrectionIndicatorOn = false;
+ mLatinIME.mHandler.showSuggestionStrip(suggestedWords);
+ }
+
/**
* Reverts a previous commit with auto-correction.
*
@@ -1551,6 +1557,10 @@ public final class InputLogic {
final String committedWordString = committedWord.toString();
final int cancelLength = committedWord.length();
final String separatorString = mLastComposedWord.mSeparatorString;
+ // If our separator is a space, we won't actually commit it,
+ // but set the space state to PHANTOM so that a space will be inserted
+ // on the next keypress
+ final boolean usePhantomSpace = separatorString.equals(Constants.STRING_SPACE);
// We want java chars, not codepoints for the following.
final int separatorLength = separatorString.length();
// TODO: should we check our saved separator against the actual contents of the text view?
@@ -1569,9 +1579,11 @@ public final class InputLogic {
}
mConnection.deleteSurroundingText(deleteLength, 0);
if (!TextUtils.isEmpty(committedWord)) {
- mDictionaryFacilitator.removeWordFromPersonalizedDicts(committedWordString);
+ unlearnWord(committedWordString, inputTransaction.mSettingsValues,
+ Constants.EVENT_REVERT);
}
- final String stringToCommit = originallyTypedWord + separatorString;
+ final String stringToCommit = originallyTypedWord +
+ (usePhantomSpace ? "" : separatorString);
final SpannableString textToCommit = new SpannableString(stringToCommit);
if (committedWord instanceof SpannableString) {
final SpannableString committedWordWithSuggestionSpans = (SpannableString)committedWord;
@@ -1583,15 +1595,11 @@ public final class InputLogic {
// 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.
+ // If this is a suggestion span, we check 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);
@@ -1604,24 +1612,17 @@ public final class InputLogic {
}
}
// Add the suggestion list to the list of suggestions.
- textToCommit.setSpan(new SuggestionSpan(inputTransaction.mSettingsValues.mLocale,
- suggestions.toArray(new String[suggestions.size()]), 0 /* flags */),
+ textToCommit.setSpan(new SuggestionSpan(mLatinIME /* context */,
+ inputTransaction.mSettingsValues.mLocale,
+ suggestions.toArray(new String[suggestions.size()]), 0 /* flags */,
+ null /* notificationTargetClass */),
0 /* start */, lastCharIndex /* end */, 0 /* flags */);
}
- final boolean shouldShowAddToDictionaryForTypedWord =
- shouldShowAddToDictionaryForTypedWord(mLastComposedWord, settingsValues);
-
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.
- if (shouldShowAddToDictionaryForTypedWord) {
- mConnection.commitTextWithBackgroundColor(textToCommit, 1,
- settingsValues.mTextHighlightColorForAddToDictionaryIndicator,
- originallyTypedWordString.length());
- } else {
- mConnection.commitText(textToCommit, 1);
+ mConnection.commitText(textToCommit, 1);
+ if (usePhantomSpace) {
+ mSpaceState = SpaceState.PHANTOM;
}
} else {
// For languages without spaces, we revert the typed string but the cursor is flush
@@ -1629,32 +1630,13 @@ public final class InputLogic {
final int[] codePoints = StringUtils.toCodePointArray(stringToCommit);
mWordComposer.setComposingWord(codePoints,
mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
- if (shouldShowAddToDictionaryForTypedWord) {
- setComposingTextInternalWithBackgroundColor(textToCommit, 1,
- settingsValues.mTextHighlightColorForAddToDictionaryIndicator,
- originallyTypedWordString.length());
- } else {
- setComposingTextInternal(textToCommit, 1);
- }
+ setComposingTextInternal(textToCommit, 1);
}
// Don't restart suggestion yet. We'll restart if the user deletes the separator.
mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
- if (shouldShowAddToDictionaryForTypedWord) {
- // Due to the API limitation as of L, we cannot reliably retrieve the reverted text
- // when the separator causes line breaking. Until this API limitation is addressed in
- // the framework, show the indicator only when the separator doesn't contain
- // line-breaking characters.
- if (!StringUtils.hasLineBreakCharacter(separatorString)) {
- mTextDecorator.showAddToDictionaryIndicator(originallyTypedWordString,
- mConnection.getExpectedSelectionStart(),
- mConnection.getExpectedSelectionEnd());
- }
- mSuggestionStripViewAccessor.showAddToDictionaryHint(originallyTypedWordString);
- } else {
- // We have a separator between the word and the cursor: we should show predictions.
- inputTransaction.setRequiresUpdateSuggestions();
- }
+ // We have a separator between the word and the cursor: we should show predictions.
+ inputTransaction.setRequiresUpdateSuggestions();
}
/**
@@ -1720,26 +1702,25 @@ public final class InputLogic {
}
/**
- * Get information fo previous words from the nth previous word before the cursor as context
+ * Get n-gram context from the nth previous word before the cursor as context
* for the suggestion process.
* @param spacingAndPunctuations the current spacing and punctuations settings.
* @param nthPreviousWord reverse index of the word to get (1-indexed)
* @return the information of previous words
*/
- // TODO: Make this private
- public PrevWordsInfo getPrevWordsInfoFromNthPreviousWordForSuggestion(
+ public NgramContext getNgramContextFromNthPreviousWordForSuggestion(
final SpacingAndPunctuations spacingAndPunctuations, final int nthPreviousWord) {
if (spacingAndPunctuations.mCurrentLanguageHasSpaces) {
// If we are typing in a language with spaces we can just look up the previous
// word information from textview.
- return mConnection.getPrevWordsInfoFromNthPreviousWord(
+ return mConnection.getNgramContextFromNthPreviousWord(
spacingAndPunctuations, nthPreviousWord);
- } else {
- return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ?
- PrevWordsInfo.BEGINNING_OF_SENTENCE :
- new PrevWordsInfo(new PrevWordsInfo.WordInfo(
- mLastComposedWord.mCommittedWord.toString()));
}
+ if (LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord) {
+ return NgramContext.BEGINNING_OF_SENTENCE;
+ }
+ return new NgramContext(new NgramContext.WordInfo(
+ mLastComposedWord.mCommittedWord.toString()));
}
/**
@@ -1792,9 +1773,8 @@ public final class InputLogic {
// If no code point, #getCodePointBeforeCursor returns NOT_A_CODE_POINT.
if (Constants.CODE_PERIOD == codePointBeforeCursor) {
return text.substring(1);
- } else {
- return text;
}
+ return text;
}
/**
@@ -1845,21 +1825,21 @@ public final class InputLogic {
* 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.
+ * SuggestedWordInfo, com.android.inputmethod.latin.SuggestedWords)}.
+ * @param typedWordInfo The typed word as a SuggestedWordInfo.
* @param previousSuggestedWords The previously suggested words.
* @return Obsolete suggestions with the newly typed word.
*/
- private SuggestedWords retrieveOlderSuggestions(final String typedWord,
+ static SuggestedWords retrieveOlderSuggestions(final SuggestedWordInfo typedWordInfo,
final SuggestedWords previousSuggestedWords) {
- final SuggestedWords oldSuggestedWords =
- previousSuggestedWords.isPunctuationSuggestions() ? SuggestedWords.EMPTY
- : previousSuggestedWords;
+ final SuggestedWords oldSuggestedWords = previousSuggestedWords.isPunctuationSuggestions()
+ ? SuggestedWords.getEmptyInstance() : previousSuggestedWords;
final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
- SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords);
+ SuggestedWords.getTypedWordAndPreviousSuggestions(typedWordInfo, oldSuggestedWords);
return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */,
- false /* typedWordValid */, false /* hasAutoCorrectionCandidate */,
- true /* isObsoleteSuggestions */, oldSuggestedWords.mInputStyle);
+ typedWordInfo, false /* typedWordValid */, false /* hasAutoCorrectionCandidate */,
+ true /* isObsoleteSuggestions */, oldSuggestedWords.mInputStyle,
+ SuggestedWords.NOT_A_SEQUENCE_NUMBER);
}
/**
@@ -1936,14 +1916,14 @@ public final class InputLogic {
}
/**
- * Promote a phantom space to an actual space.
+ * Insert an automatic space, if the options allow it.
*
- * 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.
+ * This checks the options and the text before the cursor are appropriate before inserting
+ * an automatic space.
*
* @param settingsValues the current values of the settings.
*/
- private void promotePhantomSpace(final SettingsValues settingsValues) {
+ private void insertAutomaticSpaceIfOptionsAndTextAllow(final SettingsValues settingsValues) {
if (settingsValues.shouldInsertSpacesAutomatically()
&& settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
&& !mConnection.textBeforeCursorLooksLikeURL()) {
@@ -1957,36 +1937,17 @@ public final class InputLogic {
* @param suggestedWords suggestedWords to use.
*/
public void onUpdateTailBatchInputCompleted(final SettingsValues settingsValues,
- final SuggestedWords suggestedWords,
- // TODO: remove this argument
- final KeyboardSwitcher keyboardSwitcher) {
+ final SuggestedWords suggestedWords, 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);
- setComposingTextInternal(lastWord, 1);
- } else {
- mWordComposer.setBatchInputWord(batchInputText);
- setComposingTextInternal(batchInputText, 1);
+ insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues);
}
+ mWordComposer.setBatchInputWord(batchInputText);
+ setComposingTextInternal(batchInputText, 1);
mConnection.endBatchEdit();
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.PHANTOM;
@@ -2009,13 +1970,14 @@ public final class InputLogic {
* @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) {
+ final boolean isBatchMode = mWordComposer.isBatchMode();
commitChosenWord(settingsValues, typedWord,
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString);
+ StatsUtils.onWordCommitUserTyped(typedWord, isBatchMode);
}
}
@@ -2036,9 +1998,7 @@ public final class InputLogic {
* @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) {
+ final String separator, final LatinIME.UIHandler handler) {
// Complete any pending suggestions query first
if (handler.hasPendingUpdateSuggestions()) {
handler.cancelUpdateSuggestionStrip();
@@ -2052,18 +2012,19 @@ public final class InputLogic {
// INPUT_STYLE_TYPING.
performUpdateSuggestionStripSync(settingsValues, SuggestedWords.INPUT_STYLE_TYPING);
}
- final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
+ final SuggestedWordInfo autoCorrectionOrNull = mWordComposer.getAutoCorrectionOrNull();
final String typedWord = mWordComposer.getTypedWord();
- final String autoCorrection = (typedAutoCorrection != null)
- ? typedAutoCorrection : typedWord;
- if (autoCorrection != null) {
+ final String stringToCommit = (autoCorrectionOrNull != null)
+ ? autoCorrectionOrNull.mWord : typedWord;
+ if (stringToCommit != null) {
if (TextUtils.isEmpty(typedWord)) {
throw new RuntimeException("We have an auto-correction but the typed word "
+ "is empty? Impossible! I must commit suicide.");
}
- commitChosenWord(settingsValues, autoCorrection,
+ final boolean isBatchMode = mWordComposer.isBatchMode();
+ commitChosenWord(settingsValues, stringToCommit,
LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator);
- if (!typedWord.equals(autoCorrection)) {
+ if (!typedWord.equals(stringToCommit)) {
// 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
@@ -2071,8 +2032,16 @@ public final class InputLogic {
// 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));
+ mConnection.getExpectedSelectionEnd() - stringToCommit.length(),
+ typedWord, stringToCommit));
+ String prevWordsContext = (autoCorrectionOrNull != null)
+ ? autoCorrectionOrNull.mPrevWordsContext
+ : "";
+ StatsUtils.onAutoCorrection(typedWord, stringToCommit, isBatchMode,
+ mDictionaryFacilitator, prevWordsContext);
+ StatsUtils.onWordCommitAutoCorrect(stringToCommit, isBatchMode);
+ } else {
+ StatsUtils.onWordCommitUserTyped(stringToCommit, isBatchMode);
}
}
}
@@ -2091,20 +2060,20 @@ public final class InputLogic {
final CharSequence chosenWordWithSuggestions =
SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
suggestedWords);
- // When we are composing word, get previous words information from the 2nd previous word
- // because the 1st previous word is the word to be committed. Otherwise get previous words
- // information from the 1st previous word.
- final PrevWordsInfo prevWordsInfo = mConnection.getPrevWordsInfoFromNthPreviousWord(
+ // When we are composing word, get n-gram context from the 2nd previous word because the
+ // 1st previous word is the word to be committed. Otherwise get n-gram context from the 1st
+ // previous word.
+ final NgramContext ngramContext = mConnection.getNgramContextFromNthPreviousWord(
settingsValues.mSpacingAndPunctuations, mWordComposer.isComposingWord() ? 2 : 1);
mConnection.commitText(chosenWordWithSuggestions, 1);
// Add the word to the user history dictionary
- performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo);
+ performAdditionToUserHistoryDictionary(settingsValues, chosenWord, ngramContext);
// TODO: figure out here if this is an auto-correct or if the best word is actually
// what user typed. Note: currently this is done much later in
// LastComposedWord#didCommitTypedWord by string equality of the remembered
// strings.
mLastComposedWord = mWordComposer.commitWord(commitType,
- chosenWordWithSuggestions, separatorString, prevWordsInfo);
+ chosenWordWithSuggestions, separatorString, ngramContext);
}
/**
@@ -2118,11 +2087,8 @@ public final class InputLogic {
* @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 boolean tryResumeSuggestions,
- final int remainingTries,
- // TODO: remove these arguments
- final LatinIME.UIHandler handler) {
+ final int remainingTries, final LatinIME.UIHandler handler) {
final boolean shouldFinishComposition = mConnection.hasSelection()
|| !mConnection.isCursorPositionKnown();
if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(
@@ -2137,30 +2103,25 @@ public final class InputLogic {
}
mConnection.tryFixLyingCursorPosition();
if (tryResumeSuggestions) {
- // This is triggered when starting input anew, so we want to include the resumed
- // word in suggestions.
- handler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */,
- true /* shouldDelay */);
+ handler.postResumeSuggestions(true /* shouldDelay */);
}
return true;
}
public void getSuggestedWords(final SettingsValues settingsValues,
- final ProximityInfo proximityInfo, final int keyboardShiftMode, final int inputStyle,
+ final Keyboard keyboard, final int keyboardShiftMode, final int inputStyle,
final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
mWordComposer.adviseCapitalizedModeBeforeFetchingSuggestions(
getActualCapsMode(settingsValues, keyboardShiftMode));
mSuggest.getSuggestedWords(mWordComposer,
- getPrevWordsInfoFromNthPreviousWordForSuggestion(
+ getNgramContextFromNthPreviousWordForSuggestion(
settingsValues.mSpacingAndPunctuations,
// Get the word on which we should search the bigrams. If we are composing
// a word, it's whatever is *before* the half-committed word in the buffer,
// hence 2; if we aren't, we should just skip whitespace if any, so 1.
mWordComposer.isComposingWord() ? 2 : 1),
- proximityInfo,
- new SettingsValuesForSuggestion(settingsValues.mBlockPotentiallyOffensive,
- settingsValues.mPhraseGestureEnabled,
- settingsValues.mAdditionalFeaturesSettingValues),
+ keyboard,
+ new SettingsValuesForSuggestion(settingsValues.mBlockPotentiallyOffensive),
settingsValues.mAutoCorrectionEnabledPerUserSettings,
inputStyle, sequenceNumber, callback);
}
@@ -2171,7 +2132,7 @@ public final class InputLogic {
*
* <p>Currently using this method is optional and you can still directly call
* {@link RichInputConnection#setComposingText(CharSequence, int)}, but it is recommended to
- * use this method whenever possible to optimize the behavior of {@link TextDecorator}.<p>
+ * use this method whenever possible.<p>
* <p>TODO: Should we move this mechanism to {@link RichInputConnection}?</p>
*
* @param newComposingText the composing text to be set
@@ -2216,70 +2177,44 @@ public final class InputLogic {
mConnection.setComposingText(composingTextToBeSet, newCursorPosition);
}
- //////////////////////////////////////////////////////////////////////////////////////////////
- // Following methods are tentatively placed in this class for the integration with
- // TextDecorator.
- // TODO: Decouple things that are not related to the input logic.
- //////////////////////////////////////////////////////////////////////////////////////////////
-
- /**
- * Sets the UI operator for {@link TextDecorator}.
- * @param uiOperator the UI operator which should be associated with {@link TextDecorator}.
- */
- public void setTextDecoratorUi(final TextDecoratorUiOperator uiOperator) {
- mTextDecorator.setUiOperator(uiOperator);
- }
-
/**
- * Must be called from {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} is
- * called.
- * @param info The wrapper object with which we can access cursor/anchor info.
+ * Gets an object allowing private IME commands to be sent to the
+ * underlying editor.
+ * @return An object for sending private commands to the underlying editor.
*/
- public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
- mTextDecorator.onUpdateCursorAnchorInfo(info);
+ public PrivateCommandPerformer getPrivateCommandPerformer() {
+ return mConnection;
}
/**
- * Must be called when {@link InputMethodService#updateFullscreenMode} is called.
- * @param isFullscreen {@code true} if the input method is in full-screen mode.
- */
- public void onUpdateFullscreenMode(final boolean isFullscreen) {
- mTextDecorator.notifyFullScreenMode(isFullscreen);
- }
-
- /**
- * Must be called from {@link LatinIME#addWordToUserDictionary(String)}.
+ * Gets the expected index of the first char of the composing span within the editor's text.
+ * Returns a negative value in case there appears to be no valid composing span.
+ *
+ * @see #getComposingLength()
+ * @see RichInputConnection#hasSelection()
+ * @see RichInputConnection#isCursorPositionKnown()
+ * @see RichInputConnection#getExpectedSelectionStart()
+ * @see RichInputConnection#getExpectedSelectionEnd()
+ * @return The expected index in Java chars of the first char of the composing span.
*/
- public void onAddWordToUserDictionary() {
- mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
- mTextDecorator.reset();
+ // TODO: try and see if we can get rid of this method. Ideally the users of this class should
+ // never need to know this.
+ public int getComposingStart() {
+ if (!mConnection.isCursorPositionKnown() || mConnection.hasSelection()) {
+ return -1;
+ }
+ return mConnection.getExpectedSelectionStart() - mWordComposer.size();
}
/**
- * Returns whether the add to dictionary indicator should be shown or not.
- * @param lastComposedWord the last composed word information.
- * @param settingsValues the current settings value.
- * @return {@code true} if the commit indicator should be shown.
+ * Gets the expected length in Java chars of the composing span.
+ * May be 0 if there is no valid composing span.
+ * @see #getComposingStart()
+ * @return The expected length of the composing span.
*/
- private boolean shouldShowAddToDictionaryForTypedWord(final LastComposedWord lastComposedWord,
- final SettingsValues settingsValues) {
- if (!mConnection.isCursorAnchorInfoMonitorEnabled()) {
- // We cannot help in this case because we are heavily relying on this new API.
- return false;
- }
- if (!settingsValues.mShouldShowUiToAcceptTypedWord) {
- return false;
- }
- if (TextUtils.isEmpty(lastComposedWord.mTypedWord)) {
- return false;
- }
- if (TextUtils.equals(lastComposedWord.mTypedWord, lastComposedWord.mCommittedWord)) {
- return false;
- }
- if (!mDictionaryFacilitator.isUserDictionaryEnabled()) {
- return false;
- }
- return !mDictionaryFacilitator.isValidWord(lastComposedWord.mTypedWord,
- true /* ignoreCase */);
+ // TODO: try and see if we can get rid of this method. Ideally the users of this class should
+ // never need to know this.
+ public int getComposingLength() {
+ return mWordComposer.size();
}
}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
index c6f83d0b9..ddc4ad99c 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
@@ -21,11 +21,10 @@ 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;
+import com.android.inputmethod.latin.common.InputPointers;
/**
* A helper to manage deferred tasks for the input logic.
@@ -62,7 +61,7 @@ class InputLogicHandler implements Handler.Callback {
final OnGetSuggestedWordsCallback callback) {}
};
- private InputLogicHandler() {
+ InputLogicHandler() {
mNonUIThreadHandler = null;
mLatinIME = null;
mInputLogic = null;
@@ -134,30 +133,38 @@ class InputLogicHandler implements Handler.Callback {
return;
}
mInputLogic.mWordComposer.setBatchInputPointers(batchPointers);
+ final OnGetSuggestedWordsCallback callback = new OnGetSuggestedWordsCallback() {
+ @Override
+ public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
+ showGestureSuggestionsWithPreviewVisuals(suggestedWords, isTailBatchInput);
+ }
+ };
getSuggestedWords(isTailBatchInput ? SuggestedWords.INPUT_STYLE_TAIL_BATCH
- : SuggestedWords.INPUT_STYLE_UPDATE_BATCH, 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);
- }
- }
- });
+ : SuggestedWords.INPUT_STYLE_UPDATE_BATCH, sequenceNumber, callback);
+ }
+ }
+
+ void showGestureSuggestionsWithPreviewVisuals(final SuggestedWords suggestedWordsForBatchInput,
+ final boolean isTailBatchInput) {
+ final SuggestedWords suggestedWordsToShowSuggestions;
+ // We're now inside the callback. This always runs on the Non-UI thread,
+ // no matter what thread updateBatchInput was originally called on.
+ if (suggestedWordsForBatchInput.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.
+ suggestedWordsToShowSuggestions = mInputLogic.mSuggestedWords;
+ } else {
+ suggestedWordsToShowSuggestions = suggestedWordsForBatchInput;
+ }
+ mLatinIME.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWordsToShowSuggestions,
+ isTailBatchInput /* dismissGestureFloatingPreviewText */);
+ if (isTailBatchInput) {
+ mInBatchInput = false;
+ // The following call schedules onEndBatchInputInternal
+ // to be called on the UI thread.
+ mLatinIME.mHandler.showTailBatchInputResult(suggestedWordsToShowSuggestions);
}
}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java b/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java
new file mode 100644
index 000000000..42eaa9c82
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java
@@ -0,0 +1,40 @@
+/*
+ * 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.inputlogic;
+
+import android.os.Bundle;
+
+/**
+ * Provides an interface matching
+ * {@link android.view.inputmethod.InputConnection#performPrivateCommand(String,Bundle)}.
+ */
+public interface PrivateCommandPerformer {
+ /**
+ * API to send private commands from an input method to its connected
+ * editor. This can be used to provide domain-specific features that are
+ * only known between certain input methods and their clients.
+ *
+ * @param action Name of the command to be performed. This must be a scoped
+ * name, i.e. prefixed with a package name you own, so that
+ * different developers will not create conflicting commands.
+ * @param data Any data to include with the command.
+ * @return true if the command was sent (regardless of whether the
+ * associated editor understood it), false if the input connection is no
+ * longer valid.
+ */
+ boolean performPrivateCommand(String action, Bundle data);
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java
index df447fd75..4d253b0cb 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java
@@ -19,13 +19,24 @@ package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* Class representing dictionary header.
*/
public final class DictionaryHeader {
public final int mBodyOffset;
+ @Nonnull
public final DictionaryOptions mDictionaryOptions;
+ @Nonnull
public final FormatOptions mFormatOptions;
+ @Nonnull
+ public final String mLocaleString;
+ @Nonnull
+ public final String mVersionString;
+ @Nonnull
+ public final String mIdString;
// Note that these are corresponding definitions in native code in latinime::HeaderPolicy
// and latinime::HeaderReadWriteUtils.
@@ -38,49 +49,40 @@ public final class DictionaryHeader {
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 MAX_UNIGRAM_COUNT_KEY = "MAX_UNIGRAM_ENTRY_COUNT";
+ public static final String MAX_BIGRAM_COUNT_KEY = "MAX_BIGRAM_ENTRY_COUNT";
+ public static final String MAX_TRIGRAM_COUNT_KEY = "MAX_TRIGRAM_ENTRY_COUNT";
public static final String ATTRIBUTE_VALUE_TRUE = "1";
+ public static final String CODE_POINT_TABLE_KEY = "codePointTable";
- public DictionaryHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
- final FormatOptions formatOptions) throws UnsupportedFormatException {
+ public DictionaryHeader(final int headerSize,
+ @Nonnull final DictionaryOptions dictionaryOptions,
+ @Nonnull final FormatOptions formatOptions) throws UnsupportedFormatException {
mDictionaryOptions = dictionaryOptions;
mFormatOptions = formatOptions;
mBodyOffset = formatOptions.mVersion < FormatSpec.VERSION4 ? headerSize : 0;
- if (null == getLocaleString()) {
+ final String localeString = dictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_KEY);
+ if (null == localeString) {
throw new UnsupportedFormatException("Cannot create a FileHeader without a locale");
}
- if (null == getVersion()) {
+ final String versionString = dictionaryOptions.mAttributes.get(DICTIONARY_VERSION_KEY);
+ if (null == versionString) {
throw new UnsupportedFormatException(
"Cannot create a FileHeader without a version");
}
- if (null == getId()) {
+ final String idString = dictionaryOptions.mAttributes.get(DICTIONARY_ID_KEY);
+ if (null == idString) {
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);
+ mLocaleString = localeString;
+ mVersionString = versionString;
+ mIdString = idString;
}
// Helper method to get the description
+ @Nullable
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.
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index a2ae74b20..e422c4cd2 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -17,7 +17,7 @@
package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import java.util.Date;
import java.util.HashMap;
@@ -36,9 +36,7 @@ public final class FormatSpec {
* sion
*
* o |
- * p | not used 3 bits
- * t | each unigram and bigram entry has a time stamp?
- * i | 1 bit, 1 = yes, 0 = no : CONTAINS_TIMESTAMP_FLAG
+ * p | not used, 2 bytes.
* o |
* nflags
*
@@ -48,7 +46,7 @@ public final class FormatSpec {
* d |
* ersize
*
- * | attributes list
+ * attributes list
*
* attributes list is:
* <key> = | string of characters at the char format described below, with the terminator used
@@ -86,27 +84,16 @@ public final class FormatSpec {
*/
/* Node (FusionDictionary.PtNode) layout is as follows:
- * | is moved ? 2 bits, 11 = no : FLAG_IS_NOT_MOVED
- * | This must be the same as FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
- * | 01 = yes : FLAG_IS_MOVED
- * f | the new address is stored in the same place as the parent address
- * l | is deleted? 10 = yes : FLAG_IS_DELETED
+ * | CHILDREN_ADDRESS_TYPE 2 bits, 11 : FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
+ * | 10 : FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES
+ * f | 01 : FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE
+ * l | 00 : FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS
* a | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS
* g | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL
* s | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS
* | 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 | parent address, 3byte
- * r | 1 byte = bbbbbbbb match
- * e | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
- * n | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte
- * t | This address is relative to the head of the PtNode.
- * a | If the node doesn't have a parent, this field is set to 0.
- * d |
- * dress
+ * | is possibly offensive ? 1 bit, 1 = yes, 0 = no : FLAG_IS_POSSIBLY_OFFENSIVE
*
* c | IF FLAG_HAS_MULTIPLE_CHARS
* h | char, char, char, char n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers
@@ -121,15 +108,10 @@ public final class FormatSpec {
* q |
*
* c |
- * h | children address, 3 bytes
- * i | 1 byte = bbbbbbbb match
- * l | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
- * d | otherwise => (bbbbbbbb<<16) + (next byte << 8) + next byte
- * r | if this node doesn't have children, this field is set to 0.
- * e | (see BinaryDictEncoderUtils#writeVariableSignedAddress)
- * n | This address is relative to the position of this field.
- * a |
- * ddress
+ * h | children address, CHILDREN_ADDRESS_TYPE bytes
+ * i | This address is relative to the position of this field.
+ * l |
+ * drenaddress
*
* | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS
* | shortcut string list
@@ -179,31 +161,30 @@ public final class FormatSpec {
public static final int MAGIC_NUMBER = 0x9BC13AFE;
static final int NOT_A_VERSION_NUMBER = -1;
- static final int FIRST_VERSION_WITH_DYNAMIC_UPDATE = 3;
- static final int FIRST_VERSION_WITH_TERMINAL_ID = 4;
// These MUST have the same values as the relevant constants in format_utils.h.
- // From version 4 on, we use version * 100 + revision as a version number. That allows
+ // From version 2.01 on, we use version * 100 + revision as a version number. That allows
// us to change the format during development while having testing devices remove
// older files with each upgrade, while still having a readable versioning scheme.
// When we bump up the dictionary format version, we should update
// ExpandableDictionary.needsToMigrateDictionary() and
// ExpandableDictionary.matchesExpectedBinaryDictFormatVersionForThisType().
public static final int VERSION2 = 2;
- // Dictionary version used for testing.
- public static final int VERSION4_ONLY_FOR_TESTING = 399;
- public static final int VERSION401 = 401;
- public static final int VERSION4 = 402;
- public static final int VERSION4_DEV = 403;
- static final int MINIMUM_SUPPORTED_VERSION = VERSION2;
- static final int MAXIMUM_SUPPORTED_VERSION = VERSION4_DEV;
+ public static final int VERSION201 = 201;
+ public static final int VERSION202 = 202;
+ // format version for Fava Dictionaries.
+ public static final int VERSION_DELIGHT3 = 86736212;
+ public static final int VERSION402 = 402;
+ public static final int VERSION403 = 403;
+ public static final int VERSION4 = VERSION403;
+ public static final int MINIMUM_SUPPORTED_STATIC_VERSION = VERSION202;
+ public static final int MAXIMUM_SUPPORTED_STATIC_VERSION = VERSION_DELIGHT3;
+ static final int MINIMUM_SUPPORTED_DYNAMIC_VERSION = VERSION4;
+ static final int MAXIMUM_SUPPORTED_DYNAMIC_VERSION = VERSION403;
// TODO: Make this value adaptative to content data, store it in the header, and
// use it in the reading code.
- static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
-
- static final int PARENT_ADDRESS_SIZE = 3;
- static final int FORWARD_LINK_ADDRESS_SIZE = 3;
+ static final int MAX_WORD_LENGTH = DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH;
// These flags are used only in the static dictionary.
static final int MASK_CHILDREN_ADDRESS_TYPE = 0xC0;
@@ -218,14 +199,7 @@ public final class FormatSpec {
static final int FLAG_HAS_SHORTCUT_TARGETS = 0x08;
static final int FLAG_HAS_BIGRAMS = 0x04;
static final int FLAG_IS_NOT_A_WORD = 0x02;
- static final int FLAG_IS_BLACKLISTED = 0x01;
-
- // These flags are used only in the dynamic dictionary.
- static final int MASK_MOVE_AND_DELETE_FLAG = 0xC0;
- static final int FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE = 0x40;
- static final int FLAG_IS_MOVED = 0x00 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
- static final int FLAG_IS_NOT_MOVED = 0x80 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
- static final int FLAG_IS_DELETED = 0x80;
+ static final int FLAG_IS_POSSIBLY_OFFENSIVE = 0x01;
static final int FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT = 0x80;
static final int FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE = 0x40;
@@ -240,52 +214,12 @@ public final class FormatSpec {
static final int PTNODE_TERMINATOR_SIZE = 1;
static final int PTNODE_FLAGS_SIZE = 1;
static final int PTNODE_FREQUENCY_SIZE = 1;
- static final int PTNODE_TERMINAL_ID_SIZE = 4;
static final int PTNODE_MAX_ADDRESS_SIZE = 3;
static final int PTNODE_ATTRIBUTE_FLAGS_SIZE = 1;
static final int PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE = 3;
static final int PTNODE_SHORTCUT_LIST_SIZE_SIZE = 2;
- // These values are used only by version 4 or later. They MUST match the definitions in
- // ver4_dict_constants.cpp.
- static final String TRIE_FILE_EXTENSION = ".trie";
- public static final String HEADER_FILE_EXTENSION = ".header";
- static final String FREQ_FILE_EXTENSION = ".freq";
- // tat = Terminal Address Table
- static final String TERMINAL_ADDRESS_TABLE_FILE_EXTENSION = ".tat";
- static final String BIGRAM_FILE_EXTENSION = ".bigram";
- static final String SHORTCUT_FILE_EXTENSION = ".shortcut";
- static final String LOOKUP_TABLE_FILE_SUFFIX = "_lookup";
- static final String CONTENT_TABLE_FILE_SUFFIX = "_index";
- static final int FLAGS_IN_FREQ_FILE_SIZE = 1;
- static final int FREQUENCY_AND_FLAGS_SIZE = 2;
- static final int TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE = 3;
- static final int UNIGRAM_TIMESTAMP_SIZE = 4;
- static final int UNIGRAM_COUNTER_SIZE = 1;
- static final int UNIGRAM_LEVEL_SIZE = 1;
-
- // With the English main dictionary as of October 2013, the size of bigram address table is
- // is 345KB with the block size being 16.
- // This is 54% of that of full address table.
- static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 16;
- static final int BIGRAM_CONTENT_COUNT = 1;
- static final int BIGRAM_FREQ_CONTENT_INDEX = 0;
- static final String BIGRAM_FREQ_CONTENT_ID = "_freq";
- static final int BIGRAM_TIMESTAMP_SIZE = 4;
- static final int BIGRAM_COUNTER_SIZE = 1;
- static final int BIGRAM_LEVEL_SIZE = 1;
-
- static final int SHORTCUT_CONTENT_COUNT = 1;
- static final int SHORTCUT_CONTENT_INDEX = 0;
- // With the English main dictionary as of October 2013, the size of shortcut address table is
- // 26KB with the block size being 64.
- // This is only 4.4% of that of full address table.
- static final int SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE = 64;
- static final String SHORTCUT_CONTENT_ID = "_shortcut";
-
static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
- static final int NO_PARENT_ADDRESS = 0;
- static final int NO_FORWARD_LINK_ADDRESS = 0;
static final int INVALID_CHARACTER = -1;
static final int MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT = 0x7F; // 127
@@ -302,14 +236,13 @@ public final class FormatSpec {
// This option needs to be the same numeric value as the one in binary_format.h.
static final int NOT_VALID_WORD = -99;
- static final int SIGNED_CHILDREN_ADDRESS_SIZE = 3;
static final int UINT8_MAX = 0xFF;
static final int UINT16_MAX = 0xFFFF;
static final int UINT24_MAX = 0xFFFFFF;
- static final int SINT24_MAX = 0x7FFFFF;
static final int MSB8 = 0x80;
- static final int MSB24 = 0x800000;
+ static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
+ static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF;
/**
* Options about file format.
diff --git a/java/src/com/android/inputmethod/latin/makedict/NgramProperty.java b/java/src/com/android/inputmethod/latin/makedict/NgramProperty.java
new file mode 100644
index 000000000..b1d19dc3c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/NgramProperty.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.latin.makedict;
+
+import com.android.inputmethod.latin.NgramContext;
+
+public class NgramProperty {
+ public final WeightedString mTargetWord;
+ public final NgramContext mNgramContext;
+
+ public NgramProperty(final WeightedString targetWord, final NgramContext ngramContext) {
+ mTargetWord = targetWord;
+ mNgramContext = ngramContext;
+ }
+
+ @Override
+ public int hashCode() {
+ return mTargetWord.hashCode() ^ mNgramContext.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof NgramProperty)) return false;
+ final NgramProperty n = (NgramProperty)o;
+ return mTargetWord.equals(n.mTargetWord) && mNgramContext.equals(n.mNgramContext);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java b/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java
index 5fcbb6357..03c2ece1d 100644
--- a/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java
+++ b/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java
@@ -40,11 +40,8 @@ public final class ProbabilityInfo {
if (probabilityInfo2 == null) {
return probabilityInfo1;
}
- if (probabilityInfo1.mProbability > probabilityInfo2.mProbability) {
- return probabilityInfo1;
- } else {
- return probabilityInfo2;
- }
+ return (probabilityInfo1.mProbability > probabilityInfo2.mProbability) ? probabilityInfo1
+ : probabilityInfo2;
}
public ProbabilityInfo(final int probability) {
@@ -67,9 +64,8 @@ public final class ProbabilityInfo {
public int hashCode() {
if (hasHistoricalInfo()) {
return Arrays.hashCode(new Object[] { mProbability, mTimestamp, mLevel, mCount });
- } else {
- return Arrays.hashCode(new Object[] { mProbability });
}
+ return Arrays.hashCode(new Object[] { mProbability });
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
index cd78e2235..264e75710 100644
--- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
+++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
@@ -18,12 +18,17 @@ package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.NgramContext.WordInfo;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.utils.CombinedFormatUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
+import javax.annotation.Nullable;
+
/**
* Utility class for a word with a probability.
*
@@ -32,31 +37,35 @@ import java.util.Arrays;
public final class WordProperty implements Comparable<WordProperty> {
public final String mWord;
public final ProbabilityInfo mProbabilityInfo;
- public final ArrayList<WeightedString> mShortcutTargets;
- public final ArrayList<WeightedString> mBigrams;
+ public final ArrayList<NgramProperty> mNgrams;
// TODO: Support mIsBeginningOfSentence.
public final boolean mIsBeginningOfSentence;
public final boolean mIsNotAWord;
- public final boolean mIsBlacklistEntry;
- public final boolean mHasShortcuts;
- public final boolean mHasBigrams;
+ public final boolean mIsPossiblyOffensive;
+ public final boolean mHasNgrams;
private int mHashCode = 0;
+ // TODO: Support n-gram.
@UsedForTesting
public WordProperty(final String word, final ProbabilityInfo probabilityInfo,
- final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams,
- final boolean isNotAWord, final boolean isBlacklistEntry) {
+ @Nullable final ArrayList<WeightedString> bigrams,
+ final boolean isNotAWord, final boolean isPossiblyOffensive) {
mWord = word;
mProbabilityInfo = probabilityInfo;
- mShortcutTargets = shortcutTargets;
- mBigrams = bigrams;
+ if (null == bigrams) {
+ mNgrams = null;
+ } else {
+ mNgrams = new ArrayList<>();
+ final NgramContext ngramContext = new NgramContext(new WordInfo(mWord));
+ for (final WeightedString bigramTarget : bigrams) {
+ mNgrams.add(new NgramProperty(bigramTarget, ngramContext));
+ }
+ }
mIsBeginningOfSentence = false;
mIsNotAWord = isNotAWord;
- mIsBlacklistEntry = isBlacklistEntry;
- mHasBigrams = bigrams != null && !bigrams.isEmpty();
- mHasShortcuts = shortcutTargets != null && !shortcutTargets.isEmpty();
+ mIsPossiblyOffensive = isPossiblyOffensive;
+ mHasNgrams = bigrams != null && !bigrams.isEmpty();
}
private static ProbabilityInfo createProbabilityInfoFromArray(final int[] probabilityInfo) {
@@ -70,36 +79,54 @@ public final class WordProperty implements Comparable<WordProperty> {
// 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 boolean isPossiblyOffensive, final boolean hasBigram,
final boolean isBeginningOfSentence, final int[] probabilityInfo,
- final ArrayList<int[]> bigramTargets, final ArrayList<int[]> bigramProbabilityInfo,
- final ArrayList<int[]> shortcutTargets,
- final ArrayList<Integer> shortcutProbabilities) {
+ final ArrayList<int[][]> ngramPrevWordsArray,
+ final ArrayList<boolean[]> ngramPrevWordIsBeginningOfSentenceArray,
+ final ArrayList<int[]> ngramTargets, final ArrayList<int[]> ngramProbabilityInfo) {
mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo);
- mShortcutTargets = new ArrayList<>();
- mBigrams = new ArrayList<>();
+ final ArrayList<NgramProperty> ngrams = new ArrayList<>();
mIsBeginningOfSentence = isBeginningOfSentence;
mIsNotAWord = isNotAWord;
- mIsBlacklistEntry = isBlacklisted;
- mHasShortcuts = hasShortcuts;
- mHasBigrams = hasBigram;
-
- final int bigramTargetCount = bigramTargets.size();
- for (int i = 0; i < bigramTargetCount; i++) {
- final String bigramTargetString =
- StringUtils.getStringFromNullTerminatedCodePointArray(bigramTargets.get(i));
- mBigrams.add(new WeightedString(bigramTargetString,
- createProbabilityInfoFromArray(bigramProbabilityInfo.get(i))));
+ mIsPossiblyOffensive = isPossiblyOffensive;
+ mHasNgrams = hasBigram;
+
+ final int relatedNgramCount = ngramTargets.size();
+ for (int i = 0; i < relatedNgramCount; i++) {
+ final String ngramTargetString =
+ StringUtils.getStringFromNullTerminatedCodePointArray(ngramTargets.get(i));
+ final WeightedString ngramTarget = new WeightedString(ngramTargetString,
+ createProbabilityInfoFromArray(ngramProbabilityInfo.get(i)));
+ final int[][] prevWords = ngramPrevWordsArray.get(i);
+ final boolean[] isBeginningOfSentenceArray =
+ ngramPrevWordIsBeginningOfSentenceArray.get(i);
+ final WordInfo[] wordInfoArray = new WordInfo[prevWords.length];
+ for (int j = 0; j < prevWords.length; j++) {
+ wordInfoArray[j] = isBeginningOfSentenceArray[j]
+ ? WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO
+ : new WordInfo(StringUtils.getStringFromNullTerminatedCodePointArray(
+ prevWords[j]));
+ }
+ final NgramContext ngramContext = new NgramContext(wordInfoArray);
+ ngrams.add(new NgramProperty(ngramTarget, ngramContext));
}
+ mNgrams = ngrams.isEmpty() ? null : ngrams;
+ }
- 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)));
+ // TODO: Remove
+ @UsedForTesting
+ public ArrayList<WeightedString> getBigrams() {
+ if (null == mNgrams) {
+ return null;
}
+ final ArrayList<WeightedString> bigrams = new ArrayList<>();
+ for (final NgramProperty ngram : mNgrams) {
+ if (ngram.mNgramContext.getPrevWordCount() == 1) {
+ bigrams.add(ngram.mTargetWord);
+ }
+ }
+ return bigrams;
}
public int getProbability() {
@@ -110,10 +137,9 @@ public final class WordProperty implements Comparable<WordProperty> {
return Arrays.hashCode(new Object[] {
word.mWord,
word.mProbabilityInfo,
- word.mShortcutTargets.hashCode(),
- word.mBigrams.hashCode(),
+ word.mNgrams,
word.mIsNotAWord,
- word.mIsBlacklistEntry
+ word.mIsPossiblyOffensive
});
}
@@ -141,10 +167,18 @@ public final class WordProperty implements Comparable<WordProperty> {
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;
+ return mProbabilityInfo.equals(w.mProbabilityInfo)
+ && mWord.equals(w.mWord) && equals(mNgrams, w.mNgrams)
+ && mIsNotAWord == w.mIsNotAWord && mIsPossiblyOffensive == w.mIsPossiblyOffensive
+ && mHasNgrams == w.mHasNgrams;
+ }
+
+ // TDOO: Have a utility method like java.util.Objects.equals.
+ private static <T> boolean equals(final ArrayList<T> a, final ArrayList<T> b) {
+ if (null == a) {
+ return null == b;
+ }
+ return a.equals(b);
}
@Override
@@ -157,7 +191,7 @@ public final class WordProperty implements Comparable<WordProperty> {
@UsedForTesting
public boolean isValid() {
- return getProbability() != BinaryDictionary.NOT_A_PROBABILITY;
+ return getProbability() != Dictionary.NOT_A_PROBABILITY;
}
@Override
diff --git a/java/src/com/android/inputmethod/annotations/UsedForTesting.java b/java/src/com/android/inputmethod/latin/network/AuthException.java
index 2ada091e4..1bce4c156 100644
--- a/java/src/com/android/inputmethod/annotations/UsedForTesting.java
+++ b/java/src/com/android/inputmethod/latin/network/AuthException.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,11 +14,22 @@
* limitations under the License.
*/
-package com.android.inputmethod.annotations;
+package com.android.inputmethod.latin.network;
/**
- * Denotes that the class, method or field should not be eliminated by ProGuard,
- * so that unit tests can access it. (See proguard.flags)
+ * Authentication exception. When this exception is thrown, the client may
+ * try to refresh the authentication token and try again.
*/
-public @interface UsedForTesting {
-}
+public class AuthException extends Exception {
+ public AuthException() {
+ super();
+ }
+
+ public AuthException(Throwable throwable) {
+ super(throwable);
+ }
+
+ public AuthException(String detailMessage) {
+ super(detailMessage);
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java b/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java
new file mode 100644
index 000000000..079d07eac
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.network;
+
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * A client for executing HTTP requests synchronously.
+ * This must never be called from the main thread.
+ */
+public class BlockingHttpClient {
+ private static final boolean DEBUG = false;
+ private static final String TAG = BlockingHttpClient.class.getSimpleName();
+
+ private final HttpURLConnection mConnection;
+
+ /**
+ * Interface that handles processing the response for a request.
+ */
+ public interface ResponseProcessor<T> {
+ /**
+ * Called when the HTTP request finishes successfully.
+ * The {@link InputStream} is closed by the client after the method finishes,
+ * so any processing must be done in this method itself.
+ *
+ * @param response An input stream that can be used to read the HTTP response.
+ */
+ T onSuccess(InputStream response) throws IOException;
+ }
+
+ public BlockingHttpClient(HttpURLConnection connection) {
+ mConnection = connection;
+ }
+
+ /**
+ * Executes the request on the underlying {@link HttpURLConnection}.
+ *
+ * @param request The request payload, if any, or null.
+ * @param responseProcessor A processor for the HTTP response.
+ */
+ public <T> T execute(@Nullable byte[] request, @Nonnull ResponseProcessor<T> responseProcessor)
+ throws IOException, AuthException, HttpException {
+ if (DEBUG) {
+ Log.d(TAG, "execute: " + mConnection.getURL());
+ }
+ try {
+ if (request != null) {
+ if (DEBUG) {
+ Log.d(TAG, "request size: " + request.length);
+ }
+ OutputStream out = new BufferedOutputStream(mConnection.getOutputStream());
+ out.write(request);
+ out.flush();
+ out.close();
+ }
+
+ final int responseCode = mConnection.getResponseCode();
+ if (responseCode != HttpURLConnection.HTTP_OK) {
+ Log.w(TAG, "Response error: " + responseCode + ", Message: "
+ + mConnection.getResponseMessage());
+ if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ throw new AuthException(mConnection.getResponseMessage());
+ }
+ throw new HttpException(responseCode);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "request executed successfully");
+ }
+ return responseProcessor.onSuccess(mConnection.getInputStream());
+ } finally {
+ mConnection.disconnect();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/network/HttpException.java b/java/src/com/android/inputmethod/latin/network/HttpException.java
new file mode 100644
index 000000000..b9d8b6372
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/network/HttpException.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.network;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+/**
+ * The HttpException exception represents a XML/HTTP fault with a HTTP status code.
+ */
+public class HttpException extends Exception {
+
+ /**
+ * The HTTP status code.
+ */
+ private final int mStatusCode;
+
+ /**
+ * @param statusCode int HTTP status code.
+ */
+ public HttpException(int statusCode) {
+ super("Response Code: " + statusCode);
+ mStatusCode = statusCode;
+ }
+
+ /**
+ * @return the HTTP status code related to this exception.
+ */
+ @UsedForTesting
+ public int getHttpStatusCode() {
+ return mStatusCode;
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java b/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java
new file mode 100644
index 000000000..df54bf464
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.network;
+
+import android.text.TextUtils;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+/**
+ * Builder for {@link HttpURLConnection}s.
+ *
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+@UsedForTesting
+public class HttpUrlConnectionBuilder {
+ private static final int DEFAULT_TIMEOUT_MILLIS = 5 * 1000;
+
+ /**
+ * Request header key for authentication.
+ */
+ public static final String HTTP_HEADER_AUTHORIZATION = "Authorization";
+
+ /**
+ * Request header key for cache control.
+ */
+ public static final String KEY_CACHE_CONTROL = "Cache-Control";
+ /**
+ * Request header value for cache control indicating no caching.
+ * @see #KEY_CACHE_CONTROL
+ */
+ public static final String VALUE_NO_CACHE = "no-cache";
+
+ /**
+ * Indicates that the request is unidirectional - upload-only.
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public static final int MODE_UPLOAD_ONLY = 1;
+ /**
+ * Indicates that the request is unidirectional - download only.
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public static final int MODE_DOWNLOAD_ONLY = 2;
+ /**
+ * Indicates that the request is bi-directional.
+ * TODO: Remove @UsedForTesting after this is actually used.
+ */
+ @UsedForTesting
+ public static final int MODE_BI_DIRECTIONAL = 3;
+
+ private final HashMap<String, String> mHeaderMap = new HashMap<>();
+
+ private URL mUrl;
+ private int mConnectTimeoutMillis = DEFAULT_TIMEOUT_MILLIS;
+ private int mReadTimeoutMillis = DEFAULT_TIMEOUT_MILLIS;
+ private int mContentLength = -1;
+ private boolean mUseCache;
+ private int mMode;
+
+ /**
+ * Sets the URL that'll be used for the request.
+ * This *must* be set before calling {@link #build()}
+ *
+ * TODO: Remove @UsedForTesting after this method is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setUrl(String url) throws MalformedURLException {
+ if (TextUtils.isEmpty(url)) {
+ throw new IllegalArgumentException("URL must not be empty");
+ }
+ mUrl = new URL(url);
+ return this;
+ }
+
+ /**
+ * Sets the connect timeout. Defaults to {@value #DEFAULT_TIMEOUT_MILLIS} milliseconds.
+ *
+ * TODO: Remove @UsedForTesting after this method is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setConnectTimeout(int timeoutMillis) {
+ if (timeoutMillis < 0) {
+ throw new IllegalArgumentException("connect-timeout must be >= 0, but was "
+ + timeoutMillis);
+ }
+ mConnectTimeoutMillis = timeoutMillis;
+ return this;
+ }
+
+ /**
+ * Sets the read timeout. Defaults to {@value #DEFAULT_TIMEOUT_MILLIS} milliseconds.
+ *
+ * TODO: Remove @UsedForTesting after this method is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setReadTimeout(int timeoutMillis) {
+ if (timeoutMillis < 0) {
+ throw new IllegalArgumentException("read-timeout must be >= 0, but was "
+ + timeoutMillis);
+ }
+ mReadTimeoutMillis = timeoutMillis;
+ return this;
+ }
+
+ /**
+ * Adds an entry to the request header.
+ *
+ * TODO: Remove @UsedForTesting after this method is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder addHeader(String key, String value) {
+ mHeaderMap.put(key, value);
+ return this;
+ }
+
+ /**
+ * Sets an authentication token.
+ *
+ * TODO: Remove @UsedForTesting after this method is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setAuthToken(String value) {
+ mHeaderMap.put(HTTP_HEADER_AUTHORIZATION, value);
+ return this;
+ }
+
+ /**
+ * Sets the request to be executed such that the input is not buffered.
+ * This may be set when the request size is known beforehand.
+ *
+ * TODO: Remove @UsedForTesting after this method is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setFixedLengthForStreaming(int length) {
+ mContentLength = length;
+ return this;
+ }
+
+ /**
+ * Indicates if the request can use cached responses or not.
+ *
+ * TODO: Remove @UsedForTesting after this method is actually used.
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setUseCache(boolean useCache) {
+ mUseCache = useCache;
+ return this;
+ }
+
+ /**
+ * The request mode.
+ * Sets the request mode to be one of: upload-only, download-only or bidirectional.
+ *
+ * @see #MODE_UPLOAD_ONLY
+ * @see #MODE_DOWNLOAD_ONLY
+ * @see #MODE_BI_DIRECTIONAL
+ *
+ * TODO: Remove @UsedForTesting after this method is actually used
+ */
+ @UsedForTesting
+ public HttpUrlConnectionBuilder setMode(int mode) {
+ if (mode != MODE_UPLOAD_ONLY
+ && mode != MODE_DOWNLOAD_ONLY
+ && mode != MODE_BI_DIRECTIONAL) {
+ throw new IllegalArgumentException("Invalid mode specified:" + mode);
+ }
+ mMode = mode;
+ return this;
+ }
+
+ /**
+ * Builds the {@link HttpURLConnection} instance that can be used to execute the request.
+ *
+ * TODO: Remove @UsedForTesting after this method is actually used.
+ */
+ @UsedForTesting
+ public HttpURLConnection build() throws IOException {
+ if (mUrl == null) {
+ throw new IllegalArgumentException("A URL must be specified!");
+ }
+ final HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
+ connection.setConnectTimeout(mConnectTimeoutMillis);
+ connection.setReadTimeout(mReadTimeoutMillis);
+ connection.setUseCaches(mUseCache);
+ switch (mMode) {
+ case MODE_UPLOAD_ONLY:
+ connection.setDoInput(true);
+ connection.setDoOutput(false);
+ break;
+ case MODE_DOWNLOAD_ONLY:
+ connection.setDoInput(false);
+ connection.setDoOutput(true);
+ break;
+ case MODE_BI_DIRECTIONAL:
+ connection.setDoInput(true);
+ connection.setDoOutput(true);
+ break;
+ }
+ for (final Entry<String, String> entry : mHeaderMap.entrySet()) {
+ connection.addRequestProperty(entry.getKey(), entry.getValue());
+ }
+ if (mContentLength >= 0) {
+ connection.setFixedLengthStreamingMode(mContentLength);
+ }
+ return connection;
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java b/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java
deleted file mode 100644
index ac55b9333..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.personalization;
-
-import android.content.Context;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableBinaryDictionary;
-
-import java.io.File;
-import java.util.Locale;
-
-public class ContextualDictionary extends ExpandableBinaryDictionary {
- /* package */ static final String NAME = ContextualDictionary.class.getSimpleName();
-
- private ContextualDictionary(final Context context, final Locale locale,
- final File dictFile) {
- super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_CONTEXTUAL,
- dictFile);
- // Always reset the contents.
- clear();
- }
-
- @UsedForTesting
- public static ContextualDictionary getDictionary(final Context context, final Locale locale,
- final File dictFile, final String dictNamePrefix) {
- return new ContextualDictionary(context, locale, dictFile);
- }
-
- @Override
- public boolean isValidWord(final String word) {
- // Strings out of this dictionary should not be considered existing words.
- return false;
- }
-
- @Override
- protected void loadInitialContentsLocked() {
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
deleted file mode 100644
index 1ba7b366f..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ /dev/null
@@ -1,89 +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.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableBinaryDictionary;
-import com.android.inputmethod.latin.makedict.DictionaryHeader;
-
-import java.io.File;
-import java.util.Locale;
-import java.util.Map;
-
-/**
- * This class is a base class of a dictionary that supports decaying for the personalized language
- * model.
- */
-public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableBinaryDictionary {
- private static final boolean DBG_DUMP_ON_CLOSE = false;
-
- /** Any pair being typed or picked */
- public static final int FREQUENCY_FOR_TYPED = 2;
-
- 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;
-
- /** The locale for this dictionary. */
- public final Locale mLocale;
-
- 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;
- if (mLocale != null && mLocale.toString().length() > 1) {
- reloadDictionaryIfRequired();
- }
- }
-
- @Override
- public void close() {
- if (DBG_DUMP_ON_CLOSE) {
- dumpAllWordsForDebug();
- }
- // Flush pending writes.
- asyncFlushBinaryDictionary();
- super.close();
- }
-
- @Override
- protected Map<String, String> getHeaderAttributeMap() {
- final Map<String, String> attributeMap = super.getHeaderAttributeMap();
- 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 void loadInitialContentsLocked() {
- // No initial contents.
- }
-
- /* package */ void runGCIfRequired() {
- runGCIfRequired(false /* mindsBlockByGC */);
- }
-
- @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/DictionaryDecayBroadcastReciever.java b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
deleted file mode 100644
index 221bb9a8f..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
+++ /dev/null
@@ -1,67 +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.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Broadcast receiver for periodically updating decaying dictionaries.
- */
-public class DictionaryDecayBroadcastReciever extends BroadcastReceiver {
- /**
- * The root domain for the personalization.
- */
- private static final String PERSONALIZATION_DOMAIN =
- "com.android.inputmethod.latin.personalization";
-
- /**
- * The action of the intent to tell the time to decay dictionaries.
- */
- private static final String DICTIONARY_DECAY_INTENT_ACTION =
- PERSONALIZATION_DOMAIN + ".DICT_DECAY";
-
- /**
- * Interval to update for decaying dictionaries.
- */
- /* 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);
- final Intent updateIntent = new Intent(DICTIONARY_DECAY_INTENT_ACTION);
- updateIntent.setClass(context, DictionaryDecayBroadcastReciever.class);
- final long alarmTime = System.currentTimeMillis() + DICTIONARY_DECAY_INTERVAL;
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0 /* requestCode */,
- updateIntent, PendingIntent.FLAG_CANCEL_CURRENT);
- if (null != alarmManager) alarmManager.setInexactRepeating(AlarmManager.RTC,
- alarmTime, DICTIONARY_DECAY_INTERVAL, pendingIntent);
- }
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- final String action = intent.getAction();
- if (action.equals(DICTIONARY_DECAY_INTENT_ACTION)) {
- PersonalizationHelper.runGCOnAllOpenedUserHistoryDictionaries();
- PersonalizationHelper.runGCOnAllOpenedPersonalizationDictionaries();
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
deleted file mode 100644
index 9d72de8c5..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.personalization;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-public class PersonalizationDataChunk {
- public final boolean mInputByUser;
- public final List<String> mTokens;
- public final int mTimestampInSeconds;
- public final String mPackageName;
- public final Locale mlocale = null;
-
- public PersonalizationDataChunk(boolean inputByUser, final List<String> tokens,
- final int timestampInSeconds, final String packageName) {
- mInputByUser = inputByUser;
- mTokens = Collections.unmodifiableList(tokens);
- mTimestampInSeconds = timestampInSeconds;
- mPackageName = packageName;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
deleted file mode 100644
index f2ad22ac7..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
+++ /dev/null
@@ -1,41 +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.latin.Dictionary;
-
-import java.io.File;
-import java.util.Locale;
-
-public class PersonalizationDictionary extends DecayingExpandableBinaryDictionaryBase {
- /* package */ static final String NAME = PersonalizationDictionary.class.getSimpleName();
-
- // TODO: Make this constructor private
- /* package */ PersonalizationDictionary(final Context context, final Locale locale) {
- super(context, getDictName(NAME, locale, null /* dictFile */), locale,
- Dictionary.TYPE_PERSONALIZATION, null /* dictFile */);
- }
-
- @UsedForTesting
- public static PersonalizationDictionary getDictionary(final Context context,
- final Locale locale, final File dictFile, final String dictNamePrefix) {
- return PersonalizationHelper.getPersonalizationDictionary(context, locale);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 331f85e0e..298e46c0a 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -19,130 +19,76 @@ package com.android.inputmethod.latin.personalization;
import android.content.Context;
import android.util.Log;
-import com.android.inputmethod.latin.utils.FileUtils;
+import com.android.inputmethod.latin.common.FileUtils;
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;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Helps handle and manage personalized dictionaries such as {@link UserHistoryDictionary}.
+ */
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 = new ConcurrentHashMap<>();
- private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>>
- sLangPersonalizationDictCache = new ConcurrentHashMap<>();
+ @Nonnull
public static UserHistoryDictionary getUserHistoryDictionary(
- final Context context, final Locale locale) {
- final String localeStr = locale.toString();
+ final Context context, final Locale locale, @Nullable final String accountName) {
+ String lookupStr = locale.toString();
+ if (accountName != null) {
+ lookupStr += "." + accountName;
+ }
synchronized (sLangUserHistoryDictCache) {
- if (sLangUserHistoryDictCache.containsKey(localeStr)) {
+ if (sLangUserHistoryDictCache.containsKey(lookupStr)) {
final SoftReference<UserHistoryDictionary> ref =
- sLangUserHistoryDictCache.get(localeStr);
+ sLangUserHistoryDictCache.get(lookupStr);
final UserHistoryDictionary dict = ref == null ? null : ref.get();
if (dict != null) {
if (DEBUG) {
- Log.w(TAG, "Use cached UserHistoryDictionary for " + locale);
+ Log.d(TAG, "Use cached UserHistoryDictionary with lookup: " + lookupStr);
}
dict.reloadDictionaryIfRequired();
return dict;
}
}
- final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale);
- sLangUserHistoryDictCache.put(localeStr, new SoftReference<>(dict));
- return dict;
- }
- }
-
- private static int sCurrentTimestampForTesting = 0;
- public static void currentTimeChangedForTesting(final int currentTimestamp) {
- if (TimeUnit.MILLISECONDS.toSeconds(
- DictionaryDecayBroadcastReciever.DICTIONARY_DECAY_INTERVAL)
- < currentTimestamp - sCurrentTimestampForTesting) {
- runGCOnAllOpenedUserHistoryDictionaries();
- runGCOnAllOpenedPersonalizationDictionaries();
- }
- }
-
- public static void runGCOnAllOpenedUserHistoryDictionaries() {
- runGCOnAllDictionariesIfRequired(sLangUserHistoryDictCache);
- }
-
- 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 Locale locale) {
- final String localeStr = locale.toString();
- synchronized (sLangPersonalizationDictCache) {
- if (sLangPersonalizationDictCache.containsKey(localeStr)) {
- final SoftReference<PersonalizationDictionary> ref =
- sLangPersonalizationDictCache.get(localeStr);
- final PersonalizationDictionary dict = ref == null ? null : ref.get();
- if (dict != null) {
- if (DEBUG) {
- Log.w(TAG, "Use cached PersonalizationDictionary for " + locale);
- }
- return dict;
- }
- }
- final PersonalizationDictionary dict = new PersonalizationDictionary(context, locale);
- sLangPersonalizationDictCache.put(localeStr, new SoftReference<>(dict));
+ final UserHistoryDictionary dict = new UserHistoryDictionary(
+ context, locale, accountName);
+ sLangUserHistoryDictCache.put(lookupStr, new SoftReference<>(dict));
return dict;
}
}
- 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()) {
+ synchronized (sLangUserHistoryDictCache) {
+ for (final ConcurrentHashMap.Entry<String, SoftReference<UserHistoryDictionary>> entry
+ : sLangUserHistoryDictCache.entrySet()) {
if (entry.getValue() != null) {
- final DecayingExpandableBinaryDictionaryBase dict = entry.getValue().get();
+ final UserHistoryDictionary dict = entry.getValue().get();
if (dict != null) {
dict.clear();
}
}
}
- dictionaryMap.clear();
+ sLangUserHistoryDictCache.clear();
final File filesDir = context.getFilesDir();
if (filesDir == null) {
Log.e(TAG, "context.getFilesDir() returned null.");
return;
}
- if (!FileUtils.deleteFilteredFiles(filesDir, new DictFilter(dictNamePrefix))) {
- Log.e(TAG, "Cannot remove all existing dictionary files. filesDir: "
- + filesDir.getAbsolutePath() + ", dictNamePrefix: " + dictNamePrefix);
+ final boolean filesDeleted = FileUtils.deleteFilteredFiles(
+ filesDir, new DictFilter(UserHistoryDictionary.NAME));
+ if (!filesDeleted) {
+ Log.e(TAG, "Cannot remove dictionary files. filesDir: " + filesDir.getAbsolutePath()
+ + ", dictNamePrefix: " + UserHistoryDictionary.NAME);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 34d4d4ed7..cbf0829b5 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -17,72 +17,120 @@
package com.android.inputmethod.latin.personalization;
import android.content.Context;
-import android.text.TextUtils;
+import com.android.inputmethod.annotations.ExternallyReferenced;
import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.BinaryDictionary;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.ExpandableBinaryDictionary;
-import com.android.inputmethod.latin.PrevWordsInfo;
-import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
+import com.android.inputmethod.latin.define.ProductionFlags;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
import java.io.File;
import java.util.Locale;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
/**
- * 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.
+ * Locally gathers statistics 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 */ static final String NAME = UserHistoryDictionary.class.getSimpleName();
+public class UserHistoryDictionary extends ExpandableBinaryDictionary {
+ static final String NAME = UserHistoryDictionary.class.getSimpleName();
// TODO: Make this constructor private
- /* package */ UserHistoryDictionary(final Context context, final Locale locale) {
- super(context, getDictName(NAME, locale, null /* dictFile */), locale,
- Dictionary.TYPE_USER_HISTORY, null /* dictFile */);
+ UserHistoryDictionary(final Context context, final Locale locale,
+ @Nullable final String account) {
+ super(context, getUserHistoryDictName(NAME, locale, null /* dictFile */, account), locale, Dictionary.TYPE_USER_HISTORY, null);
+ if (mLocale != null && mLocale.toString().length() > 1) {
+ reloadDictionaryIfRequired();
+ }
}
+ /**
+ * @returns the name of the {@link UserHistoryDictionary}.
+ */
@UsedForTesting
+ static String getUserHistoryDictName(final String name, final Locale locale,
+ @Nullable final File dictFile, @Nullable final String account) {
+ if (!ProductionFlags.ENABLE_PER_ACCOUNT_USER_HISTORY_DICTIONARY) {
+ return getDictName(name, locale, dictFile);
+ }
+ return getUserHistoryDictNamePerAccount(name, locale, dictFile, account);
+ }
+
+ /**
+ * Uses the currently signed in account to determine the dictionary name.
+ */
+ private static String getUserHistoryDictNamePerAccount(final String name, final Locale locale,
+ @Nullable final File dictFile, @Nullable final String account) {
+ if (dictFile != null) {
+ return dictFile.getName();
+ }
+ String dictName = name + "." + locale.toString();
+ if (account != null) {
+ dictName += "." + account;
+ }
+ return dictName;
+ }
+
+ // Note: This method is called by {@link DictionaryFacilitator} using Java reflection.
+ @SuppressWarnings("unused")
+ @ExternallyReferenced
public static UserHistoryDictionary getDictionary(final Context context, final Locale locale,
- final File dictFile, final String dictNamePrefix) {
- return PersonalizationHelper.getUserHistoryDictionary(context, locale);
+ final File dictFile, final String dictNamePrefix, @Nullable final String account) {
+ return PersonalizationHelper.getUserHistoryDictionary(context, locale, account);
}
/**
* Add a word to the user history dictionary.
*
* @param userHistoryDictionary the user history dictionary
- * @param prevWordsInfo the information of previous words
+ * @param ngramContext the n-gram context
* @param word the word the user inputted
* @param isValid whether the word is valid or not
* @param timestamp the timestamp when the word has been inputted
- * @param distracterFilter the filter to check whether the word is a distracter
*/
public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
- final PrevWordsInfo prevWordsInfo, final String word, final boolean isValid,
- final int timestamp, final DistracterFilter distracterFilter) {
- final CharSequence prevWord = prevWordsInfo.mPrevWordsInfo[0].mWord;
- if (word.length() > Constants.DICTIONARY_MAX_WORD_LENGTH ||
- (prevWord != null && prevWord.length() > Constants.DICTIONARY_MAX_WORD_LENGTH)) {
- return;
- }
- final int frequency = isValid ?
- FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
- userHistoryDictionary.addUnigramEntryWithCheckingDistracter(word, frequency,
- null /* shortcutTarget */, 0 /* shortcutFreq */, false /* isNotAWord */,
- false /* isBlacklisted */, timestamp, distracterFilter);
- // Do not insert a word as a bigram of itself
- if (TextUtils.equals(word, prevWord)) {
+ @Nonnull final NgramContext ngramContext, final String word, final boolean isValid,
+ final int timestamp) {
+ if (word.length() > BinaryDictionary.DICTIONARY_MAX_WORD_LENGTH) {
return;
}
- if (null != prevWord) {
- if (prevWordsInfo.mPrevWordsInfo[0].mIsBeginningOfSentence) {
- // Beginning-of-Sentence n-gram entry is treated as a n-gram entry of invalid word.
- userHistoryDictionary.addNgramEntry(prevWordsInfo, word,
- FREQUENCY_FOR_WORDS_NOT_IN_DICTS, timestamp);
- } else {
- userHistoryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp);
- }
- }
+ userHistoryDictionary.updateEntriesForWord(ngramContext, word,
+ isValid, 1 /* count */, timestamp);
+ }
+
+ @Override
+ public void close() {
+ // Flush pending writes.
+ asyncFlushBinaryDictionary();
+ super.close();
+ }
+
+ @Override
+ protected Map<String, String> getHeaderAttributeMap() {
+ final Map<String, String> attributeMap = super.getHeaderAttributeMap();
+ 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 void loadInitialContentsLocked() {
+ // No initial contents.
+ }
+
+ @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/settings/AccountsSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
new file mode 100644
index 000000000..a2ae0ef4e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
@@ -0,0 +1,445 @@
+/*
+ * 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 static com.android.inputmethod.latin.settings.LocalSettingsConstants.PREF_ACCOUNT_NAME;
+import static com.android.inputmethod.latin.settings.LocalSettingsConstants.PREF_ENABLE_CLOUD_SYNC;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnShowListener;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.TwoStatePreference;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.accounts.AccountStateChangedListener;
+import com.android.inputmethod.latin.accounts.LoginAccountUtils;
+import com.android.inputmethod.latin.define.ProductionFlags;
+import com.android.inputmethod.latin.utils.ManagedProfileUtils;
+
+import javax.annotation.Nullable;
+
+/**
+ * "Accounts & Privacy" settings sub screen.
+ *
+ * This settings sub screen handles the following preferences:
+ * <li> Account selection/management for IME </li>
+ * <li> Sync preferences </li>
+ * <li> Privacy preferences </li>
+ */
+public final class AccountsSettingsFragment extends SubScreenFragment {
+ private static final String PREF_ENABLE_SYNC_NOW = "pref_enable_cloud_sync";
+ private static final String PREF_SYNC_NOW = "pref_sync_now";
+ private static final String PREF_CLEAR_SYNC_DATA = "pref_clear_sync_data";
+
+ static final String PREF_ACCCOUNT_SWITCHER = "account_switcher";
+
+ /**
+ * Onclick listener for sync now pref.
+ */
+ private final Preference.OnPreferenceClickListener mSyncNowListener =
+ new SyncNowListener();
+ /**
+ * Onclick listener for delete sync pref.
+ */
+ private final Preference.OnPreferenceClickListener mDeleteSyncDataListener =
+ new DeleteSyncDataListener();
+
+ /**
+ * Onclick listener for enable sync pref.
+ */
+ private final Preference.OnPreferenceClickListener mEnableSyncClickListener =
+ new EnableSyncClickListener();
+
+ /**
+ * Enable sync checkbox pref.
+ */
+ private TwoStatePreference mEnableSyncPreference;
+
+ /**
+ * Enable sync checkbox pref.
+ */
+ private Preference mSyncNowPreference;
+
+ /**
+ * Clear sync data pref.
+ */
+ private Preference mClearSyncDataPreference;
+
+ /**
+ * Account switcher preference.
+ */
+ private Preference mAccountSwitcher;
+
+
+ @Override
+ public void onCreate(final Bundle icicle) {
+ super.onCreate(icicle);
+ addPreferencesFromResource(R.xml.prefs_screen_accounts);
+
+ if (ProductionFlags.IS_METRICS_LOGGING_SUPPORTED) {
+ final Preference enableMetricsLogging =
+ findPreference(Settings.PREF_ENABLE_METRICS_LOGGING);
+ final Resources res = getResources();
+ if (enableMetricsLogging != null) {
+ final String enableMetricsLoggingTitle = res.getString(
+ R.string.enable_metrics_logging, getApplicationName());
+ enableMetricsLogging.setTitle(enableMetricsLoggingTitle);
+ }
+ } else {
+ removePreference(Settings.PREF_ENABLE_METRICS_LOGGING);
+ }
+
+ if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) {
+ removeSyncPreferences();
+ } else {
+ // Temporarily disable the preferences till we can
+ // check that we don't have a work profile.
+ disableSyncPreferences();
+ new ManagedProfileCheckerTask(this).execute();
+ }
+ }
+
+ /**
+ * Task to check work profile. If found, it removes the sync prefs. If not,
+ * it enables them.
+ */
+ private static class ManagedProfileCheckerTask extends AsyncTask<Void, Void, Void> {
+ private final AccountsSettingsFragment mFragment;
+
+ private ManagedProfileCheckerTask(final AccountsSettingsFragment fragment) {
+ mFragment = fragment;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ if (ManagedProfileUtils.getInstance().hasWorkProfile(mFragment.getActivity())) {
+ mFragment.removeSyncPreferences();
+ } else {
+ mFragment.refreshAccountAndDependentPreferences(
+ mFragment.getSignedInAccountName());
+ }
+ return null;
+ }
+ }
+
+ private void enableSyncPreferences() {
+ mAccountSwitcher = findPreference(PREF_ACCCOUNT_SWITCHER);
+ if (mAccountSwitcher == null) {
+ // Preference has been removed because the device has a managed profile.
+ return;
+ }
+ mAccountSwitcher.setEnabled(true);
+
+ mEnableSyncPreference = (TwoStatePreference) findPreference(PREF_ENABLE_SYNC_NOW);
+ mEnableSyncPreference.setEnabled(true);
+ mEnableSyncPreference.setOnPreferenceClickListener(mEnableSyncClickListener);
+
+ mSyncNowPreference = findPreference(PREF_SYNC_NOW);
+ mSyncNowPreference.setEnabled(true);
+ mSyncNowPreference.setOnPreferenceClickListener(mSyncNowListener);
+
+ mClearSyncDataPreference = findPreference(PREF_CLEAR_SYNC_DATA);
+ mSyncNowPreference.setEnabled(true);
+ mClearSyncDataPreference.setOnPreferenceClickListener(mDeleteSyncDataListener);
+ }
+
+ private void disableSyncPreferences() {
+ mAccountSwitcher = findPreference(PREF_ACCCOUNT_SWITCHER);
+ if (mAccountSwitcher == null) {
+ // Preference has been removed because the device has a managed profile.
+ return;
+ }
+ mAccountSwitcher.setEnabled(false);
+
+ mEnableSyncPreference = (TwoStatePreference) findPreference(PREF_ENABLE_SYNC_NOW);
+ mEnableSyncPreference.setEnabled(false);
+
+ mSyncNowPreference = findPreference(PREF_SYNC_NOW);
+ mSyncNowPreference.setEnabled(false);
+
+ mClearSyncDataPreference = findPreference(PREF_CLEAR_SYNC_DATA);
+ mSyncNowPreference.setEnabled(false);
+ }
+
+ private void removeSyncPreferences() {
+ removePreference(PREF_ACCCOUNT_SWITCHER);
+ removePreference(PREF_ENABLE_CLOUD_SYNC);
+ removePreference(PREF_SYNC_NOW);
+ removePreference(PREF_CLEAR_SYNC_DATA);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshAccountAndDependentPreferences(getSignedInAccountName());
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
+ if (TextUtils.equals(key, PREF_ACCOUNT_NAME)) {
+ refreshAccountAndDependentPreferences(prefs.getString(PREF_ACCOUNT_NAME, null));
+ } else if (TextUtils.equals(key, PREF_ENABLE_CLOUD_SYNC)) {
+ final boolean syncEnabled = prefs.getBoolean(PREF_ENABLE_CLOUD_SYNC, false);
+ mEnableSyncPreference = (TwoStatePreference) findPreference(PREF_ENABLE_SYNC_NOW);
+ if (syncEnabled) {
+ mEnableSyncPreference.setSummary(R.string.cloud_sync_summary);
+ } else {
+ mEnableSyncPreference.setSummary(R.string.cloud_sync_summary_disabled);
+ }
+ AccountStateChangedListener.onSyncPreferenceChanged(getSignedInAccountName(),
+ syncEnabled);
+ }
+ }
+
+ /**
+ * Summarizes what account is being used and turns off dependent preferences if no account
+ * is currently selected.
+ */
+ private void refreshAccountAndDependentPreferences(@Nullable final String currentAccount) {
+ // TODO(cvnguyen): Write tests.
+ if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) {
+ return;
+ }
+
+ final String[] accountsForLogin =
+ LoginAccountUtils.getAccountsForLogin(getActivity());
+
+ if (accountsForLogin.length > 0) {
+ enableSyncPreferences();
+ if (mAccountSwitcher == null) {
+ return;
+ }
+ mAccountSwitcher.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(final Preference preference) {
+ if (accountsForLogin.length > 0) {
+ // TODO: Add addition of account.
+ createAccountPicker(accountsForLogin, currentAccount,
+ new AccountChangedListener(null)).show();
+ }
+ return true;
+ }
+ });
+ } else {
+ mAccountSwitcher.setEnabled(false);
+ disableSyncPreferences();
+ mEnableSyncPreference.setSummary(getString(R.string.add_account_to_enable_sync));
+ }
+
+ if (currentAccount == null) {
+ // No account is currently selected; switch enable sync preference off.
+ mAccountSwitcher.setSummary(getString(R.string.no_accounts_selected));
+ mEnableSyncPreference.setChecked(false);
+ } else {
+ // Set the currently selected account as the summary text.
+ mAccountSwitcher.setSummary(getString(R.string.account_selected, currentAccount));
+ }
+ }
+
+ @Nullable
+ String getSignedInAccountName() {
+ return getSharedPreferences().getString(LocalSettingsConstants.PREF_ACCOUNT_NAME, null);
+ }
+
+ boolean isSyncEnabled() {
+ return getSharedPreferences().getBoolean(PREF_ENABLE_CLOUD_SYNC, false);
+ }
+
+ /**
+ * Creates an account picker dialog showing the given accounts in a list and selecting
+ * the selected account by default. The list of accounts must not be null/empty.
+ *
+ * Package-private for testing.
+ *
+ * @param accounts list of accounts on the device.
+ * @param selectedAccount currently selected account
+ * @param positiveButtonClickListener listener that gets called when positive button is
+ * clicked
+ */
+ @UsedForTesting
+ AlertDialog createAccountPicker(final String[] accounts,
+ final String selectedAccount,
+ final DialogInterface.OnClickListener positiveButtonClickListener) {
+ if (accounts == null || accounts.length == 0) {
+ throw new IllegalArgumentException("List of accounts must not be empty");
+ }
+
+ // See if the currently selected account is in the list.
+ // If it is, the entry is selected, and a sign-out button is provided.
+ // If it isn't, select the 0th account by default which will get picked up
+ // if the user presses OK.
+ int index = 0;
+ boolean isSignedIn = false;
+ for (int i = 0; i < accounts.length; i++) {
+ if (TextUtils.equals(accounts[i], selectedAccount)) {
+ index = i;
+ isSignedIn = true;
+ break;
+ }
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.account_select_title)
+ .setSingleChoiceItems(accounts, index, null)
+ .setPositiveButton(R.string.account_select_ok, positiveButtonClickListener)
+ .setNegativeButton(R.string.account_select_cancel, null);
+ if (isSignedIn) {
+ builder.setNeutralButton(R.string.account_select_sign_out, positiveButtonClickListener);
+ }
+ return builder.create();
+ }
+
+ /**
+ * Listener for a account selection changes from the picker.
+ * Persists/removes the account to/from shared preferences and sets up sync if required.
+ */
+ class AccountChangedListener implements DialogInterface.OnClickListener {
+ /**
+ * Represents preference that should be changed based on account chosen.
+ */
+ private TwoStatePreference mDependentPreference;
+
+ AccountChangedListener(final TwoStatePreference dependentPreference) {
+ mDependentPreference = dependentPreference;
+ }
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ final String oldAccount = getSignedInAccountName();
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE: // Signed in
+ final ListView lv = ((AlertDialog)dialog).getListView();
+ final String newAccount =
+ (String) lv.getItemAtPosition(lv.getCheckedItemPosition());
+ getSharedPreferences()
+ .edit()
+ .putString(PREF_ACCOUNT_NAME, newAccount)
+ .apply();
+ AccountStateChangedListener.onAccountSignedIn(oldAccount, newAccount);
+ if (mDependentPreference != null) {
+ mDependentPreference.setChecked(true);
+ }
+ break;
+ case DialogInterface.BUTTON_NEUTRAL: // Signed out
+ AccountStateChangedListener.onAccountSignedOut(oldAccount);
+ getSharedPreferences()
+ .edit()
+ .remove(PREF_ACCOUNT_NAME)
+ .apply();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Listener that initiates the process of sync in the background.
+ */
+ class SyncNowListener implements Preference.OnPreferenceClickListener {
+ @Override
+ public boolean onPreferenceClick(final Preference preference) {
+ AccountStateChangedListener.forceSync(getSignedInAccountName());
+ return true;
+ }
+ }
+
+ /**
+ * Listener that initiates the process of deleting user's data from the cloud.
+ */
+ class DeleteSyncDataListener implements Preference.OnPreferenceClickListener {
+ @Override
+ public boolean onPreferenceClick(final Preference preference) {
+ final AlertDialog confirmationDialog = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.clear_sync_data_title)
+ .setMessage(R.string.clear_sync_data_confirmation)
+ .setPositiveButton(R.string.clear_sync_data_ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ AccountStateChangedListener.forceDelete(
+ getSignedInAccountName());
+ }
+ }
+ })
+ .setNegativeButton(R.string.cloud_sync_cancel, null /* OnClickListener */)
+ .create();
+ confirmationDialog.show();
+ return true;
+ }
+ }
+
+ /**
+ * Listens to events when user clicks on "Enable sync" feature.
+ */
+ class EnableSyncClickListener implements OnShowListener, Preference.OnPreferenceClickListener {
+ // TODO(cvnguyen): Write tests.
+ @Override
+ public boolean onPreferenceClick(final Preference preference) {
+ final TwoStatePreference syncPreference = (TwoStatePreference) preference;
+ if (syncPreference.isChecked()) {
+ // Uncheck for now.
+ syncPreference.setChecked(false);
+
+ // Show opt-in.
+ final AlertDialog optInDialog = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.cloud_sync_title)
+ .setMessage(R.string.cloud_sync_opt_in_text)
+ .setPositiveButton(R.string.account_select_ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog,
+ final int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ final Context context = getActivity();
+ final String[] accountsForLogin =
+ LoginAccountUtils.getAccountsForLogin(context);
+ createAccountPicker(accountsForLogin,
+ getSignedInAccountName(),
+ new AccountChangedListener(syncPreference))
+ .show();
+ }
+ }
+ })
+ .setNegativeButton(R.string.cloud_sync_cancel, null)
+ .create();
+ optInDialog.setOnShowListener(this);
+ optInDialog.show();
+ }
+ return true;
+ }
+
+ @Override
+ public void onShow(DialogInterface dialog) {
+ TextView messageView = (TextView) ((AlertDialog) dialog).findViewById(
+ android.R.id.message);
+ if (messageView != null) {
+ messageView.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
index 00f2c73dd..f2e1aed4c 100644
--- a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
@@ -23,12 +23,10 @@ import android.media.AudioManager;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
-import android.preference.TwoStatePreference;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.define.ProductionFlags;
-import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager;
/**
* "Advanced" settings sub screen.
@@ -89,26 +87,9 @@ public final class AdvancedSettingsFragment extends SubScreenFragment {
Settings.readKeyPreviewPopupEnabled(prefs, res));
}
- if (!res.getBoolean(R.bool.config_setup_wizard_available)) {
- removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
- }
-
- if (ProductionFlags.IS_METRICS_LOGGING_SUPPORTED) {
- final Preference enableMetricsLogging =
- findPreference(Settings.PREF_ENABLE_METRICS_LOGGING);
- if (enableMetricsLogging != null) {
- final int applicationLabelRes = context.getApplicationInfo().labelRes;
- final String applicationName = res.getString(applicationLabelRes);
- final String enableMetricsLoggingTitle = res.getString(
- R.string.enable_metrics_logging, applicationName);
- enableMetricsLogging.setTitle(enableMetricsLoggingTitle);
- }
- } else {
- removePreference(Settings.PREF_ENABLE_METRICS_LOGGING);
- }
-
setupKeypressVibrationDurationSettings();
setupKeypressSoundVolumeSettings();
+ setupKeyLongpressTimeoutSettings();
refreshEnablingsOfKeypressSoundAndVibrationSettings();
}
@@ -116,11 +97,6 @@ public final class AdvancedSettingsFragment extends SubScreenFragment {
public void onResume() {
super.onResume();
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
- final TwoStatePreference showSetupWizardIcon =
- (TwoStatePreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
- if (showSetupWizardIcon != null) {
- showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, getActivity()));
- }
updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
}
@@ -130,8 +106,6 @@ public final class AdvancedSettingsFragment extends SubScreenFragment {
if (key.equals(Settings.PREF_POPUP_ON)) {
setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
Settings.readKeyPreviewPopupEnabled(prefs, res));
- } else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
- LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
}
updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
refreshEnablingsOfKeypressSoundAndVibrationSettings();
@@ -245,4 +219,43 @@ public final class AdvancedSettingsFragment extends SubScreenFragment {
}
});
}
+
+ private void setupKeyLongpressTimeoutSettings() {
+ final SharedPreferences prefs = getSharedPreferences();
+ final Resources res = getResources();
+ final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
+ Settings.PREF_KEY_LONGPRESS_TIMEOUT);
+ if (pref == null) {
+ return;
+ }
+ pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
+ @Override
+ public void writeValue(final int value, final String key) {
+ prefs.edit().putInt(key, value).apply();
+ }
+
+ @Override
+ public void writeDefaultValue(final String key) {
+ prefs.edit().remove(key).apply();
+ }
+
+ @Override
+ public int readValue(final String key) {
+ return Settings.readKeyLongpressTimeout(prefs, res);
+ }
+
+ @Override
+ public int readDefaultValue(final String key) {
+ return Settings.readDefaultKeyLongpressTimeout(res);
+ }
+
+ @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/AppearanceSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
index f5e4d33a2..554edc85c 100644
--- a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
@@ -19,7 +19,8 @@ package com.android.inputmethod.latin.settings;
import android.os.Bundle;
import com.android.inputmethod.latin.R;
-
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.define.ProductionFlags;
/**
* "Appearance" settings sub screen.
@@ -29,6 +30,10 @@ public final class AppearanceSettingsFragment extends SubScreenFragment {
public void onCreate(final Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.prefs_screen_appearance);
+ if (!ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED ||
+ Constants.isPhone(Settings.readScreenMetrics(getResources()))) {
+ removePreference(Settings.PREF_ENABLE_SPLIT_KEYBOARD);
+ }
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java
index ec29a7eb2..62834cd2a 100644
--- a/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java
@@ -24,8 +24,8 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.Bundle;
-import android.preference.ListPreference;
import android.preference.Preference;
+import android.preference.TwoStatePreference;
import com.android.inputmethod.dictionarypack.DictionarySettingsActivity;
import com.android.inputmethod.latin.R;
@@ -49,7 +49,7 @@ import java.util.TreeSet;
*/
public final class CorrectionSettingsFragment extends SubScreenFragment {
private static final boolean DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS = false;
- private static final boolean USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS =
+ private static final boolean USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS =
DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
|| Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2;
@@ -61,8 +61,6 @@ public final class CorrectionSettingsFragment extends SubScreenFragment {
final Context context = getActivity();
final PackageManager pm = context.getPackageManager();
- ensureConsistencyOfAutoCorrectionSettings();
-
final Preference dictionaryLink = findPreference(Settings.PREF_CONFIGURE_DICTIONARIES_KEY);
final Intent intent = dictionaryLink.getIntent();
intent.setClassName(context.getPackageName(), DictionarySettingsActivity.class.getName());
@@ -74,7 +72,7 @@ public final class CorrectionSettingsFragment extends SubScreenFragment {
final Preference editPersonalDictionary =
findPreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY);
final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent();
- final ResolveInfo ri = USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS ? null
+ final ResolveInfo ri = USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS ? null
: pm.resolveActivity(
editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (ri == null) {
@@ -82,21 +80,6 @@ public final class CorrectionSettingsFragment extends SubScreenFragment {
}
}
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
- ensureConsistencyOfAutoCorrectionSettings();
- }
-
- private void ensureConsistencyOfAutoCorrectionSettings() {
- final String autoCorrectionOff = getString(
- R.string.auto_correction_threshold_mode_index_off);
- 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 overwriteUserDictionaryPreference(final Preference userDictionaryPreference) {
final Activity activity = getActivity();
final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity);
diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java
new file mode 100644
index 000000000..21ea8f859
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.settings;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.preference.DialogPreference;
+import android.preference.Preference;
+import android.util.Log;
+import android.view.View;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
+import com.android.inputmethod.compat.ViewCompatUtils;
+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.SubtypeLocaleUtils;
+
+import java.util.TreeSet;
+
+final class CustomInputStylePreference extends DialogPreference
+ implements DialogInterface.OnCancelListener {
+ private static final boolean DEBUG_SUBTYPE_ID = false;
+
+ interface Listener {
+ public void onRemoveCustomInputStyle(CustomInputStylePreference stylePref);
+ public void onSaveCustomInputStyle(CustomInputStylePreference stylePref);
+ public void onAddCustomInputStyle(CustomInputStylePreference stylePref);
+ public SubtypeLocaleAdapter getSubtypeLocaleAdapter();
+ public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter();
+ }
+
+ private static final String KEY_PREFIX = "subtype_pref_";
+ private static final String KEY_NEW_SUBTYPE = KEY_PREFIX + "new";
+
+ private InputMethodSubtype mSubtype;
+ private InputMethodSubtype mPreviousSubtype;
+
+ private final Listener mProxy;
+ private Spinner mSubtypeLocaleSpinner;
+ private Spinner mKeyboardLayoutSetSpinner;
+
+ public static CustomInputStylePreference newIncompleteSubtypePreference(
+ final Context context, final Listener proxy) {
+ return new CustomInputStylePreference(context, null, proxy);
+ }
+
+ public CustomInputStylePreference(final Context context, final InputMethodSubtype subtype,
+ final Listener proxy) {
+ super(context, null);
+ setDialogLayoutResource(R.layout.additional_subtype_dialog);
+ setPersistent(false);
+ mProxy = proxy;
+ setSubtype(subtype);
+ }
+
+ public void show() {
+ showDialog(null);
+ }
+
+ public final boolean isIncomplete() {
+ return mSubtype == null;
+ }
+
+ public InputMethodSubtype getSubtype() {
+ return mSubtype;
+ }
+
+ public void setSubtype(final InputMethodSubtype subtype) {
+ mPreviousSubtype = mSubtype;
+ mSubtype = subtype;
+ if (isIncomplete()) {
+ setTitle(null);
+ setDialogTitle(R.string.add_style);
+ setKey(KEY_NEW_SUBTYPE);
+ } else {
+ final String displayName =
+ SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype);
+ setTitle(displayName);
+ setDialogTitle(displayName);
+ setKey(KEY_PREFIX + subtype.getLocale() + "_"
+ + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype));
+ }
+ }
+
+ public void revert() {
+ setSubtype(mPreviousSubtype);
+ }
+
+ public boolean hasBeenModified() {
+ return mSubtype != null && !mSubtype.equals(mPreviousSubtype);
+ }
+
+ @Override
+ protected View onCreateDialogView() {
+ final View v = super.onCreateDialogView();
+ mSubtypeLocaleSpinner = (Spinner) v.findViewById(R.id.subtype_locale_spinner);
+ mSubtypeLocaleSpinner.setAdapter(mProxy.getSubtypeLocaleAdapter());
+ mKeyboardLayoutSetSpinner = (Spinner) v.findViewById(R.id.keyboard_layout_set_spinner);
+ mKeyboardLayoutSetSpinner.setAdapter(mProxy.getKeyboardLayoutSetAdapter());
+ // All keyboard layout names are in the Latin script and thus left to right. That means
+ // the view would align them to the left even if the system locale is RTL, but that
+ // would look strange. To fix this, we align them to the view's start, which will be
+ // natural for any direction.
+ ViewCompatUtils.setTextAlignment(
+ mKeyboardLayoutSetSpinner, ViewCompatUtils.TEXT_ALIGNMENT_VIEW_START);
+ return v;
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) {
+ builder.setCancelable(true).setOnCancelListener(this);
+ if (isIncomplete()) {
+ builder.setPositiveButton(R.string.add, this)
+ .setNegativeButton(android.R.string.cancel, this);
+ } else {
+ builder.setPositiveButton(R.string.save, this)
+ .setNeutralButton(android.R.string.cancel, this)
+ .setNegativeButton(R.string.remove, this);
+ final SubtypeLocaleItem localeItem = new SubtypeLocaleItem(mSubtype);
+ final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(mSubtype);
+ setSpinnerPosition(mSubtypeLocaleSpinner, localeItem);
+ setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem);
+ }
+ }
+
+ private static void setSpinnerPosition(final Spinner spinner, final Object itemToSelect) {
+ final SpinnerAdapter adapter = spinner.getAdapter();
+ final int count = adapter.getCount();
+ for (int i = 0; i < count; i++) {
+ final Object item = spinner.getItemAtPosition(i);
+ if (item.equals(itemToSelect)) {
+ spinner.setSelection(i);
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void onCancel(final DialogInterface dialog) {
+ if (isIncomplete()) {
+ mProxy.onRemoveCustomInputStyle(this);
+ }
+ }
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ super.onClick(dialog, which);
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ final boolean isEditing = !isIncomplete();
+ final SubtypeLocaleItem locale =
+ (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem();
+ final KeyboardLayoutSetItem layout =
+ (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem();
+ final InputMethodSubtype subtype =
+ AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
+ locale.mLocaleString, layout.mLayoutName);
+ setSubtype(subtype);
+ notifyChanged();
+ if (isEditing) {
+ mProxy.onSaveCustomInputStyle(this);
+ } else {
+ mProxy.onAddCustomInputStyle(this);
+ }
+ break;
+ case DialogInterface.BUTTON_NEUTRAL:
+ // Nothing to do
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ mProxy.onRemoveCustomInputStyle(this);
+ break;
+ }
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ final Dialog dialog = getDialog();
+ if (dialog == null || !dialog.isShowing()) {
+ return superState;
+ }
+
+ final SavedState myState = new SavedState(superState);
+ myState.mSubtype = mSubtype;
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ final SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ setSubtype(myState.mSubtype);
+ }
+
+ static final class SavedState extends Preference.BaseSavedState {
+ InputMethodSubtype mSubtype;
+
+ public SavedState(final Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeParcelable(mSubtype, 0);
+ }
+
+ public SavedState(final Parcel source) {
+ super(source);
+ mSubtype = (InputMethodSubtype)source.readParcelable(null);
+ }
+
+ @SuppressWarnings("hiding")
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(final Parcel source) {
+ return new SavedState(source);
+ }
+
+ @Override
+ public SavedState[] newArray(final int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ static final class SubtypeLocaleItem implements Comparable<SubtypeLocaleItem> {
+ public final String mLocaleString;
+ private final String mDisplayName;
+
+ public SubtypeLocaleItem(final InputMethodSubtype subtype) {
+ mLocaleString = subtype.getLocale();
+ mDisplayName = SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale(
+ mLocaleString);
+ }
+
+ // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()}
+ // to get display name.
+ @Override
+ public String toString() {
+ return mDisplayName;
+ }
+
+ @Override
+ public int compareTo(final SubtypeLocaleItem o) {
+ return mLocaleString.compareTo(o.mLocaleString);
+ }
+ }
+
+ static final class SubtypeLocaleAdapter extends ArrayAdapter<SubtypeLocaleItem> {
+ private static final String TAG_SUBTYPE = SubtypeLocaleAdapter.class.getSimpleName();
+
+ public SubtypeLocaleAdapter(final Context context) {
+ super(context, android.R.layout.simple_spinner_item);
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+
+ final TreeSet<SubtypeLocaleItem> items = new TreeSet<>();
+ final InputMethodInfo imi = RichInputMethodManager.getInstance()
+ .getInputMethodInfoOfThisIme();
+ final int count = imi.getSubtypeCount();
+ for (int i = 0; i < count; i++) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ if (DEBUG_SUBTYPE_ID) {
+ Log.d(TAG_SUBTYPE, String.format("%-6s 0x%08x %11d %s",
+ subtype.getLocale(), subtype.hashCode(), subtype.hashCode(),
+ SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)));
+ }
+ if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) {
+ items.add(new SubtypeLocaleItem(subtype));
+ }
+ }
+ // TODO: Should filter out already existing combinations of locale and layout.
+ addAll(items);
+ }
+ }
+
+ static final class KeyboardLayoutSetItem {
+ public final String mLayoutName;
+ private final String mDisplayName;
+
+ public KeyboardLayoutSetItem(final InputMethodSubtype subtype) {
+ mLayoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
+ mDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
+ }
+
+ // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()}
+ // to get display name.
+ @Override
+ public String toString() {
+ return mDisplayName;
+ }
+ }
+
+ static final class KeyboardLayoutSetAdapter extends ArrayAdapter<KeyboardLayoutSetItem> {
+ public KeyboardLayoutSetAdapter(final Context context) {
+ super(context, android.R.layout.simple_spinner_item);
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+
+ final String[] predefinedKeyboardLayoutSet = context.getResources().getStringArray(
+ R.array.predefined_layouts);
+ // TODO: Should filter out already existing combinations of locale and layout.
+ for (final String layout : predefinedKeyboardLayoutSet) {
+ // This is a dummy subtype with NO_LANGUAGE, only for display.
+ final InputMethodSubtype subtype =
+ AdditionalSubtypeUtils.createDummyAdditionalSubtype(
+ SubtypeLocaleUtils.NO_LANGUAGE, layout);
+ add(new KeyboardLayoutSetItem(subtype));
+ }
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
index 9bc398654..46fcc7106 100644
--- a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
@@ -17,37 +17,27 @@
package com.android.inputmethod.latin.settings;
import android.app.AlertDialog;
-import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.preference.DialogPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.support.v4.view.ViewCompat;
import android.text.TextUtils;
-import android.util.Pair;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
-import android.widget.ArrayAdapter;
-import android.widget.Spinner;
-import android.widget.SpinnerAdapter;
import android.widget.Toast;
-import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
-import com.android.inputmethod.compat.ViewCompatUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
@@ -56,13 +46,18 @@ import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.ArrayList;
-import java.util.TreeSet;
-public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
+public final class CustomInputStyleSettingsFragment extends PreferenceFragment
+ implements CustomInputStylePreference.Listener {
+ private static final String TAG = CustomInputStyleSettingsFragment.class.getSimpleName();
+ // Note: We would like to turn this debug flag true in order to see what input styles are
+ // defined in a bug-report.
+ private static final boolean DEBUG_CUSTOM_INPUT_STYLES = true;
+
private RichInputMethodManager mRichImm;
private SharedPreferences mPrefs;
- private SubtypeLocaleAdapter mSubtypeLocaleAdapter;
- private KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter;
+ private CustomInputStylePreference.SubtypeLocaleAdapter mSubtypeLocaleAdapter;
+ private CustomInputStylePreference.KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter;
private boolean mIsAddingNewSubtype;
private AlertDialog mSubtypeEnablerNotificationDialog;
@@ -73,326 +68,6 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
"is_subtype_enabler_notification_dialog_open";
private static final String KEY_SUBTYPE_FOR_SUBTYPE_ENABLER = "subtype_for_subtype_enabler";
- static final class SubtypeLocaleItem extends Pair<String, String>
- implements Comparable<SubtypeLocaleItem> {
- public SubtypeLocaleItem(final String localeString, final String displayName) {
- super(localeString, displayName);
- }
-
- public SubtypeLocaleItem(final String localeString) {
- this(localeString,
- SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale(localeString));
- }
-
- @Override
- public String toString() {
- return second;
- }
-
- @Override
- public int compareTo(final SubtypeLocaleItem o) {
- return first.compareTo(o.first);
- }
- }
-
- static final class SubtypeLocaleAdapter extends ArrayAdapter<SubtypeLocaleItem> {
- private static final String TAG = SubtypeLocaleAdapter.class.getSimpleName();
- private static final boolean DEBUG_SUBTYPE_ID = false;
-
- public SubtypeLocaleAdapter(final Context context) {
- super(context, android.R.layout.simple_spinner_item);
- setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-
- final TreeSet<SubtypeLocaleItem> items = new TreeSet<>();
- final InputMethodInfo imi = RichInputMethodManager.getInstance()
- .getInputMethodInfoOfThisIme();
- final int count = imi.getSubtypeCount();
- for (int i = 0; i < count; i++) {
- final InputMethodSubtype subtype = imi.getSubtypeAt(i);
- if (DEBUG_SUBTYPE_ID) {
- android.util.Log.d(TAG, String.format("%-6s 0x%08x %11d %s",
- subtype.getLocale(), subtype.hashCode(), subtype.hashCode(),
- SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)));
- }
- if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) {
- items.add(createItem(context, subtype.getLocale()));
- }
- }
- // TODO: Should filter out already existing combinations of locale and layout.
- addAll(items);
- }
-
- public static SubtypeLocaleItem createItem(final Context context,
- final String localeString) {
- if (localeString.equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
- final String displayName = context.getString(R.string.subtype_no_language);
- return new SubtypeLocaleItem(localeString, displayName);
- }
- return new SubtypeLocaleItem(localeString);
- }
- }
-
- static final class KeyboardLayoutSetItem extends Pair<String, String> {
- public KeyboardLayoutSetItem(final InputMethodSubtype subtype) {
- super(SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype),
- SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype));
- }
-
- @Override
- public String toString() {
- return second;
- }
- }
-
- static final class KeyboardLayoutSetAdapter extends ArrayAdapter<KeyboardLayoutSetItem> {
- public KeyboardLayoutSetAdapter(final Context context) {
- super(context, android.R.layout.simple_spinner_item);
- setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-
- // TODO: Should filter out already existing combinations of locale and layout.
- for (final String layout : SubtypeLocaleUtils.getPredefinedKeyboardLayoutSet()) {
- // This is a dummy subtype with NO_LANGUAGE, only for display.
- final InputMethodSubtype subtype =
- AdditionalSubtypeUtils.createDummyAdditionalSubtype(
- SubtypeLocaleUtils.NO_LANGUAGE, layout);
- add(new KeyboardLayoutSetItem(subtype));
- }
- }
- }
-
- private interface SubtypeDialogProxy {
- public void onRemovePressed(SubtypePreference subtypePref);
- public void onSavePressed(SubtypePreference subtypePref);
- public void onAddPressed(SubtypePreference subtypePref);
- public SubtypeLocaleAdapter getSubtypeLocaleAdapter();
- public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter();
- }
-
- static final class SubtypePreference extends DialogPreference
- implements DialogInterface.OnCancelListener {
- private static final String KEY_PREFIX = "subtype_pref_";
- private static final String KEY_NEW_SUBTYPE = KEY_PREFIX + "new";
-
- private InputMethodSubtype mSubtype;
- private InputMethodSubtype mPreviousSubtype;
-
- private final SubtypeDialogProxy mProxy;
- private Spinner mSubtypeLocaleSpinner;
- private Spinner mKeyboardLayoutSetSpinner;
-
- public static SubtypePreference newIncompleteSubtypePreference(final Context context,
- final SubtypeDialogProxy proxy) {
- return new SubtypePreference(context, null, proxy);
- }
-
- public SubtypePreference(final Context context, final InputMethodSubtype subtype,
- final SubtypeDialogProxy proxy) {
- super(context, null);
- setDialogLayoutResource(R.layout.additional_subtype_dialog);
- setPersistent(false);
- mProxy = proxy;
- setSubtype(subtype);
- }
-
- public void show() {
- showDialog(null);
- }
-
- public final boolean isIncomplete() {
- return mSubtype == null;
- }
-
- public InputMethodSubtype getSubtype() {
- return mSubtype;
- }
-
- public void setSubtype(final InputMethodSubtype subtype) {
- mPreviousSubtype = mSubtype;
- mSubtype = subtype;
- if (isIncomplete()) {
- setTitle(null);
- setDialogTitle(R.string.add_style);
- setKey(KEY_NEW_SUBTYPE);
- } else {
- final String displayName =
- SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype);
- setTitle(displayName);
- setDialogTitle(displayName);
- setKey(KEY_PREFIX + subtype.getLocale() + "_"
- + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype));
- }
- }
-
- public void revert() {
- setSubtype(mPreviousSubtype);
- }
-
- public boolean hasBeenModified() {
- return mSubtype != null && !mSubtype.equals(mPreviousSubtype);
- }
-
- @Override
- protected View onCreateDialogView() {
- final View v = super.onCreateDialogView();
- mSubtypeLocaleSpinner = (Spinner) v.findViewById(R.id.subtype_locale_spinner);
- mSubtypeLocaleSpinner.setAdapter(mProxy.getSubtypeLocaleAdapter());
- mKeyboardLayoutSetSpinner = (Spinner) v.findViewById(R.id.keyboard_layout_set_spinner);
- mKeyboardLayoutSetSpinner.setAdapter(mProxy.getKeyboardLayoutSetAdapter());
- // All keyboard layout names are in the Latin script and thus left to right. That means
- // the view would align them to the left even if the system locale is RTL, but that
- // would look strange. To fix this, we align them to the view's start, which will be
- // natural for any direction.
- ViewCompatUtils.setTextAlignment(
- mKeyboardLayoutSetSpinner, ViewCompatUtils.TEXT_ALIGNMENT_VIEW_START);
- return v;
- }
-
- @Override
- protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) {
- final Context context = builder.getContext();
- builder.setCancelable(true).setOnCancelListener(this);
- if (isIncomplete()) {
- builder.setPositiveButton(R.string.add, this)
- .setNegativeButton(android.R.string.cancel, this);
- } else {
- builder.setPositiveButton(R.string.save, this)
- .setNeutralButton(android.R.string.cancel, this)
- .setNegativeButton(R.string.remove, this);
- final SubtypeLocaleItem localeItem = SubtypeLocaleAdapter.createItem(
- context, mSubtype.getLocale());
- final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(mSubtype);
- setSpinnerPosition(mSubtypeLocaleSpinner, localeItem);
- setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem);
- }
- }
-
- private static void setSpinnerPosition(final Spinner spinner, final Object itemToSelect) {
- final SpinnerAdapter adapter = spinner.getAdapter();
- final int count = adapter.getCount();
- for (int i = 0; i < count; i++) {
- final Object item = spinner.getItemAtPosition(i);
- if (item.equals(itemToSelect)) {
- spinner.setSelection(i);
- return;
- }
- }
- }
-
- @Override
- public void onCancel(final DialogInterface dialog) {
- if (isIncomplete()) {
- mProxy.onRemovePressed(this);
- }
- }
-
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- super.onClick(dialog, which);
- switch (which) {
- case DialogInterface.BUTTON_POSITIVE:
- final boolean isEditing = !isIncomplete();
- final SubtypeLocaleItem locale =
- (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem();
- final KeyboardLayoutSetItem layout =
- (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem();
- final InputMethodSubtype subtype =
- AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
- locale.first, layout.first);
- setSubtype(subtype);
- notifyChanged();
- if (isEditing) {
- mProxy.onSavePressed(this);
- } else {
- mProxy.onAddPressed(this);
- }
- break;
- case DialogInterface.BUTTON_NEUTRAL:
- // Nothing to do
- break;
- case DialogInterface.BUTTON_NEGATIVE:
- mProxy.onRemovePressed(this);
- break;
- }
- }
-
- private static int getSpinnerPosition(final Spinner spinner) {
- if (spinner == null) return -1;
- return spinner.getSelectedItemPosition();
- }
-
- private static void setSpinnerPosition(final Spinner spinner, final int position) {
- if (spinner == null || position < 0) return;
- spinner.setSelection(position);
- }
-
- @Override
- protected Parcelable onSaveInstanceState() {
- final Parcelable superState = super.onSaveInstanceState();
- final Dialog dialog = getDialog();
- if (dialog == null || !dialog.isShowing()) {
- return superState;
- }
-
- final SavedState myState = new SavedState(superState);
- myState.mSubtype = mSubtype;
- myState.mSubtypeLocaleSelectedPos = getSpinnerPosition(mSubtypeLocaleSpinner);
- myState.mKeyboardLayoutSetSelectedPos = getSpinnerPosition(mKeyboardLayoutSetSpinner);
- return myState;
- }
-
- @Override
- protected void onRestoreInstanceState(final Parcelable state) {
- if (!(state instanceof SavedState)) {
- super.onRestoreInstanceState(state);
- return;
- }
-
- final SavedState myState = (SavedState) state;
- super.onRestoreInstanceState(myState.getSuperState());
- setSpinnerPosition(mSubtypeLocaleSpinner, myState.mSubtypeLocaleSelectedPos);
- setSpinnerPosition(mKeyboardLayoutSetSpinner, myState.mKeyboardLayoutSetSelectedPos);
- setSubtype(myState.mSubtype);
- }
-
- static final class SavedState extends Preference.BaseSavedState {
- InputMethodSubtype mSubtype;
- int mSubtypeLocaleSelectedPos;
- int mKeyboardLayoutSetSelectedPos;
-
- public SavedState(final Parcelable superState) {
- super(superState);
- }
-
- @Override
- public void writeToParcel(final Parcel dest, final int flags) {
- super.writeToParcel(dest, flags);
- dest.writeInt(mSubtypeLocaleSelectedPos);
- dest.writeInt(mKeyboardLayoutSetSelectedPos);
- dest.writeParcelable(mSubtype, 0);
- }
-
- public SavedState(final Parcel source) {
- super(source);
- mSubtypeLocaleSelectedPos = source.readInt();
- mKeyboardLayoutSetSelectedPos = source.readInt();
- mSubtype = (InputMethodSubtype)source.readParcelable(null);
- }
-
- public static final Parcelable.Creator<SavedState> CREATOR =
- new Parcelable.Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(final Parcel source) {
- return new SavedState(source);
- }
-
- @Override
- public SavedState[] newArray(final int size) {
- return new SavedState[size];
- }
- };
- }
- }
-
public CustomInputStyleSettingsFragment() {
// Empty constructor for fragment generation.
}
@@ -440,18 +115,22 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
final Context context = getActivity();
- mSubtypeLocaleAdapter = new SubtypeLocaleAdapter(context);
- mKeyboardLayoutSetAdapter = new KeyboardLayoutSetAdapter(context);
+ mSubtypeLocaleAdapter = new CustomInputStylePreference.SubtypeLocaleAdapter(context);
+ mKeyboardLayoutSetAdapter =
+ new CustomInputStylePreference.KeyboardLayoutSetAdapter(context);
final String prefSubtypes =
Settings.readPrefAdditionalSubtypes(mPrefs, getResources());
+ if (DEBUG_CUSTOM_INPUT_STYLES) {
+ Log.i(TAG, "Load custom input styles: " + prefSubtypes);
+ }
setPrefSubtypes(prefSubtypes, context);
mIsAddingNewSubtype = (savedInstanceState != null)
&& savedInstanceState.containsKey(KEY_IS_ADDING_NEW_SUBTYPE);
if (mIsAddingNewSubtype) {
getPreferenceScreen().addPreference(
- SubtypePreference.newIncompleteSubtypePreference(context, mSubtypeProxy));
+ CustomInputStylePreference.newIncompleteSubtypePreference(context, this));
}
super.onActivityCreated(savedInstanceState);
@@ -460,8 +139,6 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
KEY_IS_SUBTYPE_ENABLER_NOTIFICATION_DIALOG_OPEN)) {
mSubtypePreferenceKeyForSubtypeEnabler = savedInstanceState.getString(
KEY_SUBTYPE_FOR_SUBTYPE_ENABLER);
- final SubtypePreference subtypePref = (SubtypePreference)findPreference(
- mSubtypePreferenceKeyForSubtypeEnabler);
mSubtypeEnablerNotificationDialog = createDialog();
mSubtypeEnablerNotificationDialog.show();
}
@@ -481,62 +158,60 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
}
}
- private final SubtypeDialogProxy mSubtypeProxy = new SubtypeDialogProxy() {
- @Override
- public void onRemovePressed(final SubtypePreference subtypePref) {
- mIsAddingNewSubtype = false;
- final PreferenceGroup group = getPreferenceScreen();
- group.removePreference(subtypePref);
+ @Override
+ public void onRemoveCustomInputStyle(final CustomInputStylePreference stylePref) {
+ mIsAddingNewSubtype = false;
+ final PreferenceGroup group = getPreferenceScreen();
+ group.removePreference(stylePref);
+ mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
+ }
+
+ @Override
+ public void onSaveCustomInputStyle(final CustomInputStylePreference stylePref) {
+ final InputMethodSubtype subtype = stylePref.getSubtype();
+ if (!stylePref.hasBeenModified()) {
+ return;
+ }
+ if (findDuplicatedSubtype(subtype) == null) {
mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
+ return;
}
- @Override
- public void onSavePressed(final SubtypePreference subtypePref) {
- final InputMethodSubtype subtype = subtypePref.getSubtype();
- if (!subtypePref.hasBeenModified()) {
- return;
- }
- if (findDuplicatedSubtype(subtype) == null) {
- mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
- return;
- }
+ // Saved subtype is duplicated.
+ final PreferenceGroup group = getPreferenceScreen();
+ group.removePreference(stylePref);
+ stylePref.revert();
+ group.addPreference(stylePref);
+ showSubtypeAlreadyExistsToast(subtype);
+ }
- // Saved subtype is duplicated.
- final PreferenceGroup group = getPreferenceScreen();
- group.removePreference(subtypePref);
- subtypePref.revert();
- group.addPreference(subtypePref);
- showSubtypeAlreadyExistsToast(subtype);
+ @Override
+ public void onAddCustomInputStyle(final CustomInputStylePreference stylePref) {
+ mIsAddingNewSubtype = false;
+ final InputMethodSubtype subtype = stylePref.getSubtype();
+ if (findDuplicatedSubtype(subtype) == null) {
+ mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
+ mSubtypePreferenceKeyForSubtypeEnabler = stylePref.getKey();
+ mSubtypeEnablerNotificationDialog = createDialog();
+ mSubtypeEnablerNotificationDialog.show();
+ return;
}
- @Override
- public void onAddPressed(final SubtypePreference subtypePref) {
- mIsAddingNewSubtype = false;
- final InputMethodSubtype subtype = subtypePref.getSubtype();
- if (findDuplicatedSubtype(subtype) == null) {
- mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
- mSubtypePreferenceKeyForSubtypeEnabler = subtypePref.getKey();
- mSubtypeEnablerNotificationDialog = createDialog();
- mSubtypeEnablerNotificationDialog.show();
- return;
- }
-
- // Newly added subtype is duplicated.
- final PreferenceGroup group = getPreferenceScreen();
- group.removePreference(subtypePref);
- showSubtypeAlreadyExistsToast(subtype);
- }
+ // Newly added subtype is duplicated.
+ final PreferenceGroup group = getPreferenceScreen();
+ group.removePreference(stylePref);
+ showSubtypeAlreadyExistsToast(subtype);
+ }
- @Override
- public SubtypeLocaleAdapter getSubtypeLocaleAdapter() {
- return mSubtypeLocaleAdapter;
- }
+ @Override
+ public CustomInputStylePreference.SubtypeLocaleAdapter getSubtypeLocaleAdapter() {
+ return mSubtypeLocaleAdapter;
+ }
- @Override
- public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter() {
- return mKeyboardLayoutSetAdapter;
- }
- };
+ @Override
+ public CustomInputStylePreference.KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter() {
+ return mKeyboardLayoutSetAdapter;
+ }
private void showSubtypeAlreadyExistsToast(final InputMethodSubtype subtype) {
final Context context = getActivity();
@@ -554,6 +229,7 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
}
private AlertDialog createDialog() {
+ final String imeId = mRichImm.getInputMethodIdOfThisIme();
final AlertDialog.Builder builder = new AlertDialog.Builder(
DialogUtils.getPlatformDialogThemeContext(getActivity()));
builder.setTitle(R.string.custom_input_styles_title)
@@ -563,7 +239,7 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
@Override
public void onClick(DialogInterface dialog, int which) {
final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
- mRichImm.getInputMethodIdOfThisIme(),
+ imeId,
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -583,8 +259,8 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
final InputMethodSubtype[] subtypesArray =
AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtypes);
for (final InputMethodSubtype subtype : subtypesArray) {
- final SubtypePreference pref = new SubtypePreference(
- context, subtype, mSubtypeProxy);
+ final CustomInputStylePreference pref =
+ new CustomInputStylePreference(context, subtype, this);
group.addPreference(pref);
}
}
@@ -595,8 +271,8 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
final int count = group.getPreferenceCount();
for (int i = 0; i < count; i++) {
final Preference pref = group.getPreference(i);
- if (pref instanceof SubtypePreference) {
- final SubtypePreference subtypePref = (SubtypePreference)pref;
+ if (pref instanceof CustomInputStylePreference) {
+ final CustomInputStylePreference subtypePref = (CustomInputStylePreference)pref;
// We should not save newly adding subtype to preference because it is incomplete.
if (subtypePref.isIncomplete()) continue;
subtypes.add(subtypePref.getSubtype());
@@ -611,6 +287,9 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
final String oldSubtypes = Settings.readPrefAdditionalSubtypes(mPrefs, getResources());
final InputMethodSubtype[] subtypes = getSubtypes();
final String prefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(subtypes);
+ if (DEBUG_CUSTOM_INPUT_STYLES) {
+ Log.i(TAG, "Save custom input styles: " + prefSubtypes);
+ }
if (prefSubtypes.equals(oldSubtypes)) {
return;
}
@@ -627,8 +306,8 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
public boolean onOptionsItemSelected(final MenuItem item) {
final int itemId = item.getItemId();
if (itemId == R.id.action_add_style) {
- final SubtypePreference newSubtype =
- SubtypePreference.newIncompleteSubtypePreference(getActivity(), mSubtypeProxy);
+ final CustomInputStylePreference newSubtype =
+ CustomInputStylePreference.newIncompleteSubtypePreference(getActivity(), this);
getPreferenceScreen().addPreference(newSubtype);
newSubtype.show();
mIsAddingNewSubtype = true;
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index 48f4c758c..6fffb8e9d 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -16,29 +16,36 @@
package com.android.inputmethod.latin.settings;
+/**
+ * Debug settings for the application.
+ *
+ * Note: Even though these settings are stored in the default shared preferences file,
+ * they shouldn't be restored across devices.
+ * If a new key is added here, it should also be blacklisted for restore in
+ * {@link LocalSettingsConstants}.
+ */
public final class DebugSettings {
public static final String PREF_DEBUG_MODE = "debug_mode";
public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch";
- public static final String PREF_FORCE_PHYSICAL_KEYBOARD_SPECIAL_KEY =
- "force_physical_keyboard_special_key";
- public static final String PREF_SHOW_UI_TO_ACCEPT_TYPED_WORD =
- "pref_show_ui_to_accept_typed_word";
public static final String PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS =
"pref_has_custom_key_preview_animation_params";
- public static final String PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE =
- "pref_key_preview_show_up_start_x_scale";
- public static final String PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE =
- "pref_key_preview_show_up_start_y_scale";
+ public static final String PREF_RESIZE_KEYBOARD = "pref_resize_keyboard";
+ public static final String PREF_KEYBOARD_HEIGHT_SCALE = "pref_keyboard_height_scale";
+ public static final String PREF_KEY_PREVIEW_DISMISS_DURATION =
+ "pref_key_preview_dismiss_duration";
public static final String PREF_KEY_PREVIEW_DISMISS_END_X_SCALE =
"pref_key_preview_dismiss_end_x_scale";
public static final String PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE =
"pref_key_preview_dismiss_end_y_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";
+ public static final String PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE =
+ "pref_key_preview_show_up_start_x_scale";
+ public static final String PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE =
+ "pref_key_preview_show_up_start_y_scale";
+ public static final String PREF_SHOULD_SHOW_LXX_SUGGESTION_UI =
+ "pref_should_show_lxx_suggestion_ui";
public static final String PREF_SLIDING_KEY_INPUT_PREVIEW = "pref_sliding_key_input_preview";
- public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout";
private DebugSettings() {
// This class is not publicly instantiable.
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
index 5640e2039..37855377d 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
@@ -28,9 +28,8 @@ import android.preference.PreferenceGroup;
import android.preference.TwoStatePreference;
import com.android.inputmethod.latin.DictionaryDumpBroadcastReceiver;
-import com.android.inputmethod.latin.DictionaryFacilitator;
+import com.android.inputmethod.latin.DictionaryFacilitatorImpl;
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;
@@ -43,12 +42,10 @@ import java.util.Locale;
*/
public final class DebugSettingsFragment extends SubScreenFragment
implements OnPreferenceClickListener {
- private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary";
private static final String PREF_KEY_DUMP_DICTS = "pref_key_dump_dictionaries";
private static final String PREF_KEY_DUMP_DICT_PREFIX = "pref_key_dump_dictionaries";
private boolean mServiceNeedsRestart = false;
- private Preference mReadExternalDictionaryPref;
private TwoStatePreference mDebugMode;
@Override
@@ -56,24 +53,18 @@ public final class DebugSettingsFragment extends SubScreenFragment
super.onCreate(icicle);
addPreferencesFromResource(R.xml.prefs_screen_debug);
- if (!Settings.HAS_UI_TO_ACCEPT_TYPED_WORD) {
- removePreference(DebugSettings.PREF_SHOW_UI_TO_ACCEPT_TYPED_WORD);
- }
-
- mReadExternalDictionaryPref = findPreference(PREF_READ_EXTERNAL_DICTIONARY);
- if (mReadExternalDictionaryPref != null) {
- mReadExternalDictionaryPref.setOnPreferenceClickListener(this);
+ if (!Settings.SHOULD_SHOW_LXX_SUGGESTION_UI) {
+ removePreference(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI);
}
final PreferenceGroup dictDumpPreferenceGroup =
(PreferenceGroup)findPreference(PREF_KEY_DUMP_DICTS);
- for (final String dictName : DictionaryFacilitator.DICT_TYPE_TO_CLASS.keySet()) {
+ for (final String dictName : DictionaryFacilitatorImpl.DICT_TYPE_TO_CLASS.keySet()) {
final Preference pref = new DictDumpPreference(getActivity(), dictName);
pref.setOnPreferenceClickListener(this);
dictDumpPreferenceGroup.addPreference(pref);
}
final Resources res = getResources();
- setupKeyLongpressTimeoutSettings();
setupKeyPreviewAnimationDuration(DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION,
res.getInteger(R.integer.config_key_preview_show_up_duration));
setupKeyPreviewAnimationDuration(DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION,
@@ -90,6 +81,8 @@ public final class DebugSettingsFragment extends SubScreenFragment
defaultKeyPreviewDismissEndScale);
setupKeyPreviewAnimationScale(DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE,
defaultKeyPreviewDismissEndScale);
+ setupKeyboardHeight(
+ DebugSettings.PREF_KEYBOARD_HEIGHT_SCALE, SettingsValues.DEFAULT_SIZE_SCALE);
mServiceNeedsRestart = false;
mDebugMode = (TwoStatePreference) findPreference(DebugSettings.PREF_DEBUG_MODE);
@@ -110,11 +103,6 @@ public final class DebugSettingsFragment extends SubScreenFragment
@Override
public boolean onPreferenceClick(final Preference pref) {
final Context context = getActivity();
- if (pref == mReadExternalDictionaryPref) {
- ExternalDictionaryGetterForDebug.chooseAndInstallDictionary(context);
- mServiceNeedsRestart = true;
- return true;
- }
if (pref instanceof DictDumpPreference) {
final DictDumpPreference dictDumpPref = (DictDumpPreference)pref;
final String dictName = dictDumpPref.mDictName;
@@ -143,8 +131,7 @@ public final class DebugSettingsFragment extends SubScreenFragment
mServiceNeedsRestart = true;
return;
}
- if (key.equals(DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH)
- || key.equals(DebugSettings.PREF_FORCE_PHYSICAL_KEYBOARD_SPECIAL_KEY)) {
+ if (key.equals(DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH)) {
mServiceNeedsRestart = true;
return;
}
@@ -163,18 +150,27 @@ public final class DebugSettingsFragment extends SubScreenFragment
}
}
- private void setupKeyLongpressTimeoutSettings() {
+ private void setupKeyPreviewAnimationScale(final String prefKey, final float defaultValue) {
final SharedPreferences prefs = getSharedPreferences();
final Resources res = getResources();
- final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
- DebugSettings.PREF_KEY_LONGPRESS_TIMEOUT);
+ 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) {
- prefs.edit().putInt(key, value).apply();
+ prefs.edit().putFloat(key, getValueFromPercentage(value)).apply();
}
@Override
@@ -184,17 +180,21 @@ public final class DebugSettingsFragment extends SubScreenFragment
@Override
public int readValue(final String key) {
- return Settings.readKeyLongpressTimeout(prefs, res);
+ return getPercentageFromValue(
+ Settings.readKeyPreviewAnimationScale(prefs, key, defaultValue));
}
@Override
public int readDefaultValue(final String key) {
- return Settings.readDefaultKeyLongpressTimeout(res);
+ return getPercentageFromValue(defaultValue);
}
@Override
public String getValueText(final int value) {
- return res.getString(R.string.abbreviation_unit_milliseconds, value);
+ if (value < 0) {
+ return res.getString(R.string.settings_system_default);
+ }
+ return String.format(Locale.ROOT, "%d%%", value);
}
@Override
@@ -202,7 +202,7 @@ public final class DebugSettingsFragment extends SubScreenFragment
});
}
- private void setupKeyPreviewAnimationScale(final String prefKey, final float defaultValue) {
+ private void setupKeyPreviewAnimationDuration(final String prefKey, final int defaultValue) {
final SharedPreferences prefs = getSharedPreferences();
final Resources res = getResources();
final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(prefKey);
@@ -210,19 +210,9 @@ public final class DebugSettingsFragment extends SubScreenFragment
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) {
- prefs.edit().putFloat(key, getValueFromPercentage(value)).apply();
+ prefs.edit().putInt(key, value).apply();
}
@Override
@@ -232,21 +222,17 @@ public final class DebugSettingsFragment extends SubScreenFragment
@Override
public int readValue(final String key) {
- return getPercentageFromValue(
- Settings.readKeyPreviewAnimationScale(prefs, key, defaultValue));
+ return Settings.readKeyPreviewAnimationDuration(prefs, key, defaultValue);
}
@Override
public int readDefaultValue(final String key) {
- return getPercentageFromValue(defaultValue);
+ return defaultValue;
}
@Override
public String getValueText(final int value) {
- if (value < 0) {
- return res.getString(R.string.settings_system_default);
- }
- return String.format(Locale.ROOT, "%d%%", value);
+ return res.getString(R.string.abbreviation_unit_milliseconds, value);
}
@Override
@@ -254,17 +240,25 @@ public final class DebugSettingsFragment extends SubScreenFragment
});
}
- private void setupKeyPreviewAnimationDuration(final String prefKey, final int defaultValue) {
+ private void setupKeyboardHeight(final String prefKey, final float defaultValue) {
final SharedPreferences prefs = getSharedPreferences();
- final Resources res = getResources();
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) {
- prefs.edit().putInt(key, value).apply();
+ prefs.edit().putFloat(key, getValueFromPercentage(value)).apply();
}
@Override
@@ -274,17 +268,17 @@ public final class DebugSettingsFragment extends SubScreenFragment
@Override
public int readValue(final String key) {
- return Settings.readKeyPreviewAnimationDuration(prefs, key, defaultValue);
+ return getPercentageFromValue(Settings.readKeyboardHeight(prefs, defaultValue));
}
@Override
public int readDefaultValue(final String key) {
- return defaultValue;
+ return getPercentageFromValue(defaultValue);
}
@Override
public String getValueText(final int value) {
- return res.getString(R.string.abbreviation_unit_milliseconds, value);
+ return String.format(Locale.ROOT, "%d%%", value);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/settings/GestureSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/GestureSettingsFragment.java
index 832fbf65a..22b0655b4 100644
--- a/java/src/com/android/inputmethod/latin/settings/GestureSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/GestureSettingsFragment.java
@@ -16,7 +16,6 @@
package com.android.inputmethod.latin.settings;
-import android.content.SharedPreferences;
import android.os.Bundle;
import com.android.inputmethod.latin.R;
diff --git a/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java b/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java
new file mode 100644
index 000000000..5c416ab18
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+/**
+ * Collection of device specific preference constants.
+ */
+public class LocalSettingsConstants {
+ // Preference file for storing preferences that are tied to a device
+ // and are not backed up.
+ public static final String PREFS_FILE = "local_prefs";
+
+ // Preference key for the current account.
+ // Do not restore.
+ public static final String PREF_ACCOUNT_NAME = "pref_account_name";
+ // Preference key for enabling cloud sync feature.
+ // Do not restore.
+ public static final String PREF_ENABLE_CLOUD_SYNC = "pref_enable_cloud_sync";
+
+ // List of preference keys to skip from being restored by backup agent.
+ // These preferences are tied to a device and hence should not be restored.
+ // e.g. account name.
+ // Ideally they could have been kept in a separate file that wasn't backed up
+ // however the preference UI currently only deals with the default
+ // shared preferences which makes it non-trivial to move these out to
+ // a different shared preferences file.
+ public static final String[] PREFS_TO_SKIP_RESTORING = new String[] {
+ PREF_ACCOUNT_NAME,
+ PREF_ENABLE_CLOUD_SYNC,
+ // The debug settings are not restored on a new device.
+ // If a feature relies on these, it should ensure that the defaults are
+ // correctly set for it to work on a new device.
+ DebugSettings.PREF_DEBUG_MODE,
+ DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH,
+ DebugSettings.PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS,
+ DebugSettings.PREF_KEYBOARD_HEIGHT_SCALE,
+ DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION,
+ DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_X_SCALE,
+ DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE,
+ DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION,
+ DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE,
+ DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE,
+ DebugSettings.PREF_RESIZE_KEYBOARD,
+ DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI,
+ DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW
+ };
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java
deleted file mode 100644
index b073c50a4..000000000
--- a/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.settings;
-
-import android.os.Bundle;
-
-import com.android.inputmethod.latin.R;
-
-import java.util.ArrayList;
-
-/**
- * "Multilingual options" settings sub screen.
- *
- * This settings sub screen handles the following input preferences.
- * - Language switch key
- * - Switch to other input methods
- */
-public final class MultiLingualSettingsFragment extends SubScreenFragment {
- @Override
- public void onCreate(final Bundle icicle) {
- super.onCreate(icicle);
- addPreferencesFromResource(R.xml.prefs_screen_multilingual);
- if (!Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS) {
- removePreference(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY);
- removePreference(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST);
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
deleted file mode 100644
index 31a20c4db..000000000
--- a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.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.latin.settings;
-
-public class NativeSuggestOptions {
- // Need to update suggest_options.h when you add, remove or reorder options.
- private static final int IS_GESTURE = 0;
- private static final int USE_FULL_EDIT_DISTANCE = 1;
- private static final int BLOCK_OFFENSIVE_WORDS = 2;
- private static final int SPACE_AWARE_GESTURE_ENABLED = 3;
- private static final int OPTIONS_SIZE = 4;
-
- private final int[] mOptions = new int[OPTIONS_SIZE
- + AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
-
- public void setIsGesture(final boolean value) {
- setBooleanOption(IS_GESTURE, value);
- }
-
- public void setUseFullEditDistance(final boolean value) {
- setBooleanOption(USE_FULL_EDIT_DISTANCE, value);
- }
-
- public void setBlockOffensiveWords(final boolean value) {
- setBooleanOption(BLOCK_OFFENSIVE_WORDS, value);
- }
-
- public void setSpaceAwareGestureEnabled(final boolean value) {
- setBooleanOption(SPACE_AWARE_GESTURE_ENABLED, value);
- }
-
- public void setAdditionalFeaturesOptions(final int[] additionalOptions) {
- if (additionalOptions == null) {
- return;
- }
- for (int i = 0; i < additionalOptions.length; i++) {
- setIntegerOption(OPTIONS_SIZE + i, additionalOptions[i]);
- }
- }
-
- public int[] getOptions() {
- return mOptions;
- }
-
- private void setBooleanOption(final int key, final boolean value) {
- mOptions[key] = value ? 1 : 0;
- }
-
- private void setIntegerOption(final int key, final int value) {
- mOptions[key] = value;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java
index 49db2bdc0..d9858e61f 100644
--- a/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java
@@ -19,12 +19,13 @@ package com.android.inputmethod.latin.settings;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
+import android.os.Build;
import android.os.Bundle;
import android.preference.Preference;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.RichInputMethodManager;
/**
* "Preferences" settings sub screen.
@@ -38,6 +39,10 @@ import com.android.inputmethod.latin.SubtypeSwitcher;
* - Voice input key
*/
public final class PreferencesSettingsFragment extends SubScreenFragment {
+
+ private static final boolean VOICE_IME_ENABLED =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+
@Override
public void onCreate(final Bundle icicle) {
super.onCreate(icicle);
@@ -49,7 +54,7 @@ public final class PreferencesSettingsFragment extends SubScreenFragment {
// When we are called from the Settings application but we are not already running, some
// singleton and utility classes may not have been initialized. We have to call
// initialization method of these classes here. See {@link LatinIME#onCreate()}.
- SubtypeSwitcher.init(context);
+ RichInputMethodManager.init(context);
final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);
@@ -71,11 +76,10 @@ public final class PreferencesSettingsFragment extends SubScreenFragment {
super.onResume();
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 : getText(R.string.voice_input_disabled_summary));
+ RichInputMethodManager.getInstance().refreshSubtypeCaches();
+ voiceInputKeyOption.setEnabled(VOICE_IME_ENABLED);
+ voiceInputKeyOption.setSummary(VOICE_IME_ENABLED
+ ? null : getText(R.string.voice_input_disabled_summary));
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/RadioButtonPreference.java b/java/src/com/android/inputmethod/latin/settings/RadioButtonPreference.java
index c173d4706..91444604d 100644
--- a/java/src/com/android/inputmethod/latin/settings/RadioButtonPreference.java
+++ b/java/src/com/android/inputmethod/latin/settings/RadioButtonPreference.java
@@ -43,9 +43,7 @@ public class RadioButtonPreference extends Preference {
private final View.OnClickListener mClickListener = new View.OnClickListener() {
@Override
public void onClick(final View v) {
- if (mListener != null) {
- mListener.onRadioButtonClicked(RadioButtonPreference.this);
- }
+ callListenerOnRadioButtonClicked();
}
};
@@ -67,6 +65,12 @@ public class RadioButtonPreference extends Preference {
mListener = listener;
}
+ void callListenerOnRadioButtonClicked() {
+ if (mListener != null) {
+ mListener.onRadioButtonClicked(this);
+ }
+ }
+
@Override
protected void onBindView(final View view) {
super.onBindView(view);
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 0de2d8831..715f7bb38 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin.settings;
import android.content.Context;
import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
@@ -29,26 +28,24 @@ import com.android.inputmethod.compat.BuildCompatUtils;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.latin.utils.RunInLocale;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.StatsUtils;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
+import javax.annotation.Nonnull;
+
public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = Settings.class.getSimpleName();
// Settings screens
- public static final String SCREEN_PREFERENCES = "screen_preferences";
- public static final String SCREEN_APPEARANCE = "screen_appearance";
+ public static final String SCREEN_ACCOUNTS = "screen_accounts";
public static final String SCREEN_THEME = "screen_theme";
- public static final String SCREEN_MULTILINGUAL = "screen_multilingual";
- public static final String SCREEN_GESTURE = "screen_gesture";
- public static final String SCREEN_CORRECTION = "screen_correction";
- public static final String SCREEN_ADVANCED = "screen_advanced";
public static final String SCREEN_DEBUG = "screen_debug";
// In the same order as xml/prefs.xml
public static final String PREF_AUTO_CAP = "auto_cap";
@@ -60,7 +57,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key";
public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key";
- public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
+ // PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE is obsolete. Use PREF_AUTO_CORRECTION instead.
+ public static final String PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE =
+ "auto_correction_threshold";
+ public static final String PREF_AUTO_CORRECTION = "pref_key_auto_correction";
// PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE is obsolete. Use PREF_SHOW_SUGGESTIONS instead.
public static final String PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE = "show_suggestions_setting";
public static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
@@ -70,19 +70,16 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
"pref_key_use_double_space_period";
public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE =
"pref_key_block_potentially_offensive";
- // No multilingual options in Android L and above for now.
- public static final boolean SHOW_MULTILINGUAL_SETTINGS =
- BuildCompatUtils.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.KITKAT;
public static final boolean ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS =
BuildCompatUtils.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.KITKAT;
- public static final boolean HAS_UI_TO_ACCEPT_TYPED_WORD =
- BuildCompatUtils.EFFECTIVE_SDK_INT >= BuildCompatUtils.VERSION_CODES_LXX;
+ public static final boolean SHOULD_SHOW_LXX_SUGGESTION_UI =
+ BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
public static final String PREF_SHOW_LANGUAGE_SWITCH_KEY =
"pref_show_language_switch_key";
public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST =
"pref_include_other_imes_in_language_switch_list";
- public static final String PREF_KEYBOARD_THEME = "pref_keyboard_theme";
public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles";
+ public static final String PREF_ENABLE_SPLIT_KEYBOARD = "pref_split_keyboard";
// TODO: consolidate key preview dismiss delay with the key preview animation parameters.
public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
"pref_key_preview_popup_dismiss_delay";
@@ -90,20 +87,17 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_GESTURE_INPUT = "gesture_input";
public static final String PREF_VIBRATION_DURATION_SETTINGS =
"pref_vibration_duration_settings";
- public static final String PREF_KEYPRESS_SOUND_VOLUME =
- "pref_keypress_sound_volume";
+ public static final String PREF_KEYPRESS_SOUND_VOLUME = "pref_keypress_sound_volume";
+ public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout";
+ public static final String PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY =
+ "pref_enable_emoji_alt_physical_key";
public static final String PREF_GESTURE_PREVIEW_TRAIL = "pref_gesture_preview_trail";
public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT =
"pref_gesture_floating_preview_text";
- public static final String PREF_SHOW_SETUP_WIZARD_ICON = "pref_show_setup_wizard_icon";
- public static final String PREF_PHRASE_GESTURE_ENABLED = "pref_gesture_space_aware";
- public static final String PREF_INPUT_LANGUAGE = "input_language";
- public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
public static final String PREF_KEY_IS_INTERNAL = "pref_key_is_internal";
public static final String PREF_ENABLE_METRICS_LOGGING = "pref_enable_metrics_logging";
-
// This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead.
// This is being used only for the backward compatibility.
private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
@@ -149,6 +143,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
mRes = context.getResources();
mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
mPrefs.registerOnSharedPreferenceChangeListener(this);
+ upgradeAutocorrectionSettings(mPrefs, mRes);
}
public void onDestroy() {
@@ -166,13 +161,14 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return;
}
loadSettings(mContext, mSettingsValues.mLocale, mSettingsValues.mInputAttributes);
+ StatsUtils.onLoadSettings(mSettingsValues);
} finally {
mSettingsValuesLock.unlock();
}
}
public void loadSettings(final Context context, final Locale locale,
- final InputAttributes inputAttributes) {
+ @Nonnull final InputAttributes inputAttributes) {
mSettingsValuesLock.lock();
mContext = context;
try {
@@ -198,12 +194,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return mSettingsValues.mIsInternal;
}
- public boolean isWordSeparator(final int code) {
- return mSettingsValues.isWordSeparator(code);
- }
-
- public boolean getBlockPotentiallyOffensive() {
- return mSettingsValues.mBlockPotentiallyOffensive;
+ public static int readScreenMetrics(final Resources res) {
+ return res.getInteger(R.integer.config_screen_metrics);
}
// Accessed from the settings interface, hence public
@@ -220,11 +212,13 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
res.getBoolean(R.bool.config_default_vibration_enabled));
}
- public static boolean readAutoCorrectEnabled(final String currentAutoCorrectionSetting,
+ public static boolean readAutoCorrectEnabled(final SharedPreferences prefs,
final Resources res) {
- final String autoCorrectionOff = res.getString(
- R.string.auto_correction_threshold_mode_index_off);
- return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
+ return prefs.getBoolean(PREF_AUTO_CORRECTION, true);
+ }
+
+ public static float readPlausibilityThreshold(final Resources res) {
+ return Float.parseFloat(res.getString(R.string.plausibility_threshold));
}
public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs,
@@ -243,12 +237,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
&& prefs.getBoolean(PREF_GESTURE_INPUT, true);
}
- public static boolean readPhraseGestureEnabled(final SharedPreferences prefs,
- final Resources res) {
- return prefs.getBoolean(PREF_PHRASE_GESTURE_ENABLED,
- res.getBoolean(R.bool.config_default_phrase_gesture_enabled));
- }
-
public static boolean readFromBuildConfigIfToShowKeyPreviewPopupOption(final Resources res) {
return res.getBoolean(R.bool.config_enable_show_key_preview_popup_option);
}
@@ -314,7 +302,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static int readKeyLongpressTimeout(final SharedPreferences prefs,
final Resources res) {
final int milliseconds = prefs.getInt(
- DebugSettings.PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT);
+ PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT);
return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds
: readDefaultKeyLongpressTimeout(res);
}
@@ -352,25 +340,15 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds : defaultValue;
}
- public static boolean readUseFullscreenMode(final Resources res) {
- return res.getBoolean(R.bool.config_use_fullscreen_mode);
+ public static float readKeyboardHeight(final SharedPreferences prefs,
+ final float defaultValue) {
+ final float percentage = prefs.getFloat(
+ DebugSettings.PREF_KEYBOARD_HEIGHT_SCALE, UNDEFINED_PREFERENCE_VALUE_FLOAT);
+ return (percentage != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? percentage : defaultValue;
}
- public static boolean readShowSetupWizardIcon(final SharedPreferences prefs,
- final Context context) {
- final boolean enableSetupWizardByConfig = context.getResources().getBoolean(
- R.bool.config_setup_wizard_available);
- if (!enableSetupWizardByConfig) {
- return false;
- }
- if (!prefs.contains(PREF_SHOW_SETUP_WIZARD_ICON)) {
- final ApplicationInfo appInfo = context.getApplicationInfo();
- final boolean isApplicationInSystemImage =
- (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- // Default value
- return !isApplicationInSystemImage;
- }
- return prefs.getBoolean(PREF_SHOW_SETUP_WIZARD_ICON, false);
+ public static boolean readUseFullscreenMode(final Resources res) {
+ return res.getBoolean(R.bool.config_use_fullscreen_mode);
}
public static boolean readHasHardwareKeyboard(final Configuration conf) {
@@ -446,4 +424,21 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
final SharedPreferences prefs, final int defValue) {
return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, defValue);
}
+
+ private void upgradeAutocorrectionSettings(final SharedPreferences prefs, final Resources res) {
+ final String thresholdSetting =
+ prefs.getString(PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE, null);
+ if (thresholdSetting != null) {
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.remove(PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE);
+ final String autoCorrectionOff =
+ res.getString(R.string.auto_correction_threshold_mode_index_off);
+ if (thresholdSetting.equals(autoCorrectionOff)) {
+ editor.putBoolean(PREF_AUTO_CORRECTION, false);
+ } else {
+ editor.putBoolean(PREF_AUTO_CORRECTION, true);
+ }
+ editor.commit();
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java b/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java
index b0c494098..9975277e4 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java
@@ -17,6 +17,8 @@
package com.android.inputmethod.latin.settings;
import com.android.inputmethod.latin.utils.FragmentUtils;
+import com.android.inputmethod.latin.utils.StatsUtils;
+import com.android.inputmethod.latin.utils.StatsUtilsManager;
import android.app.ActionBar;
import android.content.Intent;
@@ -25,19 +27,30 @@ import android.preference.PreferenceActivity;
import android.view.MenuItem;
public final class SettingsActivity extends PreferenceActivity {
- public static final String EXTRA_SHOW_HOME_AS_UP = "show_home_as_up";
private static final String DEFAULT_FRAGMENT = SettingsFragment.class.getName();
+
+ public static final String EXTRA_SHOW_HOME_AS_UP = "show_home_as_up";
+ public static final String EXTRA_ENTRY_KEY = "entry";
+ public static final String EXTRA_ENTRY_VALUE_LONG_PRESS_COMMA = "long_press_comma";
+ public static final String EXTRA_ENTRY_VALUE_APP_ICON = "app_icon";
+ public static final String EXTRA_ENTRY_VALUE_NOTICE_DIALOG = "important_notice";
+ public static final String EXTRA_ENTRY_VALUE_SYSTEM_SETTINGS = "system_settings";
+
private boolean mShowHomeAsUp;
@Override
protected void onCreate(final Bundle savedState) {
super.onCreate(savedState);
final ActionBar actionBar = getActionBar();
+ final Intent intent = getIntent();
if (actionBar != null) {
- mShowHomeAsUp = getIntent().getBooleanExtra(EXTRA_SHOW_HOME_AS_UP, true);
+ mShowHomeAsUp = intent.getBooleanExtra(EXTRA_SHOW_HOME_AS_UP, true);
actionBar.setDisplayHomeAsUpEnabled(mShowHomeAsUp);
actionBar.setHomeButtonEnabled(mShowHomeAsUp);
}
+ StatsUtils.onSettingsActivity(
+ intent.hasExtra(EXTRA_ENTRY_KEY) ? intent.getStringExtra(EXTRA_ENTRY_KEY)
+ : EXTRA_ENTRY_VALUE_SYSTEM_SETTINGS);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 6c8ab573a..f5455e3db 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -27,6 +27,7 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.define.ProductionFlags;
import com.android.inputmethod.latin.utils.ApplicationUtils;
import com.android.inputmethod.latin.utils.FeedbackUtils;
import com.android.inputmethodcommon.InputMethodSettingsFragment;
@@ -49,9 +50,9 @@ public final class SettingsFragment extends InputMethodSettingsFragment {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
preferenceScreen.setTitle(
ApplicationUtils.getActivityTitleResId(getActivity(), SettingsActivity.class));
- if (!Settings.SHOW_MULTILINGUAL_SETTINGS) {
- final Preference multilingualOptions = findPreference(Settings.SCREEN_MULTILINGUAL);
- preferenceScreen.removePreference(multilingualOptions);
+ if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) {
+ final Preference accountsPreference = findPreference(Settings.SCREEN_ACCOUNTS);
+ preferenceScreen.removePreference(accountsPreference);
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index d8c548d8b..d112e7200 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -21,6 +21,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.os.Build;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
@@ -28,7 +29,6 @@ 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.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
@@ -36,17 +36,22 @@ import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
import java.util.Arrays;
import java.util.Locale;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* When you call the constructor of this class, you may want to change the current system locale by
* using {@link com.android.inputmethod.latin.utils.RunInLocale}.
*/
-public final class SettingsValues {
+// Non-final for testing via mock library.
+public class SettingsValues {
private static final String TAG = SettingsValues.class.getSimpleName();
// "floatMaxValue" and "floatNegativeInfinity" are special marker strings for
// Float.NEGATIVE_INFINITE and Float.MAX_VALUE. Currently used for auto-correction settings.
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
+ public static final float DEFAULT_SIZE_SCALE = 1.0f; // 100%
// From resources:
public final SpacingAndPunctuations mSpacingAndPunctuations;
@@ -74,12 +79,17 @@ public final class SettingsValues {
public final boolean mGestureTrailEnabled;
public final boolean mGestureFloatingPreviewTextEnabled;
public final boolean mSlidingKeyInputPreviewEnabled;
- public final boolean mPhraseGestureEnabled;
public final int mKeyLongpressTimeout;
+ public final boolean mEnableEmojiAltPhysicalKey;
+ public final boolean mCloudSyncEnabled;
public final boolean mEnableMetricsLogging;
- public final boolean mShouldShowUiToAcceptTypedWord;
+ public final boolean mShouldShowLxxSuggestionUi;
+ // Use split layout for keyboard.
+ public final boolean mIsSplitKeyboardEnabled;
+ public final int mScreenMetrics;
// From the input box
+ @Nonnull
public final InputAttributes mInputAttributes;
// Deduced settings
@@ -88,20 +98,16 @@ public final class SettingsValues {
public final int mKeyPreviewPopupDismissDelay;
private final boolean mAutoCorrectEnabled;
public final float mAutoCorrectionThreshold;
+ public final float mPlausibilityThreshold;
public final boolean mAutoCorrectionEnabledPerUserSettings;
private final boolean mSuggestionsEnabledPerUserSettings;
private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds;
- // Setting values for additional features
- public final int[] mAdditionalFeaturesSettingValues =
- new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
-
- // TextDecorator
- public final int mTextHighlightColorForAddToDictionaryIndicator;
-
// Debug settings
public final boolean mIsInternal;
public final boolean mHasCustomKeyPreviewAnimationParams;
+ public final boolean mHasKeyboardResize;
+ public final float mKeyboardHeightScale;
public final int mKeyPreviewShowUpDuration;
public final int mKeyPreviewDismissDuration;
public final float mKeyPreviewShowUpStartXScale;
@@ -109,8 +115,10 @@ public final class SettingsValues {
public final float mKeyPreviewDismissEndXScale;
public final float mKeyPreviewDismissEndYScale;
+ @Nullable public final String mAccount;
+
public SettingsValues(final Context context, final SharedPreferences prefs, final Resources res,
- final InputAttributes inputAttributes) {
+ @Nonnull final InputAttributes inputAttributes) {
mLocale = res.getConfiguration().locale;
// Get the resources
mDelayInMillisecondsToUpdateOldSuggestions =
@@ -118,12 +126,7 @@ public final class SettingsValues {
mSpacingAndPunctuations = new SpacingAndPunctuations(res);
// Store the input attributes
- if (null == inputAttributes) {
- mInputAttributes = new InputAttributes(
- null, false /* isFullscreenMode */, context.getPackageName());
- } else {
- mInputAttributes = inputAttributes;
- }
+ mInputAttributes = inputAttributes;
// Get the settings preferences
mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
@@ -134,10 +137,7 @@ public final class SettingsValues {
DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true);
mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res)
&& mInputAttributes.mShouldShowVoiceInputKey
- && SubtypeSwitcher.getInstance().isShortcutImeEnabled();
- final String autoCorrectionThresholdRawValue = prefs.getString(
- Settings.PREF_AUTO_CORRECTION_THRESHOLD,
- res.getString(R.string.auto_correction_threshold_mode_index_modest));
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
mIncludesOtherImesInLanguageSwitchList = Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS
? prefs.getBoolean(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false)
: true /* forcibly */;
@@ -148,35 +148,44 @@ public final class SettingsValues {
mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true)
&& inputAttributes.mIsGeneralTextInput;
mBlockPotentiallyOffensive = Settings.readBlockPotentiallyOffensive(prefs, res);
- mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(autoCorrectionThresholdRawValue, res);
+ mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(prefs, res);
+ final String autoCorrectionThresholdRawValue = mAutoCorrectEnabled
+ ? res.getString(R.string.auto_correction_threshold_mode_index_modest)
+ : res.getString(R.string.auto_correction_threshold_mode_index_off);
mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res);
mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout);
mHasHardwareKeyboard = Settings.readHasHardwareKeyboard(res.getConfiguration());
mEnableMetricsLogging = prefs.getBoolean(Settings.PREF_ENABLE_METRICS_LOGGING, true);
- mShouldShowUiToAcceptTypedWord = Settings.HAS_UI_TO_ACCEPT_TYPED_WORD
- && prefs.getBoolean(DebugSettings.PREF_SHOW_UI_TO_ACCEPT_TYPED_WORD, true);
+ mIsSplitKeyboardEnabled = prefs.getBoolean(Settings.PREF_ENABLE_SPLIT_KEYBOARD, false);
+ mScreenMetrics = Settings.readScreenMetrics(res);
+
+ mShouldShowLxxSuggestionUi = Settings.SHOULD_SHOW_LXX_SUGGESTION_UI
+ && prefs.getBoolean(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, true);
// Compute other readable settings
mKeyLongpressTimeout = Settings.readKeyLongpressTimeout(prefs, res);
mKeypressVibrationDuration = Settings.readKeypressVibrationDuration(prefs, res);
mKeypressSoundVolume = Settings.readKeypressSoundVolume(prefs, res);
mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res);
+ mEnableEmojiAltPhysicalKey = prefs.getBoolean(
+ Settings.PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY, true);
mAutoCorrectionThreshold = readAutoCorrectionThreshold(res,
autoCorrectionThresholdRawValue);
+ mPlausibilityThreshold = Settings.readPlausibilityThreshold(res);
mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
- mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
- Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
- mPhraseGestureEnabled = Settings.readPhraseGestureEnabled(prefs, res);
+ mCloudSyncEnabled = prefs.getBoolean(LocalSettingsConstants.PREF_ENABLE_CLOUD_SYNC, false);
+ mAccount = prefs.getString(LocalSettingsConstants.PREF_ACCOUNT_NAME,
+ null /* default */);
+ mGestureFloatingPreviewTextEnabled = !mInputAttributes.mDisableGestureFloatingPreviewText
+ && prefs.getBoolean(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
mAutoCorrectionEnabledPerUserSettings = mAutoCorrectEnabled
&& !mInputAttributes.mInputTypeNoAutoCorrect;
mSuggestionsEnabledPerUserSettings = readSuggestionsEnabled(prefs);
- AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
- prefs, mAdditionalFeaturesSettingValues);
- mTextHighlightColorForAddToDictionaryIndicator = res.getColor(
- R.color.text_decorator_add_to_dictionary_indicator_text_highlight_color);
mIsInternal = Settings.isInternal(prefs);
mHasCustomKeyPreviewAnimationParams = prefs.getBoolean(
DebugSettings.PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS, false);
+ mHasKeyboardResize = prefs.getBoolean(DebugSettings.PREF_RESIZE_KEYBOARD, false);
+ mKeyboardHeightScale = Settings.readKeyboardHeight(prefs, DEFAULT_SIZE_SCALE);
mKeyPreviewShowUpDuration = Settings.readKeyPreviewAnimationDuration(
prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION,
res.getInteger(R.integer.config_key_preview_show_up_duration));
@@ -211,6 +220,10 @@ public final class SettingsValues {
}
}
+ public boolean isMetricsLoggingEnabled() {
+ return mEnableMetricsLogging;
+ }
+
public boolean isApplicationSpecifiedCompletionsOn() {
return mInputAttributes.mApplicationSpecifiedCompletionOn;
}
@@ -224,6 +237,10 @@ public final class SettingsValues {
return mSuggestionsEnabledPerUserSettings;
}
+ public boolean isPersonalizationEnabled() {
+ return mUsePersonalizedDicts;
+ }
+
public boolean isWordSeparator(final int code) {
return mSpacingAndPunctuations.isWordSeparator(code);
}
@@ -256,9 +273,8 @@ public final class SettingsValues {
final RichInputMethodManager imm = RichInputMethodManager.getInstance();
if (mIncludesOtherImesInLanguageSwitchList) {
return imm.hasMultipleEnabledIMEsOrSubtypes(false /* include aux subtypes */);
- } else {
- return imm.hasMultipleEnabledSubtypesInThisIme(false /* include aux subtypes */);
}
+ return imm.hasMultipleEnabledSubtypesInThisIme(false /* include aux subtypes */);
}
public boolean isSameInputType(final EditorInfo editorInfo) {
@@ -388,8 +404,6 @@ public final class SettingsValues {
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 = ");
@@ -415,10 +429,6 @@ public final class SettingsValues {
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 mTextHighlightColorForAddToDictionaryIndicator = ");
- sb.append("" + mTextHighlightColorForAddToDictionaryIndicator);
sb.append("\n mIsInternal = ");
sb.append("" + mIsInternal);
sb.append("\n mKeyPreviewShowUpDuration = ");
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java b/java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java
index d80af4ba7..5e2e5a5d6 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java
@@ -18,13 +18,8 @@ package com.android.inputmethod.latin.settings;
public class SettingsValuesForSuggestion {
public final boolean mBlockPotentiallyOffensive;
- public final boolean mSpaceAwareGestureEnabled;
- public final int[] mAdditionalFeaturesSettingValues;
- public SettingsValuesForSuggestion(final boolean blockPotentiallyOffensive,
- final boolean spaceAwareGestureEnabled, final int[] additionalFeaturesSettingValues) {
+ public SettingsValuesForSuggestion(final boolean blockPotentiallyOffensive) {
mBlockPotentiallyOffensive = blockPotentiallyOffensive;
- mSpaceAwareGestureEnabled = spaceAwareGestureEnabled;
- mAdditionalFeaturesSettingValues = additionalFeaturesSettingValues;
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
index 49d81104d..70d97a5ba 100644
--- a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
+++ b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
@@ -20,10 +20,10 @@ import android.content.res.Resources;
import com.android.inputmethod.annotations.UsedForTesting;
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 com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
import java.util.Arrays;
import java.util.Locale;
@@ -36,6 +36,8 @@ public final class SpacingAndPunctuations {
public final int[] mSortedWordSeparators;
public final PunctuationSuggestions mSuggestPuncList;
private final int mSentenceSeparator;
+ private final int mAbbreviationMarker;
+ private final int[] mSortedSentenceTerminators;
public final String mSentenceSeparatorAndSpace;
public final boolean mCurrentLanguageHasSpaces;
public final boolean mUsesAmericanTypography;
@@ -55,7 +57,10 @@ public final class SpacingAndPunctuations {
res.getString(R.string.symbols_word_connectors));
mSortedWordSeparators = StringUtils.toSortedCodePointArray(
res.getString(R.string.symbols_word_separators));
+ mSortedSentenceTerminators = StringUtils.toSortedCodePointArray(
+ res.getString(R.string.symbols_sentence_terminators));
mSentenceSeparator = res.getInteger(R.integer.sentence_separator);
+ mAbbreviationMarker = res.getInteger(R.integer.abbreviation_marker);
mSentenceSeparatorAndSpace = new String(new int[] {
mSentenceSeparator, Constants.CODE_SPACE }, 0, 2);
mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces);
@@ -77,8 +82,10 @@ public final class SpacingAndPunctuations {
mSortedSymbolsClusteringTogether = model.mSortedSymbolsClusteringTogether;
mSortedWordConnectors = model.mSortedWordConnectors;
mSortedWordSeparators = overrideSortedWordSeparators;
+ mSortedSentenceTerminators = model.mSortedSentenceTerminators;
mSuggestPuncList = model.mSuggestPuncList;
mSentenceSeparator = model.mSentenceSeparator;
+ mAbbreviationMarker = model.mAbbreviationMarker;
mSentenceSeparatorAndSpace = model.mSentenceSeparatorAndSpace;
mCurrentLanguageHasSpaces = model.mCurrentLanguageHasSpaces;
mUsesAmericanTypography = model.mUsesAmericanTypography;
@@ -109,6 +116,14 @@ public final class SpacingAndPunctuations {
return Arrays.binarySearch(mSortedSymbolsClusteringTogether, code) >= 0;
}
+ public boolean isSentenceTerminator(final int code) {
+ return Arrays.binarySearch(mSortedSentenceTerminators, code) >= 0;
+ }
+
+ public boolean isAbbreviationMarker(final int code) {
+ return code == mAbbreviationMarker;
+ }
+
public boolean isSentenceSeparator(final int code) {
return code == mSentenceSeparator;
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java b/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java
index ca5b395ce..240f8f89b 100644
--- a/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java
@@ -20,6 +20,7 @@ import android.app.backup.BackupManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.Resources;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
@@ -79,6 +80,16 @@ abstract class SubScreenFragment extends PreferenceFragment
return getPreferenceManager().getSharedPreferences();
}
+ /**
+ * Gets the application name to display on the UI.
+ */
+ final String getApplicationName() {
+ final Context context = getActivity();
+ final Resources res = getResources();
+ final int applicationLabelRes = context.getApplicationInfo().labelRes;
+ return res.getString(applicationLabelRes);
+ }
+
@Override
public void addPreferencesFromResource(final int preferencesResId) {
super.addPreferencesFromResource(preferencesResId);
diff --git a/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java b/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java
new file mode 100644
index 000000000..254bc6567
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.settings;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Test activity to use when testing preference fragments. <br/>
+ * Usage: <br/>
+ * Create an ActivityInstrumentationTestCase2 for this activity
+ * and call setIntent() with an intent that specifies the fragment to load in the activity.
+ * The fragment can then be obtained from this activity and used for testing/verification.
+ */
+public final class TestFragmentActivity extends Activity {
+ /**
+ * The fragment name that should be loaded when starting this activity.
+ * This must be specified when starting this activity, as this activity is only
+ * meant to test fragments from instrumentation tests.
+ */
+ public static final String EXTRA_SHOW_FRAGMENT = "show_fragment";
+
+ public Fragment mFragment;
+
+ @Override
+ protected void onCreate(final Bundle savedState) {
+ super.onCreate(savedState);
+ final Intent intent = getIntent();
+ final String fragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
+ if (fragmentName == null) {
+ throw new IllegalArgumentException("No fragment name specified for testing");
+ }
+
+ mFragment = Fragment.instantiate(this, fragmentName);
+ FragmentManager fragmentManager = getFragmentManager();
+ fragmentManager.beginTransaction().add(mFragment, fragmentName).commit();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
index 5a3fc3600..29289aed2 100644
--- a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin.settings;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.Preference;
@@ -32,12 +31,12 @@ import com.android.inputmethod.latin.settings.RadioButtonPreference.OnRadioButto
*/
public final class ThemeSettingsFragment extends SubScreenFragment
implements OnRadioButtonClickedListener {
- private String mSelectedThemeId;
+ private int mSelectedThemeId;
static class KeyboardThemePreference extends RadioButtonPreference {
- final String mThemeId;
+ final int mThemeId;
- KeyboardThemePreference(final Context context, final String name, final String id) {
+ KeyboardThemePreference(final Context context, final String name, final int id) {
super(context);
setTitle(name);
mThemeId = id;
@@ -45,14 +44,13 @@ public final class ThemeSettingsFragment extends SubScreenFragment
}
static void updateKeyboardThemeSummary(final Preference pref) {
- final Resources res = pref.getContext().getResources();
- final SharedPreferences prefs = pref.getSharedPreferences();
- final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
- final String keyboardThemeId = String.valueOf(keyboardTheme.mThemeId);
+ final Context context = pref.getContext();
+ final Resources res = context.getResources();
+ final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context);
final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names);
- final String[] keyboardThemeIds = res.getStringArray(R.array.keyboard_theme_ids);
+ final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids);
for (int index = 0; index < keyboardThemeNames.length; index++) {
- if (keyboardThemeId.equals(keyboardThemeIds[index])) {
+ if (keyboardTheme.mThemeId == keyboardThemeIds[index]) {
pref.setSummary(keyboardThemeNames[index]);
return;
}
@@ -64,18 +62,18 @@ public final class ThemeSettingsFragment extends SubScreenFragment
super.onCreate(icicle);
addPreferencesFromResource(R.xml.prefs_screen_theme);
final PreferenceScreen screen = getPreferenceScreen();
+ final Context context = getActivity();
final Resources res = getResources();
final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names);
- final String[] keyboardThemeIds = res.getStringArray(R.array.keyboard_theme_ids);
+ final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids);
for (int index = 0; index < keyboardThemeNames.length; index++) {
final KeyboardThemePreference pref = new KeyboardThemePreference(
- getActivity(), keyboardThemeNames[index], keyboardThemeIds[index]);
+ context, keyboardThemeNames[index], keyboardThemeIds[index]);
screen.addPreference(pref);
pref.setOnRadioButtonClickedListener(this);
}
- final SharedPreferences prefs = getSharedPreferences();
- final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
- mSelectedThemeId = String.valueOf(keyboardTheme.mThemeId);
+ final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context);
+ mSelectedThemeId = keyboardTheme.mThemeId;
}
@Override
@@ -106,7 +104,7 @@ public final class ThemeSettingsFragment extends SubScreenFragment
final Preference preference = screen.getPreference(index);
if (preference instanceof KeyboardThemePreference) {
final KeyboardThemePreference pref = (KeyboardThemePreference)preference;
- final boolean selected = mSelectedThemeId.equals(pref.mThemeId);
+ final boolean selected = (mSelectedThemeId == pref.mThemeId);
pref.setSelected(selected);
}
}
diff --git a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
deleted file mode 100644
index 3f0b10225..000000000
--- a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
+++ /dev/null
@@ -1,85 +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.setup;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.preference.PreferenceManager;
-import android.util.Log;
-
-import com.android.inputmethod.latin.settings.Settings;
-
-/**
- * This class handles the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME
- * package has been replaced by a newer version of the same package. This class also handles
- * {@link Intent#ACTION_BOOT_COMPLETED} and {@link Intent#ACTION_USER_INITIALIZE} broadcast intent.
- *
- * If this IME has already been installed in the system image and a new version of this IME has
- * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received to this class to hide the
- * setup wizard's icon.
- *
- * If this IME has already been installed in the data partition and a new version of this IME has
- * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is forwarded to this class but it
- * will not hide the setup wizard's icon, and the icon will appear on the launcher.
- *
- * If this IME hasn't been installed yet and has been newly installed, no
- * {@link Intent#ACTION_MY_PACKAGE_REPLACED} will be sent and the setup wizard's icon will appear
- * on the launcher.
- *
- * When the device has been booted, {@link Intent#ACTION_BOOT_COMPLETED} is forwarded to this class
- * to check whether the setup wizard's icon should be appeared or not on the launcher
- * depending on which partition this IME is installed.
- *
- * When a multiuser account has been created, {@link Intent#ACTION_USER_INITIALIZE} is forwarded to
- * this class to check whether the setup wizard's icon should be appeared or not on the launcher
- * depending on which partition this IME is installed.
- */
-public final class LauncherIconVisibilityManager {
- private static final String TAG = LauncherIconVisibilityManager.class.getSimpleName();
-
- public static void updateSetupWizardIconVisibility(final Context context) {
- final ComponentName setupWizardActivity = new ComponentName(context, SetupActivity.class);
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- final boolean stateHasSet;
- if (Settings.readShowSetupWizardIcon(prefs, context)) {
- stateHasSet = setActivityState(context, setupWizardActivity,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
- Log.i(TAG, (stateHasSet ? "Enable activity: " : "Activity has already been enabled: ")
- + setupWizardActivity);
- } else {
- stateHasSet = setActivityState(context, setupWizardActivity,
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
- Log.i(TAG, (stateHasSet ? "Disable activity: " : "Activity has already been disabled: ")
- + setupWizardActivity);
- }
- }
-
- private static boolean setActivityState(final Context context,
- final ComponentName activityComponent, final int activityState) {
- final PackageManager pm = context.getPackageManager();
- final int activityComponentState = pm.getComponentEnabledSetting(activityComponent);
- if (activityComponentState == activityState) {
- return false;
- }
- pm.setComponentEnabledSetting(
- activityComponent, activityState, PackageManager.DONT_KILL_APP);
- return true;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index b770ea512..7607429f8 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -17,12 +17,8 @@
package com.android.inputmethod.latin.setup;
import android.app.Activity;
-import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.provider.Settings;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
public final class SetupActivity extends Activity {
@Override
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
index e455e53d3..bee22afd5 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
@@ -42,10 +42,14 @@ import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils;
import java.util.ArrayList;
+import javax.annotation.Nonnull;
+
// TODO: Use Fragment to implement welcome screen and setup steps.
public final class SetupWizardActivity extends Activity implements View.OnClickListener {
static final String TAG = SetupWizardActivity.class.getSimpleName();
+ // For debugging purpose.
+ private static final boolean FORCE_TO_SHOW_WELCOME_SCREEN = false;
private static final boolean ENABLE_WELCOME_VIDEO = true;
private InputMethodManager mImm;
@@ -80,7 +84,7 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
private final InputMethodManager mImmInHandler;
- public SettingsPoolingHandler(final SetupWizardActivity ownerInstance,
+ public SettingsPoolingHandler(@Nonnull final SetupWizardActivity ownerInstance,
final InputMethodManager imm) {
super(ownerInstance);
mImmInHandler = imm;
@@ -261,6 +265,8 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
intent.setClass(this, SettingsActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY,
+ SettingsActivity.EXTRA_ENTRY_VALUE_APP_ICON);
startActivity(intent);
}
@@ -304,6 +310,9 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
private int determineSetupStepNumber() {
mHandler.cancelPollingImeSettings();
+ if (FORCE_TO_SHOW_WELCOME_SCREEN) {
+ return STEP_1;
+ }
if (!UncachedInputMethodManagerUtils.isThisImeEnabled(this, mImm)) {
return STEP_1;
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 90398deb2..00f69f158 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -16,55 +16,37 @@
package com.android.inputmethod.latin.spellcheck;
-import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.service.textservice.SpellCheckerService;
import android.text.InputType;
-import android.util.Log;
-import android.util.LruCache;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.textservice.SuggestionsInfo;
+import android.util.Log;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.ContactsBinaryDictionary;
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.DictionaryCollection;
import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.DictionaryFactory;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.DictionaryFacilitatorLruCache;
+import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.common.ComposedData;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
-import com.android.inputmethod.latin.UserBinaryDictionary;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
-import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.SuggestionResults;
-import com.android.inputmethod.latin.WordComposer;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.Locale;
-import java.util.Map;
-import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
/**
* Service for spell checking, using LatinIME's dictionaries and mechanisms.
@@ -72,72 +54,36 @@ import java.util.concurrent.TimeUnit;
public final class AndroidSpellCheckerService extends SpellCheckerService
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
- private static final boolean DBG = false;
+ private static final boolean DEBUG = false;
public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts";
private static final int SPELLCHECKER_DUMMY_KEYBOARD_WIDTH = 480;
- private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 368;
+ private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 301;
private static final String DICTIONARY_NAME_PREFIX = "spellcheck_";
- private static final int WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS = 1000;
- private static final int MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT = 5;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private final HashSet<Locale> mCachedLocales = new HashSet<>();
-
private final int MAX_NUM_OF_THREADS_READ_DICTIONARY = 2;
private final Semaphore mSemaphore = new Semaphore(MAX_NUM_OF_THREADS_READ_DICTIONARY,
true /* fair */);
// TODO: Make each spell checker session has its own session id.
private final ConcurrentLinkedQueue<Integer> mSessionIdPool = new ConcurrentLinkedQueue<>();
- private static class DictionaryFacilitatorLruCache extends
- LruCache<Locale, DictionaryFacilitator> {
- private final HashSet<Locale> mCachedLocales;
- public DictionaryFacilitatorLruCache(final HashSet<Locale> cachedLocales, int maxSize) {
- super(maxSize);
- mCachedLocales = cachedLocales;
- }
-
- @Override
- protected void entryRemoved(boolean evicted, Locale key,
- DictionaryFacilitator oldValue, DictionaryFacilitator newValue) {
- if (oldValue != null && oldValue != newValue) {
- oldValue.closeDictionaries();
- }
- if (key != null && newValue == null) {
- // Remove locale from the cache when the dictionary facilitator for the locale is
- // evicted and new facilitator is not set for the locale.
- mCachedLocales.remove(key);
- if (size() >= maxSize()) {
- Log.w(TAG, "DictionaryFacilitator for " + key.toString()
- + " has been evicted due to cache size limit."
- + " size: " + size() + ", maxSize: " + maxSize());
- }
- }
- }
- }
-
- private static final int MAX_DICTIONARY_FACILITATOR_COUNT = 3;
- private final LruCache<Locale, DictionaryFacilitator> mDictionaryFacilitatorCache =
- new DictionaryFacilitatorLruCache(mCachedLocales, MAX_DICTIONARY_FACILITATOR_COUNT);
+ private final DictionaryFacilitatorLruCache mDictionaryFacilitatorCache =
+ new DictionaryFacilitatorLruCache(this /* context */, DICTIONARY_NAME_PREFIX);
private final ConcurrentHashMap<Locale, Keyboard> mKeyboardCache = new ConcurrentHashMap<>();
// The threshold for a suggestion to be considered "recommended".
private float mRecommendedThreshold;
- // Whether to use the contacts dictionary
- private boolean mUseContactsDictionary;
// TODO: make a spell checker option to block offensive words or not
private final SettingsValuesForSuggestion mSettingsValuesForSuggestion =
- new SettingsValuesForSuggestion(true /* blockPotentiallyOffensive */,
- true /* spaceAwareGestureEnabled */,
- null /* additionalFeaturesSettingValues */);
- private final Object mDictionaryLock = new Object();
+ new SettingsValuesForSuggestion(true /* blockPotentiallyOffensive */);
public static final String SINGLE_QUOTE = "\u0027";
public static final String APOSTROPHE = "\u2019";
+ private UserDictionaryLookup mUserDictionaryLookup;
public AndroidSpellCheckerService() {
super();
@@ -146,13 +92,33 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
}
}
- @Override public void onCreate() {
+ @Override
+ public void onCreate() {
super.onCreate();
mRecommendedThreshold =
Float.parseFloat(getString(R.string.spellchecker_recommended_threshold_value));
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.registerOnSharedPreferenceChangeListener(this);
onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY);
+ // Create a UserDictionaryLookup. It needs to be close()d and set to null in onDestroy.
+ if (mUserDictionaryLookup == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Creating mUserDictionaryLookup in onCreate");
+ }
+ mUserDictionaryLookup = new UserDictionaryLookup(this);
+ } else if (DEBUG) {
+ Log.d(TAG, "mUserDictionaryLookup already created before onCreate");
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DEBUG) {
+ Log.d(TAG, "Closing and dereferencing mUserDictionaryLookup in onDestroy");
+ }
+ mUserDictionaryLookup.close();
+ mUserDictionaryLookup = null;
+ super.onDestroy();
}
public float getRecommendedThreshold() {
@@ -175,21 +141,8 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
@Override
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
- final boolean useContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
- if (useContactsDictionary != mUseContactsDictionary) {
- mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
- try {
- mUseContactsDictionary = useContactsDictionary;
- for (final Locale locale : mCachedLocales) {
- final DictionaryFacilitator dictionaryFacilitator =
- mDictionaryFacilitatorCache.get(locale);
- resetDictionariesForLocale(this /* context */,
- dictionaryFacilitator, locale, mUseContactsDictionary);
- }
- } finally {
- mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
- }
- }
+ final boolean useContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
+ mDictionaryFacilitatorCache.setUseContactsDictionary(useContactsDictionary);
}
@Override
@@ -221,24 +174,36 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
public boolean isValidWord(final Locale locale, final String word) {
mSemaphore.acquireUninterruptibly();
try {
+ if (mUserDictionaryLookup.isValidWord(word, locale)) {
+ if (DEBUG) {
+ Log.d(TAG, "mUserDictionaryLookup.isValidWord(" + word + ")=true");
+ }
+ return true;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "mUserDictionaryLookup.isValidWord(" + word + ")=false");
+ }
+ }
DictionaryFacilitator dictionaryFacilitatorForLocale =
- getDictionaryFacilitatorForLocaleLocked(locale);
- return dictionaryFacilitatorForLocale.isValidWord(word, false /* igroreCase */);
+ mDictionaryFacilitatorCache.get(locale);
+ return dictionaryFacilitatorForLocale.isValidSpellingWord(word);
} finally {
mSemaphore.release();
}
}
- public SuggestionResults getSuggestionResults(final Locale locale, final WordComposer composer,
- final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo) {
+ public SuggestionResults getSuggestionResults(final Locale locale,
+ final ComposedData composedData, final NgramContext ngramContext,
+ @Nonnull final Keyboard keyboard) {
Integer sessionId = null;
mSemaphore.acquireUninterruptibly();
try {
sessionId = mSessionIdPool.poll();
DictionaryFacilitator dictionaryFacilitatorForLocale =
- getDictionaryFacilitatorForLocaleLocked(locale);
- return dictionaryFacilitatorForLocale.getSuggestionResults(composer, prevWordsInfo,
- proximityInfo, mSettingsValuesForSuggestion, sessionId);
+ mDictionaryFacilitatorCache.get(locale);
+ return dictionaryFacilitatorForLocale.getSuggestionResults(composedData, ngramContext,
+ keyboard, mSettingsValuesForSuggestion,
+ sessionId, SuggestedWords.INPUT_STYLE_TYPING);
} finally {
if (sessionId != null) {
mSessionIdPool.add(sessionId);
@@ -251,56 +216,18 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
mSemaphore.acquireUninterruptibly();
try {
final DictionaryFacilitator dictionaryFacilitator =
- getDictionaryFacilitatorForLocaleLocked(locale);
- return dictionaryFacilitator.hasInitializedMainDictionary();
+ mDictionaryFacilitatorCache.get(locale);
+ return dictionaryFacilitator.hasAtLeastOneInitializedMainDictionary();
} finally {
mSemaphore.release();
}
}
- private DictionaryFacilitator getDictionaryFacilitatorForLocaleLocked(final Locale locale) {
- DictionaryFacilitator dictionaryFacilitatorForLocale =
- mDictionaryFacilitatorCache.get(locale);
- if (dictionaryFacilitatorForLocale == null) {
- dictionaryFacilitatorForLocale = new DictionaryFacilitator();
- mDictionaryFacilitatorCache.put(locale, dictionaryFacilitatorForLocale);
- mCachedLocales.add(locale);
- resetDictionariesForLocale(this /* context */, dictionaryFacilitatorForLocale,
- locale, mUseContactsDictionary);
- }
- return dictionaryFacilitatorForLocale;
- }
-
- private static void resetDictionariesForLocale(final Context context,
- final DictionaryFacilitator dictionaryFacilitator, final Locale locale,
- final boolean useContactsDictionary) {
- dictionaryFacilitator.resetDictionariesWithDictNamePrefix(context, locale,
- useContactsDictionary, false /* usePersonalizedDicts */,
- false /* forceReloadMainDictionary */, null /* listener */,
- DICTIONARY_NAME_PREFIX);
- for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) {
- try {
- dictionaryFacilitator.waitForLoadingMainDictionary(
- WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
- return;
- } catch (final InterruptedException e) {
- Log.i(TAG, "Interrupted during waiting for loading main dictionary.", e);
- if (i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT - 1) {
- Log.i(TAG, "Retry", e);
- } else {
- Log.w(TAG, "Give up retrying. Retried "
- + MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT + " times.", e);
- }
- }
- }
- }
-
@Override
public boolean onUnbind(final Intent intent) {
mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
try {
- mDictionaryFacilitatorCache.evictAll();
- mCachedLocales.clear();
+ mDictionaryFacilitatorCache.closeDictionaries();
} finally {
mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
}
@@ -334,7 +261,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(this, editorInfo);
builder.setKeyboardGeometry(
SPELLCHECKER_DUMMY_KEYBOARD_WIDTH, SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT);
- builder.setSubtype(subtype);
+ builder.setSubtype(RichInputMethodSubtype.getRichInputMethodSubtype(subtype));
builder.setIsSpellChecker(true /* isSpellChecker */);
builder.disableTouchPositionCorrectionData();
return builder.build();
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 34e01197a..2c690aea7 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -16,8 +16,10 @@
package com.android.inputmethod.latin.spellcheck;
+import android.annotation.TargetApi;
import android.content.res.Resources;
import android.os.Binder;
+import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.view.textservice.SentenceSuggestionsInfo;
@@ -25,8 +27,8 @@ import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.TextInfoCompatUtils;
-import com.android.inputmethod.latin.PrevWordsInfo;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.utils.SpannableStringUtils;
import java.util.ArrayList;
import java.util.Locale;
@@ -42,6 +44,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
mResources = service.getResources();
}
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote(TextInfo ti,
SentenceSuggestionsInfo ssi) {
final CharSequence typedText = TextInfoCompatUtils.getCharSequenceOrString(ti);
@@ -62,15 +65,16 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
final int offset = ssi.getOffsetAt(i);
final int length = ssi.getLengthAt(i);
final CharSequence subText = typedText.subSequence(offset, offset + length);
- final PrevWordsInfo prevWordsInfo =
- new PrevWordsInfo(new PrevWordsInfo.WordInfo(currentWord));
+ final NgramContext ngramContext =
+ new NgramContext(new NgramContext.WordInfo(currentWord));
currentWord = subText;
if (!subText.toString().contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
continue;
}
- final CharSequence[] splitTexts = StringUtils.split(subText,
+ // Split preserving spans.
+ final CharSequence[] splitTexts = SpannableStringUtils.split(subText,
AndroidSpellCheckerService.SINGLE_QUOTE,
- true /* preserveTrailingEmptySegments */ );
+ true /* preserveTrailingEmptySegments */);
if (splitTexts == null || splitTexts.length <= 1) {
continue;
}
@@ -80,7 +84,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
if (TextUtils.isEmpty(splitText)) {
continue;
}
- if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString(), prevWordsInfo)
+ if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString(), ngramContext)
== null) {
continue;
}
@@ -149,7 +153,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
* @param textInfos an array of the text metadata
* @param suggestionsLimit the maximum number of suggestions to be returned
* @return an array of {@link SentenceSuggestionsInfo} returned by
- * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)}
+ * {@link android.service.textservice.SpellCheckerService.Session#onGetSuggestions(TextInfo, int)}
*/
private SentenceSuggestionsInfo[] splitAndSuggest(TextInfo[] textInfos, int suggestionsLimit) {
if (textInfos == null || textInfos.length == 0) {
@@ -208,10 +212,10 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
} else {
prevWord = null;
}
- final PrevWordsInfo prevWordsInfo =
- new PrevWordsInfo(new PrevWordsInfo.WordInfo(prevWord));
+ final NgramContext ngramContext =
+ new NgramContext(new NgramContext.WordInfo(prevWord));
final TextInfo textInfo = textInfos[i];
- retval[i] = onGetSuggestionsInternal(textInfo, prevWordsInfo, suggestionsLimit);
+ retval[i] = onGetSuggestionsInternal(textInfo, ngramContext, suggestionsLimit);
retval[i].setCookieAndSequence(textInfo.getCookie(), textInfo.getSequence());
}
return retval;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index d668672aa..da5c71738 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -29,19 +29,19 @@ import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.LocaleUtils;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.SuggestionResults;
import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
public abstract class AndroidWordLevelSpellCheckerSession extends Session {
@@ -73,27 +73,25 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache =
new LruCache<>(MAX_CACHE_SIZE);
- // TODO: Support n-gram input
- private static String generateKey(final String query, final PrevWordsInfo prevWordsInfo) {
- if (TextUtils.isEmpty(query) || !prevWordsInfo.isValid()) {
+ private static String generateKey(final String query, final NgramContext ngramContext) {
+ if (TextUtils.isEmpty(query) || !ngramContext.isValid()) {
return query;
}
- return query + CHAR_DELIMITER + prevWordsInfo;
+ return query + CHAR_DELIMITER + ngramContext;
}
public SuggestionsParams getSuggestionsFromCache(String query,
- final PrevWordsInfo prevWordsInfo) {
- return mUnigramSuggestionsInfoCache.get(generateKey(query, prevWordsInfo));
+ final NgramContext ngramContext) {
+ return mUnigramSuggestionsInfoCache.get(generateKey(query, ngramContext));
}
- public void putSuggestionsToCache(
- final String query, final PrevWordsInfo prevWordsInfo,
+ public void putSuggestionsToCache(final String query, final NgramContext ngramContext,
final String[] suggestions, final int flags) {
if (suggestions == null || TextUtils.isEmpty(query)) {
return;
}
mUnigramSuggestionsInfoCache.put(
- generateKey(query, prevWordsInfo), new SuggestionsParams(suggestions, flags));
+ generateKey(query, ngramContext), new SuggestionsParams(suggestions, flags));
}
public void clearCache() {
@@ -117,7 +115,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
@Override
public void onCreate() {
final String localeString = getLocale();
- mLocale = LocaleUtils.constructLocaleFromString(localeString);
+ mLocale = (null == localeString) ? null
+ : LocaleUtils.constructLocaleFromString(localeString);
mScript = ScriptUtils.getScriptFromSpellCheckerLocale(mLocale);
}
@@ -223,12 +222,11 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
}
protected SuggestionsInfo onGetSuggestionsInternal(
- final TextInfo textInfo, final PrevWordsInfo prevWordsInfo,
- final int suggestionsLimit) {
+ final TextInfo textInfo, final NgramContext ngramContext, final int suggestionsLimit) {
try {
final String inText = textInfo.getText();
final SuggestionsParams cachedSuggestionsParams =
- mSuggestionsCache.getSuggestionsFromCache(inText, prevWordsInfo);
+ mSuggestionsCache.getSuggestionsFromCache(inText, ngramContext);
if (cachedSuggestionsParams != null) {
if (DBG) {
Log.d(TAG, "Cache hit: " + inText + ", " + cachedSuggestionsParams.mFlags);
@@ -262,31 +260,28 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
final String text = inText.replaceAll(
AndroidSpellCheckerService.APOSTROPHE, AndroidSpellCheckerService.SINGLE_QUOTE);
final int capitalizeType = StringUtils.getCapitalizationType(text);
- boolean isInDict = true;
if (!mService.hasMainDictionaryForLocale(mLocale)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
false /* reportAsTypo */);
}
final Keyboard keyboard = mService.getKeyboardForLocale(mLocale);
+ if (null == keyboard) {
+ Log.d(TAG, "No keyboard for locale: " + mLocale);
+ // If there is no keyboard for this locale, don't do any spell-checking.
+ return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
+ false /* reportAsTypo */);
+ }
final WordComposer composer = new WordComposer();
final int[] codePoints = StringUtils.toCodePointArray(text);
final int[] coordinates;
- final ProximityInfo proximityInfo;
- if (null == keyboard) {
- coordinates = CoordinateUtils.newCoordinateArray(codePoints.length,
- Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
- proximityInfo = null;
- } else {
- coordinates = keyboard.getCoordinates(codePoints);
- proximityInfo = keyboard.getProximityInfo();
- }
+ coordinates = keyboard.getCoordinates(codePoints);
composer.setComposingWord(codePoints, coordinates);
// TODO: Don't gather suggestions if the limit is <= 0 unless necessary
final SuggestionResults suggestionResults = mService.getSuggestionResults(
- mLocale, composer, prevWordsInfo, proximityInfo);
+ mLocale, composer.getComposedDataSnapshot(), ngramContext, keyboard);
final Result result = getResult(capitalizeType, mLocale, suggestionsLimit,
mService.getRecommendedThreshold(), text, suggestionResults);
- isInDict = isInDictForAnyCapitalization(text, capitalizeType);
+ final boolean isInDict = isInDictForAnyCapitalization(text, capitalizeType);
if (DBG) {
Log.i(TAG, "Spell checking results for " + text + " with suggestion limit "
+ suggestionsLimit);
@@ -299,6 +294,15 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
}
}
}
+ // Handle word not in dictionary.
+ // This is called only once per unique word, so entering multiple
+ // instances of the same word does not result in more than one call
+ // to this method.
+ // Also, upon changing the orientation of the device, this is called
+ // again for every unique invalid word in the text box.
+ if (!isInDict) {
+ StatsUtils.onInvalidWordIdentification(text);
+ }
final int flags =
(isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
@@ -308,26 +312,24 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
.getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS()
: 0);
final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions);
- mSuggestionsCache.putSuggestionsToCache(text, prevWordsInfo, result.mSuggestions,
+ mSuggestionsCache.putSuggestionsToCache(text, ngramContext, result.mSuggestions,
flags);
return retval;
} catch (RuntimeException e) {
// Don't kill the keyboard if there is a bug in the spell checker
if (DBG) {
throw e;
- } else {
- Log.e(TAG, "Exception while spellcheking", e);
- return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
- false /* reportAsTypo */);
}
+ Log.e(TAG, "Exception while spellcheking", e);
+ return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
+ false /* reportAsTypo */);
}
}
private static final class Result {
public final String[] mSuggestions;
public final boolean mHasRecommendedSuggestions;
- public Result(final String[] gatheredSuggestions,
- final boolean hasRecommendedSuggestions) {
+ public Result(final String[] gatheredSuggestions, final boolean hasRecommendedSuggestions) {
mSuggestions = gatheredSuggestions;
mHasRecommendedSuggestions = hasRecommendedSuggestions;
}
@@ -361,14 +363,15 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
StringUtils.removeDupes(suggestions);
// This returns a String[], while toArray() returns an Object[] which cannot be cast
// into a String[].
+ final List<String> gatheredSuggestionsList =
+ suggestions.subList(0, Math.min(suggestions.size(), suggestionsLimit));
final String[] gatheredSuggestions =
- suggestions.subList(0, Math.min(suggestions.size(), suggestionsLimit))
- .toArray(EMPTY_STRING_ARRAY);
+ gatheredSuggestionsList.toArray(new String[gatheredSuggestionsList.size()]);
final int bestScore = suggestionResults.first().mScore;
final String bestSuggestion = suggestions.get(0);
final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
- originalText, bestSuggestion.toString(), bestScore);
+ originalText, bestSuggestion, bestScore);
final boolean hasRecommendedSuggestions = (normalizedScore > recommendedThreshold);
if (DBG) {
Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
@@ -387,8 +390,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
* That's what the following method does.
*/
@Override
- public SuggestionsInfo onGetSuggestions(final TextInfo textInfo,
- final int suggestionsLimit) {
+ public SuggestionsInfo onGetSuggestions(final TextInfo textInfo, final int suggestionsLimit) {
long ident = Binder.clearCallingIdentity();
try {
return onGetSuggestionsInternal(textInfo, suggestionsLimit);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
index 51c4b1ee8..10c458c7d 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
@@ -16,13 +16,15 @@
package com.android.inputmethod.latin.spellcheck;
+import android.annotation.TargetApi;
import android.content.res.Resources;
+import android.os.Build;
import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.TextInfoCompatUtils;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.utils.RunInLocale;
@@ -76,19 +78,19 @@ public class SentenceLevelAdapter {
private static class WordIterator {
private final SpacingAndPunctuations mSpacingAndPunctuations;
public WordIterator(final Resources res, final Locale locale) {
- final RunInLocale<SpacingAndPunctuations> job
- = new RunInLocale<SpacingAndPunctuations>() {
+ final RunInLocale<SpacingAndPunctuations> job =
+ new RunInLocale<SpacingAndPunctuations>() {
@Override
- protected SpacingAndPunctuations job(final Resources res) {
- return new SpacingAndPunctuations(res);
+ protected SpacingAndPunctuations job(final Resources r) {
+ return new SpacingAndPunctuations(r);
}
};
mSpacingAndPunctuations = job.runInLocale(res, locale);
}
- public int getEndOfWord(final CharSequence sequence, int index) {
+ public int getEndOfWord(final CharSequence sequence, final int fromIndex) {
final int length = sequence.length();
- index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1);
+ int index = fromIndex < 0 ? 0 : Character.offsetByCodePoints(sequence, fromIndex, 1);
while (index < length) {
final int codePoint = Character.codePointAt(sequence, index);
if (mSpacingAndPunctuations.isWordSeparator(codePoint)) {
@@ -111,12 +113,12 @@ public class SentenceLevelAdapter {
return index;
}
- public int getBeginningOfNextWord(final CharSequence sequence, int index) {
+ public int getBeginningOfNextWord(final CharSequence sequence, final int fromIndex) {
final int length = sequence.length();
- if (index >= length) {
+ if (fromIndex >= length) {
return -1;
}
- index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1);
+ int index = fromIndex < 0 ? 0 : Character.offsetByCodePoints(sequence, fromIndex, 1);
while (index < length) {
final int codePoint = Character.codePointAt(sequence, index);
if (!mSpacingAndPunctuations.isWordSeparator(codePoint)) {
@@ -140,14 +142,13 @@ public class SentenceLevelAdapter {
final int cookie = originalTextInfo.getCookie();
final int start = -1;
final int end = originalText.length();
- final ArrayList<SentenceWordItem> wordItems = new ArrayList<SentenceWordItem>();
+ final ArrayList<SentenceWordItem> wordItems = new ArrayList<>();
int wordStart = wordIterator.getBeginningOfNextWord(originalText, start);
int wordEnd = wordIterator.getEndOfWord(originalText, wordStart);
while (wordStart <= end && wordEnd != -1 && wordStart != -1) {
if (wordEnd >= start && wordEnd > wordStart) {
- CharSequence subSequence = originalText.subSequence(wordStart, wordEnd).toString();
- final TextInfo ti = TextInfoCompatUtils.newInstance(subSequence, 0,
- subSequence.length(), cookie, subSequence.hashCode());
+ final TextInfo ti = TextInfoCompatUtils.newInstance(originalText, wordStart,
+ wordEnd, cookie, originalText.subSequence(wordStart, wordEnd).hashCode());
wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd));
}
wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd);
@@ -159,6 +160,7 @@ public class SentenceLevelAdapter {
return new SentenceTextInfoParams(originalTextInfo, wordItems);
}
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public static SentenceSuggestionsInfo reconstructSuggestions(
SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) {
if (results == null || results.length == 0) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
index df9a76119..294666b8b 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
@@ -18,7 +18,9 @@ package com.android.inputmethod.latin.spellcheck;
import com.android.inputmethod.latin.utils.FragmentUtils;
+import android.annotation.TargetApi;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceActivity;
@@ -41,8 +43,8 @@ public final class SpellCheckerSettingsActivity extends PreferenceActivity {
return modIntent;
}
- // TODO: Uncomment the override annotation once we start using SDK version 19.
- // @Override
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ @Override
public boolean isValidFragment(String fragmentName) {
return FragmentUtils.isValidFragment(fragmentName);
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java b/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java
new file mode 100644
index 000000000..f2491f478
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2015 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.spellcheck;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.UserDictionary;
+import android.util.Log;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.common.LocaleUtils;
+import com.android.inputmethod.latin.utils.ExecutorUtils;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * UserDictionaryLookup provides the ability to lookup into the system-wide "Personal dictionary".
+ *
+ * Note, that the initial dictionary loading happens asynchronously so it is possible (hopefully
+ * rarely) that isValidWord is called before the initial load has started.
+ *
+ * The caller should explicitly call close() when the object is no longer needed, in order to
+ * release any resources and references to this object. A service should create this object in
+ * onCreate and close() it in onDestroy.
+ */
+public class UserDictionaryLookup implements Closeable {
+ private static final String TAG = UserDictionaryLookup.class.getSimpleName();
+
+ /**
+ * This guards the execution of any Log.d() logging, so that if false, they are not even
+ */
+ private static final boolean DEBUG = false;
+
+ /**
+ * To avoid loading too many dictionary entries in memory, we cap them at this number. If
+ * that number is exceeded, the lowest-frequency items will be dropped. Note, there is no
+ * explicit cap on the number of locales in every entry.
+ */
+ private static final int MAX_NUM_ENTRIES = 1000;
+
+ /**
+ * The delay (in milliseconds) to impose on reloads. Previously scheduled reloads will be
+ * cancelled if a new reload is scheduled before the delay expires. Thus, only the last
+ * reload in the series of frequent reloads will execute.
+ *
+ * Note, this value should be low enough to allow the "Add to dictionary" feature in the
+ * TextView correction (red underline) drop-down menu to work properly in the following case:
+ *
+ * 1. User types OOV (out-of-vocabulary) word.
+ * 2. The OOV is red-underlined.
+ * 3. User selects "Add to dictionary". The red underline disappears while the OOV is
+ * in a composing span.
+ * 4. The user taps space. The red underline should NOT reappear. If this value is very
+ * high and the user performs the space tap fast enough, the red underline may reappear.
+ */
+ @UsedForTesting
+ static final int RELOAD_DELAY_MS = 200;
+
+ private final ContentResolver mResolver;
+
+ /**
+ * Runnable that calls loadUserDictionary().
+ */
+ private class UserDictionaryLoader implements Runnable {
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "Executing (re)load");
+ }
+ loadUserDictionary();
+ }
+ }
+ private final UserDictionaryLoader mLoader = new UserDictionaryLoader();
+
+ /**
+ * Content observer for UserDictionary changes. It has the following properties:
+ * 1. It spawns off a UserDictionary reload in another thread, after some delay.
+ * 2. It cancels previously scheduled reloads, and only executes the latest.
+ * 3. It may be called multiple times quickly in succession (and is in fact called so
+ * when UserDictionary is edited through its settings UI, when sometimes multiple
+ * notifications are sent for the edited entry, but also for the entire UserDictionary).
+ */
+ private class UserDictionaryContentObserver extends ContentObserver {
+ public UserDictionaryContentObserver() {
+ super(null);
+ }
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+
+ // Support pre-API16 platforms.
+ @Override
+ public void onChange(boolean selfChange) {
+ onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (DEBUG) {
+ Log.d(TAG, "Received content observer onChange notification for URI: " + uri);
+ }
+ // Cancel (but don't interrupt) any pending reloads (except the initial load).
+ if (mReloadFuture != null && !mReloadFuture.isCancelled() &&
+ !mReloadFuture.isDone()) {
+ // Note, that if already cancelled or done, this will do nothing.
+ boolean isCancelled = mReloadFuture.cancel(false);
+ if (DEBUG) {
+ if (isCancelled) {
+ Log.d(TAG, "Successfully canceled previous reload request");
+ } else {
+ Log.d(TAG, "Unable to cancel previous reload request");
+ }
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Scheduling reload in " + RELOAD_DELAY_MS + " ms");
+ }
+
+ // Schedule a new reload after RELOAD_DELAY_MS.
+ mReloadFuture = ExecutorUtils.getBackgroundExecutor(ExecutorUtils.SPELLING)
+ .schedule(mLoader, RELOAD_DELAY_MS, TimeUnit.MILLISECONDS);
+ }
+ }
+ private final ContentObserver mObserver = new UserDictionaryContentObserver();
+
+ /**
+ * Indicates that a load is in progress, so no need for another.
+ */
+ private AtomicBoolean mIsLoading = new AtomicBoolean(false);
+
+ /**
+ * Indicates that this lookup object has been close()d.
+ */
+ private AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+ /**
+ * We store a map from a dictionary word to the set of locales it belongs
+ * in. We then iterate over the set of locales to find a match using
+ * LocaleUtils.
+ */
+ private volatile HashMap<String, ArrayList<Locale>> mDictWords;
+
+ /**
+ * The last-scheduled reload future. Saved in order to cancel a pending reload if a new one
+ * is coming.
+ */
+ private volatile ScheduledFuture<?> mReloadFuture;
+
+ /**
+ * @param context the context from which to obtain content resolver
+ */
+ public UserDictionaryLookup(Context context) {
+ if (DEBUG) {
+ Log.d(TAG, "UserDictionaryLookup constructor with context: " + context);
+ }
+
+ // Obtain a content resolver.
+ mResolver = context.getContentResolver();
+
+ // Schedule the initial load to run immediately. It's possible that the first call to
+ // isValidWord occurs before the dictionary has actually loaded, so it should not
+ // assume that the dictionary has been loaded.
+ ExecutorUtils.getBackgroundExecutor(ExecutorUtils.SPELLING).execute(mLoader);
+
+ // Register the observer to be notified on changes to the UserDictionary and all individual
+ // items.
+ //
+ // If the user is interacting with the UserDictionary settings UI, or with the
+ // "Add to dictionary" drop-down option, duplicate notifications will be sent for the same
+ // edit: if a new entry is added, there is a notification for the entry itself, and
+ // separately for the entire dictionary. However, when used programmatically,
+ // only notifications for the specific edits are sent. Thus, the observer is registered to
+ // receive every possible notification, and instead has throttling logic to avoid doing too
+ // many reloads.
+ mResolver.registerContentObserver(
+ UserDictionary.Words.CONTENT_URI, true /* notifyForDescendents */, mObserver);
+ }
+
+ /**
+ * To be called by the garbage collector in the off chance that the service did not clean up
+ * properly. Do not rely on this getting called, and make sure close() is called explicitly.
+ */
+ @Override
+ public void finalize() throws Throwable {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "Finalize called, calling close()");
+ }
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Cleans up UserDictionaryLookup: shuts down any extra threads and unregisters the observer.
+ *
+ * It is safe, but not advised to call this multiple times, and isValidWord would continue to
+ * work, but no data will be reloaded any longer.
+ */
+ @Override
+ public void close() {
+ if (DEBUG) {
+ Log.d(TAG, "Close called (no pun intended), cleaning up executor and observer");
+ }
+ if (mIsClosed.compareAndSet(false, true)) {
+ // Unregister the content observer.
+ mResolver.unregisterContentObserver(mObserver);
+ }
+ }
+
+ /**
+ * Returns true if the initial load has been performed.
+ *
+ * @return true if the initial load is successful
+ */
+ @UsedForTesting
+ boolean isLoaded() {
+ return mDictWords != null;
+ }
+
+ /**
+ * Determines if the given word is a valid word in the given locale based on the UserDictionary.
+ * It tries hard to find a match: for example, casing is ignored and if the word is present in a
+ * more general locale (e.g. en or all locales), and isValidWord is asking for a more specific
+ * locale (e.g. en_US), it will be considered a match.
+ *
+ * @param word the word to match
+ * @param locale the locale in which to match the word
+ * @return true iff the word has been matched for this locale in the UserDictionary.
+ */
+ public boolean isValidWord(
+ final String word, final Locale locale) {
+ if (!isLoaded()) {
+ // This is a corner case in the event the initial load of UserDictionary has not
+ // been loaded. In that case, we assume the word is not a valid word in
+ // UserDictionary.
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord invoked, but initial load not complete");
+ }
+ return false;
+ }
+
+ // Atomically obtain the current copy of mDictWords;
+ final HashMap<String, ArrayList<Locale>> dictWords = mDictWords;
+
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord invoked for word [" + word +
+ "] in locale " + locale);
+ }
+ // Lowercase the word using the given locale. Note, that dictionary
+ // words are lowercased using their locale, and theoretically the
+ // lowercasing between two matching locales may differ. For simplicity
+ // we ignore that possibility.
+ final String lowercased = word.toLowerCase(locale);
+ final ArrayList<Locale> dictLocales = dictWords.get(lowercased);
+ if (null == dictLocales) {
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord=false, since there is no entry for " +
+ "lowercased word [" + lowercased + "]");
+ }
+ return false;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord found an entry for lowercased word [" + lowercased +
+ "]; examining locales");
+ }
+ // Iterate over the locales this word is in.
+ for (final Locale dictLocale : dictLocales) {
+ final int matchLevel = LocaleUtils.getMatchLevel(dictLocale.toString(),
+ locale.toString());
+ if (DEBUG) {
+ Log.d(TAG, "matchLevel for dictLocale=" + dictLocale + ", locale=" +
+ locale + " is " + matchLevel);
+ }
+ if (LocaleUtils.isMatch(matchLevel)) {
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord=true, since matchLevel " + matchLevel +
+ " is a match");
+ }
+ return true;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "matchLevel " + matchLevel + " is not a match");
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord=false, since none of the locales matched");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Loads the UserDictionary in the current thread.
+ *
+ * Only one reload can happen at a time. If already running, will exit quickly.
+ */
+ private void loadUserDictionary() {
+ // Bail out if already in the process of loading.
+ if (!mIsLoading.compareAndSet(false, true)) {
+ if (DEBUG) {
+ Log.d(TAG, "Already in the process of loading UserDictionary, skipping");
+ }
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Loading UserDictionary");
+ }
+ HashMap<String, ArrayList<Locale>> dictWords = new HashMap<>();
+ // Load the UserDictionary. Request that items be returned in the default sort order
+ // for UserDictionary, which is by frequency.
+ Cursor cursor = mResolver.query(UserDictionary.Words.CONTENT_URI,
+ null, null, null, UserDictionary.Words.DEFAULT_SORT_ORDER);
+ if (null == cursor || cursor.getCount() < 1) {
+ if (DEBUG) {
+ Log.d(TAG, "No entries found in UserDictionary");
+ }
+ } else {
+ // Iterate over the entries in the UserDictionary. Note, that iteration is in
+ // descending frequency by default.
+ while (dictWords.size() < MAX_NUM_ENTRIES && cursor.moveToNext()) {
+ // If there is no column for locale, skip this entry. An empty
+ // locale on the other hand will not be skipped.
+ final int dictLocaleIndex = cursor.getColumnIndex(
+ UserDictionary.Words.LOCALE);
+ if (dictLocaleIndex < 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Encountered UserDictionary entry " +
+ "without LOCALE, skipping");
+ }
+ continue;
+ }
+ // If there is no column for word, skip this entry.
+ final int dictWordIndex = cursor.getColumnIndex(
+ UserDictionary.Words.WORD);
+ if (dictWordIndex < 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Encountered UserDictionary entry without " +
+ "WORD, skipping");
+ }
+ continue;
+ }
+ // If the word is null, skip this entry.
+ final String rawDictWord = cursor.getString(dictWordIndex);
+ if (null == rawDictWord) {
+ if (DEBUG) {
+ Log.d(TAG, "Encountered null word");
+ }
+ continue;
+ }
+ // If the locale is null, that's interpreted to mean all locales. Note, the special
+ // zz locale for an Alphabet (QWERTY) layout will not match any actual language.
+ String localeString = cursor.getString(dictLocaleIndex);
+ if (null == localeString) {
+ if (DEBUG) {
+ Log.d(TAG, "Encountered null locale for word [" +
+ rawDictWord + "], assuming all locales");
+ }
+ // For purposes of LocaleUtils, an empty locale matches
+ // everything.
+ localeString = "";
+ }
+ final Locale dictLocale = LocaleUtils.constructLocaleFromString(
+ localeString);
+ // Lowercase the word before storing it.
+ final String dictWord = rawDictWord.toLowerCase(dictLocale);
+ if (DEBUG) {
+ Log.d(TAG, "Incorporating UserDictionary word [" + dictWord +
+ "] for locale " + dictLocale);
+ }
+ // Check if there is an existing entry for this word.
+ ArrayList<Locale> dictLocales = dictWords.get(dictWord);
+ if (null == dictLocales) {
+ // If there is no entry for this word, create one.
+ if (DEBUG) {
+ Log.d(TAG, "Word [" + dictWord +
+ "] not seen for other locales, creating new entry");
+ }
+ dictLocales = new ArrayList<>();
+ dictWords.put(dictWord, dictLocales);
+ }
+ // Append the locale to the list of locales this word is in.
+ dictLocales.add(dictLocale);
+ }
+ }
+
+ // Atomically replace the copy of mDictWords.
+ mDictWords = dictWords;
+
+ // Allow other calls to loadUserDictionary to execute now.
+ mIsLoading.set(false);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 9d186d44d..37ab2669b 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -26,9 +26,9 @@ import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
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.R;
import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.utils.TypefaceUtils;
public final class MoreSuggestions extends Keyboard {
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index f7b6f919d..907e3fa42 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -40,6 +40,8 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
public abstract void onSuggestionSelected(final SuggestedWordInfo info);
}
+ private boolean mIsInModalMode;
+
public MoreSuggestionsView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.moreKeysKeyboardViewStyle);
}
@@ -53,6 +55,7 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
@Override
public void setKeyboard(final Keyboard keyboard) {
super.setKeyboard(keyboard);
+ mIsInModalMode = false;
// With accessibility mode off, {@link #mAccessibilityDelegate} is set to null at the
// above {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call.
// With accessibility mode on, {@link #mAccessibilityDelegate} is set to a
@@ -74,12 +77,17 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
updateKeyDrawParams(keyHeight);
}
- public void adjustVerticalCorrectionForModalMode() {
+ public void setModalMode() {
+ mIsInModalMode = true;
// Set vertical correction to zero (Reset more keys keyboard sliding allowance
// {@link R#dimen.config_more_keys_keyboard_slide_allowance}).
mKeyDetector.setKeyboard(getKeyboard(), -getPaddingLeft(), -getPaddingTop());
}
+ public boolean isInModalMode() {
+ return mIsInModalMode;
+ }
+
@Override
protected void onKeyInput(final Key key, final int x, final int y) {
if (!(key instanceof MoreSuggestionKey)) {
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 1e8df8986..d8926ffba 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -28,7 +28,6 @@ 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;
@@ -50,16 +49,16 @@ import com.android.inputmethod.latin.PunctuationSuggestions;
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.DebugFlags;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsValues;
-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;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
final class SuggestionStripLayoutHelper {
private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
@@ -94,8 +93,6 @@ final class SuggestionStripLayoutHelper {
private final int mTypedWordPositionWhenAutocorrect;
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();
@@ -213,15 +210,14 @@ final class SuggestionStripLayoutHelper {
return word;
}
- final int len = word.length();
final Spannable spannedWord = new SpannableString(word);
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);
+ addStyleSpan(spannedWord, BOLD_SPAN);
}
if (isAutoCorrection && (options & AUTO_CORRECT_UNDERLINE) != 0) {
- spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ addStyleSpan(spannedWord, UNDERLINE_SPAN);
}
return spannedWord;
}
@@ -238,9 +234,9 @@ final class SuggestionStripLayoutHelper {
final SettingsValues settingsValues = Settings.getInstance().getCurrent();
final boolean shouldOmitTypedWord = shouldOmitTypedWord(suggestedWords.mInputStyle,
settingsValues.mGestureFloatingPreviewTextEnabled,
- settingsValues.mShouldShowUiToAcceptTypedWord);
+ settingsValues.mShouldShowLxxSuggestionUi);
return getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords.mWillAutoCorrect,
- settingsValues.mShouldShowUiToAcceptTypedWord && shouldOmitTypedWord,
+ settingsValues.mShouldShowLxxSuggestionUi && shouldOmitTypedWord,
mCenterPositionInStrip, mTypedWordPositionWhenAutocorrect);
}
@@ -319,18 +315,6 @@ final class SuggestionStripLayoutHelper {
} else {
color = mColorSuggested;
}
- if (DebugFlags.DEBUG_ENABLED && suggestedWords.size() > 1) {
- // If we auto-correct, then the autocorrection is in slot 0 and the typed word
- // is in slot 1.
- if (indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION
- && suggestedWords.mWillAutoCorrect
- && AutoCorrectionUtils.shouldBlockAutoCorrectionBySafetyNet(
- suggestedWords.getLabel(SuggestedWords.INDEX_OF_AUTO_CORRECTION),
- suggestedWords.getLabel(SuggestedWords.INDEX_OF_TYPED_WORD))) {
- return 0xFFFF0000;
- }
- }
-
if (suggestedWords.mIsObsoleteSuggestions && !isTypedWord) {
return applyAlpha(color, mAlphaObsoleted);
}
@@ -358,25 +342,30 @@ final class SuggestionStripLayoutHelper {
* @param placerView the view where the debug info will be placed.
* @return the start index of more suggestions.
*/
- public int layoutAndReturnStartIndexOfMoreSuggestions(final SuggestedWords suggestedWords,
- final ViewGroup stripView, final ViewGroup placerView) {
+ public int layoutAndReturnStartIndexOfMoreSuggestions(
+ final Context context,
+ final SuggestedWords suggestedWords,
+ final ViewGroup stripView,
+ final ViewGroup placerView) {
if (suggestedWords.isPunctuationSuggestions()) {
return layoutPunctuationsAndReturnStartIndexOfMoreSuggestions(
(PunctuationSuggestions)suggestedWords, stripView);
}
+ final int wordCountToShow = suggestedWords.getWordCountToShow(
+ Settings.getInstance().getCurrent().mShouldShowLxxSuggestionUi);
final int startIndexOfMoreSuggestions = setupWordViewsAndReturnStartIndexOfMoreSuggestions(
suggestedWords, mSuggestionsCountInStrip);
final TextView centerWordView = mWordViews.get(mCenterPositionInStrip);
final int stripWidth = stripView.getWidth();
final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth);
- if (suggestedWords.size() == 1 || getTextScaleX(centerWordView.getText(), centerWidth,
+ if (wordCountToShow == 1 || getTextScaleX(centerWordView.getText(), centerWidth,
centerWordView.getPaint()) < MIN_TEXT_XSCALE) {
// Layout only the most relevant suggested word at the center of the suggestion strip
// by consolidating all slots in the strip.
final int countInStrip = 1;
- mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
- layoutWord(mCenterPositionInStrip, stripWidth - mPadding);
+ mMoreSuggestionsAvailable = (wordCountToShow > countInStrip);
+ layoutWord(context, mCenterPositionInStrip, stripWidth - mPadding);
stripView.addView(centerWordView);
setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT);
if (SuggestionStripView.DBG) {
@@ -387,7 +376,8 @@ final class SuggestionStripLayoutHelper {
}
final int countInStrip = mSuggestionsCountInStrip;
- mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
+ mMoreSuggestionsAvailable = (wordCountToShow > countInStrip);
+ @SuppressWarnings("unused")
int x = 0;
for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
if (positionInStrip != 0) {
@@ -398,7 +388,7 @@ final class SuggestionStripLayoutHelper {
}
final int width = getSuggestionWidth(positionInStrip, stripWidth);
- final TextView wordView = layoutWord(positionInStrip, width);
+ final TextView wordView = layoutWord(context, positionInStrip, width);
stripView.addView(wordView);
setLayoutWeight(wordView, getSuggestionWeight(positionInStrip),
ViewGroup.LayoutParams.MATCH_PARENT);
@@ -427,7 +417,7 @@ final class SuggestionStripLayoutHelper {
* @param width the maximum width for layout in pixels.
* @return the {@link TextView} containing the suggested word appropriately formatted.
*/
- private TextView layoutWord(final int positionInStrip, final int width) {
+ private TextView layoutWord(final Context context, final int positionInStrip, final int width) {
final TextView wordView = mWordViews.get(positionInStrip);
final CharSequence word = wordView.getText();
if (positionInStrip == mCenterPositionInStrip && mMoreSuggestionsAvailable) {
@@ -441,11 +431,15 @@ final class SuggestionStripLayoutHelper {
}
// {@link StyleSpan} in a content description may cause an issue of TTS/TalkBack.
// Use a simple {@link String} to avoid the issue.
- wordView.setContentDescription(TextUtils.isEmpty(word) ? null : word.toString());
- final CharSequence text = getEllipsizedText(word, width, wordView.getPaint());
- final float scaleX = getTextScaleX(word, width, wordView.getPaint());
+ wordView.setContentDescription(
+ TextUtils.isEmpty(word)
+ ? context.getResources().getString(R.string.spoken_empty_suggestion)
+ : word.toString());
+ final CharSequence text = getEllipsizedTextWithSettingScaleX(
+ word, width, wordView.getPaint());
+ final float scaleX = wordView.getTextScaleX();
wordView.setText(text); // TextView.setText() resets text scale x to 1.0.
- wordView.setTextScaleX(Math.max(scaleX, MIN_TEXT_XSCALE));
+ wordView.setTextScaleX(scaleX);
// A <code>wordView</code> should be disabled when <code>word</code> is empty in order to
// make it unclickable.
// With accessibility touch exploration on, <code>wordView</code> should be enabled even
@@ -548,55 +542,6 @@ final class SuggestionStripLayoutHelper {
return countInStrip;
}
- public void layoutAddToDictionaryHint(final String word, final ViewGroup addToDictionaryStrip) {
- final boolean shouldShowUiToAcceptTypedWord = Settings.getInstance().getCurrent()
- .mShouldShowUiToAcceptTypedWord;
- final int stripWidth = addToDictionaryStrip.getWidth();
- final int width = shouldShowUiToAcceptTypedWord ? stripWidth
- : stripWidth - mDividerWidth - mPadding * 2;
-
- final TextView wordView = (TextView)addToDictionaryStrip.findViewById(R.id.word_to_save);
- wordView.setTextColor(mColorTypedWord);
- final int wordWidth = (int)(width * mCenterSuggestionWeight);
- final CharSequence wordToSave = getEllipsizedText(word, wordWidth, wordView.getPaint());
- final float wordScaleX = wordView.getTextScaleX();
- wordView.setText(wordToSave);
- wordView.setTextScaleX(wordScaleX);
- setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
- final int wordVisibility = shouldShowUiToAcceptTypedWord ? View.GONE : View.VISIBLE;
- wordView.setVisibility(wordVisibility);
- addToDictionaryStrip.findViewById(R.id.word_to_save_divider).setVisibility(wordVisibility);
-
- final Resources res = addToDictionaryStrip.getResources();
- final CharSequence hintText;
- final int hintWidth;
- final float hintWeight;
- final TextView hintView = (TextView)addToDictionaryStrip.findViewById(
- R.id.hint_add_to_dictionary);
- if (shouldShowUiToAcceptTypedWord) {
- hintText = res.getText(R.string.hint_add_to_dictionary_without_word);
- hintWidth = width;
- hintWeight = 1.0f;
- hintView.setGravity(Gravity.CENTER);
- } else {
- final boolean isRtlLanguage = (ViewCompat.getLayoutDirection(addToDictionaryStrip)
- == ViewCompat.LAYOUT_DIRECTION_RTL);
- final String arrow = isRtlLanguage ? RIGHTWARDS_ARROW : LEFTWARDS_ARROW;
- final boolean isRtlSystem = SubtypeLocaleUtils.isRtlLanguage(
- res.getConfiguration().locale);
- final CharSequence hint = res.getText(R.string.hint_add_to_dictionary);
- hintText = (isRtlLanguage == isRtlSystem) ? (arrow + hint) : (hint + arrow);
- hintWidth = width - wordWidth;
- hintWeight = 1.0f - mCenterSuggestionWeight;
- hintView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
- }
- hintView.setTextColor(mColorAutoCorrect);
- final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
- hintView.setText(hintText);
- hintView.setTextScaleX(hintScaleX);
- setLayoutWeight(hintView, hintWeight, ViewGroup.LayoutParams.MATCH_PARENT);
- }
-
public void layoutImportantNotice(final View importantNoticeStrip,
final String importantNoticeTitle) {
final TextView titleView = (TextView)importantNoticeStrip.findViewById(
@@ -604,8 +549,7 @@ final class SuggestionStripLayoutHelper {
final int width = titleView.getWidth() - titleView.getPaddingLeft()
- titleView.getPaddingRight();
titleView.setTextColor(mColorAutoCorrect);
- titleView.setText(importantNoticeTitle);
- titleView.setTextScaleX(1.0f); // Reset textScaleX.
+ titleView.setText(importantNoticeTitle); // TextView.setText() resets text scale x to 1.0.
final float titleScaleX = getTextScaleX(importantNoticeTitle, width, titleView.getPaint());
titleView.setTextScaleX(titleScaleX);
}
@@ -620,18 +564,19 @@ final class SuggestionStripLayoutHelper {
}
}
- private static float getTextScaleX(final CharSequence text, final int maxWidth,
+ private static float getTextScaleX(@Nullable final CharSequence text, final int maxWidth,
final TextPaint paint) {
paint.setTextScaleX(1.0f);
final int width = getTextWidth(text, paint);
if (width <= maxWidth || maxWidth <= 0) {
return 1.0f;
}
- return maxWidth / (float)width;
+ return maxWidth / (float) width;
}
- private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth,
- final TextPaint paint) {
+ @Nullable
+ private static CharSequence getEllipsizedTextWithSettingScaleX(
+ @Nullable final CharSequence text, final int maxWidth, @Nonnull final TextPaint paint) {
if (text == null) {
return null;
}
@@ -641,62 +586,63 @@ final class SuggestionStripLayoutHelper {
return text;
}
- // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To
- // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
- final float upscaledWidth = maxWidth / MIN_TEXT_XSCALE;
- CharSequence ellipsized = TextUtils.ellipsize(
- text, paint, upscaledWidth, TextUtils.TruncateAt.MIDDLE);
- // For an unknown reason, ellipsized seems to return a text that does indeed fit inside the
- // passed width according to paint.measureText, but not according to paint.getTextWidths.
- // But when rendered, the text seems to actually take up as many pixels as returned by
- // paint.getTextWidths, hence problem.
- // To save this case, we compare the measured size of the new text, and if it's too much,
- // try it again removing the difference. This may still give a text too long by one or
- // two pixels so we take an additional 2 pixels cushion and call it a day.
- // TODO: figure out why getTextWidths and measureText don't agree with each other, and
- // remove the following code.
- final float ellipsizedTextWidth = getTextWidth(ellipsized, paint);
- if (upscaledWidth <= ellipsizedTextWidth) {
- ellipsized = TextUtils.ellipsize(
- text, paint, upscaledWidth - (ellipsizedTextWidth - upscaledWidth) - 2,
- TextUtils.TruncateAt.MIDDLE);
- }
+ // <code>text</code> must be ellipsized with minimum text scale x.
paint.setTextScaleX(MIN_TEXT_XSCALE);
- return ellipsized;
+ final boolean hasBoldStyle = hasStyleSpan(text, BOLD_SPAN);
+ final boolean hasUnderlineStyle = hasStyleSpan(text, UNDERLINE_SPAN);
+ // TextUtils.ellipsize erases any span object existed after ellipsized point.
+ // We have to restore these spans afterward.
+ final CharSequence ellipsizedText = TextUtils.ellipsize(
+ text, paint, maxWidth, TextUtils.TruncateAt.MIDDLE);
+ if (!hasBoldStyle && !hasUnderlineStyle) {
+ return ellipsizedText;
+ }
+ final Spannable spannableText = (ellipsizedText instanceof Spannable)
+ ? (Spannable)ellipsizedText : new SpannableString(ellipsizedText);
+ if (hasBoldStyle) {
+ addStyleSpan(spannableText, BOLD_SPAN);
+ }
+ if (hasUnderlineStyle) {
+ addStyleSpan(spannableText, UNDERLINE_SPAN);
+ }
+ return spannableText;
}
- private static int getTextWidth(final CharSequence text, final TextPaint paint) {
+ private static boolean hasStyleSpan(@Nullable final CharSequence text,
+ final CharacterStyle style) {
+ if (text instanceof Spanned) {
+ return ((Spanned)text).getSpanStart(style) >= 0;
+ }
+ return false;
+ }
+
+ private static void addStyleSpan(@Nonnull final Spannable text, final CharacterStyle style) {
+ text.removeSpan(style);
+ text.setSpan(style, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ }
+
+ private static int getTextWidth(@Nullable final CharSequence text, final TextPaint paint) {
if (TextUtils.isEmpty(text)) {
return 0;
}
+ final int length = text.length();
+ final float[] widths = new float[length];
+ final int count;
final Typeface savedTypeface = paint.getTypeface();
- paint.setTypeface(getTextTypeface(text));
- final int len = text.length();
- final float[] widths = new float[len];
- final int count = paint.getTextWidths(text, 0, len, widths);
+ try {
+ paint.setTypeface(getTextTypeface(text));
+ count = paint.getTextWidths(text, 0, length, widths);
+ } finally {
+ paint.setTypeface(savedTypeface);
+ }
int width = 0;
for (int i = 0; i < count; i++) {
width += Math.round(widths[i] + 0.5f);
}
- paint.setTypeface(savedTypeface);
return width;
}
- private static Typeface getTextTypeface(final CharSequence text) {
- if (!(text instanceof SpannableString)) {
- return Typeface.DEFAULT;
- }
-
- final SpannableString ss = (SpannableString)text;
- final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
- if (styles.length == 0) {
- return Typeface.DEFAULT;
- }
-
- if (styles[0].getStyle() == Typeface.BOLD) {
- return Typeface.DEFAULT_BOLD;
- }
- // TODO: BOLD_ITALIC, ITALIC case?
- return Typeface.DEFAULT;
+ private static Typeface getTextTypeface(@Nullable final CharSequence text) {
+ return hasStyleSpan(text, BOLD_SPAN) ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT;
}
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 0fd5e139e..7dd0f03df 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -43,10 +43,10 @@ import com.android.inputmethod.keyboard.Keyboard;
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.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.define.DebugFlags;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsValues;
@@ -58,7 +58,6 @@ import java.util.ArrayList;
public final class SuggestionStripView extends RelativeLayout implements OnClickListener,
OnLongClickListener {
public interface Listener {
- public void addWordToUserDictionary(String word);
public void showImportantNoticeContents();
public void pickSuggestionManually(SuggestedWordInfo word);
public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat);
@@ -69,7 +68,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private final ViewGroup mSuggestionsStrip;
private final ImageButton mVoiceKey;
- private final ViewGroup mAddToDictionaryStrip;
private final View mImportantNoticeStrip;
MainKeyboardView mMainKeyboardView;
@@ -82,7 +80,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private final ArrayList<View> mDividerViews = new ArrayList<>();
Listener mListener;
- private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+ private SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance();
private int mStartIndexOfMoreSuggestions;
private final SuggestionStripLayoutHelper mLayoutHelper;
@@ -91,15 +89,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private static class StripVisibilityGroup {
private final View mSuggestionStripView;
private final View mSuggestionsStrip;
- private final View mAddToDictionaryStrip;
private final View mImportantNoticeStrip;
public StripVisibilityGroup(final View suggestionStripView,
- final ViewGroup suggestionsStrip, final ViewGroup addToDictionaryStrip,
- final View importantNoticeStrip) {
+ final ViewGroup suggestionsStrip, final View importantNoticeStrip) {
mSuggestionStripView = suggestionStripView;
mSuggestionsStrip = suggestionsStrip;
- mAddToDictionaryStrip = addToDictionaryStrip;
mImportantNoticeStrip = importantNoticeStrip;
showSuggestionsStrip();
}
@@ -109,30 +104,21 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
: ViewCompat.LAYOUT_DIRECTION_LTR;
ViewCompat.setLayoutDirection(mSuggestionStripView, layoutDirection);
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;
+ public boolean isShowingImportantNoticeStrip() {
+ return mImportantNoticeStrip.getVisibility() == VISIBLE;
}
}
@@ -154,13 +140,13 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip);
mVoiceKey = (ImageButton)findViewById(R.id.suggestions_strip_voice_key);
- mAddToDictionaryStrip = (ViewGroup)findViewById(R.id.add_to_dictionary_strip);
mImportantNoticeStrip = findViewById(R.id.important_notice_strip);
mStripVisibilityGroup = new StripVisibilityGroup(this, mSuggestionsStrip,
- mAddToDictionaryStrip, mImportantNoticeStrip);
+ mImportantNoticeStrip);
for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) {
final TextView word = new TextView(context, null, R.attr.suggestionWordStyle);
+ word.setContentDescription(getResources().getString(R.string.spoken_empty_suggestion));
word.setOnClickListener(this);
word.setOnLongClickListener(this);
mWordViews.add(word);
@@ -215,7 +201,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mStripVisibilityGroup.setLayoutDirection(isRtlLanguage);
mSuggestedWords = suggestedWords;
mStartIndexOfMoreSuggestions = mLayoutHelper.layoutAndReturnStartIndexOfMoreSuggestions(
- mSuggestedWords, mSuggestionsStrip, this);
+ getContext(), mSuggestedWords, mSuggestionsStrip, this);
mStripVisibilityGroup.showSuggestionsStrip();
}
@@ -223,32 +209,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mLayoutHelper.setMoreSuggestionsHeight(remainingHeight);
}
- public boolean isShowingAddToDictionaryHint() {
- return mStripVisibilityGroup.isShowingAddToDictionaryStrip();
- }
-
- public void showAddToDictionaryHint(final String word) {
- mLayoutHelper.layoutAddToDictionaryHint(word, mAddToDictionaryStrip);
- // {@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() {
- if (isShowingAddToDictionaryHint()) {
- clear();
- return true;
- }
- 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() {
- if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext())) {
+ final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent();
+ if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext(), currentSettingsValues)) {
return false;
}
if (getWidth() <= 0) {
@@ -340,12 +306,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
if (mSuggestedWords.size() <= mStartIndexOfMoreSuggestions) {
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();
@@ -393,11 +353,18 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
public boolean onInterceptTouchEvent(final MotionEvent me) {
+ if (mStripVisibilityGroup.isShowingImportantNoticeStrip()) {
+ return false;
+ }
+ // Detecting sliding up finger to show {@link MoreSuggestionsView}.
if (!mMoreSuggestionsView.isShowingInParent()) {
mLastX = (int)me.getX();
mLastY = (int)me.getY();
return mMoreSuggestionsSlidingDetector.onTouchEvent(me);
}
+ if (mMoreSuggestionsView.isInModalMode()) {
+ return false;
+ }
final int action = me.getAction();
final int index = me.getActionIndex();
@@ -416,7 +383,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
// Decided to be in the modal input mode.
- mMoreSuggestionsView.adjustVerticalCorrectionForModalMode();
+ mMoreSuggestionsView.setModalMode();
}
return false;
}
@@ -429,6 +396,11 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
public boolean onTouchEvent(final MotionEvent me) {
+ if (!mMoreSuggestionsView.isShowingInParent()) {
+ // Ignore any touch event while more suggestions panel hasn't been shown.
+ // Detecting sliding up is done at {@link #onInterceptTouchEvent}.
+ return true;
+ }
// In the sliding input mode. {@link MotionEvent} should be forwarded to
// {@link MoreSuggestionsView}.
final int index = me.getActionIndex();
@@ -484,15 +456,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
false /* isKeyRepeat */);
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();
// {@link Integer} tag is set at
// {@link SuggestionStripLayoutHelper#setupWordViewsTextAndColor(SuggestedWords,int)} and
// {@link SuggestionStripLayoutHelper#layoutPunctuationSuggestions(SuggestedWords,ViewGroup}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
index 52708455e..68f417e84 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
@@ -22,9 +22,6 @@ import com.android.inputmethod.latin.SuggestedWords;
* An object that gives basic control of a suggestion strip and some info on it.
*/
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/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index eda81940f..cb615f3af 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -26,14 +26,15 @@ import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
-import com.android.inputmethod.compat.UserDictionaryCompatUtils;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.common.LocaleUtils;
import java.util.ArrayList;
import java.util.Locale;
import java.util.TreeSet;
+import javax.annotation.Nullable;
+
// Caveat: This class is basically taken from
// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java
// in order to deal with some devices that have issues with the user dictionary handling
@@ -45,10 +46,7 @@ import java.util.TreeSet;
public class UserDictionaryAddWordContents {
public static final String EXTRA_MODE = "mode";
public static final String EXTRA_WORD = "word";
- public static final String EXTRA_SHORTCUT = "shortcut";
public static final String EXTRA_LOCALE = "locale";
- public static final String EXTRA_ORIGINAL_WORD = "originalWord";
- public static final String EXTRA_ORIGINAL_SHORTCUT = "originalShortcut";
public static final int MODE_EDIT = 0;
public static final int MODE_INSERT = 1;
@@ -61,20 +59,12 @@ public class UserDictionaryAddWordContents {
private final int mMode; // Either MODE_EDIT or MODE_INSERT
private final EditText mWordEditText;
- private final EditText mShortcutEditText;
private String mLocale;
private final String mOldWord;
- private final String mOldShortcut;
private String mSavedWord;
- private String mSavedShortcut;
/* package */ UserDictionaryAddWordContents(final View view, final Bundle args) {
mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text);
- mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut);
- if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
- mShortcutEditText.setVisibility(View.GONE);
- view.findViewById(R.id.user_dictionary_add_shortcut_label).setVisibility(View.GONE);
- }
final String word = args.getString(EXTRA_WORD);
if (null != word) {
mWordEditText.setText(word);
@@ -82,17 +72,6 @@ public class UserDictionaryAddWordContents {
// it's too long to be edited.
mWordEditText.setSelection(mWordEditText.getText().length());
}
- final String shortcut;
- if (UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
- shortcut = args.getString(EXTRA_SHORTCUT);
- if (null != shortcut && null != mShortcutEditText) {
- mShortcutEditText.setText(shortcut);
- }
- mOldShortcut = args.getString(EXTRA_SHORTCUT);
- } else {
- shortcut = null;
- mOldShortcut = null;
- }
mMode = args.getInt(EXTRA_MODE); // default return value for #getInt() is 0 = MODE_EDIT
mOldWord = args.getString(EXTRA_WORD);
updateLocale(args.getString(EXTRA_LOCALE));
@@ -101,10 +80,8 @@ public class UserDictionaryAddWordContents {
/* package */ UserDictionaryAddWordContents(final View view,
final UserDictionaryAddWordContents oldInstanceToBeEdited) {
mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text);
- mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut);
mMode = MODE_EDIT;
mOldWord = oldInstanceToBeEdited.mSavedWord;
- mOldShortcut = oldInstanceToBeEdited.mSavedShortcut;
updateLocale(mLocale);
}
@@ -116,13 +93,6 @@ public class UserDictionaryAddWordContents {
/* package */ void saveStateIntoBundle(final Bundle outState) {
outState.putString(EXTRA_WORD, mWordEditText.getText().toString());
- outState.putString(EXTRA_ORIGINAL_WORD, mOldWord);
- if (null != mShortcutEditText) {
- outState.putString(EXTRA_SHORTCUT, mShortcutEditText.getText().toString());
- }
- if (null != mOldShortcut) {
- outState.putString(EXTRA_ORIGINAL_SHORTCUT, mOldShortcut);
- }
outState.putString(EXTRA_LOCALE, mLocale);
}
@@ -130,7 +100,7 @@ public class UserDictionaryAddWordContents {
if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
// Mode edit: remove the old entry.
final ContentResolver resolver = context.getContentResolver();
- UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
+ UserDictionarySettings.deleteWord(mOldWord, resolver);
}
// If we are in add mode, nothing was added, so we don't need to do anything.
}
@@ -141,50 +111,31 @@ public class UserDictionaryAddWordContents {
final ContentResolver resolver = context.getContentResolver();
if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
// Mode edit: remove the old entry.
- UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
+ UserDictionarySettings.deleteWord(mOldWord, resolver);
}
final String newWord = mWordEditText.getText().toString();
- final String newShortcut;
- if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
- newShortcut = null;
- } else if (null == mShortcutEditText) {
- newShortcut = null;
- } else {
- final String tmpShortcut = mShortcutEditText.getText().toString();
- if (TextUtils.isEmpty(tmpShortcut)) {
- newShortcut = null;
- } else {
- newShortcut = tmpShortcut;
- }
- }
if (TextUtils.isEmpty(newWord)) {
// If the word is somehow empty, don't insert it.
return CODE_CANCEL;
}
mSavedWord = newWord;
- mSavedShortcut = newShortcut;
- // If there is no shortcut, and the word already exists in the database, then we
- // should not insert, because either A. the word exists with no shortcut, in which
- // case the exact same thing we want to insert is already there, or B. the word
- // exists with at least one shortcut, in which case it has priority on our word.
- if (TextUtils.isEmpty(newShortcut) && hasWord(newWord, context)) {
+ // If the word already exists in the database, then we should not insert.
+ if (hasWord(newWord, context)) {
return CODE_ALREADY_PRESENT;
}
- // Disallow duplicates. If the same word with no shortcut is defined, remove it; if
- // the same word with the same shortcut is defined, remove it; but we don't mind if
- // there is the same word with a different, non-empty shortcut.
- UserDictionarySettings.deleteWord(newWord, null, resolver);
- if (!TextUtils.isEmpty(newShortcut)) {
- // If newShortcut is empty we just deleted this, no need to do it again
- UserDictionarySettings.deleteWord(newWord, newShortcut, resolver);
- }
+ // Disallow duplicates. If the same word is defined, remove it.
+ UserDictionarySettings.deleteWord(newWord, resolver);
// In this class we use the empty string to represent 'all locales' and mLocale cannot
// be null. However the addWord method takes null to mean 'all locales'.
- UserDictionaryCompatUtils.addWord(context, newWord.toString(),
- FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut, TextUtils.isEmpty(mLocale) ?
- null : LocaleUtils.constructLocaleFromString(mLocale));
+ final Locale locale = TextUtils.isEmpty(mLocale) ?
+ null : LocaleUtils.constructLocaleFromString(mLocale);
+ final Locale currentLocale = context.getResources().getConfiguration().locale;
+ final boolean useCurrentLocale = currentLocale.equals(locale);
+ UserDictionary.Words.addWord(context, newWord.toString(),
+ FREQUENCY_FOR_USER_DICTIONARY_ADDS, null /* shortcut */,
+ useCurrentLocale ? Locale.getDefault() : null);
return CODE_WORD_ADDED;
}
@@ -218,8 +169,8 @@ public class UserDictionaryAddWordContents {
public static class LocaleRenderer {
private final String mLocaleString;
private final String mDescription;
- // LocaleString may NOT be null.
- public LocaleRenderer(final Context context, final String localeString) {
+
+ public LocaleRenderer(final Context context, @Nullable final String localeString) {
mLocaleString = localeString;
if (null == localeString) {
mDescription = context.getString(R.string.user_dict_settings_more_languages);
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
deleted file mode 100644
index 163443036..000000000
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
+++ /dev/null
@@ -1,178 +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.userdictionary;
-
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.LocaleRenderer;
-import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker.LocationChangedListener;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Spinner;
-
-import java.util.ArrayList;
-import java.util.Locale;
-
-// Caveat: This class is basically taken from
-// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java
-// in order to deal with some devices that have issues with the user dictionary handling
-
-/**
- * Fragment to add a word/shortcut to the user dictionary.
- *
- * As opposed to the UserDictionaryActivity, this is only invoked within Settings
- * from the UserDictionarySettings.
- */
-public class UserDictionaryAddWordFragment extends Fragment
- implements AdapterView.OnItemSelectedListener, LocationChangedListener {
-
- private static final int OPTIONS_MENU_ADD = Menu.FIRST;
- private static final int OPTIONS_MENU_DELETE = Menu.FIRST + 1;
-
- private UserDictionaryAddWordContents mContents;
- private View mRootView;
- private boolean mIsDeleting = false;
-
- @Override
- public void onActivityCreated(final Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- setHasOptionsMenu(true);
- getActivity().getActionBar().setTitle(R.string.edit_personal_dictionary);
- // Keep the instance so that we remember mContents when configuration changes (eg rotation)
- setRetainInstance(true);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedState) {
- mRootView = inflater.inflate(R.layout.user_dictionary_add_word_fullscreen, null);
- mIsDeleting = false;
- // If we have a non-null mContents object, it's the old value before a configuration
- // change (eg rotation) so we need to use its values. Otherwise, read from the arguments.
- if (null == mContents) {
- mContents = new UserDictionaryAddWordContents(mRootView, getArguments());
- } else {
- // We create a new mContents object to account for the new situation : a word has
- // been added to the user dictionary when we started rotating, and we are now editing
- // it. That means in particular if the word undergoes any change, the old version should
- // be updated, so the mContents object needs to switch to EDIT mode if it was in
- // INSERT mode.
- mContents = new UserDictionaryAddWordContents(mRootView,
- mContents /* oldInstanceToBeEdited */);
- }
- getActivity().getActionBar().setSubtitle(UserDictionarySettingsUtils.getLocaleDisplayName(
- getActivity(), mContents.getCurrentUserDictionaryLocale()));
- return mRootView;
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0,
- R.string.user_dict_settings_add_menu_title).setIcon(R.drawable.ic_menu_add);
- actionItemAdd.setShowAsAction(
- MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
- final MenuItem actionItemDelete = menu.add(0, OPTIONS_MENU_DELETE, 0,
- R.string.user_dict_settings_delete).setIcon(android.R.drawable.ic_menu_delete);
- actionItemDelete.setShowAsAction(
- MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
- }
-
- /**
- * Callback for the framework when a menu option is pressed.
- *
- * @param item the item that was pressed
- * @return false to allow normal menu processing to proceed, true to consume it here
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == OPTIONS_MENU_ADD) {
- // added the entry in "onPause"
- getActivity().onBackPressed();
- return true;
- }
- if (item.getItemId() == OPTIONS_MENU_DELETE) {
- mContents.delete(getActivity());
- mIsDeleting = true;
- getActivity().onBackPressed();
- return true;
- }
- return false;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- // We are being shown: display the word
- updateSpinner();
- }
-
- private void updateSpinner() {
- final ArrayList<LocaleRenderer> localesList = mContents.getLocalesList(getActivity());
-
- final Spinner localeSpinner =
- (Spinner)mRootView.findViewById(R.id.user_dictionary_add_locale);
- final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<>(
- getActivity(), android.R.layout.simple_spinner_item, localesList);
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- localeSpinner.setAdapter(adapter);
- localeSpinner.setOnItemSelectedListener(this);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- // We are being hidden: commit changes to the user dictionary, unless we were deleting it
- if (!mIsDeleting) {
- mContents.apply(getActivity(), null);
- }
- }
-
- @Override
- public void onItemSelected(final AdapterView<?> parent, final View view, final int pos,
- final long id) {
- final LocaleRenderer locale = (LocaleRenderer)parent.getItemAtPosition(pos);
- if (locale.isMoreLanguages()) {
- PreferenceActivity preferenceActivity = (PreferenceActivity)getActivity();
- preferenceActivity.startPreferenceFragment(new UserDictionaryLocalePicker(), true);
- } else {
- mContents.updateLocale(locale.getLocaleString());
- }
- }
-
- @Override
- public void onNothingSelected(final AdapterView<?> parent) {
- // I'm not sure we can come here, but if we do, that's the right thing to do.
- final Bundle args = getArguments();
- mContents.updateLocale(args.getString(UserDictionaryAddWordContents.EXTRA_LOCALE));
- }
-
- // Called by the locale picker
- @Override
- public void onLocaleSelected(final Locale locale) {
- mContents.updateLocale(locale.toString());
- getActivity().onBackPressed();
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
index 624783a70..57347ce8c 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -20,6 +20,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
+import android.os.Build;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
@@ -31,12 +32,14 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.common.LocaleUtils;
import java.util.List;
import java.util.Locale;
import java.util.TreeSet;
+import javax.annotation.Nullable;
+
// Caveat: This class is basically taken from
// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryList.java
// in order to deal with some devices that have issues with the user dictionary handling
@@ -47,12 +50,12 @@ public class UserDictionaryList extends PreferenceFragment {
"android.settings.USER_DICTIONARY_SETTINGS";
@Override
- public void onCreate(Bundle icicle) {
+ public void onCreate(final Bundle icicle) {
super.onCreate(icicle);
setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity()));
}
- public static TreeSet<String> getUserDictionaryLocalesSet(Activity activity) {
+ public static TreeSet<String> getUserDictionaryLocalesSet(final Activity activity) {
final Cursor cursor = activity.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
new String[] { UserDictionary.Words.LOCALE },
null, null, null);
@@ -72,7 +75,7 @@ public class UserDictionaryList extends PreferenceFragment {
} finally {
cursor.close();
}
- if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
// For ICS, we need to show "For all languages" in case that the keyboard locale
// is different from the system locale
localeSet.add("");
@@ -108,7 +111,7 @@ public class UserDictionaryList extends PreferenceFragment {
* Creates the entries that allow the user to go into the user dictionary for each locale.
* @param userDictGroup The group to put the settings in.
*/
- protected void createUserDictSettings(PreferenceGroup userDictGroup) {
+ protected void createUserDictSettings(final PreferenceGroup userDictGroup) {
final Activity activity = getActivity();
userDictGroup.removeAll();
final TreeSet<String> localeSet =
@@ -121,31 +124,33 @@ public class UserDictionaryList extends PreferenceFragment {
}
if (localeSet.isEmpty()) {
- userDictGroup.addPreference(createUserDictionaryPreference(null, activity));
+ userDictGroup.addPreference(createUserDictionaryPreference(null));
} else {
for (String locale : localeSet) {
- userDictGroup.addPreference(createUserDictionaryPreference(locale, activity));
+ userDictGroup.addPreference(createUserDictionaryPreference(locale));
}
}
}
/**
* Create a single User Dictionary Preference object, with its parameters set.
- * @param locale The locale for which this user dictionary is for.
+ * @param localeString The locale for which this user dictionary is for.
* @return The corresponding preference.
*/
- protected Preference createUserDictionaryPreference(String locale, Activity activity) {
+ protected Preference createUserDictionaryPreference(@Nullable final String localeString) {
final Preference newPref = new Preference(getActivity());
final Intent intent = new Intent(USER_DICTIONARY_SETTINGS_INTENT_ACTION);
- if (null == locale) {
+ if (null == localeString) {
newPref.setTitle(Locale.getDefault().getDisplayName());
} else {
- if ("".equals(locale))
+ if (localeString.isEmpty()) {
newPref.setTitle(getString(R.string.user_dict_settings_all_languages));
- else
- newPref.setTitle(LocaleUtils.constructLocaleFromString(locale).getDisplayName());
- intent.putExtra("locale", locale);
- newPref.getExtras().putString("locale", locale);
+ } else {
+ newPref.setTitle(
+ LocaleUtils.constructLocaleFromString(localeString).getDisplayName());
+ }
+ intent.putExtra("locale", localeString);
+ newPref.getExtras().putString("locale", localeString);
}
newPref.setIntent(intent);
newPref.setFragment(UserDictionarySettings.class.getName());
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
index cf2014a1a..bd3572340 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
@@ -16,6 +16,12 @@
package com.android.inputmethod.latin.userdictionary;
+import static com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.EXTRA_LOCALE;
+import static com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.EXTRA_MODE;
+import static com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.EXTRA_WORD;
+import static com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.MODE_EDIT;
+import static com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.MODE_INSERT;
+
import com.android.inputmethod.latin.R;
import android.app.ListFragment;
@@ -25,7 +31,7 @@ import android.content.Intent;
import android.database.Cursor;
import android.os.Build;
import android.os.Bundle;
-import android.provider.UserDictionary;
+import android.provider.UserDictionary.Words;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -48,62 +54,8 @@ import java.util.Locale;
public class UserDictionarySettings extends ListFragment {
- public static final boolean IS_SHORTCUT_API_SUPPORTED =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
-
- private static final String[] QUERY_PROJECTION_SHORTCUT_UNSUPPORTED =
- { UserDictionary.Words._ID, UserDictionary.Words.WORD};
- private static final String[] QUERY_PROJECTION_SHORTCUT_SUPPORTED =
- { UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT};
- private static final String[] QUERY_PROJECTION =
- IS_SHORTCUT_API_SUPPORTED ?
- QUERY_PROJECTION_SHORTCUT_SUPPORTED : QUERY_PROJECTION_SHORTCUT_UNSUPPORTED;
-
- // The index of the shortcut in the above array.
- private static final int INDEX_SHORTCUT = 2;
-
- private static final String[] ADAPTER_FROM_SHORTCUT_UNSUPPORTED = {
- UserDictionary.Words.WORD,
- };
-
- private static final String[] ADAPTER_FROM_SHORTCUT_SUPPORTED = {
- UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT
- };
-
- private static final String[] ADAPTER_FROM = IS_SHORTCUT_API_SUPPORTED ?
- ADAPTER_FROM_SHORTCUT_SUPPORTED : ADAPTER_FROM_SHORTCUT_UNSUPPORTED;
-
- private static final int[] ADAPTER_TO_SHORTCUT_UNSUPPORTED = {
- android.R.id.text1,
- };
-
- private static final int[] ADAPTER_TO_SHORTCUT_SUPPORTED = {
- android.R.id.text1, android.R.id.text2
- };
-
- private static final int[] ADAPTER_TO = IS_SHORTCUT_API_SUPPORTED ?
- ADAPTER_TO_SHORTCUT_SUPPORTED : ADAPTER_TO_SHORTCUT_UNSUPPORTED;
-
- // Either the locale is empty (means the word is applicable to all locales)
- // or the word equals our current locale
- private static final String QUERY_SELECTION =
- UserDictionary.Words.LOCALE + "=?";
- private static final String QUERY_SELECTION_ALL_LOCALES =
- UserDictionary.Words.LOCALE + " is null";
-
- private static final String DELETE_SELECTION_WITH_SHORTCUT = UserDictionary.Words.WORD
- + "=? AND " + UserDictionary.Words.SHORTCUT + "=?";
- private static final String DELETE_SELECTION_WITHOUT_SHORTCUT = UserDictionary.Words.WORD
- + "=? AND " + UserDictionary.Words.SHORTCUT + " is null OR "
- + UserDictionary.Words.SHORTCUT + "=''";
- private static final String DELETE_SELECTION_SHORTCUT_UNSUPPORTED =
- UserDictionary.Words.WORD + "=?";
-
- private static final int OPTIONS_MENU_ADD = Menu.FIRST;
-
private Cursor mCursor;
-
- protected String mLocale;
+ private String mLocale;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -172,55 +124,60 @@ public class UserDictionarySettings extends ListFragment {
// TODO: it should be easy to make this more readable by making the special values
// human-readable, like "all_locales" and "current_locales" strings, provided they
// can be guaranteed not to match locales that may exist.
- if ("".equals(locale)) {
+ if (TextUtils.isEmpty(locale)) {
// Case-insensitive sort
- return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
- QUERY_SELECTION_ALL_LOCALES, null,
- "UPPER(" + UserDictionary.Words.WORD + ")");
- } else {
- final String queryLocale = null != locale ? locale : Locale.getDefault().toString();
- return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
- QUERY_SELECTION, new String[] { queryLocale },
- "UPPER(" + UserDictionary.Words.WORD + ")");
+ return getActivity().managedQuery(
+ Words.CONTENT_URI,
+ new String[] { Words._ID, Words.WORD },
+ Words.LOCALE + " is null",
+ null,
+ "UPPER(" + Words.WORD + ")");
}
+ return getActivity().managedQuery(
+ Words.CONTENT_URI,
+ new String[] { Words._ID, Words.WORD },
+ Words.LOCALE + "=?",
+ new String[] { locale },
+ "UPPER(" + Words.WORD + ")");
}
private ListAdapter createAdapter() {
- return new MyAdapter(getActivity(), R.layout.user_dictionary_item, mCursor,
- ADAPTER_FROM, ADAPTER_TO, this);
+ return new MyAdapter(
+ getActivity(),
+ R.layout.user_dictionary_item,
+ mCursor,
+ new String[] { Words.WORD },
+ new int[] { android.R.id.text1 });
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
final String word = getWord(position);
- final String shortcut = getShortcut(position);
if (word != null) {
- showAddOrEditDialog(word, shortcut);
+ showAddOrEditDialog(word);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
final Locale systemLocale = getResources().getConfiguration().locale;
if (!TextUtils.isEmpty(mLocale) && !mLocale.equals(systemLocale.toString())) {
// Hide the add button for ICS because it doesn't support specifying a locale
- // for an entry. This new "locale"-aware API has been added in conjunction
- // with the shortcut API.
+ // for an entry.
return;
}
}
- MenuItem actionItem =
- menu.add(0, OPTIONS_MENU_ADD, 0, R.string.user_dict_settings_add_menu_title)
- .setIcon(R.drawable.ic_menu_add);
- actionItem.setShowAsAction(
- MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ menu.add(0, Menu.FIRST, 0, R.string.user_dict_settings_add_menu_title)
+ .setIcon(R.drawable.ic_menu_add)
+ .setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == OPTIONS_MENU_ADD) {
- showAddOrEditDialog(null, null);
+ if (item.getItemId() == Menu.FIRST) {
+ showAddOrEditDialog(null);
return true;
}
return false;
@@ -229,20 +186,13 @@ public class UserDictionarySettings extends ListFragment {
/**
* Add or edit a word. If editingWord is null, it's an add; otherwise, it's an edit.
* @param editingWord the word to edit, or null if it's an add.
- * @param editingShortcut the shortcut for this entry, or null if none.
*/
- private void showAddOrEditDialog(final String editingWord, final String editingShortcut) {
+ private void showAddOrEditDialog(final String editingWord) {
final Bundle args = new Bundle();
- args.putInt(UserDictionaryAddWordContents.EXTRA_MODE, null == editingWord
- ? UserDictionaryAddWordContents.MODE_INSERT
- : UserDictionaryAddWordContents.MODE_EDIT);
- args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord);
- args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut);
- args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale);
- android.preference.PreferenceActivity pa =
- (android.preference.PreferenceActivity)getActivity();
- pa.startPreferencePanel(UserDictionaryAddWordFragment.class.getName(),
- args, R.string.user_dict_settings_add_dialog_title, null, null, 0);
+ args.putInt(EXTRA_MODE, editingWord == null ? MODE_INSERT : MODE_EDIT);
+ args.putString(EXTRA_WORD, editingWord);
+ args.putString(EXTRA_LOCALE, mLocale);
+ getActivity();
}
private String getWord(final int position) {
@@ -251,85 +201,44 @@ public class UserDictionarySettings extends ListFragment {
// Handle a possible race-condition
if (mCursor.isAfterLast()) return null;
- return mCursor.getString(
- mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD));
+ return mCursor.getString(mCursor.getColumnIndexOrThrow(Words.WORD));
}
- private String getShortcut(final int position) {
- if (!IS_SHORTCUT_API_SUPPORTED) return null;
- if (null == mCursor) return null;
- mCursor.moveToPosition(position);
- // Handle a possible race-condition
- if (mCursor.isAfterLast()) return null;
-
- return mCursor.getString(
- mCursor.getColumnIndexOrThrow(UserDictionary.Words.SHORTCUT));
- }
-
- public static void deleteWord(final String word, final String shortcut,
- final ContentResolver resolver) {
- if (!IS_SHORTCUT_API_SUPPORTED) {
- resolver.delete(UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_SHORTCUT_UNSUPPORTED,
- new String[] { word });
- } else if (TextUtils.isEmpty(shortcut)) {
- resolver.delete(
- UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT,
- new String[] { word });
- } else {
- resolver.delete(
- UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT,
- new String[] { word, shortcut });
- }
+ public static void deleteWord(final String word, final ContentResolver resolver) {
+ resolver.delete(Words.CONTENT_URI, Words.WORD + "=?", new String[] { word });
}
private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer {
-
private AlphabetIndexer mIndexer;
private ViewBinder mViewBinder = new ViewBinder() {
@Override
- public boolean setViewValue(View v, Cursor c, int columnIndex) {
- if (!IS_SHORTCUT_API_SUPPORTED) {
- // just let SimpleCursorAdapter set the view values
- return false;
- }
- if (columnIndex == INDEX_SHORTCUT) {
- final String shortcut = c.getString(INDEX_SHORTCUT);
- if (TextUtils.isEmpty(shortcut)) {
- v.setVisibility(View.GONE);
- } else {
- ((TextView)v).setText(shortcut);
- v.setVisibility(View.VISIBLE);
- }
- v.invalidate();
- return true;
- }
-
+ public boolean setViewValue(final View v, final Cursor c, final int columnIndex) {
+ // just let SimpleCursorAdapter set the view values
return false;
}
};
- @SuppressWarnings("deprecation")
- public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to,
- UserDictionarySettings settings) {
- super(context, layout, c, from, to);
+ public MyAdapter(final Context context, final int layout, final Cursor c,
+ final String[] from, final int[] to) {
+ super(context, layout, c, from, to, 0 /* flags */);
if (null != c) {
final String alphabet = context.getString(R.string.user_dict_fast_scroll_alphabet);
- final int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD);
+ final int wordColIndex = c.getColumnIndexOrThrow(Words.WORD);
mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet);
}
setViewBinder(mViewBinder);
}
@Override
- public int getPositionForSection(int section) {
+ public int getPositionForSection(final int section) {
return null == mIndexer ? 0 : mIndexer.getPositionForSection(section);
}
@Override
- public int getSectionForPosition(int position) {
+ public int getSectionForPosition(final int position) {
return null == mIndexer ? 0 : mIndexer.getSectionForPosition(position);
}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java
index e58727ec4..c0a946e42 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java
@@ -17,7 +17,7 @@
package com.android.inputmethod.latin.userdictionary;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.common.LocaleUtils;
import android.content.Context;
import android.text.TextUtils;
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index db7f2a56c..2aac7c57a 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -16,12 +16,12 @@
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.ASCII_CAPABLE;
-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 static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.ASCII_CAPABLE;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
import android.os.Build;
import android.text.TextUtils;
@@ -31,6 +31,7 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
index d12aad639..952ac2a62 100644
--- a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
+++ b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
@@ -59,11 +59,7 @@ public class AsyncResultHolder<E> {
*/
public E get(final E defaultValue, final long timeOut) {
try {
- if (mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
- return mResult;
- } else {
- return defaultValue;
- }
+ return mLatch.await(timeOut, TimeUnit.MILLISECONDS) ? mResult : defaultValue;
} catch (InterruptedException e) {
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 156fcf57c..c9ecade91 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -24,20 +24,22 @@ import com.android.inputmethod.latin.define.DebugFlags;
public final class AutoCorrectionUtils {
private static final boolean DBG = DebugFlags.DEBUG_ENABLED;
private static final String TAG = AutoCorrectionUtils.class.getSimpleName();
- private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
private AutoCorrectionUtils() {
// Purely static class: can't instantiate.
}
- public static boolean suggestionExceedsAutoCorrectionThreshold(
- final SuggestedWordInfo suggestion, final String consideredWord,
- final float autoCorrectionThreshold) {
+ public static boolean suggestionExceedsThreshold(final SuggestedWordInfo suggestion,
+ final String consideredWord, final float threshold) {
if (null != suggestion) {
// Shortlist a whitelisted word
if (suggestion.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
return true;
}
+ // TODO: return suggestion.isAprapreateForAutoCorrection();
+ if (!suggestion.isAprapreateForAutoCorrection()) {
+ return false;
+ }
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.
@@ -46,47 +48,15 @@ public final class AutoCorrectionUtils {
if (DBG) {
Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + ","
+ autoCorrectionSuggestionScore + ", " + normalizedScore
- + "(" + autoCorrectionThreshold + ")");
+ + "(" + threshold + ")");
}
- if (normalizedScore >= autoCorrectionThreshold) {
+ if (normalizedScore >= threshold) {
if (DBG) {
- Log.d(TAG, "Auto corrected by S-threshold.");
+ Log.d(TAG, "Exceeds threshold.");
}
- return !shouldBlockAutoCorrectionBySafetyNet(consideredWord, suggestion.mWord);
+ return true;
}
}
return false;
}
-
- // TODO: Resolve the inconsistencies between the native auto correction algorithms and
- // this safety net
- public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord,
- final String suggestion) {
- // Safety net for auto correction.
- // Actually if we hit this safety net, it's a bug.
- // If user selected aggressive auto correction mode, there is no need to use the safety
- // net.
- // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
- // we should not use net because relatively edit distance can be big.
- final int typedWordLength = typedWord.length();
- if (typedWordLength < MINIMUM_SAFETY_NET_CHAR_LENGTH) {
- return false;
- }
- final int maxEditDistanceOfNativeDictionary = (typedWordLength / 2) + 1;
- final int distance = BinaryDictionaryUtils.editDistance(typedWord, suggestion);
- if (DBG) {
- Log.d(TAG, "Autocorrected edit distance = " + distance
- + ", " + maxEditDistanceOfNativeDictionary);
- }
- if (distance > maxEditDistanceOfNativeDictionary) {
- if (DBG) {
- Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestion);
- Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. "
- + "Turning off auto-correction.");
- }
- return true;
- } else {
- return false;
- }
- }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
index 5d7deba15..3bf9c6200 100644
--- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
@@ -18,9 +18,9 @@ package com.android.inputmethod.latin.utils;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.common.StringUtils;
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;
@@ -40,10 +40,10 @@ public final class BinaryDictionaryUtils {
JniUtils.loadNativeLibrary();
}
+ @UsedForTesting
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)
@@ -112,14 +112,6 @@ public final class BinaryDictionaryUtils {
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.
@@ -131,8 +123,6 @@ public final class BinaryDictionaryUtils {
*/
@UsedForTesting
public static int setCurrentTimeForTest(final int currentTime) {
- final int currentNativeTimestamp = setCurrentTimeForTestNative(currentTime);
- PersonalizationHelper.currentTimeChangedForTesting(currentNativeTimestamp);
- return currentNativeTimestamp;
+ return setCurrentTimeForTestNative(currentTime);
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 936219332..0dbc7c858 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -19,10 +19,12 @@ package com.android.inputmethod.latin.utils;
import android.text.InputType;
import android.text.TextUtils;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import java.util.ArrayList;
import java.util.Locale;
public final class CapsModeUtils {
@@ -213,12 +215,22 @@ public final class CapsModeUtils {
char c = cs.charAt(--j);
// We found the next interesting chunk of text ; next we need to determine if it's the
- // end of a sentence. If we have a question mark or an exclamation mark, it's the end of
- // a sentence. If it's neither, the only remaining case is the period so we get the opposite
- // case out of the way.
- if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) {
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ // end of a sentence. If we have a sentence terminator (typically a question mark or an
+ // exclamation mark), then it's the end of a sentence; however, we treat the abbreviation
+ // marker specially because usually is the same char as the sentence separator (the
+ // period in most languages) and in this case we need to apply a heuristic to determine
+ // in which of these senses it's used.
+ if (spacingAndPunctuations.isSentenceTerminator(c)
+ && !spacingAndPunctuations.isAbbreviationMarker(c)) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+ | TextUtils.CAP_MODE_SENTENCES) & reqModes;
}
+ // If we reach here, we know we have whitespace before the cursor and before that there
+ // is something that either does not terminate the sentence, or a symbol preceded by the
+ // start of the text, or it's the sentence separator AND it happens to be the same code
+ // point as the abbreviation marker.
+ // If it's a symbol or something that does not terminate the sentence, then we need to
+ // return caps for MODE_CHARACTERS and MODE_WORDS, but not for MODE_SENTENCES.
if (!spacingAndPunctuations.isSentenceSeparator(c) || j <= 0) {
return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
}
@@ -315,4 +327,31 @@ public final class CapsModeUtils {
// Here we arrived at the start of the line. This should behave exactly like whitespace.
return (START == state || LETTER == state) ? noCaps : caps;
}
+
+ /**
+ * Convert capitalize mode flags into human readable text.
+ *
+ * @param capsFlags The modes flags to be converted. It may be any combination of
+ * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
+ * {@link TextUtils#CAP_MODE_SENTENCES}.
+ * @return the text that describe the <code>capsMode</code>.
+ */
+ public static String flagsToString(final int capsFlags) {
+ final int capsFlagsMask = TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+ | TextUtils.CAP_MODE_SENTENCES;
+ if ((capsFlags & ~capsFlagsMask) != 0) {
+ return "unknown<0x" + Integer.toHexString(capsFlags) + ">";
+ }
+ final ArrayList<String> builder = new ArrayList<>();
+ if ((capsFlags & android.text.TextUtils.CAP_MODE_CHARACTERS) != 0) {
+ builder.add("characters");
+ }
+ if ((capsFlags & android.text.TextUtils.CAP_MODE_WORDS) != 0) {
+ builder.add("words");
+ }
+ if ((capsFlags & android.text.TextUtils.CAP_MODE_SENTENCES) != 0) {
+ builder.add("sentences");
+ }
+ return builder.isEmpty() ? "none" : TextUtils.join("|", builder);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
deleted file mode 100644
index 61292fc36..000000000
--- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
+++ /dev/null
@@ -1,43 +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 java.util.ArrayList;
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
-
-public final class CollectionUtils {
- private CollectionUtils() {
- // This utility class is not publicly instantiable.
- }
-
- 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 = new ArrayList<>(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
index 34f59e8bc..5c0c4328f 100644
--- a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
@@ -17,8 +17,8 @@
package com.android.inputmethod.latin.utils;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.NgramProperty;
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;
@@ -26,14 +26,16 @@ 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 NGRAM_TAG = "ngram";
+ public static final String NGRAM_PREV_WORD_TAG = "prev_word";
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 BEGINNING_OF_SENTENCE_TAG = "beginning_of_sentence";
public static final String NOT_A_WORD_TAG = "not_a_word";
- public static final String BLACKLISTED_TAG = "blacklisted";
+ public static final String POSSIBLY_OFFENSIVE_TAG = "possibly_offensive";
+ public static final String TRUE_VALUE = "true";
public static String formatAttributeMap(final HashMap<String, String> attributeMap) {
final StringBuilder builder = new StringBuilder();
@@ -58,29 +60,29 @@ public class CombinedFormatUtils {
builder.append(",");
builder.append(formatProbabilityInfo(wordProperty.mProbabilityInfo));
if (wordProperty.mIsBeginningOfSentence) {
- builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=true");
+ builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=" + TRUE_VALUE);
}
if (wordProperty.mIsNotAWord) {
- builder.append("," + NOT_A_WORD_TAG + "=true");
+ builder.append("," + NOT_A_WORD_TAG + "=" + TRUE_VALUE);
}
- if (wordProperty.mIsBlacklistEntry) {
- builder.append("," + BLACKLISTED_TAG + "=true");
+ if (wordProperty.mIsPossiblyOffensive) {
+ builder.append("," + POSSIBLY_OFFENSIVE_TAG + "=" + TRUE_VALUE);
}
builder.append("\n");
- if (wordProperty.mShortcutTargets != null) {
- for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
- builder.append(" " + SHORTCUT_TAG + "=" + shortcutTarget.mWord);
+ if (wordProperty.mHasNgrams) {
+ for (final NgramProperty ngramProperty : wordProperty.mNgrams) {
+ builder.append(" " + NGRAM_TAG + "=" + ngramProperty.mTargetWord.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(formatProbabilityInfo(ngramProperty.mTargetWord.mProbabilityInfo));
builder.append("\n");
+ for (int i = 0; i < ngramProperty.mNgramContext.getPrevWordCount(); i++) {
+ builder.append(" " + NGRAM_PREV_WORD_TAG + "[" + i + "]="
+ + ngramProperty.mNgramContext.getNthPrevWord(i + 1));
+ if (ngramProperty.mNgramContext.isNthPrevWordBeginningOfSentence(i + 1)) {
+ builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=true");
+ }
+ builder.append("\n");
+ }
}
}
return builder.toString();
@@ -100,4 +102,8 @@ public class CombinedFormatUtils {
}
return builder.toString();
}
+
+ public static boolean isLiteralTrue(final String value) {
+ return TRUE_VALUE.equalsIgnoreCase(value);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
deleted file mode 100644
index 87df013a6..000000000
--- a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
+++ /dev/null
@@ -1,91 +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 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 ELEMENT_SIZE = INDEX_Y + 1;
-
- private CoordinateUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static int[] newInstance() {
- return new int[ELEMENT_SIZE];
- }
-
- public static int x(final int[] coords) {
- return coords[INDEX_X];
- }
-
- public static int y(final int[] coords) {
- return coords[INDEX_Y];
- }
-
- public static void set(final int[] coords, final int x, final int y) {
- coords[INDEX_X] = x;
- coords[INDEX_Y] = y;
- }
-
- public static void copy(final int[] destination, final int[] source) {
- 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/CursorAnchorInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java
index 9dc0524a2..c90d30c42 100644
--- a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java
@@ -16,17 +16,26 @@
package com.android.inputmethod.latin.utils;
+import android.annotation.TargetApi;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.inputmethodservice.ExtractEditText;
import android.inputmethodservice.InputMethodService;
+import android.os.Build;
import android.text.Layout;
import android.text.Spannable;
+import android.text.Spanned;
import android.view.View;
import android.view.ViewParent;
import android.view.inputmethod.CursorAnchorInfo;
import android.widget.TextView;
+import com.android.inputmethod.compat.BuildCompatUtils;
+import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* This class allows input methods to extract {@link CursorAnchorInfo} directly from the given
* {@link TextView}. This is useful and even necessary to support full-screen mode where the default
@@ -77,13 +86,32 @@ public final class CursorAnchorInfoUtils {
}
/**
+ * Extracts {@link CursorAnchorInfoCompatWrapper} from the given {@link TextView}.
+ * @param textView the target text view from which {@link CursorAnchorInfoCompatWrapper} is to
+ * be extracted.
+ * @return the {@link CursorAnchorInfoCompatWrapper} object based on the current layout.
+ * {@code null} if {@code Build.VERSION.SDK_INT} is 20 or prior or {@link TextView} is not
+ * ready to provide layout information.
+ */
+ @Nullable
+ public static CursorAnchorInfoCompatWrapper extractFromTextView(
+ @Nonnull final TextView textView) {
+ if (BuildCompatUtils.EFFECTIVE_SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return null;
+ }
+ return CursorAnchorInfoCompatWrapper.wrap(extractFromTextViewInternal(textView));
+ }
+
+ /**
* Returns {@link CursorAnchorInfo} from the given {@link TextView}.
* @param textView the target text view from which {@link CursorAnchorInfo} is to be extracted.
* @return the {@link CursorAnchorInfo} object based on the current layout. {@code null} if it
* is not feasible.
*/
- public static CursorAnchorInfo getCursorAnchorInfo(final TextView textView) {
- Layout layout = textView.getLayout();
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Nullable
+ private static CursorAnchorInfo extractFromTextViewInternal(@Nonnull final TextView textView) {
+ final Layout layout = textView.getLayout();
if (layout == null) {
return null;
}
@@ -122,7 +150,7 @@ public final class CursorAnchorInfoUtils {
final Object[] spans = spannable.getSpans(0, text.length(), Object.class);
for (Object span : spans) {
final int spanFlag = spannable.getSpanFlags(span);
- if ((spanFlag & Spannable.SPAN_COMPOSING) != 0) {
+ if ((spanFlag & Spanned.SPAN_COMPOSING) != 0) {
composingTextStart = Math.min(composingTextStart,
spannable.getSpanStart(span));
composingTextEnd = Math.max(composingTextEnd, spannable.getSpanEnd(span));
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 197908032..25fa723cc 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -22,11 +22,15 @@ import android.content.res.AssetManager;
import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Log;
+import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.annotations.UsedForTesting;
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.RichInputMethodManager;
+import com.android.inputmethod.latin.common.LocaleUtils;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
@@ -35,9 +39,13 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* This class encapsulates the logic for the Latin-IME side of dictionary information management.
*/
@@ -46,6 +54,7 @@ public class DictionaryInfoUtils {
private static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
private static final String DEFAULT_MAIN_DICT = "main";
private static final String MAIN_DICT_PREFIX = "main_";
+ private static final String DECODER_DICT_SUFFIX = DecoderSpecificConstants.DECODER_DICT_SUFFIX;
// 6 digits - unicode is limited to 21 bits
private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
@@ -57,28 +66,36 @@ public class DictionaryInfoUtils {
private static final String DATE_COLUMN = "date";
private static final String FILESIZE_COLUMN = "filesize";
private static final String VERSION_COLUMN = "version";
+ @Nonnull
public final String mId;
+ @Nonnull
public final Locale mLocale;
+ @Nullable
public final String mDescription;
public final AssetFileAddress mFileAddress;
public final int mVersion;
- public DictionaryInfo(final String id, final Locale locale, final String description,
- final AssetFileAddress fileAddress, final int version) {
+
+ public DictionaryInfo(@Nonnull final String id, @Nonnull final Locale locale,
+ @Nullable final String description, @Nullable final AssetFileAddress fileAddress,
+ final int version) {
mId = id;
mLocale = locale;
mDescription = description;
mFileAddress = fileAddress;
mVersion = version;
}
+
public ContentValues toContentValues() {
final ContentValues values = new ContentValues();
values.put(WORDLISTID_COLUMN, mId);
values.put(LOCALE_COLUMN, mLocale.toString());
values.put(DESCRIPTION_COLUMN, mDescription);
- values.put(LOCAL_FILENAME_COLUMN, mFileAddress.mFilename);
+ values.put(LOCAL_FILENAME_COLUMN,
+ mFileAddress != null ? mFileAddress.mFilename : "");
values.put(DATE_COLUMN, TimeUnit.MILLISECONDS.toSeconds(
- new File(mFileAddress.mFilename).lastModified()));
- values.put(FILESIZE_COLUMN, mFileAddress.mLength);
+ mFileAddress != null ? new File(mFileAddress.mFilename).lastModified() : 0));
+ values.put(FILESIZE_COLUMN,
+ mFileAddress != null ? mFileAddress.mLength : 0);
values.put(VERSION_COLUMN, mVersion);
return values;
}
@@ -140,9 +157,10 @@ public class DictionaryInfoUtils {
}
/**
- * Reverse escaping done by replaceFileNameDangerousCharacters.
+ * Reverse escaping done by {@link #replaceFileNameDangerousCharacters(String)}.
*/
- public static String getWordListIdFromFileName(final String fname) {
+ @Nonnull
+ public static String getWordListIdFromFileName(@Nonnull final String fname) {
final StringBuilder sb = new StringBuilder();
final int fnameLength = fname.length();
for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) {
@@ -174,12 +192,15 @@ public class DictionaryInfoUtils {
* {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}.
* @return The category as a string or null if it can't be found in the file name.
*/
- public static String getCategoryFromFileName(final String fileName) {
+ @Nullable
+ public static String getCategoryFromFileName(@Nonnull final String fileName) {
final String id = getWordListIdFromFileName(fileName);
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
// An id is supposed to be in format category:locale, so splitting on the separator
// should yield a 2-elements array
- if (2 != idArray.length) return null;
+ if (2 != idArray.length) {
+ return null;
+ }
return idArray[0];
}
@@ -223,12 +244,26 @@ public class DictionaryInfoUtils {
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
// An id is supposed to be in format category:locale, so splitting on the separator
// should yield a 2-elements array
- if (2 != idArray.length) return false;
+ if (2 != idArray.length) {
+ return false;
+ }
return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
}
/**
+ * Find out whether a dictionary is available for this locale.
+ * @param context the context on which to check resources.
+ * @param locale the locale to check for.
+ * @return whether a (non-placeholder) dictionary is available or not.
+ */
+ public static boolean isDictionaryAvailable(final Context context, final Locale locale) {
+ final Resources res = context.getResources();
+ return 0 != getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
+ }
+
+ /**
* Helper method to return a dictionary res id for a locale, or 0 if none.
+ * @param res resources for the app
* @param locale dictionary locale
* @return main dictionary resource id
*/
@@ -237,8 +272,8 @@ public class DictionaryInfoUtils {
int resId;
// Try to find main_language_country dictionary.
if (!locale.getCountry().isEmpty()) {
- final String dictLanguageCountry =
- MAIN_DICT_PREFIX + locale.toString().toLowerCase(Locale.ROOT);
+ final String dictLanguageCountry = MAIN_DICT_PREFIX
+ + locale.toString().toLowerCase(Locale.ROOT) + DECODER_DICT_SUFFIX;
if ((resId = res.getIdentifier(
dictLanguageCountry, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
return resId;
@@ -246,7 +281,7 @@ public class DictionaryInfoUtils {
}
// Try to find main_language dictionary.
- final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage();
+ final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage() + DECODER_DICT_SUFFIX;
if ((resId = res.getIdentifier(dictLanguage, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
return resId;
}
@@ -257,13 +292,17 @@ public class DictionaryInfoUtils {
/**
* Returns a main dictionary resource id
+ * @param res resources for the app
* @param locale dictionary locale
* @return main dictionary resource id
*/
public static int getMainDictionaryResourceId(final Resources res, final Locale locale) {
int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
- if (0 != resourceId) return resourceId;
- return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", RESOURCE_PACKAGE_NAME);
+ if (0 != resourceId) {
+ return resourceId;
+ }
+ return res.getIdentifier(DEFAULT_MAIN_DICT + DecoderSpecificConstants.DECODER_DICT_SUFFIX,
+ "raw", RESOURCE_PACKAGE_NAME);
}
/**
@@ -274,19 +313,15 @@ public class DictionaryInfoUtils {
* unique ID to them. This ID is just the name of the language (locale-wise) they
* are for, and this method returns this ID.
*/
- public static String getMainDictId(final Locale locale) {
+ public static String getMainDictId(@Nonnull final Locale locale) {
// This works because we don't include by default different dictionaries for
// different countries. This actually needs to return the id that we would
// like to use for word lists included in resources, and the following is okay.
return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY +
- BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.getLanguage().toString();
+ BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.toString().toLowerCase();
}
- public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file) {
- return getDictionaryFileHeaderOrNull(file, 0, file.length());
- }
-
- private static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
+ public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
final long offset, final long length) {
try {
final DictionaryHeader header =
@@ -303,20 +338,27 @@ public class DictionaryInfoUtils {
* Returns information of the dictionary.
*
* @param fileAddress the asset dictionary file address.
+ * @param locale Locale for this file.
* @return information of the specified dictionary.
*/
private static DictionaryInfo createDictionaryInfoFromFileAddress(
- final AssetFileAddress fileAddress) {
- 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();
- final String version = header.getVersion();
- return new DictionaryInfo(id, locale, description, fileAddress, Integer.parseInt(version));
+ final AssetFileAddress fileAddress, Locale locale) {
+ final String id = getMainDictId(locale);
+ final int version = DictionaryHeaderUtils.getContentVersion(fileAddress);
+ final String description = SubtypeLocaleUtils
+ .getSubtypeLocaleDisplayName(locale.toString());
+ return new DictionaryInfo(id, locale, description, fileAddress, version);
+ }
+
+ /**
+ * Returns dictionary information for the given locale.
+ */
+ private static DictionaryInfo createDictionaryInfoFromLocale(Locale locale) {
+ final String id = getMainDictId(locale);
+ final int version = -1;
+ final String description = SubtypeLocaleUtils
+ .getSubtypeLocaleDisplayName(locale.toString());
+ return new DictionaryInfo(id, locale, description, null, version);
}
private static void addOrUpdateDictInfo(final ArrayList<DictionaryInfo> dictList,
@@ -343,18 +385,23 @@ public class DictionaryInfoUtils {
if (null != directoryList) {
for (final File directory : directoryList) {
final String localeString = getWordListIdFromFileName(directory.getName());
- File[] dicts = BinaryDictionaryGetter.getCachedWordLists(localeString, context);
+ final File[] dicts = BinaryDictionaryGetter.getCachedWordLists(
+ localeString, context);
for (final File dict : dicts) {
final String wordListId = getWordListIdFromFileName(dict.getName());
- if (!DictionaryInfoUtils.isMainWordListId(wordListId)) continue;
+ if (!DictionaryInfoUtils.isMainWordListId(wordListId)) {
+ continue;
+ }
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
final AssetFileAddress fileAddress = AssetFileAddress.makeFromFile(dict);
final DictionaryInfo dictionaryInfo =
- createDictionaryInfoFromFileAddress(fileAddress);
+ createDictionaryInfoFromFileAddress(fileAddress, locale);
// 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 == null || !dictionaryInfo.mLocale.equals(locale)) continue;
+ if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) {
+ continue;
+ }
addOrUpdateDictInfo(dictList, dictionaryInfo);
}
}
@@ -368,25 +415,45 @@ public class DictionaryInfoUtils {
final int resourceId =
DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
context.getResources(), locale);
- if (0 == resourceId) continue;
+ if (0 == resourceId) {
+ continue;
+ }
final AssetFileAddress fileAddress =
BinaryDictionaryGetter.loadFallbackResource(context, resourceId);
- final DictionaryInfo dictionaryInfo = createDictionaryInfoFromFileAddress(fileAddress);
+ final DictionaryInfo dictionaryInfo = createDictionaryInfoFromFileAddress(fileAddress,
+ locale);
// 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;
+ // TODO: Remove dictionaryInfo == null when the static LMs have the headers.
+ if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) {
+ continue;
+ }
+ addOrUpdateDictInfo(dictList, dictionaryInfo);
+ }
+
+ // Generate the dictionary information from the enabled subtypes. This will not
+ // overwrite the real records.
+ RichInputMethodManager.init(context);
+ List<InputMethodSubtype> enabledSubtypes = RichInputMethodManager
+ .getInstance().getMyEnabledInputMethodSubtypeList(true);
+ for (InputMethodSubtype subtype : enabledSubtypes) {
+ Locale locale = LocaleUtils.constructLocaleFromString(subtype.getLocale());
+ DictionaryInfo dictionaryInfo = createDictionaryInfoFromLocale(locale);
addOrUpdateDictInfo(dictList, dictionaryInfo);
}
return dictList;
}
+ @UsedForTesting
public static boolean looksValidForDictionaryInsertion(final CharSequence text,
final SpacingAndPunctuations spacingAndPunctuations) {
- if (TextUtils.isEmpty(text)) return false;
+ if (TextUtils.isEmpty(text)) {
+ return false;
+ }
final int length = text.length();
- if (length > Constants.DICTIONARY_MAX_WORD_LENGTH) {
+ if (length > DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH) {
return false;
}
int i = 0;
@@ -400,7 +467,9 @@ public class DictionaryInfoUtils {
digitCount += charCount;
continue;
}
- if (!spacingAndPunctuations.isWordCodePoint(codePoint)) return false;
+ 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
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
deleted file mode 100644
index 787e4a59d..000000000
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import java.util.List;
-import java.util.Locale;
-
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.latin.PrevWordsInfo;
-
-public interface DistracterFilter {
- /**
- * Determine whether a word is a distracter to words in dictionaries.
- *
- * @param prevWordsInfo the information of previous words.
- * @param testedWord the word that will be tested to see whether it is a distracter to words
- * in dictionaries.
- * @param locale the locale of word.
- * @return true if testedWord is a distracter, otherwise false.
- */
- public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
- final String testedWord, final Locale locale);
-
- public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes);
-
- public void close();
-
- public static final DistracterFilter EMPTY_DISTRACTER_FILTER = new DistracterFilter() {
- @Override
- public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
- String testedWord, Locale locale) {
- return false;
- }
-
- @Override
- public void close() {
- }
-
- @Override
- public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
- }
- };
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
deleted file mode 100644
index 27973287d..000000000
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.text.InputType;
-import android.util.Log;
-import android.util.LruCache;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.PrevWordsInfo;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
-
-/**
- * This class is used to prevent distracters being added to personalization
- * or user history dictionaries
- */
-public class DistracterFilterCheckingExactMatchesAndSuggestions implements DistracterFilter {
- private static final String TAG =
- DistracterFilterCheckingExactMatchesAndSuggestions.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private static final long TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS = 120;
- private static final int MAX_DISTRACTERS_CACHE_SIZE = 512;
-
- private final Context mContext;
- private final Map<Locale, InputMethodSubtype> mLocaleToSubtypeMap;
- private final Map<Locale, Keyboard> mLocaleToKeyboardMap;
- private final DictionaryFacilitator mDictionaryFacilitator;
- private final LruCache<String, Boolean> mDistractersCache;
- private Keyboard mKeyboard;
- private final Object mLock = new Object();
-
- // If the score of the top suggestion exceeds this value, the tested word (e.g.,
- // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distractor to
- // words in dictionary. The greater the threshold is, the less likely the tested word would
- // become a distractor, which means the tested word will be more likely to be added to
- // the dictionary.
- private static final float DISTRACTER_WORD_SCORE_THRESHOLD = 0.4f;
-
- /**
- * Create a DistracterFilter instance.
- *
- * @param context the context.
- */
- public DistracterFilterCheckingExactMatchesAndSuggestions(final Context context) {
- mContext = context;
- mLocaleToSubtypeMap = new HashMap<>();
- mLocaleToKeyboardMap = new HashMap<>();
- mDictionaryFacilitator = new DictionaryFacilitator();
- mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
- mKeyboard = null;
- }
-
- @Override
- public void close() {
- mDictionaryFacilitator.closeDictionaries();
- }
-
- @Override
- public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
- final Map<Locale, InputMethodSubtype> newLocaleToSubtypeMap = new HashMap<>();
- if (enabledSubtypes != null) {
- for (final InputMethodSubtype subtype : enabledSubtypes) {
- final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
- if (newLocaleToSubtypeMap.containsKey(locale)) {
- // Multiple subtypes are enabled for one locale.
- // TODO: Investigate what we should do for this case.
- continue;
- }
- newLocaleToSubtypeMap.put(locale, subtype);
- }
- }
- if (mLocaleToSubtypeMap.equals(newLocaleToSubtypeMap)) {
- // Enabled subtypes have not been changed.
- return;
- }
- synchronized (mLock) {
- mLocaleToSubtypeMap.clear();
- mLocaleToSubtypeMap.putAll(newLocaleToSubtypeMap);
- mLocaleToKeyboardMap.clear();
- }
- }
-
- private void loadKeyboardForLocale(final Locale newLocale) {
- final Keyboard cachedKeyboard = mLocaleToKeyboardMap.get(newLocale);
- if (cachedKeyboard != null) {
- mKeyboard = cachedKeyboard;
- return;
- }
- final InputMethodSubtype subtype;
- synchronized (mLock) {
- subtype = mLocaleToSubtypeMap.get(newLocale);
- }
- if (subtype == null) {
- return;
- }
- final EditorInfo editorInfo = new EditorInfo();
- editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
- final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
- mContext, editorInfo);
- final Resources res = mContext.getResources();
- final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
- final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
- builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
- builder.setSubtype(subtype);
- builder.setIsSpellChecker(false /* isSpellChecker */);
- final KeyboardLayoutSet layoutSet = builder.build();
- mKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
- }
-
- private void loadDictionariesForLocale(final Locale newlocale) throws InterruptedException {
- mDictionaryFacilitator.resetDictionaries(mContext, newlocale,
- false /* useContactsDict */, false /* usePersonalizedDicts */,
- false /* forceReloadMainDictionary */, null /* listener */);
- mDictionaryFacilitator.waitForLoadingMainDictionary(
- TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS, TimeUnit.SECONDS);
- }
-
- /**
- * Determine whether a word is a distracter to words in dictionaries.
- *
- * @param prevWordsInfo the information of previous words. Not used for now.
- * @param testedWord the word that will be tested to see whether it is a distracter to words
- * in dictionaries.
- * @param locale the locale of word.
- * @return true if testedWord is a distracter, otherwise false.
- */
- @Override
- public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
- final String testedWord, final Locale locale) {
- if (locale == null) {
- return false;
- }
- if (!locale.equals(mDictionaryFacilitator.getLocale())) {
- synchronized (mLock) {
- if (!mLocaleToSubtypeMap.containsKey(locale)) {
- Log.e(TAG, "Locale " + locale + " is not enabled.");
- // TODO: Investigate what we should do for disabled locales.
- return false;
- }
- loadKeyboardForLocale(locale);
- // Reset dictionaries for the locale.
- try {
- mDistractersCache.evictAll();
- loadDictionariesForLocale(locale);
- } catch (final InterruptedException e) {
- Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter",
- e);
- return false;
- }
- }
- }
-
- if (DEBUG) {
- Log.d(TAG, "testedWord: " + testedWord);
- }
- final Boolean isCachedDistracter = mDistractersCache.get(testedWord);
- if (isCachedDistracter != null && isCachedDistracter) {
- if (DEBUG) {
- Log.d(TAG, "isDistracter: true (cache hit)");
- }
- return true;
- }
-
- final boolean isDistracterCheckedByGetMaxFreqencyOfExactMatches =
- checkDistracterUsingMaxFreqencyOfExactMatches(testedWord);
- if (isDistracterCheckedByGetMaxFreqencyOfExactMatches) {
- // Add the word to the cache.
- mDistractersCache.put(testedWord, Boolean.TRUE);
- return true;
- }
- final boolean isValidWord = mDictionaryFacilitator.isValidWord(testedWord,
- false /* ignoreCase */);
- if (isValidWord) {
- // Valid word is not a distractor.
- if (DEBUG) {
- Log.d(TAG, "isDistracter: false (valid word)");
- }
- return false;
- }
-
- final boolean isDistracterCheckedByGetSuggestion =
- checkDistracterUsingGetSuggestions(testedWord);
- if (isDistracterCheckedByGetSuggestion) {
- // Add the word to the cache.
- mDistractersCache.put(testedWord, Boolean.TRUE);
- return true;
- }
- return false;
- }
-
- private boolean checkDistracterUsingMaxFreqencyOfExactMatches(final String testedWord) {
- // The tested word is a distracter when there is a word that is exact matched to the tested
- // word and its probability is higher than the tested word's probability.
- final int perfectMatchFreq = mDictionaryFacilitator.getFrequency(testedWord);
- final int exactMatchFreq = mDictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord);
- final boolean isDistracter = perfectMatchFreq < exactMatchFreq;
- if (DEBUG) {
- Log.d(TAG, "perfectMatchFreq: " + perfectMatchFreq);
- Log.d(TAG, "exactMatchFreq: " + exactMatchFreq);
- Log.d(TAG, "isDistracter: " + isDistracter);
- }
- return isDistracter;
- }
-
- private boolean checkDistracterUsingGetSuggestions(final String testedWord) {
- if (mKeyboard == null) {
- return false;
- }
- final SettingsValuesForSuggestion settingsValuesForSuggestion =
- new SettingsValuesForSuggestion(false /* blockPotentiallyOffensive */,
- false /* spaceAwareGestureEnabled */,
- null /* additionalFeaturesSettingValues */);
- final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(testedWord);
- final String consideredWord = trailingSingleQuotesCount > 0 ?
- testedWord.substring(0, testedWord.length() - trailingSingleQuotesCount) :
- testedWord;
- final WordComposer composer = new WordComposer();
- final int[] codePoints = StringUtils.toCodePointArray(testedWord);
-
- synchronized (mLock) {
- final int[] coordinates = mKeyboard.getCoordinates(codePoints);
- composer.setComposingWord(codePoints, coordinates);
- final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- composer, PrevWordsInfo.EMPTY_PREV_WORDS_INFO, mKeyboard.getProximityInfo(),
- settingsValuesForSuggestion, 0 /* sessionId */);
- if (suggestionResults.isEmpty()) {
- return false;
- }
- final SuggestedWordInfo firstSuggestion = suggestionResults.first();
- final boolean isDistractor = suggestionExceedsDistracterThreshold(
- firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
- if (DEBUG) {
- Log.d(TAG, "isDistracter: " + isDistractor);
- }
- return isDistractor;
- }
- }
-
- private static boolean suggestionExceedsDistracterThreshold(final SuggestedWordInfo suggestion,
- final String consideredWord, final float distracterThreshold) {
- if (suggestion == null) {
- return false;
- }
- final int suggestionScore = suggestion.mScore;
- final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
- consideredWord, suggestion.mWord, suggestionScore);
- if (DEBUG) {
- Log.d(TAG, "normalizedScore: " + normalizedScore);
- Log.d(TAG, "distracterThreshold: " + distracterThreshold);
- }
- if (normalizedScore > distracterThreshold) {
- return true;
- }
- return false;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
deleted file mode 100644
index 4ad4ba784..000000000
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import java.util.List;
-import java.util.Locale;
-
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.PrevWordsInfo;
-
-public class DistracterFilterCheckingIsInDictionary implements DistracterFilter {
- private final DistracterFilter mDistracterFilter;
- private final Dictionary mDictionary;
-
- public DistracterFilterCheckingIsInDictionary(final DistracterFilter distracterFilter,
- final Dictionary dictionary) {
- mDistracterFilter = distracterFilter;
- mDictionary = dictionary;
- }
-
- @Override
- public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
- String testedWord, Locale locale) {
- if (mDictionary.isInDictionary(testedWord)) {
- // This filter treats entries that are already in the dictionary as non-distracters
- // because they have passed the filtering in the past.
- return false;
- } else {
- return mDistracterFilter.isDistracterToWordsInDictionaries(
- prevWordsInfo, testedWord, locale);
- }
- }
-
- @Override
- public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
- // Do nothing.
- }
-
- @Override
- public void close() {
- // Do nothing.
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
index 61da1b789..8ce6eff92 100644
--- a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
@@ -16,64 +16,136 @@
package com.android.inputmethod.latin.utils;
+import android.util.Log;
+
import com.android.inputmethod.annotations.UsedForTesting;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
+import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
/**
* Utilities to manage executors.
*/
public class ExecutorUtils {
- private static final ConcurrentHashMap<String, ExecutorService> sExecutorMap =
- new ConcurrentHashMap<>();
- private static class ThreadFactoryWithId implements ThreadFactory {
- private final String mId;
+ private static final String TAG = "ExecutorUtils";
+
+ public static final String KEYBOARD = "Keyboard";
+ public static final String SPELLING = "Spelling";
+
+ private static ScheduledExecutorService sKeyboardExecutorService = newExecutorService(KEYBOARD);
+ private static ScheduledExecutorService sSpellingExecutorService = newExecutorService(SPELLING);
+
+ private static ScheduledExecutorService newExecutorService(final String name) {
+ return Executors.newSingleThreadScheduledExecutor(new ExecutorFactory(name));
+ }
- public ThreadFactoryWithId(final String id) {
- mId = id;
+ private static class ExecutorFactory implements ThreadFactory {
+ private final String mName;
+
+ private ExecutorFactory(final String name) {
+ mName = name;
}
@Override
- public Thread newThread(final Runnable r) {
- return new Thread(r, "Executor - " + mId);
+ public Thread newThread(final Runnable runnable) {
+ Thread thread = new Thread(runnable, TAG);
+ thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread thread, Throwable ex) {
+ Log.w(mName + "-" + runnable.getClass().getSimpleName(), ex);
+ }
+ });
+ return thread;
}
}
+ @UsedForTesting
+ private static ScheduledExecutorService sExecutorServiceForTests;
+
+ @UsedForTesting
+ public static void setExecutorServiceForTests(
+ final ScheduledExecutorService executorServiceForTests) {
+ sExecutorServiceForTests = executorServiceForTests;
+ }
+
+ //
+ // Public methods used to schedule a runnable for execution.
+ //
+
/**
- * Gets the executor for the given id.
+ * @param name Executor's name.
+ * @return scheduled executor service used to run background tasks
*/
- public static ExecutorService getExecutor(final String id) {
- ExecutorService executor = sExecutorMap.get(id);
- if (executor == null) {
- synchronized(sExecutorMap) {
- executor = sExecutorMap.get(id);
- if (executor == null) {
- executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithId(id));
- sExecutorMap.put(id, executor);
- }
- }
+ public static ScheduledExecutorService getBackgroundExecutor(final String name) {
+ if (sExecutorServiceForTests != null) {
+ return sExecutorServiceForTests;
+ }
+ switch (name) {
+ case KEYBOARD:
+ return sKeyboardExecutorService;
+ case SPELLING:
+ return sSpellingExecutorService;
+ default:
+ throw new IllegalArgumentException("Invalid executor: " + name);
}
- return executor;
}
- /**
- * Shutdowns all executors and removes all executors from the executor map for testing.
- */
+ public static void killTasks(final String name) {
+ final ScheduledExecutorService executorService = getBackgroundExecutor(name);
+ executorService.shutdownNow();
+ try {
+ executorService.awaitTermination(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Log.wtf(TAG, "Failed to shut down: " + name);
+ }
+ if (executorService == sExecutorServiceForTests) {
+ // Don't do anything to the test service.
+ return;
+ }
+ switch (name) {
+ case KEYBOARD:
+ sKeyboardExecutorService = newExecutorService(KEYBOARD);
+ break;
+ case SPELLING:
+ sSpellingExecutorService = newExecutorService(SPELLING);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid executor: " + name);
+ }
+ }
+
+ @UsedForTesting
+ public static Runnable chain(final Runnable... runnables) {
+ return new RunnableChain(runnables);
+ }
+
@UsedForTesting
- public static void shutdownAllExecutors() {
- synchronized(sExecutorMap) {
- for (final ExecutorService executor : sExecutorMap.values()) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- executor.shutdown();
- sExecutorMap.remove(executor);
- }
- });
+ public static class RunnableChain implements Runnable {
+ private final Runnable[] mRunnables;
+
+ private RunnableChain(final Runnable... runnables) {
+ if (runnables == null || runnables.length == 0) {
+ throw new IllegalArgumentException("Attempting to construct an empty chain");
+ }
+ mRunnables = runnables;
+ }
+
+ @UsedForTesting
+ public Runnable[] getRunnables() {
+ return mRunnables;
+ }
+
+ @Override
+ public void run() {
+ for (Runnable runnable : mRunnables) {
+ if (Thread.interrupted()) {
+ return;
+ }
+ runnable.run();
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/FileUtils.java b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
deleted file mode 100644
index f1106a6c6..000000000
--- a/java/src/com/android/inputmethod/latin/utils/FileUtils.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.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/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
index c2167a76b..c87a7c05c 100644
--- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
@@ -18,18 +18,17 @@ package com.android.inputmethod.latin.utils;
import com.android.inputmethod.dictionarypack.DictionarySettingsFragment;
import com.android.inputmethod.latin.about.AboutPreferences;
+import com.android.inputmethod.latin.settings.AccountsSettingsFragment;
import com.android.inputmethod.latin.settings.AdvancedSettingsFragment;
import com.android.inputmethod.latin.settings.AppearanceSettingsFragment;
import com.android.inputmethod.latin.settings.CorrectionSettingsFragment;
import com.android.inputmethod.latin.settings.CustomInputStyleSettingsFragment;
import com.android.inputmethod.latin.settings.DebugSettingsFragment;
import com.android.inputmethod.latin.settings.GestureSettingsFragment;
-import com.android.inputmethod.latin.settings.MultiLingualSettingsFragment;
import com.android.inputmethod.latin.settings.PreferencesSettingsFragment;
import com.android.inputmethod.latin.settings.SettingsFragment;
import com.android.inputmethod.latin.settings.ThemeSettingsFragment;
import com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsFragment;
-import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordFragment;
import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker;
import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
@@ -42,9 +41,9 @@ public class FragmentUtils {
sLatinImeFragments.add(DictionarySettingsFragment.class.getName());
sLatinImeFragments.add(AboutPreferences.class.getName());
sLatinImeFragments.add(PreferencesSettingsFragment.class.getName());
+ sLatinImeFragments.add(AccountsSettingsFragment.class.getName());
sLatinImeFragments.add(AppearanceSettingsFragment.class.getName());
sLatinImeFragments.add(ThemeSettingsFragment.class.getName());
- sLatinImeFragments.add(MultiLingualSettingsFragment.class.getName());
sLatinImeFragments.add(CustomInputStyleSettingsFragment.class.getName());
sLatinImeFragments.add(GestureSettingsFragment.class.getName());
sLatinImeFragments.add(CorrectionSettingsFragment.class.getName());
@@ -52,7 +51,6 @@ public class FragmentUtils {
sLatinImeFragments.add(DebugSettingsFragment.class.getName());
sLatinImeFragments.add(SettingsFragment.class.getName());
sLatinImeFragments.add(SpellCheckerSettingsFragment.class.getName());
- sLatinImeFragments.add(UserDictionaryAddWordFragment.class.getName());
sLatinImeFragments.add(UserDictionaryList.class.getName());
sLatinImeFragments.add(UserDictionaryLocalePicker.class.getName());
sLatinImeFragments.add(UserDictionarySettings.class.getName());
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
index ea406fa75..df0cd8437 100644
--- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -25,6 +25,7 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.settings.SettingsValues;
import java.util.concurrent.TimeUnit;
@@ -53,7 +54,8 @@ public final class ImportantNoticeUtils {
// This utility class is not publicly instantiable.
}
- private static boolean isInSystemSetupWizard(final Context context) {
+ @UsedForTesting
+ static boolean isInSystemSetupWizard(final Context context) {
try {
final int userSetupComplete = Settings.Secure.getInt(
context.getContentResolver(), Settings_Secure_USER_SETUP_COMPLETE);
@@ -84,7 +86,8 @@ public final class ImportantNoticeUtils {
return getLastImportantNoticeVersion(context) + 1;
}
- private static boolean hasNewImportantNotice(final Context context) {
+ @UsedForTesting
+ static boolean hasNewImportantNotice(final Context context) {
final int lastVersion = getLastImportantNoticeVersion(context);
return getCurrentImportantNoticeVersion(context) > lastVersion;
}
@@ -103,7 +106,12 @@ public final class ImportantNoticeUtils {
return elapsedTime >= TIMEOUT_OF_IMPORTANT_NOTICE;
}
- public static boolean shouldShowImportantNotice(final Context context) {
+ public static boolean shouldShowImportantNotice(final Context context,
+ final SettingsValues settingsValues) {
+ // Check to see whether personalization is enabled by the user.
+ if (!settingsValues.isPersonalizationEnabled()) {
+ return false;
+ }
if (!hasNewImportantNotice(context)) {
return false;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
deleted file mode 100644
index fbce3f2fd..000000000
--- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.util.Log;
-
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.PrevWordsInfo;
-import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
-
-import java.util.ArrayList;
-import java.util.List;
-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 CharSequence 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 CharSequence 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 CharSequence word0, final CharSequence 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 List<String> tokens, final int timestamp,
- final DictionaryFacilitator dictionaryFacilitator,
- final SpacingAndPunctuations spacingAndPunctuations,
- final DistracterFilter distracterFilter) {
- final ArrayList<LanguageModelParam> languageModelParams = new ArrayList<>();
- final int N = tokens.size();
- PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
- 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.
- prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
- continue;
- }
- if (DEBUG_TOKEN) {
- Log.d(TAG, "--- word: \"" + tempWord + "\"");
- }
- final LanguageModelParam languageModelParam =
- detectWhetherVaildWordOrNotAndGetLanguageModelParam(
- prevWordsInfo, tempWord, timestamp, dictionaryFacilitator,
- distracterFilter);
- if (languageModelParam == null) {
- continue;
- }
- languageModelParams.add(languageModelParam);
- prevWordsInfo = prevWordsInfo.getNextPrevWordsInfo(
- new PrevWordsInfo.WordInfo(tempWord));
- }
- return languageModelParams;
- }
-
- private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam(
- final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
- final DictionaryFacilitator dictionaryFacilitator,
- final DistracterFilter distracterFilter) {
- final Locale locale = dictionaryFacilitator.getLocale();
- if (locale == null) {
- return null;
- }
- if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) {
- return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
- true /* isValidWord */, locale, distracterFilter);
- }
-
- final String lowerCaseTargetWord = targetWord.toLowerCase(locale);
- if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
- // Add the lower-cased word.
- return createAndGetLanguageModelParamOfWord(prevWordsInfo, lowerCaseTargetWord,
- timestamp, true /* isValidWord */, locale, distracterFilter);
- }
-
- // Treat the word as an OOV word.
- return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
- false /* isValidWord */, locale, distracterFilter);
- }
-
- private static LanguageModelParam createAndGetLanguageModelParamOfWord(
- final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
- final boolean isValidWord, final Locale locale,
- final DistracterFilter distracterFilter) {
- final String word;
- if (StringUtils.getCapitalizationType(targetWord) == StringUtils.CAPITALIZE_FIRST
- && !prevWordsInfo.isValid() && !isValidWord) {
- word = targetWord.toLowerCase(locale);
- } else {
- word = targetWord;
- }
- // Check whether the word is a distracter to words in the dictionaries.
- if (distracterFilter.isDistracterToWordsInDictionaries(prevWordsInfo, word, locale)) {
- if (DEBUG) {
- Log.d(TAG, "The word (" + word + ") is a distracter. Skip this word.");
- }
- return null;
- }
- final int unigramProbability = isValidWord ?
- UNIGRAM_PROBABILITY_FOR_VALID_WORD : UNIGRAM_PROBABILITY_FOR_OOV_WORD;
- if (!prevWordsInfo.isValid()) {
- 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 = " + prevWordsInfo + ", current("
- + (isValidWord ? "Valid" : "OOV") + ") = " + word);
- }
- final int bigramProbability = isValidWord ?
- BIGRAM_PROBABILITY_FOR_VALID_WORD : BIGRAM_PROBABILITY_FOR_OOV_WORD;
- return new LanguageModelParam(prevWordsInfo.mPrevWordsInfo[0].mWord, word,
- unigramProbability, bigramProbability, timestamp);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageOnSpacebarUtils.java b/java/src/com/android/inputmethod/latin/utils/LanguageOnSpacebarUtils.java
new file mode 100644
index 000000000..a5a1ea921
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageOnSpacebarUtils.java
@@ -0,0 +1,92 @@
+/*
+ * 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;
+
+import com.android.inputmethod.latin.RichInputMethodSubtype;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.annotation.Nonnull;
+
+/**
+ * This class determines that the language name on the spacebar should be displayed in what format.
+ */
+public final class LanguageOnSpacebarUtils {
+ 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 static List<InputMethodSubtype> sEnabledSubtypes = Collections.emptyList();
+ private static boolean sIsSystemLanguageSameAsInputLanguage;
+
+ private LanguageOnSpacebarUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static int getLanguageOnSpacebarFormatType(
+ @Nonnull final RichInputMethodSubtype subtype) {
+ if (subtype.isNoLanguage()) {
+ return FORMAT_TYPE_FULL_LOCALE;
+ }
+ // Only this subtype is enabled and equals to the system locale.
+ if (sEnabledSubtypes.size() < 2 && sIsSystemLanguageSameAsInputLanguage) {
+ return FORMAT_TYPE_NONE;
+ }
+ final Locale locale = subtype.getLocale();
+ if (locale == null) {
+ return FORMAT_TYPE_NONE;
+ }
+ final String keyboardLanguage = locale.getLanguage();
+ final String keyboardLayout = subtype.getKeyboardLayoutSetName();
+ int sameLanguageAndLayoutCount = 0;
+ for (final InputMethodSubtype ims : sEnabledSubtypes) {
+ 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 static void setEnabledSubtypes(@Nonnull final List<InputMethodSubtype> enabledSubtypes) {
+ sEnabledSubtypes = enabledSubtypes;
+ }
+
+ public static void onSubtypeChanged(@Nonnull final RichInputMethodSubtype subtype,
+ final boolean implicitlyEnabledSubtype, @Nonnull final Locale systemLocale) {
+ final Locale newLocale = subtype.getLocale();
+ if (systemLocale.equals(newLocale)) {
+ sIsSystemLanguageSameAsInputLanguage = true;
+ return;
+ }
+ if (!systemLocale.getLanguage().equals(newLocale.getLanguage())) {
+ sIsSystemLanguageSameAsInputLanguage = false;
+ return;
+ }
+ // If the subtype is enabled explicitly, the language name should be displayed even when
+ // the keyboard language and the system language are equal.
+ sIsSystemLanguageSameAsInputLanguage = implicitlyEnabledSubtype;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
index dd6fac671..9a5be99b3 100644
--- a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
+++ b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
@@ -21,21 +21,22 @@ import android.os.Looper;
import java.lang.ref.WeakReference;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
public class LeakGuardHandlerWrapper<T> extends Handler {
private final WeakReference<T> mOwnerInstanceRef;
- public LeakGuardHandlerWrapper(final T ownerInstance) {
+ public LeakGuardHandlerWrapper(@Nonnull final T ownerInstance) {
this(ownerInstance, Looper.myLooper());
}
- public LeakGuardHandlerWrapper(final T ownerInstance, final Looper looper) {
+ public LeakGuardHandlerWrapper(@Nonnull final T ownerInstance, final Looper looper) {
super(looper);
- if (ownerInstance == null) {
- throw new NullPointerException("ownerInstance is null");
- }
mOwnerInstanceRef = new WeakReference<>(ownerInstance);
}
+ @Nullable
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
deleted file mode 100644
index c519a0de6..000000000
--- a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
+++ /dev/null
@@ -1,190 +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.utils;
-
-import android.text.TextUtils;
-
-import java.util.HashMap;
-import java.util.Locale;
-
-/**
- * A class to help with handling Locales in string form.
- *
- * This file has the same meaning and features (and shares all of its code) with
- * the one in the dictionary pack. They need to be kept synchronized; for any
- * update/bugfix to this file, consider also updating/fixing the version in the
- * dictionary pack.
- */
-public final class LocaleUtils {
- private LocaleUtils() {
- // Intentional empty constructor for utility class.
- }
-
- // Locale match level constants.
- // A higher level of match is guaranteed to have a higher numerical value.
- // Some room is left within constants to add match cases that may arise necessary
- // in the future, for example differentiating between the case where the countries
- // are both present and different, and the case where one of the locales does not
- // specify the countries. This difference is not needed now.
-
- // Nothing matches.
- public static final int LOCALE_NO_MATCH = 0;
- // The languages matches, but the country are different. Or, the reference locale requires a
- // country and the tested locale does not have one.
- public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3;
- // The languages and country match, but the variants are different. Or, the reference locale
- // requires a variant and the tested locale does not have one.
- public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6;
- // The required locale is null or empty so it will accept anything, and the tested locale
- // is non-null and non-empty.
- public static final int LOCALE_ANY_MATCH = 10;
- // The language matches, and the tested locale specifies a country but the reference locale
- // does not require one.
- public static final int LOCALE_LANGUAGE_MATCH = 15;
- // The language and the country match, and the tested locale specifies a variant but the
- // reference locale does not require one.
- public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20;
- // The compared locales are fully identical. This is the best match level.
- public static final int LOCALE_FULL_MATCH = 30;
-
- // The level at which a match is "normally" considered a locale match with standard algorithms.
- // Don't use this directly, use #isMatch to test.
- private static final int LOCALE_MATCH = LOCALE_ANY_MATCH;
-
- // Make this match the maximum match level. If this evolves to have more than 2 digits
- // when written in base 10, also adjust the getMatchLevelSortedString method.
- private static final int MATCH_LEVEL_MAX = 30;
-
- /**
- * Return how well a tested locale matches a reference locale.
- *
- * This will check the tested locale against the reference locale and return a measure of how
- * a well it matches the reference. The general idea is that the tested locale has to match
- * every specified part of the required locale. A full match occur when they are equal, a
- * partial match when the tested locale agrees with the reference locale but is more specific,
- * and a difference when the tested locale does not comply with all requirements from the
- * reference locale.
- * In more detail, if the reference locale specifies at least a language and the testedLocale
- * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the
- * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH
- * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and
- * tested locale agree on the language, but not on the country,
- * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country,
- * and LOCALE_LANGUAGE_MATCH otherwise.
- * If they agree on both the language and the country, but not on the variant,
- * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale
- * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches,
- * LOCALE_FULL_MATCH is returned.
- * Examples:
- * en <=> en_US => LOCALE_LANGUAGE_MATCH
- * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER
- * en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER
- * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH
- * sp_US <=> en_US => LOCALE_NO_MATCH
- * de <=> de => LOCALE_FULL_MATCH
- * en_US <=> en_US => LOCALE_FULL_MATCH
- * "" <=> en_US => LOCALE_ANY_MATCH
- *
- * @param referenceLocale the reference locale to test against.
- * @param testedLocale the locale to test.
- * @return a constant that measures how well the tested locale matches the reference locale.
- */
- public static int getMatchLevel(String referenceLocale, String testedLocale) {
- if (TextUtils.isEmpty(referenceLocale)) {
- return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH;
- }
- if (null == testedLocale) return LOCALE_NO_MATCH;
- String[] referenceParams = referenceLocale.split("_", 3);
- String[] testedParams = testedLocale.split("_", 3);
- // By spec of String#split, [0] cannot be null and length cannot be 0.
- if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH;
- switch (referenceParams.length) {
- case 1:
- return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH;
- case 2:
- if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (!referenceParams[1].equals(testedParams[1]))
- return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH;
- return LOCALE_FULL_MATCH;
- case 3:
- if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (!referenceParams[1].equals(testedParams[1]))
- return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
- if (!referenceParams[2].equals(testedParams[2]))
- return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
- return LOCALE_FULL_MATCH;
- }
- // It should be impossible to come here
- return LOCALE_NO_MATCH;
- }
-
- /**
- * Return a string that represents this match level, with better matches first.
- *
- * The strings are sorted in lexicographic order: a better match will always be less than
- * a worse match when compared together.
- */
- public static String getMatchLevelSortedString(int matchLevel) {
- // This works because the match levels are 0~99 (actually 0~30)
- // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel
- return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel);
- }
-
- /**
- * Find out whether a match level should be considered a match.
- *
- * This method takes a match level as returned by the #getMatchLevel method, and returns whether
- * it should be considered a match in the usual sense with standard Locale functions.
- *
- * @param level the match level, as returned by getMatchLevel.
- * @return whether this is a match or not.
- */
- public static boolean isMatch(int level) {
- return LOCALE_MATCH <= level;
- }
-
- private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();
-
- /**
- * Creates a locale from a string specification.
- */
- public static Locale constructLocaleFromString(final String localeStr) {
- if (localeStr == null) {
- return null;
- }
- synchronized (sLocaleCache) {
- Locale retval = sLocaleCache.get(localeStr);
- if (retval != null) {
- return retval;
- }
- String[] localeParams = localeStr.split("_", 3);
- if (localeParams.length == 1) {
- retval = new Locale(localeParams[0]);
- } else if (localeParams.length == 2) {
- retval = new Locale(localeParams[0], localeParams[1]);
- } else if (localeParams.length == 3) {
- retval = new Locale(localeParams[0], localeParams[1], localeParams[2]);
- }
- if (retval != null) {
- sLocaleCache.put(localeStr, retval);
- }
- return retval;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java
index 3cd63612c..c05ffd693 100644
--- a/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java
@@ -16,18 +16,22 @@
package com.android.inputmethod.latin.utils;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.NgramContext.WordInfo;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+
+import java.util.Arrays;
import java.util.regex.Pattern;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.PrevWordsInfo;
-import com.android.inputmethod.latin.PrevWordsInfo.WordInfo;
-import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import javax.annotation.Nonnull;
-public final class PrevWordsInfoUtils {
- private PrevWordsInfoUtils() {
+public final class NgramContextUtils {
+ private NgramContextUtils() {
// Intentional empty constructor for utility class.
}
+ private static final Pattern NEWLINE_REGEX = Pattern.compile("[\\r\\n]+");
private static final Pattern SPACE_REGEX = Pattern.compile("\\s+");
// Get context information from nth word before the cursor. n = 1 retrieves the words
// immediately before the cursor, n = 2 retrieves the words before that, and so on. This splits
@@ -43,7 +47,7 @@ public final class PrevWordsInfoUtils {
// (n = 2) "abc def|" -> beginning-of-sentence, abc
// (n = 2) "abc def |" -> beginning-of-sentence, abc
// (n = 2) "abc 'def|" -> empty. The context is different from "abc def", but we cannot
- // represent this situation using PrevWordsInfo. See TODO in the method.
+ // represent this situation using NgramContext. See TODO in the method.
// TODO: The next example's result should be "abc, def". This have to be fixed before we
// retrieve the prior context of Beginning-of-Sentence.
// (n = 2) "abc def. |" -> beginning-of-sentence, abc
@@ -51,11 +55,18 @@ public final class PrevWordsInfoUtils {
// (n = 2) "abc|" -> beginning-of-sentence
// (n = 2) "abc |" -> beginning-of-sentence
// (n = 2) "abc. def|" -> beginning-of-sentence
- public static PrevWordsInfo getPrevWordsInfoFromNthPreviousWord(final CharSequence prev,
+ @Nonnull
+ public static NgramContext getNgramContextFromNthPreviousWord(final CharSequence prev,
final SpacingAndPunctuations spacingAndPunctuations, final int n) {
- if (prev == null) return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
- final String[] w = SPACE_REGEX.split(prev);
- final WordInfo[] prevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ if (prev == null) return NgramContext.EMPTY_PREV_WORDS_INFO;
+ final String[] lines = NEWLINE_REGEX.split(prev);
+ if (lines.length == 0) {
+ return new NgramContext(WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO);
+ }
+ final String[] w = SPACE_REGEX.split(lines[lines.length - 1]);
+ final WordInfo[] prevWordsInfo =
+ new WordInfo[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ Arrays.fill(prevWordsInfo, WordInfo.EMPTY_WORD_INFO);
for (int i = 0; i < prevWordsInfo.length; i++) {
final int focusedWordIndex = w.length - n - i;
// Referring to the word after the focused word.
@@ -66,38 +77,37 @@ public final class PrevWordsInfoUtils {
if (spacingAndPunctuations.isWordConnector(firstChar)) {
// The word following the focused word is starting with a word connector.
// TODO: Return meaningful context for this case.
- prevWordsInfo[i] = WordInfo.EMPTY_WORD_INFO;
break;
}
}
}
// If we can't find (n + i) words, the context is beginning-of-sentence.
if (focusedWordIndex < 0) {
- prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE;
+ prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO;
break;
}
+
final String focusedWord = w[focusedWordIndex];
- // If the word is, the context is beginning-of-sentence.
+ // If the word is empty, the context is beginning-of-sentence.
final int length = focusedWord.length();
if (length <= 0) {
- prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE;
+ prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO;
break;
}
- // If ends in a sentence separator, the context is beginning-of-sentence.
+ // If the word ends in a sentence terminator, the context is beginning-of-sentence.
final char lastChar = focusedWord.charAt(length - 1);
- if (spacingAndPunctuations.isSentenceSeparator(lastChar)) {
- prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE;
+ if (spacingAndPunctuations.isSentenceTerminator(lastChar)) {
+ prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO;
break;
}
// If ends in a word separator or connector, the context is unclear.
// TODO: Return meaningful context for this case.
if (spacingAndPunctuations.isWordSeparator(lastChar)
|| spacingAndPunctuations.isWordConnector(lastChar)) {
- prevWordsInfo[i] = WordInfo.EMPTY_WORD_INFO;
break;
}
prevWordsInfo[i] = new WordInfo(focusedWord);
}
- return new PrevWordsInfo(prevWordsInfo);
+ return new NgramContext(prevWordsInfo);
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
index e3cac97f0..a381649a4 100644
--- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
+++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin.utils;
+import com.android.inputmethod.latin.common.StringUtils;
+
import java.util.Locale;
/**
@@ -49,6 +51,17 @@ public class RecapitalizeStatus {
}
}
+ public static String modeToString(final int recapitalizeMode) {
+ switch (recapitalizeMode) {
+ case NOT_A_RECAPITALIZE_MODE: return "undefined";
+ case CAPS_MODE_ORIGINAL_MIXED_CASE: return "mixedCase";
+ case CAPS_MODE_ALL_LOWER: return "allLower";
+ case CAPS_MODE_FIRST_WORD_UPPER: return "firstWordUpper";
+ case CAPS_MODE_ALL_UPPER: return "allUpper";
+ default: return "unknown<" + recapitalizeMode + ">";
+ }
+ }
+
/**
* We store the location of the cursor and the string that was there before the recapitalize
* action was done, and the location of the cursor and the string that was there after.
diff --git a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
deleted file mode 100644
index 64c9e2cff..000000000
--- a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
+++ /dev/null
@@ -1,155 +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 java.util.Arrays;
-
-// TODO: This class is not thread-safe.
-public final class ResizableIntArray {
- private int[] mArray;
- private int mLength;
-
- public ResizableIntArray(final int capacity) {
- reset(capacity);
- }
-
- public int get(final int index) {
- if (index < mLength) {
- return mArray[index];
- }
- throw new ArrayIndexOutOfBoundsException("length=" + mLength + "; index=" + index);
- }
-
- public void addAt(final int index, final int val) {
- if (index < mLength) {
- mArray[index] = val;
- } else {
- mLength = index;
- add(val);
- }
- }
-
- public void add(final int val) {
- final int currentLength = mLength;
- ensureCapacity(currentLength + 1);
- mArray[currentLength] = val;
- mLength = currentLength + 1;
- }
-
- /**
- * Calculate the new capacity of {@code mArray}.
- * @param minimumCapacity the minimum capacity that the {@code mArray} should have.
- * @return the new capacity that the {@code mArray} should have. Returns zero when there is no
- * need to expand {@code mArray}.
- */
- private int calculateCapacity(final int minimumCapacity) {
- final int currentCapcity = mArray.length;
- if (currentCapcity < minimumCapacity) {
- final int nextCapacity = currentCapcity * 2;
- // The following is the same as return Math.max(minimumCapacity, nextCapacity);
- return minimumCapacity > nextCapacity ? minimumCapacity : nextCapacity;
- }
- return 0;
- }
-
- private void ensureCapacity(final int minimumCapacity) {
- final int newCapacity = calculateCapacity(minimumCapacity);
- if (newCapacity > 0) {
- // TODO: Implement primitive array pool.
- mArray = Arrays.copyOf(mArray, newCapacity);
- }
- }
-
- public int getLength() {
- return mLength;
- }
-
- public void setLength(final int newLength) {
- ensureCapacity(newLength);
- mLength = newLength;
- }
-
- public void reset(final int capacity) {
- // TODO: Implement primitive array pool.
- mArray = new int[capacity];
- mLength = 0;
- }
-
- public int[] getPrimitiveArray() {
- return mArray;
- }
-
- public void set(final ResizableIntArray ip) {
- // TODO: Implement primitive array pool.
- mArray = ip.mArray;
- mLength = ip.mLength;
- }
-
- public void copy(final ResizableIntArray ip) {
- final int newCapacity = calculateCapacity(ip.mLength);
- if (newCapacity > 0) {
- // TODO: Implement primitive array pool.
- mArray = new int[newCapacity];
- }
- System.arraycopy(ip.mArray, 0, mArray, 0, ip.mLength);
- mLength = ip.mLength;
- }
-
- public void append(final ResizableIntArray src, final int startPos, final int length) {
- if (length == 0) {
- return;
- }
- final int currentLength = mLength;
- final int newLength = currentLength + length;
- ensureCapacity(newLength);
- System.arraycopy(src.mArray, startPos, mArray, currentLength, length);
- mLength = newLength;
- }
-
- public void fill(final int value, final int startPos, final int length) {
- if (startPos < 0 || length < 0) {
- throw new IllegalArgumentException("startPos=" + startPos + "; length=" + length);
- }
- final int endPos = startPos + length;
- ensureCapacity(endPos);
- Arrays.fill(mArray, startPos, endPos, value);
- if (mLength < endPos) {
- mLength = endPos;
- }
- }
-
- /**
- * Shift to the left by elementCount, discarding elementCount pointers at the start.
- * @param elementCount how many elements to shift.
- */
- public void shift(final int elementCount) {
- System.arraycopy(mArray, elementCount, mArray, 0, mLength - elementCount);
- mLength -= elementCount;
- }
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mLength; i++) {
- if (i != 0) {
- sb.append(",");
- }
- sb.append(mArray[i]);
- }
- return "[" + sb + "]";
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
index 093c5a6c1..cc0d470df 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
@@ -26,6 +26,7 @@ import android.util.TypedValue;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.settings.SettingsValues;
import java.util.ArrayList;
import java.util.HashMap;
@@ -110,7 +111,6 @@ public final class ResourceUtils {
* are true for the specified key value pairs.
*
* For example, "condition,constant" has the following format.
- * (See {@link ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp()})
* - HARDWARE=mako,constantForNexus4
* - MODEL=Nexus 4:MANUFACTURER=LGE,constantForNexus4
* - ,defaultConstant
@@ -119,6 +119,7 @@ public final class ResourceUtils {
* @param conditionConstantArray an array of "condition,constant" elements to be searched.
* @return the constant part of the matched "condition,constant" element. Returns null if no
* condition matches.
+ * @see com.android.inputmethod.latin.utils.ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp()
*/
@UsedForTesting
static String findConstantForKeyValuePairs(final HashMap<String, String> keyValuePairs,
@@ -186,6 +187,15 @@ public final class ResourceUtils {
return dm.widthPixels;
}
+ public static int getKeyboardHeight(final Resources res, final SettingsValues settingsValues) {
+ final int defaultKeyboardHeight = getDefaultKeyboardHeight(res);
+ if (settingsValues.mHasKeyboardResize) {
+ // mKeyboardHeightScale Ranges from [.5,1.2], from xml/prefs_screen_debug.xml
+ return (int)(defaultKeyboardHeight * settingsValues.mKeyboardHeightScale);
+ }
+ return defaultKeyboardHeight;
+ }
+
public static int getDefaultKeyboardHeight(final Resources res) {
final DisplayMetrics dm = res.getDisplayMetrics();
final String keyboardHeightInDp = getDeviceOverrideValue(
diff --git a/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java b/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
index 0e244666d..4679f7814 100644
--- a/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
@@ -23,9 +23,10 @@ import java.util.TreeMap;
* A class to help with handling different writing scripts.
*/
public class ScriptUtils {
+
// Used for hardware keyboards
public static final int SCRIPT_UNKNOWN = -1;
- // TODO: should we use ISO 15924 identifiers instead?
+
public static final int SCRIPT_ARABIC = 0;
public static final int SCRIPT_ARMENIAN = 1;
public static final int SCRIPT_BENGALI = 2;
@@ -44,35 +45,31 @@ public class ScriptUtils {
public static final int SCRIPT_TAMIL = 15;
public static final int SCRIPT_TELUGU = 16;
public static final int SCRIPT_THAI = 17;
- public static final TreeMap<String, Integer> mSpellCheckerLanguageToScript;
+
+ private static final TreeMap<String, Integer> mLanguageCodeToScriptCode;
+
static {
- // List of the supported languages and their associated script. We won't check
- // words written in another script than the selected script, because we know we
- // don't have those in our dictionary so we will underline everything and we
- // will never have any suggestions, so it makes no sense checking them, and this
- // is done in {@link #shouldFilterOut}. Also, the script is used to choose which
- // proximity to pass to the dictionary descent algorithm.
- // IMPORTANT: this only contains languages - do not write countries in there.
- // Only the language is searched from the map.
- mSpellCheckerLanguageToScript = new TreeMap<>();
- mSpellCheckerLanguageToScript.put("cs", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("da", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("de", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("el", SCRIPT_GREEK);
- mSpellCheckerLanguageToScript.put("en", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("es", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("fi", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("fr", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("hr", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("it", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("lt", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("lv", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("nb", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("nl", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("pt", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("sl", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("ru", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode = new TreeMap<>();
+ mLanguageCodeToScriptCode.put("", SCRIPT_LATIN); // default
+ mLanguageCodeToScriptCode.put("ar", SCRIPT_ARABIC);
+ mLanguageCodeToScriptCode.put("hy", SCRIPT_ARMENIAN);
+ mLanguageCodeToScriptCode.put("bn", SCRIPT_BENGALI);
+ mLanguageCodeToScriptCode.put("bg", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode.put("sr", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode.put("ru", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode.put("ka", SCRIPT_GEORGIAN);
+ mLanguageCodeToScriptCode.put("el", SCRIPT_GREEK);
+ mLanguageCodeToScriptCode.put("he", SCRIPT_HEBREW);
+ mLanguageCodeToScriptCode.put("km", SCRIPT_KHMER);
+ mLanguageCodeToScriptCode.put("lo", SCRIPT_LAO);
+ mLanguageCodeToScriptCode.put("ml", SCRIPT_MALAYALAM);
+ mLanguageCodeToScriptCode.put("my", SCRIPT_MYANMAR);
+ mLanguageCodeToScriptCode.put("si", SCRIPT_SINHALA);
+ mLanguageCodeToScriptCode.put("ta", SCRIPT_TAMIL);
+ mLanguageCodeToScriptCode.put("te", SCRIPT_TELUGU);
+ mLanguageCodeToScriptCode.put("th", SCRIPT_THAI);
}
+
/*
* Returns whether the code point is a letter that makes sense for the specified
* locale for this spell checker.
@@ -181,11 +178,17 @@ public class ScriptUtils {
}
}
+ /**
+ * @param locale spell checker locale
+ * @return internal Latin IME script code that maps to a language code
+ * {@see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes}
+ */
public static int getScriptFromSpellCheckerLocale(final Locale locale) {
- final Integer script = mSpellCheckerLanguageToScript.get(locale.getLanguage());
- if (null == script) {
- throw new RuntimeException("We have been called with an unsupported language: \""
- + locale.getLanguage() + "\". Framework bug?");
+ String language = locale.getLanguage();
+ Integer script = mLanguageCodeToScriptCode.get(language);
+ if (script == null) {
+ // Default to Latin.
+ script = mLanguageCodeToScriptCode.get("");
}
return script;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
deleted file mode 100644
index 1ca895fdb..000000000
--- a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.view.inputmethod.InputMethodSubtype;
-
-public final class SpacebarLanguageUtils {
- private SpacebarLanguageUtils() {
- // Intentional empty constructor for utility class.
- }
-
- // InputMethodSubtype's display name for spacebar text in its locale.
- // isAdditionalSubtype (T=true, F=false)
- // locale layout | Middle Full
- // ------ ------- - --------- ----------------------
- // en_US qwerty F English English (US) exception
- // en_GB qwerty F English English (UK) exception
- // es_US spanish F Español Español (EE.UU.) exception
- // fr azerty F Français Français
- // fr_CA qwerty F Français Français (Canada)
- // fr_CH swiss F Français Français (Suisse)
- // de qwertz F Deutsch Deutsch
- // de_CH swiss T Deutsch Deutsch (Schweiz)
- // zz qwerty F QWERTY QWERTY
- // fr qwertz T Français Français
- // de qwerty T Deutsch Deutsch
- // en_US azerty T English English (US)
- // zz azerty T AZERTY AZERTY
- // Get InputMethodSubtype's full display name in its locale.
- public static String getFullDisplayName(final InputMethodSubtype subtype) {
- if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
- return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
- }
- return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(subtype.getLocale());
- }
-
- // Get InputMethodSubtype's middle display name in its locale.
- public static String getMiddleDisplayName(final InputMethodSubtype subtype) {
- if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
- return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
- }
- return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(subtype.getLocale());
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
index 38164cb36..c41817fe6 100644
--- a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
@@ -24,6 +24,12 @@ import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.text.style.URLSpan;
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
public final class SpannableStringUtils {
/**
* Copies the spans from the region <code>start...end</code> in
@@ -51,7 +57,7 @@ public final class SpannableStringUtils {
// 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;
+ fl &= ~Spanned.SPAN_PARAGRAPH;
int st = source.getSpanStart(spans[i]);
int en = source.getSpanEnd(spans[i]);
@@ -125,4 +131,53 @@ public final class SpannableStringUtils {
final URLSpan[] spans = spanned.getSpans(startIndex - 1, endIndex + 1, URLSpan.class);
return null != spans && spans.length > 0;
}
+
+ /**
+ * Splits the given {@code charSequence} with at occurrences of the given {@code regex}.
+ * <p>
+ * This is equivalent to
+ * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)}
+ * except that the spans are preserved in the result array.
+ * </p>
+ * @param charSequence the character sequence to be split.
+ * @param regex the regex pattern to be used as the separator.
+ * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty
+ * segments. Otherwise, trailing empty segments will be removed before being returned.
+ * @return the array which contains the result. All the spans in the <code>charSequence</code>
+ * is preserved.
+ */
+ @UsedForTesting
+ public static CharSequence[] split(final CharSequence charSequence, final String regex,
+ final boolean preserveTrailingEmptySegments) {
+ // A short-cut for non-spanned strings.
+ if (!(charSequence instanceof Spanned)) {
+ // -1 means that trailing empty segments will be preserved.
+ return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0);
+ }
+
+ // Hereafter, emulate String.split for CharSequence.
+ final ArrayList<CharSequence> sequences = new ArrayList<>();
+ final Matcher matcher = Pattern.compile(regex).matcher(charSequence);
+ int nextStart = 0;
+ boolean matched = false;
+ while (matcher.find()) {
+ sequences.add(charSequence.subSequence(nextStart, matcher.start()));
+ nextStart = matcher.end();
+ matched = true;
+ }
+ if (!matched) {
+ // never matched. preserveTrailingEmptySegments is ignored in this case.
+ return new CharSequence[] { charSequence };
+ }
+ sequences.add(charSequence.subSequence(nextStart, charSequence.length()));
+ if (!preserveTrailingEmptySegments) {
+ for (int i = sequences.size() - 1; i >= 0; --i) {
+ if (!TextUtils.isEmpty(sequences.get(i))) {
+ break;
+ }
+ sequences.remove(i);
+ }
+ }
+ return sequences.toArray(new CharSequence[sequences.size()]);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
deleted file mode 100644
index 79128dbd2..000000000
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ /dev/null
@@ -1,646 +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 static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
-
-import android.text.Spanned;
-import android.text.TextUtils;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public final class StringUtils {
- public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
- 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 static final char CHAR_LINE_FEED = 0X000A;
- private static final char CHAR_VERTICAL_TAB = 0X000B;
- private static final char CHAR_FORM_FEED = 0X000C;
- private static final char CHAR_CARRIAGE_RETURN = 0X000D;
- private static final char CHAR_NEXT_LINE = 0X0085;
- private static final char CHAR_LINE_SEPARATOR = 0X2028;
- private static final char CHAR_PARAGRAPH_SEPARATOR = 0X2029;
-
- private StringUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static int codePointCount(final String text) {
- if (TextUtils.isEmpty(text)) return 0;
- return text.codePointCount(0, text.length());
- }
-
- public static String newSingleCodePointString(int codePoint) {
- if (Character.charCount(codePoint) == 1) {
- // Optimization: avoid creating a temporary array for characters that are
- // represented by a single char value
- return String.valueOf((char) codePoint);
- }
- // For surrogate pair
- return new String(Character.toChars(codePoint));
- }
-
- public static boolean containsInArray(final String text, final String[] array) {
- for (final String element : array) {
- if (text.equals(element)) return true;
- }
- return false;
- }
-
- /**
- * Comma-Splittable Text is similar to Comma-Separated Values (CSV) but has much simpler syntax.
- * Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain
- * a comma character in it.
- */
- private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ",";
-
- public static boolean containsInCommaSplittableText(final String text,
- final String extraValues) {
- if (TextUtils.isEmpty(extraValues)) {
- return false;
- }
- return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
- }
-
- public static String removeFromCommaSplittableTextIfExists(final String text,
- final String extraValues) {
- if (TextUtils.isEmpty(extraValues)) {
- return EMPTY_STRING;
- }
- final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
- if (!containsInArray(text, elements)) {
- return extraValues;
- }
- final ArrayList<String> result = new ArrayList<>(elements.length - 1);
- for (final String element : elements) {
- if (!text.equals(element)) {
- result.add(element);
- }
- }
- return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result);
- }
-
- /**
- * Remove duplicates from an array of strings.
- *
- * This method will always keep the first occurrence of all strings at their position
- * in the array, removing the subsequent ones.
- */
- public static void removeDupes(final ArrayList<String> suggestions) {
- if (suggestions.size() < 2) return;
- int i = 1;
- // Don't cache suggestions.size(), since we may be removing items
- while (i < suggestions.size()) {
- final String cur = suggestions.get(i);
- // Compare each suggestion with each previous suggestion
- for (int j = 0; j < i; j++) {
- final String previous = suggestions.get(j);
- if (TextUtils.equals(cur, previous)) {
- suggestions.remove(i);
- i--;
- break;
- }
- }
- i++;
- }
- }
-
- public static String capitalizeFirstCodePoint(final String s, final Locale locale) {
- if (s.length() <= 1) {
- return toUpperCaseOfStringForLocale(s, true /* needsToUpperCase */, locale);
- }
- // Please refer to the comment below in
- // {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings
- final int cutoff = s.offsetByCodePoints(0, 1);
- return toUpperCaseOfStringForLocale(
- s.substring(0, cutoff), true /* needsToUpperCase */, locale) + s.substring(cutoff);
- }
-
- public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) {
- if (s.length() <= 1) {
- return toUpperCaseOfStringForLocale(s, true /* needsToUpperCase */, locale);
- }
- // TODO: fix the bugs below
- // - It does not work for Serbian, because it fails to account for the "lj" character,
- // which should be "Lj" in title case and "LJ" in upper case.
- // - It does not work for Dutch, because it fails to account for the "ij" digraph when it's
- // written as two separate code points. They are two different characters but both should
- // be capitalized as "IJ" as if they were a single letter in most words (not all). If the
- // unicode char for the ligature is used however, it works.
- final int cutoff = s.offsetByCodePoints(0, 1);
- final String titleCaseFirstLetter = toUpperCaseOfStringForLocale(
- s.substring(0, cutoff), true /* needsToUpperCase */, locale);
- return titleCaseFirstLetter + s.substring(cutoff).toLowerCase(locale);
- }
-
- private static final int[] EMPTY_CODEPOINTS = {};
-
- 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[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 = 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
- // camel case, and in either case we return CAPITALIZE_NONE.
- final int len = text.length();
- int index = 0;
- for (; index < len; index = text.offsetByCodePoints(index, 1)) {
- if (Character.isLetter(text.codePointAt(index))) {
- break;
- }
- }
- if (index == len) return CAPITALIZE_NONE;
- if (!Character.isUpperCase(text.codePointAt(index))) {
- return CAPITALIZE_NONE;
- }
- int capsCount = 1;
- int letterCount = 1;
- for (index = text.offsetByCodePoints(index, 1); index < len;
- index = text.offsetByCodePoints(index, 1)) {
- if (1 != capsCount && letterCount != capsCount) break;
- final int codePoint = text.codePointAt(index);
- if (Character.isUpperCase(codePoint)) {
- ++capsCount;
- ++letterCount;
- } else if (Character.isLetter(codePoint)) {
- // We need to discount non-letters since they may not be upper-case, but may
- // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME")
- ++letterCount;
- }
- }
- // We know the first char is upper case. So we want to test if either every letter other
- // than the first is lower case, or if they are all upper case. If the string is exactly
- // one char long, then we will arrive here with letterCount 1, and this is correct, too.
- if (1 == capsCount) return CAPITALIZE_FIRST;
- return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
- }
-
- public static boolean isIdenticalAfterUpcase(final String text) {
- final int length = text.length();
- int i = 0;
- while (i < length) {
- final int codePoint = text.codePointAt(i);
- if (Character.isLetter(codePoint) && !Character.isUpperCase(codePoint)) {
- return false;
- }
- i += Character.charCount(codePoint);
- }
- return true;
- }
-
- public static boolean isIdenticalAfterDowncase(final String text) {
- final int length = text.length();
- int i = 0;
- while (i < length) {
- final int codePoint = text.codePointAt(i);
- if (Character.isLetter(codePoint) && !Character.isLowerCase(codePoint)) {
- return false;
- }
- i += Character.charCount(codePoint);
- }
- return true;
- }
-
- public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
- 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 ((needsCapsNext && !Character.isUpperCase(codePoint))
- || (!needsCapsNext && !Character.isLowerCase(codePoint))) {
- return false;
- }
- }
- // We need a capital letter next if this is a separator.
- 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 int[] sortedSeparators,
- final Locale locale) {
- final StringBuilder builder = new StringBuilder();
- 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 (needsCapsNext) {
- builder.append(nextChar.toUpperCase(locale));
- } else {
- builder.append(nextChar.toLowerCase(locale));
- }
- // We need a capital letter next if this is a separator.
- needsCapsNext = (Arrays.binarySearch(sortedSeparators, nextChar.codePointAt(0)) >= 0);
- }
- return builder.toString();
- }
-
- /**
- * Approximates whether the text before the cursor looks like a URL.
- *
- * This is not foolproof, but it should work well in the practice.
- * Essentially it walks backward from the cursor until it finds something that's not a letter,
- * digit, or common URL symbol like underscore. If it hasn't found a period yet, then it
- * does not look like a URL.
- * If the text:
- * - starts with www and contains a period
- * - starts with a slash preceded by either a slash, whitespace, or start-of-string
- * Then it looks like a URL and we return true. Otherwise, we return false.
- *
- * Note: this method is called quite often, and should be fast.
- *
- * TODO: This will return that "abc./def" and ".abc/def" look like URLs to keep down the
- * code complexity, but ideally it should not. It's acceptable for now.
- */
- public static boolean lastPartLooksLikeURL(final CharSequence text) {
- int i = text.length();
- if (0 == i) return false;
- int wCount = 0;
- int slashCount = 0;
- boolean hasSlash = false;
- boolean hasPeriod = false;
- int codePoint = 0;
- while (i > 0) {
- 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,
- // underscore, arrobase, question mark, equal sign. It excludes spaces, exclamation
- // marks, double quotes...
- // Anything that's not a URL-like character causes us to break from here and
- // evaluate normally.
- break;
- }
- if (Constants.CODE_PERIOD == codePoint) {
- hasPeriod = true;
- }
- if (Constants.CODE_SLASH == codePoint) {
- hasSlash = true;
- if (2 == ++slashCount) {
- return true;
- }
- } else {
- slashCount = 0;
- }
- if ('w' == codePoint) {
- ++wCount;
- } else {
- wCount = 0;
- }
- i = Character.offsetByCodePoints(text, i, -1);
- }
- // End of the text run.
- // If it starts with www and includes a period, then it looks like a URL.
- if (wCount >= 3 && hasPeriod) return true;
- // If it starts with a slash, and the code point before is whitespace, it looks like an URL.
- if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) return true;
- // If it has both a period and a slash, it looks like an URL.
- if (hasPeriod && hasSlash) return true;
- // Otherwise, it doesn't look like an URL.
- return false;
- }
-
- /**
- * 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))) {
- return false;
- }
- }
- return true;
- }
-
- @UsedForTesting
- public static String byteArrayToHexString(final byte[] bytes) {
- if (bytes == null || bytes.length == 0) {
- return EMPTY_STRING;
- }
- final StringBuilder sb = new StringBuilder();
- for (byte b : bytes) {
- sb.append(String.format("%02x", b & 0xff));
- }
- return sb.toString();
- }
-
- /**
- * Convert hex string to byte array. The string length must be an even number.
- */
- @UsedForTesting
- public static byte[] hexStringToByteArray(final String hexString) {
- if (TextUtils.isEmpty(hexString)) {
- return null;
- }
- final int N = hexString.length();
- if (N % 2 != 0) {
- throw new NumberFormatException("Input hex string length must be an even number."
- + " Length = " + N);
- }
- final byte[] bytes = new byte[N / 2];
- for (int i = 0; i < N; i += 2) {
- bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
- + Character.digit(hexString.charAt(i + 1), 16));
- }
- return bytes;
- }
-
- private static final String LANGUAGE_GREEK = "el";
-
- private static Locale getLocaleUsedForToTitleCase(final Locale locale) {
- // In Greek locale {@link String#toUpperCase(Locale)} eliminates accents from its result.
- // In order to get accented upper case letter, {@link Locale#ROOT} should be used.
- if (LANGUAGE_GREEK.equals(locale.getLanguage())) {
- return Locale.ROOT;
- }
- return locale;
- }
-
- public static String toUpperCaseOfStringForLocale(final String text,
- final boolean needsToUpperCase, final Locale locale) {
- if (text == null || !needsToUpperCase) {
- return text;
- }
- return text.toUpperCase(getLocaleUsedForToTitleCase(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;
- }
-
- public static int getTrailingSingleQuotesCount(final CharSequence charSequence) {
- final int lastIndex = charSequence.length() - 1;
- int i = lastIndex;
- while (i >= 0 && charSequence.charAt(i) == Constants.CODE_SINGLE_QUOTE) {
- --i;
- }
- return lastIndex - i;
- }
-
- /**
- * Splits the given {@code charSequence} with at occurrences of the given {@code regex}.
- * <p>
- * This is equivalent to
- * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)}
- * except that the spans are preserved in the result array.
- * </p>
- * @param input the character sequence to be split.
- * @param regex the regex pattern to be used as the separator.
- * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty
- * segments. Otherwise, trailing empty segments will be removed before being returned.
- * @return the array which contains the result. All the spans in the {@param input} is
- * preserved.
- */
- @UsedForTesting
- public static CharSequence[] split(final CharSequence charSequence, final String regex,
- final boolean preserveTrailingEmptySegments) {
- // A short-cut for non-spanned strings.
- if (!(charSequence instanceof Spanned)) {
- // -1 means that trailing empty segments will be preserved.
- return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0);
- }
-
- // Hereafter, emulate String.split for CharSequence.
- final ArrayList<CharSequence> sequences = new ArrayList<>();
- final Matcher matcher = Pattern.compile(regex).matcher(charSequence);
- int nextStart = 0;
- boolean matched = false;
- while (matcher.find()) {
- sequences.add(charSequence.subSequence(nextStart, matcher.start()));
- nextStart = matcher.end();
- matched = true;
- }
- if (!matched) {
- // never matched. preserveTrailingEmptySegments is ignored in this case.
- return new CharSequence[] { charSequence };
- }
- sequences.add(charSequence.subSequence(nextStart, charSequence.length()));
- if (!preserveTrailingEmptySegments) {
- for (int i = sequences.size() - 1; i >= 0; --i) {
- if (!TextUtils.isEmpty(sequences.get(i))) {
- break;
- }
- sequences.remove(i);
- }
- }
- return sequences.toArray(new CharSequence[sequences.size()]);
- }
-
- @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;
- }
-
- protected String joinStringArray(final String[] stringArray, final String delimiter) {
- if (stringArray == null) {
- return "null";
- }
- 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 + "]";
- }
- }
-
- /**
- * Returns whether the last composed word contains line-breaking character (e.g. CR or LF).
- * @param text the text to be examined.
- * @return {@code true} if the last composed word contains line-breaking separator.
- */
- @UsedForTesting
- public static boolean hasLineBreakCharacter(final String text) {
- if (TextUtils.isEmpty(text)) {
- return false;
- }
- for (int i = text.length() - 1; i >= 0; --i) {
- final char c = text.charAt(i);
- switch (c) {
- case CHAR_LINE_FEED:
- case CHAR_VERTICAL_TAB:
- case CHAR_FORM_FEED:
- case CHAR_CARRIAGE_RETURN:
- case CHAR_NEXT_LINE:
- case CHAR_LINE_SEPARATOR:
- case CHAR_PARAGRAPH_SEPARATOR:
- return true;
- }
- }
- return false;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 351d01400..54a3fc39c 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -16,8 +16,9 @@
package com.android.inputmethod.latin.utils;
-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 static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.COMBINING_RULES;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
import android.content.Context;
import android.content.res.Resources;
@@ -25,18 +26,25 @@ import android.os.Build;
import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.LocaleUtils;
+import com.android.inputmethod.latin.common.StringUtils;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * A helper class to deal with subtype locales.
+ */
+// TODO: consolidate this into RichInputMethodSubtype
public final class SubtypeLocaleUtils {
- private static final String TAG = SubtypeLocaleUtils.class.getSimpleName();
+ 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();
+ // This reference class {@link R} must be located in the same package as LatinIME.java.
+ private static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
// Special language code to represent "no language".
public static final String NO_LANGUAGE = "zz";
@@ -47,11 +55,13 @@ public final class SubtypeLocaleUtils {
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.
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = new HashMap<>();
// Keyboard layout to subtype name resource id map.
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>();
+ // Exceptional locale whose name should be displayed in Locale.ROOT.
+ private static final HashMap<String, Integer> sExceptionalLocaleDisplayedInRootLocale =
+ new HashMap<>();
// Exceptional locale to subtype name resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>();
// Exceptional locale to subtype name with layout resource id map.
@@ -65,6 +75,8 @@ public final class SubtypeLocaleUtils {
"string/subtype_with_layout_";
private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX =
"string/subtype_no_language_";
+ private static final String SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX =
+ "string/subtype_in_root_locale_";
// Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
// This is for compatibility to keep the same subtype ids as pre-JellyBean.
private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
@@ -89,7 +101,6 @@ public final class SubtypeLocaleUtils {
sResources = res;
final String[] predefinedLayoutSet = res.getStringArray(R.array.predefined_layouts);
- sPredefinedKeyboardLayoutSet = predefinedLayoutSet;
final String[] layoutDisplayNames = res.getStringArray(
R.array.predefined_layout_display_names);
for (int i = 0; i < predefinedLayoutSet.length; i++) {
@@ -106,6 +117,15 @@ public final class SubtypeLocaleUtils {
sKeyboardLayoutToNameIdsMap.put(key, noLanguageResId);
}
+ final String[] exceptionalLocaleInRootLocale = res.getStringArray(
+ R.array.subtype_locale_displayed_in_root_locale);
+ for (int i = 0; i < exceptionalLocaleInRootLocale.length; i++) {
+ final String localeString = exceptionalLocaleInRootLocale[i];
+ final String resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + localeString;
+ final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME);
+ sExceptionalLocaleDisplayedInRootLocale.put(localeString, resId);
+ }
+
final String[] exceptionalLocales = res.getStringArray(
R.array.subtype_locale_exception_keys);
for (int i = 0; i < exceptionalLocales.length; i++) {
@@ -129,10 +149,6 @@ public final class SubtypeLocaleUtils {
}
}
- public static String[] getPredefinedKeyboardLayoutSet() {
- return sPredefinedKeyboardLayoutSet;
- }
-
public static boolean isExceptionalLocale(final String localeString) {
return sExceptionalLocaleToNameIdsMap.containsKey(localeString);
}
@@ -153,36 +169,58 @@ public final class SubtypeLocaleUtils {
return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId;
}
- private static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) {
+ @Nonnull
+ public static Locale getDisplayLocaleOfSubtypeLocale(@Nonnull final String localeString) {
if (NO_LANGUAGE.equals(localeString)) {
return sResources.getConfiguration().locale;
}
+ if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
+ return Locale.ROOT;
+ }
return LocaleUtils.constructLocaleFromString(localeString);
}
- public static String getSubtypeLocaleDisplayNameInSystemLocale(final String localeString) {
+ public static String getSubtypeLocaleDisplayNameInSystemLocale(
+ @Nonnull final String localeString) {
final Locale displayLocale = sResources.getConfiguration().locale;
return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
}
- public static String getSubtypeLocaleDisplayName(final String localeString) {
+ @Nonnull
+ public static String getSubtypeLocaleDisplayName(@Nonnull final String localeString) {
final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
}
- public static String getSubtypeLanguageDisplayName(final String localeString) {
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+ @Nonnull
+ public static String getSubtypeLanguageDisplayName(@Nonnull final String localeString) {
final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
- return getSubtypeLocaleDisplayNameInternal(locale.getLanguage(), displayLocale);
+ final String languageString;
+ if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
+ languageString = localeString;
+ } else {
+ languageString = LocaleUtils.constructLocaleFromString(localeString).getLanguage();
+ }
+ return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale);
}
- private static String getSubtypeLocaleDisplayNameInternal(final String localeString,
- final Locale displayLocale) {
+ @Nonnull
+ private static String getSubtypeLocaleDisplayNameInternal(@Nonnull final String localeString,
+ @Nonnull 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 Integer exceptionalNameResId;
+ if (displayLocale.equals(Locale.ROOT)
+ && sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
+ exceptionalNameResId = sExceptionalLocaleDisplayedInRootLocale.get(localeString);
+ } else if (sExceptionalLocaleToNameIdsMap.containsKey(localeString)) {
+ exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
+ } else {
+ exceptionalNameResId = null;
+ }
+
final String displayName;
if (exceptionalNameResId != null) {
final RunInLocale<String> getExceptionalName = new RunInLocale<String>() {
@@ -193,8 +231,8 @@ public final class SubtypeLocaleUtils {
};
displayName = getExceptionalName.runInLocale(sResources, displayLocale);
} else {
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
- displayName = locale.getDisplayName(displayLocale);
+ displayName = LocaleUtils.constructLocaleFromString(localeString)
+ .getDisplayName(displayLocale);
}
return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale);
}
@@ -217,31 +255,36 @@ public final class SubtypeLocaleUtils {
// en_US azerty T English (US) (AZERTY) exception
// zz azerty T Alphabet (AZERTY) in system locale
- private static String getReplacementString(final InputMethodSubtype subtype,
- final Locale displayLocale) {
+ @Nonnull
+ private static String getReplacementString(@Nonnull final InputMethodSubtype subtype,
+ @Nonnull final Locale displayLocale) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
&& subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME);
- } else {
- return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale);
}
+ return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale);
}
- public static String getSubtypeDisplayNameInSystemLocale(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static String getSubtypeDisplayNameInSystemLocale(
+ @Nonnull final InputMethodSubtype subtype) {
final Locale displayLocale = sResources.getConfiguration().locale;
return getSubtypeDisplayNameInternal(subtype, displayLocale);
}
- public static String getSubtypeNameForLogging(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static String getSubtypeNameForLogging(@Nullable final InputMethodSubtype subtype) {
if (subtype == null) {
return "<null subtype>";
}
return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype);
}
- private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype,
- final Locale displayLocale) {
+ @Nonnull
+ private static String getSubtypeDisplayNameInternal(@Nonnull final InputMethodSubtype subtype,
+ @Nonnull final Locale displayLocale) {
final String replacementString = getReplacementString(subtype, displayLocale);
+ // TODO: rework this for multi-lingual subtypes
final int nameResId = subtype.getNameResId();
final RunInLocale<String> getSubtypeName = new RunInLocale<String>() {
@Override
@@ -264,25 +307,25 @@ public final class SubtypeLocaleUtils {
getSubtypeName.runInLocale(sResources, displayLocale), displayLocale);
}
- public static boolean isNoLanguage(final InputMethodSubtype subtype) {
- final String localeString = subtype.getLocale();
- return NO_LANGUAGE.equals(localeString);
- }
-
- public static Locale getSubtypeLocale(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static Locale getSubtypeLocale(@Nonnull final InputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
return LocaleUtils.constructLocaleFromString(localeString);
}
- public static String getKeyboardLayoutSetDisplayName(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static String getKeyboardLayoutSetDisplayName(
+ @Nonnull final InputMethodSubtype subtype) {
final String layoutName = getKeyboardLayoutSetName(subtype);
return getKeyboardLayoutSetDisplayName(layoutName);
}
- public static String getKeyboardLayoutSetDisplayName(final String layoutName) {
+ @Nonnull
+ public static String getKeyboardLayoutSetDisplayName(@Nonnull final String layoutName) {
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
}
+ @Nonnull
public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) {
String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET);
if (keyboardLayoutSet == null) {
@@ -302,27 +345,7 @@ public final class SubtypeLocaleUtils {
return keyboardLayoutSet;
}
- // 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);
- }
-
- public static boolean isRtlLanguage(final Locale locale) {
- final String language = locale.getLanguage();
- return Arrays.binarySearch(SORTED_RTL_LANGUAGES, language) >= 0;
- }
-
- public static boolean isRtlLanguage(final InputMethodSubtype subtype) {
- return isRtlLanguage(getSubtypeLocale(subtype));
- }
-
public static String getCombiningRulesExtraValue(final InputMethodSubtype subtype) {
- return subtype.getExtraValueOf(Constants.Subtype.ExtraValue.COMBINING_RULES);
+ return subtype.getExtraValueOf(COMBINING_RULES);
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
index 8cd49509f..981355115 100644
--- a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
+++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
@@ -22,7 +22,6 @@ import com.android.inputmethod.latin.define.ProductionFlags;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
-import java.util.Locale;
import java.util.TreeSet;
/**
@@ -30,22 +29,23 @@ import java.util.TreeSet;
* than its limit
*/
public final class SuggestionResults extends TreeSet<SuggestedWordInfo> {
- public final Locale mLocale;
public final ArrayList<SuggestedWordInfo> mRawSuggestions;
// TODO: Instead of a boolean , we may want to include the context of this suggestion results,
- // such as {@link PrevWordsInfo}.
+ // such as {@link NgramContext}.
public final boolean mIsBeginningOfSentence;
+ public final boolean mFirstSuggestionExceedsConfidenceThreshold;
private final int mCapacity;
- public SuggestionResults(final Locale locale, final int capacity,
- final boolean isBeginningOfSentence) {
- this(locale, sSuggestedWordInfoComparator, capacity, isBeginningOfSentence);
+ public SuggestionResults(final int capacity, final boolean isBeginningOfSentence,
+ final boolean firstSuggestionExceedsConfidenceThreshold) {
+ this(sSuggestedWordInfoComparator, capacity, isBeginningOfSentence,
+ firstSuggestionExceedsConfidenceThreshold);
}
- private SuggestionResults(final Locale locale, final Comparator<SuggestedWordInfo> comparator,
- final int capacity, final boolean isBeginningOfSentence) {
+ private SuggestionResults(final Comparator<SuggestedWordInfo> comparator, final int capacity,
+ final boolean isBeginningOfSentence,
+ final boolean firstSuggestionExceedsConfidenceThreshold) {
super(comparator);
- mLocale = locale;
mCapacity = capacity;
if (ProductionFlags.INCLUDE_RAW_SUGGESTIONS) {
mRawSuggestions = new ArrayList<>();
@@ -53,6 +53,7 @@ public final class SuggestionResults extends TreeSet<SuggestedWordInfo> {
mRawSuggestions = null;
}
mIsBeginningOfSentence = isBeginningOfSentence;
+ mFirstSuggestionExceedsConfidenceThreshold = firstSuggestionExceedsConfidenceThreshold;
}
@Override
@@ -70,8 +71,7 @@ public final class SuggestionResults extends TreeSet<SuggestedWordInfo> {
return super.addAll(e);
}
- private static final class SuggestedWordInfoComparator
- implements Comparator<SuggestedWordInfo> {
+ 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
diff --git a/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
index dd122b634..0bcba2754 100644
--- a/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
@@ -57,7 +57,7 @@ public final class ViewLayoutUtils {
public static void updateLayoutHeightOf(final Window window, final int layoutHeight) {
final WindowManager.LayoutParams params = window.getAttributes();
- if (params.height != layoutHeight) {
+ if (params != null && params.height != layoutHeight) {
params.height = layoutHeight;
window.setAttributes(params);
}
@@ -65,7 +65,7 @@ public final class ViewLayoutUtils {
public static void updateLayoutHeightOf(final View view, final int layoutHeight) {
final ViewGroup.LayoutParams params = view.getLayoutParams();
- if (params.height != layoutHeight) {
+ if (params != null && params.height != layoutHeight) {
params.height = layoutHeight;
view.setLayoutParams(params);
}
diff --git a/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
new file mode 100644
index 000000000..fc0a9cb6c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
@@ -0,0 +1,106 @@
+/*
+ * 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.annotations.UsedForTesting;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.common.StringUtils;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+
+import java.util.ArrayList;
+import java.util.List;
+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 WordInputEventForPersonalization {
+ private static final String TAG = WordInputEventForPersonalization.class.getSimpleName();
+ private static final boolean DEBUG_TOKEN = false;
+
+ public final int[] mTargetWord;
+ public final int mPrevWordsCount;
+ public final int[][] mPrevWordArray =
+ new int[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
+ public final boolean[] mIsPrevWordBeginningOfSentenceArray =
+ new boolean[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ // Time stamp in seconds.
+ public final int mTimestamp;
+
+ @UsedForTesting
+ public WordInputEventForPersonalization(final CharSequence targetWord,
+ final NgramContext ngramContext, final int timestamp) {
+ mTargetWord = StringUtils.toCodePointArray(targetWord);
+ mPrevWordsCount = ngramContext.getPrevWordCount();
+ ngramContext.outputToArray(mPrevWordArray, mIsPrevWordBeginningOfSentenceArray);
+ mTimestamp = timestamp;
+ }
+
+ // Process a list of words and return a list of {@link WordInputEventForPersonalization}
+ // objects.
+ public static ArrayList<WordInputEventForPersonalization> createInputEventFrom(
+ final List<String> tokens, final int timestamp,
+ final SpacingAndPunctuations spacingAndPunctuations, final Locale locale) {
+ final ArrayList<WordInputEventForPersonalization> inputEvents = new ArrayList<>();
+ final int N = tokens.size();
+ NgramContext ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO;
+ 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.
+ // TODO: Detect whether the context is beginning-of-sentence.
+ ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO;
+ continue;
+ }
+ if (DEBUG_TOKEN) {
+ Log.d(TAG, "--- word: \"" + tempWord + "\"");
+ }
+ final WordInputEventForPersonalization inputEvent =
+ detectWhetherVaildWordOrNotAndGetInputEvent(
+ ngramContext, tempWord, timestamp, locale);
+ if (inputEvent == null) {
+ continue;
+ }
+ inputEvents.add(inputEvent);
+ ngramContext = ngramContext.getNextNgramContext(new NgramContext.WordInfo(tempWord));
+ }
+ return inputEvents;
+ }
+
+ private static WordInputEventForPersonalization detectWhetherVaildWordOrNotAndGetInputEvent(
+ final NgramContext ngramContext, final String targetWord, final int timestamp,
+ final Locale locale) {
+ if (locale == null) {
+ return null;
+ }
+ return new WordInputEventForPersonalization(targetWord, ngramContext, timestamp);
+ }
+}