aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java4
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java2
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java2
-rw-r--r--java/src/com/android/inputmethod/compat/IntentCompatUtils.java9
-rw-r--r--java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java6
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ActionBatch.java51
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java18
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java22
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java11
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java6
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryService.java84
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java43
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/EventHandler.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java80
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java84
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/WordListPreference.java20
-rw-r--r--java/src/com/android/inputmethod/event/EventInterpreter.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java16
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java10
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java54
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java74
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java13
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java436
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java48
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java14
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java19
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java226
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityInfo.java129
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java25
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java68
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java10
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java86
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java14
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java15
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java57
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java1278
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeysCache.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java165
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java113
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java7
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java7
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java14
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java102
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java155
-rw-r--r--java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java78
-rw-r--r--java/src/com/android/inputmethod/latin/AssetFileAddress.java2
-rw-r--r--java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java2
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java76
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java57
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java37
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java52
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java8
-rw-r--r--java/src/com/android/inputmethod/latin/DicTraverseSession.java2
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java7
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java5
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java31
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java1
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryWriter.java107
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java185
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java385
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java5
-rw-r--r--java/src/com/android/inputmethod/latin/InputPointers.java1
-rw-r--r--java/src/com/android/inputmethod/latin/LastComposedWord.java4
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java507
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java166
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java61
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java44
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java67
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java38
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java10
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java491
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java58
-rw-r--r--java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java (renamed from java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java)36
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java6
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java207
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java2
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java38
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/AccountUtils.java19
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java (renamed from java/src/com/android/inputmethod/latin/UserHistoryDictionary.java)149
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java61
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java82
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java21
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java36
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java (renamed from java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java)7
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java39
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java47
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java (renamed from java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java)39
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettings.java (renamed from java/src/com/android/inputmethod/latin/DebugSettings.java)20
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java (renamed from java/src/com/android/inputmethod/latin/DebugSettingsActivity.java)4
-rw-r--r--java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java53
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SeekBarDialogPreference.java (renamed from java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java)50
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java (renamed from java/src/com/android/inputmethod/latin/Settings.java)63
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsActivity.java (renamed from java/src/com/android/inputmethod/latin/SettingsActivity.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java (renamed from java/src/com/android/inputmethod/latin/SettingsFragment.java)103
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java (renamed from java/src/com/android/inputmethod/latin/SettingsValues.java)26
-rw-r--r--java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java25
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupActivity.java48
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java43
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java6
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java2
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java4
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java2
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java4
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java116
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java600
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java571
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java30
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java24
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java63
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java24
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java42
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java (renamed from java/src/com/android/inputmethod/latin/AdditionalSubtype.java)30
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java65
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java (renamed from java/src/com/android/inputmethod/latin/AutoCorrection.java)26
-rw-r--r--java/src/com/android/inputmethod/latin/utils/Base64Reader.java117
-rw-r--r--java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java (renamed from java/src/com/android/inputmethod/latin/BoundedTreeSet.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ByteArrayWrapper.java81
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java (renamed from java/src/com/android/inputmethod/latin/CapsModeUtils.java)5
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CollectionUtils.java (renamed from java/src/com/android/inputmethod/latin/CollectionUtils.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java (renamed from java/src/com/android/inputmethod/latin/CompletionInfoUtils.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java (renamed from java/src/com/android/inputmethod/latin/CoordinateUtils.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CsvUtils.java318
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java (renamed from java/src/com/android/inputmethod/dictionarypack/Utils.java)31
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java (renamed from java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java)26
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java (renamed from java/src/com/android/inputmethod/latin/FeedbackUtils.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FileTransforms.java (renamed from java/src/com/android/inputmethod/latin/FileTransforms.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java (renamed from java/src/com/android/inputmethod/latin/InputTypeUtils.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/IntentUtils.java (renamed from java/src/com/android/inputmethod/latin/IntentUtils.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/JniUtils.java (renamed from java/src/com/android/inputmethod/latin/JniUtils.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java77
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LocaleUtils.java (renamed from java/src/com/android/inputmethod/latin/LocaleUtils.java)39
-rw-r--r--java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java (renamed from java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java)4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java (renamed from java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java)4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java (renamed from java/src/com/android/inputmethod/latin/RecapitalizeStatus.java)9
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java (renamed from java/src/com/android/inputmethod/latin/ResizableIntArray.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResourceUtils.java (renamed from java/src/com/android/inputmethod/latin/ResourceUtils.java)83
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RunInLocale.java55
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java (renamed from java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java (renamed from java/src/com/android/inputmethod/latin/StringUtils.java)94
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java (renamed from java/src/com/android/inputmethod/latin/SubtypeLocale.java)63
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java (renamed from java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java)2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TextRange.java116
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java (renamed from java/src/com/android/inputmethod/keyboard/TypefaceUtils.java)4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java293
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java (renamed from java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java)61
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java (renamed from java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java)9
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java133
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java (renamed from java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java)8
-rw-r--r--java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java (renamed from java/src/com/android/inputmethod/latin/XmlParseUtils.java)2
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackActivity.java1
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackFragment.java1
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackLog.java32
-rw-r--r--java/src/com/android/inputmethod/research/JsonUtils.java9
-rw-r--r--java/src/com/android/inputmethod/research/LogUnit.java40
-rw-r--r--java/src/com/android/inputmethod/research/MainLogBuffer.java72
-rw-r--r--java/src/com/android/inputmethod/research/MotionEventReader.java10
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLog.java14
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java444
-rw-r--r--java/src/com/android/inputmethod/research/Statistics.java50
-rw-r--r--java/src/com/android/inputmethod/research/Uploader.java4
-rw-r--r--java/src/com/android/inputmethod/research/UploaderService.java5
-rw-r--r--java/src/com/android/inputmethod/research/ui/SplashScreen.java111
177 files changed, 7166 insertions, 4262 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 0576f666c..77f323440 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -35,8 +35,8 @@ 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.CollectionUtils;
-import com.android.inputmethod.latin.CoordinateUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
/**
* Exposes a virtual view sub-tree for {@link KeyboardView} and generates
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index ee52de1d1..8929dc7e9 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -33,8 +33,8 @@ import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.SettingsSecureCompatUtils;
-import com.android.inputmethod.latin.InputTypeUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
public final class AccessibilityUtils {
private static final String TAG = AccessibilityUtils.class.getSimpleName();
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 05d8269b7..41f5b9a24 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -25,9 +25,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.CollectionUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.HashMap;
diff --git a/java/src/com/android/inputmethod/compat/IntentCompatUtils.java b/java/src/com/android/inputmethod/compat/IntentCompatUtils.java
index df2e22fe8..965a2a891 100644
--- a/java/src/com/android/inputmethod/compat/IntentCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/IntentCompatUtils.java
@@ -21,16 +21,15 @@ import android.content.Intent;
public final class IntentCompatUtils {
// Note that Intent.ACTION_USER_INITIALIZE have been introduced in API level 17
// (Build.VERSION_CODE.JELLY_BEAN_MR1).
- public static final String ACTION_USER_INITIALIZE =
- (String)CompatUtils.getFieldValue(null, null,
+ private static final String ACTION_USER_INITIALIZE =
+ (String)CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */,
CompatUtils.getField(Intent.class, "ACTION_USER_INITIALIZE"));
private IntentCompatUtils() {
// This utility class is not publicly instantiable.
}
- public static boolean has_ACTION_USER_INITIALIZE(final Intent intent) {
- return ACTION_USER_INITIALIZE != null && intent != null
- && ACTION_USER_INITIALIZE.equals(intent.getAction());
+ public static boolean is_ACTION_USER_INITIALIZE(final String action) {
+ return ACTION_USER_INITIALIZE != null && ACTION_USER_INITIALIZE.equals(action);
}
}
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 141e08a8d..55282c583 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -23,17 +23,15 @@ import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
public final class SuggestionSpanUtils {
- private static final String TAG = SuggestionSpanUtils.class.getSimpleName();
-
// Note that SuggestionSpan.FLAG_AUTO_CORRECTION has been introduced
// in API level 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1).
public static final Field FIELD_FLAG_AUTO_CORRECTION = CompatUtils.getField(
@@ -60,7 +58,7 @@ public final class SuggestionSpanUtils {
}
final Spannable spannable = new SpannableString(text);
final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null /* locale */,
- new String[] {} /* suggestions */, (int)OBJ_FLAG_AUTO_CORRECTION,
+ new String[] {} /* suggestions */, OBJ_FLAG_AUTO_CORRECTION,
SuggestionSpanPickedNotificationReceiver.class);
spannable.setSpan(suggestionSpan, 0, text.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index bf2230553..d5e638e7e 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -28,6 +28,8 @@ import android.util.Log;
import com.android.inputmethod.compat.DownloadManagerCompatUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
import java.util.LinkedList;
import java.util.Queue;
@@ -98,7 +100,7 @@ public final class ActionBatch {
final boolean mForceStartNow;
public StartDownloadAction(final String clientId,
final WordListMetadata wordList, final boolean forceStartNow) {
- Utils.l("New download action for client ", clientId, " : ", wordList);
+ DebugLogUtils.l("New download action for client ", clientId, " : ", wordList);
mClientId = clientId;
mWordList = wordList;
mForceStartNow = forceStartNow;
@@ -110,7 +112,7 @@ public final class ActionBatch {
Log.e(TAG, "UpdateAction with a null parameter!");
return;
}
- Utils.l("Downloading word list");
+ DebugLogUtils.l("Downloading word list");
final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
mWordList.mId, mWordList.mVersion);
@@ -132,7 +134,7 @@ public final class ActionBatch {
+ " for an upgrade action. Fall back to download.");
}
// Download it.
- Utils.l("Upgrade word list, downloading", mWordList.mRemoteFilename);
+ DebugLogUtils.l("Upgrade word list, downloading", mWordList.mRemoteFilename);
// TODO: if DownloadManager is disabled or not installed, download by ourselves
if (null == manager) return;
@@ -142,7 +144,7 @@ public final class ActionBatch {
// DownloadManager also stupidly cuts the extension to replace with its own that it
// gets from the content-type. We need to circumvent this.
final String disambiguator = "#" + System.currentTimeMillis()
- + com.android.inputmethod.latin.Utils.getVersionName(context) + ".dict";
+ + ApplicationUtils.getVersionName(context) + ".dict";
final Uri uri = Uri.parse(mWordList.mRemoteFilename + disambiguator);
final Request request = new Request(uri);
@@ -178,7 +180,7 @@ public final class ActionBatch {
final long downloadId = UpdateHandler.registerDownloadRequest(manager, request, db,
mWordList.mId, mWordList.mVersion);
- Utils.l("Starting download of", uri, "with id", downloadId);
+ DebugLogUtils.l("Starting download of", uri, "with id", downloadId);
PrivateLog.log("Starting download of " + uri + ", id : " + downloadId);
}
}
@@ -195,7 +197,8 @@ public final class ActionBatch {
public InstallAfterDownloadAction(final String clientId,
final ContentValues wordListValues) {
- Utils.l("New InstallAfterDownloadAction for client ", clientId, " : ", wordListValues);
+ DebugLogUtils.l("New InstallAfterDownloadAction for client ", clientId, " : ",
+ wordListValues);
mClientId = clientId;
mWordListValues = wordListValues;
}
@@ -213,7 +216,7 @@ public final class ActionBatch {
+ " for an InstallAfterDownload action. Bailing out.");
return;
}
- Utils.l("Setting word list as installed");
+ DebugLogUtils.l("Setting word list as installed");
final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
MetadataDbHelper.markEntryAsFinishedDownloadingAndInstalled(db, mWordListValues);
}
@@ -229,7 +232,7 @@ public final class ActionBatch {
final WordListMetadata mWordList;
public EnableAction(final String clientId, final WordListMetadata wordList) {
- Utils.l("New EnableAction for client ", clientId, " : ", wordList);
+ DebugLogUtils.l("New EnableAction for client ", clientId, " : ", wordList);
mClientId = clientId;
mWordList = wordList;
}
@@ -240,7 +243,7 @@ public final class ActionBatch {
Log.e(TAG, "EnableAction with a null parameter!");
return;
}
- Utils.l("Enabling word list");
+ DebugLogUtils.l("Enabling word list");
final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
mWordList.mId, mWordList.mVersion);
@@ -264,7 +267,7 @@ public final class ActionBatch {
// The word list to disable. May not be null.
final WordListMetadata mWordList;
public DisableAction(final String clientId, final WordListMetadata wordlist) {
- Utils.l("New Disable action for client ", clientId, " : ", wordlist);
+ DebugLogUtils.l("New Disable action for client ", clientId, " : ", wordlist);
mClientId = clientId;
mWordList = wordlist;
}
@@ -275,7 +278,7 @@ public final class ActionBatch {
Log.e(TAG, "DisableAction with a null word list!");
return;
}
- Utils.l("Disabling word list : " + mWordList);
+ DebugLogUtils.l("Disabling word list : " + mWordList);
final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
mWordList.mId, mWordList.mVersion);
@@ -311,7 +314,7 @@ public final class ActionBatch {
// The word list to make available. May not be null.
final WordListMetadata mWordList;
public MakeAvailableAction(final String clientId, final WordListMetadata wordlist) {
- Utils.l("New MakeAvailable action", clientId, " : ", wordlist);
+ DebugLogUtils.l("New MakeAvailable action", clientId, " : ", wordlist);
mClientId = clientId;
mWordList = wordlist;
}
@@ -328,7 +331,7 @@ public final class ActionBatch {
Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' "
+ " for a makeavailable action. Marking as available anyway.");
}
- Utils.l("Making word list available : " + mWordList);
+ DebugLogUtils.l("Making word list available : " + mWordList);
// If mLocalFilename is null, then it's a remote file that hasn't been downloaded
// yet, so we set the local filename to the empty string.
final ContentValues values = MetadataDbHelper.makeContentValues(0,
@@ -360,7 +363,7 @@ public final class ActionBatch {
// The word list to mark pre-installed. May not be null.
final WordListMetadata mWordList;
public MarkPreInstalledAction(final String clientId, final WordListMetadata wordlist) {
- Utils.l("New MarkPreInstalled action", clientId, " : ", wordlist);
+ DebugLogUtils.l("New MarkPreInstalled action", clientId, " : ", wordlist);
mClientId = clientId;
mWordList = wordlist;
}
@@ -377,7 +380,7 @@ public final class ActionBatch {
Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' "
+ " for a markpreinstalled action. Marking as preinstalled anyway.");
}
- Utils.l("Marking word list preinstalled : " + mWordList);
+ DebugLogUtils.l("Marking word list preinstalled : " + mWordList);
// This word list is pre-installed : we don't have its file. We should reset
// the local file name to the empty string so that we don't try to open it
// accidentally. The remote filename may be set by the application if it so wishes.
@@ -401,7 +404,7 @@ public final class ActionBatch {
private final String mClientId;
final WordListMetadata mWordList;
public UpdateDataAction(final String clientId, final WordListMetadata wordlist) {
- Utils.l("New UpdateData action for client ", clientId, " : ", wordlist);
+ DebugLogUtils.l("New UpdateData action for client ", clientId, " : ", wordlist);
mClientId = clientId;
mWordList = wordlist;
}
@@ -419,7 +422,7 @@ public final class ActionBatch {
Log.e(TAG, "Trying to update data about a non-existing word list. Bailing out.");
return;
}
- Utils.l("Updating data about a word list : " + mWordList);
+ DebugLogUtils.l("Updating data about a word list : " + mWordList);
final ContentValues values = MetadataDbHelper.makeContentValues(
oldValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN),
oldValues.getAsInteger(MetadataDbHelper.TYPE_COLUMN),
@@ -453,7 +456,7 @@ public final class ActionBatch {
final boolean mHasNewerVersion;
public ForgetAction(final String clientId, final WordListMetadata wordlist,
final boolean hasNewerVersion) {
- Utils.l("New TryRemove action for client ", clientId, " : ", wordlist);
+ DebugLogUtils.l("New TryRemove action for client ", clientId, " : ", wordlist);
mClientId = clientId;
mWordList = wordlist;
mHasNewerVersion = hasNewerVersion;
@@ -465,7 +468,7 @@ public final class ActionBatch {
Log.e(TAG, "TryRemoveAction with a null word list!");
return;
}
- Utils.l("Trying to remove word list : " + mWordList);
+ DebugLogUtils.l("Trying to remove word list : " + mWordList);
final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
mWordList.mId, mWordList.mVersion);
@@ -525,7 +528,7 @@ public final class ActionBatch {
// The word list to delete. May not be null.
final WordListMetadata mWordList;
public StartDeleteAction(final String clientId, final WordListMetadata wordlist) {
- Utils.l("New StartDelete action for client ", clientId, " : ", wordlist);
+ DebugLogUtils.l("New StartDelete action for client ", clientId, " : ", wordlist);
mClientId = clientId;
mWordList = wordlist;
}
@@ -536,7 +539,7 @@ public final class ActionBatch {
Log.e(TAG, "StartDeleteAction with a null word list!");
return;
}
- Utils.l("Trying to delete word list : " + mWordList);
+ DebugLogUtils.l("Trying to delete word list : " + mWordList);
final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
mWordList.mId, mWordList.mVersion);
@@ -564,7 +567,7 @@ public final class ActionBatch {
// The word list to delete. May not be null.
final WordListMetadata mWordList;
public FinishDeleteAction(final String clientId, final WordListMetadata wordlist) {
- Utils.l("New FinishDelete action for client", clientId, " : ", wordlist);
+ DebugLogUtils.l("New FinishDelete action for client", clientId, " : ", wordlist);
mClientId = clientId;
mWordList = wordlist;
}
@@ -575,7 +578,7 @@ public final class ActionBatch {
Log.e(TAG, "FinishDeleteAction with a null word list!");
return;
}
- Utils.l("Trying to delete word list : " + mWordList);
+ DebugLogUtils.l("Trying to delete word list : " + mWordList);
final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
mWordList.mId, mWordList.mVersion);
@@ -632,7 +635,7 @@ public final class ActionBatch {
* @param reporter a Reporter to send errors to.
*/
public void execute(final Context context, final ProblemReporter reporter) {
- Utils.l("Executing a batch of actions");
+ DebugLogUtils.l("Executing a batch of actions");
Queue<Action> remainingActions = mActions;
while (!remainingActions.isEmpty()) {
final Action a = remainingActions.poll();
diff --git a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
index 5ab94a429..6d6c8f5c6 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
@@ -47,6 +47,7 @@ public class ButtonSwitcher extends FrameLayout {
private Button mInstallButton;
private Button mCancelButton;
private Button mDeleteButton;
+ private DictionaryListInterfaceState mInterfaceState;
private OnClickListener mOnClickListener;
public ButtonSwitcher(Context context, AttributeSet attrs) {
@@ -57,6 +58,12 @@ public class ButtonSwitcher extends FrameLayout {
super(context, attrs, defStyle);
}
+ public void reset(final DictionaryListInterfaceState interfaceState) {
+ mStatus = NOT_INITIALIZED;
+ mAnimateToStatus = NOT_INITIALIZED;
+ mInterfaceState = interfaceState;
+ }
+
@Override
protected void onLayout(final boolean changed, final int left, final int top, final int right,
final int bottom) {
@@ -64,9 +71,7 @@ public class ButtonSwitcher extends FrameLayout {
mInstallButton = (Button)findViewById(R.id.dict_install_button);
mCancelButton = (Button)findViewById(R.id.dict_cancel_button);
mDeleteButton = (Button)findViewById(R.id.dict_delete_button);
- mInstallButton.setOnClickListener(mOnClickListener);
- mCancelButton.setOnClickListener(mOnClickListener);
- mDeleteButton.setOnClickListener(mOnClickListener);
+ setInternalOnClickListener(mOnClickListener);
setButtonPositionWithoutAnimation(mStatus);
if (mAnimateToStatus != NOT_INITIALIZED) {
// We have been asked to animate before we were ready, so we took a note of it.
@@ -139,11 +144,18 @@ public class ButtonSwitcher extends FrameLayout {
public void setInternalOnClickListener(final OnClickListener listener) {
mOnClickListener = listener;
+ if (null != mInstallButton) {
+ // Already laid out : do it now
+ mInstallButton.setOnClickListener(mOnClickListener);
+ mCancelButton.setOnClickListener(mOnClickListener);
+ mDeleteButton.setOnClickListener(mOnClickListener);
+ }
}
private ViewPropertyAnimator animateButton(final View button, final int direction) {
final float outerX = getWidth();
final float innerX = button.getX() - button.getTranslationX();
+ mInterfaceState.removeFromCache((View)getParent());
if (ANIMATION_IN == direction) {
button.setClickable(true);
return button.animate().translationX(0);
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
index de3711c27..13c07de35 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
@@ -16,8 +16,11 @@
package com.android.inputmethod.dictionarypack;
-import com.android.inputmethod.latin.CollectionUtils;
+import android.view.View;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.util.ArrayList;
import java.util.HashMap;
/**
@@ -37,6 +40,7 @@ public class DictionaryListInterfaceState {
}
private HashMap<String, State> mWordlistToState = CollectionUtils.newHashMap();
+ private ArrayList<View> mViewCache = CollectionUtils.newArrayList();
public boolean isOpen(final String wordlistId) {
final State state = mWordlistToState.get(wordlistId);
@@ -64,4 +68,20 @@ public class DictionaryListInterfaceState {
state.mOpen = false;
}
}
+
+ public View findFirstOrphanedView() {
+ for (final View v : mViewCache) {
+ if (null == v.getParent()) return v;
+ }
+ return null;
+ }
+
+ public View addToCacheAndReturnView(final View view) {
+ mViewCache.add(view);
+ return view;
+ }
+
+ public void removeFromCache(final View view) {
+ mViewCache.remove(view);
+ }
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
index 69615887f..df0e3f0e1 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
@@ -28,13 +28,13 @@ public class DictionaryPackConstants {
* The root domain for the dictionary pack, upon which authorities and actions will append
* their own distinctive strings.
*/
- private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack";
+ private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack.aosp";
/**
* Authority for the ContentProvider protocol.
*/
// TODO: find some way to factorize this string with the one in the resources
- public static final String AUTHORITY = DICTIONARY_DOMAIN + ".aosp";
+ public static final String AUTHORITY = DICTIONARY_DOMAIN;
/**
* The action of the intent for publishing that new dictionary data is available.
@@ -52,7 +52,14 @@ public class DictionaryPackConstants {
*/
public static final String UNKNOWN_DICTIONARY_PROVIDER_CLIENT = DICTIONARY_DOMAIN
+ ".UNKNOWN_CLIENT";
+
// In the above intents, the name of the string extra that contains the name of the client
// we want information about.
public static final String DICTIONARY_PROVIDER_CLIENT_EXTRA = "client";
+
+ /**
+ * The action of the intent to tell the dictionary provider to update now.
+ */
+ public static final String UPDATE_NOW_INTENT_ACTION = DICTIONARY_DOMAIN
+ + ".UPDATE_NOW";
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
index 4fbe16233..1d9b9991e 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.utils.DebugLogUtils;
import java.io.File;
import java.io.FileNotFoundException;
@@ -53,7 +54,6 @@ public final class DictionaryProvider extends ContentProvider {
private static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt";
private static final String QUERY_PARAMETER_TRUE = "true";
private static final String QUERY_PARAMETER_DELETE_RESULT = "result";
- private static final String QUERY_PARAMETER_SUCCESS = "success";
private static final String QUERY_PARAMETER_FAILURE = "failure";
public static final String QUERY_PARAMETER_PROTOCOL_VERSION = "protocol";
private static final int NO_MATCH = 0;
@@ -219,7 +219,7 @@ public final class DictionaryProvider extends ContentProvider {
@Override
public Cursor query(final Uri uri, final String[] projection, final String selection,
final String[] selectionArgs, final String sortOrder) {
- Utils.l("Uri =", uri);
+ DebugLogUtils.l("Uri =", uri);
PrivateLog.log("Query : " + uri);
final String clientId = getClientId(uri);
final int match = matchUri(uri);
@@ -227,7 +227,7 @@ public final class DictionaryProvider extends ContentProvider {
case DICTIONARY_V1_WHOLE_LIST:
case DICTIONARY_V2_WHOLE_LIST:
final Cursor c = MetadataDbHelper.queryDictionaries(getContext(), clientId);
- Utils.l("List of dictionaries with count", c.getCount());
+ DebugLogUtils.l("List of dictionaries with count", c.getCount());
PrivateLog.log("Returned a list of " + c.getCount() + " items");
return c;
case DICTIONARY_V2_DICT_INFO:
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index 46bb5543a..41916b614 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -22,14 +22,15 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
-import android.text.format.DateUtils;
-import android.util.Log;
import android.widget.Toast;
import com.android.inputmethod.latin.R;
import java.util.Locale;
import java.util.Random;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
/**
* Service that handles background tasks for the dictionary provider.
@@ -49,17 +50,10 @@ import java.util.Random;
* to access, and mark the current state as such.
*/
public final class DictionaryService extends Service {
- private static final String TAG = DictionaryService.class.getName();
-
/**
* The package name, to use in the intent actions.
*/
- private static final String PACKAGE_NAME = "com.android.android.inputmethod.latin";
-
- /**
- * The action of the intent to tell the dictionary provider to update now.
- */
- private static final String UPDATE_NOW_INTENT_ACTION = PACKAGE_NAME + ".UPDATE_NOW";
+ private static final String PACKAGE_NAME = "com.android.inputmethod.latin";
/**
* The action of the date changing, used to schedule a periodic freshness check
@@ -82,36 +76,42 @@ 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 = 4 * DateUtils.DAY_IN_MILLIS;
+ private static final long UPDATE_FREQUENCY = 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 = 6 * ((int)AlarmManager.INTERVAL_HOUR);
+ private static final int MAX_ALARM_DELAY = (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 = 14 * DateUtils.DAY_IN_MILLIS;
-
- /**
- * The last seen start Id. This must be stored because we must only call stopSelfResult() with
- * the last seen Id, or the service won't stop.
- */
- private int mLastSeenStartId;
+ private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14);
/**
- * The command count. We need this because we need to not call stopSelfResult() while we still
- * have commands running.
+ * An executor that serializes tasks given to it.
*/
- private int mCommandCount;
+ private ThreadPoolExecutor mExecutor;
+ private static final int WORKER_THREAD_TIMEOUT_SECONDS = 15;
@Override
public void onCreate() {
- mLastSeenStartId = 0;
- mCommandCount = 0;
+ // By default, a thread pool executor does not timeout its core threads, so it will
+ // never kill them when there isn't any work to do any more. That would mean the service
+ // can never die! By creating it this way and calling allowCoreThreadTimeOut, we allow
+ // the single thread to time out after WORKER_THREAD_TIMEOUT_SECONDS = 15 seconds, allowing
+ // the process to be reclaimed by the system any time after that if it's not doing
+ // anything else.
+ // Executors#newSingleThreadExecutor creates a ThreadPoolExecutor but it returns the
+ // superclass ExecutorService which does not have the #allowCoreThreadTimeOut method,
+ // so we can't use that.
+ mExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
+ WORKER_THREAD_TIMEOUT_SECONDS /* keepAliveTime */,
+ TimeUnit.SECONDS /* unit for keepAliveTime */,
+ new LinkedBlockingQueue<Runnable>() /* workQueue */);
+ mExecutor.allowCoreThreadTimeOut(true);
}
@Override
@@ -136,33 +136,35 @@ public final class DictionaryService extends Service {
* - Handle a finished download.
* This executes the actions that must be taken after a file (metadata or dictionary data
* has been downloaded (or failed to download).
+ * The commands that can be spun an another thread will be executed serially, in order, on
+ * a worker thread that is created on demand and terminates after a short while if there isn't
+ * any work left to do.
*/
@Override
public synchronized int onStartCommand(final Intent intent, final int flags,
final int startId) {
final DictionaryService self = this;
- mLastSeenStartId = startId;
- mCommandCount += 1;
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)));
} else {
- // If it's a command that does not require UI, create a thread to do the work
- // and return right away. DATE_CHANGED or UPDATE_NOW are examples of such commands.
- new Thread("updateOrFinishDownload") {
+ // 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
+ // if necessary, or reuse a thread that has become idle as appropriate.
+ // DATE_CHANGED or UPDATE_NOW are examples of commands that can be done on another
+ // thread.
+ mExecutor.submit(new Runnable() {
@Override
public void run() {
dispatchBroadcast(self, intent);
- synchronized(self) {
- if (--mCommandCount <= 0) {
- if (!stopSelfResult(mLastSeenStartId)) {
- Log.e(TAG, "Can't stop ourselves");
- }
- }
- }
+ // Since calls to onStartCommand are serialized, the submissions to the executor
+ // are serialized. That means we are guaranteed to call the stopSelfResult()
+ // in the same order that we got them, so we don't need to take care of the
+ // order.
+ stopSelfResult(startId);
}
- }.start();
+ });
}
return Service.START_REDELIVER_INTENT;
}
@@ -173,9 +175,9 @@ public final class DictionaryService extends Service {
// at midnight local time, but it may happen if the user changes the date
// by hand or something similar happens.
checkTimeAndMaybeSetupUpdateAlarm(context);
- } else if (UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) {
+ } else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) {
// Intent to trigger an update now.
- UpdateHandler.update(context, false);
+ UpdateHandler.tryUpdate(context, false);
} else {
UpdateHandler.downloadFinished(context, intent);
}
@@ -196,7 +198,7 @@ public final class DictionaryService extends Service {
// 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 Intent updateIntent = new Intent(DictionaryService.UPDATE_NOW_INTENT_ACTION);
+ final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
updateIntent, PendingIntent.FLAG_CANCEL_CURRENT);
@@ -226,7 +228,7 @@ public final class DictionaryService extends Service {
*/
public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) {
if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return;
- UpdateHandler.update(context, false);
+ UpdateHandler.tryUpdate(context, false);
}
/**
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index 618322357..7bbd041e7 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -30,6 +30,7 @@ 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.animation.AnimationUtils;
@@ -104,9 +105,16 @@ public final class DictionarySettingsFragment extends PreferenceFragment
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- mUpdateNowMenu = menu.add(Menu.NONE, MENU_UPDATE_NOW, 0, R.string.check_for_updates_now);
- mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- refreshNetworkState();
+ 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();
+ }
}
@Override
@@ -222,7 +230,9 @@ public final class DictionarySettingsFragment extends PreferenceFragment
refreshNetworkState();
removeAnyDictSettings(prefScreen);
+ int i = 0;
for (Preference preference : prefList) {
+ preference.setOrder(i++);
prefScreen.addPreference(preference);
}
}
@@ -302,7 +312,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment
// the description.
final String key = matchLevelString + "." + description + "." + wordlistId;
final WordListPreference existingPref = prefMap.get(key);
- if (null == existingPref || hasPriority(status, existingPref.mStatus)) {
+ if (null == existingPref || existingPref.hasPriorityOver(status)) {
final WordListPreference oldPreference = mCurrentPreferenceMap.get(key);
final WordListPreference pref;
if (null != oldPreference
@@ -313,7 +323,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment
// need to be the same, others have been tested through the key of the
// map. Also, status may differ so we don't want to use #equals() here.
pref = oldPreference;
- pref.mStatus = status;
+ pref.setStatus(status);
} else {
// Otherwise, discard it and create a new one instead.
pref = new WordListPreference(activity, mDictionaryListInterfaceState,
@@ -329,18 +339,6 @@ public final class DictionarySettingsFragment extends PreferenceFragment
}
}
- /**
- * Finds out if a given status has priority over another for display order.
- *
- * @param newStatus
- * @param oldStatus
- * @return whether newStatus has priority over oldStatus.
- */
- private static boolean hasPriority(final int newStatus, final int oldStatus) {
- // Both of these should be one of MetadataDbHelper.STATUS_*
- return newStatus > oldStatus;
- }
-
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
@@ -363,7 +361,12 @@ public final class DictionarySettingsFragment extends PreferenceFragment
new Thread("updateByHand") {
@Override
public void run() {
- UpdateHandler.update(activity, true);
+ // We call tryUpdate(), which returns whether we could successfully start an update.
+ // If we couldn't, we'll never receive the end callback, so we stop the loading
+ // animation and return to the previous screen.
+ if (!UpdateHandler.tryUpdate(activity, true)) {
+ stopLoadingAnimation();
+ }
}
}.start();
}
@@ -378,7 +381,9 @@ public final class DictionarySettingsFragment extends PreferenceFragment
private void startLoadingAnimation() {
mLoadingView.setVisibility(View.VISIBLE);
getView().setVisibility(View.GONE);
- mUpdateNowMenu.setTitle(R.string.cancel);
+ // We come here when the menu element is pressed so presumably it can't be null. But
+ // better safe than sorry.
+ if (null != mUpdateNowMenu) mUpdateNowMenu.setTitle(R.string.cancel);
}
private void stopLoadingAnimation() {
diff --git a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
index d8aa33bb8..859f1b35b 100644
--- a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
@@ -21,8 +21,6 @@ import android.content.Context;
import android.content.Intent;
public final class EventHandler extends BroadcastReceiver {
- private static final String TAG = EventHandler.class.getName();
-
/**
* Receives a intent broadcast.
*
diff --git a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
index d0e8446f5..77f67b8a3 100644
--- a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
@@ -144,7 +144,7 @@ public final class LocaleUtils {
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("%02d", MATCH_LEVEL_MAX - matchLevel);
+ return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel);
}
/**
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
index 03ed267c3..ff5aba6d8 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -25,6 +25,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
import java.io.File;
import java.util.ArrayList;
@@ -36,8 +37,6 @@ import java.util.TreeMap;
* Various helper functions for the state database
*/
public class MetadataDbHelper extends SQLiteOpenHelper {
-
- @SuppressWarnings("unused")
private static final String TAG = MetadataDbHelper.class.getSimpleName();
// This was the initial release version of the database. It should never be
@@ -200,6 +199,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final ContentValues defaultMetadataValues = new ContentValues();
defaultMetadataValues.put(CLIENT_CLIENT_ID_COLUMN, "");
defaultMetadataValues.put(CLIENT_METADATA_URI_COLUMN, defaultMetadataUri);
+ defaultMetadataValues.put(CLIENT_PENDINGID_COLUMN, UpdateHandler.NOT_AN_ID);
db.insert(CLIENT_TABLE_NAME, null, defaultMetadataValues);
}
}
@@ -359,21 +359,21 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
}
/**
- * Get the metadata download ID for a client ID.
+ * Get the metadata download ID for a metadata URI.
*
- * This will retrieve the download ID for the metadata file associated with a client ID.
- * If there is no metadata download in progress for this client, it will return NOT_AN_ID.
+ * This will retrieve the download ID for the metadata file that has the passed URI.
+ * If this URI is not being downloaded right now, it will return NOT_AN_ID.
*
* @param context a context instance to open the database on
- * @param clientId the client ID to retrieve the metadata download ID of
+ * @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
*/
- public static long getMetadataDownloadIdForClient(final Context context,
- final String clientId) {
+ public static long getMetadataDownloadIdForURI(final Context context,
+ final String uri) {
SQLiteDatabase defaultDb = getDb(context, null);
final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME,
new String[] { CLIENT_PENDINGID_COLUMN },
- CLIENT_CLIENT_ID_COLUMN + " = ?", new String[] { clientId },
+ CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri },
null, null, null, null);
try {
if (!cursor.moveToFirst()) return UpdateHandler.NOT_AN_ID;
@@ -437,37 +437,37 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
*/
public static ContentValues completeWithDefaultValues(final ContentValues result)
throws BadFormatException {
- if (!result.containsKey(WORDLISTID_COLUMN) || !result.containsKey(LOCALE_COLUMN)) {
+ if (null == result.get(WORDLISTID_COLUMN) || null == result.get(LOCALE_COLUMN)) {
throw new BadFormatException();
}
// 0 for the pending id, because there is none
- if (!result.containsKey(PENDINGID_COLUMN)) result.put(PENDINGID_COLUMN, 0);
+ if (null == result.get(PENDINGID_COLUMN)) result.put(PENDINGID_COLUMN, 0);
// This is a binary blob of a dictionary
- if (!result.containsKey(TYPE_COLUMN)) result.put(TYPE_COLUMN, TYPE_BULK);
+ if (null == result.get(TYPE_COLUMN)) result.put(TYPE_COLUMN, TYPE_BULK);
// This word list is unknown, but it's present, else we wouldn't be here, so INSTALLED
- if (!result.containsKey(STATUS_COLUMN)) result.put(STATUS_COLUMN, STATUS_INSTALLED);
+ if (null == result.get(STATUS_COLUMN)) result.put(STATUS_COLUMN, STATUS_INSTALLED);
// No description unless specified, because we can't guess it
- if (!result.containsKey(DESCRIPTION_COLUMN)) result.put(DESCRIPTION_COLUMN, "");
+ if (null == result.get(DESCRIPTION_COLUMN)) result.put(DESCRIPTION_COLUMN, "");
// File name - this is an asset, so it works as an already deleted file.
// hence, we need to supply a non-existent file name. Anything will
// do as long as it returns false when tested with File#exist(), and
// the empty string does not, so it's set to "_".
- if (!result.containsKey(LOCAL_FILENAME_COLUMN)) result.put(LOCAL_FILENAME_COLUMN, "_");
+ if (null == result.get(LOCAL_FILENAME_COLUMN)) result.put(LOCAL_FILENAME_COLUMN, "_");
// No remote file name : this can't be downloaded. Unless specified.
- if (!result.containsKey(REMOTE_FILENAME_COLUMN)) result.put(REMOTE_FILENAME_COLUMN, "");
+ if (null == result.get(REMOTE_FILENAME_COLUMN)) result.put(REMOTE_FILENAME_COLUMN, "");
// 0 for the update date : 1970/1/1. Unless specified.
- if (!result.containsKey(DATE_COLUMN)) result.put(DATE_COLUMN, 0);
+ if (null == result.get(DATE_COLUMN)) result.put(DATE_COLUMN, 0);
// Checksum unknown unless specified
- if (!result.containsKey(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, "");
+ if (null == result.get(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, "");
// No filesize unless specified
- if (!result.containsKey(FILESIZE_COLUMN)) result.put(FILESIZE_COLUMN, 0);
+ if (null == result.get(FILESIZE_COLUMN)) result.put(FILESIZE_COLUMN, 0);
// Smallest possible version unless specified
- if (!result.containsKey(VERSION_COLUMN)) result.put(VERSION_COLUMN, 1);
+ if (null == result.get(VERSION_COLUMN)) result.put(VERSION_COLUMN, 1);
// Assume current format unless specified
- if (!result.containsKey(FORMATVERSION_COLUMN))
+ if (null == result.get(FORMATVERSION_COLUMN))
result.put(FORMATVERSION_COLUMN, UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION);
// No flags unless specified
- if (!result.containsKey(FLAGS_COLUMN)) result.put(FLAGS_COLUMN, 0);
+ if (null == result.get(FLAGS_COLUMN)) result.put(FLAGS_COLUMN, 0);
return result;
}
@@ -572,7 +572,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
* If several clients use the same metadata URL, we know to only download it once, and
* dispatch the update process across all relevant clients when the download ends. This means
* several clients may share a single download ID if they share a metadata URI.
- * The dispatching is done in {@link UpdateHandler#downloadFinished(Context, Intent)}, which
+ * The dispatching is done in
+ * {@link UpdateHandler#downloadFinished(Context, android.content.Intent)}, which
* finds out about the list of relevant clients by calling this method.
*
* @param context a context instance to open the databases
@@ -773,15 +774,17 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
if (TextUtils.isEmpty(valuesClientId) || null == valuesMetadataUri
|| null == valuesMetadataAdditionalId) {
// We need all these columns to be filled in
- Utils.l("Missing parameter for updateClientInfo");
+ DebugLogUtils.l("Missing parameter for updateClientInfo");
return;
}
if (!clientId.equals(valuesClientId)) {
// Mismatch! The client violates the protocol.
- Utils.l("Received an updateClientInfo request for ", clientId, " but the values "
- + "contain a different ID : ", valuesClientId);
+ DebugLogUtils.l("Received an updateClientInfo request for ", clientId,
+ " but the values " + "contain a different ID : ", valuesClientId);
return;
}
+ // Default value for a pending ID is NOT_AN_ID
+ values.put(CLIENT_PENDINGID_COLUMN, UpdateHandler.NOT_AN_ID);
final SQLiteDatabase defaultDb = getDb(context, "");
if (-1 == defaultDb.insert(CLIENT_TABLE_NAME, null, values)) {
defaultDb.update(CLIENT_TABLE_NAME, values,
@@ -848,7 +851,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final ContentValues r) {
switch (r.getAsInteger(TYPE_COLUMN)) {
case TYPE_BULK:
- Utils.l("Ended processing a wordlist");
+ DebugLogUtils.l("Ended processing a wordlist");
// Updating a bulk word list is a three-step operation:
// - Add the new entry to the table
// - Remove the old entry from the table
@@ -863,17 +866,20 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
r.getAsString(WORDLISTID_COLUMN),
Integer.toString(STATUS_INSTALLED) },
null, null, null);
- if (c.moveToFirst()) {
- // There should never be more than one file, but if there are, it's a bug
- // and we should remove them all. I think it might happen if the power of the
- // phone is suddenly cut during an update.
- final int filenameIndex = c.getColumnIndex(LOCAL_FILENAME_COLUMN);
- do {
- Utils.l("Setting for removal", c.getString(filenameIndex));
- filenames.add(c.getString(filenameIndex));
- } while (c.moveToNext());
+ try {
+ if (c.moveToFirst()) {
+ // There should never be more than one file, but if there are, it's a bug
+ // and we should remove them all. I think it might happen if the power of
+ // the phone is suddenly cut during an update.
+ final int filenameIndex = c.getColumnIndex(LOCAL_FILENAME_COLUMN);
+ do {
+ DebugLogUtils.l("Setting for removal", c.getString(filenameIndex));
+ filenames.add(c.getString(filenameIndex));
+ } while (c.moveToNext());
+ }
+ } finally {
+ c.close();
}
-
r.put(STATUS_COLUMN, STATUS_INSTALLED);
db.beginTransactionNonExclusive();
// Delete all old entries. There should never be any stalled entries, but if
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index 3f917f13f..0e7c3bb7e 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -38,6 +38,8 @@ import android.util.Log;
import com.android.inputmethod.compat.ConnectivityManagerCompatUtils;
import com.android.inputmethod.compat.DownloadManagerCompatUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
import java.io.File;
import java.io.FileInputStream;
@@ -171,25 +173,27 @@ public final class UpdateHandler {
* Download latest metadata from the server through DownloadManager for all known clients
* @param context The context for retrieving resources
* @param updateNow Whether we should update NOW, or respect bandwidth policies
+ * @return true if an update successfully started, false otherwise.
*/
- public static void update(final Context context, final boolean updateNow) {
+ public static boolean tryUpdate(final Context context, final boolean updateNow) {
// TODO: loop through all clients instead of only doing the default one.
final TreeSet<String> uris = new TreeSet<String>();
final Cursor cursor = MetadataDbHelper.queryClientIds(context);
- if (null == cursor) return;
+ if (null == cursor) return false;
try {
- if (!cursor.moveToFirst()) return;
+ if (!cursor.moveToFirst()) return false;
do {
final String clientId = cursor.getString(0);
final String metadataUri =
MetadataDbHelper.getMetadataUriAsString(context, clientId);
- PrivateLog.log("Update for clientId " + Utils.s(clientId));
- Utils.l("Update for clientId", clientId, " which uses URI ", metadataUri);
+ PrivateLog.log("Update for clientId " + DebugLogUtils.s(clientId));
+ DebugLogUtils.l("Update for clientId", clientId, " which uses URI ", metadataUri);
uris.add(metadataUri);
} while (cursor.moveToNext());
} finally {
cursor.close();
}
+ boolean started = false;
for (final String metadataUri : uris) {
if (!TextUtils.isEmpty(metadataUri)) {
// If the metadata URI is empty, that means we should never update it at all.
@@ -198,8 +202,10 @@ public final class UpdateHandler {
// is a bug and it happens anyway, doing nothing is the right thing to do.
// For more information, {@see DictionaryProvider#insert(Uri, ContentValues)}.
updateClientsWithMetadataUri(context, updateNow, metadataUri);
+ started = true;
}
}
+ return started;
}
/**
@@ -211,14 +217,14 @@ public final class UpdateHandler {
*/
private static void updateClientsWithMetadataUri(final Context context,
final boolean updateNow, final String metadataUri) {
- PrivateLog.log("Update for metadata URI " + Utils.s(metadataUri));
+ PrivateLog.log("Update for metadata URI " + DebugLogUtils.s(metadataUri));
// Adding a disambiguator to circumvent a bug in older versions of DownloadManager.
// DownloadManager also stupidly cuts the extension to replace with its own that it
// gets from the content-type. We need to circumvent this.
final String disambiguator = "#" + System.currentTimeMillis()
- + com.android.inputmethod.latin.Utils.getVersionName(context) + ".json";
+ + ApplicationUtils.getVersionName(context) + ".json";
final Request metadataRequest = new Request(Uri.parse(metadataUri + disambiguator));
- Utils.l("Request =", metadataRequest);
+ DebugLogUtils.l("Request =", metadataRequest);
final Resources res = context.getResources();
// By default, download over roaming is allowed and all network types are allowed too.
@@ -254,7 +260,7 @@ public final class UpdateHandler {
final long downloadId;
synchronized (sSharedIdProtector) {
downloadId = manager.enqueue(metadataRequest);
- Utils.l("Metadata download requested with id", downloadId);
+ DebugLogUtils.l("Metadata download requested with id", downloadId);
// If there is already 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
@@ -266,23 +272,22 @@ public final class UpdateHandler {
}
/**
- * Cancels a pending update, if there is one.
+ * Cancels downloading a file, if there is one for this URI.
*
- * If none, this is a no-op.
+ * 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 clientId the id of the client
+ * @param metadataUri the URI to cancel
* @param manager an instance of DownloadManager
*/
private static void cancelUpdateWithDownloadManager(final Context context,
- final String clientId, final DownloadManager manager) {
+ final String metadataUri, final DownloadManager manager) {
synchronized (sSharedIdProtector) {
final long metadataDownloadId =
- MetadataDbHelper.getMetadataDownloadIdForClient(context, clientId);
+ MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri);
if (NOT_AN_ID == metadataDownloadId) return;
manager.remove(metadataDownloadId);
- writeMetadataDownloadId(context,
- MetadataDbHelper.getMetadataUriAsString(context, clientId), NOT_AN_ID);
+ writeMetadataDownloadId(context, metadataUri, NOT_AN_ID);
}
// Consider a cancellation as a failure. As such, inform listeners that the download
// has failed.
@@ -292,10 +297,10 @@ public final class UpdateHandler {
}
/**
- * Cancels a pending update, if there is one.
+ * Cancels a pending update for this client, if there is one.
*
- * If there is none, this is a no-op. This is a helper method that gets the
- * download manager service.
+ * If we are not currently updating metadata for this client, this is a no-op. This is a helper
+ * method that gets the download manager service and the metadata URI for this client.
*
* @param context the context, to get an instance of DownloadManager
* @param clientId the ID of the client we want to cancel the update of
@@ -303,7 +308,8 @@ public final class UpdateHandler {
public static void cancelUpdate(final Context context, final String clientId) {
final DownloadManager manager =
(DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
- if (null != manager) cancelUpdateWithDownloadManager(context, clientId, manager);
+ final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId);
+ if (null != manager) cancelUpdateWithDownloadManager(context, metadataUri, manager);
}
/**
@@ -326,11 +332,11 @@ public final class UpdateHandler {
*/
public static long registerDownloadRequest(final DownloadManager manager, final Request request,
final SQLiteDatabase db, final String id, final int version) {
- Utils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version);
+ DebugLogUtils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version);
final long downloadId;
synchronized (sSharedIdProtector) {
downloadId = manager.enqueue(request);
- Utils.l("Download requested with id", downloadId);
+ DebugLogUtils.l("Download requested with id", downloadId);
MetadataDbHelper.markEntryAsDownloading(db, id, version, downloadId);
}
return downloadId;
@@ -416,7 +422,7 @@ public final class UpdateHandler {
// Get and check the ID of the file that was downloaded
final long fileId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, NOT_AN_ID);
PrivateLog.log("Download finished with id " + fileId);
- Utils.l("DownloadFinished with id", fileId);
+ DebugLogUtils.l("DownloadFinished with id", fileId);
if (NOT_AN_ID == fileId) return; // Spurious wake-up: ignore
final DownloadManager manager =
@@ -426,7 +432,7 @@ public final class UpdateHandler {
final ArrayList<DownloadRecord> recordList =
getDownloadRecordsForCompletedDownloadInfo(context, downloadInfo);
if (null == recordList) return; // It was someone else's download.
- Utils.l("Received result for download ", fileId);
+ DebugLogUtils.l("Received result for download ", fileId);
// TODO: handle gracefully a null pointer here. This is practically impossible because
// we come here only when DownloadManager explicitly called us when it ended a
@@ -503,7 +509,7 @@ public final class UpdateHandler {
private static void publishUpdateCycleCompletedEvent(final Context context) {
// Even if this is not successful, we have to publish the new state.
PrivateLog.log("Publishing update cycle completed event");
- Utils.l("Publishing update cycle completed event");
+ DebugLogUtils.l("Publishing update cycle completed event");
for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) {
listener.updateCycleCompleted();
}
@@ -517,12 +523,12 @@ public final class UpdateHandler {
// {@link handleWordList(Context,InputStream,ContentValues)}.
// Handle the downloaded file according to its type
if (downloadRecord.isMetadata()) {
- Utils.l("Data D/L'd is metadata for", downloadRecord.mClientId);
+ DebugLogUtils.l("Data D/L'd is metadata for", downloadRecord.mClientId);
// #handleMetadata() closes its InputStream argument
handleMetadata(context, new ParcelFileDescriptor.AutoCloseInputStream(
manager.openDownloadedFile(fileId)), downloadRecord.mClientId);
} else {
- Utils.l("Data D/L'd is a word list");
+ DebugLogUtils.l("Data D/L'd is a word list");
final int wordListStatus = downloadRecord.mAttributes.getAsInteger(
MetadataDbHelper.STATUS_COLUMN);
if (MetadataDbHelper.STATUS_DOWNLOADING == wordListStatus) {
@@ -582,7 +588,7 @@ public final class UpdateHandler {
*/
private static void handleMetadata(final Context context, final InputStream stream,
final String clientId) throws IOException, BadFormatException {
- Utils.l("Entering handleMetadata");
+ DebugLogUtils.l("Entering handleMetadata");
final List<WordListMetadata> newMetadata;
final InputStreamReader reader = new InputStreamReader(stream);
try {
@@ -592,7 +598,7 @@ public final class UpdateHandler {
reader.close();
}
- Utils.l("Downloaded metadata :", newMetadata);
+ DebugLogUtils.l("Downloaded metadata :", newMetadata);
PrivateLog.log("Downloaded metadata\n" + newMetadata);
final ActionBatch actions = computeUpgradeTo(context, clientId, newMetadata);
@@ -617,7 +623,7 @@ public final class UpdateHandler {
// DownloadManager does not have the ability to put the file directly where we want
// it, so we had it download to a temporary place. Now we move it. It will be deleted
// automatically by DownloadManager.
- Utils.l("Downloaded a new word list :", downloadRecord.mAttributes.getAsString(
+ DebugLogUtils.l("Downloaded a new word list :", downloadRecord.mAttributes.getAsString(
MetadataDbHelper.DESCRIPTION_COLUMN), "for", downloadRecord.mClientId);
PrivateLog.log("Downloaded a new word list with description : "
+ downloadRecord.mAttributes.getAsString(MetadataDbHelper.DESCRIPTION_COLUMN)
@@ -676,9 +682,9 @@ public final class UpdateHandler {
*/
private static void copyFile(final InputStream in, final OutputStream out)
throws IOException {
- Utils.l("Copying files");
+ DebugLogUtils.l("Copying files");
if (!(in instanceof FileInputStream) || !(out instanceof FileOutputStream)) {
- Utils.l("Not the right types");
+ DebugLogUtils.l("Not the right types");
copyFileFallback(in, out);
} else {
try {
@@ -687,7 +693,7 @@ public final class UpdateHandler {
sourceChannel.transferTo(0, Integer.MAX_VALUE, destinationChannel);
} catch (IOException e) {
// Can't work with channels, or something went wrong. Copy by hand.
- Utils.l("Won't work");
+ DebugLogUtils.l("Won't work");
copyFileFallback(in, out);
}
}
@@ -702,7 +708,7 @@ public final class UpdateHandler {
*/
private static void copyFileFallback(final InputStream in, final OutputStream out)
throws IOException {
- Utils.l("Falling back to slow copy");
+ DebugLogUtils.l("Falling back to slow copy");
final byte[] buffer = new byte[FILE_COPY_BUFFER_SIZE];
for (int readBytes = in.read(buffer); readBytes >= 0; readBytes = in.read(buffer))
out.write(buffer, 0, readBytes);
@@ -717,10 +723,10 @@ public final class UpdateHandler {
*/
private static String getTempFileName(final Context context, final String locale)
throws IOException {
- Utils.l("Entering openTempFileOutput");
+ DebugLogUtils.l("Entering openTempFileOutput");
final File dir = context.getFilesDir();
final File f = File.createTempFile(locale + "___", DICT_FILE_SUFFIX, dir);
- Utils.l("File name is", f.getName());
+ DebugLogUtils.l("File name is", f.getName());
return f.getName();
}
@@ -741,7 +747,7 @@ public final class UpdateHandler {
final String clientId, List<WordListMetadata> from, List<WordListMetadata> to) {
final ActionBatch actions = new ActionBatch();
// Upgrade existing word lists
- Utils.l("Comparing dictionaries");
+ DebugLogUtils.l("Comparing dictionaries");
final Set<String> wordListIds = new TreeSet<String>();
// TODO: Can these be null?
if (null == from) from = new ArrayList<WordListMetadata>();
@@ -756,7 +762,7 @@ public final class UpdateHandler {
final WordListMetadata newInfo = null == metadataInfo
|| metadataInfo.mFormatVersion > MAXIMUM_SUPPORTED_FORMAT_VERSION
? null : metadataInfo;
- Utils.l("Considering updating ", id, "currentInfo =", currentInfo);
+ DebugLogUtils.l("Considering updating ", id, "currentInfo =", currentInfo);
if (null == currentInfo && null == newInfo) {
// This may happen if a new word list appeared that we can't handle.
@@ -767,7 +773,7 @@ public final class UpdateHandler {
// We may come here if there is a new word list that we can't handle.
Log.i(TAG, "Can't handle word list with id '" + id + "' because it has format"
+ " version " + metadataInfo.mFormatVersion + " and the maximum version"
- + "we can handle is " + MAXIMUM_SUPPORTED_FORMAT_VERSION);
+ + " we can handle is " + MAXIMUM_SUPPORTED_FORMAT_VERSION);
}
continue;
} else if (null == currentInfo) {
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
index 451a0fb82..ba1fce1a8 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
@@ -61,7 +61,7 @@ public final class WordListPreference extends Preference {
public final Locale mLocale;
public final String mDescription;
// The status
- public int mStatus;
+ private int mStatus;
// The size of the dictionary file
private final int mFilesize;
@@ -92,12 +92,25 @@ public final class WordListPreference extends Preference {
setKey(wordlistId);
}
- private void setStatus(final int status) {
+ public void setStatus(final int status) {
if (status == mStatus) return;
mStatus = status;
setSummary(getSummary(status));
}
+ @Override
+ public View onCreateView(final ViewGroup parent) {
+ final View orphanedView = mInterfaceState.findFirstOrphanedView();
+ if (null != orphanedView) return orphanedView; // Will be sent to onBindView
+ final View newView = super.onCreateView(parent);
+ return mInterfaceState.addToCacheAndReturnView(newView);
+ }
+
+ public boolean hasPriorityOver(final int otherPrefStatus) {
+ // Both of these should be one of MetadataDbHelper.STATUS_*
+ return mStatus > otherPrefStatus;
+ }
+
private String getSummary(final int status) {
switch (status) {
// If we are deleting the word list, for the user it's like it's already deleted.
@@ -209,6 +222,9 @@ public final class WordListPreference extends Preference {
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);
if (mInterfaceState.isOpen(mWordlistId)) {
// The button is open.
final int previousStatus = mInterfaceState.getStatus(mWordlistId);
diff --git a/java/src/com/android/inputmethod/event/EventInterpreter.java b/java/src/com/android/inputmethod/event/EventInterpreter.java
index 6efe899bb..726b9206b 100644
--- a/java/src/com/android/inputmethod/event/EventInterpreter.java
+++ b/java/src/com/android/inputmethod/event/EventInterpreter.java
@@ -19,9 +19,9 @@ package com.android.inputmethod.event;
import android.util.SparseArray;
import android.view.KeyEvent;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 1550e77e3..09f1145e9 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -41,7 +41,7 @@ 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.StringUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -113,12 +113,12 @@ public class Key implements Comparable<Key> {
private static final int MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER = 0x80000000;
private static final int MORE_KEYS_FLAGS_HAS_LABELS = 0x40000000;
private static final int MORE_KEYS_FLAGS_NEEDS_DIVIDERS = 0x20000000;
- private static final int MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY = 0x10000000;
+ private static final int MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY = 0x10000000;
private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!";
private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!";
private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!";
private static final String MORE_KEYS_NEEDS_DIVIDERS = "!needsDividers!";
- private static final String MORE_KEYS_EMBEDDED_MORE_KEY = "!embeddedMoreKey!";
+ private static final String MORE_KEYS_NO_PANEL_AUTO_MORE_KEY = "!noPanelAutoMoreKey!";
/** Background type that represents different key background visual than normal one. */
public final int mBackgroundType;
@@ -281,8 +281,8 @@ public class Key implements Comparable<Key> {
if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
}
- if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_EMBEDDED_MORE_KEY)) {
- moreKeysColumn |= MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY;
+ if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
+ moreKeysColumn |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY;
}
mMoreKeysColumnAndFlags = moreKeysColumn;
@@ -453,7 +453,7 @@ public class Key implements Comparable<Key> {
} else {
label = "/" + mLabel;
}
- return String.format("%s%s %d,%d %dx%d %s/%s/%s",
+ return String.format(Locale.ROOT, "%s%s %d,%d %dx%d %s/%s/%s",
Constants.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel,
KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
}
@@ -657,8 +657,8 @@ public class Key implements Comparable<Key> {
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0;
}
- public final boolean hasEmbeddedMoreKey() {
- return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0;
+ public final boolean hasNoPanelAutoMoreKey() {
+ return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0;
}
public final String getOutputText() {
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index e87ecbc7e..fefac96fd 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -21,8 +21,8 @@ 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.CollectionUtils;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
/**
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index 9eeee5baf..b26698665 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -20,16 +20,16 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
public interface KeyboardActionListener {
-
/**
* Called when the user presses a key. This is sent before the {@link #onCodeInput} is called.
* For keys that repeat, this is only called once.
*
* @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key,
* the value will be zero.
+ * @param isRepeatKey true if pressing has occurred while key repeat input.
* @param isSinglePointer true if pressing has occurred while no other key is being pressed.
*/
- public void onPressKey(int primaryCode, boolean isSinglePointer);
+ public void onPressKey(int primaryCode, boolean isRepeatKey, boolean isSinglePointer);
/**
* Called when the user releases a key. This is sent after the {@link #onCodeInput} is called.
@@ -99,11 +99,11 @@ public interface KeyboardActionListener {
*/
public boolean onCustomRequest(int requestCode);
- public static class Adapter implements KeyboardActionListener {
- public static final Adapter EMPTY_LISTENER = new Adapter();
+ public static final KeyboardActionListener EMPTY_LISTENER = new Adapter();
+ public static class Adapter implements KeyboardActionListener {
@Override
- public void onPressKey(int primaryCode, boolean isSinglePointer) {}
+ public void onPressKey(int primaryCode, boolean isRepeatKey, boolean isSinglePointer) {}
@Override
public void onReleaseKey(int primaryCode, boolean withSliding) {}
@Override
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index aa27067bc..8864b7603 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -25,8 +25,8 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
-import com.android.inputmethod.latin.InputTypeUtils;
-import com.android.inputmethod.latin.SubtypeLocale;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Arrays;
import java.util.Locale;
@@ -76,7 +76,7 @@ public final class KeyboardId {
public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) {
mSubtype = params.mSubtype;
- mLocale = SubtypeLocale.getSubtypeLocale(mSubtype);
+ mLocale = SubtypeLocaleUtils.getSubtypeLocale(mSubtype);
mOrientation = params.mOrientation;
mWidth = params.mKeyboardWidth;
mHeight = params.mKeyboardHeight;
@@ -187,7 +187,7 @@ public final class KeyboardId {
public String toString() {
final String orientation = (mOrientation == Configuration.ORIENTATION_PORTRAIT)
? "port" : "land";
- return String.format("[%s %s:%s %s:%dx%d %s %s %s%s%s%s%s%s%s%s%s]",
+ return String.format(Locale.ROOT, "[%s %s:%s %s:%dx%d %s %s %s%s%s%s%s%s%s%s%s]",
elementIdToName(mElementId),
mLocale,
mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 1fe23a330..6a900b45f 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -36,20 +36,21 @@ import android.util.Xml;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
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.latin.AdditionalSubtype;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputAttributes;
-import com.android.inputmethod.latin.InputTypeUtils;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
-import com.android.inputmethod.latin.SubtypeLocale;
import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.XmlParseUtils;
+import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import com.android.inputmethod.latin.utils.XmlParseUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -79,6 +80,14 @@ public final class KeyboardLayoutSet {
private final Context mContext;
private final Params mParams;
+ // How many layouts we forcibly keep in cache. This only includes ALPHABET (default) and
+ // ALPHABET_AUTOMATIC_SHIFTED layouts - other layouts may stay in memory in the map of
+ // soft-references, but we forcibly cache this many alphabetic/auto-shifted layouts.
+ private static final int FORCIBLE_CACHE_SIZE = 4;
+ // By construction of soft references, anything that is also referenced somewhere else
+ // will stay in the cache. So we forcibly keep some references in an array to prevent
+ // them from disappearing from sKeyboardCache.
+ private static final Keyboard[] sForcibleKeyboardCache = new Keyboard[FORCIBLE_CACHE_SIZE];
private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
CollectionUtils.newHashMap();
private static final KeysCache sKeysCache = new KeysCache();
@@ -109,6 +118,7 @@ public final class KeyboardLayoutSet {
boolean mNoSettingsKey;
boolean mLanguageSwitchKeyEnabled;
InputMethodSubtype mSubtype;
+ boolean mIsSpellChecker;
int mOrientation;
int mKeyboardWidth;
int mKeyboardHeight;
@@ -184,7 +194,18 @@ public final class KeyboardLayoutSet {
elementParams.mProximityCharsCorrectionEnabled);
keyboard = builder.build();
sKeyboardCache.put(id, new SoftReference<Keyboard>(keyboard));
-
+ if ((id.mElementId == KeyboardId.ELEMENT_ALPHABET
+ || id.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)
+ && !mParams.mIsSpellChecker) {
+ // We only forcibly cache the primary, "ALPHABET", layouts.
+ for (int i = sForcibleKeyboardCache.length - 1; i >= 1; --i) {
+ sForcibleKeyboardCache[i] = sForcibleKeyboardCache[i - 1];
+ }
+ sForcibleKeyboardCache[0] = keyboard;
+ if (DEBUG_CACHE) {
+ Log.d(TAG, "forcing caching of keyboard with id=" + id);
+ }
+ }
if (DEBUG_CACHE) {
Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": "
+ ((ref == null) ? "LOAD" : "GCed") + " id=" + id);
@@ -267,7 +288,12 @@ public final class KeyboardLayoutSet {
: subtype;
mParams.mSubtype = keyboardSubtype;
mParams.mKeyboardLayoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX
- + SubtypeLocale.getKeyboardLayoutSetName(keyboardSubtype);
+ + SubtypeLocaleUtils.getKeyboardLayoutSetName(keyboardSubtype);
+ return this;
+ }
+
+ public Builder setIsSpellChecker(final boolean isSpellChecker) {
+ mParams.mIsSpellChecker = true;
return this;
}
@@ -419,11 +445,13 @@ public final class KeyboardLayoutSet {
public static KeyboardLayoutSet createKeyboardSetForSpellChecker(final Context context,
final String locale, final String layout) {
final InputMethodSubtype subtype =
- AdditionalSubtype.createAdditionalSubtype(locale, layout, null);
+ AdditionalSubtypeUtils.createAdditionalSubtype(locale, layout, null);
return createKeyboardSet(context, subtype, SPELLCHECKER_DUMMY_KEYBOARD_WIDTH,
- SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false);
+ SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false /* testCasesHaveTouchCoordinates */,
+ true /* isSpellChecker */);
}
+ @UsedForTesting
public static KeyboardLayoutSet createKeyboardSetForTest(final Context context,
final InputMethodSubtype subtype, final int orientation,
final boolean testCasesHaveTouchCoordinates) {
@@ -440,18 +468,20 @@ public final class KeyboardLayoutSet {
throw new RuntimeException("Orientation should be ORIENTATION_LANDSCAPE or "
+ "ORIENTATION_PORTRAIT: orientation=" + orientation);
}
- return createKeyboardSet(context, subtype, width, height, testCasesHaveTouchCoordinates);
+ return createKeyboardSet(context, subtype, width, height, testCasesHaveTouchCoordinates,
+ false /* isSpellChecker */);
}
private static KeyboardLayoutSet createKeyboardSet(final Context context,
final InputMethodSubtype subtype, final int width, final int height,
- final boolean testCasesHaveTouchCoordinates) {
+ final boolean testCasesHaveTouchCoordinates, final boolean isSpellChecker) {
final EditorInfo editorInfo = new EditorInfo();
editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
context, editorInfo);
builder.setScreenGeometry(width, height);
builder.setSubtype(subtype);
+ builder.setIsSpellChecker(isSpellChecker);
if (!testCasesHaveTouchCoordinates) {
// For spell checker and tests
builder.disableTouchPositionCorrectionData();
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index ad08d6477..8880af48c 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -29,18 +29,16 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
-import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
import com.android.inputmethod.keyboard.internal.KeyboardState;
-import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.InputView;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.Settings;
-import com.android.inputmethod.latin.SettingsValues;
import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.settings.SettingsValues;
public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
@@ -68,8 +66,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
};
- private final AudioAndHapticFeedbackManager mFeedbackManager =
- AudioAndHapticFeedbackManager.getInstance();
private SubtypeSwitcher mSubtypeSwitcher;
private SharedPreferences mPrefs;
@@ -151,7 +147,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
mKeyboardLayoutSet = builder.build();
try {
mState.onLoadKeyboard();
- mFeedbackManager.onSettingsChanged(settingsValues);
} catch (KeyboardLayoutSetException e) {
Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
@@ -159,10 +154,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
}
- public void onRingerModeChanged() {
- mFeedbackManager.onRingerModeChanged();
- }
-
public void saveKeyboardState() {
if (getKeyboard() != null) {
mState.onSaveKeyboardState();
@@ -217,9 +208,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
public void onPressKey(final int code, final boolean isSinglePointer) {
- if (isVibrateAndSoundFeedbackRequired()) {
- mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView);
- }
mState.onPressKey(code, isSinglePointer, mLatinIME.getCurrentAutoCapsState());
}
@@ -282,68 +270,27 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
// Implements {@link KeyboardState.SwitchActions}.
@Override
- public void startDoubleTapTimer() {
- final MainKeyboardView keyboardView = getMainKeyboardView();
- if (keyboardView != null) {
- final TimerProxy timer = keyboardView.getTimerProxy();
- timer.startDoubleTapTimer();
- }
- }
-
- // Implements {@link KeyboardState.SwitchActions}.
- @Override
- public void cancelDoubleTapTimer() {
+ public void startDoubleTapShiftKeyTimer() {
final MainKeyboardView keyboardView = getMainKeyboardView();
if (keyboardView != null) {
- final TimerProxy timer = keyboardView.getTimerProxy();
- timer.cancelDoubleTapTimer();
+ keyboardView.startDoubleTapShiftKeyTimer();
}
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
- public boolean isInDoubleTapTimeout() {
- final MainKeyboardView keyboardView = getMainKeyboardView();
- return (keyboardView != null)
- ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false;
- }
-
- // Implements {@link KeyboardState.SwitchActions}.
- @Override
- public void startLongPressTimer(final int code) {
+ public void cancelDoubleTapShiftKeyTimer() {
final MainKeyboardView keyboardView = getMainKeyboardView();
if (keyboardView != null) {
- final TimerProxy timer = keyboardView.getTimerProxy();
- timer.startLongPressTimer(code);
+ keyboardView.cancelDoubleTapShiftKeyTimer();
}
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
- public void cancelLongPressTimer() {
+ public boolean isInDoubleTapShiftKeyTimeout() {
final MainKeyboardView keyboardView = getMainKeyboardView();
- if (keyboardView != null) {
- final TimerProxy timer = keyboardView.getTimerProxy();
- timer.cancelLongPressTimer();
- }
- }
-
- // Implements {@link KeyboardState.SwitchActions}.
- @Override
- public void hapticAndAudioFeedback(final int code) {
- mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView);
- }
-
- public void onLongPressTimeout(final int code) {
- mState.onLongPressTimeout(code);
- }
-
- public boolean isInMomentarySwitchState() {
- return mState.isInMomentarySwitchState();
- }
-
- private boolean isVibrateAndSoundFeedbackRequired() {
- return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
+ return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout();
}
/**
@@ -367,10 +314,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
R.layout.input_view, null);
mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
- if (isHardwareAcceleratedDrawingEnabled) {
- mKeyboardView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
- }
+ mKeyboardView.setHardwareAcceleratedDrawingEnabled(isHardwareAcceleratedDrawingEnabled);
mKeyboardView.setKeyboardActionListener(mLatinIME);
// This always needs to be set since the accessibility state can
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 7941fcba2..054c503d8 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -32,11 +32,12 @@ import android.view.View;
import com.android.inputmethod.keyboard.internal.KeyDrawParams;
import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.util.HashSet;
@@ -153,6 +154,12 @@ public class KeyboardView extends View {
Color.red(color), Color.green(color), Color.blue(color));
}
+ public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
+ if (!enabled) return;
+ // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
+
/**
* Attaches a keyboard to this view. The keyboard can be switched at any time and the
* view will re-layout itself to accommodate the keyboard.
@@ -604,4 +611,8 @@ public class KeyboardView extends View {
super.onDetachedFromWindow();
freeOffscreenBuffer();
}
+
+ public void deallocateMemory() {
+ freeOffscreenBuffer();
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 6c6fc6157..6782317d0 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -21,7 +21,6 @@ import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -54,27 +53,24 @@ import com.android.inputmethod.keyboard.internal.GestureFloatingPreviewText;
import com.android.inputmethod.keyboard.internal.GestureTrailsPreview;
import com.android.inputmethod.keyboard.internal.KeyDrawParams;
import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
+import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview;
-import com.android.inputmethod.keyboard.internal.TouchScreenRegulator;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.CoordinateUtils;
-import com.android.inputmethod.latin.DebugSettings;
-import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
-import com.android.inputmethod.latin.Settings;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-import com.android.inputmethod.latin.StringUtils;
-import com.android.inputmethod.latin.SubtypeLocale;
import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.DebugSettings;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
+import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
+import com.android.inputmethod.latin.utils.ViewLayoutUtils;
import com.android.inputmethod.research.ResearchLogger;
-import java.util.Locale;
import java.util.WeakHashMap;
/**
@@ -119,13 +115,9 @@ import java.util.WeakHashMap;
* @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
*/
public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
- PointerTracker.DrawingProxy, MoreKeysPanel.Controller,
- TouchScreenRegulator.ProcessMotionEvent {
+ PointerTracker.DrawingProxy, MoreKeysPanel.Controller {
private static final String TAG = MainKeyboardView.class.getSimpleName();
- // TODO: Kill process when the usability study mode was changed.
- private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
-
/** Listener for {@link KeyboardActionListener}. */
private KeyboardActionListener mKeyboardActionListener;
@@ -187,12 +179,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
// TODO: Make this parameter customizable by user via settings.
private int mGestureFloatingPreviewTextLingerTimeout;
- private final TouchScreenRegulator mTouchScreenRegulator;
-
private KeyDetector mKeyDetector;
- private final boolean mHasDistinctMultitouch;
- private int mOldPointerCount = 1;
- private Key mOldKey;
+ private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
private final KeyTimerHandler mKeyTimerHandler;
@@ -201,12 +189,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private static final int MSG_TYPING_STATE_EXPIRED = 0;
private static final int MSG_REPEAT_KEY = 1;
private static final int MSG_LONGPRESS_KEY = 2;
- private static final int MSG_DOUBLE_TAP = 3;
+ private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 3;
private static final int MSG_UPDATE_BATCH_INPUT = 4;
- private final int mKeyRepeatStartTimeout;
- private final int mKeyRepeatInterval;
- private final int mLongPressShiftLockTimeout;
private final int mIgnoreAltCodeKeyTimeout;
private final int mGestureRecognitionUpdateTime;
@@ -214,12 +199,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final TypedArray mainKeyboardViewAttr) {
super(outerInstance);
- mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
- mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_keyRepeatInterval, 0);
- mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
@@ -238,18 +217,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
startWhileTypingFadeinAnimation(keyboardView);
break;
case MSG_REPEAT_KEY:
- final Key currentKey = tracker.getKey();
- if (currentKey != null && currentKey.mCode == msg.arg1) {
- tracker.onRegisterKey(currentKey);
- startKeyRepeatTimer(tracker, mKeyRepeatInterval);
- }
+ tracker.onKeyRepeat(msg.arg1);
break;
case MSG_LONGPRESS_KEY:
- if (tracker != null) {
- keyboardView.onLongPress(tracker);
- } else {
- KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1);
- }
+ keyboardView.onLongPress(tracker);
break;
case MSG_UPDATE_BATCH_INPUT:
tracker.updateBatchInputByTimer(SystemClock.uptimeMillis());
@@ -258,19 +229,15 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
}
- private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) {
+ @Override
+ public void startKeyRepeatTimer(final PointerTracker tracker, final int delay) {
final Key key = tracker.getKey();
- if (key == null) {
+ if (key == null || delay == 0) {
return;
}
sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
}
- @Override
- public void startKeyRepeatTimer(final PointerTracker tracker) {
- startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
- }
-
public void cancelKeyRepeatTimer() {
removeMessages(MSG_REPEAT_KEY);
}
@@ -281,49 +248,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
@Override
- public void startLongPressTimer(final int code) {
- cancelLongPressTimer();
- final int delay;
- switch (code) {
- case Constants.CODE_SHIFT:
- delay = mLongPressShiftLockTimeout;
- break;
- default:
- delay = 0;
- break;
- }
- if (delay > 0) {
- sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, code, 0), delay);
- }
- }
-
- @Override
- public void startLongPressTimer(final PointerTracker tracker) {
+ public void startLongPressTimer(final PointerTracker tracker, final int delay) {
cancelLongPressTimer();
- if (tracker == null) {
- return;
- }
- final Key key = tracker.getKey();
- final int delay;
- switch (key.mCode) {
- case Constants.CODE_SHIFT:
- delay = mLongPressShiftLockTimeout;
- break;
- default:
- final int longpressTimeout =
- Settings.getInstance().getCurrent().mKeyLongpressTimeout;
- if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
- // We use longer timeout for sliding finger input started from the symbols
- // mode key.
- delay = longpressTimeout * 3;
- } else {
- delay = longpressTimeout;
- }
- break;
- }
- if (delay > 0) {
- sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
- }
+ if (delay <= 0) return;
+ sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
}
@Override
@@ -390,19 +318,19 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
@Override
- public void startDoubleTapTimer() {
- sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP),
+ public void startDoubleTapShiftKeyTimer() {
+ sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP_SHIFT_KEY),
ViewConfiguration.getDoubleTapTimeout());
}
@Override
- public void cancelDoubleTapTimer() {
- removeMessages(MSG_DOUBLE_TAP);
+ public void cancelDoubleTapShiftKeyTimer() {
+ removeMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
}
@Override
- public boolean isInDoubleTapTimeout() {
- return hasMessages(MSG_DOUBLE_TAP);
+ public boolean isInDoubleTapShiftKeyTimeout() {
+ return hasMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
}
@Override
@@ -494,19 +422,16 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
- mTouchScreenRegulator = new TouchScreenRegulator(context, this);
-
+ PointerTracker.init(getResources());
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final boolean forceNonDistinctMultitouch = prefs.getBoolean(
DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false);
final boolean hasDistinctMultitouch = context.getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
- mHasDistinctMultitouch = hasDistinctMultitouch && !forceNonDistinctMultitouch;
- final Resources res = getResources();
- final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
- ResourceUtils.getDeviceOverrideValue(
- res, R.array.phantom_sudden_move_event_device_list));
- PointerTracker.init(needsPhantomSuddenMoveEventHack);
+ .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)
+ && !forceNonDistinctMultitouch;
+ mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null
+ : new NonDistinctMultitouchHelper();
+
mPreviewPlacerView = new PreviewPlacerView(context, attrs);
final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
@@ -583,6 +508,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
altCodeKeyWhileTypingFadeoutAnimatorResId, this);
mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
altCodeKeyWhileTypingFadeinAnimatorResId, this);
+
+ mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
+ }
+
+ @Override
+ public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
+ super.setHardwareAcceleratedDrawingEnabled(enabled);
+ mPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled);
}
private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
@@ -674,7 +607,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
PointerTracker.setKeyDetector(mKeyDetector);
- mTouchScreenRegulator.setKeyboardGeometry(keyboard.mOccupiedWidth);
mMoreKeysKeyboardCache.clear();
mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
@@ -796,8 +728,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private static final int STATE_RIGHT = 2;
private static final int STATE_NORMAL = 0;
private static final int STATE_HAS_MOREKEYS = 1;
- private static final int[] KEY_PREVIEW_BACKGROUND_DEFAULT_STATE =
- KEY_PREVIEW_BACKGROUND_STATE_TABLE[STATE_MIDDLE][STATE_NORMAL];
@Override
public void showKeyPreview(final PointerTracker tracker) {
@@ -827,10 +757,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final KeyDrawParams drawParams = mKeyDrawParams;
previewText.setTextColor(drawParams.mPreviewTextColor);
final Drawable background = previewText.getBackground();
- if (background != null) {
- background.setState(KEY_PREVIEW_BACKGROUND_DEFAULT_STATE);
- background.setAlpha(PREVIEW_ALPHA);
- }
final String label = key.getPreviewLabel();
// What we show as preview should match what we show on a key top in onDraw().
if (label != null) {
@@ -884,6 +810,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (background != null) {
final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
+ background.setAlpha(PREVIEW_ALPHA);
}
ViewLayoutUtils.placeViewAt(
previewText, previewX, previewY, previewWidth, previewHeight);
@@ -927,9 +854,12 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
@Override
- public void showGestureTrail(final PointerTracker tracker) {
+ public void showGestureTrail(final PointerTracker tracker,
+ final boolean showsFloatingPreviewText) {
locatePreviewPlacerView();
- mGestureFloatingPreviewText.setPreviewPosition(tracker);
+ if (showsFloatingPreviewText) {
+ mGestureFloatingPreviewText.setPreviewPosition(tracker);
+ }
mGestureTrailsPreview.setPreviewPosition(tracker);
}
@@ -987,57 +917,44 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
/**
* Called when a key is long pressed.
* @param tracker the pointer tracker which pressed the parent key
- * @return true if the long press is handled, false otherwise. Subclasses should call the
- * method on the base class if the subclass doesn't wish to handle the call.
*/
- private boolean onLongPress(final PointerTracker tracker) {
+ private void onLongPress(final PointerTracker tracker) {
if (isShowingMoreKeysPanel()) {
- return false;
+ return;
}
final Key key = tracker.getKey();
if (key == null) {
- return false;
+ return;
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.mainKeyboardView_onLongPress();
}
- final int code = key.mCode;
- if (key.hasEmbeddedMoreKey()) {
- final int embeddedCode = key.mMoreKeys[0].mCode;
+ final KeyboardActionListener listener = mKeyboardActionListener;
+ if (key.hasNoPanelAutoMoreKey()) {
+ final int moreKeyCode = key.mMoreKeys[0].mCode;
tracker.onLongPressed();
- invokeCodeInput(embeddedCode);
- invokeReleaseKey(code);
- KeyboardSwitcher.getInstance().hapticAndAudioFeedback(code);
- return true;
+ listener.onPressKey(moreKeyCode, false /* isRepeatKey */, true /* isSinglePointer */);
+ listener.onCodeInput(moreKeyCode,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+ listener.onReleaseKey(moreKeyCode, false /* withSliding */);
+ return;
}
+ final int code = key.mCode;
if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
// Long pressing the space key invokes IME switcher dialog.
- if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
+ if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
tracker.onLongPressed();
- invokeReleaseKey(code);
- return true;
+ listener.onReleaseKey(code, false /* withSliding */);
+ return;
}
}
- return openMoreKeysPanel(key, tracker);
- }
-
- private boolean invokeCustomRequest(final int requestCode) {
- return mKeyboardActionListener.onCustomRequest(requestCode);
+ openMoreKeysPanel(key, tracker);
}
- private void invokeCodeInput(final int code) {
- mKeyboardActionListener.onCodeInput(
- code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
- }
-
- private void invokeReleaseKey(final int code) {
- mKeyboardActionListener.onReleaseKey(code, false);
- }
-
- private boolean openMoreKeysPanel(final Key key, final PointerTracker tracker) {
+ private void openMoreKeysPanel(final Key key, final PointerTracker tracker) {
final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext());
if (moreKeysPanel == null) {
- return false;
+ return;
}
final int[] lastCoords = CoordinateUtils.newInstance();
@@ -1059,7 +976,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final int translatedX = moreKeysPanel.translateX(CoordinateUtils.x(lastCoords));
final int translatedY = moreKeysPanel.translateY(CoordinateUtils.y(lastCoords));
tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
- return true;
}
public boolean isInSlidingKeyInput() {
@@ -1072,8 +988,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
locatePreviewPlacerView();
- if (isShowingMoreKeysPanel()) {
- onDismissMoreKeysPanel();
+ // TODO: Remove this check
+ if (panel.isShowingInParent()) {
+ panel.dismissMoreKeysPanel();
}
mPreviewPlacerView.addView(panel.getContainerView());
mMoreKeysPanel = panel;
@@ -1085,19 +1002,29 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
@Override
- public void onCancelMoreKeysPanel() {
+ public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {
PointerTracker.dismissAllMoreKeysPanels();
}
@Override
- public boolean onDismissMoreKeysPanel() {
+ public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {
dimEntireKeyboard(false /* dimmed */);
if (isShowingMoreKeysPanel()) {
mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView());
mMoreKeysPanel = null;
- return true;
}
- return false;
+ }
+
+ public void startDoubleTapShiftKeyTimer() {
+ mKeyTimerHandler.startDoubleTapShiftKeyTimer();
+ }
+
+ public void cancelDoubleTapShiftKeyTimer() {
+ mKeyTimerHandler.cancelDoubleTapShiftKeyTimer();
+ }
+
+ public boolean isInDoubleTapShiftKeyTimeout() {
+ return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout();
}
@Override
@@ -1113,149 +1040,46 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (getKeyboard() == null) {
return false;
}
- return mTouchScreenRegulator.onTouchEvent(me);
- }
-
- @Override
- public boolean processMotionEvent(final MotionEvent me) {
- final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
- final int action = me.getActionMasked();
- final int pointerCount = me.getPointerCount();
- final int oldPointerCount = mOldPointerCount;
- mOldPointerCount = pointerCount;
-
- // TODO: cleanup this code into a multi-touch to single-touch event converter class?
- // If the device does not have distinct multi-touch support panel, ignore all multi-touch
- // events except a transition from/to single-touch.
- if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
+ if (mNonDistinctMultitouchHelper != null) {
+ if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) {
+ // Key repeating timer will be canceled if 2 or more keys are in action.
+ mKeyTimerHandler.cancelKeyRepeatTimer();
+ }
+ // Non distinct multitouch screen support
+ mNonDistinctMultitouchHelper.processMotionEvent(me, this);
return true;
}
+ return processMotionEvent(me);
+ }
- final long eventTime = me.getEventTime();
- final int index = me.getActionIndex();
- final int id = me.getPointerId(index);
- final int x = (int)me.getX(index);
- final int y = (int)me.getY(index);
-
- // TODO: This might be moved to the tracker.processMotionEvent() call below.
- if (ENABLE_USABILITY_STUDY_LOG && action != MotionEvent.ACTION_MOVE) {
- writeUsabilityStudyLog(me, action, eventTime, index, id, x, y);
+ public boolean processMotionEvent(final MotionEvent me) {
+ if (LatinImeLogger.sUsabilityStudy) {
+ UsabilityStudyLogUtils.writeMotionEvent(me);
}
- // TODO: This should be moved to the tracker.processMotionEvent() call below.
// Currently the same "move" event is being logged twice.
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.mainKeyboardView_processMotionEvent(
- me, action, eventTime, index, id, x, y);
- }
-
- if (mKeyTimerHandler.isInKeyRepeat()) {
- final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
- // Key repeating timer will be canceled if 2 or more keys are in action, and current
- // event (UP or DOWN) is non-modifier key.
- if (pointerCount > 1 && !tracker.isModifier()) {
- mKeyTimerHandler.cancelKeyRepeatTimer();
- }
- // Up event will pass through.
- }
-
- // TODO: cleanup this code into a multi-touch to single-touch event converter class?
- // Translate mutli-touch event to single-touch events on the device that has no distinct
- // multi-touch panel.
- if (nonDistinctMultitouch) {
- // Use only main (id=0) pointer tracker.
- final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
- if (pointerCount == 1 && oldPointerCount == 2) {
- // Multi-touch to single touch transition.
- // Send a down event for the latest pointer if the key is different from the
- // previous key.
- final Key newKey = tracker.getKeyOn(x, y);
- if (mOldKey != newKey) {
- tracker.onDownEvent(x, y, eventTime, this);
- if (action == MotionEvent.ACTION_UP) {
- tracker.onUpEvent(x, y, eventTime);
- }
- }
- } else if (pointerCount == 2 && oldPointerCount == 1) {
- // Single-touch to multi-touch transition.
- // Send an up event for the last pointer.
- final int[] lastCoords = CoordinateUtils.newInstance();
- mOldKey = tracker.getKeyOn(
- CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords));
- tracker.onUpEvent(
- CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords), eventTime);
- } else if (pointerCount == 1 && oldPointerCount == 1) {
- tracker.processMotionEvent(action, x, y, eventTime, this);
- } else {
- Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
- + " (old " + oldPointerCount + ")");
- }
- return true;
- }
-
- if (action == MotionEvent.ACTION_MOVE) {
- for (int i = 0; i < pointerCount; i++) {
- final int pointerId = me.getPointerId(i);
- final PointerTracker tracker = PointerTracker.getPointerTracker(
- pointerId, this);
- final int px = (int)me.getX(i);
- final int py = (int)me.getY(i);
- tracker.onMoveEvent(px, py, eventTime, me);
- if (ENABLE_USABILITY_STUDY_LOG) {
- writeUsabilityStudyLog(me, action, eventTime, i, pointerId, px, py);
- }
- // TODO: This seems to be no longer necessary, and confusing because it leads to
- // duplicate MotionEvents being recorded.
- // if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- // ResearchLogger.mainKeyboardView_processMotionEvent(
- // me, action, eventTime, i, pointerId, px, py);
- // }
- }
- } else {
- final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
- tracker.processMotionEvent(action, x, y, eventTime, this);
+ ResearchLogger.mainKeyboardView_processMotionEvent(me);
}
+ final int index = me.getActionIndex();
+ final int id = me.getPointerId(index);
+ final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
+ tracker.processMotionEvent(me, this);
return true;
}
- private static void writeUsabilityStudyLog(final MotionEvent me, final int action,
- final long eventTime, final int index, final int id, final int x, final int y) {
- final String eventTag;
- switch (action) {
- case MotionEvent.ACTION_UP:
- eventTag = "[Up]";
- break;
- case MotionEvent.ACTION_DOWN:
- eventTag = "[Down]";
- break;
- case MotionEvent.ACTION_POINTER_UP:
- eventTag = "[PointerUp]";
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- eventTag = "[PointerDown]";
- break;
- case MotionEvent.ACTION_MOVE:
- eventTag = "[Move]";
- break;
- default:
- eventTag = "[Action" + action + "]";
- break;
- }
- final float size = me.getSize(index);
- final float pressure = me.getPressure(index);
- UsabilityStudyLogUtils.getInstance().write(
- eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure);
- }
-
- public void cancelAllMessages() {
+ public void cancelAllOngoingEvents() {
mKeyTimerHandler.cancelAllMessages();
mDrawingHandler.cancelAllMessages();
+ dismissAllKeyPreviews();
+ dismissGestureFloatingPreviewText();
+ dismissSlidingKeyInputPreview();
+ PointerTracker.dismissAllMoreKeysPanels();
+ PointerTracker.cancelAllPointerTrackers();
}
public void closing() {
- dismissAllKeyPreviews();
- cancelAllMessages();
- onDismissMoreKeysPanel();
+ cancelAllOngoingEvents();
mMoreKeysKeyboardCache.clear();
}
@@ -1380,17 +1204,17 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private static String layoutLanguageOnSpacebar(final Paint paint,
final InputMethodSubtype subtype, final int width) {
// Choose appropriate language name to fit into the width.
- final String fullText = getFullDisplayName(subtype);
+ final String fullText = SubtypeLocaleUtils.getFullDisplayName(subtype);
if (fitsTextIntoWidth(width, fullText, paint)) {
return fullText;
}
- final String middleText = getMiddleDisplayName(subtype);
+ final String middleText = SubtypeLocaleUtils.getMiddleDisplayName(subtype);
if (fitsTextIntoWidth(width, middleText, paint)) {
return middleText;
}
- final String shortText = getShortDisplayName(subtype);
+ final String shortText = SubtypeLocaleUtils.getShortDisplayName(subtype);
if (fitsTextIntoWidth(width, shortText, paint)) {
return shortText;
}
@@ -1437,45 +1261,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
}
- // InputMethodSubtype's display name for spacebar text in its locale.
- // isAdditionalSubtype (T=true, F=false)
- // locale layout | Short Middle Full
- // ------ ------- - ---- --------- ----------------------
- // en_US qwerty F En English English (US) exception
- // en_GB qwerty F En English English (UK) exception
- // es_US spanish F Es Español Español (EE.UU.) exception
- // fr azerty F Fr Français Français
- // fr_CA qwerty F Fr Français Français (Canada)
- // de qwertz F De Deutsch Deutsch
- // zz qwerty F QWERTY QWERTY
- // fr qwertz T Fr Français Français
- // de qwerty T De Deutsch Deutsch
- // en_US azerty T En English English (US)
- // zz azerty T AZERTY AZERTY
-
- // Get InputMethodSubtype's full display name in its locale.
- static String getFullDisplayName(final InputMethodSubtype subtype) {
- if (SubtypeLocale.isNoLanguage(subtype)) {
- return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
- }
- return SubtypeLocale.getSubtypeLocaleDisplayName(subtype.getLocale());
- }
-
- // Get InputMethodSubtype's short display name in its locale.
- static String getShortDisplayName(final InputMethodSubtype subtype) {
- if (SubtypeLocale.isNoLanguage(subtype)) {
- return "";
- }
- final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
- return StringUtils.capitalizeFirstCodePoint(locale.getLanguage(), locale);
- }
-
- // Get InputMethodSubtype's middle display name in its locale.
- static String getMiddleDisplayName(final InputMethodSubtype subtype) {
- if (SubtypeLocale.isNoLanguage(subtype)) {
- return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
- }
- final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
- return SubtypeLocale.getSubtypeLocaleDisplayName(locale.getLanguage());
+ @Override
+ public void deallocateMemory() {
+ super.deallocateMemory();
+ mGestureTrailsPreview.deallocateMemory();
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index ae08a5953..3fd29dcfd 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.keyboard;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
@@ -28,7 +27,8 @@ import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
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.StringUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
public final class MoreKeysKeyboard extends Keyboard {
private final int mDefaultKeyCoordX;
@@ -75,10 +75,8 @@ public final class MoreKeysKeyboard extends Keyboard {
final boolean isFixedColumnOrder, final int dividerWidth) {
mIsFixedOrder = isFixedColumnOrder;
if (parentKeyboardWidth / keyWidth < Math.min(numKeys, maxColumns)) {
- throw new IllegalArgumentException(
- "Keyboard is too small to hold more keys keyboard: "
- + parentKeyboardWidth + " " + keyWidth + " "
- + numKeys + " " + maxColumns);
+ throw new IllegalArgumentException("Keyboard is too small to hold more keys: "
+ + parentKeyboardWidth + " " + keyWidth + " " + numKeys + " " + maxColumns);
}
mDefaultKeyWidth = keyWidth;
mDefaultRowHeight = rowHeight;
@@ -279,8 +277,14 @@ public final class MoreKeysKeyboard extends Keyboard {
mParentKey = parentKey;
final int width, height;
+ // {@link KeyPreviewDrawParams#mPreviewVisibleWidth} should have been set at
+ // {@link MainKeyboardView#showKeyPreview(PointerTracker}, though there may be
+ // some chances that the value is zero. <code>width == 0</code> will cause
+ // zero-division error at
+ // {@link MoreKeysKeyboardParams#setParameters(int,int,int,int,int,int,boolean,int)}.
final boolean singleMoreKeyWithPreview = parentKeyboardView.isKeyPreviewPopupEnabled()
- && !parentKey.noKeyPreview() && parentKey.mMoreKeys.length == 1;
+ && !parentKey.noKeyPreview() && parentKey.mMoreKeys.length == 1
+ && keyPreviewDrawParams.mPreviewVisibleWidth > 0;
if (singleMoreKeyWithPreview) {
// Use pre-computed width and height if this more keys keyboard has only one key to
// mitigate visual flicker between key preview and more keys keyboard.
@@ -291,22 +295,14 @@ public final class MoreKeysKeyboard extends Keyboard {
// adjusted with their bottom paddings deducted.
width = keyPreviewDrawParams.mPreviewVisibleWidth;
height = keyPreviewDrawParams.mPreviewVisibleHeight + mParams.mVerticalGap;
- // TODO: Remove this check.
- if (width == 0) {
- throw new IllegalArgumentException(
- "Zero width key detected: " + parentKey + " in " + parentKeyboard.mId);
- }
} else {
- width = getMaxKeyWidth(parentKeyboardView, parentKey, mParams.mDefaultKeyWidth,
- context.getResources());
+ final float padding = context.getResources().getDimension(
+ R.dimen.more_keys_keyboard_key_horizontal_padding)
+ + (parentKey.hasLabelsInMoreKeys()
+ ? mParams.mDefaultKeyWidth * LABEL_PADDING_RATIO : 0.0f);
+ width = getMaxKeyWidth(parentKey, mParams.mDefaultKeyWidth, padding,
+ parentKeyboardView.newLabelPaint(parentKey));
height = parentKeyboard.mMostCommonKeyHeight;
- // TODO: Remove this check.
- if (width == 0) {
- throw new IllegalArgumentException(
- "Zero width calculated: " + parentKey
- + " moreKeys=" + java.util.Arrays.toString(parentKey.mMoreKeys)
- + " in " + parentKeyboard.mId);
- }
}
final int dividerWidth;
if (parentKey.needsDividersInMoreKeys()) {
@@ -318,16 +314,12 @@ public final class MoreKeysKeyboard extends Keyboard {
}
mParams.setParameters(parentKey.mMoreKeys.length, parentKey.getMoreKeysColumn(),
width, height, parentKey.mX + parentKey.mWidth / 2,
- parentKeyboardView.getMeasuredWidth(), parentKey.isFixedColumnOrderMoreKeys(),
+ parentKeyboard.mId.mWidth, parentKey.isFixedColumnOrderMoreKeys(),
dividerWidth);
}
- private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey,
- final int minKeyWidth, final Resources res) {
- final float padding =
- res.getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
- + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0.0f);
- final Paint paint = view.newLabelPaint(parentKey);
+ private static int getMaxKeyWidth(final Key parentKey, final int minKeyWidth,
+ final float padding, final Paint paint) {
int maxWidth = minKeyWidth;
for (final MoreKeySpec spec : parentKey.mMoreKeys) {
final String label = spec.mLabel;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index a82fb79bd..94f6a3cf2 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -23,8 +23,8 @@ import android.view.MotionEvent;
import android.view.View;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.CoordinateUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
/**
* A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and
@@ -34,7 +34,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
private final int[] mCoordinates = CoordinateUtils.newInstance();
protected final KeyDetector mKeyDetector;
- private Controller mController;
+ private Controller mController = EMPTY_CONTROLLER;
protected KeyboardActionListener mListener;
private int mOriginX;
private int mOriginY;
@@ -119,7 +119,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
onMoveKeyInternal(x, y, pointerId);
if (hasOldKey && mCurrentKey == null) {
// If the pointer has moved too far away from any target then cancel the panel.
- mController.onCancelMoreKeysPanel();
+ mController.onCancelMoreKeysPanel(this);
}
}
@@ -173,9 +173,11 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
}
@Override
- public boolean dismissMoreKeysPanel() {
- if (mController == null) return false;
- return mController.onDismissMoreKeysPanel();
+ public void dismissMoreKeysPanel() {
+ if (!isShowingInParent()) {
+ return;
+ }
+ mController.onDismissMoreKeysPanel(this);
}
@Override
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
index 9c677e5c8..886c6286f 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
@@ -22,21 +22,32 @@ public interface MoreKeysPanel {
public interface Controller {
/**
* Add the {@link MoreKeysPanel} to the target view.
- * @param panel
+ * @param panel the panel to be shown.
*/
public void onShowMoreKeysPanel(final MoreKeysPanel panel);
/**
* Remove the current {@link MoreKeysPanel} from the target view.
+ * @param panel the panel to be dismissed.
*/
- public boolean onDismissMoreKeysPanel();
+ public void onDismissMoreKeysPanel(final MoreKeysPanel panel);
/**
* Instructs the parent to cancel the panel (e.g., when entering a different input mode).
+ * @param panel the panel to be canceled.
*/
- public void onCancelMoreKeysPanel();
+ public void onCancelMoreKeysPanel(final MoreKeysPanel panel);
}
+ public static final Controller EMPTY_CONTROLLER = new Controller() {
+ @Override
+ public void onShowMoreKeysPanel(final MoreKeysPanel panel) {}
+ @Override
+ public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {}
+ @Override
+ public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {}
+ };
+
/**
* Initializes the layout and event handling of this {@link MoreKeysPanel} and calls the
* controller's onShowMoreKeysPanel to add the panel's container view.
@@ -57,7 +68,7 @@ public interface MoreKeysPanel {
* Dismisses the more keys panel and calls the controller's onDismissMoreKeysPanel to remove
* the panel's container view.
*/
- public boolean dismissMoreKeysPanel();
+ public void dismissMoreKeysPanel();
/**
* Process a move event on the more keys panel.
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 174239325..ab5fee99d 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -16,8 +16,10 @@
package com.android.inputmethod.keyboard;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.SystemClock;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
@@ -27,13 +29,15 @@ import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokePara
import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints;
import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints.GestureStrokePreviewParams;
import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.CoordinateUtils;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
@@ -84,19 +88,18 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
public void dismissKeyPreview(PointerTracker tracker);
public void showSlidingKeyInputPreview(PointerTracker tracker);
public void dismissSlidingKeyInputPreview();
- public void showGestureTrail(PointerTracker tracker);
+ public void showGestureTrail(PointerTracker tracker, boolean showsFloatingPreviewText);
}
public interface TimerProxy {
public void startTypingStateTimer(Key typedKey);
public boolean isTypingState();
- public void startKeyRepeatTimer(PointerTracker tracker);
- public void startLongPressTimer(PointerTracker tracker);
- public void startLongPressTimer(int code);
+ public void startKeyRepeatTimer(PointerTracker tracker, int delay);
+ public void startLongPressTimer(PointerTracker tracker, int delay);
public void cancelLongPressTimer();
- public void startDoubleTapTimer();
- public void cancelDoubleTapTimer();
- public boolean isInDoubleTapTimeout();
+ public void startDoubleTapShiftKeyTimer();
+ public void cancelDoubleTapShiftKeyTimer();
+ public boolean isInDoubleTapShiftKeyTimeout();
public void cancelKeyTimers();
public void startUpdateBatchInputTimer(PointerTracker tracker);
public void cancelUpdateBatchInputTimer(PointerTracker tracker);
@@ -108,19 +111,17 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
@Override
public boolean isTypingState() { return false; }
@Override
- public void startKeyRepeatTimer(PointerTracker tracker) {}
+ public void startKeyRepeatTimer(PointerTracker tracker, int delay) {}
@Override
- public void startLongPressTimer(PointerTracker tracker) {}
- @Override
- public void startLongPressTimer(int code) {}
+ public void startLongPressTimer(PointerTracker tracker, int delay) {}
@Override
public void cancelLongPressTimer() {}
@Override
- public void startDoubleTapTimer() {}
+ public void startDoubleTapShiftKeyTimer() {}
@Override
- public void cancelDoubleTapTimer() {}
+ public void cancelDoubleTapShiftKeyTimer() {}
@Override
- public boolean isInDoubleTapTimeout() { return false; }
+ public boolean isInDoubleTapShiftKeyTimeout() { return false; }
@Override
public void cancelKeyTimers() {}
@Override
@@ -137,6 +138,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
public final int mTouchNoiseThresholdTime;
public final int mTouchNoiseThresholdDistance;
public final int mSuppressKeyPreviewAfterBatchInputDuration;
+ public final int mKeyRepeatStartTimeout;
+ public final int mKeyRepeatInterval;
+ public final int mLongPressShiftLockTimeout;
public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
@@ -145,6 +149,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
mTouchNoiseThresholdTime = 0;
mTouchNoiseThresholdDistance = 0;
mSuppressKeyPreviewAfterBatchInputDuration = 0;
+ mKeyRepeatStartTimeout = 0;
+ mKeyRepeatInterval = 0;
+ mLongPressShiftLockTimeout = 0;
}
public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
@@ -156,6 +163,12 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0);
+ mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
+ mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_keyRepeatInterval, 0);
+ mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
}
}
@@ -167,8 +180,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// Move this threshold to resource.
// TODO: Device specific parameter would be better for device specific hack?
private static final float PHANTOM_SUDDEN_MOVE_THRESHOLD = 0.25f; // in keyWidth
- // This hack might be device specific.
- private static final boolean sNeedsProximateBogusDownMoveUpEventHack = true;
+ // This hack is applied to certain classes of tablets.
+ // See {@link #needsProximateBogusDownMoveUpEventHack(Resources)}.
+ private static boolean sNeedsProximateBogusDownMoveUpEventHack;
private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
private static final PointerTrackerQueue sPointerTrackerQueue = new PointerTrackerQueue();
@@ -178,7 +192,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private DrawingProxy mDrawingProxy;
private TimerProxy mTimerProxy;
private KeyDetector mKeyDetector;
- private KeyboardActionListener mListener = KeyboardActionListener.Adapter.EMPTY_LISTENER;
+ private KeyboardActionListener mListener = KeyboardActionListener.EMPTY_LISTENER;
private Keyboard mKeyboard;
private int mPhantonSuddenMoveThreshold;
@@ -326,6 +340,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// the more keys panel currently being shown. equals null if no panel is active.
private MoreKeysPanel mMoreKeysPanel;
+ private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3;
// true if this pointer is in a sliding key input.
boolean mIsInSlidingKeyInput;
// true if this pointer is in a sliding key input from a modifier key,
@@ -337,8 +352,34 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints;
- public static void init(final boolean needsPhantomSuddenMoveEventHack) {
- sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
+ private static final int SMALL_TABLET_SMALLEST_WIDTH = 600; // dp
+ private static final int LARGE_TABLET_SMALLEST_WIDTH = 768; // dp
+
+ private static boolean needsProximateBogusDownMoveUpEventHack(final Resources res) {
+ // The proximate bogus down move up event hack is needed for a device such like,
+ // 1) is large tablet, or 2) is small tablet and the screen density is less than hdpi.
+ // Though it seems odd to use screen density as criteria of the quality of the touch
+ // screen, the small table that has a less density screen than hdpi most likely has been
+ // made with the touch screen that needs the hack.
+ final int sw = res.getConfiguration().smallestScreenWidthDp;
+ final boolean isLargeTablet = (sw >= LARGE_TABLET_SMALLEST_WIDTH);
+ final boolean isSmallTablet =
+ (sw >= SMALL_TABLET_SMALLEST_WIDTH && sw < LARGE_TABLET_SMALLEST_WIDTH);
+ final int densityDpi = res.getDisplayMetrics().densityDpi;
+ final boolean hasLowDensityScreen = (densityDpi < DisplayMetrics.DENSITY_HIGH);
+ final boolean needsTheHack = isLargeTablet || (isSmallTablet && hasLowDensityScreen);
+ if (DEBUG_MODE) {
+ Log.d(TAG, "needsProximateBogusDownMoveUpEventHack=" + needsTheHack
+ + " smallestScreenWidthDp=" + sw + " densityDpi=" + densityDpi);
+ }
+ return needsTheHack;
+ }
+
+ public static void init(final Resources res) {
+ sNeedsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
+ ResourceUtils.getDeviceOverrideValue(
+ res, R.array.phantom_sudden_move_event_device_list));
+ sNeedsProximateBogusDownMoveUpEventHack = needsProximateBogusDownMoveUpEventHack(res);
sParams = PointerTrackerParams.DEFAULT;
sGestureStrokeParams = GestureStrokeParams.DEFAULT;
sGesturePreviewParams = GestureStrokePreviewParams.DEFAULT;
@@ -386,6 +427,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return sPointerTrackerQueue.isAnyInSlidingKeyInput();
}
+ public static void cancelAllPointerTrackers() {
+ sPointerTrackerQueue.cancelAllPointerTrackers();
+ }
+
public static void setKeyboardActionListener(final KeyboardActionListener listener) {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
@@ -433,6 +478,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
mPointerId = id;
mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(
id, sGestureStrokeParams, sGesturePreviewParams);
+ setKeyEventHandler(handler);
+ }
+
+ private void setKeyEventHandler(final KeyEventHandler handler) {
setKeyDetectorInner(handler.getKeyDetector());
mListener = handler.getKeyboardActionListener();
mDrawingProxy = handler.getDrawingProxy();
@@ -440,7 +489,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
// Returns true if keyboard has been changed by this callback.
- private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
+ private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key,
+ final boolean isRepeatKey) {
// While gesture input is going on, this method should be a no-operation. But when gesture
// input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code>
// are set to false. To keep this method is a no-operation,
@@ -450,16 +500,17 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
if (DEBUG_LISTENER) {
- Log.d(TAG, String.format("[%d] onPress : %s%s%s", mPointerId,
+ Log.d(TAG, String.format("[%d] onPress : %s%s%s%s", mPointerId,
KeyDetector.printableCode(key),
ignoreModifierKey ? " ignoreModifier" : "",
- key.isEnabled() ? "" : " disabled"));
+ key.isEnabled() ? "" : " disabled",
+ isRepeatKey ? " repeat" : ""));
}
if (ignoreModifierKey) {
return false;
}
if (key.isEnabled()) {
- mListener.onPressKey(key.mCode, getActivePointerTrackerCount() == 1);
+ mListener.onPressKey(key.mCode, isRepeatKey, getActivePointerTrackerCount() == 1);
final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
mKeyboardLayoutHasBeenChanged = false;
mTimerProxy.startTypingStateTimer(key);
@@ -570,10 +621,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return mIsInSlidingKeyInput;
}
- public boolean isInSlidingKeyInputFromModifier() {
- return mIsInSlidingKeyInputFromModifier;
- }
-
public Key getKey() {
return mCurrentKey;
}
@@ -721,7 +768,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return sPointerTrackerQueue.size();
}
- public boolean isOldestTrackerInQueue() {
+ private boolean isOldestTrackerInQueue() {
return sPointerTrackerQueue.getOldestElement() == this;
}
@@ -744,7 +791,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
dismissAllMoreKeysPanels();
}
mTimerProxy.cancelLongPressTimer();
- mDrawingProxy.showGestureTrail(this);
+ // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+ mDrawingProxy.showGestureTrail(
+ this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
}
public void updateBatchInputByTimer(final long eventTime) {
@@ -760,7 +809,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (mIsTrackingForActionDisabled) {
return;
}
- mDrawingProxy.showGestureTrail(this);
+ // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+ mDrawingProxy.showGestureTrail(
+ this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
}
private void updateBatchInput(final long eventTime) {
@@ -801,11 +852,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (mIsTrackingForActionDisabled) {
return;
}
- mDrawingProxy.showGestureTrail(this);
+ // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+ mDrawingProxy.showGestureTrail(
+ this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
}
private void cancelBatchInput() {
- sPointerTrackerQueue.cancelAllPointerTracker();
+ cancelAllPointerTrackers();
mIsDetectingGesture = false;
if (!sInGesture) {
return;
@@ -817,8 +870,23 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
mListener.onCancelBatchInput();
}
- public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
- final KeyEventHandler handler) {
+ public void processMotionEvent(final MotionEvent me, final KeyEventHandler handler) {
+ final int action = me.getActionMasked();
+ final long eventTime = me.getEventTime();
+ if (action == MotionEvent.ACTION_MOVE) {
+ final int pointerCount = me.getPointerCount();
+ for (int index = 0; index < pointerCount; index++) {
+ final int id = me.getPointerId(index);
+ final PointerTracker tracker = getPointerTracker(id, handler);
+ final int x = (int)me.getX(index);
+ final int y = (int)me.getY(index);
+ tracker.onMoveEvent(x, y, eventTime, me);
+ }
+ return;
+ }
+ final int index = me.getActionIndex();
+ final int x = (int)me.getX(index);
+ final int y = (int)me.getY(index);
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
@@ -828,24 +896,18 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
case MotionEvent.ACTION_POINTER_UP:
onUpEvent(x, y, eventTime);
break;
- case MotionEvent.ACTION_MOVE:
- onMoveEvent(x, y, eventTime, null);
- break;
case MotionEvent.ACTION_CANCEL:
onCancelEvent(x, y, eventTime);
break;
}
}
- public void onDownEvent(final int x, final int y, final long eventTime,
+ private void onDownEvent(final int x, final int y, final long eventTime,
final KeyEventHandler handler) {
if (DEBUG_EVENT) {
printTouchEvent("onDownEvent:", x, y, eventTime);
}
- mDrawingProxy = handler.getDrawingProxy();
- mTimerProxy = handler.getTimerProxy();
- setKeyboardActionListener(handler.getKeyboardActionListener());
- setKeyDetectorInner(handler.getKeyDetector());
+ setKeyEventHandler(handler);
// Naive up-to-down noise filter.
final long deltaT = eventTime - mUpTime;
if (deltaT < sParams.mTouchNoiseThresholdTime) {
@@ -877,7 +939,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
// A gesture should start only from a non-modifier key.
mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard()
- && key != null && !key.isModifier();
+ && key != null && !key.isModifier() && !key.isRepeatable();
if (mIsDetectingGesture) {
if (getActivePointerTrackerCount() == 1) {
sGestureFirstDownTime = eventTime;
@@ -905,7 +967,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// This onPress call may have changed keyboard layout. Those cases are detected at
// {@link #setKeyboard}. In those cases, we should update key according to the new
// keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
key = onDownKey(x, y, eventTime);
}
@@ -955,7 +1017,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
+ private void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
if (DEBUG_MOVE_EVENT) {
printTouchEvent("onMoveEvent:", x, y, eventTime);
}
@@ -981,7 +1043,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final int translatedY = mMoreKeysPanel.translateY(y);
mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime);
onMoveKey(x, y);
- mDrawingProxy.showSlidingKeyInputPreview(this);
+ if (mIsInSlidingKeyInputFromModifier) {
+ mDrawingProxy.showSlidingKeyInputPreview(this);
+ }
return;
}
onMoveEventInternal(x, y, eventTime);
@@ -993,7 +1057,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// at {@link #setKeyboard}. In those cases, we should update key according
// to the new keyboard layout.
Key key = newKey;
- if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
key = onMoveKey(x, y);
}
onMoveToNewKey(key, x, y);
@@ -1136,10 +1200,12 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
slideOutFromOldKey(oldKey, x, y);
}
}
- mDrawingProxy.showSlidingKeyInputPreview(this);
+ if (mIsInSlidingKeyInputFromModifier) {
+ mDrawingProxy.showSlidingKeyInputPreview(this);
+ }
}
- public void onUpEvent(final int x, final int y, final long eventTime) {
+ private void onUpEvent(final int x, final int y, final long eventTime) {
if (DEBUG_EVENT) {
printTouchEvent("onUpEvent :", x, y, eventTime);
}
@@ -1239,13 +1305,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
sPointerTrackerQueue.remove(this);
}
- public void onCancelEvent(final int x, final int y, final long eventTime) {
+ private void onCancelEvent(final int x, final int y, final long eventTime) {
if (DEBUG_EVENT) {
printTouchEvent("onCancelEvt:", x, y, eventTime);
}
cancelBatchInput();
- sPointerTrackerQueue.cancelAllPointerTracker();
+ cancelAllPointerTrackers();
sPointerTrackerQueue.releaseAllPointers(eventTime);
onCancelEventInternal();
}
@@ -1260,23 +1326,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void startRepeatKey(final Key key) {
- if (sInGesture) return;
- if (key == null) return;
- if (!key.isRepeatable()) return;
- // Don't start key repeat when we are in sliding input mode.
- if (mIsInSlidingKeyInput) return;
- onRegisterKey(key);
- mTimerProxy.startKeyRepeatTimer(this);
- }
-
- public void onRegisterKey(final Key key) {
- if (key != null) {
- detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
- mTimerProxy.startTypingStateTimer(key);
- }
- }
-
private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
final Key newKey) {
if (mKeyDetector == null) {
@@ -1328,7 +1377,22 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// We always need to start the long press timer if the key has its more keys regardless of
// whether or not we are in the sliding input mode.
if (mIsInSlidingKeyInput && key.mMoreKeys == null) return;
- mTimerProxy.startLongPressTimer(this);
+ final int delay;
+ switch (key.mCode) {
+ case Constants.CODE_SHIFT:
+ delay = sParams.mLongPressShiftLockTimeout;
+ break;
+ default:
+ final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout;
+ if (mIsInSlidingKeyInputFromModifier) {
+ // We use longer timeout for sliding finger input started from the modifier key.
+ delay = longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
+ } else {
+ delay = longpressTimeout;
+ }
+ break;
+ }
+ mTimerProxy.startLongPressTimer(this, delay);
}
private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) {
@@ -1342,6 +1406,26 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
callListenerOnRelease(key, code, false /* withSliding */);
}
+ private void startRepeatKey(final Key key) {
+ if (sInGesture) return;
+ if (key == null) return;
+ if (!key.isRepeatable()) return;
+ // Don't start key repeat when we are in sliding input mode.
+ if (mIsInSlidingKeyInput) return;
+ detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
+ mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatStartTimeout);
+ }
+
+ public void onKeyRepeat(final int code) {
+ final Key key = getKey();
+ if (key == null || key.mCode != code) {
+ return;
+ }
+ mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatInterval);
+ callListenerOnPressAndCheckKeyboardLayoutChange(key, true /* isRepeatKey */);
+ callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis());
+ }
+
private void printTouchEvent(final String title, final int x, final int y,
final long eventTime) {
final Key key = mKeyDetector.detectHitKey(x, y);
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 57d3fede4..9b0a33cec 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -22,7 +22,7 @@ import android.util.Log;
import com.android.inputmethod.keyboard.internal.TouchPositionCorrection;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.JniUtils;
+import com.android.inputmethod.latin.utils.JniUtils;
import java.util.Arrays;
@@ -240,28 +240,121 @@ public class ProximityInfo {
private void computeNearestNeighbors() {
final int defaultWidth = mMostCommonKeyWidth;
- final Key[] keys = mKeys;
- final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE);
- final int threshold = thresholdBase * thresholdBase;
+ final int keyCount = mKeys.length;
+ final int gridSize = mGridNeighbors.length;
+ final int threshold = (int) (defaultWidth * SEARCH_DISTANCE);
+ final int thresholdSquared = threshold * threshold;
// Round-up so we don't have any pixels outside the grid
- final Key[] neighborKeys = new Key[keys.length];
- final int gridWidth = mGridWidth * mCellWidth;
- final int gridHeight = mGridHeight * mCellHeight;
- for (int x = 0; x < gridWidth; x += mCellWidth) {
- for (int y = 0; y < gridHeight; y += mCellHeight) {
- final int centerX = x + mCellWidth / 2;
- final int centerY = y + mCellHeight / 2;
- int count = 0;
- for (final Key key : keys) {
- if (key.isSpacer()) continue;
- if (key.squaredDistanceToEdge(centerX, centerY) < threshold) {
- neighborKeys[count++] = key;
+ final int fullGridWidth = mGridWidth * mCellWidth;
+ final int fullGridHeight = mGridHeight * mCellHeight;
+
+ // For large layouts, 'neighborsFlatBuffer' is about 80k of memory: gridSize is usually 512,
+ // keycount is about 40 and a pointer to a Key is 4 bytes. This contains, for each cell,
+ // enough space for as many keys as there are on the keyboard. Hence, every
+ // keycount'th element is the start of a new cell, and each of these virtual subarrays
+ // start empty with keycount spaces available. This fills up gradually in the loop below.
+ // Since in the practice each cell does not have a lot of neighbors, most of this space is
+ // actually just empty padding in this fixed-size buffer.
+ final Key[] neighborsFlatBuffer = new Key[gridSize * keyCount];
+ final int[] neighborCountPerCell = new int[gridSize];
+ final int halfCellWidth = mCellWidth / 2;
+ final int halfCellHeight = mCellHeight / 2;
+ for (final Key key : mKeys) {
+ if (key.isSpacer()) continue;
+
+/* HOW WE PRE-SELECT THE CELLS (iterate over only the relevant cells, instead of all of them)
+
+ We want to compute the distance for keys that are in the cells that are close enough to the
+ key border, as this method is performance-critical. These keys are represented with 'star'
+ background on the diagram below. Let's consider the Y case first.
+
+ We want to select the cells which center falls between the top of the key minus the threshold,
+ and the bottom of the key plus the threshold.
+ topPixelWithinThreshold is key.mY - threshold, and bottomPixelWithinThreshold is
+ key.mY + key.mHeight + threshold.
+
+ Then we need to compute the center of the top row that we need to evaluate, as we'll iterate
+ from there.
+
+(0,0)----> x
+| .-------------------------------------------.
+| | | | | | | | | | | | |
+| |---+---+---+---+---+---+---+---+---+---+---| .- top of top cell (aligned on the grid)
+| | | | | | | | | | | | | |
+| |-----------+---+---+---+---+---+---+---+---|---' v
+| | | | |***|***|*_________________________ topPixelWithinThreshold | yDeltaToGrid
+| |---+---+---+-----^-+-|-+---+---+---+---+---| ^
+| | | | |***|*|*|*|*|***|***| | | | ______________________________________
+v |---+---+--threshold--|-+---+---+---+---+---| |
+ | | | |***|*|*|*|*|***|***| | | | | Starting from key.mY, we substract
+y |---+---+---+---+-v-+-|-+---+---+---+---+---| | thresholdBase and get the top pixel
+ | | | |***|**########------------------- key.mY | within the threshold. We align that on
+ |---+---+---+---+--#+---+-#-+---+---+---+---| | the grid by computing the delta to the
+ | | | |***|**#|***|*#*|***| | | | | grid, and get the top of the top cell.
+ |---+---+---+---+--#+---+-#-+---+---+---+---| |
+ | | | |***|**########*|***| | | | | Adding half the cell height to the top
+ |---+---+---+---+---+-|-+---+---+---+---+---| | of the top cell, we get the middle of
+ | | | |***|***|*|*|***|***| | | | | the top cell (yMiddleOfTopCell).
+ |---+---+---+---+---+-|-+---+---+---+---+---| |
+ | | | |***|***|*|*|***|***| | | | |
+ |---+---+---+---+---+-|________________________ yEnd | Since we only want to add the key to
+ | | | | | | | (bottomPixelWithinThreshold) | the proximity if it's close enough to
+ |---+---+---+---+---+---+---+---+---+---+---| | the center of the cell, we only need
+ | | | | | | | | | | | | | to compute for these cells where
+ '---'---'---'---'---'---'---'---'---'---'---' | topPixelWithinThreshold is above the
+ (positive x,y) | center of the cell. This is the case
+ | when yDeltaToGrid is less than half
+ [Zoomed in diagram] | the height of the cell.
+ +-------+-------+-------+-------+-------+ |
+ | | | | | | | On the zoomed in diagram, on the right
+ | | | | | | | the topPixelWithinThreshold (represented
+ | | | | | | top of | with an = sign) is below and we can skip
+ +-------+-------+-------+--v----+-------+ .. top cell | this cell, while on the left it's above
+ | | = topPixelWT | | yDeltaToGrid | and we need to compute for this cell.
+ |..yStart.|.....|.......|..|....|.......|... y middle | Thus, if yDeltaToGrid is more than half
+ | (left)| | | ^ = | | of top cell | the height of the cell, we start the
+ +-------+-|-----+-------+----|--+-------+ | iteration one cell below the top cell,
+ | | | | | | | | | else we start it on the top cell. This
+ |.......|.|.....|.......|....|..|.....yStart (right) | is stored in yStart.
+
+ Since we only want to go up to bottomPixelWithinThreshold, and we only iterate on the center
+ of the keys, we can stop as soon as the y value exceeds bottomPixelThreshold, so we don't
+ have to align this on the center of the key. Hence, we don't need a separate value for
+ bottomPixelWithinThreshold and call this yEnd right away.
+*/
+ final int topPixelWithinThreshold = key.mY - threshold;
+ final int yDeltaToGrid = topPixelWithinThreshold % mCellHeight;
+ final int yMiddleOfTopCell = topPixelWithinThreshold - yDeltaToGrid + halfCellHeight;
+ final int yStart = Math.max(halfCellHeight,
+ yMiddleOfTopCell + (yDeltaToGrid <= halfCellHeight ? 0 : mCellHeight));
+ final int yEnd = Math.min(fullGridHeight, key.mY + key.mHeight + threshold);
+
+ final int leftPixelWithinThreshold = key.mX - threshold;
+ final int xDeltaToGrid = leftPixelWithinThreshold % mCellWidth;
+ final int xMiddleOfLeftCell = leftPixelWithinThreshold - xDeltaToGrid + halfCellWidth;
+ final int xStart = Math.max(halfCellWidth,
+ xMiddleOfLeftCell + (xDeltaToGrid <= halfCellWidth ? 0 : mCellWidth));
+ final int xEnd = Math.min(fullGridWidth, key.mX + key.mWidth + threshold);
+
+ int baseIndexOfCurrentRow = (yStart / mCellHeight) * mGridWidth + (xStart / mCellWidth);
+ for (int centerY = yStart; centerY <= yEnd; centerY += mCellHeight) {
+ int index = baseIndexOfCurrentRow;
+ for (int centerX = xStart; centerX <= xEnd; centerX += mCellWidth) {
+ if (key.squaredDistanceToEdge(centerX, centerY) < thresholdSquared) {
+ neighborsFlatBuffer[index * keyCount + neighborCountPerCell[index]] = key;
+ ++neighborCountPerCell[index];
}
+ ++index;
}
- mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] =
- Arrays.copyOfRange(neighborKeys, 0, count);
+ baseIndexOfCurrentRow += mGridWidth;
}
}
+
+ for (int i = 0; i < gridSize; ++i) {
+ final int base = i * keyCount;
+ mGridNeighbors[i] =
+ Arrays.copyOfRange(neighborsFlatBuffer, base, base + neighborCountPerCell[i]);
+ }
}
public void fillArrayWithNearestKeyCodes(final int x, final int y, final int primaryKeyCode,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
index ab810a580..c6dd9e100 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
@@ -26,9 +26,9 @@ import android.text.TextUtils;
import android.view.View;
import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.latin.CoordinateUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
/**
* The class for single gesture preview text. The class for multiple gesture preview text will be
@@ -115,9 +115,7 @@ public class GestureFloatingPreviewText extends AbstractDrawingPreview {
@Override
public void setPreviewPosition(final PointerTracker tracker) {
- final boolean needsToUpdateLastPointer =
- tracker.isOldestTrackerInQueue() && isPreviewEnabled();
- if (!needsToUpdateLastPointer) {
+ if (!isPreviewEnabled()) {
return;
}
tracker.getLastCoordinates(mLastPointerCoords);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 70363e602..f29ade861 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -21,8 +21,8 @@ import android.util.Log;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResizableIntArray;
-import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.utils.ResizableIntArray;
+import com.android.inputmethod.latin.utils.ResourceUtils;
public class GestureStroke {
private static final String TAG = GestureStroke.class.getSimpleName();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index b31f00b62..ecc67dd44 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.keyboard.internal;
import android.content.res.TypedArray;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResizableIntArray;
+import com.android.inputmethod.latin.utils.ResizableIntArray;
public final class GestureStrokeWithPreviewPoints extends GestureStroke {
public static final int PREVIEW_CAPACITY = 256;
@@ -58,7 +58,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
}
private static double degreeToRadian(final int degree) {
- return (double)degree / 180.0d * Math.PI;
+ return degree / 180.0d * Math.PI;
}
public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) {
@@ -125,8 +125,18 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
}
+ /**
+ * Append sampled preview points.
+ *
+ * @param eventTimes the event time array of gesture trail to be drawn.
+ * @param xCoords the x-coordinates array of gesture trail to be drawn.
+ * @param yCoords the y-coordinates array of gesture trail to be drawn.
+ * @param types the point types array of gesture trail. This is valid only when
+ * {@link GestureTrail#DEBUG_SHOW_POINTS} is true.
+ */
public void appendPreviewStroke(final ResizableIntArray eventTimes,
- final ResizableIntArray xCoords, final ResizableIntArray yCoords) {
+ final ResizableIntArray xCoords, final ResizableIntArray yCoords,
+ final ResizableIntArray types) {
final int length = mPreviewEventTimes.getLength() - mLastPreviewSize;
if (length <= 0) {
return;
@@ -134,6 +144,9 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length);
xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length);
yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
+ if (GestureTrail.DEBUG_SHOW_POINTS) {
+ types.fill(GestureTrail.POINT_TYPE_SAMPLED, types.getLength(), length);
+ }
mLastPreviewSize = mPreviewEventTimes.getLength();
}
@@ -148,6 +161,8 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
* @param eventTimes the event time array of gesture trail to be drawn.
* @param xCoords the x-coordinates array of gesture trail to be drawn.
* @param yCoords the y-coordinates array of gesture trail to be drawn.
+ * @param types the point types array of gesture trail. This is valid only when
+ * {@link GestureTrail#DEBUG_SHOW_POINTS} is true.
* @return the start index of the last interpolated segment of input arrays.
*/
public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex,
@@ -189,7 +204,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
eventTimes.add(d1, (int)(dt * t) + t1);
xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
yCoords.add(d1, (int)mInterpolator.mInterpolatedY);
- if (GestureTrail.DBG_SHOW_POINTS) {
+ if (GestureTrail.DEBUG_SHOW_POINTS) {
types.add(d1, GestureTrail.POINT_TYPE_INTERPOLATED);
}
d1++;
@@ -197,7 +212,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
eventTimes.add(d1, pt[p2]);
xCoords.add(d1, px[p2]);
yCoords.add(d1, py[p2]);
- if (GestureTrail.DBG_SHOW_POINTS) {
+ if (GestureTrail.DEBUG_SHOW_POINTS) {
types.add(d1, GestureTrail.POINT_TYPE_SAMPLED);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
index 03dd1c372..aca667919 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
@@ -26,7 +26,7 @@ import android.os.SystemClock;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResizableIntArray;
+import com.android.inputmethod.latin.utils.ResizableIntArray;
/*
* @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutStartDelay
@@ -36,10 +36,11 @@ import com.android.inputmethod.latin.ResizableIntArray;
* @attr ref R.styleable#MainKeyboardView_gestureTrailWidth
*/
final class GestureTrail {
- public static final boolean DBG_SHOW_POINTS = false;
- public static final int POINT_TYPE_SAMPLED = 0;
- public static final int POINT_TYPE_INTERPOLATED = 1;
- public static final int POINT_TYPE_COMPROMISED = 2;
+ public static final boolean DEBUG_SHOW_POINTS = false;
+ public static final int POINT_TYPE_SAMPLED = 1;
+ public static final int POINT_TYPE_INTERPOLATED = 2;
+ private static final int FADEOUT_START_DELAY_FOR_DEBUG = 2000; // millisecond
+ private static final int FADEOUT_DURATION_FOR_DEBUG = 200; // millisecond
private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
@@ -48,7 +49,7 @@ final class GestureTrail {
private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
private final ResizableIntArray mPointTypes = new ResizableIntArray(
- DBG_SHOW_POINTS ? DEFAULT_CAPACITY : 0);
+ DEBUG_SHOW_POINTS ? DEFAULT_CAPACITY : 0);
private int mCurrentStrokeId = -1;
// The wall time of the zero value in {@link #mEventTimes}
private long mCurrentTimeBase;
@@ -83,10 +84,12 @@ final class GestureTrail {
R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0);
mTrailShadowEnabled = (trailShadowRatioInt > 0);
mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT;
- mFadeoutStartDelay = DBG_SHOW_POINTS ? 2000 : mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0);
- mFadeoutDuration = DBG_SHOW_POINTS ? 200 : mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0);
+ mFadeoutStartDelay = DEBUG_SHOW_POINTS ? FADEOUT_START_DELAY_FOR_DEBUG
+ : mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0);
+ mFadeoutDuration = DEBUG_SHOW_POINTS ? FADEOUT_DURATION_FOR_DEBUG
+ : mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0);
mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
mUpdateInterval = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0);
@@ -117,7 +120,7 @@ final class GestureTrail {
private void addStrokeLocked(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
final int trailSize = mEventTimes.getLength();
- stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
+ stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates, mPointTypes);
if (mEventTimes.getLength() == trailSize) {
return;
}
@@ -242,7 +245,7 @@ final class GestureTrail {
final float body1 = r1 * params.mTrailBodyRatio;
final float body2 = r2 * params.mTrailBodyRatio;
final Path path = roundedLine.makePath(p1x, p1y, body1, p2x, p2y, body2);
- if (path != null) {
+ if (!path.isEmpty()) {
roundedLine.getBounds(mRoundedLineBounds);
if (params.mTrailShadowEnabled) {
final float shadow2 = r2 * params.mTrailShadowRatio;
@@ -255,23 +258,15 @@ final class GestureTrail {
final int alpha = getAlpha(elapsedTime, params);
paint.setAlpha(alpha);
canvas.drawPath(path, paint);
- if (DBG_SHOW_POINTS) {
- if (pointTypes[i] == POINT_TYPE_INTERPOLATED) {
- paint.setColor(Color.RED);
- } else if (pointTypes[i] == POINT_TYPE_SAMPLED) {
- paint.setColor(0xFFA000FF);
- } else {
- paint.setColor(Color.GREEN);
- }
- canvas.drawCircle(p1x - 1, p1y - 1, 2, paint);
- paint.setColor(params.mTrailColor);
- }
}
}
p1x = p2x;
p1y = p2y;
r1 = r2;
}
+ if (DEBUG_SHOW_POINTS) {
+ debugDrawPoints(canvas, startIndex, trailSize, paint);
+ }
}
final int newSize = trailSize - startIndex;
@@ -281,11 +276,14 @@ final class GestureTrail {
System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
+ if (DEBUG_SHOW_POINTS) {
+ System.arraycopy(pointTypes, startIndex, pointTypes, 0, newSize);
+ }
}
mEventTimes.setLength(newSize);
mXCoordinates.setLength(newSize);
mYCoordinates.setLength(newSize);
- if (DBG_SHOW_POINTS) {
+ if (DEBUG_SHOW_POINTS) {
mPointTypes.setLength(newSize);
}
// The start index of the last segment of the stroke
@@ -295,4 +293,26 @@ final class GestureTrail {
}
return newSize > 0;
}
+
+ private void debugDrawPoints(final Canvas canvas, final int startIndex, final int endIndex,
+ final Paint paint) {
+ final int[] xCoords = mXCoordinates.getPrimitiveArray();
+ final int[] yCoords = mYCoordinates.getPrimitiveArray();
+ final int[] pointTypes = mPointTypes.getPrimitiveArray();
+ // {@link Paint} that is zero width stroke and anti alias off draws exactly 1 pixel.
+ paint.setAntiAlias(false);
+ paint.setStrokeWidth(0);
+ for (int i = startIndex; i < endIndex; i++) {
+ final int pointType = pointTypes[i];
+ if (pointType == POINT_TYPE_INTERPOLATED) {
+ paint.setColor(Color.RED);
+ } else if (pointType == POINT_TYPE_SAMPLED) {
+ paint.setColor(0xFFA000FF);
+ } else {
+ paint.setColor(Color.GREEN);
+ }
+ canvas.drawPoint(getXCoordValue(xCoords[i]), yCoords[i], paint);
+ }
+ paint.setAntiAlias(true);
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
index 1e4c43ee5..19e995548 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
@@ -30,8 +30,8 @@ import android.view.View;
import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.keyboard.internal.GestureTrail.Params;
-import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
/**
* Draw gesture trail preview graphics during gesture.
@@ -104,7 +104,13 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview {
freeOffscreenBuffer();
}
+ public void deallocateMemory() {
+ freeOffscreenBuffer();
+ }
+
private void freeOffscreenBuffer() {
+ mOffscreenCanvas.setBitmap(null);
+ mOffscreenCanvas.setMatrix(null);
if (mOffscreenBuffer != null) {
mOffscreenBuffer.recycle();
mOffscreenBuffer = null;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
index 0ec8153f5..b526a942a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
@@ -16,8 +16,6 @@
package com.android.inputmethod.keyboard.internal;
-import com.android.inputmethod.annotations.UsedForTesting;
-
/**
* Interpolates XY-coordinates using Cubic Hermite Curve.
*/
@@ -54,7 +52,6 @@ public final class HermiteInterpolator {
* @param minPos the minimum index of left-open interval of valid data.
* @param maxPos the maximum index of left-open interval of valid data.
*/
- @UsedForTesting
public void reset(final int[] xCoords, final int[] yCoords, final int minPos,
final int maxPos) {
mXCoords = xCoords;
@@ -79,7 +76,6 @@ public final class HermiteInterpolator {
* valid points, <code>p3</code> must be equal or greater than <code>maxPos</code> of
* {@link #reset(int[],int[],int,int)}.
*/
- @UsedForTesting
public void setInterval(final int p0, final int p1, final int p2, final int p3) {
mP1X = mXCoords[p1];
mP1Y = mYCoords[p1];
@@ -152,7 +148,6 @@ public final class HermiteInterpolator {
*
* @param t the interpolation parameter. The value must be in close interval <code>[0,1]</code>.
*/
- @UsedForTesting
public void interpolate(final float t) {
final float omt = 1.0f - t;
final float tm2 = 2.0f * t;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
index 5dcd842f7..1716fa049 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
@@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard.internal;
import android.graphics.Typeface;
-import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
public final class KeyDrawParams {
public Typeface mTypeface;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index b1813a141..22f5b3dd1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -21,10 +21,10 @@ import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
import android.text.TextUtils;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -53,7 +53,9 @@ public final class KeySpecParser {
private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
// Constants for parsing.
- private static final char LABEL_END = '|';
+ private static final char COMMA = ',';
+ private static final char BACKSLASH = '\\';
+ private static final char VERTICAL_BAR = '|';
private static final String PREFIX_TEXT = "!text/";
static final String PREFIX_ICON = "!icon/";
private static final String PREFIX_CODE = "!code/";
@@ -64,6 +66,59 @@ public final class KeySpecParser {
// Intentional empty constructor for utility class.
}
+ /**
+ * Split the text containing multiple key specifications separated by commas into an array of
+ * key specifications.
+ * A key specification can contain a character escaped by the backslash character, including a
+ * comma character.
+ * Note that an empty key specification will be eliminated from the result array.
+ *
+ * @param text the text containing multiple key specifications.
+ * @return an array of key specification text. Null if the specified <code>text</code> is empty
+ * or has no key specifications.
+ */
+ public static String[] splitKeySpecs(final String text) {
+ final int size = text.length();
+ if (size == 0) {
+ return null;
+ }
+ // Optimization for one-letter key specification.
+ if (size == 1) {
+ return text.charAt(0) == COMMA ? null : new String[] { text };
+ }
+
+ ArrayList<String> list = null;
+ int start = 0;
+ // The characters in question in this loop are COMMA and BACKSLASH. These characters never
+ // match any high or low surrogate character. So it is OK to iterate through with char
+ // index.
+ for (int pos = 0; pos < size; pos++) {
+ final char c = text.charAt(pos);
+ if (c == COMMA) {
+ // Skip empty entry.
+ if (pos - start > 0) {
+ if (list == null) {
+ list = CollectionUtils.newArrayList();
+ }
+ list.add(text.substring(start, pos));
+ }
+ // Skip comma
+ start = pos + 1;
+ } else if (c == BACKSLASH) {
+ // Skip escape character and escaped character.
+ pos++;
+ }
+ }
+ final String remain = (size - start > 0) ? text.substring(start) : null;
+ if (list == null) {
+ return remain != null ? new String[] { remain } : null;
+ }
+ if (remain != null) {
+ list.add(remain);
+ }
+ return list.toArray(new String[list.size()]);
+ }
+
private static boolean hasIcon(final String moreKeySpec) {
return moreKeySpec.startsWith(PREFIX_ICON);
}
@@ -78,14 +133,14 @@ public final class KeySpecParser {
}
private static String parseEscape(final String text) {
- if (text.indexOf(Constants.CSV_ESCAPE) < 0) {
+ if (text.indexOf(BACKSLASH) < 0) {
return text;
}
final int length = text.length();
final StringBuilder sb = new StringBuilder();
for (int pos = 0; pos < length; pos++) {
final char c = text.charAt(pos);
- if (c == Constants.CSV_ESCAPE && pos + 1 < length) {
+ if (c == BACKSLASH && pos + 1 < length) {
// Skip escape char
pos++;
sb.append(text.charAt(pos));
@@ -97,20 +152,20 @@ public final class KeySpecParser {
}
private static int indexOfLabelEnd(final String moreKeySpec, final int start) {
- if (moreKeySpec.indexOf(Constants.CSV_ESCAPE, start) < 0) {
- final int end = moreKeySpec.indexOf(LABEL_END, start);
+ if (moreKeySpec.indexOf(BACKSLASH, start) < 0) {
+ final int end = moreKeySpec.indexOf(VERTICAL_BAR, start);
if (end == 0) {
- throw new KeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec);
+ throw new KeySpecParserError(VERTICAL_BAR + " at " + start + ": " + moreKeySpec);
}
return end;
}
final int length = moreKeySpec.length();
for (int pos = start; pos < length; pos++) {
final char c = moreKeySpec.charAt(pos);
- if (c == Constants.CSV_ESCAPE && pos + 1 < length) {
+ if (c == BACKSLASH && pos + 1 < length) {
// Skip escape char
pos++;
- } else if (c == LABEL_END) {
+ } else if (c == VERTICAL_BAR) {
return pos;
}
}
@@ -136,9 +191,9 @@ public final class KeySpecParser {
return null;
}
if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
- throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
+ throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec);
}
- return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1));
+ return parseEscape(moreKeySpec.substring(end + /* VERTICAL_BAR */1));
}
static String getOutputText(final String moreKeySpec) {
@@ -169,7 +224,7 @@ public final class KeySpecParser {
if (hasCode(moreKeySpec)) {
final int end = indexOfLabelEnd(moreKeySpec, 0);
if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
- throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
+ throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec);
}
return parseCode(moreKeySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED);
}
@@ -204,7 +259,7 @@ public final class KeySpecParser {
public static int getIconId(final String moreKeySpec) {
if (moreKeySpec != null && hasIcon(moreKeySpec)) {
- final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length());
+ final int end = moreKeySpec.indexOf(VERTICAL_BAR, PREFIX_ICON.length());
final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length())
: moreKeySpec.substring(PREFIX_ICON.length(), end);
return KeyboardIconsSet.getIconId(name);
@@ -351,7 +406,7 @@ public final class KeySpecParser {
final String name = text.substring(pos + prefixLen, end);
sb.append(textsSet.getText(name));
pos = end - 1;
- } else if (c == Constants.CSV_ESCAPE) {
+ } else if (c == BACKSLASH) {
if (sb != null) {
// Append both escape character and escaped character.
sb.append(text.substring(pos, Math.min(pos + 2, size)));
@@ -366,7 +421,6 @@ public final class KeySpecParser {
text = sb.toString();
}
} while (sb != null);
-
return text;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
index 5db3ebbd1..f65056948 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
@@ -18,8 +18,6 @@ package com.android.inputmethod.keyboard.internal;
import android.content.res.TypedArray;
-import com.android.inputmethod.latin.StringUtils;
-
public abstract class KeyStyle {
private final KeyboardTextsSet mTextsSet;
@@ -42,7 +40,7 @@ public abstract class KeyStyle {
protected String[] parseStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) {
final String text = KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
- return StringUtils.parseCsvString(text);
+ return KeySpecParser.splitKeySpecs(text);
}
return null;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index a048ad09f..6aab3e7b3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -20,9 +20,9 @@ import android.content.res.TypedArray;
import android.util.Log;
import android.util.SparseArray;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.XmlParseUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.XmlParseUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
index 6ddd2a6fb..7a2622cbb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
@@ -21,7 +21,7 @@ import android.graphics.Typeface;
import android.util.SparseIntArray;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
public final class KeyVisualAttributes {
public final Typeface mTypeface;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index be178f516..b34d7c45f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -29,12 +29,12 @@ import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
-import com.android.inputmethod.latin.StringUtils;
-import com.android.inputmethod.latin.SubtypeLocale;
-import com.android.inputmethod.latin.XmlParseUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+import com.android.inputmethod.latin.utils.RunInLocale;
+import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import com.android.inputmethod.latin.utils.XmlParseUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -279,13 +279,13 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
params.mTextsSet.setLanguage(language);
final RunInLocale<Void> job = new RunInLocale<Void>() {
@Override
- protected Void job(Resources res) {
+ protected Void job(final Resources res) {
params.mTextsSet.loadStringResources(mContext);
return null;
}
};
// Null means the current system locale.
- final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype)
+ final Locale locale = SubtypeLocaleUtils.isNoLanguage(params.mId.mSubtype)
? null : params.mId.mLocale;
job.runInLocale(mResources, locale);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index 3e25c3b86..d65aae2dc 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -16,8 +16,8 @@
package com.android.inputmethod.keyboard.internal;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.HashMap;
@@ -43,6 +43,7 @@ public final class KeyboardCodesSet {
"key_enter",
"key_space",
"key_shift",
+ "key_capslock",
"key_switch_alpha_symbol",
"key_output_text",
"key_delete",
@@ -79,6 +80,7 @@ public final class KeyboardCodesSet {
Constants.CODE_ENTER,
Constants.CODE_SPACE,
Constants.CODE_SHIFT,
+ Constants.CODE_CAPSLOCK,
Constants.CODE_SWITCH_ALPHA_SYMBOL,
Constants.CODE_OUTPUT_TEXT,
Constants.CODE_DELETE,
@@ -116,6 +118,7 @@ public final class KeyboardCodesSet {
DEFAULT[12],
DEFAULT[13],
DEFAULT[14],
+ DEFAULT[15],
CODE_RIGHT_PARENTHESIS,
CODE_LEFT_PARENTHESIS,
CODE_GREATER_THAN_SIGN,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 4ac2549c7..4e3f7618b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -22,8 +22,8 @@ import android.graphics.drawable.Drawable;
import android.util.Log;
import android.util.SparseIntArray;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.HashMap;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
index 15eb690e1..a57b83ac0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -20,8 +20,8 @@ import android.util.SparseIntArray;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.TreeSet;
@@ -84,11 +84,16 @@ public class KeyboardParams {
public void onAddKey(final Key newKey) {
final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
- final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0;
- if (!zeroWidthSpacer) {
- mKeys.add(key);
- updateHistogram(key);
+ final boolean isSpacer = key.isSpacer();
+ if (isSpacer && key.mWidth == 0) {
+ // Ignore zero width {@link Spacer}.
+ return;
}
+ mKeys.add(key);
+ if (isSpacer) {
+ return;
+ }
+ updateHistogram(key);
if (key.mCode == Constants.CODE_SHIFT) {
mShiftKeys.add(key);
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
index 855f65507..5fe84a704 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
@@ -23,7 +23,7 @@ import android.util.Xml;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
import org.xmlpull.v1.XmlPullParser;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 6af1bd75f..164910dd4 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -20,7 +20,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.RecapitalizeStatus;
+import com.android.inputmethod.latin.utils.RecapitalizeStatus;
/**
* Keyboard state machine.
@@ -29,8 +29,8 @@ import com.android.inputmethod.latin.RecapitalizeStatus;
*
* The input events are {@link #onLoadKeyboard()}, {@link #onSaveKeyboardState()},
* {@link #onPressKey(int,boolean,int)}, {@link #onReleaseKey(int,boolean)},
- * {@link #onCodeInput(int,int)}, {@link #onFinishSlidingInput()}, {@link #onCancelInput()},
- * {@link #onUpdateShiftState(int,int)}, {@link #onLongPressTimeout(int)}.
+ * {@link #onCodeInput(int,int)}, {@link #onFinishSlidingInput()},
+ * {@link #onUpdateShiftState(int,int)}, {@link #onResetKeyboardStateToAlphabet()}.
*
* The actions are {@link SwitchActions}'s methods.
*/
@@ -53,12 +53,9 @@ public final class KeyboardState {
*/
public void requestUpdatingShiftState();
- public void startDoubleTapTimer();
- public boolean isInDoubleTapTimeout();
- public void cancelDoubleTapTimer();
- public void startLongPressTimer(int code);
- public void cancelLongPressTimer();
- public void hapticAndAudioFeedback(int code);
+ public void startDoubleTapShiftKeyTimer();
+ public boolean isInDoubleTapShiftKeyTimeout();
+ public void cancelDoubleTapShiftKeyTimer();
}
private final SwitchActions mSwitchActions;
@@ -84,9 +81,6 @@ public final class KeyboardState {
private boolean mPrevSymbolsKeyboardWasShifted;
private int mRecapitalizeMode;
- // For handling long press.
- private boolean mLongPressShiftLockFired;
-
// For handling double tap.
private boolean mIsInAlphabetUnshiftedFromShifted;
private boolean mIsInDoubleTapShiftKey;
@@ -321,14 +315,18 @@ public final class KeyboardState {
Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code)
+ " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this);
}
+ if (code != Constants.CODE_SHIFT) {
+ // Because the double tap shift key timer is to detect two consecutive shift key press,
+ // it should be canceled when a non-shift key is pressed.
+ mSwitchActions.cancelDoubleTapShiftKeyTimer();
+ }
if (code == Constants.CODE_SHIFT) {
onPressShift();
+ } else if (code == Constants.CODE_CAPSLOCK) {
+ // Nothing to do here. See {@link #onReleaseKey(int,boolean)}.
} else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
onPressSymbol();
} else {
- mSwitchActions.cancelDoubleTapTimer();
- mSwitchActions.cancelLongPressTimer();
- mLongPressShiftLockFired = false;
mShiftKeyState.onOtherKeyPressed();
mSymbolKeyState.onOtherKeyPressed();
// It is required to reset the auto caps state when all of the following conditions
@@ -356,6 +354,8 @@ public final class KeyboardState {
}
if (code == Constants.CODE_SHIFT) {
onReleaseShift(withSliding);
+ } else if (code == Constants.CODE_CAPSLOCK) {
+ setShiftLocked(!mAlphabetShiftState.isShiftLocked());
} else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
onReleaseSymbol(withSliding);
}
@@ -381,16 +381,6 @@ public final class KeyboardState {
mSymbolKeyState.onRelease();
}
- public void onLongPressTimeout(final int code) {
- if (DEBUG_EVENT) {
- Log.d(TAG, "onLongPressTimeout: code=" + Constants.printableCode(code) + " " + this);
- }
- if (mIsAlphabetMode && code == Constants.CODE_SHIFT) {
- mLongPressShiftLockFired = true;
- mSwitchActions.hapticAndAudioFeedback(code);
- }
- }
-
public void onUpdateShiftState(final int autoCaps, final int recapitalizeMode) {
if (DEBUG_EVENT) {
Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + ", recapitalizeMode="
@@ -447,15 +437,16 @@ public final class KeyboardState {
}
private void onPressShift() {
- mLongPressShiftLockFired = false;
// If we are recapitalizing, we don't do any of the normal processing, including
// importantly the double tap timer.
- if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) return;
+ if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
+ return;
+ }
if (mIsAlphabetMode) {
- mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapTimeout();
+ mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapShiftKeyTimeout();
if (!mIsInDoubleTapShiftKey) {
// This is first tap.
- mSwitchActions.startDoubleTapTimer();
+ mSwitchActions.startDoubleTapShiftKeyTimer();
}
if (mIsInDoubleTapShiftKey) {
if (mAlphabetShiftState.isManualShifted() || mIsInAlphabetUnshiftedFromShifted) {
@@ -486,7 +477,6 @@ public final class KeyboardState {
setShifted(MANUAL_SHIFT);
mShiftKeyState.onPress();
}
- mSwitchActions.startLongPressTimer(Constants.CODE_SHIFT);
}
} else {
// In symbol mode, just toggle symbol and symbol more keyboard.
@@ -508,8 +498,6 @@ public final class KeyboardState {
// Double tap shift key has been handled in {@link #onPressShift}, so that just
// ignore this release shift key here.
mIsInDoubleTapShiftKey = false;
- } else if (mLongPressShiftLockFired) {
- setShiftLocked(!mAlphabetShiftState.isShiftLocked());
} else if (mShiftKeyState.isChording()) {
if (mAlphabetShiftState.isShiftLockShifted()) {
// After chording input while shift locked state.
@@ -576,11 +564,6 @@ public final class KeyboardState {
}
}
- public boolean isInMomentarySwitchState() {
- return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
- || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
- }
-
private static boolean isSpaceCharacter(final int c) {
return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 711cad67d..7bb7442f3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -20,7 +20,7 @@ import android.content.Context;
import android.content.res.Resources;
import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.HashMap;
@@ -133,122 +133,125 @@ public final class KeyboardTextsSet {
/* 28 */ "keylabel_for_east_slavic_row2_11",
/* 29 */ "keylabel_for_east_slavic_row3_5",
/* 30 */ "more_keys_for_cyrillic_u",
- /* 31 */ "more_keys_for_cyrillic_en",
- /* 32 */ "more_keys_for_cyrillic_ghe",
- /* 33 */ "more_keys_for_east_slavic_row2_1",
- /* 34 */ "more_keys_for_cyrillic_o",
- /* 35 */ "more_keys_for_cyrillic_soft_sign",
- /* 36 */ "keylabel_for_south_slavic_row1_6",
- /* 37 */ "keylabel_for_south_slavic_row2_11",
- /* 38 */ "keylabel_for_south_slavic_row3_1",
- /* 39 */ "keylabel_for_south_slavic_row3_8",
- /* 40 */ "more_keys_for_cyrillic_ie",
- /* 41 */ "more_keys_for_cyrillic_i",
- /* 42 */ "label_to_alpha_key",
- /* 43 */ "single_quotes",
- /* 44 */ "double_quotes",
- /* 45 */ "single_angle_quotes",
- /* 46 */ "double_angle_quotes",
- /* 47 */ "more_keys_for_currency_dollar",
- /* 48 */ "keylabel_for_currency_generic",
- /* 49 */ "more_keys_for_currency_generic",
- /* 50 */ "more_keys_for_punctuation",
- /* 51 */ "more_keys_for_star",
- /* 52 */ "more_keys_for_bullet",
- /* 53 */ "more_keys_for_plus",
- /* 54 */ "more_keys_for_left_parenthesis",
- /* 55 */ "more_keys_for_right_parenthesis",
- /* 56 */ "more_keys_for_less_than",
- /* 57 */ "more_keys_for_greater_than",
- /* 58 */ "more_keys_for_arabic_diacritics",
- /* 59 */ "keyhintlabel_for_arabic_diacritics",
- /* 60 */ "keylabel_for_symbols_1",
- /* 61 */ "keylabel_for_symbols_2",
- /* 62 */ "keylabel_for_symbols_3",
- /* 63 */ "keylabel_for_symbols_4",
- /* 64 */ "keylabel_for_symbols_5",
- /* 65 */ "keylabel_for_symbols_6",
- /* 66 */ "keylabel_for_symbols_7",
- /* 67 */ "keylabel_for_symbols_8",
- /* 68 */ "keylabel_for_symbols_9",
- /* 69 */ "keylabel_for_symbols_0",
- /* 70 */ "label_to_symbol_key",
- /* 71 */ "label_to_symbol_with_microphone_key",
- /* 72 */ "additional_more_keys_for_symbols_1",
- /* 73 */ "additional_more_keys_for_symbols_2",
- /* 74 */ "additional_more_keys_for_symbols_3",
- /* 75 */ "additional_more_keys_for_symbols_4",
- /* 76 */ "additional_more_keys_for_symbols_5",
- /* 77 */ "additional_more_keys_for_symbols_6",
- /* 78 */ "additional_more_keys_for_symbols_7",
- /* 79 */ "additional_more_keys_for_symbols_8",
- /* 80 */ "additional_more_keys_for_symbols_9",
- /* 81 */ "additional_more_keys_for_symbols_0",
- /* 82 */ "more_keys_for_symbols_1",
- /* 83 */ "more_keys_for_symbols_2",
- /* 84 */ "more_keys_for_symbols_3",
- /* 85 */ "more_keys_for_symbols_4",
- /* 86 */ "more_keys_for_symbols_5",
- /* 87 */ "more_keys_for_symbols_6",
- /* 88 */ "more_keys_for_symbols_7",
- /* 89 */ "more_keys_for_symbols_8",
- /* 90 */ "more_keys_for_symbols_9",
- /* 91 */ "more_keys_for_symbols_0",
- /* 92 */ "keylabel_for_comma",
- /* 93 */ "more_keys_for_comma",
- /* 94 */ "keylabel_for_symbols_question",
- /* 95 */ "keylabel_for_symbols_semicolon",
- /* 96 */ "keylabel_for_symbols_percent",
- /* 97 */ "more_keys_for_symbols_exclamation",
- /* 98 */ "more_keys_for_symbols_question",
- /* 99 */ "more_keys_for_symbols_semicolon",
- /* 100 */ "more_keys_for_symbols_percent",
- /* 101 */ "keylabel_for_tablet_comma",
- /* 102 */ "keyhintlabel_for_tablet_comma",
- /* 103 */ "more_keys_for_tablet_comma",
- /* 104 */ "keyhintlabel_for_tablet_period",
- /* 105 */ "more_keys_for_tablet_period",
- /* 106 */ "keylabel_for_apostrophe",
- /* 107 */ "keyhintlabel_for_apostrophe",
- /* 108 */ "more_keys_for_apostrophe",
- /* 109 */ "more_keys_for_q",
- /* 110 */ "more_keys_for_x",
- /* 111 */ "keylabel_for_q",
- /* 112 */ "keylabel_for_w",
- /* 113 */ "keylabel_for_y",
- /* 114 */ "keylabel_for_x",
- /* 115 */ "keylabel_for_spanish_row2_10",
- /* 116 */ "more_keys_for_am_pm",
- /* 117 */ "settings_as_more_key",
- /* 118 */ "shortcut_as_more_key",
- /* 119 */ "action_next_as_more_key",
- /* 120 */ "action_previous_as_more_key",
- /* 121 */ "label_to_more_symbol_key",
- /* 122 */ "label_to_more_symbol_for_tablet_key",
- /* 123 */ "label_tab_key",
- /* 124 */ "label_to_phone_numeric_key",
- /* 125 */ "label_to_phone_symbols_key",
- /* 126 */ "label_time_am",
- /* 127 */ "label_time_pm",
- /* 128 */ "label_to_symbol_key_pcqwerty",
- /* 129 */ "keylabel_for_popular_domain",
- /* 130 */ "more_keys_for_popular_domain",
- /* 131 */ "more_keys_for_smiley",
- /* 132 */ "single_laqm_raqm",
- /* 133 */ "single_laqm_raqm_rtl",
- /* 134 */ "single_raqm_laqm",
- /* 135 */ "double_laqm_raqm",
- /* 136 */ "double_laqm_raqm_rtl",
- /* 137 */ "double_raqm_laqm",
- /* 138 */ "single_lqm_rqm",
- /* 139 */ "single_9qm_lqm",
- /* 140 */ "single_9qm_rqm",
- /* 141 */ "double_lqm_rqm",
- /* 142 */ "double_9qm_lqm",
- /* 143 */ "double_9qm_rqm",
- /* 144 */ "more_keys_for_single_quote",
- /* 145 */ "more_keys_for_double_quote",
- /* 146 */ "more_keys_for_tablet_double_quote",
+ /* 31 */ "more_keys_for_cyrillic_ka",
+ /* 32 */ "more_keys_for_cyrillic_en",
+ /* 33 */ "more_keys_for_cyrillic_ghe",
+ /* 34 */ "more_keys_for_east_slavic_row2_1",
+ /* 35 */ "more_keys_for_cyrillic_a",
+ /* 36 */ "more_keys_for_cyrillic_o",
+ /* 37 */ "more_keys_for_cyrillic_soft_sign",
+ /* 38 */ "more_keys_for_east_slavic_row2_11",
+ /* 39 */ "keylabel_for_south_slavic_row1_6",
+ /* 40 */ "keylabel_for_south_slavic_row2_11",
+ /* 41 */ "keylabel_for_south_slavic_row3_1",
+ /* 42 */ "keylabel_for_south_slavic_row3_8",
+ /* 43 */ "more_keys_for_cyrillic_ie",
+ /* 44 */ "more_keys_for_cyrillic_i",
+ /* 45 */ "label_to_alpha_key",
+ /* 46 */ "single_quotes",
+ /* 47 */ "double_quotes",
+ /* 48 */ "single_angle_quotes",
+ /* 49 */ "double_angle_quotes",
+ /* 50 */ "more_keys_for_currency_dollar",
+ /* 51 */ "keylabel_for_currency_generic",
+ /* 52 */ "more_keys_for_currency_generic",
+ /* 53 */ "more_keys_for_punctuation",
+ /* 54 */ "more_keys_for_star",
+ /* 55 */ "more_keys_for_bullet",
+ /* 56 */ "more_keys_for_plus",
+ /* 57 */ "more_keys_for_left_parenthesis",
+ /* 58 */ "more_keys_for_right_parenthesis",
+ /* 59 */ "more_keys_for_less_than",
+ /* 60 */ "more_keys_for_greater_than",
+ /* 61 */ "more_keys_for_arabic_diacritics",
+ /* 62 */ "keyhintlabel_for_arabic_diacritics",
+ /* 63 */ "keylabel_for_symbols_1",
+ /* 64 */ "keylabel_for_symbols_2",
+ /* 65 */ "keylabel_for_symbols_3",
+ /* 66 */ "keylabel_for_symbols_4",
+ /* 67 */ "keylabel_for_symbols_5",
+ /* 68 */ "keylabel_for_symbols_6",
+ /* 69 */ "keylabel_for_symbols_7",
+ /* 70 */ "keylabel_for_symbols_8",
+ /* 71 */ "keylabel_for_symbols_9",
+ /* 72 */ "keylabel_for_symbols_0",
+ /* 73 */ "label_to_symbol_key",
+ /* 74 */ "label_to_symbol_with_microphone_key",
+ /* 75 */ "additional_more_keys_for_symbols_1",
+ /* 76 */ "additional_more_keys_for_symbols_2",
+ /* 77 */ "additional_more_keys_for_symbols_3",
+ /* 78 */ "additional_more_keys_for_symbols_4",
+ /* 79 */ "additional_more_keys_for_symbols_5",
+ /* 80 */ "additional_more_keys_for_symbols_6",
+ /* 81 */ "additional_more_keys_for_symbols_7",
+ /* 82 */ "additional_more_keys_for_symbols_8",
+ /* 83 */ "additional_more_keys_for_symbols_9",
+ /* 84 */ "additional_more_keys_for_symbols_0",
+ /* 85 */ "more_keys_for_symbols_1",
+ /* 86 */ "more_keys_for_symbols_2",
+ /* 87 */ "more_keys_for_symbols_3",
+ /* 88 */ "more_keys_for_symbols_4",
+ /* 89 */ "more_keys_for_symbols_5",
+ /* 90 */ "more_keys_for_symbols_6",
+ /* 91 */ "more_keys_for_symbols_7",
+ /* 92 */ "more_keys_for_symbols_8",
+ /* 93 */ "more_keys_for_symbols_9",
+ /* 94 */ "more_keys_for_symbols_0",
+ /* 95 */ "keylabel_for_comma",
+ /* 96 */ "more_keys_for_comma",
+ /* 97 */ "keylabel_for_symbols_question",
+ /* 98 */ "keylabel_for_symbols_semicolon",
+ /* 99 */ "keylabel_for_symbols_percent",
+ /* 100 */ "more_keys_for_symbols_exclamation",
+ /* 101 */ "more_keys_for_symbols_question",
+ /* 102 */ "more_keys_for_symbols_semicolon",
+ /* 103 */ "more_keys_for_symbols_percent",
+ /* 104 */ "keylabel_for_tablet_comma",
+ /* 105 */ "keyhintlabel_for_tablet_comma",
+ /* 106 */ "more_keys_for_tablet_comma",
+ /* 107 */ "keyhintlabel_for_tablet_period",
+ /* 108 */ "more_keys_for_tablet_period",
+ /* 109 */ "keylabel_for_apostrophe",
+ /* 110 */ "keyhintlabel_for_apostrophe",
+ /* 111 */ "more_keys_for_apostrophe",
+ /* 112 */ "more_keys_for_q",
+ /* 113 */ "more_keys_for_x",
+ /* 114 */ "keylabel_for_q",
+ /* 115 */ "keylabel_for_w",
+ /* 116 */ "keylabel_for_y",
+ /* 117 */ "keylabel_for_x",
+ /* 118 */ "keylabel_for_spanish_row2_10",
+ /* 119 */ "more_keys_for_am_pm",
+ /* 120 */ "settings_as_more_key",
+ /* 121 */ "shortcut_as_more_key",
+ /* 122 */ "action_next_as_more_key",
+ /* 123 */ "action_previous_as_more_key",
+ /* 124 */ "label_to_more_symbol_key",
+ /* 125 */ "label_to_more_symbol_for_tablet_key",
+ /* 126 */ "label_tab_key",
+ /* 127 */ "label_to_phone_numeric_key",
+ /* 128 */ "label_to_phone_symbols_key",
+ /* 129 */ "label_time_am",
+ /* 130 */ "label_time_pm",
+ /* 131 */ "label_to_symbol_key_pcqwerty",
+ /* 132 */ "keylabel_for_popular_domain",
+ /* 133 */ "more_keys_for_popular_domain",
+ /* 134 */ "more_keys_for_smiley",
+ /* 135 */ "single_laqm_raqm",
+ /* 136 */ "single_laqm_raqm_rtl",
+ /* 137 */ "single_raqm_laqm",
+ /* 138 */ "double_laqm_raqm",
+ /* 139 */ "double_laqm_raqm_rtl",
+ /* 140 */ "double_raqm_laqm",
+ /* 141 */ "single_lqm_rqm",
+ /* 142 */ "single_9qm_lqm",
+ /* 143 */ "single_9qm_rqm",
+ /* 144 */ "double_lqm_rqm",
+ /* 145 */ "double_9qm_lqm",
+ /* 146 */ "double_9qm_rqm",
+ /* 147 */ "more_keys_for_single_quote",
+ /* 148 */ "more_keys_for_double_quote",
+ /* 149 */ "more_keys_for_tablet_double_quote",
};
private static final String EMPTY = "";
@@ -259,146 +262,146 @@ public final class KeyboardTextsSet {
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
- EMPTY, EMPTY, EMPTY,
- /* ~41 */
+ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+ /* ~44 */
// Label for "switch to alphabetic" key.
- /* 42 */ "ABC",
- /* 43 */ "!text/single_lqm_rqm",
- /* 44 */ "!text/double_lqm_rqm",
- /* 45 */ "!text/single_laqm_raqm",
- /* 46 */ "!text/double_laqm_raqm",
+ /* 45 */ "ABC",
+ /* 46 */ "!text/single_lqm_rqm",
+ /* 47 */ "!text/double_lqm_rqm",
+ /* 48 */ "!text/single_laqm_raqm",
+ /* 49 */ "!text/double_laqm_raqm",
// U+00A2: "¢" CENT SIGN
// U+00A3: "£" POUND SIGN
// U+20AC: "€" EURO SIGN
// U+00A5: "¥" YEN SIGN
// U+20B1: "₱" PESO SIGN
- /* 47 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
- /* 48 */ "$",
- /* 49 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
- /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
+ /* 50 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
+ /* 51 */ "$",
+ /* 52 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
+ /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
// U+2020: "†" DAGGER
// U+2021: "‡" DOUBLE DAGGER
// U+2605: "★" BLACK STAR
- /* 51 */ "\u2020,\u2021,\u2605",
+ /* 54 */ "\u2020,\u2021,\u2605",
// U+266A: "♪" EIGHTH NOTE
// U+2665: "♥" BLACK HEART SUIT
// U+2660: "♠" BLACK SPADE SUIT
// U+2666: "♦" BLACK DIAMOND SUIT
// U+2663: "♣" BLACK CLUB SUIT
- /* 52 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
+ /* 55 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
// U+00B1: "±" PLUS-MINUS SIGN
- /* 53 */ "\u00B1",
+ /* 56 */ "\u00B1",
// The all letters need to be mirrored are found at
// http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
- /* 54 */ "!fixedColumnOrder!3,<,{,[",
- /* 55 */ "!fixedColumnOrder!3,>,},]",
+ /* 57 */ "!fixedColumnOrder!3,<,{,[",
+ /* 58 */ "!fixedColumnOrder!3,>,},]",
// U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
// U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
// U+2264: "≤" LESS-THAN OR EQUAL TO
// U+2265: "≥" GREATER-THAN EQUAL TO
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- /* 56 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
- /* 57 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
- /* 58 */ EMPTY,
- /* 59 */ EMPTY,
- /* 60 */ "1",
- /* 61 */ "2",
- /* 62 */ "3",
- /* 63 */ "4",
- /* 64 */ "5",
- /* 65 */ "6",
- /* 66 */ "7",
- /* 67 */ "8",
- /* 68 */ "9",
- /* 69 */ "0",
+ /* 59 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
+ /* 60 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
+ /* 61 */ EMPTY,
+ /* 62 */ EMPTY,
+ /* 63 */ "1",
+ /* 64 */ "2",
+ /* 65 */ "3",
+ /* 66 */ "4",
+ /* 67 */ "5",
+ /* 68 */ "6",
+ /* 69 */ "7",
+ /* 70 */ "8",
+ /* 71 */ "9",
+ /* 72 */ "0",
// Label for "switch to symbols" key.
- /* 70 */ "?123",
+ /* 73 */ "?123",
// Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
// part because it'll be appended by the code.
- /* 71 */ "123",
- /* 72~ */
+ /* 74 */ "123",
+ /* 75~ */
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
- /* ~81 */
+ /* ~84 */
// U+00B9: "¹" SUPERSCRIPT ONE
// U+00BD: "½" VULGAR FRACTION ONE HALF
// U+2153: "⅓" VULGAR FRACTION ONE THIRD
// U+00BC: "¼" VULGAR FRACTION ONE QUARTER
// U+215B: "⅛" VULGAR FRACTION ONE EIGHTH
- /* 82 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
+ /* 85 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
// U+00B2: "²" SUPERSCRIPT TWO
// U+2154: "⅔" VULGAR FRACTION TWO THIRDS
- /* 83 */ "\u00B2,\u2154",
+ /* 86 */ "\u00B2,\u2154",
// U+00B3: "³" SUPERSCRIPT THREE
// U+00BE: "¾" VULGAR FRACTION THREE QUARTERS
// U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS
- /* 84 */ "\u00B3,\u00BE,\u215C",
+ /* 87 */ "\u00B3,\u00BE,\u215C",
// U+2074: "⁴" SUPERSCRIPT FOUR
- /* 85 */ "\u2074",
+ /* 88 */ "\u2074",
// U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS
- /* 86 */ "\u215D",
- /* 87 */ EMPTY,
- // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
- /* 88 */ "\u215E",
- /* 89 */ EMPTY,
+ /* 89 */ "\u215D",
/* 90 */ EMPTY,
+ // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
+ /* 91 */ "\u215E",
+ /* 92 */ EMPTY,
+ /* 93 */ EMPTY,
// U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N
// U+2205: "∅" EMPTY SET
- /* 91 */ "\u207F,\u2205",
- /* 92 */ ",",
- /* 93 */ EMPTY,
- /* 94 */ "?",
- /* 95 */ ";",
- /* 96 */ "%",
+ /* 94 */ "\u207F,\u2205",
+ /* 95 */ ",",
+ /* 96 */ EMPTY,
+ /* 97 */ "?",
+ /* 98 */ ";",
+ /* 99 */ "%",
// U+00A1: "¡" INVERTED EXCLAMATION MARK
- /* 97 */ "\u00A1",
+ /* 100 */ "\u00A1",
// U+00BF: "¿" INVERTED QUESTION MARK
- /* 98 */ "\u00BF",
- /* 99 */ EMPTY,
+ /* 101 */ "\u00BF",
+ /* 102 */ EMPTY,
// U+2030: "‰" PER MILLE SIGN
- /* 100 */ "\u2030",
- /* 101 */ ",",
- /* 102 */ "!",
- /* 103 */ "!",
- /* 104 */ "?",
- /* 105 */ "?",
- /* 106 */ "\'",
- /* 107 */ "\"",
- /* 108 */ "\"",
- /* 109 */ EMPTY,
- /* 110 */ EMPTY,
- /* 111 */ "q",
- /* 112 */ "w",
- /* 113 */ "y",
- /* 114 */ "x",
- /* 115 */ EMPTY,
- /* 116 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
- /* 117 */ "!icon/settings_key|!code/key_settings",
- /* 118 */ "!icon/shortcut_key|!code/key_shortcut",
- /* 119 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
- /* 120 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
+ /* 103 */ "\u2030",
+ /* 104 */ ",",
+ /* 105 */ "!",
+ /* 106 */ "!",
+ /* 107 */ "?",
+ /* 108 */ "?",
+ /* 109 */ "\'",
+ /* 110 */ "\"",
+ /* 111 */ "\"",
+ /* 112 */ EMPTY,
+ /* 113 */ EMPTY,
+ /* 114 */ "q",
+ /* 115 */ "w",
+ /* 116 */ "y",
+ /* 117 */ "x",
+ /* 118 */ EMPTY,
+ /* 119 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
+ /* 120 */ "!icon/settings_key|!code/key_settings",
+ /* 121 */ "!icon/shortcut_key|!code/key_shortcut",
+ /* 122 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
+ /* 123 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
// Label for "switch to more symbol" modifier key. Must be short to fit on key!
- /* 121 */ "= \\ <",
+ /* 124 */ "= \\ <",
// Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key!
- /* 122 */ "~ \\ {",
+ /* 125 */ "~ \\ {",
// Label for "Tab" key. Must be short to fit on key!
- /* 123 */ "Tab",
+ /* 126 */ "Tab",
// Label for "switch to phone numeric" key. Must be short to fit on key!
- /* 124 */ "123",
+ /* 127 */ "123",
// Label for "switch to phone symbols" key. Must be short to fit on key!
// U+FF0A: "*" FULLWIDTH ASTERISK
// U+FF03: "#" FULLWIDTH NUMBER SIGN
- /* 125 */ "\uFF0A\uFF03",
+ /* 128 */ "\uFF0A\uFF03",
// Key label for "ante meridiem"
- /* 126 */ "AM",
+ /* 129 */ "AM",
// Key label for "post meridiem"
- /* 127 */ "PM",
+ /* 130 */ "PM",
// Label for "switch to symbols" key on PC QWERTY layout
- /* 128 */ "Sym",
- /* 129 */ ".com",
+ /* 131 */ "Sym",
+ /* 132 */ ".com",
// popular web domains for the locale - most popular, displayed on the keyboard
- /* 130 */ "!hasLabels!,.net,.org,.gov,.edu",
- /* 131 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
+ /* 133 */ "!hasLabels!,.net,.org,.gov,.edu",
+ /* 134 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
// U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
// U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
@@ -420,24 +423,24 @@ public final class KeyboardTextsSet {
// The following each quotation mark pair consist of
// <opening quotation mark>, <closing quotation mark>
// and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
- /* 132 */ "\u2039,\u203A",
- /* 133 */ "\u2039|\u203A,\u203A|\u2039",
- /* 134 */ "\u203A,\u2039",
- /* 135 */ "\u00AB,\u00BB",
- /* 136 */ "\u00AB|\u00BB,\u00BB|\u00AB",
- /* 137 */ "\u00BB,\u00AB",
+ /* 135 */ "\u2039,\u203A",
+ /* 136 */ "\u2039|\u203A,\u203A|\u2039",
+ /* 137 */ "\u203A,\u2039",
+ /* 138 */ "\u00AB,\u00BB",
+ /* 139 */ "\u00AB|\u00BB,\u00BB|\u00AB",
+ /* 140 */ "\u00BB,\u00AB",
// The following each quotation mark triplet consists of
// <another quotation mark>, <opening quotation mark>, <closing quotation mark>
// and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
- /* 138 */ "\u201A,\u2018,\u2019",
- /* 139 */ "\u2019,\u201A,\u2018",
- /* 140 */ "\u2018,\u201A,\u2019",
- /* 141 */ "\u201E,\u201C,\u201D",
- /* 142 */ "\u201D,\u201E,\u201C",
- /* 143 */ "\u201C,\u201E,\u201D",
- /* 144 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
- /* 145 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
- /* 146 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
+ /* 141 */ "\u201A,\u2018,\u2019",
+ /* 142 */ "\u2019,\u201A,\u2018",
+ /* 143 */ "\u2018,\u201A,\u2019",
+ /* 144 */ "\u201E,\u201C,\u201D",
+ /* 145 */ "\u201D,\u201E,\u201C",
+ /* 146 */ "\u201C,\u201E,\u201D",
+ /* 147 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
+ /* 148 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
+ /* 149 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
};
/* Language af: Afrikaans */
@@ -498,45 +501,45 @@ public final class KeyboardTextsSet {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~41 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~44 */
// Label for "switch to alphabetic" key.
// U+0623: "ا" ARABIC LETTER ALEF
// U+200C: ZERO WIDTH NON-JOINER
// U+0628: "ب" ARABIC LETTER BEH
// U+062C: "پ" ARABIC LETTER PEH
- /* 42 */ "\u0623\u200C\u0628\u200C\u062C",
- /* 43 */ null,
- /* 44 */ null,
- /* 45 */ "!text/single_laqm_raqm_rtl",
- /* 46 */ "!text/double_laqm_raqm_rtl",
- /* 47~ */
+ /* 45 */ "\u0623\u200C\u0628\u200C\u062C",
+ /* 46 */ null,
+ /* 47 */ null,
+ /* 48 */ "!text/single_laqm_raqm_rtl",
+ /* 49 */ "!text/double_laqm_raqm_rtl",
+ /* 50~ */
null, null, null,
- /* ~49 */
+ /* ~52 */
// U+061F: "؟" ARABIC QUESTION MARK
// U+060C: "،" ARABIC COMMA
// U+061B: "؛" ARABIC SEMICOLON
- /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
+ /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
// U+2605: "★" BLACK STAR
// U+066D: "٭" ARABIC FIVE POINTED STAR
- /* 51 */ "\u2605,\u066D",
+ /* 54 */ "\u2605,\u066D",
// U+266A: "♪" EIGHTH NOTE
- /* 52 */ "\u266A",
- /* 53 */ null,
+ /* 55 */ "\u266A",
+ /* 56 */ null,
// The all letters need to be mirrored are found at
// http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
// U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
// U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
- /* 54 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
- /* 55 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+ /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+ /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
// U+2264: "≤" LESS-THAN OR EQUAL TO
// U+2265: "≥" GREATER-THAN EQUAL TO
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
// U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
- /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+ /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+ /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
// U+0655: "ٕ" ARABIC HAMZA BELOW
// U+0654: "ٔ" ARABIC HAMZA ABOVE
// U+0652: "ْ" ARABIC SUKUN
@@ -553,70 +556,116 @@ public final class KeyboardTextsSet {
// U+0640: "ـ" ARABIC TATWEEL
// In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
// Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
- /* 58 */ "!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",
- /* 59 */ "\u0651",
+ /* 61 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
+ /* 62 */ "\u0651",
// U+0661: "١" ARABIC-INDIC DIGIT ONE
- /* 60 */ "\u0661",
+ /* 63 */ "\u0661",
// U+0662: "٢" ARABIC-INDIC DIGIT TWO
- /* 61 */ "\u0662",
+ /* 64 */ "\u0662",
// U+0663: "٣" ARABIC-INDIC DIGIT THREE
- /* 62 */ "\u0663",
+ /* 65 */ "\u0663",
// U+0664: "٤" ARABIC-INDIC DIGIT FOUR
- /* 63 */ "\u0664",
+ /* 66 */ "\u0664",
// U+0665: "٥" ARABIC-INDIC DIGIT FIVE
- /* 64 */ "\u0665",
+ /* 67 */ "\u0665",
// U+0666: "٦" ARABIC-INDIC DIGIT SIX
- /* 65 */ "\u0666",
+ /* 68 */ "\u0666",
// U+0667: "٧" ARABIC-INDIC DIGIT SEVEN
- /* 66 */ "\u0667",
+ /* 69 */ "\u0667",
// U+0668: "٨" ARABIC-INDIC DIGIT EIGHT
- /* 67 */ "\u0668",
+ /* 70 */ "\u0668",
// U+0669: "٩" ARABIC-INDIC DIGIT NINE
- /* 68 */ "\u0669",
+ /* 71 */ "\u0669",
// U+0660: "٠" ARABIC-INDIC DIGIT ZERO
- /* 69 */ "\u0660",
+ /* 72 */ "\u0660",
// Label for "switch to symbols" key.
// U+061F: "؟" ARABIC QUESTION MARK
- /* 70 */ "\u0663\u0662\u0661\u061F",
+ /* 73 */ "\u0663\u0662\u0661\u061F",
// Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
// part because it'll be appended by the code.
- /* 71 */ "\u0663\u0662\u0661",
- /* 72 */ "1",
- /* 73 */ "2",
- /* 74 */ "3",
- /* 75 */ "4",
- /* 76 */ "5",
- /* 77 */ "6",
- /* 78 */ "7",
- /* 79 */ "8",
- /* 80 */ "9",
+ /* 74 */ "\u0663\u0662\u0661",
+ /* 75 */ "1",
+ /* 76 */ "2",
+ /* 77 */ "3",
+ /* 78 */ "4",
+ /* 79 */ "5",
+ /* 80 */ "6",
+ /* 81 */ "7",
+ /* 82 */ "8",
+ /* 83 */ "9",
// U+066B: "٫" ARABIC DECIMAL SEPARATOR
// U+066C: "٬" ARABIC THOUSANDS SEPARATOR
- /* 81 */ "0,\u066B,\u066C",
- /* 82~ */
+ /* 84 */ "0,\u066B,\u066C",
+ /* 85~ */
null, null, null, null, null, null, null, null, null, null,
- /* ~91 */
+ /* ~94 */
// U+060C: "،" ARABIC COMMA
- /* 92 */ "\u060C",
- /* 93 */ "\\,",
- /* 94 */ "\u061F",
- /* 95 */ "\u061B",
+ /* 95 */ "\u060C",
+ /* 96 */ "\\,",
+ /* 97 */ "\u061F",
+ /* 98 */ "\u061B",
// U+066A: "٪" ARABIC PERCENT SIGN
- /* 96 */ "\u066A",
- /* 97 */ null,
- /* 98 */ "?",
- /* 99 */ ";",
+ /* 99 */ "\u066A",
+ /* 100 */ null,
+ /* 101 */ "?",
+ /* 102 */ ";",
// U+2030: "‰" PER MILLE SIGN
- /* 100 */ "\\%,\u2030",
- /* 101~ */
+ /* 103 */ "\\%,\u2030",
+ /* 104~ */
null, null, null, null, null,
- /* ~105 */
+ /* ~108 */
// U+060C: "،" ARABIC COMMA
// U+061B: "؛" ARABIC SEMICOLON
// U+061F: "؟" ARABIC QUESTION MARK
- /* 106 */ "\u060C",
- /* 107 */ "\u061F",
- /* 108 */ "\u061F,\u061B,!,:,-,/,\',\"",
+ /* 109 */ "\u060C",
+ /* 110 */ "\u061F",
+ /* 111 */ "\u061F,\u061B,!,:,-,/,\',\"",
+ };
+
+ /* Language az: Azerbaijani */
+ private static final String[] LANGUAGE_az = {
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ /* 0 */ "\u00E2",
+ // U+0259: "ə" LATIN SMALL LETTER SCHWA
+ /* 1 */ "\u0259",
+ // U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+ /* 2 */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+ /* 3 */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D",
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ /* 4 */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
+ // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+ // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+ // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+ // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+ /* 5 */ "\u015F,\u00DF,\u015B,\u0161",
+ /* 6 */ null,
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ /* 7 */ "\u00E7,\u0107,\u010D",
+ /* 8~ */
+ null, null, null, null, null, null, null,
+ /* ~14 */
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ /* 15 */ "\u011F",
};
/* Language be: Belarusian */
@@ -636,23 +685,23 @@ public final class KeyboardTextsSet {
// U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
/* 29 */ "\u0456",
/* 30~ */
- null, null, null, null, null,
- /* ~34 */
+ null, null, null, null, null, null, null,
+ /* ~36 */
// U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 35 */ "\u044A",
- /* 36~ */
- null, null, null, null,
- /* ~39 */
+ /* 37 */ "\u044A",
+ /* 38~ */
+ null, null, null, null, null,
+ /* ~42 */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
- /* 40 */ "\u0451",
- /* 41 */ null,
+ /* 43 */ "\u0451",
+ /* 44 */ null,
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 42 */ "\u0410\u0411\u0412",
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
+ /* 45 */ "\u0410\u0411\u0412",
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
};
/* Language bg: Bulgarian */
@@ -660,16 +709,16 @@ public final class KeyboardTextsSet {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~41 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~44 */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 42 */ "\u0410\u0411\u0412",
- /* 43 */ null,
+ /* 45 */ "\u0410\u0411\u0412",
+ /* 46 */ null,
// single_quotes of Bulgarian is default single_quotes_right_left.
- /* 44 */ "!text/double_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
};
/* Language ca: Catalan */
@@ -733,22 +782,22 @@ public final class KeyboardTextsSet {
/* 15~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
- /* ~49 */
+ null, null, null, null, null, null, null, null,
+ /* ~52 */
// U+00B7: "·" MIDDLE DOT
- /* 50 */ "!fixedColumnOrder!9,\u00B7,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
- /* 51~ */
+ /* 53 */ "!fixedColumnOrder!9,\u00B7,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
+ /* 54~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null,
- /* ~104 */
- /* 105 */ "?,\u00B7",
- /* 106~ */
+ /* ~107 */
+ /* 108 */ "?,\u00B7",
+ /* 109~ */
null, null, null, null, null, null, null, null, null,
- /* ~114 */
+ /* ~117 */
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- /* 115 */ "\u00E7",
+ /* 118 */ "\u00E7",
};
/* Language cs: Czech */
@@ -822,11 +871,12 @@ public final class KeyboardTextsSet {
/* 13~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
- /* 45 */ "!text/single_raqm_laqm",
- /* 46 */ "!text/double_raqm_laqm",
+ null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
+ /* 48 */ "!text/single_raqm_laqm",
+ /* 49 */ "!text/double_raqm_laqm",
};
/* Language da: Danish */
@@ -890,12 +940,12 @@ public final class KeyboardTextsSet {
/* 24 */ "\u00F6",
/* 25~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
- /* 45 */ "!text/single_raqm_laqm",
- /* 46 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
+ /* 48 */ "!text/single_raqm_laqm",
+ /* 49 */ "!text/double_raqm_laqm",
};
/* Language de: German */
@@ -941,12 +991,12 @@ public final class KeyboardTextsSet {
/* 7~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
- /* 45 */ "!text/single_raqm_laqm",
- /* 46 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null, null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
+ /* 48 */ "!text/single_raqm_laqm",
+ /* 49 */ "!text/double_raqm_laqm",
};
/* Language el: Greek */
@@ -954,13 +1004,13 @@ public final class KeyboardTextsSet {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~41 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~44 */
// Label for "switch to alphabetic" key.
// U+0391: "Α" GREEK CAPITAL LETTER ALPHA
// U+0392: "Β" GREEK CAPITAL LETTER BETA
// U+0393: "Γ" GREEK CAPITAL LETTER GAMMA
- /* 42 */ "\u0391\u0392\u0393",
+ /* 45 */ "\u0391\u0392\u0393",
};
/* Language en: English */
@@ -1131,20 +1181,21 @@ public final class KeyboardTextsSet {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~108 */
- /* 109 */ "q",
- /* 110 */ "x",
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null,
+ /* ~111 */
+ /* 112 */ "q",
+ /* 113 */ "x",
// U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
- /* 111 */ "\u015D",
+ /* 114 */ "\u015D",
// U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
- /* 112 */ "\u011D",
+ /* 115 */ "\u011D",
// U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
- /* 113 */ "\u016D",
+ /* 116 */ "\u016D",
// U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
- /* 114 */ "\u0109",
+ /* 117 */ "\u0109",
// U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
- /* 115 */ "\u0135",
+ /* 118 */ "\u0135",
};
/* Language es: Spanish */
@@ -1202,30 +1253,30 @@ public final class KeyboardTextsSet {
/* 8~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~49 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~52 */
// U+00A1: "¡" INVERTED EXCLAMATION MARK
// U+00BF: "¿" INVERTED QUESTION MARK
- /* 50 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)",
- /* 51~ */
+ /* 53 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)",
+ /* 54~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null,
- /* ~102 */
+ /* ~105 */
// U+00A1: "¡" INVERTED EXCLAMATION MARK
- /* 103 */ "!,\u00A1",
- /* 104 */ null,
+ /* 106 */ "!,\u00A1",
+ /* 107 */ null,
// U+00BF: "¿" INVERTED QUESTION MARK
- /* 105 */ "?,\u00BF",
- /* 106 */ "\"",
- /* 107 */ "\'",
- /* 108 */ "\'",
- /* 109~ */
+ /* 108 */ "?,\u00BF",
+ /* 109 */ "\"",
+ /* 110 */ "\'",
+ /* 111 */ "\'",
+ /* 112~ */
null, null, null, null, null, null,
- /* ~114 */
+ /* ~117 */
// U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* 115 */ "\u00F1",
+ /* 118 */ "\u00F1",
};
/* Language et: Estonian */
@@ -1328,10 +1379,10 @@ public final class KeyboardTextsSet {
/* 23 */ "\u00F5",
/* 24~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
+ null, null, null, null, null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
};
/* Language fa: Persian */
@@ -1339,45 +1390,45 @@ public final class KeyboardTextsSet {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~41 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~44 */
// Label for "switch to alphabetic" key.
// U+0627: "ا" ARABIC LETTER ALEF
// U+200C: ZERO WIDTH NON-JOINER
// U+0628: "ب" ARABIC LETTER BEH
// U+067E: "پ" ARABIC LETTER PEH
- /* 42 */ "\u0627\u200C\u0628\u200C\u067E",
- /* 43 */ null,
- /* 44 */ null,
- /* 45 */ "!text/single_laqm_raqm_rtl",
- /* 46 */ "!text/double_laqm_raqm_rtl",
- /* 47~ */
+ /* 45 */ "\u0627\u200C\u0628\u200C\u067E",
+ /* 46 */ null,
+ /* 47 */ null,
+ /* 48 */ "!text/single_laqm_raqm_rtl",
+ /* 49 */ "!text/double_laqm_raqm_rtl",
+ /* 50~ */
null, null, null,
- /* ~49 */
+ /* ~52 */
// U+061F: "؟" ARABIC QUESTION MARK
// U+060C: "،" ARABIC COMMA
// U+061B: "؛" ARABIC SEMICOLON
- /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
+ /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
// U+2605: "★" BLACK STAR
// U+066D: "٭" ARABIC FIVE POINTED STAR
- /* 51 */ "\u2605,\u066D",
+ /* 54 */ "\u2605,\u066D",
// U+266A: "♪" EIGHTH NOTE
- /* 52 */ "\u266A",
- /* 53 */ null,
+ /* 55 */ "\u266A",
+ /* 56 */ null,
// The all letters need to be mirrored are found at
// http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
// U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
// U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
- /* 54 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
- /* 55 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+ /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+ /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
// U+2264: "≤" LESS-THAN OR EQUAL TO
// U+2265: "≥" GREATER-THAN EQUAL TO
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
// U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
- /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
+ /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
+ /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
// U+0655: "ٕ" ARABIC HAMZA BELOW
// U+0652: "ْ" ARABIC SUKUN
// U+0651: "ّ" ARABIC SHADDA
@@ -1394,74 +1445,74 @@ public final class KeyboardTextsSet {
// U+0640: "ـ" ARABIC TATWEEL
// In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
// Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
- /* 58 */ "!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",
- /* 59 */ "\u064B",
+ /* 61 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
+ /* 62 */ "\u064B",
// U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE
- /* 60 */ "\u06F1",
+ /* 63 */ "\u06F1",
// U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO
- /* 61 */ "\u06F2",
+ /* 64 */ "\u06F2",
// U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE
- /* 62 */ "\u06F3",
+ /* 65 */ "\u06F3",
// U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR
- /* 63 */ "\u06F4",
+ /* 66 */ "\u06F4",
// U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE
- /* 64 */ "\u06F5",
+ /* 67 */ "\u06F5",
// U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX
- /* 65 */ "\u06F6",
+ /* 68 */ "\u06F6",
// U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN
- /* 66 */ "\u06F7",
+ /* 69 */ "\u06F7",
// U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT
- /* 67 */ "\u06F8",
+ /* 70 */ "\u06F8",
// U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE
- /* 68 */ "\u06F9",
+ /* 71 */ "\u06F9",
// U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO
- /* 69 */ "\u06F0",
+ /* 72 */ "\u06F0",
// Label for "switch to symbols" key.
// U+061F: "؟" ARABIC QUESTION MARK
- /* 70 */ "\u06F3\u06F2\u06F1\u061F",
+ /* 73 */ "\u06F3\u06F2\u06F1\u061F",
// Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
// part because it'll be appended by the code.
- /* 71 */ "\u06F3\u06F2\u06F1",
- /* 72 */ "1",
- /* 73 */ "2",
- /* 74 */ "3",
- /* 75 */ "4",
- /* 76 */ "5",
- /* 77 */ "6",
- /* 78 */ "7",
- /* 79 */ "8",
- /* 80 */ "9",
+ /* 74 */ "\u06F3\u06F2\u06F1",
+ /* 75 */ "1",
+ /* 76 */ "2",
+ /* 77 */ "3",
+ /* 78 */ "4",
+ /* 79 */ "5",
+ /* 80 */ "6",
+ /* 81 */ "7",
+ /* 82 */ "8",
+ /* 83 */ "9",
// U+066B: "٫" ARABIC DECIMAL SEPARATOR
// U+066C: "٬" ARABIC THOUSANDS SEPARATOR
- /* 81 */ "0,\u066B,\u066C",
- /* 82~ */
+ /* 84 */ "0,\u066B,\u066C",
+ /* 85~ */
null, null, null, null, null, null, null, null, null, null,
- /* ~91 */
+ /* ~94 */
// U+060C: "،" ARABIC COMMA
- /* 92 */ "\u060C",
- /* 93 */ "\\,",
- /* 94 */ "\u061F",
- /* 95 */ "\u061B",
+ /* 95 */ "\u060C",
+ /* 96 */ "\\,",
+ /* 97 */ "\u061F",
+ /* 98 */ "\u061B",
// U+066A: "٪" ARABIC PERCENT SIGN
- /* 96 */ "\u066A",
- /* 97 */ null,
- /* 98 */ "?",
- /* 99 */ ";",
+ /* 99 */ "\u066A",
+ /* 100 */ null,
+ /* 101 */ "?",
+ /* 102 */ ";",
// U+2030: "‰" PER MILLE SIGN
- /* 100 */ "\\%,\u2030",
+ /* 103 */ "\\%,\u2030",
// U+060C: "،" ARABIC COMMA
// U+061B: "؛" ARABIC SEMICOLON
// U+061F: "؟" ARABIC QUESTION MARK
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- /* 101 */ "\u060C",
- /* 102 */ "!",
- /* 103 */ "!,\\,",
- /* 104 */ "\u061F",
- /* 105 */ "\u061F,?",
- /* 106 */ "\u060C",
+ /* 104 */ "\u060C",
+ /* 105 */ "!",
+ /* 106 */ "!,\\,",
/* 107 */ "\u061F",
- /* 108 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
+ /* 108 */ "\u061F,?",
+ /* 109 */ "\u060C",
+ /* 110 */ "\u061F",
+ /* 111 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
};
/* Language fi: Finnish */
@@ -1569,56 +1620,56 @@ public final class KeyboardTextsSet {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~41 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~44 */
// Label for "switch to alphabetic" key.
// U+0915: "क" DEVANAGARI LETTER KA
// U+0916: "ख" DEVANAGARI LETTER KHA
// U+0917: "ग" DEVANAGARI LETTER GA
- /* 42 */ "\u0915\u0916\u0917",
- /* 43~ */
+ /* 45 */ "\u0915\u0916\u0917",
+ /* 46~ */
null, null, null, null, null,
- /* ~47 */
+ /* ~50 */
// U+20B9: "₹" INDIAN RUPEE SIGN
- /* 48 */ "\u20B9",
- /* 49~ */
+ /* 51 */ "\u20B9",
+ /* 52~ */
null, null, null, null, null, null, null, null, null, null, null,
- /* ~59 */
+ /* ~62 */
// U+0967: "१" DEVANAGARI DIGIT ONE
- /* 60 */ "\u0967",
+ /* 63 */ "\u0967",
// U+0968: "२" DEVANAGARI DIGIT TWO
- /* 61 */ "\u0968",
+ /* 64 */ "\u0968",
// U+0969: "३" DEVANAGARI DIGIT THREE
- /* 62 */ "\u0969",
+ /* 65 */ "\u0969",
// U+096A: "४" DEVANAGARI DIGIT FOUR
- /* 63 */ "\u096A",
+ /* 66 */ "\u096A",
// U+096B: "५" DEVANAGARI DIGIT FIVE
- /* 64 */ "\u096B",
+ /* 67 */ "\u096B",
// U+096C: "६" DEVANAGARI DIGIT SIX
- /* 65 */ "\u096C",
+ /* 68 */ "\u096C",
// U+096D: "७" DEVANAGARI DIGIT SEVEN
- /* 66 */ "\u096D",
+ /* 69 */ "\u096D",
// U+096E: "८" DEVANAGARI DIGIT EIGHT
- /* 67 */ "\u096E",
+ /* 70 */ "\u096E",
// U+096F: "९" DEVANAGARI DIGIT NINE
- /* 68 */ "\u096F",
+ /* 71 */ "\u096F",
// U+0966: "०" DEVANAGARI DIGIT ZERO
- /* 69 */ "\u0966",
+ /* 72 */ "\u0966",
// Label for "switch to symbols" key.
- /* 70 */ "?\u0967\u0968\u0969",
+ /* 73 */ "?\u0967\u0968\u0969",
// Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
// part because it'll be appended by the code.
- /* 71 */ "\u0967\u0968\u0969",
- /* 72 */ "1",
- /* 73 */ "2",
- /* 74 */ "3",
- /* 75 */ "4",
- /* 76 */ "5",
- /* 77 */ "6",
- /* 78 */ "7",
- /* 79 */ "8",
- /* 80 */ "9",
- /* 81 */ "0",
+ /* 74 */ "\u0967\u0968\u0969",
+ /* 75 */ "1",
+ /* 76 */ "2",
+ /* 77 */ "3",
+ /* 78 */ "4",
+ /* 79 */ "5",
+ /* 80 */ "6",
+ /* 81 */ "7",
+ /* 82 */ "8",
+ /* 83 */ "9",
+ /* 84 */ "0",
};
/* Language hr: Croatian */
@@ -1649,11 +1700,12 @@ public final class KeyboardTextsSet {
/* 13~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_rqm",
- /* 44 */ "!text/double_9qm_rqm",
- /* 45 */ "!text/single_raqm_laqm",
- /* 46 */ "!text/double_raqm_laqm",
+ null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_rqm",
+ /* 47 */ "!text/double_9qm_rqm",
+ /* 48 */ "!text/single_raqm_laqm",
+ /* 49 */ "!text/double_raqm_laqm",
};
/* Language hu: Hungarian */
@@ -1702,12 +1754,12 @@ public final class KeyboardTextsSet {
/* 5~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_rqm",
- /* 44 */ "!text/double_9qm_rqm",
- /* 45 */ "!text/single_raqm_laqm",
- /* 46 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null, null, null, null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_rqm",
+ /* 47 */ "!text/double_9qm_rqm",
+ /* 48 */ "!text/single_raqm_laqm",
+ /* 49 */ "!text/double_raqm_laqm",
};
/* Language is: Icelandic */
@@ -1773,10 +1825,10 @@ public final class KeyboardTextsSet {
/* 22 */ "\u00FE",
/* 23~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
+ null, null, null, null, null, null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
};
/* Language it: Italian */
@@ -1829,13 +1881,13 @@ public final class KeyboardTextsSet {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~41 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~44 */
// Label for "switch to alphabetic" key.
// U+05D0: "א" HEBREW LETTER ALEF
// U+05D1: "ב" HEBREW LETTER BET
// U+05D2: "ג" HEBREW LETTER GIMEL
- /* 42 */ "\u05D0\u05D1\u05D2",
+ /* 45 */ "\u05D0\u05D1\u05D2",
// The following characters don't need BIDI mirroring.
// U+2018: "‘" LEFT SINGLE QUOTATION MARK
// U+2019: "’" RIGHT SINGLE QUOTATION MARK
@@ -1843,31 +1895,31 @@ public final class KeyboardTextsSet {
// U+201C: "“" LEFT DOUBLE QUOTATION MARK
// U+201D: "”" RIGHT DOUBLE QUOTATION MARK
// U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
- /* 43 */ "\u2018,\u2019,\u201A",
- /* 44 */ "\u201C,\u201D,\u201E",
- /* 45 */ "!text/single_laqm_raqm_rtl",
- /* 46 */ "!text/double_laqm_raqm_rtl",
- /* 47~ */
+ /* 46 */ "\u2018,\u2019,\u201A",
+ /* 47 */ "\u201C,\u201D,\u201E",
+ /* 48 */ "!text/single_laqm_raqm_rtl",
+ /* 49 */ "!text/double_laqm_raqm_rtl",
+ /* 50~ */
null, null, null, null,
- /* ~50 */
+ /* ~53 */
// U+2605: "★" BLACK STAR
- /* 51 */ "\u2605",
- /* 52 */ null,
+ /* 54 */ "\u2605",
+ /* 55 */ null,
// U+00B1: "±" PLUS-MINUS SIGN
// U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
- /* 53 */ "\u00B1,\uFB29",
+ /* 56 */ "\u00B1,\uFB29",
// The all letters need to be mirrored are found at
// http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
- /* 54 */ "!fixedColumnOrder!3,<|>,{|},[|]",
- /* 55 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
+ /* 57 */ "!fixedColumnOrder!3,<|>,{|},[|]",
+ /* 58 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
// U+2264: "≤" LESS-THAN OR EQUAL TO
// U+2265: "≥" GREATER-THAN EQUAL TO
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
// U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
- /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+ /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+ /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
};
/* Language ka: Georgian */
@@ -1875,15 +1927,63 @@ public final class KeyboardTextsSet {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~41 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~44 */
// Label for "switch to alphabetic" key.
// U+10D0: "ა" GEORGIAN LETTER AN
// U+10D1: "ბ" GEORGIAN LETTER BAN
// U+10D2: "გ" GEORGIAN LETTER GAN
- /* 42 */ "\u10D0\u10D1\u10D2",
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
+ /* 45 */ "\u10D0\u10D1\u10D2",
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
+ };
+
+ /* Language kk: Kazakh */
+ private static final String[] LANGUAGE_kk = {
+ /* 0~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null,
+ /* ~24 */
+ // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
+ /* 25 */ "\u0449",
+ // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
+ /* 26 */ "\u044A",
+ // U+044B: "ы" CYRILLIC SMALL LETTER YERU
+ /* 27 */ "\u044B",
+ // U+044D: "э" CYRILLIC SMALL LETTER E
+ /* 28 */ "\u044D",
+ // U+0438: "и" CYRILLIC SMALL LETTER I
+ /* 29 */ "\u0438",
+ // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
+ // U+04B1: "ұ" CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE
+ /* 30 */ "\u04AF,\u04B1",
+ // U+049B: "қ" CYRILLIC SMALL LETTER KA WITH DESCENDER
+ /* 31 */ "\u049B",
+ // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER
+ /* 32 */ "\u04A3",
+ // U+0493: "ғ" CYRILLIC SMALL LETTER GHE WITH STROKE
+ /* 33 */ "\u0493",
+ // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+ /* 34 */ "\u0456",
+ // U+04D9: "ә" CYRILLIC SMALL LETTER SCHWA
+ /* 35 */ "\u04D9",
+ // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
+ /* 36 */ "\u04E9",
+ // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
+ /* 37 */ "\u044A",
+ // U+04BB: "һ" CYRILLIC SMALL LETTER SHHA
+ /* 38 */ "\u04BB",
+ /* 39~ */
+ null, null, null, null,
+ /* ~42 */
+ // U+0451: "ё" CYRILLIC SMALL LETTER IO
+ /* 43 */ "\u0451",
+ /* 44 */ null,
+ // Label for "switch to alphabetic" key.
+ // U+0410: "А" CYRILLIC CAPITAL LETTER A
+ // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
+ // U+0412: "В" CYRILLIC CAPITAL LETTER VE
+ /* 45 */ "\u0410\u0411\u0412",
};
/* Language ky: Kirghiz */
@@ -1904,25 +2004,27 @@ public final class KeyboardTextsSet {
/* 29 */ "\u0438",
// U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
/* 30 */ "\u04AF",
+ /* 31 */ null,
// U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER
- /* 31 */ "\u04A3",
- /* 32 */ null,
- /* 33 */ null,
+ /* 32 */ "\u04A3",
+ /* 33~ */
+ null, null, null,
+ /* ~35 */
// U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
- /* 34 */ "\u04E9",
+ /* 36 */ "\u04E9",
// U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 35 */ "\u044A",
- /* 36~ */
- null, null, null, null,
- /* ~39 */
+ /* 37 */ "\u044A",
+ /* 38~ */
+ null, null, null, null, null,
+ /* ~42 */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
- /* 40 */ "\u0451",
- /* 41 */ null,
+ /* 43 */ "\u0451",
+ /* 44 */ null,
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 42 */ "\u0410\u0411\u0412",
+ /* 45 */ "\u0410\u0411\u0412",
};
/* Language lt: Lithuanian */
@@ -2015,10 +2117,10 @@ public final class KeyboardTextsSet {
/* 15 */ "\u0123,\u011F",
/* 16~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
};
/* Language lv: Latvian */
@@ -2110,10 +2212,10 @@ public final class KeyboardTextsSet {
/* 15 */ "\u0123,\u011F",
/* 16~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
};
/* Language mk: Macedonian */
@@ -2121,27 +2223,27 @@ public final class KeyboardTextsSet {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
- /* ~35 */
+ null, null, null, null, null, null, null, null, null,
+ /* ~38 */
// U+0455: "ѕ" CYRILLIC SMALL LETTER DZE
- /* 36 */ "\u0455",
+ /* 39 */ "\u0455",
// U+045C: "ќ" CYRILLIC SMALL LETTER KJE
- /* 37 */ "\u045C",
+ /* 40 */ "\u045C",
// U+0437: "з" CYRILLIC SMALL LETTER ZE
- /* 38 */ "\u0437",
+ /* 41 */ "\u0437",
// U+0453: "ѓ" CYRILLIC SMALL LETTER GJE
- /* 39 */ "\u0453",
+ /* 42 */ "\u0453",
// U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE
- /* 40 */ "\u0450",
+ /* 43 */ "\u0450",
// U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
- /* 41 */ "\u045D",
+ /* 44 */ "\u045D",
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 42 */ "\u0410\u0411\u0412",
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
+ /* 45 */ "\u0410\u0411\u0412",
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
};
/* Language mn: Mongolian */
@@ -2149,18 +2251,18 @@ public final class KeyboardTextsSet {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~41 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~44 */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 42 */ "\u0410\u0411\u0412",
- /* 43~ */
+ /* 45 */ "\u0410\u0411\u0412",
+ /* 46~ */
null, null, null, null, null,
- /* ~47 */
+ /* ~50 */
// U+20AE: "₮" TUGRIK SIGN
- /* 48 */ "\u20AE",
+ /* 51 */ "\u20AE",
};
/* Language nb: Norwegian Bokmål */
@@ -2210,10 +2312,10 @@ public final class KeyboardTextsSet {
/* 24 */ "\u00E4",
/* 25~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_rqm",
- /* 44 */ "!text/double_9qm_rqm",
+ null, null, null, null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_rqm",
+ /* 47 */ "!text/double_9qm_rqm",
};
/* Language nl: Dutch */
@@ -2268,10 +2370,10 @@ public final class KeyboardTextsSet {
/* 9~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_rqm",
- /* 44 */ "!text/double_9qm_rqm",
+ null, null, null, null, null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_rqm",
+ /* 47 */ "!text/double_9qm_rqm",
};
/* Language pl: Polish */
@@ -2328,10 +2430,11 @@ public final class KeyboardTextsSet {
/* 14 */ "\u0142",
/* 15~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_rqm",
- /* 44 */ "!text/double_9qm_rqm",
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_rqm",
+ /* 47 */ "!text/double_9qm_rqm",
};
/* Language pt: Portuguese */
@@ -2434,10 +2537,10 @@ public final class KeyboardTextsSet {
/* 12~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_rqm",
- /* 44 */ "!text/double_9qm_rqm",
+ null, null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_rqm",
+ /* 47 */ "!text/double_9qm_rqm",
};
/* Language ru: Russian */
@@ -2457,23 +2560,23 @@ public final class KeyboardTextsSet {
// U+0438: "и" CYRILLIC SMALL LETTER I
/* 29 */ "\u0438",
/* 30~ */
- null, null, null, null, null,
- /* ~34 */
+ null, null, null, null, null, null, null,
+ /* ~36 */
// U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 35 */ "\u044A",
- /* 36~ */
- null, null, null, null,
- /* ~39 */
+ /* 37 */ "\u044A",
+ /* 38~ */
+ null, null, null, null, null,
+ /* ~42 */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
- /* 40 */ "\u0451",
- /* 41 */ null,
+ /* 43 */ "\u0451",
+ /* 44 */ null,
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 42 */ "\u0410\u0411\u0412",
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
+ /* 45 */ "\u0410\u0411\u0412",
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
};
/* Language sk: Slovak */
@@ -2566,12 +2669,12 @@ public final class KeyboardTextsSet {
/* 15 */ "\u0123,\u011F",
/* 16~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
- /* 45 */ "!text/single_raqm_laqm",
- /* 46 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
+ /* 48 */ "!text/single_raqm_laqm",
+ /* 49 */ "!text/double_raqm_laqm",
};
/* Language sl: Slovenian */
@@ -2595,11 +2698,12 @@ public final class KeyboardTextsSet {
/* 13~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~42 */
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
- /* 45 */ "!text/single_raqm_laqm",
- /* 46 */ "!text/double_raqm_laqm",
+ null, null, null,
+ /* ~45 */
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
+ /* 48 */ "!text/single_raqm_laqm",
+ /* 49 */ "!text/double_raqm_laqm",
};
/* Language sr: Serbian */
@@ -2607,8 +2711,8 @@ public final class KeyboardTextsSet {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
- /* ~35 */
+ null, null, null, null, null, null, null, null, null,
+ /* ~38 */
// TODO: Move these to sr-Latn once we can handle IETF language tag with script name specified.
// BEGIN: More keys definitions for Serbian (Latin)
// U+0161: "š" LATIN SMALL LETTER S WITH CARON
@@ -2628,27 +2732,27 @@ public final class KeyboardTextsSet {
// END: More keys definitions for Serbian (Latin)
// BEGIN: More keys definitions for Serbian (Cyrillic)
// U+0437: "з" CYRILLIC SMALL LETTER ZE
- /* 36 */ "\u0437",
+ /* 39 */ "\u0437",
// U+045B: "ћ" CYRILLIC SMALL LETTER TSHE
- /* 37 */ "\u045B",
+ /* 40 */ "\u045B",
// U+0455: "ѕ" CYRILLIC SMALL LETTER DZE
- /* 38 */ "\u0455",
+ /* 41 */ "\u0455",
// U+0452: "ђ" CYRILLIC SMALL LETTER DJE
- /* 39 */ "\u0452",
+ /* 42 */ "\u0452",
// U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE
- /* 40 */ "\u0450",
+ /* 43 */ "\u0450",
// U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
- /* 41 */ "\u045D",
+ /* 44 */ "\u045D",
// END: More keys definitions for Serbian (Cyrillic)
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 42 */ "\u0410\u0411\u0412",
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
- /* 45 */ "!text/single_raqm_laqm",
- /* 46 */ "!text/double_raqm_laqm",
+ /* 45 */ "\u0410\u0411\u0412",
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
+ /* 48 */ "!text/single_raqm_laqm",
+ /* 49 */ "!text/double_raqm_laqm",
};
/* Language sv: Swedish */
@@ -2693,10 +2797,10 @@ public final class KeyboardTextsSet {
/* 24 */ "\u00E6",
/* 25~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
- /* ~44 */
- /* 45 */ "!text/single_raqm_laqm",
- /* 46 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null, null, null,
+ /* ~47 */
+ /* 48 */ "!text/single_raqm_laqm",
+ /* 49 */ "!text/double_raqm_laqm",
};
/* Language sw: Swahili */
@@ -2755,18 +2859,18 @@ public final class KeyboardTextsSet {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~41 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~44 */
// Label for "switch to alphabetic" key.
// U+0E01: "ก" THAI CHARACTER KO KAI
// U+0E02: "ข" THAI CHARACTER KHO KHAI
// U+0E04: "ค" THAI CHARACTER KHO KHWAI
- /* 42 */ "\u0E01\u0E02\u0E04",
- /* 43~ */
+ /* 45 */ "\u0E01\u0E02\u0E04",
+ /* 46~ */
null, null, null, null, null,
- /* ~47 */
+ /* ~50 */
// U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
- /* 48 */ "\u0E3F",
+ /* 51 */ "\u0E3F",
};
/* Language tl: Tagalog */
@@ -2884,30 +2988,32 @@ public final class KeyboardTextsSet {
/* 28 */ "\u0454",
// U+0438: "и" CYRILLIC SMALL LETTER I
/* 29 */ "\u0438",
- /* 30 */ null,
- /* 31 */ null,
+ /* 30~ */
+ null, null, null,
+ /* ~32 */
// U+0491: "ґ" CYRILLIC SMALL LETTER GHE WITH UPTURN
- /* 32 */ "\u0491",
+ /* 33 */ "\u0491",
// U+0457: "ї" CYRILLIC SMALL LETTER YI
- /* 33 */ "\u0457",
- /* 34 */ null,
+ /* 34 */ "\u0457",
+ /* 35 */ null,
+ /* 36 */ null,
// U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
- /* 35 */ "\u044A",
- /* 36~ */
- null, null, null, null, null, null,
- /* ~41 */
+ /* 37 */ "\u044A",
+ /* 38~ */
+ null, null, null, null, null, null, null,
+ /* ~44 */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 42 */ "\u0410\u0411\u0412",
- /* 43 */ "!text/single_9qm_lqm",
- /* 44 */ "!text/double_9qm_lqm",
- /* 45~ */
+ /* 45 */ "\u0410\u0411\u0412",
+ /* 46 */ "!text/single_9qm_lqm",
+ /* 47 */ "!text/double_9qm_lqm",
+ /* 48~ */
null, null, null,
- /* ~47 */
+ /* ~50 */
// U+20B4: "₴" HRYVNIA SIGN
- /* 48 */ "\u20B4",
+ /* 51 */ "\u20B4",
};
/* Language vi: Vietnamese */
@@ -2992,10 +3098,10 @@ public final class KeyboardTextsSet {
/* 10~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
- /* ~47 */
+ null, null, null, null, null, null, null, null, null, null, null,
+ /* ~50 */
// U+20AB: "₫" DONG SIGN
- /* 48 */ "\u20AB",
+ /* 51 */ "\u20AB",
};
/* Language zu: Zulu */
@@ -3172,6 +3278,7 @@ public final class KeyboardTextsSet {
"DEFAULT", LANGUAGE_DEFAULT, /* default */
"af", LANGUAGE_af, /* Afrikaans */
"ar", LANGUAGE_ar, /* Arabic */
+ "az", LANGUAGE_az, /* Azerbaijani */
"be", LANGUAGE_be, /* Belarusian */
"bg", LANGUAGE_bg, /* Bulgarian */
"ca", LANGUAGE_ca, /* Catalan */
@@ -3193,6 +3300,7 @@ public final class KeyboardTextsSet {
"it", LANGUAGE_it, /* Italian */
"iw", LANGUAGE_iw, /* Hebrew */
"ka", LANGUAGE_ka, /* Georgian */
+ "kk", LANGUAGE_kk, /* Kazakh */
"ky", LANGUAGE_ky, /* Kirghiz */
"lt", LANGUAGE_lt, /* Lithuanian */
"lv", LANGUAGE_lv, /* Latvian */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
index db154a391..7c2e3e174 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
@@ -17,7 +17,7 @@
package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.HashMap;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java
new file mode 100644
index 000000000..c1f374964
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Utilities for matrix operations. Don't instantiate objects inside this class to prevent
+ * unexpected performance regressions.
+ */
+@UsedForTesting
+public class MatrixUtils {
+ private static final String TAG = MatrixUtils.class.getSimpleName();
+ public static class MatrixOperationFailedException extends Exception {
+ private static final long serialVersionUID = 4384485606788583829L;
+
+ public MatrixOperationFailedException(String msg) {
+ super(msg);
+ Log.d(TAG, msg);
+ }
+ }
+
+ /**
+ * A utility function to inverse matrix.
+ * Find a pivot and swap the row of squareMatrix0 and squareMatrix1
+ */
+ private static void findPivotAndSwapRow(final int row, final float[][] squareMatrix0,
+ final float[][] squareMatrix1, final int size) {
+ int ip = row;
+ float pivot = Math.abs(squareMatrix0[row][row]);
+ for (int i = row + 1; i < size; ++i) {
+ if (pivot < Math.abs(squareMatrix0[i][row])) {
+ ip = i;
+ pivot = Math.abs(squareMatrix0[i][row]);
+ }
+ }
+ if (ip != row) {
+ for (int j = 0; j < size; ++j) {
+ final float temp0 = squareMatrix0[ip][j];
+ squareMatrix0[ip][j] = squareMatrix0[row][j];
+ squareMatrix0[row][j] = temp0;
+ final float temp1 = squareMatrix1[ip][j];
+ squareMatrix1[ip][j] = squareMatrix1[row][j];
+ squareMatrix1[row][j] = temp1;
+ }
+ }
+ }
+
+ /**
+ * A utility function to inverse matrix. This function calculates answer for each row by
+ * sweeping method of Gauss Jordan elimination
+ */
+ private static void sweep(final int row, final float[][] squareMatrix0,
+ final float[][] squareMatrix1, final int size) throws MatrixOperationFailedException {
+ final float pivot = squareMatrix0[row][row];
+ if (pivot == 0) {
+ throw new MatrixOperationFailedException("Inverse failed. Invalid pivot");
+ }
+ for (int j = 0; j < size; ++j) {
+ squareMatrix0[row][j] /= pivot;
+ squareMatrix1[row][j] /= pivot;
+ }
+ for (int i = 0; i < size; i++) {
+ final float sweepTargetValue = squareMatrix0[i][row];
+ if (i != row) {
+ for (int j = row; j < size; ++j) {
+ squareMatrix0[i][j] -= sweepTargetValue * squareMatrix0[row][j];
+ }
+ for (int j = 0; j < size; ++j) {
+ squareMatrix1[i][j] -= sweepTargetValue * squareMatrix1[row][j];
+ }
+ }
+ }
+ }
+
+ /**
+ * A function to inverse matrix.
+ * The inverse matrix of squareMatrix will be output to inverseMatrix. Please notice that
+ * the value of squareMatrix is modified in this function and can't be resuable.
+ */
+ @UsedForTesting
+ public static void inverse(final float[][] squareMatrix,
+ final float[][] inverseMatrix) throws MatrixOperationFailedException {
+ final int size = squareMatrix.length;
+ if (squareMatrix[0].length != size || inverseMatrix.length != size
+ || inverseMatrix[0].length != size) {
+ throw new MatrixOperationFailedException(
+ "--- invalid length. column should be 2 times larger than row.");
+ }
+ for (int i = 0; i < size; ++i) {
+ Arrays.fill(inverseMatrix[i], 0.0f);
+ inverseMatrix[i][i] = 1.0f;
+ }
+ for (int i = 0; i < size; ++i) {
+ findPivotAndSwapRow(i, squareMatrix, inverseMatrix, size);
+ sweep(i, squareMatrix, inverseMatrix, size);
+ }
+ }
+
+ /**
+ * A matrix operation to multiply m0 and m1.
+ */
+ @UsedForTesting
+ public static void multiply(final float[][] m0, final float[][] m1,
+ final float[][] retval) throws MatrixOperationFailedException {
+ if (m0[0].length != m1.length) {
+ throw new MatrixOperationFailedException(
+ "--- invalid length for multiply " + m0[0].length + ", " + m1.length);
+ }
+ final int m0h = m0.length;
+ final int m0w = m0[0].length;
+ final int m1w = m1[0].length;
+ if (retval.length != m0h || retval[0].length != m1w) {
+ throw new MatrixOperationFailedException(
+ "--- invalid length of retval " + retval.length + ", " + retval[0].length);
+ }
+
+ for (int i = 0; i < m0h; i++) {
+ Arrays.fill(retval[i], 0);
+ for (int j = 0; j < m1w; j++) {
+ for (int k = 0; k < m0w; k++) {
+ retval[i][j] += m0[i][k] * m1[k][j];
+ }
+ }
+ }
+ }
+
+ /**
+ * A utility function to dump the specified matrix in a readable way
+ */
+ @UsedForTesting
+ public static void dump(final String title, final float[][] a) {
+ final int column = a[0].length;
+ final int row = a.length;
+ Log.d(TAG, "Dump matrix: " + title);
+ Log.d(TAG, "/*---------------------");
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < row; ++i) {
+ sb.setLength(0);
+ for (int j = 0; j < column; ++j) {
+ sb.append(String.format("%4f", a[i][j])).append(' ');
+ }
+ Log.d(TAG, sb.toString());
+ }
+ Log.d(TAG, "---------------------*/");
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
index b38d79f7b..110936f8f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.keyboard.internal;
import android.text.TextUtils;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
new file mode 100644
index 000000000..a0935b985
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
@@ -0,0 +1,113 @@
+/*
+ * 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.util.Log;
+import android.view.MotionEvent;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
+
+public final class NonDistinctMultitouchHelper {
+ private static final String TAG = NonDistinctMultitouchHelper.class.getSimpleName();
+
+ private int mOldPointerCount = 1;
+ private Key mOldKey;
+ private int[] mLastCoords = CoordinateUtils.newInstance();
+
+ public void processMotionEvent(final MotionEvent me, final KeyEventHandler keyEventHandler) {
+ final int pointerCount = me.getPointerCount();
+ final int oldPointerCount = mOldPointerCount;
+ mOldPointerCount = pointerCount;
+ // Ignore continuous multi-touch events because we can't trust the coordinates
+ // in multi-touch events.
+ if (pointerCount > 1 && oldPointerCount > 1) {
+ return;
+ }
+
+ // Use only main (id=0) pointer tracker.
+ final PointerTracker mainTracker = PointerTracker.getPointerTracker(0, keyEventHandler);
+ final int action = me.getActionMasked();
+ final int index = me.getActionIndex();
+ final long eventTime = me.getEventTime();
+ final long downTime = me.getDownTime();
+
+ // In single-touch.
+ if (oldPointerCount == 1 && pointerCount == 1) {
+ if (me.getPointerId(index) == mainTracker.mPointerId) {
+ mainTracker.processMotionEvent(me, keyEventHandler);
+ return;
+ }
+ // Inject a copied event.
+ injectMotionEvent(action, me.getX(index), me.getY(index), downTime, eventTime,
+ mainTracker, keyEventHandler);
+ return;
+ }
+
+ // Single-touch to multi-touch transition.
+ if (oldPointerCount == 1 && pointerCount == 2) {
+ // Send an up event for the last pointer, be cause we can't trust the coordinates of
+ // this multi-touch event.
+ mainTracker.getLastCoordinates(mLastCoords);
+ final int x = CoordinateUtils.x(mLastCoords);
+ final int y = CoordinateUtils.y(mLastCoords);
+ mOldKey = mainTracker.getKeyOn(x, y);
+ // Inject an artifact up event for the old key.
+ injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime,
+ mainTracker, keyEventHandler);
+ return;
+ }
+
+ // Multi-touch to single-touch transition.
+ if (oldPointerCount == 2 && pointerCount == 1) {
+ // Send a down event for the latest pointer if the key is different from the previous
+ // key.
+ final int x = (int)me.getX(index);
+ final int y = (int)me.getY(index);
+ final Key newKey = mainTracker.getKeyOn(x, y);
+ if (mOldKey != newKey) {
+ // Inject an artifact down event for the new key.
+ // An artifact up event for the new key will usually be injected as a single-touch.
+ injectMotionEvent(MotionEvent.ACTION_DOWN, x, y, downTime, eventTime,
+ mainTracker, keyEventHandler);
+ if (action == MotionEvent.ACTION_UP) {
+ // Inject an artifact up event for the new key also.
+ injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime,
+ mainTracker, keyEventHandler);
+ }
+ }
+ return;
+ }
+
+ Log.w(TAG, "Unknown touch panel behavior: pointer count is "
+ + pointerCount + " (previously " + oldPointerCount + ")");
+ }
+
+ private static void injectMotionEvent(final int action, final float x, final float y,
+ final long downTime, final long eventTime, final PointerTracker tracker,
+ final KeyEventHandler handler) {
+ final MotionEvent me = MotionEvent.obtain(
+ downTime, eventTime, action, x, y, 0 /* metaState */);
+ try {
+ tracker.processMotionEvent(me, handler);
+ } finally {
+ me.recycle();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 31ef3cd8f..7ee45e8f6 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard.internal;
import android.util.Log;
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
@@ -207,7 +207,7 @@ public final class PointerTrackerQueue {
}
}
- public void cancelAllPointerTracker() {
+ public void cancelAllPointerTrackers() {
synchronized (mExpandableArrayOfActivePointers) {
if (DEBUG) {
Log.d(TAG, "cancelAllPointerTracker: " + this);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 23761103f..4c8607da8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -24,8 +24,8 @@ import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
-import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.CoordinateUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
import java.util.ArrayList;
@@ -37,7 +37,10 @@ public final class PreviewPlacerView extends RelativeLayout {
public PreviewPlacerView(final Context context, final AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
+ }
+ public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
+ if (!enabled) return;
final Paint layerPaint = new Paint();
layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
index 2eefd6ae9..211ef5f8b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
@@ -37,16 +37,18 @@ public final class RoundedLine {
* @param p2x the x-coordinate of the end point.
* @param p2y the y-coordinate of the end point.
* @param r2 the radius at the end point
- * @return the path of rounded line
+ * @return an instance of {@link Path} that holds the result rounded line, or an instance of
+ * {@link Path} that holds an empty path if the start and end points are equal.
*/
public Path makePath(final float p1x, final float p1y, final float r1,
final float p2x, final float p2y, final float r2) {
+ mPath.rewind();
final double dx = p2x - p1x;
final double dy = p2y - p1y;
// Distance of the points.
final double l = Math.hypot(dx, dy);
if (Double.compare(0.0d, l) == 0) {
- return null;
+ return mPath; // Return an empty path
}
// Angle of the line p1-p2
final double a = Math.atan2(dy, dx);
@@ -86,7 +88,6 @@ public final class RoundedLine {
mArc2.set(p2x, p2y, p2x, p2y);
mArc2.inset(-r2, -r2);
- mPath.rewind();
// Trail cap at P1.
mPath.moveTo(p1x, p1y);
mPath.arcTo(mArc1, angle, a1);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
index 33dbbafa3..2787ebfb9 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
@@ -23,8 +23,8 @@ import android.graphics.Path;
import android.view.View;
import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.latin.CoordinateUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
/**
* Draw rubber band preview graphics during sliding key input.
@@ -32,7 +32,7 @@ import com.android.inputmethod.latin.R;
public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
private final float mPreviewBodyRadius;
- private boolean mShowSlidingKeyInputPreview;
+ private boolean mShowsSlidingKeyInputPreview;
private final int[] mPreviewFrom = CoordinateUtils.newInstance();
private final int[] mPreviewTo = CoordinateUtils.newInstance();
@@ -62,7 +62,7 @@ public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
}
public void dismissSlidingKeyInputPreview() {
- mShowSlidingKeyInputPreview = false;
+ mShowsSlidingKeyInputPreview = false;
getDrawingView().invalidate();
}
@@ -72,7 +72,7 @@ public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
*/
@Override
public void drawPreview(final Canvas canvas) {
- if (!isPreviewEnabled() || !mShowSlidingKeyInputPreview) {
+ if (!isPreviewEnabled() || !mShowsSlidingKeyInputPreview) {
return;
}
@@ -90,13 +90,9 @@ public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
*/
@Override
public void setPreviewPosition(final PointerTracker tracker) {
- if (!tracker.isInSlidingKeyInputFromModifier()) {
- mShowSlidingKeyInputPreview = false;
- return;
- }
tracker.getDownCoordinates(mPreviewFrom);
tracker.getLastCoordinates(mPreviewTo);
- mShowSlidingKeyInputPreview = true;
+ mShowsSlidingKeyInputPreview = true;
getDrawingView().invalidate();
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java b/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java
new file mode 100644
index 000000000..10847f62d
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.keyboard.internal.MatrixUtils.MatrixOperationFailedException;
+
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Utilities to smooth coordinates. Currently, we calculate 3d least squares formula by using
+ * Lagrangian smoothing
+ */
+@UsedForTesting
+public class SmoothingUtils {
+ private static final String TAG = SmoothingUtils.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private SmoothingUtils() {
+ // not allowed to instantiate publicly
+ }
+
+ /**
+ * Find a most likely 3d least squares formula for specified coordinates.
+ * "retval" should be a 1x4 size matrix.
+ */
+ @UsedForTesting
+ public static void get3DParameters(final float[] xs, final float[] ys,
+ final float[][] retval) throws MatrixOperationFailedException {
+ final int COEFF_COUNT = 4; // Coefficient count for 3d smoothing
+ if (retval.length != COEFF_COUNT || retval[0].length != 1) {
+ Log.d(TAG, "--- invalid length of 3d retval " + retval.length + ", "
+ + retval[0].length);
+ return;
+ }
+ final int N = xs.length;
+ // TODO: Never isntantiate the matrix
+ final float[][] m0 = new float[COEFF_COUNT][COEFF_COUNT];
+ final float[][] m0Inv = new float[COEFF_COUNT][COEFF_COUNT];
+ final float[][] m1 = new float[COEFF_COUNT][N];
+ final float[][] m2 = new float[N][1];
+
+ // m0
+ for (int i = 0; i < COEFF_COUNT; ++i) {
+ Arrays.fill(m0[i], 0);
+ for (int j = 0; j < COEFF_COUNT; ++j) {
+ final int pow = i + j;
+ for (int k = 0; k < N; ++k) {
+ m0[i][j] += (float) Math.pow(xs[k], pow);
+ }
+ }
+ }
+ // m0Inv
+ MatrixUtils.inverse(m0, m0Inv);
+ if (DEBUG) {
+ MatrixUtils.dump("m0-1", m0Inv);
+ }
+
+ // m1
+ for (int i = 0; i < COEFF_COUNT; ++i) {
+ for (int j = 0; j < N; ++j) {
+ m1[i][j] = (i == 0) ? 1.0f : m1[i - 1][j] * xs[j];
+ }
+ }
+
+ // m2
+ for (int i = 0; i < N; ++i) {
+ m2[i][0] = ys[i];
+ }
+
+ final float[][] m0Invxm1 = new float[COEFF_COUNT][N];
+ if (DEBUG) {
+ MatrixUtils.dump("a0", m0Inv);
+ MatrixUtils.dump("a1", m1);
+ }
+ MatrixUtils.multiply(m0Inv, m1, m0Invxm1);
+ if (DEBUG) {
+ MatrixUtils.dump("a2", m0Invxm1);
+ MatrixUtils.dump("a3", m2);
+ }
+ MatrixUtils.multiply(m0Invxm1, m2, retval);
+ if (DEBUG) {
+ MatrixUtils.dump("result", retval);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java
deleted file mode 100644
index d6b1cc6fc..000000000
--- a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java
+++ /dev/null
@@ -1,155 +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.keyboard.internal;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.inputmethod.keyboard.MainKeyboardView;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
-import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.research.ResearchLogger;
-
-public final class TouchScreenRegulator {
- private static final String TAG = TouchScreenRegulator.class.getSimpleName();
- private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
-
- public interface ProcessMotionEvent {
- public boolean processMotionEvent(MotionEvent me);
- }
-
- private final ProcessMotionEvent mView;
- private final boolean mNeedsSuddenJumpingHack;
-
- /** Whether we've started dropping move events because we found a big jump */
- private boolean mDroppingEvents;
- /**
- * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has
- * occured
- */
- private boolean mDisableDisambiguation;
- /** The distance threshold at which we start treating the touch session as a multi-touch */
- private int mJumpThresholdSquare = Integer.MAX_VALUE;
- private int mLastX;
- private int mLastY;
- // One-seventh of the keyboard width seems like a reasonable threshold
- private static final float JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH = 1.0f / 7.0f;
-
- public TouchScreenRegulator(final Context context, final ProcessMotionEvent view) {
- mView = view;
- mNeedsSuddenJumpingHack = Boolean.parseBoolean(ResourceUtils.getDeviceOverrideValue(
- context.getResources(), R.array.sudden_jumping_touch_event_device_list));
- }
-
- public void setKeyboardGeometry(final int keyboardWidth) {
- final float jumpThreshold = keyboardWidth * JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH;
- mJumpThresholdSquare = (int)(jumpThreshold * jumpThreshold);
- }
-
- /**
- * This function checks to see if we need to handle any sudden jumps in the pointer location
- * that could be due to a multi-touch being treated as a move by the firmware or hardware.
- * Once a sudden jump is detected, all subsequent move events are discarded
- * until an UP is received.<P>
- * When a sudden jump is detected, an UP event is simulated at the last position and when
- * the sudden moves subside, a DOWN event is simulated for the second key.
- * @param me the motion event
- * @return true if the event was consumed, so that it doesn't continue to be handled by
- * {@link MainKeyboardView}.
- */
- private boolean handleSuddenJumping(final MotionEvent me) {
- if (!mNeedsSuddenJumpingHack)
- return false;
- final int action = me.getAction();
- final int x = (int) me.getX();
- final int y = (int) me.getY();
- boolean result = false;
-
- // Real multi-touch event? Stop looking for sudden jumps
- if (me.getPointerCount() > 1) {
- mDisableDisambiguation = true;
- }
- if (mDisableDisambiguation) {
- // If UP, reset the multi-touch flag
- if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false;
- return false;
- }
-
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- // Reset the "session"
- mDroppingEvents = false;
- mDisableDisambiguation = false;
- break;
- case MotionEvent.ACTION_MOVE:
- // Is this a big jump?
- final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y);
- // Check the distance.
- if (distanceSquare > mJumpThresholdSquare) {
- // If we're not yet dropping events, start dropping and send an UP event
- if (!mDroppingEvents) {
- mDroppingEvents = true;
- // Send an up event
- MotionEvent translated = MotionEvent.obtain(
- me.getEventTime(), me.getEventTime(),
- MotionEvent.ACTION_UP,
- mLastX, mLastY, me.getMetaState());
- mView.processMotionEvent(translated);
- translated.recycle();
- }
- result = true;
- } else if (mDroppingEvents) {
- // If moves are small and we're already dropping events, continue dropping
- result = true;
- }
- break;
- case MotionEvent.ACTION_UP:
- if (mDroppingEvents) {
- // Send a down event first, as we dropped a bunch of sudden jumps and assume that
- // the user is releasing the touch on the second key.
- MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
- MotionEvent.ACTION_DOWN,
- x, y, me.getMetaState());
- mView.processMotionEvent(translated);
- translated.recycle();
- mDroppingEvents = false;
- // Let the up event get processed as well, result = false
- }
- break;
- }
- // Track the previous coordinate
- mLastX = x;
- mLastY = y;
- return result;
- }
-
- public boolean onTouchEvent(final MotionEvent me) {
- // If there was a sudden jump, return without processing the actual motion event.
- if (handleSuddenJumping(me)) {
- if (DEBUG_MODE)
- Log.w(TAG, "onTouchEvent: ignore sudden jump " + me);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.suddenJumpingTouchEventHandler_onTouchEvent(me);
- }
- return true;
- }
- return mView.processMotionEvent(me);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
new file mode 100644
index 000000000..ebbcedc96
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+// TODO: Quit extending Dictionary after implementing dynamic binary dictionary.
+abstract public class AbstractDictionaryWriter extends Dictionary {
+ /** Used for Log actions from this class */
+ private static final String TAG = AbstractDictionaryWriter.class.getSimpleName();
+
+ private final Context mContext;
+
+ public AbstractDictionaryWriter(final Context context, final String dictType) {
+ super(dictType);
+ mContext = context;
+ }
+
+ abstract public void clear();
+
+ abstract public void addUnigramWord(final String word, final String shortcutTarget,
+ final int frequency, final boolean isNotAWord);
+
+ abstract public void addBigramWords(final String word0, final String word1,
+ final int frequency, final boolean isValid);
+
+ abstract public void removeBigramWords(final String word0, final String word1);
+
+ abstract protected void writeBinaryDictionary(final FileOutputStream out)
+ throws IOException, UnsupportedFormatException;
+
+ public void write(final String fileName) {
+ final String tempFileName = fileName + ".temp";
+ final File file = new File(mContext.getFilesDir(), fileName);
+ final File tempFile = new File(mContext.getFilesDir(), tempFileName);
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(tempFile);
+ writeBinaryDictionary(out);
+ out.flush();
+ out.close();
+ tempFile.renameTo(file);
+ } catch (IOException e) {
+ Log.e(TAG, "IO exception while writing file", e);
+ } catch (UnsupportedFormatException e) {
+ Log.e(TAG, "Unsupported format", e);
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
index 47c750f54..875192554 100644
--- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -24,7 +24,7 @@ import java.io.File;
* the package file. Open it correctly thus requires the name of the package it is in, but
* also the offset in the file and the length of this data. This class encapsulates these three.
*/
-final class AssetFileAddress {
+public final class AssetFileAddress {
public final String mFilename;
public final long mOffset;
public final long mLength;
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 986b1a178..42c57946d 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.settings.SettingsValues;
+
import android.content.Context;
import android.media.AudioManager;
import android.os.Vibrator;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 4fc1919dc..d181bf697 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -21,6 +21,11 @@ import android.util.SparseArray;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.settings.AdditionalFeaturesSettingUtils;
+import com.android.inputmethod.latin.settings.NativeSuggestOptions;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.JniUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -33,7 +38,7 @@ public final class BinaryDictionary extends Dictionary {
private static final String TAG = BinaryDictionary.class.getSimpleName();
// Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
- private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
+ private static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
// Must be equal to MAX_RESULTS in native/jni/src/defines.h
private static final int MAX_RESULTS = 18;
@@ -45,7 +50,7 @@ public final class BinaryDictionary extends Dictionary {
private final int[] mOutputScores = new int[MAX_RESULTS];
private final int[] mOutputTypes = new int[MAX_RESULTS];
- private final boolean mUseFullEditDistance;
+ private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
private final SparseArray<DicTraverseSession> mDicTraverseSessions =
CollectionUtils.newSparseArray();
@@ -74,35 +79,42 @@ public final class BinaryDictionary extends Dictionary {
* @param length the length of the binary data.
* @param useFullEditDistance whether to use the full edit distance in suggestions
* @param dictType the dictionary type, as a human-readable string
+ * @param isUpdatable whether to open the dictionary file in writable mode.
*/
public BinaryDictionary(final String filename, final long offset, final long length,
- final boolean useFullEditDistance, final Locale locale, final String dictType) {
+ final boolean useFullEditDistance, final Locale locale, final String dictType,
+ final boolean isUpdatable) {
super(dictType);
mLocale = locale;
- mUseFullEditDistance = useFullEditDistance;
- loadDictionary(filename, offset, length);
+ mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
+ loadDictionary(filename, offset, length, isUpdatable);
}
static {
JniUtils.loadNativeLibrary();
}
- private static native long openNative(String sourceDir, long dictOffset, long dictSize);
+ private static native long openNative(String sourceDir, long dictOffset, long dictSize,
+ boolean isUpdatable);
private static native void closeNative(long dict);
private static native int getProbabilityNative(long dict, int[] word);
- private static native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
+ private static native boolean isValidBigramNative(long dict, int[] word0, int[] word1);
private static native int getSuggestionsNative(long dict, long proximityInfo,
long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint,
- boolean isGesture, int[] prevWordCodePointArray, boolean useFullEditDistance,
+ int[] suggestOptions, int[] prevWordCodePointArray,
int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes);
private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
private static native int editDistanceNative(int[] before, int[] after);
+ private static native void addUnigramWordNative(long dict, int[] word, int probability);
+ private static native void addBigramWordsNative(long dict, int[] word0, int[] word1,
+ int probability);
+ private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
// TODO: Move native dict into session
private final void loadDictionary(final String path, final long startOffset,
- final long length) {
- mNativeDict = openNative(path, startOffset, length);
+ final long length, final boolean isUpdatable) {
+ mNativeDict = openNative(path, startOffset, length, isUpdatable);
}
@Override
@@ -135,12 +147,15 @@ public final class BinaryDictionary extends Dictionary {
final InputPointers ips = composer.getInputPointers();
final int inputSize = isGesture ? ips.getPointerSize() : composerSize;
+ mNativeSuggestOptions.setIsGesture(isGesture);
+ mNativeSuggestOptions.setAdditionalFeaturesOptions(
+ AdditionalFeaturesSettingUtils.getAdditionalNativeSuggestOptions());
// proximityInfo and/or prevWordForBigrams may not be null.
final int count = getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
getTraverseSession(sessionId).getSession(), ips.getXCoordinates(),
ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints,
- inputSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
- mUseFullEditDistance, mOutputCodePoints, mOutputScores, mSpaceIndices,
+ inputSize, 0 /* commitPoint */, mNativeSuggestOptions.getOptions(),
+ prevWordCodePointArray, mOutputCodePoints, mOutputScores, mSpaceIndices,
mOutputTypes);
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
for (int j = 0; j < count; ++j) {
@@ -202,11 +217,40 @@ public final class BinaryDictionary extends Dictionary {
// TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
// calls when checking for changes in an entire dictionary.
- public boolean isValidBigram(final String word1, final String word2) {
- if (TextUtils.isEmpty(word1) || TextUtils.isEmpty(word2)) return false;
+ public boolean isValidBigram(final String word0, final String word1) {
+ if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) return false;
+ final int[] codePoints0 = StringUtils.toCodePointArray(word0);
final int[] codePoints1 = StringUtils.toCodePointArray(word1);
- final int[] codePoints2 = StringUtils.toCodePointArray(word2);
- return isValidBigramNative(mNativeDict, codePoints1, codePoints2);
+ return isValidBigramNative(mNativeDict, codePoints0, codePoints1);
+ }
+
+ // Add a unigram entry to binary dictionary in native code.
+ public void addUnigramWord(final String word, final int probability) {
+ if (TextUtils.isEmpty(word)) {
+ return;
+ }
+ final int[] codePoints = StringUtils.toCodePointArray(word);
+ addUnigramWordNative(mNativeDict, codePoints, probability);
+ }
+
+ // Add a bigram entry to binary dictionary in native code.
+ public void addBigramWords(final String word0, final String word1, final int probability) {
+ if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
+ return;
+ }
+ final int[] codePoints0 = StringUtils.toCodePointArray(word0);
+ final int[] codePoints1 = StringUtils.toCodePointArray(word1);
+ addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability);
+ }
+
+ // Remove a bigram entry form binary dictionary in native code.
+ public void removeBigramWords(final String word0, final String word1) {
+ if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
+ return;
+ }
+ final int[] codePoints0 = StringUtils.toCodePointArray(word0);
+ final int[] codePoints1 = StringUtils.toCodePointArray(word1);
+ removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index a9b58de44..722a82961 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -28,10 +28,15 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
-import com.android.inputmethod.latin.DictionaryInfoUtils.DictionaryInfo;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
+import com.android.inputmethod.latin.utils.DictionaryInfoUtils.DictionaryInfo;
+import com.android.inputmethod.latin.utils.FileTransforms;
+import com.android.inputmethod.latin.utils.MetadataFileUriGetter;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
+import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -304,6 +309,7 @@ public final class BinaryDictionaryFileDumper {
Log.e(TAG, "Could not have the dictionary pack delete a word list");
}
BinaryDictionaryGetter.removeFilesWithIdExcept(context, wordlistId, finalFile);
+ Log.e(TAG, "Successfully copied file for wordlist ID " + wordlistId);
// Success! Close files (through the finally{} clause) and return.
return;
} catch (Exception e) {
@@ -319,20 +325,12 @@ public final class BinaryDictionaryFileDumper {
// Try the next method.
} finally {
// Ignore exceptions while closing files.
- try {
- if (null != afd) afd.close();
- if (null != inputStream) inputStream.close();
- if (null != uncompressedStream) uncompressedStream.close();
- if (null != decryptedStream) decryptedStream.close();
- if (null != bufferedInputStream) bufferedInputStream.close();
- } catch (Exception e) {
- Log.e(TAG, "Exception while closing a file descriptor", e);
- }
- try {
- if (null != bufferedOutputStream) bufferedOutputStream.close();
- } catch (Exception e) {
- Log.e(TAG, "Exception while closing a file", e);
- }
+ closeAssetFileDescriptorAndReportAnyException(afd);
+ closeCloseableAndReportAnyException(inputStream);
+ closeCloseableAndReportAnyException(uncompressedStream);
+ closeCloseableAndReportAnyException(decryptedStream);
+ closeCloseableAndReportAnyException(bufferedInputStream);
+ closeCloseableAndReportAnyException(bufferedOutputStream);
}
}
@@ -352,6 +350,26 @@ public final class BinaryDictionaryFileDumper {
}
}
+ // Ideally the two following methods should be merged, but AssetFileDescriptor does not
+ // implement Closeable although it does implement #close(), and Java does not have
+ // structural typing.
+ private static void closeAssetFileDescriptorAndReportAnyException(
+ final AssetFileDescriptor file) {
+ try {
+ if (null != file) file.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while closing a file", e);
+ }
+ }
+
+ private static void closeCloseableAndReportAnyException(final Closeable file) {
+ try {
+ if (null != file) file.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while closing a file", e);
+ }
+ }
+
/**
* Queries a content provider for word list data for some locale and cache the returned files
*
@@ -363,8 +381,14 @@ public final class BinaryDictionaryFileDumper {
*/
public static void cacheWordListsFromContentProvider(final Locale locale,
final Context context, final boolean hasDefaultWordList) {
- final ContentProviderClient providerClient = context.getContentResolver().
+ final ContentProviderClient providerClient;
+ try {
+ providerClient = context.getContentResolver().
acquireContentProviderClient(getProviderUriBuilder("").build());
+ } catch (final SecurityException e) {
+ Log.e(TAG, "No permission to communicate with the dictionary provider", e);
+ return;
+ }
if (null == providerClient) {
Log.e(TAG, "Can't establish communication with the dictionary provider");
return;
@@ -417,7 +441,6 @@ public final class BinaryDictionaryFileDumper {
final ContentProviderClient client, final String clientId) throws RemoteException {
final String metadataFileUri = MetadataFileUriGetter.getMetadataUri(context);
final String metadataAdditionalId = MetadataFileUriGetter.getMetadataAdditionalId(context);
- if (TextUtils.isEmpty(metadataFileUri)) return;
// Tell the content provider to reset all information about this client id
final Uri metadataContentUri = getProviderUriBuilder(clientId)
.appendPath(QUERY_PATH_METADATA)
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 98eadcacb..fa301b5a6 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -16,17 +16,17 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
-import com.android.inputmethod.latin.makedict.FormatSpec;
-
import android.content.Context;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetFileDescriptor;
import android.util.Log;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -39,7 +39,7 @@ import java.util.Locale;
/**
* Helper class to get the address of a mmap'able dictionary file.
*/
-final class BinaryDictionaryGetter {
+final public class BinaryDictionaryGetter {
/**
* Used for Log actions from this class
@@ -223,14 +223,10 @@ final class BinaryDictionaryGetter {
}
}
- // ## HACK ## we prevent usage of a dictionary before version 18 for English only. 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.
+ // ## HACK ## we prevent usage of a dictionary before version 18. The reason for this is, since
+ // those do not include whitelist entries, the new code with an old version of the dictionary
+ // would lose whitelist functionality.
private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
- // Only for English - other languages didn't have a whitelist, hence this
- // ad-hoc ## HACK ##
- if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
-
FileInputStream inStream = null;
try {
// Read the version of the file
@@ -290,17 +286,8 @@ final class BinaryDictionaryGetter {
final Context context) {
final boolean hasDefaultWordList = DictionaryFactory.isDictionaryAvailable(context, locale);
- // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
- // Service yet
- if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- // We need internet access to do the following. Only do this if the package actually
- // has the permission.
- if (context.checkCallingOrSelfPermission(android.Manifest.permission.INTERNET)
- == PackageManager.PERMISSION_GRANTED) {
- BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context,
- hasDefaultWordList);
- }
- }
+ BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context,
+ hasDefaultWordList);
final File[] cachedWordLists = getCachedWordLists(locale.toString(), context);
final String mainDictId = DictionaryInfoUtils.getMainDictId(locale);
final DictPackSettings dictPackSettings = new DictPackSettings(context);
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 86bb25562..6d67bdb04 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -126,15 +126,6 @@ public final class Constants {
}
}
- public static final class Dictionary {
- // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
- public static final int MAX_WORD_LENGTH = 48;
-
- private Dictionary() {
- // This utility class is no publicly instantiable.
- }
- }
-
public static final int NOT_A_CODE = -1;
public static final int NOT_A_COORDINATE = -1;
@@ -142,6 +133,10 @@ public final class Constants {
public static final int SPELL_CHECKER_COORDINATE = -3;
public static final int EXTERNAL_KEYBOARD_COORDINATE = -4;
+
+ // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
+ public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
+
public static boolean isValidCoordinate(final int coordinate) {
// Detect {@link NOT_A_COORDINATE}, {@link SUGGESTION_STRIP_COORDINATE},
// and {@link SPELL_CHECKER_COORDINATE}.
@@ -149,6 +144,13 @@ public final class Constants {
}
/**
+ * 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';
@@ -172,22 +174,23 @@ public final class Constants {
/**
* Special keys code. Must be negative.
- * These should be aligned with KeyboardCodesSet.ID_TO_NAME[],
- * KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[]
+ * 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_SWITCH_ALPHA_SYMBOL = -2;
- public static final int CODE_OUTPUT_TEXT = -3;
- public static final int CODE_DELETE = -4;
- public static final int CODE_SETTINGS = -5;
- public static final int CODE_SHORTCUT = -6;
- public static final int CODE_ACTION_NEXT = -7;
- public static final int CODE_ACTION_PREVIOUS = -8;
- public static final int CODE_LANGUAGE_SWITCH = -9;
- public static final int CODE_RESEARCH = -10;
- public static final int CODE_SHIFT_ENTER = -11;
+ 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_RESEARCH = -11;
+ public static final int CODE_SHIFT_ENTER = -12;
// Code value representing the code is not specified.
- public static final int CODE_UNSPECIFIED = -12;
+ public static final int CODE_UNSPECIFIED = -13;
public static boolean isLetterCode(final int code) {
return code >= CODE_SPACE;
@@ -196,6 +199,7 @@ public final class Constants {
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";
@@ -215,10 +219,6 @@ public final class Constants {
}
}
- // Constants for CSV parsing.
- public static final char CSV_SEPARATOR = ',';
- public static final char CSV_ESCAPE = '\\';
-
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 b9db9a092..c99d0e2ea 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -30,6 +30,8 @@ import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.latin.utils.StringUtils;
+
import java.util.List;
import java.util.Locale;
@@ -107,7 +109,6 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
@Override
public void loadDictionaryAsync() {
- clearFusionDictionary();
loadDeviceAccountsEmailAddresses();
loadDictionaryAsyncForUri(ContactsContract.Profile.CONTENT_URI);
// TODO: Switch this URL to the newer ContactsContract too
@@ -234,6 +235,11 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
}
@Override
+ protected boolean needsToReloadBeforeWriting() {
+ return true;
+ }
+
+ @Override
protected boolean hasContentChanged() {
final long startTime = SystemClock.uptimeMillis();
final int contactCount = getContactCount();
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index 534e2116b..45b281318 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.utils.JniUtils;
+
import java.util.Locale;
public final class DicTraverseSession {
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index acd7c2aa1..7c3e4a740 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -35,8 +35,13 @@ public abstract class Dictionary {
public static final String TYPE_CONTACTS = "contacts";
// User dictionary, the system-managed one.
public static final String TYPE_USER = "user";
- // User history dictionary internal to LatinIME.
+ // User history dictionary internal to LatinIME. This assumes bigram prediction for now.
public static final String TYPE_USER_HISTORY = "history";
+ // Personalization binary dictionary internal to LatinIME.
+ public static final String TYPE_PERSONALIZATION = "personalization";
+ // Personalization prediction dictionary internal to LatinIME's Java code.
+ public static final String TYPE_PERSONALIZATION_PREDICTION_IN_JAVA =
+ "personalization_prediction_in_java";
// Spawned by resuming suggestions. Comes from a span that was in the TextView.
public static final String TYPE_RESUMED = "resumed";
protected final String mDictType;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index ed2b44223..d05bb1e55 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -16,10 +16,11 @@
package com.android.inputmethod.latin;
+import android.util.Log;
+
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
-import android.util.Log;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.Collection;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 40e51672a..3721132c5 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -21,6 +21,10 @@ 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.CollectionUtils;
+import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
+
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -56,7 +60,8 @@ public final class DictionaryFactory {
if (null != assetFileList) {
for (final AssetFileAddress f : assetFileList) {
final BinaryDictionary binaryDictionary = new BinaryDictionary(f.mFilename,
- f.mOffset, f.mLength, useFullEditDistance, locale, Dictionary.TYPE_MAIN);
+ f.mOffset, f.mLength, useFullEditDistance, locale, Dictionary.TYPE_MAIN,
+ false /* isUpdatable */);
if (binaryDictionary.isValidDictionary()) {
dictList.add(binaryDictionary);
}
@@ -109,7 +114,8 @@ public final class DictionaryFactory {
return null;
}
return new BinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(),
- false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
+ false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN,
+ false /* isUpdatable */);
} catch (android.content.res.Resources.NotFoundException e) {
Log.e(TAG, "Could not find the resource");
return null;
@@ -126,21 +132,22 @@ public final class DictionaryFactory {
/**
* Create a dictionary from passed data. This is intended for unit tests only.
- * @param dictionary the file to read
- * @param startOffset the offset in the file where the data starts
- * @param length the length of the data
+ * @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.
*/
- public static Dictionary createDictionaryForTest(File dictionary, long startOffset, long length,
+ @UsedForTesting
+ public static Dictionary createDictionaryForTest(final AssetFileAddress[] dictionaryList,
final boolean useFullEditDistance, Locale locale) {
- if (dictionary.isFile()) {
- return new BinaryDictionary(dictionary.getAbsolutePath(), startOffset, length,
- useFullEditDistance, locale, Dictionary.TYPE_MAIN);
- } else {
- Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
- return null;
+ final DictionaryCollection dictionaryCollection =
+ new DictionaryCollection(Dictionary.TYPE_MAIN);
+ for (final AssetFileAddress address : dictionaryList) {
+ final BinaryDictionary binaryDictionary = new BinaryDictionary(address.mFilename,
+ address.mOffset, address.mLength, useFullEditDistance, locale,
+ Dictionary.TYPE_MAIN, false /* isUpdatable */);
+ dictionaryCollection.addDictionary(binaryDictionary);
}
+ return dictionaryCollection;
}
/**
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
index 56096127e..2dcfdb0b7 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
+import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
import android.content.BroadcastReceiver;
import android.content.Context;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
new file mode 100644
index 000000000..8be04c1c0
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * An in memory dictionary for memorizing entries and writing a binary dictionary.
+ */
+public class DictionaryWriter extends AbstractDictionaryWriter {
+ // TODO: Regenerate version 3 binary dictionary.
+ private static final int BINARY_DICT_VERSION = 2;
+ private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
+ new FormatSpec.FormatOptions(BINARY_DICT_VERSION);
+
+ private FusionDictionary mFusionDictionary;
+
+ public DictionaryWriter(final Context context, final String dictType) {
+ super(context, dictType);
+ clear();
+ }
+
+ @Override
+ public void clear() {
+ final HashMap<String, String> attributes = CollectionUtils.newHashMap();
+ mFusionDictionary = new FusionDictionary(new Node(),
+ new FusionDictionary.DictionaryOptions(attributes, false, false));
+ }
+
+ /**
+ * Adds a word unigram to the fusion dictionary.
+ */
+ // TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries,
+ // considering performance regression.
+ @Override
+ public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
+ final boolean isNotAWord) {
+ if (shortcutTarget == null) {
+ mFusionDictionary.add(word, frequency, null, isNotAWord);
+ } else {
+ // TODO: Do this in the subclass, with this class taking an arraylist.
+ final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
+ shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
+ mFusionDictionary.add(word, frequency, shortcutTargets, isNotAWord);
+ }
+ }
+
+ @Override
+ public void addBigramWords(final String word0, final String word1, final int frequency,
+ final boolean isValid) {
+ mFusionDictionary.setBigram(word0, word1, frequency);
+ }
+
+ @Override
+ public void removeBigramWords(final String word0, final String word1) {
+ // This class don't support removing bigram words.
+ }
+
+ @Override
+ protected void writeBinaryDictionary(final FileOutputStream out)
+ throws IOException, UnsupportedFormatException {
+ BinaryDictInputOutput.writeDictionaryBinary(out, mFusionDictionary, FORMAT_OPTIONS);
+ }
+
+ @Override
+ public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+ final String prevWord, final ProximityInfo proximityInfo,
+ boolean blockOffensiveWords) {
+ // This class doesn't support suggestion.
+ return null;
+ }
+
+ @Override
+ public boolean isValidWord(String word) {
+ // This class doesn't support dictionary retrieval.
+ return false;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 887d657e9..3f11391ba 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -22,19 +22,12 @@ import android.util.Log;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
-import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Abstract base class for an expandable dictionary that can be created and updated dynamically
@@ -55,7 +48,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/**
* 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 = Constants.DICTIONARY_MAX_WORD_LENGTH;
/**
* A static map of locks, each of which controls access to a single binary dictionary file. They
@@ -75,8 +68,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
private BinaryDictionary mBinaryDictionary;
- /** The expandable fusion dictionary used to generate the binary dictionary. */
- private FusionDictionary mFusionDictionary;
+ /** The in-memory dictionary used to generate the binary dictionary. */
+ private AbstractDictionaryWriter mDictionaryWriter;
/**
* The name of this dictionary, used as the filename for storing the binary dictionary. Multiple
@@ -91,10 +84,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/** Controls access to the local binary dictionary for this instance. */
private final DictionaryController mLocalDictionaryController = new DictionaryController();
- private static final int BINARY_DICT_VERSION = 1;
- private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
- new FormatSpec.FormatOptions(BINARY_DICT_VERSION);
-
/**
* Abstract method for loading the unigrams and bigrams of a given dictionary in a background
* thread.
@@ -136,7 +125,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
mContext = context;
mBinaryDictionary = null;
mSharedDictionaryController = getSharedDictionaryController(filename);
- clearFusionDictionary();
+ mDictionaryWriter = new DictionaryWriter(context, dictType);
}
protected static String getFilenameWithLocale(final String name, final String localeStr) {
@@ -149,53 +138,57 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
@Override
public void close() {
// Ensure that no other threads are accessing the local binary dictionary.
- mLocalDictionaryController.lock();
+ mLocalDictionaryController.writeLock().lock();
try {
if (mBinaryDictionary != null) {
mBinaryDictionary.close();
mBinaryDictionary = null;
}
+ mDictionaryWriter.close();
} finally {
- mLocalDictionaryController.unlock();
+ mLocalDictionaryController.writeLock().unlock();
}
}
/**
- * Clears the fusion dictionary on the Java side. Note: Does not modify the binary dictionary on
- * the native side.
+ * Adds a word unigram to the dictionary. Used for loading a dictionary.
*/
- public void clearFusionDictionary() {
- final HashMap<String, String> attributes = CollectionUtils.newHashMap();
- mFusionDictionary = new FusionDictionary(new Node(),
- new FusionDictionary.DictionaryOptions(attributes, false, false));
+ protected void addWord(final String word, final String shortcutTarget,
+ final int frequency, final boolean isNotAWord) {
+ mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
}
/**
- * Adds a word unigram to the fusion dictionary. Call updateBinaryDictionary when all changes
- * are done to update the binary dictionary.
+ * Sets a word bigram in the dictionary. Used for loading a dictionary.
*/
- // TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries,
- // considering performance regression.
- protected void addWord(final String word, final String shortcutTarget, final int frequency,
- final boolean isNotAWord) {
- if (shortcutTarget == null) {
- mFusionDictionary.add(word, frequency, null, isNotAWord);
- } else {
- // TODO: Do this in the subclass, with this class taking an arraylist.
- final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
- shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
- mFusionDictionary.add(word, frequency, shortcutTargets, isNotAWord);
+ protected void setBigram(final String prevWord, final String word, final int frequency) {
+ mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */);
+ }
+
+ /**
+ * Dynamically adds a word unigram to the dictionary.
+ */
+ protected void addWordDynamically(final String word, final String shortcutTarget,
+ final int frequency, final boolean isNotAWord) {
+ mLocalDictionaryController.writeLock().lock();
+ try {
+ mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
+ } finally {
+ mLocalDictionaryController.writeLock().unlock();
}
}
/**
- * Sets a word bigram in the fusion dictionary. Call updateBinaryDictionary when all changes are
- * done to update the binary dictionary.
+ * Dynamically sets a word bigram in the dictionary.
*/
- // TODO: Create "cache dictionary" to cache fresh bigrams for frequently updated dictionaries,
- // considering performance regression.
- protected void setBigram(final String prevWord, final String word, final int frequency) {
- mFusionDictionary.setBigram(prevWord, word, frequency);
+ protected void setBigramDynamically(final String prevWord, final String word,
+ final int frequency) {
+ mLocalDictionaryController.writeLock().lock();
+ try {
+ mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */);
+ } finally {
+ mLocalDictionaryController.writeLock().unlock();
+ }
}
@Override
@@ -203,14 +196,29 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
final String prevWord, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords) {
asyncReloadDictionaryIfRequired();
- if (mLocalDictionaryController.tryLock()) {
+ // Write lock because getSuggestions in native updates session status.
+ if (mLocalDictionaryController.writeLock().tryLock()) {
try {
+ final ArrayList<SuggestedWordInfo> inMemDictSuggestion =
+ mDictionaryWriter.getSuggestions(composer, prevWord, proximityInfo,
+ blockOffensiveWords);
if (mBinaryDictionary != null) {
- return mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
- blockOffensiveWords);
+ final ArrayList<SuggestedWordInfo> binarySuggestion =
+ mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
+ blockOffensiveWords);
+ if (inMemDictSuggestion == null) {
+ return binarySuggestion;
+ } else if (binarySuggestion == null) {
+ return inMemDictSuggestion;
+ } else {
+ binarySuggestion.addAll(binarySuggestion);
+ return binarySuggestion;
+ }
+ } else {
+ return inMemDictSuggestion;
}
} finally {
- mLocalDictionaryController.unlock();
+ mLocalDictionaryController.writeLock().unlock();
}
}
return null;
@@ -223,11 +231,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
protected boolean isValidWordInner(final String word) {
- if (mLocalDictionaryController.tryLock()) {
+ if (mLocalDictionaryController.readLock().tryLock()) {
try {
return isValidWordLocked(word);
} finally {
- mLocalDictionaryController.unlock();
+ mLocalDictionaryController.readLock().unlock();
}
}
return false;
@@ -238,22 +246,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return mBinaryDictionary.isValidWord(word);
}
- protected boolean isValidBigram(final String word1, final String word2) {
- if (mBinaryDictionary == null) return false;
- return mBinaryDictionary.isValidBigram(word1, word2);
- }
-
- protected boolean isValidBigramInner(final String word1, final String word2) {
- if (mLocalDictionaryController.tryLock()) {
- try {
- return isValidBigramLocked(word1, word2);
- } finally {
- mLocalDictionaryController.unlock();
- }
- }
- return false;
- }
-
protected boolean isValidBigramLocked(final String word1, final String word2) {
if (mBinaryDictionary == null) return false;
return mBinaryDictionary.isValidBigram(word1, word2);
@@ -272,7 +264,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* Loads the current binary dictionary from internal storage. Assumes the dictionary file
* exists.
*/
- protected void loadBinaryDictionary() {
+ private void loadBinaryDictionary() {
if (DEBUG) {
Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
+ mSharedDictionaryController.mLastUpdateRequestTime + " update="
@@ -285,15 +277,18 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
// Build the new binary dictionary
final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0, length,
- true /* useFullEditDistance */, null, mDictType);
+ true /* useFullEditDistance */, null, mDictType, false /* isUpdatable */);
if (mBinaryDictionary != null) {
// Ensure all threads accessing the current dictionary have finished before swapping in
// the new one.
final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
- mLocalDictionaryController.lock();
- mBinaryDictionary = newBinaryDictionary;
- mLocalDictionaryController.unlock();
+ mLocalDictionaryController.writeLock().lock();
+ try {
+ mBinaryDictionary = newBinaryDictionary;
+ } finally {
+ mLocalDictionaryController.writeLock().unlock();
+ }
oldBinaryDictionary.close();
} else {
mBinaryDictionary = newBinaryDictionary;
@@ -301,6 +296,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
/**
+ * Abstract method for checking if it is required to reload the dictionary before writing
+ * a binary dictionary.
+ */
+ abstract protected boolean needsToReloadBeforeWriting();
+
+ /**
* Generates and writes a new binary dictionary based on the contents of the fusion dictionary.
*/
private void generateBinaryDictionary() {
@@ -309,33 +310,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
+ mSharedDictionaryController.mLastUpdateRequestTime + " update="
+ mSharedDictionaryController.mLastUpdateTime);
}
-
- loadDictionaryAsync();
-
- final String tempFileName = mFilename + ".temp";
- final File file = new File(mContext.getFilesDir(), mFilename);
- final File tempFile = new File(mContext.getFilesDir(), tempFileName);
- FileOutputStream out = null;
- try {
- out = new FileOutputStream(tempFile);
- BinaryDictInputOutput.writeDictionaryBinary(out, mFusionDictionary, FORMAT_OPTIONS);
- out.flush();
- out.close();
- tempFile.renameTo(file);
- clearFusionDictionary();
- } catch (IOException e) {
- Log.e(TAG, "IO exception while writing file", e);
- } catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported format", e);
- } finally {
- if (out != null) {
- try {
- out.close();
- } catch (IOException e) {
- // ignore
- }
- }
+ if (needsToReloadBeforeWriting()) {
+ mDictionaryWriter.clear();
+ loadDictionaryAsync();
}
+ mDictionaryWriter.write(mFilename);
}
/**
@@ -388,7 +367,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private final void syncReloadDictionaryInternal() {
// Ensure that only one thread attempts to read or write to the shared binary dictionary
// file at the same time.
- mSharedDictionaryController.lock();
+ mSharedDictionaryController.writeLock().lock();
try {
final long time = SystemClock.uptimeMillis();
final boolean dictionaryFileExists = dictionaryFileExists();
@@ -414,9 +393,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
// shared dictionary.
loadBinaryDictionary();
}
+ if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
+ // Binary dictionary is not valid. Regenerate the dictionary file.
+ mSharedDictionaryController.mLastUpdateTime = time;
+ generateBinaryDictionary();
+ loadBinaryDictionary();
+ }
mLocalDictionaryController.mLastUpdateTime = time;
} finally {
- mSharedDictionaryController.unlock();
+ mSharedDictionaryController.writeLock().unlock();
}
}
@@ -441,7 +426,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* dictionary is out of date. Can be shared across multiple dictionary instances that access the
* same filename.
*/
- private static class DictionaryController extends ReentrantLock {
+ private static class DictionaryController extends ReentrantReadWriteLock {
private volatile long mLastUpdateTime = 0;
private volatile long mLastUpdateRequestTime = 0;
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 0dabdb835..bd2d70365 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -22,7 +22,8 @@ import android.util.Log;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -42,7 +43,7 @@ public class ExpandableDictionary extends Dictionary {
protected static final int BIGRAM_MAX_FREQUENCY = 255;
private Context mContext;
- private char[] mWordBuilder = new char[Constants.Dictionary.MAX_WORD_LENGTH];
+ private char[] mWordBuilder = new char[Constants.DICTIONARY_MAX_WORD_LENGTH];
private int mMaxDepth;
private int mInputLength;
@@ -86,7 +87,7 @@ public class ExpandableDictionary extends Dictionary {
}
}
- protected interface NextWord {
+ public interface NextWord {
public Node getWordNode();
public int getFrequency();
public ForgettingCurveParams getFcParams();
@@ -160,7 +161,7 @@ public class ExpandableDictionary extends Dictionary {
super(dictType);
mContext = context;
clearDictionary();
- mCodes = new int[Constants.Dictionary.MAX_WORD_LENGTH][];
+ mCodes = new int[Constants.DICTIONARY_MAX_WORD_LENGTH][];
}
public void loadDictionary() {
@@ -197,11 +198,11 @@ public class ExpandableDictionary extends Dictionary {
}
public int getMaxWordLength() {
- return Constants.Dictionary.MAX_WORD_LENGTH;
+ return Constants.DICTIONARY_MAX_WORD_LENGTH;
}
public void addWord(final String word, final String shortcutTarget, final int frequency) {
- if (word.length() >= Constants.Dictionary.MAX_WORD_LENGTH) {
+ if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
return;
}
addWordRec(mRoots, word, 0, shortcutTarget, frequency, null);
@@ -257,7 +258,7 @@ public class ExpandableDictionary extends Dictionary {
final boolean blockOffensiveWords) {
if (reloadDictionaryIfRequired()) return null;
if (composer.size() > 1) {
- if (composer.size() >= Constants.Dictionary.MAX_WORD_LENGTH) {
+ if (composer.size() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
return null;
}
final ArrayList<SuggestedWordInfo> suggestions =
@@ -628,7 +629,7 @@ public class ExpandableDictionary extends Dictionary {
}
// Local to reverseLookUp, but do not allocate each time.
- private final char[] mLookedUpString = new char[Constants.Dictionary.MAX_WORD_LENGTH];
+ private final char[] mLookedUpString = new char[Constants.DICTIONARY_MAX_WORD_LENGTH];
/**
* reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
@@ -643,7 +644,7 @@ public class ExpandableDictionary extends Dictionary {
for (NextWord nextWord : terminalNodes) {
node = nextWord.getWordNode();
freq = nextWord.getFrequency();
- int index = Constants.Dictionary.MAX_WORD_LENGTH;
+ int index = Constants.DICTIONARY_MAX_WORD_LENGTH;
do {
--index;
mLookedUpString[index] = node.mCode;
@@ -655,7 +656,7 @@ public class ExpandableDictionary extends Dictionary {
// to ignore the word in this case.
if (freq >= 0 && node == null) {
suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index,
- Constants.Dictionary.MAX_WORD_LENGTH - index),
+ Constants.DICTIONARY_MAX_WORD_LENGTH - index),
freq, SuggestedWordInfo.KIND_CORRECTION, mDictType));
}
}
@@ -727,172 +728,206 @@ public class ExpandableDictionary extends Dictionary {
* to their base characters. If c is in range, BASE_CHARS[c] == c
* if c is not a combined character, or the base character if it
* is combined.
+ *
+ * cf. native/jni/src/utils/char_utils.cpp
*/
private static final char BASE_CHARS[] = {
- 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
- 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
- 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
- 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
- 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
- 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
- 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
- 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
- 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
- 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
- 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
- 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
- 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
- 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
- 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
- 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
- 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
- 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
- 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
- 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
- 0x0020, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
- 0x0020, 0x00a9, 0x0061, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0020,
- 0x00b0, 0x00b1, 0x0032, 0x0033, 0x0020, 0x03bc, 0x00b6, 0x00b7,
- 0x0020, 0x0031, 0x006f, 0x00bb, 0x0031, 0x0031, 0x0033, 0x00bf,
- 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00c6, 0x0043,
- 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
- 0x00d0, 0x004e, 0x004f, 0x004f, 0x004f, 0x004f, 0x004f, 0x00d7,
- 0x004f, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00de, 0x0073, // Manually changed d8 to 4f
- // Manually changed df to 73
- 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063,
- 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
- 0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00f7,
- 0x006f, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0079, // Manually changed f8 to 6f
- 0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063,
- 0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064,
- 0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065,
- 0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067,
- 0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127,
- 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069,
- 0x0049, 0x0131, 0x0049, 0x0069, 0x004a, 0x006a, 0x004b, 0x006b,
- 0x0138, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c,
- 0x006c, 0x0141, 0x0142, 0x004e, 0x006e, 0x004e, 0x006e, 0x004e,
- 0x006e, 0x02bc, 0x014a, 0x014b, 0x004f, 0x006f, 0x004f, 0x006f,
- 0x004f, 0x006f, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072,
- 0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073,
- 0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167,
- 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075,
- 0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079,
- 0x0059, 0x005a, 0x007a, 0x005a, 0x007a, 0x005a, 0x007a, 0x0073,
- 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187,
- 0x0188, 0x0189, 0x018a, 0x018b, 0x018c, 0x018d, 0x018e, 0x018f,
- 0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197,
- 0x0198, 0x0199, 0x019a, 0x019b, 0x019c, 0x019d, 0x019e, 0x019f,
- 0x004f, 0x006f, 0x01a2, 0x01a3, 0x01a4, 0x01a5, 0x01a6, 0x01a7,
- 0x01a8, 0x01a9, 0x01aa, 0x01ab, 0x01ac, 0x01ad, 0x01ae, 0x0055,
- 0x0075, 0x01b1, 0x01b2, 0x01b3, 0x01b4, 0x01b5, 0x01b6, 0x01b7,
- 0x01b8, 0x01b9, 0x01ba, 0x01bb, 0x01bc, 0x01bd, 0x01be, 0x01bf,
- 0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x0044, 0x0044, 0x0064, 0x004c,
- 0x004c, 0x006c, 0x004e, 0x004e, 0x006e, 0x0041, 0x0061, 0x0049,
- 0x0069, 0x004f, 0x006f, 0x0055, 0x0075, 0x00dc, 0x00fc, 0x00dc,
- 0x00fc, 0x00dc, 0x00fc, 0x00dc, 0x00fc, 0x01dd, 0x00c4, 0x00e4,
- 0x0226, 0x0227, 0x00c6, 0x00e6, 0x01e4, 0x01e5, 0x0047, 0x0067,
- 0x004b, 0x006b, 0x004f, 0x006f, 0x01ea, 0x01eb, 0x01b7, 0x0292,
- 0x006a, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01f6, 0x01f7,
- 0x004e, 0x006e, 0x00c5, 0x00e5, 0x00c6, 0x00e6, 0x00d8, 0x00f8,
- 0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065,
- 0x0049, 0x0069, 0x0049, 0x0069, 0x004f, 0x006f, 0x004f, 0x006f,
- 0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075,
- 0x0053, 0x0073, 0x0054, 0x0074, 0x021c, 0x021d, 0x0048, 0x0068,
- 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061,
- 0x0045, 0x0065, 0x00d6, 0x00f6, 0x00d5, 0x00f5, 0x004f, 0x006f,
- 0x022e, 0x022f, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237,
- 0x0238, 0x0239, 0x023a, 0x023b, 0x023c, 0x023d, 0x023e, 0x023f,
- 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247,
- 0x0248, 0x0249, 0x024a, 0x024b, 0x024c, 0x024d, 0x024e, 0x024f,
- 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
- 0x0258, 0x0259, 0x025a, 0x025b, 0x025c, 0x025d, 0x025e, 0x025f,
- 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
- 0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e, 0x026f,
- 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
- 0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f,
- 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
- 0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d, 0x028e, 0x028f,
- 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
- 0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f,
- 0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7,
- 0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af,
- 0x0068, 0x0266, 0x006a, 0x0072, 0x0279, 0x027b, 0x0281, 0x0077,
- 0x0079, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf,
- 0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7,
- 0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf,
- 0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7,
- 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02de, 0x02df,
- 0x0263, 0x006c, 0x0073, 0x0078, 0x0295, 0x02e5, 0x02e6, 0x02e7,
- 0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef,
- 0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7,
- 0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff,
- 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
- 0x0308, 0x0309, 0x030a, 0x030b, 0x030c, 0x030d, 0x030e, 0x030f,
- 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
- 0x0318, 0x0319, 0x031a, 0x031b, 0x031c, 0x031d, 0x031e, 0x031f,
- 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
- 0x0328, 0x0329, 0x032a, 0x032b, 0x032c, 0x032d, 0x032e, 0x032f,
- 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
- 0x0338, 0x0339, 0x033a, 0x033b, 0x033c, 0x033d, 0x033e, 0x033f,
- 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347,
- 0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x034f,
- 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
- 0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f,
- 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
- 0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f,
- 0x0370, 0x0371, 0x0372, 0x0373, 0x02b9, 0x0375, 0x0376, 0x0377,
- 0x0378, 0x0379, 0x0020, 0x037b, 0x037c, 0x037d, 0x003b, 0x037f,
- 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00a8, 0x0391, 0x00b7,
- 0x0395, 0x0397, 0x0399, 0x038b, 0x039f, 0x038d, 0x03a5, 0x03a9,
- 0x03ca, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
- 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f,
- 0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7,
- 0x03a8, 0x03a9, 0x0399, 0x03a5, 0x03b1, 0x03b5, 0x03b7, 0x03b9,
- 0x03cb, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7,
- 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf,
- 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7,
- 0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03bf, 0x03c5, 0x03c9, 0x03cf,
- 0x03b2, 0x03b8, 0x03a5, 0x03d2, 0x03d2, 0x03c6, 0x03c0, 0x03d7,
- 0x03d8, 0x03d9, 0x03da, 0x03db, 0x03dc, 0x03dd, 0x03de, 0x03df,
- 0x03e0, 0x03e1, 0x03e2, 0x03e3, 0x03e4, 0x03e5, 0x03e6, 0x03e7,
- 0x03e8, 0x03e9, 0x03ea, 0x03eb, 0x03ec, 0x03ed, 0x03ee, 0x03ef,
- 0x03ba, 0x03c1, 0x03c2, 0x03f3, 0x0398, 0x03b5, 0x03f6, 0x03f7,
- 0x03f8, 0x03a3, 0x03fa, 0x03fb, 0x03fc, 0x03fd, 0x03fe, 0x03ff,
- 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406,
- 0x0408, 0x0409, 0x040a, 0x040b, 0x041a, 0x0418, 0x0423, 0x040f,
- 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
- 0x0418, 0x0418, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f,
- 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
- 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f,
- 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
- 0x0438, 0x0438, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
- 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
- 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f,
- 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
- 0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f,
- 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467,
- 0x0468, 0x0469, 0x046a, 0x046b, 0x046c, 0x046d, 0x046e, 0x046f,
- 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475,
- 0x0478, 0x0479, 0x047a, 0x047b, 0x047c, 0x047d, 0x047e, 0x047f,
- 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
- 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x048f,
- 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497,
- 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x049f,
- 0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7,
- 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x04af,
- 0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7,
- 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x04be, 0x04bf,
- 0x04c0, 0x0416, 0x0436, 0x04c3, 0x04c4, 0x04c5, 0x04c6, 0x04c7,
- 0x04c8, 0x04c9, 0x04ca, 0x04cb, 0x04cc, 0x04cd, 0x04ce, 0x04cf,
- 0x0410, 0x0430, 0x0410, 0x0430, 0x04d4, 0x04d5, 0x0415, 0x0435,
- 0x04d8, 0x04d9, 0x04d8, 0x04d9, 0x0416, 0x0436, 0x0417, 0x0437,
- 0x04e0, 0x04e1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041e, 0x043e,
- 0x04e8, 0x04e9, 0x04e8, 0x04e9, 0x042d, 0x044d, 0x0423, 0x0443,
- 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04f6, 0x04f7,
- 0x042b, 0x044b, 0x04fa, 0x04fb, 0x04fc, 0x04fd, 0x04fe, 0x04ff,
+ /* U+0000 */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+ /* U+0008 */ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
+ /* U+0010 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+ /* U+0018 */ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
+ /* U+0020 */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ /* U+0028 */ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
+ /* U+0030 */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ /* U+0038 */ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
+ /* U+0040 */ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ /* U+0048 */ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
+ /* U+0050 */ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ /* U+0058 */ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
+ /* U+0060 */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ /* U+0068 */ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
+ /* U+0070 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ /* U+0078 */ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
+ /* U+0080 */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+ /* U+0088 */ 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
+ /* U+0090 */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+ /* U+0098 */ 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
+ /* U+00A0 */ 0x0020, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
+ /* U+00A8 */ 0x0020, 0x00A9, 0x0061, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0020,
+ /* U+00B0 */ 0x00B0, 0x00B1, 0x0032, 0x0033, 0x0020, 0x03BC, 0x00B6, 0x00B7,
+ /* U+00B8 */ 0x0020, 0x0031, 0x006F, 0x00BB, 0x0031, 0x0031, 0x0033, 0x00BF,
+ /* U+00C0 */ 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00C6, 0x0043,
+ /* U+00C8 */ 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
+ /* U+00D0 */ 0x00D0, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x00D7,
+ /* U+00D8 */ 0x004F, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00DE, 0x0073,
+ // U+00D8: Manually changed from 00D8 to 004F
+ // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
+ // U+00DF: Manually changed from 00DF to 0073
+ /* U+00E0 */ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00E6, 0x0063,
+ /* U+00E8 */ 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
+ /* U+00F0 */ 0x00F0, 0x006E, 0x006F, 0x006F, 0x006F, 0x006F, 0x006F, 0x00F7,
+ /* U+00F8 */ 0x006F, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00FE, 0x0079,
+ // U+00F8: Manually changed from 00F8 to 006F
+ // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
+ /* U+0100 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063,
+ /* U+0108 */ 0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064,
+ /* U+0110 */ 0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065,
+ /* U+0118 */ 0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067,
+ /* U+0120 */ 0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127,
+ /* U+0128 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069,
+ /* U+0130 */ 0x0049, 0x0131, 0x0049, 0x0069, 0x004A, 0x006A, 0x004B, 0x006B,
+ /* U+0138 */ 0x0138, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C,
+ /* U+0140 */ 0x006C, 0x004C, 0x006C, 0x004E, 0x006E, 0x004E, 0x006E, 0x004E,
+ // U+0141: Manually changed from 0141 to 004C
+ // U+0142: Manually changed from 0142 to 006C
+ /* U+0148 */ 0x006E, 0x02BC, 0x014A, 0x014B, 0x004F, 0x006F, 0x004F, 0x006F,
+ /* U+0150 */ 0x004F, 0x006F, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072,
+ /* U+0158 */ 0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073,
+ /* U+0160 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167,
+ /* U+0168 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075,
+ /* U+0170 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079,
+ /* U+0178 */ 0x0059, 0x005A, 0x007A, 0x005A, 0x007A, 0x005A, 0x007A, 0x0073,
+ /* U+0180 */ 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187,
+ /* U+0188 */ 0x0188, 0x0189, 0x018A, 0x018B, 0x018C, 0x018D, 0x018E, 0x018F,
+ /* U+0190 */ 0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197,
+ /* U+0198 */ 0x0198, 0x0199, 0x019A, 0x019B, 0x019C, 0x019D, 0x019E, 0x019F,
+ /* U+01A0 */ 0x004F, 0x006F, 0x01A2, 0x01A3, 0x01A4, 0x01A5, 0x01A6, 0x01A7,
+ /* U+01A8 */ 0x01A8, 0x01A9, 0x01AA, 0x01AB, 0x01AC, 0x01AD, 0x01AE, 0x0055,
+ /* U+01B0 */ 0x0075, 0x01B1, 0x01B2, 0x01B3, 0x01B4, 0x01B5, 0x01B6, 0x01B7,
+ /* U+01B8 */ 0x01B8, 0x01B9, 0x01BA, 0x01BB, 0x01BC, 0x01BD, 0x01BE, 0x01BF,
+ /* U+01C0 */ 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x0044, 0x0044, 0x0064, 0x004C,
+ /* U+01C8 */ 0x004C, 0x006C, 0x004E, 0x004E, 0x006E, 0x0041, 0x0061, 0x0049,
+ /* U+01D0 */ 0x0069, 0x004F, 0x006F, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055,
+ // U+01D5: Manually changed from 00DC to 0055
+ // U+01D6: Manually changed from 00FC to 0075
+ // U+01D7: Manually changed from 00DC to 0055
+ /* U+01D8 */ 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x01DD, 0x0041, 0x0061,
+ // U+01D8: Manually changed from 00FC to 0075
+ // U+01D9: Manually changed from 00DC to 0055
+ // U+01DA: Manually changed from 00FC to 0075
+ // U+01DB: Manually changed from 00DC to 0055
+ // U+01DC: Manually changed from 00FC to 0075
+ // U+01DE: Manually changed from 00C4 to 0041
+ // U+01DF: Manually changed from 00E4 to 0061
+ /* U+01E0 */ 0x0041, 0x0061, 0x00C6, 0x00E6, 0x01E4, 0x01E5, 0x0047, 0x0067,
+ // U+01E0: Manually changed from 0226 to 0041
+ // U+01E1: Manually changed from 0227 to 0061
+ /* U+01E8 */ 0x004B, 0x006B, 0x004F, 0x006F, 0x004F, 0x006F, 0x01B7, 0x0292,
+ // U+01EC: Manually changed from 01EA to 004F
+ // U+01ED: Manually changed from 01EB to 006F
+ /* U+01F0 */ 0x006A, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01F6, 0x01F7,
+ /* U+01F8 */ 0x004E, 0x006E, 0x0041, 0x0061, 0x00C6, 0x00E6, 0x004F, 0x006F,
+ // U+01FA: Manually changed from 00C5 to 0041
+ // U+01FB: Manually changed from 00E5 to 0061
+ // U+01FE: Manually changed from 00D8 to 004F
+ // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
+ // U+01FF: Manually changed from 00F8 to 006F
+ // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
+ /* U+0200 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065,
+ /* U+0208 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x004F, 0x006F, 0x004F, 0x006F,
+ /* U+0210 */ 0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075,
+ /* U+0218 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x021C, 0x021D, 0x0048, 0x0068,
+ /* U+0220 */ 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061,
+ /* U+0228 */ 0x0045, 0x0065, 0x004F, 0x006F, 0x004F, 0x006F, 0x004F, 0x006F,
+ // U+022A: Manually changed from 00D6 to 004F
+ // U+022B: Manually changed from 00F6 to 006F
+ // U+022C: Manually changed from 00D5 to 004F
+ // U+022D: Manually changed from 00F5 to 006F
+ /* U+0230 */ 0x004F, 0x006F, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237,
+ // U+0230: Manually changed from 022E to 004F
+ // U+0231: Manually changed from 022F to 006F
+ /* U+0238 */ 0x0238, 0x0239, 0x023A, 0x023B, 0x023C, 0x023D, 0x023E, 0x023F,
+ /* U+0240 */ 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247,
+ /* U+0248 */ 0x0248, 0x0249, 0x024A, 0x024B, 0x024C, 0x024D, 0x024E, 0x024F,
+ /* U+0250 */ 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
+ /* U+0258 */ 0x0258, 0x0259, 0x025A, 0x025B, 0x025C, 0x025D, 0x025E, 0x025F,
+ /* U+0260 */ 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
+ /* U+0268 */ 0x0268, 0x0269, 0x026A, 0x026B, 0x026C, 0x026D, 0x026E, 0x026F,
+ /* U+0270 */ 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
+ /* U+0278 */ 0x0278, 0x0279, 0x027A, 0x027B, 0x027C, 0x027D, 0x027E, 0x027F,
+ /* U+0280 */ 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
+ /* U+0288 */ 0x0288, 0x0289, 0x028A, 0x028B, 0x028C, 0x028D, 0x028E, 0x028F,
+ /* U+0290 */ 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
+ /* U+0298 */ 0x0298, 0x0299, 0x029A, 0x029B, 0x029C, 0x029D, 0x029E, 0x029F,
+ /* U+02A0 */ 0x02A0, 0x02A1, 0x02A2, 0x02A3, 0x02A4, 0x02A5, 0x02A6, 0x02A7,
+ /* U+02A8 */ 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC, 0x02AD, 0x02AE, 0x02AF,
+ /* U+02B0 */ 0x0068, 0x0266, 0x006A, 0x0072, 0x0279, 0x027B, 0x0281, 0x0077,
+ /* U+02B8 */ 0x0079, 0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF,
+ /* U+02C0 */ 0x02C0, 0x02C1, 0x02C2, 0x02C3, 0x02C4, 0x02C5, 0x02C6, 0x02C7,
+ /* U+02C8 */ 0x02C8, 0x02C9, 0x02CA, 0x02CB, 0x02CC, 0x02CD, 0x02CE, 0x02CF,
+ /* U+02D0 */ 0x02D0, 0x02D1, 0x02D2, 0x02D3, 0x02D4, 0x02D5, 0x02D6, 0x02D7,
+ /* U+02D8 */ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02DE, 0x02DF,
+ /* U+02E0 */ 0x0263, 0x006C, 0x0073, 0x0078, 0x0295, 0x02E5, 0x02E6, 0x02E7,
+ /* U+02E8 */ 0x02E8, 0x02E9, 0x02EA, 0x02EB, 0x02EC, 0x02ED, 0x02EE, 0x02EF,
+ /* U+02F0 */ 0x02F0, 0x02F1, 0x02F2, 0x02F3, 0x02F4, 0x02F5, 0x02F6, 0x02F7,
+ /* U+02F8 */ 0x02F8, 0x02F9, 0x02FA, 0x02FB, 0x02FC, 0x02FD, 0x02FE, 0x02FF,
+ /* U+0300 */ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
+ /* U+0308 */ 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F,
+ /* U+0310 */ 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
+ /* U+0318 */ 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F,
+ /* U+0320 */ 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
+ /* U+0328 */ 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F,
+ /* U+0330 */ 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
+ /* U+0338 */ 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F,
+ /* U+0340 */ 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347,
+ /* U+0348 */ 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F,
+ /* U+0350 */ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
+ /* U+0358 */ 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F,
+ /* U+0360 */ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
+ /* U+0368 */ 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F,
+ /* U+0370 */ 0x0370, 0x0371, 0x0372, 0x0373, 0x02B9, 0x0375, 0x0376, 0x0377,
+ /* U+0378 */ 0x0378, 0x0379, 0x0020, 0x037B, 0x037C, 0x037D, 0x003B, 0x037F,
+ /* U+0380 */ 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00A8, 0x0391, 0x00B7,
+ /* U+0388 */ 0x0395, 0x0397, 0x0399, 0x038B, 0x039F, 0x038D, 0x03A5, 0x03A9,
+ /* U+0390 */ 0x03CA, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
+ /* U+0398 */ 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
+ /* U+03A0 */ 0x03A0, 0x03A1, 0x03A2, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7,
+ /* U+03A8 */ 0x03A8, 0x03A9, 0x0399, 0x03A5, 0x03B1, 0x03B5, 0x03B7, 0x03B9,
+ /* U+03B0 */ 0x03CB, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
+ /* U+03B8 */ 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
+ /* U+03C0 */ 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
+ /* U+03C8 */ 0x03C8, 0x03C9, 0x03B9, 0x03C5, 0x03BF, 0x03C5, 0x03C9, 0x03CF,
+ /* U+03D0 */ 0x03B2, 0x03B8, 0x03A5, 0x03D2, 0x03D2, 0x03C6, 0x03C0, 0x03D7,
+ /* U+03D8 */ 0x03D8, 0x03D9, 0x03DA, 0x03DB, 0x03DC, 0x03DD, 0x03DE, 0x03DF,
+ /* U+03E0 */ 0x03E0, 0x03E1, 0x03E2, 0x03E3, 0x03E4, 0x03E5, 0x03E6, 0x03E7,
+ /* U+03E8 */ 0x03E8, 0x03E9, 0x03EA, 0x03EB, 0x03EC, 0x03ED, 0x03EE, 0x03EF,
+ /* U+03F0 */ 0x03BA, 0x03C1, 0x03C2, 0x03F3, 0x0398, 0x03B5, 0x03F6, 0x03F7,
+ /* U+03F8 */ 0x03F8, 0x03A3, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF,
+ /* U+0400 */ 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406,
+ /* U+0408 */ 0x0408, 0x0409, 0x040A, 0x040B, 0x041A, 0x0418, 0x0423, 0x040F,
+ /* U+0410 */ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
+ /* U+0418 */ 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+ // U+0419: Manually changed from 0418 to 0419
+ /* U+0420 */ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
+ /* U+0428 */ 0x0428, 0x0429, 0x042C, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+ // U+042A: Manually changed from 042A to 042C
+ /* U+0430 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
+ /* U+0438 */ 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+ // U+0439: Manually changed from 0438 to 0439
+ /* U+0440 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+ /* U+0448 */ 0x0448, 0x0449, 0x044C, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+ // U+044A: Manually changed from 044A to 044C
+ /* U+0450 */ 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
+ /* U+0458 */ 0x0458, 0x0459, 0x045A, 0x045B, 0x043A, 0x0438, 0x0443, 0x045F,
+ /* U+0460 */ 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467,
+ /* U+0468 */ 0x0468, 0x0469, 0x046A, 0x046B, 0x046C, 0x046D, 0x046E, 0x046F,
+ /* U+0470 */ 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475,
+ /* U+0478 */ 0x0478, 0x0479, 0x047A, 0x047B, 0x047C, 0x047D, 0x047E, 0x047F,
+ /* U+0480 */ 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
+ /* U+0488 */ 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048D, 0x048E, 0x048F,
+ /* U+0490 */ 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497,
+ /* U+0498 */ 0x0498, 0x0499, 0x049A, 0x049B, 0x049C, 0x049D, 0x049E, 0x049F,
+ /* U+04A0 */ 0x04A0, 0x04A1, 0x04A2, 0x04A3, 0x04A4, 0x04A5, 0x04A6, 0x04A7,
+ /* U+04A8 */ 0x04A8, 0x04A9, 0x04AA, 0x04AB, 0x04AC, 0x04AD, 0x04AE, 0x04AF,
+ /* U+04B0 */ 0x04B0, 0x04B1, 0x04B2, 0x04B3, 0x04B4, 0x04B5, 0x04B6, 0x04B7,
+ /* U+04B8 */ 0x04B8, 0x04B9, 0x04BA, 0x04BB, 0x04BC, 0x04BD, 0x04BE, 0x04BF,
+ /* U+04C0 */ 0x04C0, 0x0416, 0x0436, 0x04C3, 0x04C4, 0x04C5, 0x04C6, 0x04C7,
+ /* U+04C8 */ 0x04C8, 0x04C9, 0x04CA, 0x04CB, 0x04CC, 0x04CD, 0x04CE, 0x04CF,
+ /* U+04D0 */ 0x0410, 0x0430, 0x0410, 0x0430, 0x04D4, 0x04D5, 0x0415, 0x0435,
+ /* U+04D8 */ 0x04D8, 0x04D9, 0x04D8, 0x04D9, 0x0416, 0x0436, 0x0417, 0x0437,
+ /* U+04E0 */ 0x04E0, 0x04E1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041E, 0x043E,
+ /* U+04E8 */ 0x04E8, 0x04E9, 0x04E8, 0x04E9, 0x042D, 0x044D, 0x0423, 0x0443,
+ /* U+04F0 */ 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04F6, 0x04F7,
+ /* U+04F8 */ 0x042B, 0x044B, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF,
};
-
- // generated with:
- // cat UnicodeData.txt | perl -e 'while (<>) { @foo = split(/;/); $foo[5] =~ s/<.*> //; $base[hex($foo[0])] = hex($foo[5]);} for ($i = 0; $i < 0x500; $i += 8) { for ($j = $i; $j < $i + 8; $j++) { printf("0x%04x, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }'
-
}
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index dd58db575..21b103e5a 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -20,6 +20,9 @@ import android.text.InputType;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+
/**
* Class to hold attributes of the input field.
*/
@@ -199,6 +202,6 @@ public final class InputAttributes {
if (editorInfo == null) return false;
final String findingKey = (packageName != null) ? packageName + "." + key
: key;
- return StringUtils.containsInCsv(findingKey, editorInfo.privateImeOptions);
+ return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions);
}
}
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index 81c833000..e96a46e12 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.utils.ResizableIntArray;
import android.util.Log;
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 826dc11e7..642b3a4da 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.utils.StringUtils;
+
import android.text.TextUtils;
/**
@@ -47,7 +49,7 @@ public final class LastComposedWord {
public final String mPrevWord;
public final int mCapitalizedMode;
public final InputPointers mInputPointers =
- new InputPointers(Constants.Dictionary.MAX_WORD_LENGTH);
+ new InputPointers(Constants.DICTIONARY_MAX_WORD_LENGTH);
private boolean mActive;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index c464a7067..719c7c81f 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -43,7 +43,6 @@ import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
-import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.Log;
@@ -74,11 +73,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.latin.RichInputConnection.Range;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.Utils.Stats;
import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionaryHelper;
+import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
+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.utils.ApplicationUtils;
+import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
+import com.android.inputmethod.latin.utils.CapsModeUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CompletionInfoUtils;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.IntentUtils;
+import com.android.inputmethod.latin.utils.JniUtils;
+import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
+import com.android.inputmethod.latin.utils.PositionalInfoForUserDictPendingAddition;
+import com.android.inputmethod.latin.utils.RecapitalizeStatus;
+import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
+import com.android.inputmethod.latin.utils.TextRange;
import com.android.inputmethod.research.ResearchLogger;
import java.io.FileDescriptor;
@@ -139,7 +156,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private SuggestionStripView mSuggestionStripView;
// Never null
private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
- @UsedForTesting Suggest mSuggest;
+ private Suggest mSuggest;
private CompletionInfo[] mApplicationSpecifiedCompletions;
private AppWorkaroundsUtils mAppWorkAroundsUtils = new AppWorkaroundsUtils();
@@ -153,7 +170,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private boolean mIsMainDictionaryAvailable;
private UserBinaryDictionary mUserDictionary;
- private UserHistoryDictionary mUserHistoryDictionary;
+ private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary;
+ private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary;
private boolean mIsUserDictionaryAvailable;
private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
@@ -179,11 +197,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private int mDisplayOrientation;
// Object for reacting to adding/removing a dictionary pack.
- // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
- // Service yet.
private BroadcastReceiver mDictionaryPackInstallReceiver =
- ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS
- ? null : new DictionaryPackInstallBroadcastReceiver(this);
+ new DictionaryPackInstallBroadcastReceiver(this);
// Keeps track of most recently inserted text (multi-character key) for reverting
private String mEnteredText;
@@ -204,6 +219,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3;
private static final int MSG_RESUME_SUGGESTIONS = 4;
+ private static final int MSG_REOPEN_DICTIONARIES = 5;
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
@@ -244,6 +260,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
case MSG_RESUME_SUGGESTIONS:
latinIme.restartSuggestionsOnWordTouchedByCursor();
break;
+ case MSG_REOPEN_DICTIONARIES:
+ latinIme.initSuggest();
+ // In theory we could call latinIme.updateSuggestionStrip() right away, but
+ // in the practice, the dictionary is not finished opening yet so we wouldn't
+ // get any suggestions. Wait one frame.
+ postUpdateSuggestionStrip();
+ break;
}
}
@@ -251,6 +274,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions);
}
+ public void postReopenDictionaries() {
+ sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES));
+ }
+
public void postResumeSuggestions() {
removeMessages(MSG_RESUME_SUGGESTIONS);
sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
@@ -264,6 +291,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return hasMessages(MSG_UPDATE_SUGGESTION_STRIP);
}
+ public boolean hasPendingReopenDictionaries() {
+ return hasMessages(MSG_REOPEN_DICTIONARIES);
+ }
+
public void postUpdateShiftState() {
removeMessages(MSG_UPDATE_SHIFT_STATE);
sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), mDelayUpdateShiftState);
@@ -416,6 +447,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
+ // Loading the native library eagerly to avoid unexpected UnsatisfiedLinkError at the initial
+ // JNI call as much as possible.
+ static {
+ JniUtils.loadNativeLibrary();
+ }
+
public LatinIME() {
super();
mSettings = Settings.getInstance();
@@ -458,19 +495,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
registerReceiver(mReceiver, filter);
- // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
- // Service yet.
- if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final IntentFilter packageFilter = new IntentFilter();
- packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- packageFilter.addDataScheme(SCHEME_PACKAGE);
- registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
+ final IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addDataScheme(SCHEME_PACKAGE);
+ registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
- final IntentFilter newDictFilter = new IntentFilter();
- newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
- registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
- }
+ final IntentFilter newDictFilter = new IntentFilter();
+ newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
+ registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
}
// Has to be package-visible for unit tests
@@ -480,8 +513,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final InputAttributes inputAttributes =
new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode());
mSettings.loadSettings(locale, inputAttributes);
- // May need to reset the contacts dictionary depending on the user settings.
- resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
+ AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(mSettings.getCurrent());
+ // To load the keyboard we need to load all the settings once, but resetting the
+ // contacts dictionary should be deferred until after the new layout has been displayed
+ // to improve responsivity. In the language switching process, we post a reopenDictionaries
+ // message, then come here to read the settings for the new language before we change
+ // the layout; at this time, we need to skip resetting the contacts dictionary. It will
+ // be done later inside {@see #initSuggest()} when the reopenDictionaries message is
+ // processed.
+ if (!mHandler.hasPendingReopenDictionaries()) {
+ // May need to reset the contacts dictionary depending on the user settings.
+ resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
+ }
}
// Note that this method is called from a non-UI thread.
@@ -498,33 +541,35 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
final String localeStr = subtypeLocale.toString();
- final ContactsBinaryDictionary oldContactsDictionary;
- if (mSuggest != null) {
- oldContactsDictionary = mSuggest.getContactsDictionary();
- mSuggest.close();
- } else {
- oldContactsDictionary = null;
- }
- mSuggest = new Suggest(this /* Context */, subtypeLocale,
+ final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale,
this /* SuggestInitializationListener */);
- if (mSettings.getCurrent().mCorrectionEnabled) {
- mSuggest.setAutoCorrectionThreshold(mSettings.getCurrent().mAutoCorrectionThreshold);
+ final SettingsValues settingsValues = mSettings.getCurrent();
+ if (settingsValues.mCorrectionEnabled) {
+ newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold);
}
mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().initSuggest(mSuggest);
+ ResearchLogger.getInstance().initSuggest(newSuggest);
}
mUserDictionary = new UserBinaryDictionary(this, localeStr);
mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
- mSuggest.setUserDictionary(mUserDictionary);
-
- resetContactsDictionary(oldContactsDictionary);
+ newSuggest.setUserDictionary(mUserDictionary);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- mUserHistoryDictionary = UserHistoryDictionary.getInstance(this, localeStr, prefs);
- mSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
+
+ mUserHistoryPredictionDictionary = PersonalizationDictionaryHelper
+ .getUserHistoryPredictionDictionary(this, localeStr, prefs);
+ newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary);
+ mPersonalizationPredictionDictionary = PersonalizationDictionaryHelper
+ .getPersonalizationPredictionDictionary(this, localeStr, prefs);
+ newSuggest.setPersonalizationPredictionDictionary(mPersonalizationPredictionDictionary);
+
+ final Suggest oldSuggest = mSuggest;
+ resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null);
+ mSuggest = newSuggest;
+ if (oldSuggest != null) oldSuggest.close();
}
/**
@@ -536,8 +581,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
* @param oldContactsDictionary an optional dictionary to use, or null
*/
private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary) {
+ final Suggest suggest = mSuggest;
final boolean shouldSetDictionary =
- (null != mSuggest && mSettings.getCurrent().mUseContactsDict);
+ (null != suggest && mSettings.getCurrent().mUseContactsDict);
final ContactsBinaryDictionary dictionaryToUse;
if (!shouldSetDictionary) {
@@ -564,8 +610,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- if (null != mSuggest) {
- mSuggest.setContactsDictionary(dictionaryToUse);
+ if (null != suggest) {
+ suggest.setContactsDictionary(dictionaryToUse);
}
}
@@ -577,8 +623,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onDestroy() {
- if (mSuggest != null) {
- mSuggest.close();
+ final Suggest suggest = mSuggest;
+ if (suggest != null) {
+ suggest.close();
mSuggest = null;
}
mSettings.onDestroy();
@@ -586,11 +633,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().onDestroy();
}
- // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
- // Service yet.
- if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- unregisterReceiver(mDictionaryPackInstallReceiver);
- }
+ unregisterReceiver(mDictionaryPackInstallReceiver);
LatinImeLogger.commit();
LatinImeLogger.onDestroy();
super.onDestroy();
@@ -676,7 +719,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
super.onStartInputView(editorInfo, restarting);
final KeyboardSwitcher switcher = mKeyboardSwitcher;
final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
- final SettingsValues currentSettings = mSettings.getCurrent();
+ // If we are starting input in a different text field from before, we'll have to reload
+ // settings, so currentSettingsValues can't be final.
+ SettingsValues currentSettingsValues = mSettings.getCurrent();
if (editorInfo == null) {
Log.e(TAG, "Null EditorInfo in onStartInputView()");
@@ -731,7 +776,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
}
- final boolean inputTypeChanged = !currentSettings.isSameInputType(editorInfo);
+ final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo);
final boolean isDifferentTextField = !restarting || inputTypeChanged;
if (isDifferentTextField) {
mSubtypeSwitcher.updateParametersOnStartInputView();
@@ -746,6 +791,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// span, so we should reset our state unconditionally, even if restarting is true.
mEnteredText = null;
resetComposingState(true /* alsoResetLastComposedWord */);
+ if (isDifferentTextField) mHandler.postResumeSuggestions();
mDeleteCount = 0;
mSpaceState = SPACE_STATE_NONE;
mRecapitalizeStatus.deactivate();
@@ -753,7 +799,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Note: the following does a round-trip IPC on the main thread: be careful
final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- if (null != mSuggest && null != currentLocale && !currentLocale.equals(mSuggest.mLocale)) {
+ final Suggest suggest = mSuggest;
+ if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) {
initSuggest();
}
if (mSuggestionStripView != null) {
@@ -769,12 +816,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (isDifferentTextField) {
mainKeyboardView.closing();
loadSettings();
+ currentSettingsValues = mSettings.getCurrent();
- if (mSuggest != null && currentSettings.mCorrectionEnabled) {
- mSuggest.setAutoCorrectionThreshold(currentSettings.mAutoCorrectionThreshold);
+ if (suggest != null && currentSettingsValues.mCorrectionEnabled) {
+ suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
}
- switcher.loadKeyboard(editorInfo, currentSettings);
+ switcher.loadKeyboard(editorInfo, currentSettingsValues);
} else if (restarting) {
// TODO: Come up with a more comprehensive way to reset the keyboard layout when
// a keyboard layout set doesn't get reloaded in this method.
@@ -795,14 +843,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelDoubleSpacePeriodTimer();
mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable);
- mainKeyboardView.setKeyPreviewPopupEnabled(currentSettings.mKeyPreviewPopupOn,
- currentSettings.mKeyPreviewPopupDismissDelay);
+ mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
+ currentSettingsValues.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setSlidingKeyInputPreviewEnabled(
- currentSettings.mSlidingKeyInputPreviewEnabled);
+ currentSettingsValues.mSlidingKeyInputPreviewEnabled);
mainKeyboardView.setGestureHandlingEnabledByUser(
- currentSettings.mGestureInputEnabled);
- mainKeyboardView.setGesturePreviewMode(currentSettings.mGesturePreviewTrailEnabled,
- currentSettings.mGestureFloatingPreviewTextEnabled);
+ currentSettingsValues.mGestureInputEnabled);
+ mainKeyboardView.setGesturePreviewMode(currentSettingsValues.mGesturePreviewTrailEnabled,
+ currentSettingsValues.mGestureFloatingPreviewTextEnabled);
// If we have a user dictionary addition in progress, we should check now if we should
// replace the previously committed string with the word that has actually been added
@@ -850,11 +898,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mKeyboardSwitcher.onFinishInputView();
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
- mainKeyboardView.cancelAllMessages();
+ mainKeyboardView.cancelAllOngoingEvents();
+ mainKeyboardView.deallocateMemory();
}
// Remove pending messages related to update suggestions
mHandler.cancelUpdateSuggestionStrip();
+ // Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
+ if (mWordComposer.isComposingWord()) mConnection.finishComposingText();
resetComposingState(true /* alsoResetLastComposedWord */);
+ mRichImm.clearSubtypeCaches();
// Notify ResearchLogger
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, mLastSelectionStart,
@@ -891,20 +943,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- // TODO: refactor the following code to be less contrived.
- // "newSelStart != composingSpanEnd" || "newSelEnd != composingSpanEnd" means
- // that the cursor is not at the end of the composing span, or there is a selection.
- // "mLastSelectionStart != newSelStart" means that the cursor is not in the same place
- // as last time we were called (if there is a selection, it means the start hasn't
- // changed, so it's the end that did).
- final boolean selectionChanged = (newSelStart != composingSpanEnd
- || newSelEnd != composingSpanEnd) && mLastSelectionStart != newSelStart;
+ final boolean selectionChanged = mLastSelectionStart != newSelStart
+ || mLastSelectionEnd != newSelEnd;
+
// if composingSpanStart and composingSpanEnd are -1, it means there is no composing
// span in the view - we can use that to narrow down whether the cursor was moved
// by us or not. If we are composing a word but there is no composing span, then
// we know for sure the cursor moved while we were composing and we should reset
- // the state.
+ // the state. TODO: rescind this policy: the framework never removes the composing
+ // span on its own accord while editing. This test is useless.
final boolean noComposingSpan = composingSpanStart == -1 && composingSpanEnd == -1;
+
// If the keyboard is not visible, we don't need to do all the housekeeping work, as it
// will be reset when the keyboard shows up anyway.
// TODO: revisit this when LatinIME supports hardware keyboards.
@@ -926,7 +975,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// state-related special processing to kick in.
mSpaceState = SPACE_STATE_NONE;
- if ((!mWordComposer.isComposingWord()) || selectionChanged || noComposingSpan) {
+ // TODO: is it still necessary to test for composingSpan related stuff?
+ final boolean selectionChangedOrSafeToReset = selectionChanged
+ || (!mWordComposer.isComposingWord()) || noComposingSpan;
+ final boolean hasOrHadSelection = (oldSelStart != oldSelEnd
+ || newSelStart != newSelEnd);
+ final int moveAmount = newSelStart - oldSelStart;
+ if (selectionChangedOrSafeToReset && (hasOrHadSelection
+ || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
// If we are composing a word and moving the cursor, we would want to set a
// suggestion span for recorrection to work correctly. Unfortunately, that
// would involve the keyboard committing some new text, which would move the
@@ -937,6 +993,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// text, but that is probably too expensive to do, so we decided to leave things
// as is.
resetEntireInputState(newSelStart);
+ } else {
+ // resetEntireInputState calls resetCachesUponCursorMove, but with the second
+ // argument as true. But in all cases where we don't reset the entire input state,
+ // we still want to tell the rich input connection about the new cursor position so
+ // that it can update its caches.
+ mConnection.resetCachesUponCursorMove(newSelStart,
+ false /* shouldFinishComposition */);
}
// We moved the cursor. If we are touching a word, we need to resume suggestion,
@@ -1160,10 +1223,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void resetEntireInputState(final int newCursorPosition) {
final boolean shouldFinishComposition = mWordComposer.isComposingWord();
resetComposingState(true /* alsoResetLastComposedWord */);
- if (mSettings.getCurrent().mBigramPredictionEnabled) {
+ final SettingsValues settingsValues = mSettings.getCurrent();
+ if (settingsValues.mBigramPredictionEnabled) {
clearSuggestionStrip();
} else {
- setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false);
+ setSuggestedWords(settingsValues.mSuggestPuncList, false);
}
mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition);
}
@@ -1237,8 +1301,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private boolean maybeDoubleSpacePeriod() {
- if (!mSettings.getCurrent().mCorrectionEnabled) return false;
- if (!mSettings.getCurrent().mUseDoubleSpacePeriod) return false;
+ final SettingsValues settingsValues = mSettings.getCurrent();
+ if (!settingsValues.mCorrectionEnabled) return false;
+ if (!settingsValues.mUseDoubleSpacePeriod) return false;
if (!mHandler.isAcceptingDoubleSpacePeriod()) return false;
final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0);
if (lastThree != null && lastThree.length() == 3
@@ -1314,14 +1379,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
showSubtypeSelectorAndSettings();
}
- // Virtual codes representing custom requests. These are used in onCustomRequest() below.
- public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1;
-
@Override
public boolean onCustomRequest(final int requestCode) {
if (isShowingOptionDialog()) return false;
switch (requestCode) {
- case CODE_SHOW_INPUT_METHOD_PICKER:
+ case Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER:
if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
mRichImm.getInputMethodManager().showInputMethodPicker();
return true;
@@ -1418,8 +1480,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
LatinImeLogger.logOnDelete(x, y);
break;
case Constants.CODE_SHIFT:
- // Note: calling back to the keyboard on Shift key is handled in onPressKey()
- // and onReleaseKey().
+ // Note: Calling back to the keyboard on Shift key is handled in
+ // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}.
final Keyboard currentKeyboard = switcher.getKeyboard();
if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
// TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
@@ -1427,9 +1489,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
handleRecapitalize();
}
break;
+ case Constants.CODE_CAPSLOCK:
+ // Note: Changing keyboard to shift lock state is handled in
+ // {@link KeyboardSwitcher#onCodeInput(int)}.
+ break;
case Constants.CODE_SWITCH_ALPHA_SYMBOL:
- // Note: calling back to the keyboard on symbol key is handled in onPressKey()
- // and onReleaseKey().
+ // Note: Calling back to the keyboard on symbol key is handled in
+ // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}.
break;
case Constants.CODE_SETTINGS:
onSettingsKeyPressed();
@@ -1482,8 +1548,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
break;
}
switcher.onCodeInput(primaryCode);
- // Reset after any single keystroke, except shift and symbol-shift
+ // Reset after any single keystroke, except shift, capslock, and symbol-shift
if (!didAutoCorrect && primaryCode != Constants.CODE_SHIFT
+ && primaryCode != Constants.CODE_CAPSLOCK
&& primaryCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
mLastComposedWord.deactivate();
if (Constants.CODE_DELETE != primaryCode) {
@@ -1496,14 +1563,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final int spaceState) {
mSpaceState = SPACE_STATE_NONE;
final boolean didAutoCorrect;
- if (mSettings.getCurrent().isWordSeparator(primaryCode)) {
+ final SettingsValues settingsValues = mSettings.getCurrent();
+ if (settingsValues.isWordSeparator(primaryCode)) {
didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
} else {
didAutoCorrect = false;
if (SPACE_STATE_PHANTOM == spaceState) {
- if (mSettings.isInternal()) {
+ if (settingsValues.mIsInternal) {
if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
- Stats.onAutoCorrection(
+ LatinImeLoggerUtils.onAutoCorrection(
"", mWordComposer.getTypedWord(), " ", mWordComposer);
}
}
@@ -1561,10 +1629,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
BatchInputUpdater.getInstance().onStartBatchInput(this);
mHandler.cancelUpdateSuggestionStrip();
mConnection.beginBatchEdit();
+ final SettingsValues settingsValues = mSettings.getCurrent();
if (mWordComposer.isComposingWord()) {
- if (mSettings.isInternal()) {
+ if (settingsValues.mIsInternal) {
if (mWordComposer.isBatchMode()) {
- Stats.onAutoCorrection("", mWordComposer.getTypedWord(), " ", mWordComposer);
+ LatinImeLoggerUtils.onAutoCorrection(
+ "", mWordComposer.getTypedWord(), " ", mWordComposer);
}
}
final int wordComposerSize = mWordComposer.size();
@@ -1590,7 +1660,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
if (Character.isLetterOrDigit(codePointBeforeCursor)
- || mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+ || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
mSpaceState = SPACE_STATE_PHANTOM;
}
mConnection.endBatchEdit();
@@ -1635,8 +1705,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void onStartBatchInput(final LatinIME latinIme) {
synchronized (mLock) {
mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
- mLatinIme = latinIme;
mInBatchInput = true;
+ mLatinIme = latinIme;
+ mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+ SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
}
}
@@ -1795,8 +1867,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
final String word = mWordComposer.getTypedWord();
ResearchLogger.latinIME_handleBackspace_batch(word, 1);
- ResearchLogger.getInstance().uncommitCurrentLogUnit(
- word, false /* dumpCurrentLogUnit */);
}
final String rejectedSuggestion = mWordComposer.getTypedWord();
mWordComposer.reset();
@@ -1810,9 +1880,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mConnection.deleteSurroundingText(1, 0);
}
} else {
+ final SettingsValues currentSettings = mSettings.getCurrent();
if (mLastComposedWord.canRevertCommit()) {
- if (mSettings.isInternal()) {
- Stats.onAutoCorrectionCancellation();
+ if (currentSettings.mIsInternal) {
+ LatinImeLoggerUtils.onAutoCorrectionCancellation();
}
revertCommit();
return;
@@ -1823,6 +1894,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// like the smiley key or the .com key.
final int length = mEnteredText.length();
mConnection.deleteSurroundingText(length, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText);
+ }
mEnteredText = null;
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
// In addition we know that spaceState is false, and that we should not be
@@ -1856,7 +1930,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mLastSelectionEnd = mLastSelectionStart;
mConnection.deleteSurroundingText(numCharsDeleted, 0);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(numCharsDeleted);
+ ResearchLogger.latinIME_handleBackspace(numCharsDeleted,
+ false /* shouldUncommitLogUnit */);
}
} else {
// There is no selection, just delete one character.
@@ -1874,16 +1949,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mConnection.deleteSurroundingText(1, 0);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(1);
+ ResearchLogger.latinIME_handleBackspace(1, true /* shouldUncommitLogUnit */);
}
if (mDeleteCount > DELETE_ACCELERATE_AT) {
mConnection.deleteSurroundingText(1, 0);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(1);
+ ResearchLogger.latinIME_handleBackspace(1,
+ true /* shouldUncommitLogUnit */);
}
}
}
- if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
+ if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
restartSuggestionsOnWordBeforeCursorIfAtEndOfWord();
}
}
@@ -1900,8 +1976,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
&& isFromSuggestionStrip) {
- if (mSettings.getCurrent().isUsuallyPrecededBySpace(code)) return false;
- if (mSettings.getCurrent().isUsuallyFollowedBySpace(code)) return true;
+ final SettingsValues currentSettings = mSettings.getCurrent();
+ if (currentSettings.isUsuallyPrecededBySpace(code)) return false;
+ if (currentSettings.isUsuallyFollowedBySpace(code)) return true;
mConnection.removeTrailingSpace();
}
return false;
@@ -1913,8 +1990,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
// See onStartBatchInput() to see how to do it.
- if (SPACE_STATE_PHANTOM == spaceState &&
- !mSettings.getCurrent().isWordConnector(primaryCode)) {
+ final SettingsValues currentSettings = mSettings.getCurrent();
+ if (SPACE_STATE_PHANTOM == spaceState && !currentSettings.isWordConnector(primaryCode)) {
if (isComposingWord) {
// Sanity check
throw new RuntimeException("Should not be composing here");
@@ -1932,9 +2009,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
// thread here.
if (!isComposingWord && (isAlphabet(primaryCode)
- || mSettings.getCurrent().isWordConnector(primaryCode))
- && mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation) &&
- !mConnection.isCursorTouchingWord(mSettings.getCurrent())) {
+ || currentSettings.isWordConnector(primaryCode))
+ && currentSettings.isSuggestionsRequested(mDisplayOrientation) &&
+ !mConnection.isCursorTouchingWord(currentSettings)) {
// Reset entirely the composing state anyway, then start composing a new word unless
// the character is a single quote. The idea here is, single quote is not a
// separator and it should be treated as a normal character, except in the first
@@ -1977,8 +2054,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (null != mSuggestionStripView) mSuggestionStripView.dismissAddToDictionaryHint();
}
mHandler.postUpdateSuggestionStrip();
- if (mSettings.isInternal()) {
- Utils.Stats.onNonSeparator((char)primaryCode, x, y);
+ if (currentSettings.mIsInternal) {
+ LatinImeLoggerUtils.onNonSeparator((char)primaryCode, x, y);
}
}
@@ -1990,9 +2067,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final CharSequence selectedText =
mConnection.getSelectedText(0 /* flags, 0 for no styles */);
if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection
+ final SettingsValues currentSettings = mSettings.getCurrent();
mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd,
- selectedText.toString(), mSettings.getCurrentLocale(),
- mSettings.getWordSeparators());
+ selectedText.toString(), currentSettings.mLocale,
+ currentSettings.mWordSeparators);
// We trim leading and trailing whitespace.
mRecapitalizeStatus.trim();
// Trimming the object may have changed the length of the string, and we need to
@@ -2020,17 +2098,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Returns true if we did an autocorrection, false otherwise.
private boolean handleSeparator(final int primaryCode, final int x, final int y,
final int spaceState) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
- }
boolean didAutoCorrect = false;
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the separator at the current cursor position.
resetEntireInputState(mLastSelectionStart);
}
+ final SettingsValues currentSettings = mSettings.getCurrent();
if (mWordComposer.isComposingWord()) {
- if (mSettings.getCurrent().mCorrectionEnabled) {
+ if (currentSettings.mCorrectionEnabled) {
// TODO: maybe cache Strings in an <String> sparse array or something
commitCurrentAutoCorrection(new String(new int[]{primaryCode}, 0, 1));
didAutoCorrect = true;
@@ -2043,13 +2119,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
Constants.SUGGESTION_STRIP_COORDINATE == x);
if (SPACE_STATE_PHANTOM == spaceState &&
- mSettings.getCurrent().isUsuallyPrecededBySpace(primaryCode)) {
+ currentSettings.isUsuallyPrecededBySpace(primaryCode)) {
promotePhantomSpace();
}
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
+ }
sendKeyCodePoint(primaryCode);
if (Constants.CODE_SPACE == primaryCode) {
- if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
+ if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
if (maybeDoubleSpacePeriod()) {
mSpaceState = SPACE_STATE_DOUBLE;
} else if (!isShowingPunctuationList()) {
@@ -2064,7 +2143,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
swapSwapperAndSpace();
mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
} else if (SPACE_STATE_PHANTOM == spaceState
- && mSettings.getCurrent().isUsuallyFollowedBySpace(primaryCode)) {
+ && currentSettings.isUsuallyFollowedBySpace(primaryCode)) {
// If we are in phantom space state, and the user presses a separator, we want to
// stay in phantom space state so that the next keypress has a chance to add the
// space. For example, if I type "Good dat", pick "day" from the suggestion strip
@@ -2082,8 +2161,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// already displayed or not, so it's okay.
setPunctuationSuggestions();
}
- if (mSettings.isInternal()) {
- Utils.Stats.onSeparator((char)primaryCode, x, y);
+ if (currentSettings.mIsInternal) {
+ LatinImeLoggerUtils.onSeparator((char)primaryCode, x, y);
}
mKeyboardSwitcher.updateShiftState();
@@ -2115,17 +2194,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private boolean isSuggestionsStripVisible() {
+ final SettingsValues currentSettings = mSettings.getCurrent();
if (mSuggestionStripView == null)
return false;
if (mSuggestionStripView.isShowingAddToDictionaryHint())
return true;
- if (null == mSettings.getCurrent())
+ if (null == currentSettings)
return false;
- if (!mSettings.getCurrent().isSuggestionStripVisibleInOrientation(mDisplayOrientation))
+ if (!currentSettings.isSuggestionStripVisibleInOrientation(mDisplayOrientation))
return false;
- if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn())
+ if (currentSettings.isApplicationSpecifiedCompletionsOn())
return true;
- return mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation);
+ return currentSettings.isSuggestionsRequested(mDisplayOrientation);
}
private void clearSuggestionStrip() {
@@ -2158,10 +2238,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void updateSuggestionStrip() {
mHandler.cancelUpdateSuggestionStrip();
+ final SettingsValues currentSettings = mSettings.getCurrent();
// Check if we have a suggestion engine attached.
if (mSuggest == null
- || !mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
+ || !currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
if (mWordComposer.isComposingWord()) {
Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
+ "requested!");
@@ -2169,7 +2250,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
- if (!mWordComposer.isComposingWord() && !mSettings.getCurrent().mBigramPredictionEnabled) {
+ if (!mWordComposer.isComposingWord() && !currentSettings.mBigramPredictionEnabled) {
setPunctuationSuggestions();
return;
}
@@ -2182,19 +2263,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private SuggestedWords getSuggestedWords(final int sessionId) {
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
- if (keyboard == null || mSuggest == null) {
+ final Suggest suggest = mSuggest;
+ if (keyboard == null || suggest == null) {
return SuggestedWords.EMPTY;
}
// 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.
// TODO: this is slow (2-way IPC) - we should probably cache this instead.
+ final SettingsValues currentSettings = mSettings.getCurrent();
final String prevWord =
- mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators,
+ mConnection.getNthPreviousWord(currentSettings.mWordSeparators,
mWordComposer.isComposingWord() ? 2 : 1);
- return mSuggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
- mSettings.getBlockPotentiallyOffensive(),
- mSettings.getCurrent().mCorrectionEnabled, sessionId);
+ return suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
+ currentSettings.mBlockPotentiallyOffensive,
+ currentSettings.mCorrectionEnabled, sessionId);
}
private SuggestedWords getSuggestedWordsOrOlderSuggestions(final int sessionId) {
@@ -2272,7 +2355,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ "is empty? Impossible! I must commit suicide.");
}
if (mSettings.isInternal()) {
- Stats.onAutoCorrection(typedWord, autoCorrection, separatorString, mWordComposer);
+ LatinImeLoggerUtils.onAutoCorrection(
+ typedWord, autoCorrection, separatorString, mWordComposer);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
final SuggestedWords suggestedWords = mSuggestedWords;
@@ -2319,18 +2403,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mConnection.beginBatchEdit();
+ final SettingsValues currentSettings = mSettings.getCurrent();
if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0
// In the batch input mode, a manually picked suggested word should just replace
// the current batch input text and there is no need for a phantom space.
&& !mWordComposer.isBatchMode()) {
final int firstChar = Character.codePointAt(suggestion, 0);
- if (!mSettings.getCurrent().isWordSeparator(firstChar)
- || mSettings.getCurrent().isUsuallyPrecededBySpace(firstChar)) {
+ if (!currentSettings.isWordSeparator(firstChar)
+ || currentSettings.isUsuallyPrecededBySpace(firstChar)) {
promotePhantomSpace();
}
}
- if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()
+ if (currentSettings.isApplicationSpecifiedCompletionsOn()
&& mApplicationSpecifiedCompletions != null
&& index >= 0 && index < mApplicationSpecifiedCompletions.length) {
mSuggestedWords = SuggestedWords.EMPTY;
@@ -2354,7 +2439,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
LastComposedWord.NOT_A_SEPARATOR);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
- mWordComposer.isBatchMode());
+ mWordComposer.isBatchMode(), suggestionInfo.mScore, suggestionInfo.mKind,
+ suggestionInfo.mSourceDict);
}
mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
@@ -2367,18 +2453,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// AND it's in none of our current dictionaries (main, user or otherwise).
// Please note that if mSuggest is null, it means that everything is off: suggestion
// and correction, so we shouldn't try to show the hint
+ final Suggest suggest = mSuggest;
final boolean showingAddToDictionaryHint =
- SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind && mSuggest != null
- // If the suggestion is not in the dictionary, the hint should be shown.
- && !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
-
- if (mSettings.isInternal()) {
- Stats.onSeparator((char)Constants.CODE_SPACE,
+ (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
+ || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
+ && suggest != null
+ // If the suggestion is not in the dictionary, the hint should be shown.
+ && !AutoCorrectionUtils.isValidWord(suggest, suggestion, true);
+
+ if (currentSettings.mIsInternal) {
+ LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
mSuggestionStripView.showAddToDictionaryHint(
- suggestion, mSettings.getCurrent().mHintToSaveText);
+ suggestion, currentSettings.mHintToSaveText);
} else {
// If we're not showing the "Touch again to save", then update the suggestion strip.
mHandler.postUpdateSuggestionStrip();
@@ -2404,10 +2493,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void setPunctuationSuggestions() {
- if (mSettings.getCurrent().mBigramPredictionEnabled) {
+ final SettingsValues currentSettings = mSettings.getCurrent();
+ if (currentSettings.mBigramPredictionEnabled) {
clearSuggestionStrip();
} else {
- setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false);
+ setSuggestedWords(currentSettings.mSuggestPuncList, false);
}
setAutoCorrectionIndicator(false);
setSuggestionStripShown(isSuggestionsStripVisible());
@@ -2415,21 +2505,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private String addToUserHistoryDictionary(final String suggestion) {
if (TextUtils.isEmpty(suggestion)) return null;
- if (mSuggest == null) return null;
+ final Suggest suggest = mSuggest;
+ if (suggest == null) return null;
// If correction is not enabled, we don't add words to the user history dictionary.
// That's to avoid unintended additions in some sensitive fields, or fields that
// expect to receive non-words.
- if (!mSettings.getCurrent().mCorrectionEnabled) return null;
+ final SettingsValues currentSettings = mSettings.getCurrent();
+ if (!currentSettings.mCorrectionEnabled) return null;
- final Suggest suggest = mSuggest;
- final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
- if (suggest == null || userHistoryDictionary == null) {
- // Avoid concurrent issue
- return null;
- }
- final String prevWord
- = mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators, 2);
+ final UserHistoryPredictionDictionary userHistoryPredictionDictionary =
+ mUserHistoryPredictionDictionary;
+ if (userHistoryPredictionDictionary == null) return null;
+
+ final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2);
final String secondWord;
if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
@@ -2438,10 +2527,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
// We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
// We don't add words with 0-frequency (assuming they would be profanity etc.).
- final int maxFreq = AutoCorrection.getMaxFrequency(
+ final int maxFreq = AutoCorrectionUtils.getMaxFrequency(
suggest.getUnigramDictionaries(), suggestion);
if (maxFreq == 0) return null;
- userHistoryDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0);
+ userHistoryPredictionDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0);
return prevWord;
}
@@ -2458,35 +2547,34 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mLastSelectionStart != mLastSelectionEnd) return;
// If we don't know the cursor location, return.
if (mLastSelectionStart < 0) return;
- if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return;
- final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
+ final SettingsValues currentSettings = mSettings.getCurrent();
+ if (!mConnection.isCursorTouchingWord(currentSettings)) return;
+ final TextRange range = mConnection.getWordRangeAtCursor(currentSettings.mWordSeparators,
0 /* additionalPrecedingWordsCount */);
if (null == range) return; // Happens if we don't have an input connection at all
// If for some strange reason (editor bug or so) we measure the text before the cursor as
// longer than what the entire text is supposed to be, the safe thing to do is bail out.
- if (range.mCharsBefore > mLastSelectionStart) return;
+ final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor();
+ if (numberOfCharsInWordBeforeCursor > mLastSelectionStart) return;
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
final String typedWord = range.mWord.toString();
- if (range.mWord instanceof SpannableString) {
- final SpannableString spannableString = (SpannableString)range.mWord;
- int i = 0;
- for (Object object : spannableString.getSpans(0, spannableString.length(),
- SuggestionSpan.class)) {
- SuggestionSpan span = (SuggestionSpan)object;
- for (String s : span.getSuggestions()) {
- ++i;
- if (!TextUtils.equals(s, typedWord)) {
- suggestions.add(new SuggestedWordInfo(s,
- SuggestionStripView.MAX_SUGGESTIONS - i,
- SuggestedWordInfo.KIND_RESUMED, Dictionary.TYPE_RESUMED));
- }
+ int i = 0;
+ for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) {
+ for (final String s : span.getSuggestions()) {
+ ++i;
+ if (!TextUtils.equals(s, typedWord)) {
+ suggestions.add(new SuggestedWordInfo(s,
+ SuggestionStripView.MAX_SUGGESTIONS - i,
+ SuggestedWordInfo.KIND_RESUMED, Dictionary.TYPE_RESUMED));
}
}
}
mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard());
- mWordComposer.setCursorPositionWithinWord(range.mCharsBefore);
- mConnection.setComposingRegion(mLastSelectionStart - range.mCharsBefore,
- mLastSelectionEnd + range.mCharsAfter);
+ // TODO: this is in chars but the callee expects code points!
+ mWordComposer.setCursorPositionWithinWord(numberOfCharsInWordBeforeCursor);
+ mConnection.setComposingRegion(
+ mLastSelectionStart - numberOfCharsInWordBeforeCursor,
+ mLastSelectionEnd + range.getNumberOfCharsInWordAfterCursor());
final SuggestedWords suggestedWords;
if (suggestions.isEmpty()) {
// We come here if there weren't any suggestion spans on this word. We will try to
@@ -2575,18 +2663,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mConnection.deleteSurroundingText(deleteLength, 0);
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
- mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
+ mUserHistoryPredictionDictionary.cancelAddingUserHistory(previousWord, committedWord);
}
mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
if (mSettings.isInternal()) {
- Stats.onSeparator(mLastComposedWord.mSeparatorString,
+ LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_revertCommit(committedWord, originallyTypedWord,
mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString);
- ResearchLogger.getInstance().uncommitCurrentLogUnit(committedWord,
- true /* dumpCurrentLogUnit */);
}
// Don't restart suggestion yet. We'll restart if the user deletes the
// separator.
@@ -2599,45 +2685,54 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void promotePhantomSpace() {
if (mSettings.getCurrent().shouldInsertSpacesAutomatically()
&& !mConnection.textBeforeCursorLooksLikeURL()) {
- sendKeyCodePoint(Constants.CODE_SPACE);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_promotePhantomSpace();
}
+ sendKeyCodePoint(Constants.CODE_SPACE);
}
}
- // Used by the RingCharBuffer
- public boolean isWordSeparator(final int code) {
- return mSettings.getCurrent().isWordSeparator(code);
- }
-
// TODO: Make this private
// Outside LatinIME, only used by the {@link InputTestsBase} test suite.
@UsedForTesting
void loadKeyboard() {
- // TODO: Why are we calling {@link #loadSettings()} and {@link #initSuggest()} in a
- // different order than in {@link #onStartInputView}?
- initSuggest();
+ // Since we are switching languages, the most urgent thing is to let the keyboard graphics
+ // update. LoadKeyboard does that, but we need to wait for buffer flip for it to be on
+ // the screen. Anything we do right now will delay this, so wait until the next frame
+ // before we do the rest, like reopening dictionaries and updating suggestions. So we
+ // post a message.
+ mHandler.postReopenDictionaries();
loadSettings();
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
// Reload keyboard because the current language has been changed.
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent());
}
- // Since we just changed languages, we should re-evaluate suggestions with whatever word
- // we are currently composing. If we are not composing anything, we may want to display
- // predictions or punctuation signs (which is done by the updateSuggestionStrip anyway).
- mHandler.postUpdateSuggestionStrip();
}
- // Callback called by PointerTracker through the KeyboardActionListener. This is called when a
- // key is depressed; release matching call is onReleaseKey below.
+ private void hapticAndAudioFeedback(final int code, final boolean isRepeatKey) {
+ final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView();
+ if (keyboardView != null && keyboardView.isInSlidingKeyInput()) {
+ // No need to feedback while sliding input.
+ return;
+ }
+ if (isRepeatKey && code == Constants.CODE_DELETE && !mConnection.canDeleteCharacters()) {
+ // No need to feedback when repeating delete key will have no effect.
+ return;
+ }
+ AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, keyboardView);
+ }
+
+ // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed;
+ // release matching call is {@link #onReleaseKey(int,boolean)} below.
@Override
- public void onPressKey(final int primaryCode, final boolean isSinglePointer) {
+ public void onPressKey(final int primaryCode, final boolean isRepeatKey,
+ final boolean isSinglePointer) {
mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer);
+ hapticAndAudioFeedback(primaryCode, isRepeatKey);
}
- // Callback by PointerTracker through the KeyboardActionListener. This is called when a key
- // is released; press matching call is onPressKey above.
+ // Callback of the {@link KeyboardActionListener}. This is called when a key is released;
+ // press matching call is {@link #onPressKey(int,boolean,boolean)} above.
@Override
public void onReleaseKey(final int primaryCode, final boolean withSliding) {
mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
@@ -2703,7 +2798,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
mSubtypeSwitcher.onNetworkStateChanged(intent);
} else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
- mKeyboardSwitcher.onRingerModeChanged();
+ AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged();
}
}
};
@@ -2734,7 +2829,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final CharSequence[] items = new CharSequence[] {
// TODO: Should use new string "Select active input modes".
getString(R.string.language_selection_title),
- getString(Utils.getAcitivityTitleResId(this, SettingsActivity.class)),
+ getString(ApplicationUtils.getAcitivityTitleResId(this, SettingsActivity.class)),
};
final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
@Override
@@ -2787,6 +2882,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return mSuggestedWords.size() > 0 ? mSuggestedWords.getWord(0) : null;
}
+ // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
+ @UsedForTesting
+ /* package for test */ boolean isCurrentlyWaitingForMainDictionary() {
+ return mSuggest.isCurrentlyWaitingForMainDictionary();
+ }
+
+ // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
+ @UsedForTesting
+ /* package for test */ boolean hasMainDictionary() {
+ return mSuggest.hasMainDictionary();
+ }
+
public void debugDumpStateAndCrashWithException(final String context) {
final StringBuilder s = new StringBuilder(mAppWorkAroundsUtils.toString());
s.append("\nAttributes : ").append(mSettings.getCurrent().mInputAttributes)
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 980215de6..b69e3f8d2 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin;
import android.inputmethodservice.InputMethodService;
-import android.text.SpannableString;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -28,6 +27,11 @@ import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.SettingsValues;
+import com.android.inputmethod.latin.utils.CapsModeUtils;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.TextRange;
import com.android.inputmethod.research.ResearchLogger;
import java.util.Locale;
@@ -47,16 +51,19 @@ public final class RichInputConnection {
private static final boolean DEBUG_PREVIOUS_TEXT = false;
private static final boolean DEBUG_BATCH_NESTING = false;
// Provision for a long word pair and a separator
- private static final int LOOKBACK_CHARACTER_NUM = Constants.Dictionary.MAX_WORD_LENGTH * 2 + 1;
+ private static final int LOOKBACK_CHARACTER_NUM = Constants.DICTIONARY_MAX_WORD_LENGTH * 2 + 1;
private static final Pattern spaceRegex = Pattern.compile("\\s+");
private static final int INVALID_CURSOR_POSITION = -1;
/**
- * This variable contains the value LatinIME thinks the cursor position should be at now.
- * This is a few steps in advance of what the TextView thinks it is, because TextView will
- * only know after the IPC calls gets through.
+ * This variable contains an expected value for the cursor position. This is where the
+ * cursor may end up after all the keyboard-triggered updates have passed. We keep this to
+ * compare it to the actual cursor position to guess whether the move was caused by a
+ * keyboard command or not.
+ * It's not really the cursor position: the cursor may not be there yet, and it's also expected
+ * there be cases where it never actually comes to be there.
*/
- private int mCurrentCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points
+ private int mExpectedCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points
/**
* This contains the committed text immediately preceding the cursor and the composing
* text if any. It is refreshed when the cursor moves by calling upon the TextView.
@@ -97,16 +104,16 @@ public final class RichInputConnection {
final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString()
: beforeCursor.subSequence(beforeCursor.length() - actualLength,
beforeCursor.length()).toString();
- if (et.selectionStart != mCurrentCursorPosition
+ if (et.selectionStart != mExpectedCursorPosition
|| !(reference.equals(internal.toString()))) {
- final String context = "Expected cursor position = " + mCurrentCursorPosition
+ final String context = "Expected cursor position = " + mExpectedCursorPosition
+ "\nActual cursor position = " + et.selectionStart
+ "\nExpected text = " + internal.length() + " " + internal
+ "\nActual text = " + reference.length() + " " + reference;
((LatinIME)mParent).debugDumpStateAndCrashWithException(context);
} else {
- Log.e(TAG, Utils.getStackTrace(2));
- Log.e(TAG, "Exp <> Actual : " + mCurrentCursorPosition + " <> " + et.selectionStart);
+ Log.e(TAG, DebugLogUtils.getStackTrace(2));
+ Log.e(TAG, "Exp <> Actual : " + mExpectedCursorPosition + " <> " + et.selectionStart);
}
}
@@ -137,7 +144,7 @@ public final class RichInputConnection {
public void resetCachesUponCursorMove(final int newCursorPosition,
final boolean shouldFinishComposition) {
- mCurrentCursorPosition = newCursorPosition;
+ mExpectedCursorPosition = newCursorPosition;
mComposingText.setLength(0);
mCommittedTextBeforeComposingText.setLength(0);
final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
@@ -154,7 +161,7 @@ public final class RichInputConnection {
if (mNestLevel != 1) {
// TODO: exception instead
Log.e(TAG, "Batch edit level incorrect : " + mNestLevel);
- Log.e(TAG, Utils.getStackTrace(4));
+ Log.e(TAG, DebugLogUtils.getStackTrace(4));
}
}
@@ -162,7 +169,7 @@ public final class RichInputConnection {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
mCommittedTextBeforeComposingText.append(mComposingText);
- mCurrentCursorPosition += mComposingText.length();
+ mExpectedCursorPosition += mComposingText.length();
mComposingText.setLength(0);
if (null != mIC) {
mIC.finishComposingText();
@@ -176,7 +183,7 @@ public final class RichInputConnection {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
mCommittedTextBeforeComposingText.append(text);
- mCurrentCursorPosition += text.length() - mComposingText.length();
+ mExpectedCursorPosition += text.length() - mComposingText.length();
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitText(text, i);
@@ -188,6 +195,10 @@ public final class RichInputConnection {
return mIC.getSelectedText(flags);
}
+ public boolean canDeleteCharacters() {
+ return mExpectedCursorPosition > 0;
+ }
+
/**
* Gets the caps modes we should be in after this specific string.
*
@@ -222,7 +233,7 @@ public final class RichInputConnection {
// heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so.
// getCapsMode should be updated to be able to return a "not enough info" result so that
// we can get more context only when needed.
- if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mCurrentCursorPosition) {
+ if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) {
mCommittedTextBeforeComposingText.append(
getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
}
@@ -238,22 +249,35 @@ public final class RichInputConnection {
mCommittedTextBeforeComposingText.length());
}
- public CharSequence getTextBeforeCursor(final int i, final int j) {
- // TODO: use mCommittedTextBeforeComposingText if possible to improve performance
+ public CharSequence getTextBeforeCursor(final int n, final int flags) {
+ final int cachedLength =
+ mCommittedTextBeforeComposingText.length() + mComposingText.length();
+ // If we have enough characters to satisfy the request, or if we have all characters in
+ // the text field, then we can return the cached version right away.
+ if (cachedLength >= n || cachedLength >= mExpectedCursorPosition) {
+ final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText);
+ s.append(mComposingText);
+ if (s.length() > n) {
+ s.delete(0, s.length() - n);
+ }
+ return s;
+ }
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) return mIC.getTextBeforeCursor(i, j);
+ if (null != mIC) {
+ return mIC.getTextBeforeCursor(n, flags);
+ }
return null;
}
- public CharSequence getTextAfterCursor(final int i, final int j) {
+ public CharSequence getTextAfterCursor(final int n, final int flags) {
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) return mIC.getTextAfterCursor(i, j);
+ if (null != mIC) return mIC.getTextAfterCursor(n, flags);
return null;
}
- public void deleteSurroundingText(final int i, final int j) {
+ public void deleteSurroundingText(final int beforeLength, final int afterLength) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
- final int remainingChars = mComposingText.length() - i;
+ final int remainingChars = mComposingText.length() - beforeLength;
if (remainingChars >= 0) {
mComposingText.setLength(remainingChars);
} else {
@@ -263,15 +287,15 @@ public final class RichInputConnection {
+ remainingChars, 0);
mCommittedTextBeforeComposingText.setLength(len);
}
- if (mCurrentCursorPosition > i) {
- mCurrentCursorPosition -= i;
+ if (mExpectedCursorPosition > beforeLength) {
+ mExpectedCursorPosition -= beforeLength;
} else {
- mCurrentCursorPosition = 0;
+ mExpectedCursorPosition = 0;
}
if (null != mIC) {
- mIC.deleteSurroundingText(i, j);
+ mIC.deleteSurroundingText(beforeLength, afterLength);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_deleteSurroundingText(i, j);
+ ResearchLogger.richInputConnection_deleteSurroundingText(beforeLength, afterLength);
}
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
@@ -300,7 +324,7 @@ public final class RichInputConnection {
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_ENTER:
mCommittedTextBeforeComposingText.append("\n");
- mCurrentCursorPosition += 1;
+ mExpectedCursorPosition += 1;
break;
case KeyEvent.KEYCODE_DEL:
if (0 == mComposingText.length()) {
@@ -312,18 +336,18 @@ public final class RichInputConnection {
} else {
mComposingText.delete(mComposingText.length() - 1, mComposingText.length());
}
- if (mCurrentCursorPosition > 0) mCurrentCursorPosition -= 1;
+ if (mExpectedCursorPosition > 0) mExpectedCursorPosition -= 1;
break;
case KeyEvent.KEYCODE_UNKNOWN:
if (null != keyEvent.getCharacters()) {
mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
- mCurrentCursorPosition += keyEvent.getCharacters().length();
+ mExpectedCursorPosition += keyEvent.getCharacters().length();
}
break;
default:
final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1);
mCommittedTextBeforeComposingText.append(text);
- mCurrentCursorPosition += text.length();
+ mExpectedCursorPosition += text.length();
break;
}
}
@@ -338,7 +362,6 @@ public final class RichInputConnection {
public void setComposingRegion(final int start, final int end) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
- mCurrentCursorPosition = end;
final CharSequence textBeforeCursor =
getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE + (end - start), 0);
mCommittedTextBeforeComposingText.setLength(0);
@@ -355,32 +378,32 @@ public final class RichInputConnection {
}
}
- public void setComposingText(final CharSequence text, final int i) {
+ public void setComposingText(final CharSequence text, final int newCursorPosition) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
- mCurrentCursorPosition += text.length() - mComposingText.length();
+ mExpectedCursorPosition += text.length() - mComposingText.length();
mComposingText.setLength(0);
mComposingText.append(text);
// TODO: support values of i != 1. At this time, this is never called with i != 1.
if (null != mIC) {
- mIC.setComposingText(text, i);
+ mIC.setComposingText(text, newCursorPosition);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_setComposingText(text, i);
+ ResearchLogger.richInputConnection_setComposingText(text, newCursorPosition);
}
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
- public void setSelection(final int from, final int to) {
+ public void setSelection(final int start, final int end) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
if (null != mIC) {
- mIC.setSelection(from, to);
+ mIC.setSelection(start, end);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_setSelection(from, to);
+ ResearchLogger.richInputConnection_setSelection(start, end);
}
}
- mCurrentCursorPosition = from;
+ mExpectedCursorPosition = start;
mCommittedTextBeforeComposingText.setLength(0);
mCommittedTextBeforeComposingText.append(getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
}
@@ -403,7 +426,7 @@ public final class RichInputConnection {
// text should never be null, but just in case, it's better to insert nothing than to crash
if (null == text) text = "";
mCommittedTextBeforeComposingText.append(text);
- mCurrentCursorPosition += text.length() - mComposingText.length();
+ mExpectedCursorPosition += text.length() - mComposingText.length();
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitCompletion(completionInfo);
@@ -418,7 +441,7 @@ public final class RichInputConnection {
public String getNthPreviousWord(final String sentenceSeperators, final int n) {
mIC = mParent.getCurrentInputConnection();
if (null == mIC) return null;
- final CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
+ final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
if (DEBUG_PREVIOUS_TEXT && null != prev) {
final int checkLength = LOOKBACK_CHARACTER_NUM - 1;
final String reference = prev.length() <= checkLength ? prev.toString()
@@ -437,32 +460,6 @@ public final class RichInputConnection {
return getNthPreviousWord(prev, sentenceSeperators, n);
}
- /**
- * Represents a range of text, relative to the current cursor position.
- */
- public static final class Range {
- /** Characters before selection start */
- public final int mCharsBefore;
-
- /**
- * Characters after selection start, including one trailing word
- * separator.
- */
- public final int mCharsAfter;
-
- /** The actual characters that make up a word */
- public final CharSequence mWord;
-
- public Range(int charsBefore, int charsAfter, CharSequence word) {
- if (charsBefore < 0 || charsAfter < 0) {
- throw new IndexOutOfBoundsException();
- }
- this.mCharsBefore = charsBefore;
- this.mCharsAfter = charsAfter;
- this.mWord = word;
- }
- }
-
private static boolean isSeparator(int code, String sep) {
return sep.indexOf(code) != -1;
}
@@ -509,7 +506,7 @@ public final class RichInputConnection {
*/
public CharSequence getWordAtCursor(String separators) {
// getWordRangeAtCursor returns null if the connection is null
- Range r = getWordRangeAtCursor(separators, 0);
+ TextRange r = getWordRangeAtCursor(separators, 0);
return (r == null) ? null : r.mWord;
}
@@ -521,7 +518,8 @@ public final class RichInputConnection {
* be included in the returned range
* @return a range containing the text surrounding the cursor
*/
- public Range getWordRangeAtCursor(final String sep, final int additionalPrecedingWordsCount) {
+ public TextRange getWordRangeAtCursor(final String sep,
+ final int additionalPrecedingWordsCount) {
mIC = mParent.getCurrentInputConnection();
if (mIC == null || sep == null) {
return null;
@@ -571,19 +569,18 @@ public final class RichInputConnection {
}
}
- final SpannableString word = new SpannableString(TextUtils.concat(
- before.subSequence(startIndexInBefore, before.length()),
- after.subSequence(0, endIndexInAfter)));
- return new Range(before.length() - startIndexInBefore, endIndexInAfter, word);
+ return new TextRange(TextUtils.concat(before, after), startIndexInBefore,
+ before.length() + endIndexInAfter, before.length());
}
public boolean isCursorTouchingWord(final SettingsValues settingsValues) {
- final CharSequence before = getTextBeforeCursor(1, 0);
- final CharSequence after = getTextAfterCursor(1, 0);
- if (!TextUtils.isEmpty(before) && !settingsValues.isWordSeparator(before.charAt(0))
- && !settingsValues.isWordConnector(before.charAt(0))) {
+ final int codePointBeforeCursor = getCodePointBeforeCursor();
+ if (Constants.NOT_A_CODE != codePointBeforeCursor
+ && !settingsValues.isWordSeparator(codePointBeforeCursor)
+ && !settingsValues.isWordConnector(codePointBeforeCursor)) {
return true;
}
+ final CharSequence after = getTextAfterCursor(1, 0);
if (!TextUtils.isEmpty(after) && !settingsValues.isWordSeparator(after.charAt(0))
&& !settingsValues.isWordConnector(after.charAt(0))) {
return true;
@@ -593,9 +590,8 @@ public final class RichInputConnection {
public void removeTrailingSpace() {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
- final CharSequence lastOne = getTextBeforeCursor(1, 0);
- if (lastOne != null && lastOne.length() == 1
- && lastOne.charAt(0) == Constants.CODE_SPACE) {
+ final int codePointBeforeCursor = getCodePointBeforeCursor();
+ if (Constants.CODE_SPACE == codePointBeforeCursor) {
deleteSurroundingText(1, 0);
}
}
@@ -650,7 +646,7 @@ public final class RichInputConnection {
// be needed, but it's there just in case something went wrong.
final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0);
final String periodSpace = ". ";
- if (!periodSpace.equals(textBeforeCursor)) {
+ if (!TextUtils.equals(periodSpace, 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.
@@ -712,14 +708,14 @@ public final class RichInputConnection {
*/
public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart) {
// If this is an update that arrives at our expected position, it's a belated update.
- if (newSelStart == mCurrentCursorPosition) return true;
+ if (newSelStart == mExpectedCursorPosition) return true;
// If this is an update that moves the cursor from our expected position, it must be
// an explicit move.
- if (oldSelStart == mCurrentCursorPosition) return false;
+ if (oldSelStart == mExpectedCursorPosition) return false;
// The following returns true if newSelStart is between oldSelStart and
// mCurrentCursorPosition. We assume that if the updated position is between the old
// position and the expected position, then it must be a belated update.
- return (newSelStart - oldSelStart) * (mCurrentCursorPosition - newSelStart) >= 0;
+ return (newSelStart - oldSelStart) * (mExpectedCursorPosition - newSelStart) >= 0;
}
/**
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 0dd302afa..6b6bbf3a7 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -28,8 +28,13 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
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.CollectionUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
/**
@@ -46,6 +51,10 @@ public final class RichInputMethodManager {
private InputMethodManagerCompatWrapper mImmWrapper;
private InputMethodInfo mInputMethodInfoOfThisIme;
+ final HashMap<InputMethodInfo, List<InputMethodSubtype>>
+ mSubtypeListCacheWithImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
+ final HashMap<InputMethodInfo, List<InputMethodSubtype>>
+ mSubtypeListCacheWithoutImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
private static final int INDEX_NOT_FOUND = -1;
@@ -54,13 +63,6 @@ public final class RichInputMethodManager {
return sInstance;
}
- // Caveat: This may cause IPC
- public static boolean isInputMethodManagerValidForUserOfThisProcess(final Context context) {
- // Basically called to check whether this IME has been triggered by the current user or not
- return !((InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE)).
- getInputMethodList().isEmpty();
- }
-
public static void init(final Context context) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
sInstance.initInternal(context, prefs);
@@ -84,11 +86,11 @@ public final class RichInputMethodManager {
mInputMethodInfoOfThisIme = getInputMethodInfoOfThisIme(context);
// Initialize additional subtypes.
- SubtypeLocale.init(context);
+ SubtypeLocaleUtils.init(context);
final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(
prefs, context.getResources());
final InputMethodSubtype[] additionalSubtypes =
- AdditionalSubtype.createAdditionalSubtypesArray(prefAdditionalSubtypes);
+ AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes);
setAdditionalInputMethodSubtypes(additionalSubtypes);
}
@@ -109,8 +111,8 @@ public final class RichInputMethodManager {
public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList(
boolean allowsImplicitlySelectedSubtypes) {
- return mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
- mInputMethodInfoOfThisIme, allowsImplicitlySelectedSubtypes);
+ return getEnabledInputMethodSubtypeList(mInputMethodInfoOfThisIme,
+ allowsImplicitlySelectedSubtypes);
}
public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
@@ -134,7 +136,7 @@ public final class RichInputMethodManager {
final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes);
if (currentIndex == INDEX_NOT_FOUND) {
Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype="
- + SubtypeLocale.getSubtypeDisplayName(currentSubtype));
+ + SubtypeLocaleUtils.getSubtypeNameForLogging(currentSubtype));
return false;
}
final int nextIndex = (currentIndex + 1) % enabledSubtypes.size();
@@ -158,8 +160,8 @@ public final class RichInputMethodManager {
return false;
}
final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis);
- final List<InputMethodSubtype> enabledSubtypes = imm.getEnabledInputMethodSubtypeList(
- nextImi, true /* allowsImplicitlySelectedSubtypes */);
+ final List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeList(nextImi,
+ true /* allowsImplicitlySelectedSubtypes */);
if (enabledSubtypes.isEmpty()) {
// The next IME has no subtype.
imm.setInputMethod(token, nextImi.getId());
@@ -234,9 +236,8 @@ public final class RichInputMethodManager {
public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi,
final InputMethodSubtype subtype) {
- return checkIfSubtypeBelongsToList(
- subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
- imi, true /* allowsImplicitlySelectedSubtypes */));
+ return checkIfSubtypeBelongsToList(subtype, getEnabledInputMethodSubtypeList(imi,
+ true /* allowsImplicitlySelectedSubtypes */));
}
private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype,
@@ -297,8 +298,7 @@ public final class RichInputMethodManager {
for (InputMethodInfo imi : imiList) {
// We can return true immediately after we find two or more filtered IMEs.
if (filteredImisCount > 1) return true;
- final List<InputMethodSubtype> subtypes =
- mImmWrapper.mImm.getEnabledInputMethodSubtypeList(imi, true);
+ final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeList(imi, true);
// IMEs that have no subtypes should be counted.
if (subtypes.isEmpty()) {
++filteredImisCount;
@@ -344,7 +344,7 @@ public final class RichInputMethodManager {
final int count = myImi.getSubtypeCount();
for (int i = 0; i < count; i++) {
final InputMethodSubtype subtype = myImi.getSubtypeAt(i);
- final String layoutName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
+ final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
if (localeString.equals(subtype.getLocale())
&& keyboardLayoutSetName.equals(layoutName)) {
return subtype;
@@ -361,5 +361,26 @@ public final class RichInputMethodManager {
public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) {
mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
mInputMethodInfoOfThisIme.getId(), subtypes);
+ // Clear the cache so that we go read the subtypes again next time.
+ clearSubtypeCaches();
+ }
+
+ 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(
+ imi, allowsImplicitlySelectedSubtypes);
+ cache.put(imi, result);
+ return result;
+ }
+
+ public void clearSubtypeCaches() {
+ mSubtypeListCacheWithImplicitlySelectedSubtypes.clear();
+ mSubtypeListCacheWithoutImplicitlySelectedSubtypes.clear();
}
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 282b5794f..be03d4ae5 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -33,6 +33,7 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.List;
import java.util.Locale;
@@ -43,20 +44,23 @@ public final class SubtypeSwitcher {
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 */ ConnectivityManager mConnectivityManager;
- /*-----------------------------------------------------------*/
- // Variants which should be changed only by reload functions.
- private NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage();
+ private final NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage();
private InputMethodInfo mShortcutInputMethodInfo;
private InputMethodSubtype mShortcutSubtype;
private InputMethodSubtype mNoLanguageSubtype;
- /*-----------------------------------------------------------*/
-
private boolean mIsNetworkConnected;
+ // Dummy no language QWERTY subtype. See {@link R.xml.method}.
+ private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = new InputMethodSubtype(
+ R.string.subtype_no_language_qwerty, R.drawable.ic_subtype_keyboard, "zz", "keyboard",
+ "KeyboardLayoutSet=qwerty,AsciiCapable,EnabledWhenDefaultIsNotAsciiCapable",
+ false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
+
static final class NeedsToDisplayLanguage {
private int mEnabledSubtypeCount;
private boolean mIsSystemLanguageSameAsInputLanguage;
@@ -79,7 +83,7 @@ public final class SubtypeSwitcher {
}
public static void init(final Context context) {
- SubtypeLocale.init(context);
+ SubtypeLocaleUtils.init(context);
RichInputMethodManager.init(context);
sInstance.initialize(context);
}
@@ -96,11 +100,6 @@ public final class SubtypeSwitcher {
mRichImm = RichInputMethodManager.getInstance();
mConnectivityManager = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
- mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
- SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY);
- if (mNoLanguageSubtype == null) {
- throw new RuntimeException("Can't find no lanugage with QWERTY subtype");
- }
final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
mIsNetworkConnected = (info != null && info.isConnected());
@@ -155,10 +154,11 @@ public final class SubtypeSwitcher {
// Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
public void onSubtypeChanged(final InputMethodSubtype newSubtype) {
if (DBG) {
- Log.w(TAG, "onSubtypeChanged: " + SubtypeLocale.getSubtypeDisplayName(newSubtype));
+ Log.w(TAG, "onSubtypeChanged: "
+ + SubtypeLocaleUtils.getSubtypeNameForLogging(newSubtype));
}
- final Locale newLocale = SubtypeLocale.getSubtypeLocale(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());
@@ -234,7 +234,7 @@ public final class SubtypeSwitcher {
//////////////////////////////////
public boolean needsToDisplayLanguage(final Locale keyboardLocale) {
- if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) {
+ if (keyboardLocale.toString().equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
return true;
}
if (!keyboardLocale.equals(getCurrentSubtypeLocale())) {
@@ -251,14 +251,24 @@ public final class SubtypeSwitcher {
public Locale getCurrentSubtypeLocale() {
if (null != sForcedLocaleForTesting) return sForcedLocaleForTesting;
- return SubtypeLocale.getSubtypeLocale(getCurrentSubtype());
+ return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype());
}
public InputMethodSubtype getCurrentSubtype() {
- return mRichImm.getCurrentInputMethodSubtype(mNoLanguageSubtype);
+ return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype());
}
public InputMethodSubtype getNoLanguageSubtype() {
- return mNoLanguageSubtype;
+ if (mNoLanguageSubtype == null) {
+ mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+ SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
+ }
+ if (mNoLanguageSubtype != null) {
+ return mNoLanguageSubtype;
+ }
+ Log.w(TAG, "Can't find no lanugage with QWERTY subtype");
+ Log.w(TAG, "No input method subtype found; return dummy subtype: "
+ + DUMMY_NO_LANGUAGE_SUBTYPE);
+ return DUMMY_NO_LANGUAGE_SUBTYPE;
}
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index dc9bef22a..c2fdcb552 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -17,13 +17,21 @@
package com.android.inputmethod.latin;
import android.content.Context;
+import android.preference.PreferenceManager;
import android.text.TextUtils;
+import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
+import com.android.inputmethod.latin.utils.BoundedTreeSet;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
-import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
@@ -50,21 +58,22 @@ public final class Suggest {
// Close to -2**31
private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
+ public static final int MAX_SUGGESTIONS = 18;
+
public interface SuggestInitializationListener {
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
}
private static final boolean DBG = LatinImeLogger.sDBG;
- private Dictionary mMainDictionary;
- private ContactsBinaryDictionary mContactsDict;
private final ConcurrentHashMap<String, Dictionary> mDictionaries =
CollectionUtils.newConcurrentHashMap();
+ private HashSet<String> mOnlyDictionarySetForDebug = null;
+ private Dictionary mMainDictionary;
+ private ContactsBinaryDictionary mContactsDict;
@UsedForTesting
private boolean mIsCurrentlyWaitingForMainDictionary = false;
- public static final int MAX_SUGGESTIONS = 18;
-
private float mAutoCorrectionThreshold;
// Locale used for upper- and title-casing words
@@ -74,15 +83,22 @@ public final class Suggest {
final SuggestInitializationListener listener) {
initAsynchronously(context, locale, listener);
mLocale = locale;
+ // initialize a debug flag for the personalization
+ if (Settings.readUseOnlyPersonalizationDictionaryForDebug(
+ PreferenceManager.getDefaultSharedPreferences(context))) {
+ mOnlyDictionarySetForDebug = new HashSet<String>();
+ mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
+ mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
+ }
}
@UsedForTesting
- Suggest(final File dictionary, final long startOffset, final long length, final Locale locale) {
- final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionary,
- startOffset, length /* useFullEditDistance */, false, locale);
+ Suggest(final AssetFileAddress[] dictionaryList, final Locale locale) {
+ final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionaryList,
+ false /* useFullEditDistance */, locale);
mLocale = locale;
mMainDictionary = mainDict;
- addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
+ addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, mainDict);
}
private void initAsynchronously(final Context context, final Locale locale,
@@ -90,6 +106,14 @@ public final class Suggest {
resetMainDict(context, locale, listener);
}
+ private void addOrReplaceDictionaryInternal(final String key, final Dictionary dict) {
+ if (mOnlyDictionarySetForDebug != null && mOnlyDictionarySetForDebug.contains(key)) {
+ Log.w(TAG, "Ignore add " + key + " dictionary for debug.");
+ return;
+ }
+ addOrReplaceDictionary(mDictionaries, key, dict);
+ }
+
private static void addOrReplaceDictionary(
final ConcurrentHashMap<String, Dictionary> dictionaries,
final String key, final Dictionary dict) {
@@ -113,7 +137,7 @@ public final class Suggest {
public void run() {
final DictionaryCollection newMainDict =
DictionaryFactory.createMainDictionaryFromManager(context, locale);
- addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
+ addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, newMainDict);
mMainDictionary = newMainDict;
if (listener != null) {
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
@@ -151,7 +175,7 @@ public final class Suggest {
* before the main dictionary, if set. This refers to the system-managed user dictionary.
*/
public void setUserDictionary(final UserBinaryDictionary userDictionary) {
- addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER, userDictionary);
+ addOrReplaceDictionaryInternal(Dictionary.TYPE_USER, userDictionary);
}
/**
@@ -161,11 +185,19 @@ public final class Suggest {
*/
public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
mContactsDict = contactsDictionary;
- addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary);
+ addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary);
+ }
+
+ public void setUserHistoryPredictionDictionary(
+ final UserHistoryPredictionDictionary userHistoryPredictionDictionary) {
+ addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY,
+ userHistoryPredictionDictionary);
}
- public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
- addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
+ public void setPersonalizationPredictionDictionary(
+ final PersonalizationPredictionDictionary personalizationPredictionDictionary) {
+ addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
+ personalizationPredictionDictionary);
}
public void setAutoCorrectionThreshold(float threshold) {
@@ -229,7 +261,7 @@ public final class Suggest {
// or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
&& !whitelistedWord.equals(consideredWord))
- || (consideredWord.length() > 1 && !AutoCorrection.isInTheDictionary(mDictionaries,
+ || (consideredWord.length() > 1 && !AutoCorrectionUtils.isValidWord(this,
consideredWord, wordComposer.isFirstCharCapitalized()));
final boolean hasAutoCorrection;
@@ -250,7 +282,7 @@ public final class Suggest {
// auto-correct.
hasAutoCorrection = false;
} else {
- hasAutoCorrection = AutoCorrection.suggestionExceedsAutoCorrectionThreshold(
+ hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold);
}
@@ -379,7 +411,8 @@ public final class Suggest {
typedWord, cur.toString(), cur.mScore);
final String scoreInfoString;
if (normalizedScore > 0) {
- scoreInfoString = String.format("%d (%4.2f)", cur.mScore, normalizedScore);
+ scoreInfoString = String.format(
+ Locale.ROOT, "%d (%4.2f)", cur.mScore, normalizedScore);
} else {
scoreInfoString = Integer.toString(cur.mScore);
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index dfddb0ffe..22beaefee 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -19,11 +19,17 @@ package com.android.inputmethod.latin;
import android.text.TextUtils;
import android.view.inputmethod.CompletionInfo;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
public final class SuggestedWords {
+ public static final int INDEX_OF_TYPED_WORD = 0;
+ public static final int INDEX_OF_AUTO_CORRECTION = 1;
+
private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
CollectionUtils.newArrayList(0);
public static final SuggestedWords EMPTY = new SuggestedWords(
@@ -61,12 +67,27 @@ public final class SuggestedWords {
return mSuggestedWordInfoList.size();
}
- public String getWord(int pos) {
- return mSuggestedWordInfoList.get(pos).mWord;
+ public String getWord(final int index) {
+ return mSuggestedWordInfoList.get(index).mWord;
+ }
+
+ public SuggestedWordInfo getInfo(final int index) {
+ return mSuggestedWordInfoList.get(index);
}
- public SuggestedWordInfo getInfo(int pos) {
- return mSuggestedWordInfoList.get(pos);
+ public String getDebugString(final int pos) {
+ if (!LatinImeLogger.sDBG) {
+ return null;
+ }
+ final SuggestedWordInfo wordInfo = getInfo(pos);
+ if (wordInfo == null) {
+ return null;
+ }
+ final String debugString = wordInfo.getDebugString();
+ if (TextUtils.isEmpty(debugString)) {
+ return null;
+ }
+ return debugString;
}
public boolean willAutoCorrect() {
@@ -108,8 +129,8 @@ public final class SuggestedWords {
SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
alreadySeen.add(typedWord.toString());
final int previousSize = previousSuggestions.size();
- for (int pos = 1; pos < previousSize; pos++) {
- final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(pos);
+ for (int index = 1; index < previousSize; index++) {
+ final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(index);
final String prevWord = prevWordInfo.mWord;
// Filter out duplicate suggestion.
if (!alreadySeen.contains(prevWord)) {
@@ -132,7 +153,10 @@ public final class SuggestedWords {
public static final int KIND_APP_DEFINED = 6; // Suggested by the application
public static final int KIND_SHORTCUT = 7; // A shortcut
public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input)
- public static final int KIND_RESUMED = 9; // A resumed suggestion (comes from a span)
+ // KIND_RESUMED: A resumed suggestion (comes from a span, currently this type is used only
+ // in java for re-correction)
+ public static final int KIND_RESUMED = 9;
+ public static final int KIND_OOV_CORRECTION = 10; // Most probable string correction
public static final int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags
public static final int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 90f92972a..ed6fefae4 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -28,6 +28,8 @@ import android.provider.UserDictionary.Words;
import android.text.TextUtils;
import com.android.inputmethod.compat.UserDictionaryCompatUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Arrays;
import java.util.Locale;
@@ -75,7 +77,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
final boolean alsoUseMoreRestrictiveLocales) {
super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_USER);
if (null == locale) throw new NullPointerException(); // Catch the error earlier
- if (SubtypeLocale.NO_LANGUAGE.equals(locale)) {
+ if (SubtypeLocaleUtils.NO_LANGUAGE.equals(locale)) {
// If we don't have a locale, insert into the "all locales" user dictionary.
mLocale = USER_DICTIONARY_ALL_LANGUAGES;
} else {
@@ -239,7 +241,6 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
private void addWords(final Cursor cursor) {
final boolean hasShortcutColumn = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
- clearFusionDictionary();
if (cursor == null) return;
if (cursor.moveToFirst()) {
final int indexWord = cursor.getColumnIndex(Words.WORD);
@@ -266,4 +267,9 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
protected boolean hasContentChanged() {
return true;
}
+
+ @Override
+ protected boolean needsToReloadBeforeWriting() {
+ return true;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
deleted file mode 100644
index 0f96c54dc..000000000
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ /dev/null
@@ -1,491 +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 android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.inputmethodservice.InputMethodService;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.channels.FileChannel;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-public final class Utils {
- private static final String TAG = Utils.class.getSimpleName();
-
- private Utils() {
- // This utility class is not publicly instantiable.
- }
-
- /**
- * Cancel an {@link AsyncTask}.
- *
- * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
- * task should be interrupted; otherwise, in-progress tasks are allowed
- * to complete.
- */
- public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) {
- if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) {
- task.cancel(mayInterruptIfRunning);
- }
- }
-
- /* package */ static final class RingCharBuffer {
- private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
- private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
- private static final int INVALID_COORDINATE = -2;
- /* package */ static final int BUFSIZE = 20;
- private InputMethodService mContext;
- private boolean mEnabled = false;
- private int mEnd = 0;
- /* package */ int mLength = 0;
- private char[] mCharBuf = new char[BUFSIZE];
- private int[] mXBuf = new int[BUFSIZE];
- private int[] mYBuf = new int[BUFSIZE];
-
- private RingCharBuffer() {
- // Intentional empty constructor for singleton.
- }
- @UsedForTesting
- public static RingCharBuffer getInstance() {
- return sRingCharBuffer;
- }
- public static RingCharBuffer init(InputMethodService context, boolean enabled,
- boolean usabilityStudy) {
- if (!(enabled || usabilityStudy)) return null;
- sRingCharBuffer.mContext = context;
- sRingCharBuffer.mEnabled = true;
- UsabilityStudyLogUtils.getInstance().init(context);
- return sRingCharBuffer;
- }
- private static int normalize(int in) {
- int ret = in % BUFSIZE;
- return ret < 0 ? ret + BUFSIZE : ret;
- }
- // TODO: accept code points
- @UsedForTesting
- public void push(char c, int x, int y) {
- if (!mEnabled) return;
- mCharBuf[mEnd] = c;
- mXBuf[mEnd] = x;
- mYBuf[mEnd] = y;
- mEnd = normalize(mEnd + 1);
- if (mLength < BUFSIZE) {
- ++mLength;
- }
- }
- public char pop() {
- if (mLength < 1) {
- return PLACEHOLDER_DELIMITER_CHAR;
- } else {
- mEnd = normalize(mEnd - 1);
- --mLength;
- return mCharBuf[mEnd];
- }
- }
- public char getBackwardNthChar(int n) {
- if (mLength <= n || n < 0) {
- return PLACEHOLDER_DELIMITER_CHAR;
- } else {
- return mCharBuf[normalize(mEnd - n - 1)];
- }
- }
- public int getPreviousX(char c, int back) {
- int index = normalize(mEnd - 2 - back);
- if (mLength <= back
- || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
- return INVALID_COORDINATE;
- } else {
- return mXBuf[index];
- }
- }
- public int getPreviousY(char c, int back) {
- int index = normalize(mEnd - 2 - back);
- if (mLength <= back
- || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
- return INVALID_COORDINATE;
- } else {
- return mYBuf[index];
- }
- }
- public String getLastWord(int ignoreCharCount) {
- StringBuilder sb = new StringBuilder();
- int i = ignoreCharCount;
- for (; i < mLength; ++i) {
- char c = mCharBuf[normalize(mEnd - 1 - i)];
- if (!((LatinIME)mContext).isWordSeparator(c)) {
- break;
- }
- }
- for (; i < mLength; ++i) {
- char c = mCharBuf[normalize(mEnd - 1 - i)];
- if (!((LatinIME)mContext).isWordSeparator(c)) {
- sb.append(c);
- } else {
- break;
- }
- }
- return sb.reverse().toString();
- }
- public void reset() {
- mLength = 0;
- }
- }
-
- // Get the current stack trace
- public static String getStackTrace(final int limit) {
- StringBuilder sb = new StringBuilder();
- try {
- throw new RuntimeException();
- } catch (RuntimeException e) {
- StackTraceElement[] frames = e.getStackTrace();
- // Start at 1 because the first frame is here and we don't care about it
- for (int j = 1; j < frames.length && j < limit + 1; ++j) {
- sb.append(frames[j].toString() + "\n");
- }
- }
- return sb.toString();
- }
-
- public static String getStackTrace() {
- return getStackTrace(Integer.MAX_VALUE - 1);
- }
-
- public static final class UsabilityStudyLogUtils {
- // TODO: remove code duplication with ResearchLog class
- private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
- private static final String FILENAME = "log.txt";
- private final Handler mLoggingHandler;
- private File mFile;
- private File mDirectory;
- private InputMethodService mIms;
- private PrintWriter mWriter;
- private final Date mDate;
- private final SimpleDateFormat mDateFormat;
-
- private UsabilityStudyLogUtils() {
- mDate = new Date();
- mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US);
-
- HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
- Process.THREAD_PRIORITY_BACKGROUND);
- handlerThread.start();
- mLoggingHandler = new Handler(handlerThread.getLooper());
- }
-
- // Initialization-on-demand holder
- private static final class OnDemandInitializationHolder {
- public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
- }
-
- public static UsabilityStudyLogUtils getInstance() {
- return OnDemandInitializationHolder.sInstance;
- }
-
- public void init(InputMethodService ims) {
- mIms = ims;
- mDirectory = ims.getFilesDir();
- }
-
- private void createLogFileIfNotExist() {
- if ((mFile == null || !mFile.exists())
- && (mDirectory != null && mDirectory.exists())) {
- try {
- mWriter = getPrintWriter(mDirectory, FILENAME, false);
- } catch (IOException e) {
- Log.e(USABILITY_TAG, "Can't create log file.");
- }
- }
- }
-
- public static void writeBackSpace(int x, int y) {
- UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
- }
-
- public void writeChar(char c, int x, int y) {
- String inputChar = String.valueOf(c);
- switch (c) {
- case '\n':
- inputChar = "<enter>";
- break;
- case '\t':
- inputChar = "<tab>";
- break;
- case ' ':
- inputChar = "<space>";
- break;
- }
- UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
- LatinImeLogger.onPrintAllUsabilityStudyLogs();
- }
-
- public void write(final String log) {
- mLoggingHandler.post(new Runnable() {
- @Override
- public void run() {
- createLogFileIfNotExist();
- final long currentTime = System.currentTimeMillis();
- mDate.setTime(currentTime);
-
- final String printString = String.format(Locale.US, "%s\t%d\t%s\n",
- mDateFormat.format(mDate), currentTime, log);
- if (LatinImeLogger.sDBG) {
- Log.d(USABILITY_TAG, "Write: " + log);
- }
- mWriter.print(printString);
- }
- });
- }
-
- private synchronized String getBufferedLogs() {
- mWriter.flush();
- StringBuilder sb = new StringBuilder();
- BufferedReader br = getBufferedReader();
- String line;
- try {
- while ((line = br.readLine()) != null) {
- sb.append('\n');
- sb.append(line);
- }
- } catch (IOException e) {
- Log.e(USABILITY_TAG, "Can't read log file.");
- } finally {
- if (LatinImeLogger.sDBG) {
- Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
- }
- try {
- br.close();
- } catch (IOException e) {
- // ignore.
- }
- }
- return sb.toString();
- }
-
- public void emailResearcherLogsAll() {
- mLoggingHandler.post(new Runnable() {
- @Override
- public void run() {
- final Date date = new Date();
- date.setTime(System.currentTimeMillis());
- final String currentDateTimeString =
- new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date);
- if (mFile == null) {
- Log.w(USABILITY_TAG, "No internal log file found.");
- return;
- }
- if (mIms.checkCallingOrSelfPermission(
- android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
- != PackageManager.PERMISSION_GRANTED) {
- Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
- return;
- }
- mWriter.flush();
- final String destPath = Environment.getExternalStorageDirectory()
- + "/research-" + currentDateTimeString + ".log";
- final File destFile = new File(destPath);
- try {
- final FileInputStream srcStream = new FileInputStream(mFile);
- final FileOutputStream destStream = new FileOutputStream(destFile);
- final FileChannel src = srcStream.getChannel();
- final FileChannel dest = destStream.getChannel();
- src.transferTo(0, src.size(), dest);
- src.close();
- srcStream.close();
- dest.close();
- destStream.close();
- } catch (FileNotFoundException e1) {
- Log.w(USABILITY_TAG, e1);
- return;
- } catch (IOException e2) {
- Log.w(USABILITY_TAG, e2);
- return;
- }
- if (destFile == null || !destFile.exists()) {
- Log.w(USABILITY_TAG, "Dest file doesn't exist.");
- return;
- }
- final Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- if (LatinImeLogger.sDBG) {
- Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
- }
- intent.setType("text/plain");
- intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
- intent.putExtra(Intent.EXTRA_SUBJECT,
- "[Research Logs] " + currentDateTimeString);
- mIms.startActivity(intent);
- }
- });
- }
-
- public void printAll() {
- mLoggingHandler.post(new Runnable() {
- @Override
- public void run() {
- mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
- }
- });
- }
-
- public void clearAll() {
- mLoggingHandler.post(new Runnable() {
- @Override
- public void run() {
- if (mFile != null && mFile.exists()) {
- if (LatinImeLogger.sDBG) {
- Log.d(USABILITY_TAG, "Delete log file.");
- }
- mFile.delete();
- mWriter.close();
- }
- }
- });
- }
-
- private BufferedReader getBufferedReader() {
- createLogFileIfNotExist();
- try {
- return new BufferedReader(new FileReader(mFile));
- } catch (FileNotFoundException e) {
- return null;
- }
- }
-
- private PrintWriter getPrintWriter(
- File dir, String filename, boolean renew) throws IOException {
- mFile = new File(dir, filename);
- if (mFile.exists()) {
- if (renew) {
- mFile.delete();
- }
- }
- return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
- }
- }
-
- public static final class Stats {
- public static void onNonSeparator(final char code, final int x,
- final int y) {
- RingCharBuffer.getInstance().push(code, x, y);
- LatinImeLogger.logOnInputChar();
- }
-
- public static void onSeparator(final int code, final int x, final int y) {
- // Helper method to log a single code point separator
- // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
- onSeparator(new String(new int[]{code}, 0, 1), x, y);
- }
-
- public static void onSeparator(final String separator, final int x, final int y) {
- final int length = separator.length();
- for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
- int codePoint = Character.codePointAt(separator, i);
- // TODO: accept code points
- RingCharBuffer.getInstance().push((char)codePoint, x, y);
- }
- LatinImeLogger.logOnInputSeparator();
- }
-
- public static void onAutoCorrection(final String typedWord, final String correctedWord,
- final String separatorString, final WordComposer wordComposer) {
- final boolean isBatchMode = wordComposer.isBatchMode();
- if (!isBatchMode && TextUtils.isEmpty(typedWord)) return;
- // TODO: this fails when the separator is more than 1 code point long, but
- // the backend can't handle it yet. The only case when this happens is with
- // smileys and other multi-character keys.
- final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
- : separatorString.codePointAt(0);
- if (!isBatchMode) {
- LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
- } else {
- if (!TextUtils.isEmpty(correctedWord)) {
- // We must make sure that InputPointer contains only the relative timestamps,
- // not actual timestamps.
- LatinImeLogger.logOnAutoCorrectionForGeometric(
- "", correctedWord, codePoint, wordComposer.getInputPointers());
- }
- }
- }
-
- public static void onAutoCorrectionCancellation() {
- LatinImeLogger.logOnAutoCorrectionCancelled();
- }
- }
-
- public static String getDebugInfo(final SuggestedWords suggestions, final int pos) {
- if (!LatinImeLogger.sDBG) return null;
- final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
- if (wordInfo == null) return null;
- final String info = wordInfo.getDebugString();
- if (TextUtils.isEmpty(info)) return null;
- return info;
- }
-
- public static int getAcitivityTitleResId(Context context, Class<? extends Activity> cls) {
- final ComponentName cn = new ComponentName(context, cls);
- try {
- final ActivityInfo ai = context.getPackageManager().getActivityInfo(cn, 0);
- if (ai != null) {
- return ai.labelRes;
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Failed to get settings activity title res id.", e);
- }
- return 0;
- }
-
- public static String getVersionName(Context context) {
- try {
- if (context == null) {
- return "";
- }
- final String packageName = context.getPackageName();
- PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
- return info.versionName;
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Could not find version info.", e);
- }
- return "";
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index e078f03f4..a09ca605c 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -18,6 +18,7 @@ package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.utils.StringUtils;
import java.util.Arrays;
@@ -25,7 +26,7 @@ import java.util.Arrays;
* 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 = Constants.DICTIONARY_MAX_WORD_LENGTH;
private static final boolean DBG = LatinImeLogger.sDBG;
public static final int CAPS_MODE_OFF = 0;
@@ -36,8 +37,16 @@ public final class WordComposer {
public static final int CAPS_MODE_AUTO_SHIFTED = 0x5;
public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
+ // An array of code points representing the characters typed so far.
+ // The array is limited to MAX_WORD_LENGTH code points, but mTypedWord extends past that
+ // and mCodePointSize can go past that. If mCodePointSize is greater than MAX_WORD_LENGTH,
+ // this just does not contain the associated code points past MAX_WORD_LENGTH.
private int[] mPrimaryKeyCodes;
private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
+ // This is the typed word, as a StringBuilder. This has the same contents as mPrimaryKeyCodes
+ // but under a StringBuilder representation for ease of use, depending on what is more useful
+ // at any given time. However this is not limited in size, while mPrimaryKeyCodes is limited
+ // to MAX_WORD_LENGTH code points.
private final StringBuilder mTypedWord;
private String mAutoCorrection;
private boolean mIsResumed;
@@ -55,6 +64,10 @@ public final class WordComposer {
private int mDigitsCount;
private int mCapitalizedMode;
private int mTrailingSingleQuotesCount;
+ // This is the number of code points entered so far. This is not limited to MAX_WORD_LENGTH.
+ // In general, this contains the size of mPrimaryKeyCodes, except when this is greater than
+ // MAX_WORD_LENGTH in which case mPrimaryKeyCodes only contain the first MAX_WORD_LENGTH
+ // code points.
private int mCodePointSize;
private int mCursorPositionWithinWord;
@@ -192,6 +205,49 @@ public final class WordComposer {
return mCursorPositionWithinWord != mCodePointSize;
}
+ /**
+ * When the cursor is moved by the user, we need to update its position.
+ * If it falls inside the currently composing word, we don't reset the composition, and
+ * only update the cursor position.
+ *
+ * @param expectedMoveAmount How many java chars to move the cursor. Negative values move
+ * the cursor backward, positive values move the cursor forward.
+ * @return true if the cursor is still inside the composing word, false otherwise.
+ */
+ public boolean moveCursorByAndReturnIfInsideComposingWord(final int expectedMoveAmount) {
+ int actualMoveAmountWithinWord = 0;
+ int cursorPos = mCursorPositionWithinWord;
+ final int[] codePoints;
+ if (mCodePointSize >= MAX_WORD_LENGTH) {
+ // If we have more than MAX_WORD_LENGTH characters, we don't have everything inside
+ // mPrimaryKeyCodes. This should be rare enough that we can afford to just compute
+ // the array on the fly when this happens.
+ codePoints = StringUtils.toCodePointArray(mTypedWord.toString());
+ } else {
+ codePoints = mPrimaryKeyCodes;
+ }
+ 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]);
+ ++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) {
+ --cursorPos;
+ actualMoveAmountWithinWord -= 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;
+ mCursorPositionWithinWord = cursorPos;
+ return true;
+ }
+
public void setBatchInputPointers(final InputPointers batchPointers) {
mInputPointers.set(batchPointers);
mIsBatchMode = true;
diff --git a/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
index 9f91639a2..028f78a87 100644
--- a/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java
+++ b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
@@ -14,15 +14,22 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+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.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -56,7 +63,7 @@ public class ExternalDictionaryGetterForDebug {
if (0 == fileNames.length) {
showNoFileDialog(context);
} else if (1 == fileNames.length) {
- askInstallFile(context, fileNames[0]);
+ askInstallFile(context, SOURCE_FOLDER, fileNames[0], null /* completeRunnable */);
} else {
showChooseFileDialog(context, fileNames);
}
@@ -79,14 +86,19 @@ public class ExternalDictionaryGetterForDebug {
.setItems(fileNames, new OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
- askInstallFile(context, fileNames[which]);
+ askInstallFile(context, SOURCE_FOLDER, fileNames[which],
+ null /* completeRunnable */);
}
})
.create().show();
}
- private static void askInstallFile(final Context context, final String fileName) {
- final File file = new File(SOURCE_FOLDER, fileName.toString());
+ /**
+ * 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 FileHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file);
final StringBuilder message = new StringBuilder();
final String locale = header.getLocaleString();
@@ -106,12 +118,26 @@ public class ExternalDictionaryGetterForDebug {
@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();
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index c87a9254d..167c6915c 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -181,7 +181,7 @@ public final class BinaryDictIOUtils {
final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
int wordPos = 0;
final int wordLen = word.codePointCount(0, word.length());
- for (int depth = 0; depth < Constants.Dictionary.MAX_WORD_LENGTH; ++depth) {
+ for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) {
if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD;
do {
@@ -746,7 +746,7 @@ public final class BinaryDictIOUtils {
final int[] codePoints = FusionDictionary.getCodePoints(word);
final int wordLen = codePoints.length;
- for (int depth = 0; depth < Constants.Dictionary.MAX_WORD_LENGTH; ++depth) {
+ for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) {
if (wordPos >= wordLen) break;
nodeOriginAddress = buffer.position();
int nodeParentAddress = -1;
@@ -982,6 +982,7 @@ public final class BinaryDictIOUtils {
return null;
}
+ private static final int HEADER_READING_BUFFER_SIZE = 16384;
/**
* Convenience method to read the header of a binary file.
*
@@ -991,7 +992,6 @@ public final class BinaryDictIOUtils {
* @param offset The offset in the file where to start reading the data.
* @param length The length of the data file.
*/
- private static final int HEADER_READING_BUFFER_SIZE = 16384;
public static FileHeader getDictionaryFileHeader(
final File file, final long offset, final long length)
throws FileNotFoundException, IOException, UnsupportedFormatException {
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 467f6a053..1b187d85d 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -389,7 +389,7 @@ public final class BinaryDictInputOutput {
* @param node the node to compute the maximum size of.
* @param options file format options.
*/
- private static void setNodeMaximumSize(final Node node, final FormatOptions options) {
+ private static void calculateNodeMaximumSize(final Node node, final FormatOptions options) {
int size = getGroupCountSize(node);
for (CharGroup g : node.mData) {
final int groupSize = getCharGroupMaximumSize(g, options);
@@ -518,14 +518,56 @@ public final class BinaryDictInputOutput {
}
/**
- * Finds the absolute address of a word in the dictionary.
+ * Get the offset from a position inside a current node to a target node, during update.
*
- * @param dict the dictionary in which to search.
- * @param word the word we are searching for.
- * @return the word address. If it is not found, an exception is thrown.
+ * If the current node is before the target node, the target node has not been updated yet,
+ * so we should return the offset from the old position of the current node to the old position
+ * of the target node. If on the other hand the target is before the current node, it already
+ * has been updated, so we should return the offset from the new position in the current node
+ * to the new position in the target node.
+ * @param currentNode the node containing the CharGroup where the offset will be written
+ * @param offsetFromStartOfCurrentNode the offset, in bytes, from the start of currentNode
+ * @param targetNode the target node to get the offset to
+ * @return the offset to the target node
*/
- private static int findAddressOfWord(final FusionDictionary dict, final String word) {
- return FusionDictionary.findWordInTree(dict.mRoot, word).mCachedAddress;
+ private static int getOffsetToTargetNodeDuringUpdate(final Node currentNode,
+ final int offsetFromStartOfCurrentNode, final Node targetNode) {
+ final boolean isTargetBeforeCurrent = (targetNode.mCachedAddressBeforeUpdate
+ < currentNode.mCachedAddressBeforeUpdate);
+ if (isTargetBeforeCurrent) {
+ return targetNode.mCachedAddressAfterUpdate
+ - (currentNode.mCachedAddressAfterUpdate + offsetFromStartOfCurrentNode);
+ } else {
+ return targetNode.mCachedAddressBeforeUpdate
+ - (currentNode.mCachedAddressBeforeUpdate + offsetFromStartOfCurrentNode);
+ }
+ }
+
+ /**
+ * Get the offset from a position inside a current node to a target CharGroup, during update.
+ * @param currentNode the node containing the CharGroup where the offset will be written
+ * @param offsetFromStartOfCurrentNode the offset, in bytes, from the start of currentNode
+ * @param targetCharGroup the target CharGroup to get the offset to
+ * @return the offset to the target CharGroup
+ */
+ // TODO: is there any way to factorize this method with the one above?
+ private static int getOffsetToTargetCharGroupDuringUpdate(final Node currentNode,
+ final int offsetFromStartOfCurrentNode, final CharGroup targetCharGroup) {
+ final int oldOffsetBasePoint = currentNode.mCachedAddressBeforeUpdate
+ + offsetFromStartOfCurrentNode;
+ final boolean isTargetBeforeCurrent = (targetCharGroup.mCachedAddressBeforeUpdate
+ < oldOffsetBasePoint);
+ // If the target is before the current node, then its address has already been updated.
+ // We can use the AfterUpdate member, and compare it to our own member after update.
+ // Otherwise, the AfterUpdate member is not updated yet, so we need to use the BeforeUpdate
+ // member, and of course we have to compare this to our own address before update.
+ if (isTargetBeforeCurrent) {
+ final int newOffsetBasePoint = currentNode.mCachedAddressAfterUpdate
+ + offsetFromStartOfCurrentNode;
+ return targetCharGroup.mCachedAddressAfterUpdate - newOffsetBasePoint;
+ } else {
+ return targetCharGroup.mCachedAddressBeforeUpdate - oldOffsetBasePoint;
+ }
}
/**
@@ -548,33 +590,28 @@ public final class BinaryDictInputOutput {
boolean changed = false;
int size = getGroupCountSize(node);
for (CharGroup group : node.mData) {
- if (group.mCachedAddress != node.mCachedAddress + size) {
+ group.mCachedAddressAfterUpdate = node.mCachedAddressAfterUpdate + size;
+ if (group.mCachedAddressAfterUpdate != group.mCachedAddressBeforeUpdate) {
changed = true;
- group.mCachedAddress = node.mCachedAddress + size;
}
int groupSize = getGroupHeaderSize(group, formatOptions);
if (group.isTerminal()) groupSize += FormatSpec.GROUP_FREQUENCY_SIZE;
if (null == group.mChildren && formatOptions.mSupportsDynamicUpdate) {
groupSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
} else if (null != group.mChildren) {
- final int offsetBasePoint = groupSize + node.mCachedAddress + size;
- final int offset = group.mChildren.mCachedAddress - offsetBasePoint;
- // assign my address to children's parent address
- group.mChildren.mCachedParentAddress = group.mCachedAddress
- - group.mChildren.mCachedAddress;
if (formatOptions.mSupportsDynamicUpdate) {
groupSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
} else {
- groupSize += getByteSize(offset);
+ groupSize += getByteSize(getOffsetToTargetNodeDuringUpdate(node,
+ groupSize + size, group.mChildren));
}
}
groupSize += getShortcutListSize(group.mShortcutTargets);
if (null != group.mBigrams) {
for (WeightedString bigram : group.mBigrams) {
- final int offsetBasePoint = groupSize + node.mCachedAddress + size
- + FormatSpec.GROUP_FLAGS_SIZE;
- final int addressOfBigram = findAddressOfWord(dict, bigram.mWord);
- final int offset = addressOfBigram - offsetBasePoint;
+ final int offset = getOffsetToTargetCharGroupDuringUpdate(node,
+ groupSize + size + FormatSpec.GROUP_FLAGS_SIZE,
+ FusionDictionary.findWordInTree(dict.mRoot, bigram.mWord));
groupSize += getByteSize(offset) + FormatSpec.GROUP_FLAGS_SIZE;
}
}
@@ -592,35 +629,69 @@ public final class BinaryDictInputOutput {
}
/**
- * Computes the byte size of a list of nodes and updates each node cached position.
+ * Initializes the cached addresses of nodes from their size.
*
* @param flatNodes the array of nodes.
* @param formatOptions file format options.
* @return the byte size of the entire stack.
*/
- private static int stackNodes(final ArrayList<Node> flatNodes,
+ private static int initializeNodesCachedAddresses(final ArrayList<Node> flatNodes,
final FormatOptions formatOptions) {
int nodeOffset = 0;
- for (Node n : flatNodes) {
- n.mCachedAddress = nodeOffset;
+ for (final Node n : flatNodes) {
+ n.mCachedAddressBeforeUpdate = nodeOffset;
int groupCountSize = getGroupCountSize(n);
int groupOffset = 0;
- for (CharGroup g : n.mData) {
- g.mCachedAddress = groupCountSize + nodeOffset + groupOffset;
+ for (final CharGroup g : n.mData) {
+ g.mCachedAddressBeforeUpdate = g.mCachedAddressAfterUpdate =
+ groupCountSize + nodeOffset + groupOffset;
groupOffset += g.mCachedSize;
}
final int nodeSize = groupCountSize + groupOffset
+ (formatOptions.mSupportsDynamicUpdate
? FormatSpec.FORWARD_LINK_ADDRESS_SIZE : 0);
- if (nodeSize != n.mCachedSize) {
- throw new RuntimeException("Bug : Stored and computed node size differ");
- }
nodeOffset += n.mCachedSize;
}
return nodeOffset;
}
/**
+ * Updates the cached addresses of nodes after recomputing their new positions.
+ *
+ * @param flatNodes the array of nodes.
+ */
+ private static void updateNodeCachedAddresses(final ArrayList<Node> flatNodes) {
+ for (final Node n : flatNodes) {
+ n.mCachedAddressBeforeUpdate = n.mCachedAddressAfterUpdate;
+ for (final CharGroup g : n.mData) {
+ g.mCachedAddressBeforeUpdate = g.mCachedAddressAfterUpdate;
+ }
+ }
+ }
+
+ /**
+ * Compute the cached parent addresses after all has been updated.
+ *
+ * The parent addresses are used by some binary formats at write-to-disk time. Not all formats
+ * need them. In particular, version 2 does not need them, and version 3 does.
+ *
+ * @param flatNodes the flat array of nodes to fill in
+ */
+ private static void computeParentAddresses(final ArrayList<Node> flatNodes) {
+ for (final Node node : flatNodes) {
+ for (final CharGroup group : node.mData) {
+ if (null != group.mChildren) {
+ // Assign my address to children's parent address
+ // Here BeforeUpdate and AfterUpdate addresses have the same value, so it
+ // does not matter which we use.
+ group.mChildren.mCachedParentAddress = group.mCachedAddressAfterUpdate
+ - group.mChildren.mCachedAddressAfterUpdate;
+ }
+ }
+ }
+ }
+
+ /**
* Compute the addresses and sizes of an ordered node array.
*
* This method takes a node array and will update its cached address and size values
@@ -637,9 +708,9 @@ public final class BinaryDictInputOutput {
*/
private static ArrayList<Node> computeAddresses(final FusionDictionary dict,
final ArrayList<Node> flatNodes, final FormatOptions formatOptions) {
- // First get the worst sizes and offsets
- for (Node n : flatNodes) setNodeMaximumSize(n, formatOptions);
- final int offset = stackNodes(flatNodes, formatOptions);
+ // First get the worst possible sizes and offsets
+ for (final Node n : flatNodes) calculateNodeMaximumSize(n, formatOptions);
+ final int offset = initializeNodesCachedAddresses(flatNodes, formatOptions);
MakedictLog.i("Compressing the array addresses. Original size : " + offset);
MakedictLog.i("(Recursively seen size : " + offset + ")");
@@ -648,22 +719,28 @@ public final class BinaryDictInputOutput {
boolean changesDone = false;
do {
changesDone = false;
- for (Node n : flatNodes) {
+ int nodeStartOffset = 0;
+ for (final Node n : flatNodes) {
+ n.mCachedAddressAfterUpdate = nodeStartOffset;
final int oldNodeSize = n.mCachedSize;
final boolean changed = computeActualNodeSize(n, dict, formatOptions);
final int newNodeSize = n.mCachedSize;
if (oldNodeSize < newNodeSize) throw new RuntimeException("Increased size ?!");
+ nodeStartOffset += newNodeSize;
changesDone |= changed;
}
- stackNodes(flatNodes, formatOptions);
+ updateNodeCachedAddresses(flatNodes);
++passes;
if (passes > MAX_PASSES) throw new RuntimeException("Too many passes - probably a bug");
} while (changesDone);
+ if (formatOptions.mSupportsDynamicUpdate) {
+ computeParentAddresses(flatNodes);
+ }
final Node lastNode = flatNodes.get(flatNodes.size() - 1);
MakedictLog.i("Compression complete in " + passes + " passes.");
MakedictLog.i("After address compression : "
- + (lastNode.mCachedAddress + lastNode.mCachedSize));
+ + (lastNode.mCachedAddressAfterUpdate + lastNode.mCachedSize));
return flatNodes;
}
@@ -681,10 +758,12 @@ public final class BinaryDictInputOutput {
private static void checkFlatNodeArray(final ArrayList<Node> array) {
int offset = 0;
int index = 0;
- for (Node n : array) {
- if (n.mCachedAddress != offset) {
+ for (final Node n : array) {
+ // BeforeUpdate and AfterUpdate addresses are the same here, so it does not matter
+ // which we use.
+ if (n.mCachedAddressAfterUpdate != offset) {
throw new RuntimeException("Wrong address for node " + index
- + " : expected " + offset + ", got " + n.mCachedAddress);
+ + " : expected " + offset + ", got " + n.mCachedAddressAfterUpdate);
}
++index;
offset += n.mCachedSize;
@@ -926,7 +1005,7 @@ public final class BinaryDictInputOutput {
private static int writePlacedNode(final FusionDictionary dict, byte[] buffer,
final Node node, final FormatOptions formatOptions) {
// TODO: Make the code in common with BinaryDictIOUtils#writeCharGroup
- int index = node.mCachedAddress;
+ int index = node.mCachedAddressAfterUpdate;
final int groupCount = node.mData.size();
final int countSize = getGroupCountSize(node);
@@ -943,10 +1022,11 @@ public final class BinaryDictInputOutput {
}
int groupAddress = index;
for (int i = 0; i < groupCount; ++i) {
- CharGroup group = node.mData.get(i);
- if (index != group.mCachedAddress) throw new RuntimeException("Bug: write index is not "
- + "the same as the cached address of the group : "
- + index + " <> " + group.mCachedAddress);
+ final CharGroup group = node.mData.get(i);
+ if (index != group.mCachedAddressAfterUpdate) {
+ throw new RuntimeException("Bug: write index is not the same as the cached address "
+ + "of the group : " + index + " <> " + group.mCachedAddressAfterUpdate);
+ }
groupAddress += getGroupHeaderSize(group, formatOptions);
// Sanity checks.
if (DBG && group.mFrequency > FormatSpec.MAX_TERMINAL_FREQUENCY) {
@@ -957,15 +1037,15 @@ public final class BinaryDictInputOutput {
if (group.mFrequency >= 0) groupAddress += FormatSpec.GROUP_FREQUENCY_SIZE;
final int childrenOffset = null == group.mChildren
? FormatSpec.NO_CHILDREN_ADDRESS
- : group.mChildren.mCachedAddress - groupAddress;
- byte flags = makeCharGroupFlags(group, groupAddress, childrenOffset, formatOptions);
- buffer[index++] = flags;
+ : group.mChildren.mCachedAddressAfterUpdate - groupAddress;
+ buffer[index++] =
+ makeCharGroupFlags(group, groupAddress, childrenOffset, formatOptions);
if (parentAddress == FormatSpec.NO_PARENT_ADDRESS) {
index = writeParentAddress(buffer, index, parentAddress, formatOptions);
} else {
- index = writeParentAddress(buffer, index,
- parentAddress + (node.mCachedAddress - group.mCachedAddress),
+ index = writeParentAddress(buffer, index, parentAddress
+ + (node.mCachedAddressAfterUpdate - group.mCachedAddressAfterUpdate),
formatOptions);
}
@@ -1016,7 +1096,7 @@ public final class BinaryDictInputOutput {
final WeightedString bigram = bigramIterator.next();
final CharGroup target =
FusionDictionary.findWordInTree(dict.mRoot, bigram.mWord);
- final int addressOfBigram = target.mCachedAddress;
+ final int addressOfBigram = target.mCachedAddressAfterUpdate;
final int unigramFrequencyForThisWord = target.mFrequency;
++groupAddress;
final int offset = addressOfBigram - groupAddress;
@@ -1035,9 +1115,9 @@ public final class BinaryDictInputOutput {
= FormatSpec.NO_FORWARD_LINK_ADDRESS;
index += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
}
- if (index != node.mCachedAddress + node.mCachedSize) throw new RuntimeException(
+ if (index != node.mCachedAddressAfterUpdate + node.mCachedSize) throw new RuntimeException(
"Not the same size : written "
- + (index - node.mCachedAddress) + " bytes out of a node that should have "
+ + (index - node.mCachedAddressAfterUpdate) + " bytes from a node that should have "
+ node.mCachedSize + " bytes");
return index;
}
@@ -1057,25 +1137,27 @@ public final class BinaryDictInputOutput {
int charGroups = 0;
int maxGroups = 0;
int maxRuns = 0;
- for (Node n : nodes) {
+ for (final Node n : nodes) {
if (maxGroups < n.mData.size()) maxGroups = n.mData.size();
- for (CharGroup cg : n.mData) {
+ for (final CharGroup cg : n.mData) {
++charGroups;
if (cg.mChars.length > maxRuns) maxRuns = cg.mChars.length;
if (cg.mFrequency >= 0) {
- if (n.mCachedAddress < firstTerminalAddress)
- firstTerminalAddress = n.mCachedAddress;
- if (n.mCachedAddress > lastTerminalAddress)
- lastTerminalAddress = n.mCachedAddress;
+ if (n.mCachedAddressAfterUpdate < firstTerminalAddress)
+ firstTerminalAddress = n.mCachedAddressAfterUpdate;
+ if (n.mCachedAddressAfterUpdate > lastTerminalAddress)
+ lastTerminalAddress = n.mCachedAddressAfterUpdate;
}
}
- if (n.mCachedAddress + n.mCachedSize > size) size = n.mCachedAddress + n.mCachedSize;
+ if (n.mCachedAddressAfterUpdate + n.mCachedSize > size) {
+ size = n.mCachedAddressAfterUpdate + n.mCachedSize;
+ }
}
final int[] groupCounts = new int[maxGroups + 1];
final int[] runCounts = new int[maxRuns + 1];
- for (Node n : nodes) {
+ for (final Node n : nodes) {
++groupCounts[n.mData.size()];
- for (CharGroup cg : n.mData) {
+ for (final CharGroup cg : n.mData) {
++runCounts[cg.mChars.length];
}
}
@@ -1185,7 +1267,7 @@ public final class BinaryDictInputOutput {
// Create a buffer that matches the final dictionary size.
final Node lastNode = flatNodes.get(flatNodes.size() - 1);
- final int bufferSize = lastNode.mCachedAddress + lastNode.mCachedSize;
+ final int bufferSize = lastNode.mCachedAddressAfterUpdate + lastNode.mCachedSize;
final byte[] buffer = new byte[bufferSize];
int index = 0;
@@ -1564,8 +1646,9 @@ public final class BinaryDictInputOutput {
buffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
final Node node = new Node(nodeContents);
- node.mCachedAddress = nodeOrigin;
- reverseNodeMap.put(node.mCachedAddress, node);
+ node.mCachedAddressBeforeUpdate = nodeOrigin;
+ node.mCachedAddressAfterUpdate = nodeOrigin;
+ reverseNodeMap.put(node.mCachedAddressAfterUpdate, node);
return node;
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index e1e5e5500..feadcda76 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -167,7 +167,7 @@ public final class FormatSpec {
// 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 MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
static final int PARENT_ADDRESS_SIZE = 3;
static final int FORWARD_LINK_ADDRESS_SIZE = 3;
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 17d281518..118dc22b8 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -34,6 +34,8 @@ import java.util.LinkedList;
public final class FusionDictionary implements Iterable<Word> {
private static final boolean DBG = MakedictLog.DBG;
+ private static int CHARACTER_NOT_FOUND_INDEX = -1;
+
/**
* A node of the dictionary, containing several CharGroups.
*
@@ -46,7 +48,13 @@ public final class FusionDictionary implements Iterable<Word> {
ArrayList<CharGroup> mData;
// To help with binary generation
int mCachedSize = Integer.MIN_VALUE;
- int mCachedAddress = Integer.MIN_VALUE;
+ // mCachedAddressBefore/AfterUpdate are helpers for binary dictionary generation. They
+ // always hold the same value except between dictionary address compression, during which
+ // the update process needs to know about both values at the same time. Updating will
+ // update the AfterUpdate value, and the code will move them to BeforeUpdate before
+ // the next update pass.
+ int mCachedAddressBeforeUpdate = Integer.MIN_VALUE;
+ int mCachedAddressAfterUpdate = Integer.MIN_VALUE;
int mCachedParentAddress = 0;
public Node() {
@@ -105,9 +113,15 @@ public final class FusionDictionary implements Iterable<Word> {
Node mChildren;
boolean mIsNotAWord; // Only a shortcut
boolean mIsBlacklistEntry;
- // The two following members to help with binary generation
- int mCachedSize;
- int mCachedAddress;
+ // mCachedSize and mCachedAddressBefore/AfterUpdate are helpers for binary dictionary
+ // generation. Before and After always hold the same value except during dictionary
+ // address compression, where the update process needs to know about both values at the
+ // same time. Updating will update the AfterUpdate value, and the code will move them
+ // to BeforeUpdate before the next update pass.
+ // The update process does not need two versions of mCachedSize.
+ int mCachedSize; // The size, in bytes, of this char group.
+ int mCachedAddressBeforeUpdate; // The address of this char group (before update)
+ int mCachedAddressAfterUpdate; // The address of this char group (after update)
public CharGroup(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
final ArrayList<WeightedString> bigrams, final int frequency,
@@ -450,7 +464,7 @@ public final class FusionDictionary implements Iterable<Word> {
final ArrayList<WeightedString> shortcutTargets,
final boolean isNotAWord, final boolean isBlacklistEntry) {
assert(frequency >= 0 && frequency <= 255);
- if (word.length >= Constants.Dictionary.MAX_WORD_LENGTH) {
+ if (word.length >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
MakedictLog.w("Ignoring a word that is too long: word.length = " + word.length);
return;
}
@@ -461,7 +475,7 @@ public final class FusionDictionary implements Iterable<Word> {
CharGroup currentGroup = null;
int differentCharIndex = 0; // Set by the loop to the index of the char that differs
int nodeIndex = findIndexOfChar(mRoot, word[charIndex]);
- while (CHARACTER_NOT_FOUND != nodeIndex) {
+ while (CHARACTER_NOT_FOUND_INDEX != nodeIndex) {
currentGroup = currentNode.mData.get(nodeIndex);
differentCharIndex = compareArrays(currentGroup.mChars, word, charIndex);
if (ARRAYS_ARE_EQUAL != differentCharIndex
@@ -473,7 +487,7 @@ public final class FusionDictionary implements Iterable<Word> {
nodeIndex = findIndexOfChar(currentNode, word[charIndex]);
}
- if (-1 == nodeIndex) {
+ if (CHARACTER_NOT_FOUND_INDEX == nodeIndex) {
// No node at this point to accept the word. Create one.
final int insertionIndex = findInsertionIndex(currentNode, word[charIndex]);
final CharGroup newGroup = new CharGroup(
@@ -600,20 +614,18 @@ public final class FusionDictionary implements Iterable<Word> {
return result >= 0 ? result : -result - 1;
}
- private static int CHARACTER_NOT_FOUND = -1;
-
/**
* Find the index of a char in a node, if it exists.
*
* @param node the node to search in.
* @param character the character to search for.
- * @return the position of the character if it's there, or CHARACTER_NOT_FOUND = -1 else.
+ * @return the position of the character if it's there, or CHARACTER_NOT_FOUND_INDEX = -1 else.
*/
private static int findIndexOfChar(final Node node, int character) {
final int insertionIndex = findInsertionIndex(node, character);
- if (node.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND;
+ if (node.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND_INDEX;
return character == node.mData.get(insertionIndex).mChars[0] ? insertionIndex
- : CHARACTER_NOT_FOUND;
+ : CHARACTER_NOT_FOUND_INDEX;
}
/**
@@ -628,7 +640,7 @@ public final class FusionDictionary implements Iterable<Word> {
CharGroup currentGroup;
do {
int indexOfGroup = findIndexOfChar(node, codePoints[index]);
- if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
+ if (CHARACTER_NOT_FOUND_INDEX == indexOfGroup) return null;
currentGroup = node.mData.get(indexOfGroup);
if (codePoints.length - index < currentGroup.mChars.length) return null;
diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
index 93687e193..a446672cb 100644
--- a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
+++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
@@ -23,6 +23,7 @@ import android.util.Patterns;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
public class AccountUtils {
private AccountUtils() {
@@ -44,4 +45,22 @@ public class AccountUtils {
}
return retval;
}
+
+ /**
+ * Get all device accounts having specified domain name.
+ * @param context application context
+ * @param domain domain name used for filtering
+ * @return List of account names that contain the specified domain name
+ */
+ public static List<String> getDeviceAccountsWithDomain(
+ final Context context, final String domain) {
+ final ArrayList<String> retval = new ArrayList<String>();
+ final String atDomain = "@" + domain.toLowerCase(Locale.ROOT);
+ for (final Account account : getAccounts(context)) {
+ if (account.name.toLowerCase(Locale.ROOT).endsWith(atDomain)) {
+ retval.add(account.name);
+ }
+ }
+ return retval;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index 528028328..9d041f4eb 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.personalization;
import android.content.Context;
import android.content.SharedPreferences;
@@ -23,33 +23,40 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.ExpandableDictionary;
+import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.UserHistoryDictIOUtils.BigramDictionaryInterface;
-import com.android.inputmethod.latin.UserHistoryDictIOUtils.OnAddWordListener;
-import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
+import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.utils.ByteArrayWrapper;
+import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
+import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface;
+import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener;
+import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils;
+import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.lang.ref.SoftReference;
import java.util.ArrayList;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
/**
- * 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.
+ * This class is a base class of a dictionary for the personalized prediction language model.
*/
-public final class UserHistoryDictionary extends ExpandableDictionary {
- private static final String TAG = UserHistoryDictionary.class.getSimpleName();
- private static final String NAME = UserHistoryDictionary.class.getSimpleName();
+public abstract class DynamicPredictionDictionaryBase extends ExpandableDictionary {
+ public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) {
+ // TODO: Implement
+ }
+
+ private static final String TAG = DynamicPredictionDictionaryBase.class.getSimpleName();
public static final boolean DBG_SAVE_RESTORE = false;
- public static final boolean DBG_STRESS_TEST = false;
- public static final boolean DBG_ALWAYS_WRITE = false;
- public static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
+ private static final boolean DBG_STRESS_TEST = false;
+ private static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
private static final FormatOptions VERSION3 = new FormatOptions(3,
true /* supportsDynamicUpdate */);
@@ -58,14 +65,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
private static final int FREQUENCY_FOR_TYPED = 2;
/** Maximum number of pairs. Pruning will start when databases goes above this number. */
- public static final int MAX_HISTORY_BIGRAMS = 10000;
-
- /**
- * When it hits maximum bigram pair, it will delete until you are left with
- * only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
- * Do not keep this number small to avoid deleting too often.
- */
- public static final int DELETE_HISTORY_BIGRAMS = 1000;
+ private static final int MAX_HISTORY_BIGRAMS = 10000;
/** Locale for which this user history dictionary is storing words */
private final String mLocale;
@@ -78,30 +78,9 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
// Should always be false except when we use this class for test
@UsedForTesting boolean isTest = false;
- private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
- sLangDictCache = CollectionUtils.newConcurrentHashMap();
-
- public static synchronized UserHistoryDictionary getInstance(
- final Context context, final String locale, final SharedPreferences sp) {
- if (sLangDictCache.containsKey(locale)) {
- final SoftReference<UserHistoryDictionary> ref = sLangDictCache.get(locale);
- final UserHistoryDictionary dict = ref == null ? null : ref.get();
- if (dict != null) {
- if (PROFILE_SAVE_RESTORE) {
- Log.w(TAG, "Use cached UserHistoryDictionary for " + locale);
- }
- return dict;
- }
- }
- final UserHistoryDictionary dict =
- new UserHistoryDictionary(context, locale, sp);
- sLangDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict));
- return dict;
- }
-
- private UserHistoryDictionary(final Context context, final String locale,
- final SharedPreferences sp) {
- super(context, Dictionary.TYPE_USER_HISTORY);
+ /* package */ DynamicPredictionDictionaryBase(final Context context, final String locale,
+ final SharedPreferences sp, final String dictionaryType) {
+ super(context, dictionaryType);
mLocale = locale;
mPrefs = sp;
if (mLocale != null && mLocale.length() > 1) {
@@ -116,8 +95,8 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
// Also, the database is written to somewhat frequently, so it needs to be kept alive
// throughout the life of the process.
// mOpenHelper.close();
- // Ignore close because we cache UserHistoryDictionary for each language. See getInstance()
- // above.
+ // Ignore close because we cache PersonalizationPredictionDictionary for each language.
+ // See getInstance() above.
// super.close();
}
@@ -147,8 +126,8 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
* The second word may not be null (a NullPointerException would be thrown).
*/
public int addToUserHistory(final String word1, final String word2, final boolean isValid) {
- if (word2.length() >= Constants.Dictionary.MAX_WORD_LENGTH ||
- (word1 != null && word1.length() >= Constants.Dictionary.MAX_WORD_LENGTH)) {
+ if (word2.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
+ (word1 != null && word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
return -1;
}
if (mBigramListLock.tryLock()) {
@@ -198,7 +177,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
}
@Override
- public void loadDictionaryAsync() {
+ public final void loadDictionaryAsync() {
// This must be run on non-main thread
mBigramListLock.lock();
try {
@@ -208,48 +187,47 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
}
}
- private int profTotal;
-
private void loadDictionaryAsyncLocked() {
+ final int[] profTotalCount = { 0 };
+ final String locale = getLocale();
if (DBG_STRESS_TEST) {
try {
- Log.w(TAG, "Start stress in loading: " + mLocale);
+ Log.w(TAG, "Start stress in loading: " + locale);
Thread.sleep(15000);
Log.w(TAG, "End stress in loading");
} catch (InterruptedException e) {
}
}
- final long last = Settings.readLastUserHistoryWriteTime(mPrefs, mLocale);
+ final long last = Settings.readLastUserHistoryWriteTime(mPrefs, locale);
final boolean initializing = last == 0;
final long now = System.currentTimeMillis();
- profTotal = 0;
- final String fileName = NAME + "." + mLocale + ".dict";
+ final String fileName = getDictionaryFileName();
final ExpandableDictionary dictionary = this;
final OnAddWordListener listener = new OnAddWordListener() {
@Override
public void setUnigram(final String word, final String shortcutTarget,
final int frequency) {
- profTotal++;
if (DBG_SAVE_RESTORE) {
Log.d(TAG, "load unigram: " + word + "," + frequency);
}
dictionary.addWord(word, shortcutTarget, frequency);
- mBigramList.addBigram(null, word, (byte)frequency);
+ ++profTotalCount[0];
+ addToBigramListLocked(null, word, (byte)frequency);
}
@Override
public void setBigram(final String word1, final String word2, final int frequency) {
- if (word1.length() < Constants.Dictionary.MAX_WORD_LENGTH
- && word2.length() < Constants.Dictionary.MAX_WORD_LENGTH) {
- profTotal++;
+ if (word1.length() < Constants.DICTIONARY_MAX_WORD_LENGTH
+ && word2.length() < Constants.DICTIONARY_MAX_WORD_LENGTH) {
if (DBG_SAVE_RESTORE) {
Log.d(TAG, "load bigram: " + word1 + "," + word2 + "," + frequency);
}
+ ++profTotalCount[0];
dictionary.setBigramAndGetFrequency(
word1, word2, initializing ? new ForgettingCurveParams(true)
: new ForgettingCurveParams(frequency, now, last));
}
- mBigramList.addBigram(word1, word2, (byte)frequency);
+ addToBigramListLocked(word1, word2, (byte)frequency);
}
};
@@ -261,7 +239,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
inStream = new FileInputStream(file);
inStream.read(buffer);
UserHistoryDictIOUtils.readDictionaryBinary(
- new UserHistoryDictIOUtils.ByteArrayWrapper(buffer), listener);
+ new ByteArrayWrapper(buffer), listener);
} catch (FileNotFoundException e) {
// This is an expected condition: we don't have a user history dictionary for this
// language yet. It will be created sometime later.
@@ -278,11 +256,21 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
if (PROFILE_SAVE_RESTORE) {
final long diff = System.currentTimeMillis() - now;
Log.d(TAG, "PROF: Load UserHistoryDictionary: "
- + mLocale + ", " + diff + "ms. load " + profTotal + "entries.");
+ + locale + ", " + diff + "ms. load " + profTotalCount[0] + "entries.");
}
}
}
+ protected abstract String getDictionaryFileName();
+
+ protected String getLocale() {
+ return mLocale;
+ }
+
+ private void addToBigramListLocked(String word0, String word1, byte fcValue) {
+ mBigramList.addBigram(word0, word1, fcValue);
+ }
+
/**
* Async task to write pending words to the binarydicts.
*/
@@ -291,16 +279,16 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
private final UserHistoryDictionaryBigramList mBigramList;
private final boolean mAddLevel0Bigrams;
private final String mLocale;
- private final UserHistoryDictionary mUserHistoryDictionary;
+ private final DynamicPredictionDictionaryBase mDynamicPredictionDictionary;
private final SharedPreferences mPrefs;
private final Context mContext;
public UpdateBinaryTask(final UserHistoryDictionaryBigramList pendingWrites,
- final String locale, final UserHistoryDictionary dict,
+ final String locale, final DynamicPredictionDictionaryBase dict,
final SharedPreferences prefs, final Context context) {
mBigramList = pendingWrites;
mLocale = locale;
- mUserHistoryDictionary = dict;
+ mDynamicPredictionDictionary = dict;
mPrefs = prefs;
mContext = context;
mAddLevel0Bigrams = mBigramList.size() <= MAX_HISTORY_BIGRAMS;
@@ -308,16 +296,20 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
@Override
protected Void doInBackground(final Void... v) {
- if (mUserHistoryDictionary.isTest) {
+ if (mDynamicPredictionDictionary.isTest) {
// If isTest == true, wait until the lock is released.
- mUserHistoryDictionary.mBigramListLock.lock();
+ mDynamicPredictionDictionary.mBigramListLock.lock();
+ try {
+ doWriteTaskLocked();
+ } finally {
+ mDynamicPredictionDictionary.mBigramListLock.unlock();
+ }
+ } else if (mDynamicPredictionDictionary.mBigramListLock.tryLock()) {
try {
doWriteTaskLocked();
} finally {
- mUserHistoryDictionary.mBigramListLock.unlock();
+ mDynamicPredictionDictionary.mBigramListLock.unlock();
}
- } else if (mUserHistoryDictionary.mBigramListLock.tryLock()) {
- doWriteTaskLocked();
}
return null;
}
@@ -334,7 +326,8 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
}
final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0;
- final String fileName = NAME + "." + mLocale + ".dict";
+ final String fileName =
+ mDynamicPredictionDictionary.getDictionaryFileName();
final File file = new File(mContext.getFilesDir(), fileName);
FileOutputStream out = null;
@@ -370,7 +363,8 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
freq = FREQUENCY_FOR_TYPED;
final byte prevFc = mBigramList.getBigrams(word1).get(word2);
} else { // bigram
- final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2);
+ final NextWord nw =
+ mDynamicPredictionDictionary.getBigramWord(word1, word2);
if (nw != null) {
final ForgettingCurveParams fcp = nw.getFcParams();
final byte prevFc = mBigramList.getBigrams(word1).get(word2);
@@ -395,7 +389,8 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
}
@UsedForTesting
- void forceAddWordForTest(final String word1, final String word2, final boolean isValid) {
+ /* package for test */ void forceAddWordForTest(
+ final String word1, final String word2, final boolean isValid) {
mBigramListLock.lock();
try {
addToUserHistory(word1, word2, isValid);
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
new file mode 100644
index 000000000..19554d639
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+
+import android.content.Context;
+
+/**
+ * This class is a dictionary for the personalized language model that uses binary dictionary.
+ */
+public class PersonalizationDictionary extends ExpandableBinaryDictionary {
+ private static final String NAME = "personalization";
+
+ public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) {
+ // TODO: Implement
+ }
+
+ /** Locale for which this user history dictionary is storing words */
+ private final String mLocale;
+
+ // Singleton
+ private PersonalizationDictionary(final Context context, final String locale) {
+ super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_PERSONALIZATION);
+ mLocale = locale;
+ }
+
+ @Override
+ protected void loadDictionaryAsync() {
+ // TODO: Implement
+ }
+
+ @Override
+ protected boolean hasContentChanged() {
+ // TODO: Implement
+ return false;
+ }
+
+ @Override
+ protected boolean needsToReloadBeforeWriting() {
+ // TODO: Implement
+ return false;
+ }
+
+ // TODO: Implement
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
new file mode 100644
index 000000000..9f013df1c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import java.lang.ref.SoftReference;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PersonalizationDictionaryHelper {
+ private static final String TAG = PersonalizationDictionaryHelper.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final ConcurrentHashMap<String, SoftReference<UserHistoryPredictionDictionary>>
+ sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
+
+ private static final ConcurrentHashMap<String,
+ SoftReference<PersonalizationPredictionDictionary>>
+ sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
+
+ public static UserHistoryPredictionDictionary getUserHistoryPredictionDictionary(
+ final Context context, final String locale, final SharedPreferences sp) {
+ synchronized (sLangUserHistoryDictCache) {
+ if (sLangUserHistoryDictCache.containsKey(locale)) {
+ final SoftReference<UserHistoryPredictionDictionary> ref =
+ sLangUserHistoryDictCache.get(locale);
+ final UserHistoryPredictionDictionary dict = ref == null ? null : ref.get();
+ if (dict != null) {
+ if (DEBUG) {
+ Log.w(TAG, "Use cached UserHistoryPredictionDictionary for " + locale);
+ }
+ return dict;
+ }
+ }
+ final UserHistoryPredictionDictionary dict =
+ new UserHistoryPredictionDictionary(context, locale, sp);
+ sLangUserHistoryDictCache.put(
+ locale, new SoftReference<UserHistoryPredictionDictionary>(dict));
+ return dict;
+ }
+ }
+
+ public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
+ final Context context, final String locale, final SharedPreferences sp) {
+ synchronized (sLangPersonalizationDictCache) {
+ if (sLangPersonalizationDictCache.containsKey(locale)) {
+ final SoftReference<PersonalizationPredictionDictionary> ref =
+ sLangPersonalizationDictCache.get(locale);
+ final PersonalizationPredictionDictionary dict = ref == null ? null : ref.get();
+ if (dict != null) {
+ if (DEBUG) {
+ Log.w(TAG, "Use cached PersonalizationPredictionDictionary for " + locale);
+ }
+ return dict;
+ }
+ }
+ final PersonalizationPredictionDictionary dict =
+ new PersonalizationPredictionDictionary(context, locale, sp);
+ sLangPersonalizationDictCache.put(
+ locale, new SoftReference<PersonalizationPredictionDictionary>(dict));
+ return dict;
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
new file mode 100644
index 000000000..c78e5a95b
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
@@ -0,0 +1,21 @@
+/*
+ * 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;
+
+public interface PersonalizationDictionaryUpdateListener {
+ // TODO: Implement
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
new file mode 100644
index 000000000..955bd2762
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import com.android.inputmethod.latin.Dictionary;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class PersonalizationPredictionDictionary extends DynamicPredictionDictionaryBase {
+ private static final String NAME = PersonalizationPredictionDictionary.class.getSimpleName();
+
+ /* package */ PersonalizationPredictionDictionary(final Context context, final String locale,
+ final SharedPreferences sp) {
+ super(context, locale, sp, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
+ }
+
+ @Override
+ protected String getDictionaryFileName() {
+ return NAME + "." + getLocale() + ".dict";
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
index 316f09603..f21db25a6 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.personalization;
import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.HashMap;
import java.util.Set;
@@ -52,7 +53,7 @@ public final class UserHistoryDictionaryBigramList {
* Called when loaded from the SQL DB.
*/
public void addBigram(String word1, String word2, byte fcValue) {
- if (UserHistoryDictionary.DBG_SAVE_RESTORE) {
+ if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) {
Log.d(TAG, "--- add bigram: " + word1 + ", " + word2 + ", " + fcValue);
}
final HashMap<String, Byte> map;
@@ -72,7 +73,7 @@ public final class UserHistoryDictionaryBigramList {
* Called when inserted to the SQL DB.
*/
public void updateBigram(String word1, String word2, byte fcValue) {
- if (UserHistoryDictionary.DBG_SAVE_RESTORE) {
+ if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) {
Log.d(TAG, "--- update bigram: " + word1 + ", " + word2 + ", " + fcValue);
}
final HashMap<String, Byte> map;
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
new file mode 100644
index 000000000..d11784454
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import com.android.inputmethod.latin.Dictionary;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Locally gathers stats about the words user types and various other signals like auto-correction
+ * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
+ */
+public class UserHistoryPredictionDictionary extends DynamicPredictionDictionaryBase {
+ private static final String NAME = UserHistoryPredictionDictionary.class.getSimpleName();
+ /* package */ UserHistoryPredictionDictionary(final Context context, final String locale,
+ final SharedPreferences sp) {
+ super(context, locale, sp, Dictionary.TYPE_USER_HISTORY);
+ }
+
+ @Override
+ protected String getDictionaryFileName() {
+ return NAME + "." + getLocale() + ".dict";
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java b/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java
new file mode 100644
index 000000000..139f5e290
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.android.inputmethodcommon.InputMethodSettingsFragment;
+
+/**
+ * Utility class for managing additional features settings.
+ */
+public class AdditionalFeaturesSettingUtils {
+ public static final int ADDITIONAL_FEATURES_SETTINGS_SIZE = 0;
+
+ private AdditionalFeaturesSettingUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static void addAdditionalFeaturesPreferences(
+ final Context context, final InputMethodSettingsFragment settingsFragment) {
+ // do nothing.
+ }
+
+ public static void readAdditionalFeaturesPreferencesIntoArray(
+ final SharedPreferences prefs, final int[] additionalFeaturesPreferences) {
+ // do nothing.
+ }
+
+ public static int[] getAdditionalNativeSuggestOptions() {
+ return Settings.getInstance().getCurrent().mAdditionalFeaturesSettingValues;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
index ff5e33949..4bf524cbb 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.ASCII_CAPABLE;
@@ -44,6 +44,13 @@ import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.Toast;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.IntentUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
import java.util.ArrayList;
import java.util.TreeSet;
@@ -71,7 +78,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
public SubtypeLocaleItem(final String localeString) {
this(localeString,
- SubtypeLocale.getSubtypeLocaleDisplayNameInSystemLocale(localeString));
+ SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale(localeString));
}
@Override
@@ -102,7 +109,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
if (DEBUG_SUBTYPE_ID) {
android.util.Log.d(TAG, String.format("%-6s 0x%08x %11d %s",
subtype.getLocale(), subtype.hashCode(), subtype.hashCode(),
- SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype)));
+ SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)));
}
if (subtype.containsExtraValueKey(ASCII_CAPABLE)) {
items.add(createItem(context, subtype.getLocale()));
@@ -114,7 +121,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
public static SubtypeLocaleItem createItem(final Context context,
final String localeString) {
- if (localeString.equals(SubtypeLocale.NO_LANGUAGE)) {
+ if (localeString.equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
final String displayName = context.getString(R.string.subtype_no_language);
return new SubtypeLocaleItem(localeString, displayName);
} else {
@@ -125,8 +132,8 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
static final class KeyboardLayoutSetItem extends Pair<String, String> {
public KeyboardLayoutSetItem(final InputMethodSubtype subtype) {
- super(SubtypeLocale.getKeyboardLayoutSetName(subtype),
- SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype));
+ super(SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype),
+ SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype));
}
@Override
@@ -141,10 +148,10 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// TODO: Should filter out already existing combinations of locale and layout.
- for (final String layout : SubtypeLocale.getPredefinedKeyboardLayoutSet()) {
+ for (final String layout : SubtypeLocaleUtils.getPredefinedKeyboardLayoutSet()) {
// This is a dummy subtype with NO_LANGUAGE, only for display.
- final InputMethodSubtype subtype = AdditionalSubtype.createAdditionalSubtype(
- SubtypeLocale.NO_LANGUAGE, layout, null);
+ final InputMethodSubtype subtype = AdditionalSubtypeUtils.createAdditionalSubtype(
+ SubtypeLocaleUtils.NO_LANGUAGE, layout, null);
add(new KeyboardLayoutSetItem(subtype));
}
}
@@ -205,11 +212,11 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
setKey(KEY_NEW_SUBTYPE);
} else {
final String displayName =
- SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype);
+ SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype);
setTitle(displayName);
setDialogTitle(displayName);
setKey(KEY_PREFIX + subtype.getLocale() + "_"
- + SubtypeLocale.getKeyboardLayoutSetName(subtype));
+ + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype));
}
}
@@ -279,7 +286,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
(SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem();
final KeyboardLayoutSetItem layout =
(KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem();
- final InputMethodSubtype subtype = AdditionalSubtype.createAdditionalSubtype(
+ final InputMethodSubtype subtype = AdditionalSubtypeUtils.createAdditionalSubtype(
locale.first, layout.first, ASCII_CAPABLE);
setSubtype(subtype);
notifyChanged();
@@ -497,13 +504,13 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
final Context context = getActivity();
final Resources res = context.getResources();
final String message = res.getString(R.string.custom_input_style_already_exists,
- SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype));
+ SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype));
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
private InputMethodSubtype findDuplicatedSubtype(final InputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
- final String keyboardLayoutSetName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
+ final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
return mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
localeString, keyboardLayoutSetName);
}
@@ -536,7 +543,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
final PreferenceGroup group = getPreferenceScreen();
group.removeAll();
final InputMethodSubtype[] subtypesArray =
- AdditionalSubtype.createAdditionalSubtypesArray(prefSubtypes);
+ AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtypes);
for (final InputMethodSubtype subtype : subtypesArray) {
final SubtypePreference pref = new SubtypePreference(
context, subtype, mSubtypeProxy);
@@ -565,7 +572,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
super.onPause();
final String oldSubtypes = Settings.readPrefAdditionalSubtypes(mPrefs, getResources());
final InputMethodSubtype[] subtypes = getSubtypes();
- final String prefSubtypes = AdditionalSubtype.createPrefSubtypes(subtypes);
+ final String prefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(subtypes);
if (prefSubtypes.equals(oldSubtypes)) {
return;
}
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index 5969a63de..b1cd88729 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -14,31 +14,31 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
-import android.content.Context;
import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Process;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
-import android.util.Log;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.research.ResearchLogger;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.debug.ExternalDictionaryGetterForDebug;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
public final class DebugSettings extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
- private static final String TAG = DebugSettings.class.getSimpleName();
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_USABILITY_STUDY_MODE = "usability_study_mode";
public static final String PREF_STATISTICS_LOGGING = "enable_logging";
+ public static final String PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG =
+ "use_only_personalization_dictionary_for_debug";
private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary";
private static final boolean SHOW_STATISTICS_LOGGING = false;
@@ -68,7 +68,7 @@ public final class DebugSettings extends PreferenceFragment
}
}
- PreferenceScreen readExternalDictionary =
+ final PreferenceScreen readExternalDictionary =
(PreferenceScreen) findPreference(PREF_READ_EXTERNAL_DICTIONARY);
if (null != readExternalDictionary) {
readExternalDictionary.setOnPreferenceClickListener(
@@ -113,6 +113,8 @@ public final class DebugSettings extends PreferenceFragment
} else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)
|| key.equals(KeyboardSwitcher.PREF_KEYBOARD_LAYOUT)) {
mServiceNeedsRestart = true;
+ } else if (key.equals(PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG)) {
+ mServiceNeedsRestart = true;
}
}
@@ -122,7 +124,7 @@ public final class DebugSettings extends PreferenceFragment
}
boolean isDebugMode = mDebugMode.isChecked();
final String version = getResources().getString(
- R.string.version_text, Utils.getVersionName(getActivity()));
+ R.string.version_text, ApplicationUtils.getVersionName(getActivity()));
if (!isDebugMode) {
mDebugMode.setTitle(version);
mDebugMode.setSummary("");
diff --git a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java b/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java
index e1b5a802e..b499c26b6 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceActivity;
+import com.android.inputmethod.latin.R;
+
public final class DebugSettingsActivity extends PreferenceActivity {
private static final String DEFAULT_FRAGMENT = DebugSettings.class.getName();
diff --git a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
new file mode 100644
index 000000000..878c505bd
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
@@ -0,0 +1,53 @@
+/*
+ * 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 OPTIONS_SIZE = 2;
+
+ 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 setAdditionalFeaturesOptions(final int[] additionalOptions) {
+ 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/SeekBarDialogPreference.java b/java/src/com/android/inputmethod/latin/settings/SeekBarDialogPreference.java
index 7c4156c48..802574aa8 100644
--- a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
+++ b/java/src/com/android/inputmethod/latin/settings/SeekBarDialogPreference.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
import android.app.AlertDialog;
import android.content.Context;
@@ -26,16 +26,19 @@ import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;
+import com.android.inputmethod.latin.R;
+
public final class SeekBarDialogPreference extends DialogPreference
implements SeekBar.OnSeekBarChangeListener {
public interface ValueProxy {
public int readValue(final String key);
public int readDefaultValue(final String key);
public void writeValue(final int value, final String key);
+ public void writeDefaultValue(final String key);
+ public String getValueText(final int value);
public void feedbackValue(final int value);
}
- private final int mValueFormatResId;
private final int mMaxValue;
private final int mMinValue;
private final int mStepValue;
@@ -49,7 +52,6 @@ public final class SeekBarDialogPreference extends DialogPreference
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.SeekBarDialogPreference, 0, 0);
- mValueFormatResId = a.getResourceId(R.styleable.SeekBarDialogPreference_valueFormatText, 0);
mMaxValue = a.getInt(R.styleable.SeekBarDialogPreference_maxValue, 0);
mMinValue = a.getInt(R.styleable.SeekBarDialogPreference_minValue, 0);
mStepValue = a.getInt(R.styleable.SeekBarDialogPreference_stepValue, 0);
@@ -59,15 +61,8 @@ public final class SeekBarDialogPreference extends DialogPreference
public void setInterface(final ValueProxy proxy) {
mValueProxy = proxy;
- setSummary(getValueText(clipValue(proxy.readValue(getKey()))));
- }
-
- private String getValueText(final int value) {
- if (mValueFormatResId == 0) {
- return Integer.toString(value);
- } else {
- return getContext().getString(mValueFormatResId, value);
- }
+ final int value = mValueProxy.readValue(getKey());
+ setSummary(mValueProxy.getValueText(value));
}
@Override
@@ -100,16 +95,11 @@ public final class SeekBarDialogPreference extends DialogPreference
return clipValue(getValueFromProgress(progress));
}
- private void setValue(final int value, final boolean fromUser) {
- mValueView.setText(getValueText(value));
- if (!fromUser) {
- mSeekBar.setProgress(getProgressFromValue(value));
- }
- }
-
@Override
protected void onBindDialogView(final View view) {
- setValue(clipValue(mValueProxy.readValue(getKey())), false /* fromUser */);
+ final int value = mValueProxy.readValue(getKey());
+ mValueView.setText(mValueProxy.getValueText(value));
+ mSeekBar.setProgress(getProgressFromValue(clipValue(value)));
}
@Override
@@ -122,19 +112,29 @@ public final class SeekBarDialogPreference extends DialogPreference
@Override
public void onClick(final DialogInterface dialog, final int which) {
super.onClick(dialog, which);
+ final String key = getKey();
if (which == DialogInterface.BUTTON_NEUTRAL) {
- setValue(clipValue(mValueProxy.readDefaultValue(getKey())), false /* fromUser */);
+ final int value = mValueProxy.readDefaultValue(key);
+ setSummary(mValueProxy.getValueText(value));
+ mValueProxy.writeDefaultValue(key);
+ return;
}
- if (which != DialogInterface.BUTTON_NEGATIVE) {
- setSummary(mValueView.getText());
- mValueProxy.writeValue(getClippedValueFromProgress(mSeekBar.getProgress()), getKey());
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ final int value = getClippedValueFromProgress(mSeekBar.getProgress());
+ setSummary(mValueProxy.getValueText(value));
+ mValueProxy.writeValue(value, key);
+ return;
}
}
@Override
public void onProgressChanged(final SeekBar seekBar, final int progress,
final boolean fromUser) {
- setValue(getClippedValueFromProgress(progress), fromUser);
+ final int value = getClippedValueFromProgress(progress);
+ mValueView.setText(mValueProxy.getValueText(value));
+ if (!fromUser) {
+ mSeekBar.setProgress(getProgressFromValue(value));
+ }
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 9fefb58a6..d432087d3 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -14,20 +14,30 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.preference.PreferenceManager;
+import android.util.Log;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
+import com.android.inputmethod.latin.InputAttributes;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+import com.android.inputmethod.latin.utils.RunInLocale;
import java.util.HashMap;
import java.util.Locale;
+import java.util.concurrent.locks.ReentrantLock;
public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
+ private static final String TAG = Settings.class.getSimpleName();
// In the same order as xml/prefs.xml
public static final String PREF_GENERAL_SETTINGS = "general_settings";
public static final String PREF_AUTO_CAP = "auto_cap";
@@ -85,8 +95,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
private Resources mRes;
private SharedPreferences mPrefs;
- private Locale mCurrentLocale;
private SettingsValues mSettingsValues;
+ private final ReentrantLock mSettingsValuesLock = new ReentrantLock();
private static final Settings sInstance = new Settings();
@@ -114,19 +124,34 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
@Override
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
- loadSettings(mCurrentLocale, mSettingsValues.mInputAttributes);
+ mSettingsValuesLock.lock();
+ try {
+ if (mSettingsValues == null) {
+ // TODO: Introduce a static function to register this class and ensure that
+ // loadSettings must be called before "onSharedPreferenceChanged" is called.
+ Log.w(TAG, "onSharedPreferenceChanged called before loadSettings.");
+ return;
+ }
+ loadSettings(mSettingsValues.mLocale, mSettingsValues.mInputAttributes);
+ } finally {
+ mSettingsValuesLock.unlock();
+ }
}
public void loadSettings(final Locale locale, final InputAttributes inputAttributes) {
- mCurrentLocale = locale;
- final SharedPreferences prefs = mPrefs;
- final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
- @Override
- protected SettingsValues job(final Resources res) {
- return new SettingsValues(prefs, res, inputAttributes);
- }
- };
- mSettingsValues = job.runInLocale(mRes, locale);
+ mSettingsValuesLock.lock();
+ try {
+ final SharedPreferences prefs = mPrefs;
+ final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
+ @Override
+ protected SettingsValues job(final Resources res) {
+ return new SettingsValues(prefs, locale, res, inputAttributes);
+ }
+ };
+ mSettingsValues = job.runInLocale(mRes, locale);
+ } finally {
+ mSettingsValuesLock.unlock();
+ }
}
// TODO: Remove this method and add proxy method to SettingsValues.
@@ -142,8 +167,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return mSettingsValues.mWordSeparators;
}
- public Locale getCurrentLocale() {
- return mCurrentLocale;
+ public boolean isWordSeparator(final int code) {
+ return mSettingsValues.isWordSeparator(code);
}
public boolean getBlockPotentiallyOffensive() {
@@ -223,7 +248,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static String readPrefAdditionalSubtypes(final SharedPreferences prefs,
final Resources res) {
- final String predefinedPrefSubtypes = AdditionalSubtype.createPrefSubtypes(
+ final String predefinedPrefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(
res.getStringArray(R.array.predefined_subtypes));
return prefs.getString(PREF_CUSTOM_INPUT_STYLES, predefinedPrefSubtypes);
}
@@ -312,4 +337,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static boolean isInternal(final SharedPreferences prefs) {
return prefs.getBoolean(Settings.PREF_KEY_IS_INTERNAL, false);
}
+
+ public static boolean readUseOnlyPersonalizationDictionaryForDebug(
+ final SharedPreferences prefs) {
+ return prefs.getBoolean(
+ DebugSettings.PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/SettingsActivity.java b/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java
index 37ac2e35c..6c3818651 100644
--- a/java/src/com/android/inputmethod/latin/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
import android.content.Intent;
import android.preference.PreferenceActivity;
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 835ef7b46..446777704 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
import android.app.Activity;
import android.app.backup.BackupManager;
@@ -25,6 +25,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.media.AudioManager;
+import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
@@ -32,20 +33,33 @@ import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
+import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
-import java.util.TreeSet;
-
import com.android.inputmethod.dictionarypack.DictionarySettingsActivity;
+import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager;
import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
+import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
+import com.android.inputmethod.latin.utils.FeedbackUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import com.android.inputmethod.research.ResearchLogger;
import com.android.inputmethodcommon.InputMethodSettingsFragment;
+import java.util.TreeSet;
+
public final class SettingsFragment extends InputMethodSettingsFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
- private static final boolean DBG_USE_INTERNAL_USER_DICTIONARY_SETTINGS = false;
+ private static final String TAG = SettingsFragment.class.getSimpleName();
+ private static final boolean DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS = false;
+ private static final boolean USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS =
+ DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
+ || Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */;
private ListPreference mVoicePreference;
private ListPreference mShowCorrectionSuggestionsPreference;
@@ -80,7 +94,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
preferenceScreen.setTitle(
- Utils.getAcitivityTitleResId(getActivity(), SettingsActivity.class));
+ ApplicationUtils.getAcitivityTitleResId(getActivity(), SettingsActivity.class));
}
final Resources res = getResources();
@@ -90,7 +104,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
// 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);
- SubtypeLocale.init(context);
+ SubtypeLocaleUtils.init(context);
AudioAndHapticFeedbackManager.init(context);
mVoicePreference = (ListPreference) findPreference(Settings.PREF_VOICE_MODE);
@@ -128,7 +142,12 @@ public final class SettingsFragment extends InputMethodSettingsFragment
feedbackSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference pref) {
- FeedbackUtils.showFeedbackForm(getActivity());
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ // Use development-only feedback mechanism
+ ResearchLogger.getInstance().presentFeedbackDialogFromSettings();
+ } else {
+ FeedbackUtils.showFeedbackForm(getActivity());
+ }
return true;
}
});
@@ -139,6 +158,10 @@ public final class SettingsFragment extends InputMethodSettingsFragment
miscSettings.removePreference(aboutSettings);
}
}
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ // The about screen contains items that may be confusing in development-only versions.
+ miscSettings.removePreference(aboutSettings);
+ }
final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);
@@ -190,23 +213,24 @@ public final class SettingsFragment extends InputMethodSettingsFragment
final Intent intent = dictionaryLink.getIntent();
intent.setClassName(context.getPackageName(), DictionarySettingsActivity.class.getName());
final int number = context.getPackageManager().queryIntentActivities(intent, 0).size();
- // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
- // Service yet
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS || 0 >= number) {
+ if (0 >= number) {
textCorrectionGroup.removePreference(dictionaryLink);
}
final Preference editPersonalDictionary =
findPreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY);
final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent();
- final ResolveInfo ri = context.getPackageManager().resolveActivity(
- editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY);
- if (DBG_USE_INTERNAL_USER_DICTIONARY_SETTINGS || ri == null) {
- updateUserDictionaryPreference(editPersonalDictionary);
+ final ResolveInfo ri = USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS ? null
+ : context.getPackageManager().resolveActivity(
+ editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (ri == null) {
+ overwriteUserDictionaryPreference(editPersonalDictionary);
}
if (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) {
removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen());
+ } else {
+ AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this);
}
setupKeyLongpressTimeoutSettings(prefs, res);
@@ -244,7 +268,14 @@ public final class SettingsFragment extends InputMethodSettingsFragment
@Override
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
- (new BackupManager(getActivity())).dataChanged();
+ final Activity activity = getActivity();
+ if (activity == null) {
+ // TODO: Introduce a static function to register this class and ensure that
+ // onCreate must be called before "onSharedPreferenceChanged" is called.
+ Log.w(TAG, "onSharedPreferenceChanged called before activity starts.");
+ return;
+ }
+ (new BackupManager(activity)).dataChanged();
final Resources res = getResources();
if (key.equals(Settings.PREF_POPUP_ON)) {
setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
@@ -283,11 +314,11 @@ public final class SettingsFragment extends InputMethodSettingsFragment
final Resources res = getResources();
final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res);
final InputMethodSubtype[] subtypes =
- AdditionalSubtype.createAdditionalSubtypesArray(prefSubtype);
+ AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype);
final StringBuilder styles = new StringBuilder();
for (final InputMethodSubtype subtype : subtypes) {
if (styles.length() > 0) styles.append(", ");
- styles.append(SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype));
+ styles.append(SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype));
}
customInputStyles.setSummary(styles);
}
@@ -327,6 +358,11 @@ public final class SettingsFragment extends InputMethodSettingsFragment
}
@Override
+ public void writeDefaultValue(final String key) {
+ sp.edit().remove(key).apply();
+ }
+
+ @Override
public int readValue(final String key) {
return Settings.readKeypressVibrationDuration(sp, res);
}
@@ -340,6 +376,14 @@ public final class SettingsFragment extends InputMethodSettingsFragment
public void feedbackValue(final int value) {
AudioAndHapticFeedbackManager.getInstance().vibrate(value);
}
+
+ @Override
+ public String getValueText(final int value) {
+ if (value < 0) {
+ return res.getString(R.string.settings_system_default);
+ }
+ return res.getString(R.string.abbreviation_unit_milliseconds, value);
+ }
});
}
@@ -357,6 +401,11 @@ public final class SettingsFragment extends InputMethodSettingsFragment
}
@Override
+ public void writeDefaultValue(final String key) {
+ sp.edit().remove(key).apply();
+ }
+
+ @Override
public int readValue(final String key) {
return Settings.readKeyLongpressTimeout(sp, res);
}
@@ -367,6 +416,11 @@ public final class SettingsFragment extends InputMethodSettingsFragment
}
@Override
+ public String getValueText(final int value) {
+ return res.getString(R.string.abbreviation_unit_milliseconds, value);
+ }
+
+ @Override
public void feedbackValue(final int value) {}
});
}
@@ -395,6 +449,11 @@ public final class SettingsFragment extends InputMethodSettingsFragment
}
@Override
+ public void writeDefaultValue(final String key) {
+ sp.edit().remove(key).apply();
+ }
+
+ @Override
public int readValue(final String key) {
return getPercentageFromValue(Settings.readKeypressSoundVolume(sp, res));
}
@@ -405,6 +464,14 @@ public final class SettingsFragment extends InputMethodSettingsFragment
}
@Override
+ public String getValueText(final int value) {
+ if (value < 0) {
+ return res.getString(R.string.settings_system_default);
+ }
+ return Integer.toString(value);
+ }
+
+ @Override
public void feedbackValue(final int value) {
am.playSoundEffect(
AudioManager.FX_KEYPRESS_STANDARD, getValueFromPercentage(value));
@@ -412,7 +479,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
});
}
- private void updateUserDictionaryPreference(Preference userDictionaryPreference) {
+ private void overwriteUserDictionaryPreference(Preference userDictionaryPreference) {
final Activity activity = getActivity();
final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity);
if (null == localeList) {
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 615b2dfab..8aafb0738 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
import android.content.SharedPreferences;
import android.content.res.Configuration;
@@ -23,14 +23,24 @@ import android.util.Log;
import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.keyboard.internal.KeySpecParser;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.InputAttributes;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Locale;
/**
* When you call the constructor of this class, you may want to change the current system locale by
- * using {@link LocaleUtils.RunInLocale}.
+ * using {@link com.android.inputmethod.latin.utils.RunInLocale}.
*/
public final class SettingsValues {
private static final String TAG = SettingsValues.class.getSimpleName();
@@ -65,6 +75,7 @@ public final class SettingsValues {
public final boolean mGestureFloatingPreviewTextEnabled;
public final boolean mSlidingKeyInputPreviewEnabled;
public final int mKeyLongpressTimeout;
+ public final Locale mLocale;
// From the input box
public final InputAttributes mInputAttributes;
@@ -80,11 +91,16 @@ public final class SettingsValues {
private final boolean mVoiceKeyEnabled;
private final boolean mVoiceKeyOnMain;
+ // Setting values for additional features
+ public final int[] mAdditionalFeaturesSettingValues =
+ new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
+
// Debug settings
public final boolean mIsInternal;
- public SettingsValues(final SharedPreferences prefs, final Resources res,
+ public SettingsValues(final SharedPreferences prefs, final Locale locale, final Resources res,
final InputAttributes inputAttributes) {
+ mLocale = locale;
// Get the resources
mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions);
mSymbolsPrecededBySpace =
@@ -96,7 +112,7 @@ public final class SettingsValues {
mWordConnectors =
StringUtils.toCodePointArray(res.getString(R.string.symbols_word_connectors));
Arrays.sort(mWordConnectors);
- final String[] suggestPuncsSpec = StringUtils.parseCsvString(res.getString(
+ final String[] suggestPuncsSpec = KeySpecParser.splitKeySpecs(res.getString(
R.string.suggested_punctuations));
mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
mWordSeparators = res.getString(R.string.symbols_word_separators);
@@ -149,6 +165,8 @@ public final class SettingsValues {
Settings.PREF_SHOW_SUGGESTIONS_SETTING,
res.getString(R.string.prefs_suggestion_visibility_default_value));
mSuggestionVisibility = createSuggestionVisibility(res, showSuggestionsSetting);
+ AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
+ prefs, mAdditionalFeaturesSettingValues);
mIsInternal = Settings.isInternal(prefs);
}
diff --git a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
index 6a7cd9b6f..050d8d26f 100644
--- a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
+++ b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
@@ -25,10 +25,10 @@ import android.content.pm.PackageManager;
import android.os.Process;
import android.preference.PreferenceManager;
import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
import com.android.inputmethod.compat.IntentCompatUtils;
-import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.Settings;
+import com.android.inputmethod.latin.settings.Settings;
/**
* This class detects the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME
@@ -65,17 +65,16 @@ public final class LauncherIconVisibilityManager extends BroadcastReceiver {
}
// 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 been booted,
- // 3) a multiuser has been created.
+ // 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 boolean isCurrentImeOfCurrentUser;
- if (RichInputMethodManager.isInputMethodManagerValidForUserOfThisProcess(context)) {
- RichInputMethodManager.init(context);
- isCurrentImeOfCurrentUser = SetupActivity.isThisImeCurrent(context);
- } else {
- isCurrentImeOfCurrentUser = false;
- }
-
+ 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();
+ final boolean isCurrentImeOfCurrentUser = isInputMethodManagerValidForUserOfThisProcess
+ && SetupActivity.isThisImeCurrent(context, imm);
if (!isCurrentImeOfCurrentUser) {
final int myPid = Process.myPid();
Log.i(TAG, "Killing my process: pid=" + myPid);
@@ -91,7 +90,7 @@ public final class LauncherIconVisibilityManager extends BroadcastReceiver {
} else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
Log.i(TAG, "Boot has been completed");
return true;
- } else if (IntentCompatUtils.has_ACTION_USER_INITIALIZE(intent)) {
+ } else if (IntentCompatUtils.is_ACTION_USER_INITIALIZE(action)) {
Log.i(TAG, "User initialize");
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 8a2de887d..a68f98fe7 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -24,8 +24,6 @@ import android.provider.Settings;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
-import com.android.inputmethod.latin.RichInputMethodManager;
-
public final class SetupActivity extends Activity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
@@ -40,17 +38,24 @@ public final class SetupActivity extends Activity {
}
}
+ /*
+ * We may not be able to get our own {@link InputMethodInfo} just after this IME is installed
+ * because {@link InputMethodManagerService} may not be aware of this IME yet.
+ * Note: {@link RichInputMethodManager} has similar methods. Here in setup wizard, we can't
+ * use it for the reason above.
+ */
+
/**
* Check if the IME specified by the context is enabled.
- * Note that {@link RichInputMethodManager} must have been initialized before calling this
- * method.
+ * CAVEAT: This may cause a round trip IPC.
*
* @param context package context of the IME to be checked.
+ * @param imm the {@link InputMethodManager}.
* @return true if this IME is enabled.
*/
- public static boolean isThisImeEnabled(final Context context) {
+ /* package */ static boolean isThisImeEnabled(final Context context,
+ final InputMethodManager imm) {
final String packageName = context.getPackageName();
- final InputMethodManager imm = RichInputMethodManager.getInstance().getInputMethodManager();
for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
if (packageName.equals(imi.getPackageName())) {
return true;
@@ -61,17 +66,36 @@ public final class SetupActivity extends Activity {
/**
* Check if the IME specified by the context is the current IME.
- * Note that {@link RichInputMethodManager} must have been initialized before calling this
- * method.
+ * CAVEAT: This may cause a round trip IPC.
*
* @param context package context of the IME to be checked.
+ * @param imm the {@link InputMethodManager}.
* @return true if this IME is the current IME.
*/
- public static boolean isThisImeCurrent(final Context context) {
- final InputMethodInfo myImi =
- RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+ /* package */ static boolean isThisImeCurrent(final Context context,
+ final InputMethodManager imm) {
+ final InputMethodInfo imi = getInputMethodInfoOf(context.getPackageName(), imm);
final String currentImeId = Settings.Secure.getString(
context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
- return myImi.getId().equals(currentImeId);
+ return imi != null && imi.getId().equals(currentImeId);
+ }
+
+ /**
+ * Get {@link InputMethodInfo} of the IME specified by the package name.
+ * CAVEAT: This may cause a round trip IPC.
+ *
+ * @param packageName package name of the IME.
+ * @param imm the {@link InputMethodManager}.
+ * @return the {@link InputMethodInfo} of the IME specified by the <code>packageName</code>,
+ * or null if not found.
+ */
+ /* package */ static InputMethodInfo getInputMethodInfoOf(final String packageName,
+ final InputMethodManager imm) {
+ for (final InputMethodInfo imi : imm.getInputMethodList()) {
+ if (packageName.equals(imi.getPackageName())) {
+ return imi;
+ }
+ }
+ return null;
}
}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
index 78a6478c6..c4a813c24 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
@@ -28,17 +28,17 @@ import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.VideoView;
import com.android.inputmethod.compat.TextViewCompatUtils;
import com.android.inputmethod.compat.ViewCompatUtils;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.SettingsActivity;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.settings.SettingsActivity;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
import java.util.ArrayList;
@@ -48,6 +48,8 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
private static final boolean ENABLE_WELCOME_VIDEO = true;
+ private InputMethodManager mImm;
+
private View mSetupWizard;
private View mWelcomeScreen;
private View mSetupScreen;
@@ -69,15 +71,19 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
private static final int STEP_LAUNCHING_IME_SETTINGS = 4;
private static final int STEP_BACK_FROM_IME_SETTINGS = 5;
- final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this);
+ private SettingsPoolingHandler mHandler;
- static final class SettingsPoolingHandler
+ private static final class SettingsPoolingHandler
extends StaticInnerHandlerWrapper<SetupWizardActivity> {
private static final int MSG_POLLING_IME_SETTINGS = 0;
private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
- public SettingsPoolingHandler(final SetupWizardActivity outerInstance) {
+ private final InputMethodManager mImmInHandler;
+
+ public SettingsPoolingHandler(final SetupWizardActivity outerInstance,
+ final InputMethodManager imm) {
super(outerInstance);
+ mImmInHandler = imm;
}
@Override
@@ -88,7 +94,7 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
}
switch (msg.what) {
case MSG_POLLING_IME_SETTINGS:
- if (SetupActivity.isThisImeEnabled(setupWizardActivity)) {
+ if (SetupActivity.isThisImeEnabled(setupWizardActivity, mImmInHandler)) {
setupWizardActivity.invokeSetupWizardOfThisIme();
return;
}
@@ -112,11 +118,12 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
setTheme(android.R.style.Theme_Translucent_NoTitleBar);
super.onCreate(savedInstanceState);
+ mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
+ mHandler = new SettingsPoolingHandler(this, mImm);
+
setContentView(R.layout.setup_wizard);
mSetupWizard = findViewById(R.id.setup_wizard);
- RichInputMethodManager.init(this);
-
if (savedInstanceState == null) {
mStepNumber = determineSetupStepNumberFromLauncher();
} else {
@@ -143,11 +150,12 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
R.string.setup_step1_title, R.string.setup_step1_instruction,
R.string.setup_step1_finished_instruction, R.drawable.ic_setup_step1,
R.string.setup_step1_action);
+ final SettingsPoolingHandler handler = mHandler;
step1.setAction(new Runnable() {
@Override
public void run() {
invokeLanguageAndInputSettings();
- mHandler.startPollingImeSettings();
+ handler.startPollingImeSettings();
}
});
mSetupStepGroup.addStep(step1);
@@ -265,14 +273,15 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
void invokeInputMethodPicker() {
// Invoke input method picker.
- RichInputMethodManager.getInstance().getInputMethodManager()
- .showInputMethodPicker();
+ mImm.showInputMethodPicker();
mNeedsToAdjustStepNumberToSystemState = true;
}
void invokeSubtypeEnablerOfThisIme() {
- final InputMethodInfo imi =
- RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+ final InputMethodInfo imi = SetupActivity.getInputMethodInfoOf(getPackageName(), mImm);
+ if (imi == null) {
+ return;
+ }
final Intent intent = new Intent();
intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
@@ -293,10 +302,10 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
private int determineSetupStepNumber() {
mHandler.cancelPollingImeSettings();
- if (!SetupActivity.isThisImeEnabled(this)) {
+ if (!SetupActivity.isThisImeEnabled(this, mImm)) {
return STEP_1;
}
- if (!SetupActivity.isThisImeCurrent(this)) {
+ if (!SetupActivity.isThisImeCurrent(this, mImm)) {
return STEP_2;
}
return STEP_3;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 13fcaf48a..692e7392c 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -25,17 +25,17 @@ import android.view.textservice.SuggestionsInfo;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.latin.BinaryDictionary;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.ContactsBinaryDictionary;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryCollection;
import com.android.inputmethod.latin.DictionaryFactory;
-import com.android.inputmethod.latin.LocaleUtils;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
import com.android.inputmethod.latin.UserBinaryDictionary;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 9a1114f7f..ddda52d71 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -23,7 +23,7 @@ import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 16e9fb77e..6719e98da 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -30,11 +30,11 @@ import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.LocaleUtils;
-import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index a20e09ee8..ac8f68781 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -19,10 +19,10 @@ package com.android.inputmethod.latin.spellcheck;
import android.util.Log;
import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
index 5ce9d8e47..999ca775b 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -21,7 +21,7 @@ import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
/**
* Preference screen.
@@ -39,7 +39,7 @@ public final class SpellCheckerSettingsFragment extends PreferenceFragment {
addPreferencesFromResource(R.xml.spell_checker_settings);
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
- preferenceScreen.setTitle(Utils.getAcitivityTitleResId(
+ preferenceScreen.setTitle(ApplicationUtils.getAcitivityTitleResId(
getActivity(), SpellCheckerSettingsActivity.class));
}
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 09f81d4c7..e97069dff 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -24,14 +24,13 @@ import android.graphics.drawable.Drawable;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
-import com.android.inputmethod.keyboard.TypefaceUtils;
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.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
public final class MoreSuggestions extends Keyboard {
public static final int SUGGESTION_CODE_BASE = 1024;
@@ -61,7 +60,7 @@ public final class MoreSuggestions extends Keyboard {
super();
}
- public int layout(final SuggestedWords suggestedWords, final int fromPos,
+ public int layout(final SuggestedWords suggestedWords, final int fromIndex,
final int maxWidth, final int minWidth, final int maxRow, final Paint paint,
final Resources res) {
clearKeys();
@@ -70,53 +69,54 @@ public final class MoreSuggestions extends Keyboard {
final float padding = res.getDimension(R.dimen.more_suggestions_key_horizontal_padding);
int row = 0;
- int pos = fromPos, rowStartPos = fromPos;
+ int index = fromIndex;
+ int rowStartIndex = fromIndex;
final int size = Math.min(suggestedWords.size(), SuggestionStripView.MAX_SUGGESTIONS);
- while (pos < size) {
- final String word = suggestedWords.getWord(pos);
+ while (index < size) {
+ final String word = suggestedWords.getWord(index);
// TODO: Should take care of text x-scaling.
- mWidths[pos] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
- final int numColumn = pos - rowStartPos + 1;
+ mWidths[index] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
+ final int numColumn = index - rowStartIndex + 1;
final int columnWidth =
(maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
if (numColumn > MAX_COLUMNS_IN_ROW
- || !fitInWidth(rowStartPos, pos + 1, columnWidth)) {
+ || !fitInWidth(rowStartIndex, index + 1, columnWidth)) {
if ((row + 1) >= maxRow) {
break;
}
- mNumColumnsInRow[row] = pos - rowStartPos;
- rowStartPos = pos;
+ mNumColumnsInRow[row] = index - rowStartIndex;
+ rowStartIndex = index;
row++;
}
- mColumnOrders[pos] = pos - rowStartPos;
- mRowNumbers[pos] = row;
- pos++;
+ mColumnOrders[index] = index - rowStartIndex;
+ mRowNumbers[index] = row;
+ index++;
}
- mNumColumnsInRow[row] = pos - rowStartPos;
+ mNumColumnsInRow[row] = index - rowStartIndex;
mNumRows = row + 1;
mBaseWidth = mOccupiedWidth = Math.max(
- minWidth, calcurateMaxRowWidth(fromPos, pos));
+ minWidth, calcurateMaxRowWidth(fromIndex, index));
mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
- return pos - fromPos;
+ return index - fromIndex;
}
- private boolean fitInWidth(final int startPos, final int endPos, final int width) {
- for (int pos = startPos; pos < endPos; pos++) {
- if (mWidths[pos] > width)
+ private boolean fitInWidth(final int startIndex, final int endIndex, final int width) {
+ for (int index = startIndex; index < endIndex; index++) {
+ if (mWidths[index] > width)
return false;
}
return true;
}
- private int calcurateMaxRowWidth(final int startPos, final int endPos) {
+ private int calcurateMaxRowWidth(final int startIndex, final int endIndex) {
int maxRowWidth = 0;
- int pos = startPos;
+ int index = startIndex;
for (int row = 0; row < mNumRows; row++) {
final int numColumnInRow = mNumColumnsInRow[row];
int maxKeyWidth = 0;
- while (pos < endPos && mRowNumbers[pos] == row) {
- maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]);
- pos++;
+ while (index < endIndex && mRowNumbers[index] == row) {
+ maxKeyWidth = Math.max(maxKeyWidth, mWidths[index]);
+ index++;
}
maxRowWidth = Math.max(maxRowWidth,
maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
@@ -130,40 +130,40 @@ public final class MoreSuggestions extends Keyboard {
{ 2, 0, 1},
};
- public int getNumColumnInRow(final int pos) {
- return mNumColumnsInRow[mRowNumbers[pos]];
+ public int getNumColumnInRow(final int index) {
+ return mNumColumnsInRow[mRowNumbers[index]];
}
- public int getColumnNumber(final int pos) {
- final int columnOrder = mColumnOrders[pos];
- final int numColumn = getNumColumnInRow(pos);
+ public int getColumnNumber(final int index) {
+ final int columnOrder = mColumnOrders[index];
+ final int numColumn = getNumColumnInRow(index);
return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
}
- public int getX(final int pos) {
- final int columnNumber = getColumnNumber(pos);
- return columnNumber * (getWidth(pos) + mDividerWidth);
+ public int getX(final int index) {
+ final int columnNumber = getColumnNumber(index);
+ return columnNumber * (getWidth(index) + mDividerWidth);
}
- public int getY(final int pos) {
- final int row = mRowNumbers[pos];
+ public int getY(final int index) {
+ final int row = mRowNumbers[index];
return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
}
- public int getWidth(final int pos) {
- final int numColumnInRow = getNumColumnInRow(pos);
+ public int getWidth(final int index) {
+ final int numColumnInRow = getNumColumnInRow(index);
return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
}
- public void markAsEdgeKey(final Key key, final int pos) {
- final int row = mRowNumbers[pos];
+ public void markAsEdgeKey(final Key key, final int index) {
+ final int row = mRowNumbers[index];
if (row == 0)
key.markAsBottomEdge(this);
if (row == mNumRows - 1)
key.markAsTopEdge(this);
final int numColumnInRow = mNumColumnsInRow[row];
- final int column = getColumnNumber(pos);
+ final int column = getColumnNumber(index);
if (column == 0)
key.markAsLeftEdge(this);
if (column == numColumnInRow - 1)
@@ -174,15 +174,15 @@ public final class MoreSuggestions extends Keyboard {
public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
private final MoreSuggestionsView mPaneView;
private SuggestedWords mSuggestedWords;
- private int mFromPos;
- private int mToPos;
+ private int mFromIndex;
+ private int mToIndex;
public Builder(final Context context, final MoreSuggestionsView paneView) {
super(context, new MoreSuggestionsParam());
mPaneView = paneView;
}
- public Builder layout(final SuggestedWords suggestedWords, final int fromPos,
+ public Builder layout(final SuggestedWords suggestedWords, final int fromIndex,
final int maxWidth, final int minWidth, final int maxRow,
final Keyboard parentKeyboard) {
final int xmlId = R.xml.kbd_suggestions_pane_template;
@@ -190,10 +190,10 @@ public final class MoreSuggestions extends Keyboard {
mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
- final int count = mParams.layout(suggestedWords, fromPos, maxWidth, minWidth, maxRow,
+ final int count = mParams.layout(suggestedWords, fromIndex, maxWidth, minWidth, maxRow,
mPaneView.newLabelPaint(null /* key */), mResources);
- mFromPos = fromPos;
- mToPos = fromPos + count;
+ mFromIndex = fromIndex;
+ mToIndex = fromIndex + count;
mSuggestedWords = suggestedWords;
return this;
}
@@ -201,20 +201,20 @@ public final class MoreSuggestions extends Keyboard {
@Override
public MoreSuggestions build() {
final MoreSuggestionsParam params = mParams;
- for (int pos = mFromPos; pos < mToPos; pos++) {
- final int x = params.getX(pos);
- final int y = params.getY(pos);
- final int width = params.getWidth(pos);
- final String word = mSuggestedWords.getWord(pos);
- final String info = Utils.getDebugInfo(mSuggestedWords, pos);
- final int index = pos + SUGGESTION_CODE_BASE;
+ for (int index = mFromIndex; index < mToIndex; index++) {
+ final int x = params.getX(index);
+ final int y = params.getY(index);
+ final int width = params.getWidth(index);
+ final String word = mSuggestedWords.getWord(index);
+ final String info = mSuggestedWords.getDebugString(index);
+ final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE;
final Key key = new Key(
- params, word, info, KeyboardIconsSet.ICON_UNDEFINED, index, null, x, y,
- width, params.mDefaultRowHeight, 0);
- params.markAsEdgeKey(key, pos);
+ params, word, info, KeyboardIconsSet.ICON_UNDEFINED, indexInMoreSuggestions,
+ null, x, y, width, params.mDefaultRowHeight, 0);
+ params.markAsEdgeKey(key, index);
params.onAddKey(key);
- final int columnNumber = params.getColumnNumber(pos);
- final int numColumnInRow = params.getNumColumnInRow(pos);
+ final int columnNumber = params.getColumnNumber(index);
+ final int numColumnInRow = params.getNumColumnInRow(index);
if (columnNumber < numColumnInRow - 1) {
final Divider divider = new Divider(params, params.mDivider, x + width, y,
params.mDividerWidth, params.mDefaultRowHeight);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
new file mode 100644
index 000000000..1dd04fc4d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -0,0 +1,600 @@
+/*
+ * 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.suggestions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.CharacterStyle;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+import com.android.inputmethod.latin.utils.ViewLayoutUtils;
+
+import java.util.ArrayList;
+
+final class SuggestionStripLayoutHelper {
+ private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
+ private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
+ private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
+ private static final int PUNCTUATIONS_IN_STRIP = 5;
+ private static final float MIN_TEXT_XSCALE = 0.70f;
+
+ public final int mPadding;
+ public final int mDividerWidth;
+ public final int mSuggestionsStripHeight;
+ public final int mSuggestionsCountInStrip;
+ public final int mMoreSuggestionsRowHeight;
+ private int mMaxMoreSuggestionsRow;
+ public final float mMinMoreSuggestionsWidth;
+ public final int mMoreSuggestionsBottomGap;
+ public boolean mMoreSuggestionsAvailable;
+
+ // The index of these {@link ArrayList} is the position in the suggestion strip. The indices
+ // increase towards the right for LTR scripts and the left for RTL scripts, starting with 0.
+ // The position of the most important suggestion is in {@link #mCenterPositionInStrip}
+ private final ArrayList<TextView> mWordViews;
+ private final ArrayList<View> mDividerViews;
+ private final ArrayList<TextView> mDebugInfoViews;
+
+ private final int mColorValidTypedWord;
+ private final int mColorTypedWord;
+ private final int mColorAutoCorrect;
+ private final int mColorSuggested;
+ private final float mAlphaObsoleted;
+ private final float mCenterSuggestionWeight;
+ private final int mCenterPositionInStrip;
+ 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 CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
+ private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
+
+ private final int mSuggestionStripOption;
+ // These constants are the flag values of
+ // {@link R.styleable#SuggestionStripView_suggestionStripOption} attribute.
+ private static final int AUTO_CORRECT_BOLD = 0x01;
+ private static final int AUTO_CORRECT_UNDERLINE = 0x02;
+ private static final int VALID_TYPED_WORD_BOLD = 0x04;
+
+ private final TextView mWordToSaveView;
+ private final TextView mLeftwardsArrowView;
+ private final TextView mHintToSaveView;
+
+ public SuggestionStripLayoutHelper(final Context context, final AttributeSet attrs,
+ final int defStyle, final ArrayList<TextView> wordViews,
+ final ArrayList<View> dividerViews, final ArrayList<TextView> debugInfoViews) {
+ mWordViews = wordViews;
+ mDividerViews = dividerViews;
+ mDebugInfoViews = debugInfoViews;
+
+ final TextView wordView = wordViews.get(0);
+ final View dividerView = dividerViews.get(0);
+ mPadding = wordView.getCompoundPaddingLeft() + wordView.getCompoundPaddingRight();
+ dividerView.measure(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ mDividerWidth = dividerView.getMeasuredWidth();
+
+ final Resources res = wordView.getResources();
+ mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
+ mSuggestionStripOption = a.getInt(
+ R.styleable.SuggestionStripView_suggestionStripOption, 0);
+ final float alphaValidTypedWord = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
+ final float alphaTypedWord = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
+ final float alphaAutoCorrect = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
+ final float alphaSuggested = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
+ mAlphaObsoleted = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
+ mColorValidTypedWord = applyAlpha(a.getColor(
+ R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
+ mColorTypedWord = applyAlpha(a.getColor(
+ R.styleable.SuggestionStripView_colorTypedWord, 0), alphaTypedWord);
+ mColorAutoCorrect = applyAlpha(a.getColor(
+ R.styleable.SuggestionStripView_colorAutoCorrect, 0), alphaAutoCorrect);
+ mColorSuggested = applyAlpha(a.getColor(
+ R.styleable.SuggestionStripView_colorSuggested, 0), alphaSuggested);
+ mSuggestionsCountInStrip = a.getInt(
+ R.styleable.SuggestionStripView_suggestionsCountInStrip,
+ DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
+ mCenterSuggestionWeight = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_centerSuggestionPercentile,
+ DEFAULT_CENTER_SUGGESTION_PERCENTILE);
+ mMaxMoreSuggestionsRow = a.getInt(
+ R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
+ DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
+ mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
+ a.recycle();
+
+ mMoreSuggestionsHint = getMoreSuggestionsHint(res,
+ res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect);
+ mCenterPositionInStrip = mSuggestionsCountInStrip / 2;
+ // Assuming there are at least three suggestions. Also, note that the suggestions are
+ // laid out according to script direction, so this is left of the center for LTR scripts
+ // and right of the center for RTL scripts.
+ mTypedWordPositionWhenAutocorrect = mCenterPositionInStrip - 1;
+ mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
+ R.dimen.more_suggestions_bottom_gap);
+ mMoreSuggestionsRowHeight = res.getDimensionPixelSize(R.dimen.more_suggestions_row_height);
+
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
+ mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
+ mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
+ }
+
+ public int getMaxMoreSuggestionsRow() {
+ return mMaxMoreSuggestionsRow;
+ }
+
+ private int getMoreSuggestionsHeight() {
+ return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
+ }
+
+ public int setMoreSuggestionsHeight(final int remainingHeight) {
+ final int currentHeight = getMoreSuggestionsHeight();
+ if (currentHeight <= remainingHeight) {
+ return currentHeight;
+ }
+
+ mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap)
+ / mMoreSuggestionsRowHeight;
+ final int newHeight = getMoreSuggestionsHeight();
+ return newHeight;
+ }
+
+ private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize,
+ final int color) {
+ final Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setTextAlign(Align.CENTER);
+ paint.setTextSize(textSize);
+ paint.setColor(color);
+ final Rect bounds = new Rect();
+ paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds);
+ final int width = Math.round(bounds.width() + 0.5f);
+ final int height = Math.round(bounds.height() + 0.5f);
+ final Bitmap buffer = Bitmap.createBitmap(width, (height * 3 / 2), Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(buffer);
+ canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint);
+ return new BitmapDrawable(res, buffer);
+ }
+
+ private CharSequence getStyledSuggestedWord(final SuggestedWords suggestedWords,
+ final int indexInSuggestedWords) {
+ if (indexInSuggestedWords >= suggestedWords.size()) {
+ return null;
+ }
+ final String word = suggestedWords.getWord(indexInSuggestedWords);
+ final boolean isAutoCorrect = indexInSuggestedWords == 1
+ && suggestedWords.willAutoCorrect();
+ final boolean isTypedWordValid = indexInSuggestedWords == 0
+ && suggestedWords.mTypedWordValid;
+ if (!isAutoCorrect && !isTypedWordValid) {
+ return word;
+ }
+
+ final int len = word.length();
+ final Spannable spannedWord = new SpannableString(word);
+ final int option = mSuggestionStripOption;
+ if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0)
+ || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) {
+ spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ }
+ if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) {
+ spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ }
+ return spannedWord;
+ }
+
+ private int getPositionInSuggestionStrip(final int indexInSuggestedWords,
+ final SuggestedWords suggestedWords) {
+ final int indexToDisplayMostImportantSuggestion;
+ final int indexToDisplaySecondMostImportantSuggestion;
+ if (suggestedWords.willAutoCorrect()) {
+ indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION;
+ indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD;
+ } else {
+ indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD;
+ indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION;
+ }
+ if (indexInSuggestedWords == indexToDisplayMostImportantSuggestion) {
+ return mCenterPositionInStrip;
+ }
+ if (indexInSuggestedWords == indexToDisplaySecondMostImportantSuggestion) {
+ return mTypedWordPositionWhenAutocorrect;
+ }
+ // If neither of those, the order in the suggestion strip is the same as in SuggestedWords.
+ return indexInSuggestedWords;
+ }
+
+ private int getSuggestionTextColor(final int indexInSuggestedWords,
+ final SuggestedWords suggestedWords) {
+ final int positionInStrip =
+ getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords);
+ // TODO: Need to revisit this logic with bigram suggestions
+ final boolean isSuggested = (indexInSuggestedWords != SuggestedWords.INDEX_OF_TYPED_WORD);
+
+ final int color;
+ if (positionInStrip == mCenterPositionInStrip && suggestedWords.willAutoCorrect()) {
+ color = mColorAutoCorrect;
+ } else if (positionInStrip == mCenterPositionInStrip && suggestedWords.mTypedWordValid) {
+ color = mColorValidTypedWord;
+ } else if (isSuggested) {
+ color = mColorSuggested;
+ } else {
+ color = mColorTypedWord;
+ }
+ if (LatinImeLogger.sDBG && suggestedWords.size() > 1) {
+ // If we auto-correct, then the autocorrection is in slot 0 and the typed word
+ // is in slot 1.
+ if (positionInStrip == mCenterPositionInStrip
+ && AutoCorrectionUtils.shouldBlockAutoCorrectionBySafetyNet(
+ suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION),
+ suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD))) {
+ return 0xFFFF0000;
+ }
+ }
+
+ if (suggestedWords.mIsObsoleteSuggestions && isSuggested) {
+ return applyAlpha(color, mAlphaObsoleted);
+ }
+ return color;
+ }
+
+ private static int applyAlpha(final int color, final float alpha) {
+ final int newAlpha = (int)(Color.alpha(color) * alpha);
+ return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ private static void addDivider(final ViewGroup stripView, final View dividerView) {
+ stripView.addView(dividerView);
+ final LinearLayout.LayoutParams params =
+ (LinearLayout.LayoutParams)dividerView.getLayoutParams();
+ params.gravity = Gravity.CENTER;
+ }
+
+ public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView,
+ final ViewGroup placerView) {
+ if (suggestedWords.mIsPunctuationSuggestions) {
+ layoutPunctuationSuggestions(suggestedWords, stripView);
+ return;
+ }
+
+ final int countInStrip = mSuggestionsCountInStrip;
+ setupWordViewsTextAndColor(suggestedWords, countInStrip);
+ final TextView centerWordView = mWordViews.get(mCenterPositionInStrip);
+ final int stripWidth = placerView.getWidth();
+ final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth);
+ if (getTextScaleX(centerWordView.getText(), centerWidth, centerWordView.getPaint())
+ < MIN_TEXT_XSCALE) {
+ // Layout only the most relevant suggested word at the center of the suggestion strip
+ // by consolidating all slots in the strip.
+ mMoreSuggestionsAvailable = (suggestedWords.size() > 1);
+ layoutWord(mCenterPositionInStrip, stripWidth);
+ stripView.addView(centerWordView);
+ setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT);
+ if (SuggestionStripView.DBG) {
+ layoutDebugInfo(mCenterPositionInStrip, placerView, stripWidth);
+ }
+ return;
+ }
+
+ mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
+ int x = 0;
+ for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
+ if (positionInStrip != 0) {
+ final View divider = mDividerViews.get(positionInStrip);
+ // Add divider if this isn't the left most suggestion in suggestions strip.
+ addDivider(stripView, divider);
+ x += divider.getMeasuredWidth();
+ }
+
+ final int width = getSuggestionWidth(positionInStrip, stripWidth);
+ final TextView wordView = layoutWord(positionInStrip, width);
+ stripView.addView(wordView);
+ setLayoutWeight(wordView, getSuggestionWeight(positionInStrip),
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ x += wordView.getMeasuredWidth();
+
+ if (SuggestionStripView.DBG) {
+ layoutDebugInfo(positionInStrip, placerView, x);
+ }
+ }
+ }
+
+ /**
+ * Format appropriately the suggested word in {@link #mWordViews} specified by
+ * <code>positionInStrip</code>. When the suggested word doesn't exist, the corresponding
+ * {@link TextView} will be disabled and never respond to user interaction. The suggested word
+ * may be shrunk or ellipsized to fit in the specified width.
+ *
+ * The <code>positionInStrip</code> argument is the index in the suggestion strip. The indices
+ * increase towards the right for LTR scripts and the left for RTL scripts, starting with 0.
+ * The position of the most important suggestion is in {@link #mCenterPositionInStrip}. This
+ * usually doesn't match the index in <code>suggedtedWords</code> -- see
+ * {@link #getPositionInSuggestionStrip(int,SuggestedWords)}.
+ *
+ * @param positionInStrip the position in the suggestion strip.
+ * @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) {
+ final TextView wordView = mWordViews.get(positionInStrip);
+ final CharSequence word = wordView.getText();
+ if (positionInStrip == mCenterPositionInStrip && mMoreSuggestionsAvailable) {
+ // TODO: This "more suggestions hint" should have a nicely designed icon.
+ wordView.setCompoundDrawablesWithIntrinsicBounds(
+ null, null, null, mMoreSuggestionsHint);
+ // HACK: Align with other TextViews that have no compound drawables.
+ wordView.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight());
+ } else {
+ wordView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ }
+
+ // Disable this suggestion if the suggestion is null or empty.
+ wordView.setEnabled(!TextUtils.isEmpty(word));
+ final CharSequence text = getEllipsizedText(word, width, wordView.getPaint());
+ final float scaleX = wordView.getTextScaleX();
+ wordView.setText(text); // TextView.setText() resets text scale x to 1.0.
+ wordView.setTextScaleX(scaleX);
+ return wordView;
+ }
+
+ private void layoutDebugInfo(final int positionInStrip, final ViewGroup placerView,
+ final int x) {
+ final TextView debugInfoView = mDebugInfoViews.get(positionInStrip);
+ final CharSequence debugInfo = debugInfoView.getText();
+ if (debugInfo == null) {
+ return;
+ }
+ placerView.addView(debugInfoView);
+ debugInfoView.measure(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ final int infoWidth = debugInfoView.getMeasuredWidth();
+ final int y = debugInfoView.getMeasuredHeight();
+ ViewLayoutUtils.placeViewAt(
+ debugInfoView, x - infoWidth, y, infoWidth, debugInfoView.getMeasuredHeight());
+ }
+
+ private int getSuggestionWidth(final int positionInStrip, final int maxWidth) {
+ final int paddings = mPadding * mSuggestionsCountInStrip;
+ final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
+ final int availableWidth = maxWidth - paddings - dividers;
+ return (int)(availableWidth * getSuggestionWeight(positionInStrip));
+ }
+
+ private float getSuggestionWeight(final int positionInStrip) {
+ if (positionInStrip == mCenterPositionInStrip) {
+ return mCenterSuggestionWeight;
+ }
+ // TODO: Revisit this for cases of 5 or more suggestions
+ return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1);
+ }
+
+ private void setupWordViewsTextAndColor(final SuggestedWords suggestedWords,
+ final int countInStrip) {
+ // Clear all suggestions first
+ for (int positionInStrip = 0; positionInStrip < countInStrip; ++positionInStrip) {
+ mWordViews.get(positionInStrip).setText(null);
+ // Make this inactive for touches in {@link #layoutWord(int,int)}.
+ if (SuggestionStripView.DBG) {
+ mDebugInfoViews.get(positionInStrip).setText(null);
+ }
+ }
+ final int count = Math.min(suggestedWords.size(), countInStrip);
+ for (int indexInSuggestedWords = 0; indexInSuggestedWords < count;
+ indexInSuggestedWords++) {
+ final int positionInStrip =
+ getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords);
+ final TextView wordView = mWordViews.get(positionInStrip);
+ // {@link TextView#getTag()} is used to get the index in suggestedWords at
+ // {@link SuggestionStripView#onClick(View)}.
+ wordView.setTag(indexInSuggestedWords);
+ wordView.setText(getStyledSuggestedWord(suggestedWords, indexInSuggestedWords));
+ wordView.setTextColor(getSuggestionTextColor(positionInStrip, suggestedWords));
+ if (SuggestionStripView.DBG) {
+ mDebugInfoViews.get(positionInStrip).setText(
+ suggestedWords.getDebugString(indexInSuggestedWords));
+ }
+ }
+ }
+
+ private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords,
+ final ViewGroup stripView) {
+ final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
+ for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
+ if (positionInStrip != 0) {
+ // Add divider if this isn't the left most suggestion in suggestions strip.
+ addDivider(stripView, mDividerViews.get(positionInStrip));
+ }
+
+ final TextView wordView = mWordViews.get(positionInStrip);
+ wordView.setEnabled(true);
+ wordView.setTextColor(mColorAutoCorrect);
+ // {@link TextView#getTag()} is used to get the index in suggestedWords at
+ // {@link SuggestionStripView#onClick(View)}.
+ wordView.setTag(positionInStrip);
+ wordView.setText(suggestedWords.getWord(positionInStrip));
+ wordView.setTextScaleX(1.0f);
+ wordView.setCompoundDrawables(null, null, null, null);
+ stripView.addView(wordView);
+ setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight);
+ }
+ mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
+ }
+
+ public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView,
+ final int stripWidth, final CharSequence hintText, final OnClickListener listener) {
+ final int width = stripWidth - mDividerWidth - mPadding * 2;
+
+ final TextView wordView = mWordToSaveView;
+ wordView.setTextColor(mColorTypedWord);
+ final int wordWidth = (int)(width * mCenterSuggestionWeight);
+ final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint());
+ final float wordScaleX = wordView.getTextScaleX();
+ // {@link TextView#setTag()} is used to hold the word to be added to dictionary. The word
+ // will be extracted at {@link #getAddToDictionaryWord()}.
+ wordView.setTag(word);
+ wordView.setText(text);
+ wordView.setTextScaleX(wordScaleX);
+ stripView.addView(wordView);
+ setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
+
+ stripView.addView(mDividerViews.get(0));
+
+ final TextView leftArrowView = mLeftwardsArrowView;
+ leftArrowView.setTextColor(mColorAutoCorrect);
+ leftArrowView.setText(LEFTWARDS_ARROW);
+ stripView.addView(leftArrowView);
+
+ final TextView hintView = mHintToSaveView;
+ hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ hintView.setTextColor(mColorAutoCorrect);
+ final int hintWidth = width - wordWidth - leftArrowView.getWidth();
+ final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
+ hintView.setText(hintText);
+ hintView.setTextScaleX(hintScaleX);
+ stripView.addView(hintView);
+ setLayoutWeight(
+ hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
+
+ wordView.setOnClickListener(listener);
+ leftArrowView.setOnClickListener(listener);
+ hintView.setOnClickListener(listener);
+ }
+
+ public String getAddToDictionaryWord() {
+ // String tag is set at
+ // {@link #layoutAddToDictionaryHint(String,ViewGroup,int,CharSequence,OnClickListener}.
+ return (String)mWordToSaveView.getTag();
+ }
+
+ public boolean isAddToDictionaryShowing(final View v) {
+ return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView;
+ }
+
+ private static void setLayoutWeight(final View v, final float weight, final int height) {
+ final ViewGroup.LayoutParams lp = v.getLayoutParams();
+ if (lp instanceof LinearLayout.LayoutParams) {
+ final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
+ llp.weight = weight;
+ llp.width = 0;
+ llp.height = height;
+ }
+ }
+
+ private static float getTextScaleX(final CharSequence text, final int maxWidth,
+ final TextPaint paint) {
+ paint.setTextScaleX(1.0f);
+ final int width = getTextWidth(text, paint);
+ if (width <= maxWidth) {
+ return 1.0f;
+ }
+ return maxWidth / (float)width;
+ }
+
+ private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth,
+ final TextPaint paint) {
+ if (text == null) {
+ return null;
+ }
+ final float scaleX = getTextScaleX(text, maxWidth, paint);
+ if (scaleX >= MIN_TEXT_XSCALE) {
+ paint.setTextScaleX(scaleX);
+ 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 CharSequence ellipsized = TextUtils.ellipsize(
+ text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
+ paint.setTextScaleX(MIN_TEXT_XSCALE);
+ return ellipsized;
+ }
+
+ private static int getTextWidth(final CharSequence text, final TextPaint paint) {
+ if (TextUtils.isEmpty(text)) {
+ return 0;
+ }
+ 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);
+ 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;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index ad350a02f..497a791d9 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -18,34 +18,14 @@ package com.android.inputmethod.latin.suggestions;
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.text.style.CharacterStyle;
-import android.text.style.StyleSpan;
-import android.text.style.UnderlineSpan;
import android.util.AttributeSet;
import android.view.GestureDetector;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
-import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -53,18 +33,15 @@ import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.keyboard.MoreKeysPanel;
-import com.android.inputmethod.keyboard.ViewLayoutUtils;
-import com.android.inputmethod.latin.AutoCorrection;
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.Utils;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
+import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
@@ -88,477 +65,14 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private final MoreSuggestionsView mMoreSuggestionsView;
private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
- private final ArrayList<TextView> mWords = CollectionUtils.newArrayList();
- private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList();
- private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
+ private final ArrayList<TextView> mWordViews = CollectionUtils.newArrayList();
+ private final ArrayList<TextView> mDebugInfoViews = CollectionUtils.newArrayList();
+ private final ArrayList<View> mDividerViews = CollectionUtils.newArrayList();
Listener mListener;
private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
- private final SuggestionStripViewParams mParams;
- private static final float MIN_TEXT_XSCALE = 0.70f;
-
- private static final class SuggestionStripViewParams {
- private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
- private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
- private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
- private static final int PUNCTUATIONS_IN_STRIP = 5;
-
- public final int mPadding;
- public final int mDividerWidth;
- public final int mSuggestionsStripHeight;
- public final int mSuggestionsCountInStrip;
- public final int mMoreSuggestionsRowHeight;
- private int mMaxMoreSuggestionsRow;
- public final float mMinMoreSuggestionsWidth;
- public final int mMoreSuggestionsBottomGap;
-
- private final ArrayList<TextView> mWords;
- private final ArrayList<View> mDividers;
- private final ArrayList<TextView> mInfos;
-
- private final int mColorValidTypedWord;
- private final int mColorTypedWord;
- private final int mColorAutoCorrect;
- private final int mColorSuggested;
- private final float mAlphaObsoleted;
- private final float mCenterSuggestionWeight;
- private final int mCenterSuggestionIndex;
- private final Drawable mMoreSuggestionsHint;
- private static final String MORE_SUGGESTIONS_HINT = "\u2026";
- private static final String LEFTWARDS_ARROW = "\u2190";
-
- private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
- private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
- private static final int AUTO_CORRECT_BOLD = 0x01;
- private static final int AUTO_CORRECT_UNDERLINE = 0x02;
- private static final int VALID_TYPED_WORD_BOLD = 0x04;
-
- private final int mSuggestionStripOption;
-
- private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList();
-
- public boolean mMoreSuggestionsAvailable;
-
- private final TextView mWordToSaveView;
- private final TextView mLeftwardsArrowView;
- private final TextView mHintToSaveView;
-
- public SuggestionStripViewParams(final Context context, final AttributeSet attrs,
- final int defStyle, final ArrayList<TextView> words, final ArrayList<View> dividers,
- final ArrayList<TextView> infos) {
- mWords = words;
- mDividers = dividers;
- mInfos = infos;
-
- final TextView word = words.get(0);
- final View divider = dividers.get(0);
- mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight();
- divider.measure(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- mDividerWidth = divider.getMeasuredWidth();
-
- final Resources res = word.getResources();
- mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
-
- final TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
- mSuggestionStripOption = a.getInt(
- R.styleable.SuggestionStripView_suggestionStripOption, 0);
- final float alphaValidTypedWord = ResourceUtils.getFraction(a,
- R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
- final float alphaTypedWord = ResourceUtils.getFraction(a,
- R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
- final float alphaAutoCorrect = ResourceUtils.getFraction(a,
- R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
- final float alphaSuggested = ResourceUtils.getFraction(a,
- R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
- mAlphaObsoleted = ResourceUtils.getFraction(a,
- R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
- mColorValidTypedWord = applyAlpha(a.getColor(
- R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
- mColorTypedWord = applyAlpha(a.getColor(
- R.styleable.SuggestionStripView_colorTypedWord, 0), alphaTypedWord);
- mColorAutoCorrect = applyAlpha(a.getColor(
- R.styleable.SuggestionStripView_colorAutoCorrect, 0), alphaAutoCorrect);
- mColorSuggested = applyAlpha(a.getColor(
- R.styleable.SuggestionStripView_colorSuggested, 0), alphaSuggested);
- mSuggestionsCountInStrip = a.getInt(
- R.styleable.SuggestionStripView_suggestionsCountInStrip,
- DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
- mCenterSuggestionWeight = ResourceUtils.getFraction(a,
- R.styleable.SuggestionStripView_centerSuggestionPercentile,
- DEFAULT_CENTER_SUGGESTION_PERCENTILE);
- mMaxMoreSuggestionsRow = a.getInt(
- R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
- DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
- mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a,
- R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
- a.recycle();
-
- mMoreSuggestionsHint = getMoreSuggestionsHint(res,
- res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect);
- mCenterSuggestionIndex = mSuggestionsCountInStrip / 2;
- mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
- R.dimen.more_suggestions_bottom_gap);
- mMoreSuggestionsRowHeight = res.getDimensionPixelSize(
- R.dimen.more_suggestions_row_height);
-
- final LayoutInflater inflater = LayoutInflater.from(context);
- mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
- mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
- mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
- }
-
- public int getMaxMoreSuggestionsRow() {
- return mMaxMoreSuggestionsRow;
- }
-
- private int getMoreSuggestionsHeight() {
- return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
- }
-
- public int setMoreSuggestionsHeight(final int remainingHeight) {
- final int currentHeight = getMoreSuggestionsHeight();
- if (currentHeight <= remainingHeight) {
- return currentHeight;
- }
-
- mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap)
- / mMoreSuggestionsRowHeight;
- final int newHeight = getMoreSuggestionsHeight();
- return newHeight;
- }
-
- private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize,
- final int color) {
- final Paint paint = new Paint();
- paint.setAntiAlias(true);
- paint.setTextAlign(Align.CENTER);
- paint.setTextSize(textSize);
- paint.setColor(color);
- final Rect bounds = new Rect();
- paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds);
- final int width = Math.round(bounds.width() + 0.5f);
- final int height = Math.round(bounds.height() + 0.5f);
- final Bitmap buffer = Bitmap.createBitmap(
- width, (height * 3 / 2), Bitmap.Config.ARGB_8888);
- final Canvas canvas = new Canvas(buffer);
- canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint);
- return new BitmapDrawable(res, buffer);
- }
-
- private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords,
- final int pos) {
- final String word = suggestedWords.getWord(pos);
- final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect();
- final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid;
- if (!isAutoCorrect && !isTypedWordValid)
- return word;
-
- final int len = word.length();
- final Spannable spannedWord = new SpannableString(word);
- final int option = mSuggestionStripOption;
- if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0)
- || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) {
- spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- }
- if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) {
- spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- }
- return spannedWord;
- }
-
- private int getWordPosition(final int index, final SuggestedWords suggestedWords) {
- // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
- // suggestions.
- final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0;
- if (index == mCenterSuggestionIndex) {
- return centerPos;
- } else if (index == centerPos) {
- return mCenterSuggestionIndex;
- } else {
- return index;
- }
- }
-
- private int getSuggestionTextColor(final int index, final SuggestedWords suggestedWords,
- final int pos) {
- // TODO: Need to revisit this logic with bigram suggestions
- final boolean isSuggested = (pos != 0);
-
- final int color;
- if (index == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) {
- color = mColorAutoCorrect;
- } else if (index == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) {
- color = mColorValidTypedWord;
- } else if (isSuggested) {
- color = mColorSuggested;
- } else {
- color = mColorTypedWord;
- }
- if (LatinImeLogger.sDBG && suggestedWords.size() > 1) {
- // If we auto-correct, then the autocorrection is in slot 0 and the typed word
- // is in slot 1.
- if (index == mCenterSuggestionIndex
- && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet(
- suggestedWords.getWord(1), suggestedWords.getWord(0))) {
- return 0xFFFF0000;
- }
- }
-
- if (suggestedWords.mIsObsoleteSuggestions && isSuggested) {
- return applyAlpha(color, mAlphaObsoleted);
- } else {
- return color;
- }
- }
-
- private static int applyAlpha(final int color, final float alpha) {
- final int newAlpha = (int)(Color.alpha(color) * alpha);
- return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
- }
-
- private static void addDivider(final ViewGroup stripView, final View divider) {
- stripView.addView(divider);
- final LinearLayout.LayoutParams params =
- (LinearLayout.LayoutParams)divider.getLayoutParams();
- params.gravity = Gravity.CENTER;
- }
-
- public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView,
- final ViewGroup placer, final int stripWidth) {
- if (suggestedWords.mIsPunctuationSuggestions) {
- layoutPunctuationSuggestions(suggestedWords, stripView);
- return;
- }
-
- final int countInStrip = mSuggestionsCountInStrip;
- setupTexts(suggestedWords, countInStrip);
- mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
- int x = 0;
- for (int index = 0; index < countInStrip; index++) {
- final int pos = getWordPosition(index, suggestedWords);
-
- if (index != 0) {
- final View divider = mDividers.get(pos);
- // Add divider if this isn't the left most suggestion in suggestions strip.
- addDivider(stripView, divider);
- x += divider.getMeasuredWidth();
- }
-
- final CharSequence styled = mTexts.get(pos);
- final TextView word = mWords.get(pos);
- if (index == mCenterSuggestionIndex && mMoreSuggestionsAvailable) {
- // TODO: This "more suggestions hint" should have nicely designed icon.
- word.setCompoundDrawablesWithIntrinsicBounds(
- null, null, null, mMoreSuggestionsHint);
- // HACK: To align with other TextView that has no compound drawables.
- word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight());
- } else {
- word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
- }
-
- // Disable this suggestion if the suggestion is null or empty.
- word.setEnabled(!TextUtils.isEmpty(styled));
- word.setTextColor(getSuggestionTextColor(index, suggestedWords, pos));
- final int width = getSuggestionWidth(index, stripWidth);
- final CharSequence text = getEllipsizedText(styled, width, word.getPaint());
- final float scaleX = word.getTextScaleX();
- word.setText(text); // TextView.setText() resets text scale x to 1.0.
- word.setTextScaleX(scaleX);
- stripView.addView(word);
- setLayoutWeight(
- word, getSuggestionWeight(index), ViewGroup.LayoutParams.MATCH_PARENT);
- x += word.getMeasuredWidth();
-
- if (DBG && pos < suggestedWords.size()) {
- final String debugInfo = Utils.getDebugInfo(suggestedWords, pos);
- if (debugInfo != null) {
- final TextView info = mInfos.get(pos);
- info.setText(debugInfo);
- placer.addView(info);
- info.measure(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- final int infoWidth = info.getMeasuredWidth();
- final int y = info.getMeasuredHeight();
- ViewLayoutUtils.placeViewAt(
- info, x - infoWidth, y, infoWidth, info.getMeasuredHeight());
- }
- }
- }
- }
-
- private int getSuggestionWidth(final int index, final int maxWidth) {
- final int paddings = mPadding * mSuggestionsCountInStrip;
- final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
- final int availableWidth = maxWidth - paddings - dividers;
- return (int)(availableWidth * getSuggestionWeight(index));
- }
-
- private float getSuggestionWeight(final int index) {
- if (index == mCenterSuggestionIndex) {
- return mCenterSuggestionWeight;
- } else {
- // TODO: Revisit this for cases of 5 or more suggestions
- return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1);
- }
- }
-
- private void setupTexts(final SuggestedWords suggestedWords, final int countInStrip) {
- mTexts.clear();
- final int count = Math.min(suggestedWords.size(), countInStrip);
- for (int pos = 0; pos < count; pos++) {
- final CharSequence styled = getStyledSuggestionWord(suggestedWords, pos);
- mTexts.add(styled);
- }
- for (int pos = count; pos < countInStrip; pos++) {
- // Make this inactive for touches in layout().
- mTexts.add(null);
- }
- }
-
- private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords,
- final ViewGroup stripView) {
- final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
- for (int index = 0; index < countInStrip; index++) {
- if (index != 0) {
- // Add divider if this isn't the left most suggestion in suggestions strip.
- addDivider(stripView, mDividers.get(index));
- }
-
- final TextView word = mWords.get(index);
- word.setEnabled(true);
- word.setTextColor(mColorAutoCorrect);
- final String text = suggestedWords.getWord(index);
- word.setText(text);
- word.setTextScaleX(1.0f);
- word.setCompoundDrawables(null, null, null, null);
- stripView.addView(word);
- setLayoutWeight(word, 1.0f, mSuggestionsStripHeight);
- }
- mMoreSuggestionsAvailable = false;
- }
-
- public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView,
- final int stripWidth, final CharSequence hintText, final OnClickListener listener) {
- final int width = stripWidth - mDividerWidth - mPadding * 2;
-
- final TextView wordView = mWordToSaveView;
- wordView.setTextColor(mColorTypedWord);
- final int wordWidth = (int)(width * mCenterSuggestionWeight);
- final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint());
- final float wordScaleX = wordView.getTextScaleX();
- wordView.setTag(word);
- wordView.setText(text);
- wordView.setTextScaleX(wordScaleX);
- stripView.addView(wordView);
- setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
-
- stripView.addView(mDividers.get(0));
-
- final TextView leftArrowView = mLeftwardsArrowView;
- leftArrowView.setTextColor(mColorAutoCorrect);
- leftArrowView.setText(LEFTWARDS_ARROW);
- stripView.addView(leftArrowView);
-
- final TextView hintView = mHintToSaveView;
- hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
- hintView.setTextColor(mColorAutoCorrect);
- final int hintWidth = width - wordWidth - leftArrowView.getWidth();
- final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
- hintView.setText(hintText);
- hintView.setTextScaleX(hintScaleX);
- stripView.addView(hintView);
- setLayoutWeight(
- hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
-
- wordView.setOnClickListener(listener);
- leftArrowView.setOnClickListener(listener);
- hintView.setOnClickListener(listener);
- }
-
- public CharSequence getAddToDictionaryWord() {
- return (CharSequence)mWordToSaveView.getTag();
- }
-
- public boolean isAddToDictionaryShowing(final View v) {
- return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView;
- }
-
- private static void setLayoutWeight(final View v, final float weight, final int height) {
- final ViewGroup.LayoutParams lp = v.getLayoutParams();
- if (lp instanceof LinearLayout.LayoutParams) {
- final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
- llp.weight = weight;
- llp.width = 0;
- llp.height = height;
- }
- }
-
- private static float getTextScaleX(final CharSequence text, final int maxWidth,
- final TextPaint paint) {
- paint.setTextScaleX(1.0f);
- final int width = getTextWidth(text, paint);
- if (width <= maxWidth) {
- return 1.0f;
- }
- return maxWidth / (float)width;
- }
-
- private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth,
- final TextPaint paint) {
- if (text == null) return null;
- paint.setTextScaleX(1.0f);
- final int width = getTextWidth(text, paint);
- if (width <= maxWidth) {
- return text;
- }
- final float scaleX = maxWidth / (float)width;
- if (scaleX >= MIN_TEXT_XSCALE) {
- paint.setTextScaleX(scaleX);
- 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 CharSequence ellipsized = TextUtils.ellipsize(
- text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
- paint.setTextScaleX(MIN_TEXT_XSCALE);
- return ellipsized;
- }
-
- private static int getTextWidth(final CharSequence text, final TextPaint paint) {
- if (TextUtils.isEmpty(text)) return 0;
- 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);
- 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;
-
- switch (styles[0].getStyle()) {
- case Typeface.BOLD: return Typeface.DEFAULT_BOLD;
- // TODO: BOLD_ITALIC, ITALIC case?
- default: return Typeface.DEFAULT;
- }
- }
- }
+ private final SuggestionStripLayoutHelper mLayoutHelper;
/**
* Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user.
@@ -579,19 +93,17 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip);
for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) {
final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null);
- word.setTag(pos);
word.setOnClickListener(this);
word.setOnLongClickListener(this);
- mWords.add(word);
+ mWordViews.add(word);
final View divider = inflater.inflate(R.layout.suggestion_divider, null);
- divider.setTag(pos);
divider.setOnClickListener(this);
- mDividers.add(divider);
- mInfos.add((TextView)inflater.inflate(R.layout.suggestion_info, null));
+ mDividerViews.add(divider);
+ mDebugInfoViews.add((TextView)inflater.inflate(R.layout.suggestion_info, null));
}
- mParams = new SuggestionStripViewParams(
- context, attrs, defStyle, mWords, mDividers, mInfos);
+ mLayoutHelper = new SuggestionStripLayoutHelper(
+ context, attrs, defStyle, mWordViews, mDividerViews, mDebugInfoViews);
mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null);
mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer
@@ -617,24 +129,25 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
public void setSuggestions(final SuggestedWords suggestedWords) {
clear();
mSuggestedWords = suggestedWords;
- mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth());
+ mLayoutHelper.layout(mSuggestedWords, mSuggestionsStrip, this);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords);
}
}
public int setMoreSuggestionsHeight(final int remainingHeight) {
- return mParams.setMoreSuggestionsHeight(remainingHeight);
+ return mLayoutHelper.setMoreSuggestionsHeight(remainingHeight);
}
public boolean isShowingAddToDictionaryHint() {
return mSuggestionsStrip.getChildCount() > 0
- && mParams.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0));
+ && mLayoutHelper.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0));
}
public void showAddToDictionaryHint(final String word, final CharSequence hintText) {
clear();
- mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText, this);
+ mLayoutHelper.layoutAddToDictionaryHint(
+ word, mSuggestionsStrip, getWidth(), hintText, this);
}
public boolean dismissAddToDictionaryHint() {
@@ -649,27 +162,27 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mSuggestionsStrip.removeAllViews();
removeAllViews();
addView(mSuggestionsStrip);
- dismissMoreSuggestions();
+ mMoreSuggestionsView.dismissMoreKeysPanel();
}
private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener() {
@Override
public void onSuggestionSelected(final int index, final SuggestedWordInfo wordInfo) {
mListener.pickSuggestionManually(index, wordInfo);
- dismissMoreSuggestions();
+ mMoreSuggestionsView.dismissMoreKeysPanel();
}
@Override
public void onCancelInput() {
- dismissMoreSuggestions();
+ mMoreSuggestionsView.dismissMoreKeysPanel();
}
};
private final MoreKeysPanel.Controller mMoreSuggestionsController =
new MoreKeysPanel.Controller() {
@Override
- public boolean onDismissMoreKeysPanel() {
- return mMainKeyboardView.onDismissMoreKeysPanel();
+ public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {
+ mMainKeyboardView.onDismissMoreKeysPanel(panel);
}
@Override
@@ -678,18 +191,15 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
@Override
- public void onCancelMoreKeysPanel() {
- dismissMoreSuggestions();
+ public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {
+ mMoreSuggestionsView.dismissMoreKeysPanel();
}
};
- boolean dismissMoreSuggestions() {
- return mMoreSuggestionsView.dismissMoreKeysPanel();
- }
-
@Override
public boolean onLongClick(final View view) {
- KeyboardSwitcher.getInstance().hapticAndAudioFeedback(Constants.NOT_A_CODE);
+ AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(
+ Constants.NOT_A_CODE, this);
return showMoreSuggestions();
}
@@ -698,30 +208,30 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
if (parentKeyboard == null) {
return false;
}
- final SuggestionStripViewParams params = mParams;
- if (!params.mMoreSuggestionsAvailable) {
+ final SuggestionStripLayoutHelper layoutHelper = mLayoutHelper;
+ if (!layoutHelper.mMoreSuggestionsAvailable) {
return false;
}
final int stripWidth = getWidth();
final View container = mMoreSuggestionsContainer;
final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight();
final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
- builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth,
- (int)(maxWidth * params.mMinMoreSuggestionsWidth),
- params.getMaxMoreSuggestionsRow(), parentKeyboard);
+ builder.layout(mSuggestedWords, layoutHelper.mSuggestionsCountInStrip, maxWidth,
+ (int)(maxWidth * layoutHelper.mMinMoreSuggestionsWidth),
+ layoutHelper.getMaxMoreSuggestionsRow(), parentKeyboard);
mMoreSuggestionsView.setKeyboard(builder.build());
container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView;
final int pointX = stripWidth / 2;
- final int pointY = -params.mMoreSuggestionsBottomGap;
+ final int pointY = -layoutHelper.mMoreSuggestionsBottomGap;
moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY,
mMoreSuggestionsListener);
mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
mOriginX = mLastX;
mOriginY = mLastY;
- for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
- mWords.get(i).setPressed(false);
+ for (int i = 0; i < layoutHelper.mSuggestionsCountInStrip; i++) {
+ mWordViews.get(i).setPressed(false);
}
return true;
}
@@ -791,18 +301,23 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
public void onClick(final View view) {
- if (mParams.isAddToDictionaryShowing(view)) {
- mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString());
+ if (mLayoutHelper.isAddToDictionaryShowing(view)) {
+ mListener.addWordToUserDictionary(mLayoutHelper.getAddToDictionaryWord());
clear();
return;
}
final Object tag = view.getTag();
- if (!(tag instanceof Integer))
+ // Integer tag is set at
+ // {@link SuggestionStripLayoutHelper#setupWordViewsTextAndColor(SuggestedWords,int)} and
+ // {@link SuggestionStripLayoutHelper#layoutPunctuationSuggestions(SuggestedWords,ViewGroup}
+ if (!(tag instanceof Integer)) {
return;
+ }
final int index = (Integer) tag;
- if (index >= mSuggestedWords.size())
+ if (index >= mSuggestedWords.size()) {
return;
+ }
final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index);
mListener.pickSuggestionManually(index, wordInfo);
@@ -811,6 +326,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- dismissMoreSuggestions();
+ mMoreSuggestionsView.dismissMoreKeysPanel();
}
}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index 2b6fda381..21426d1eb 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -16,10 +16,6 @@
package com.android.inputmethod.latin.userdictionary;
-import com.android.inputmethod.compat.UserDictionaryCompatUtils;
-import com.android.inputmethod.latin.LocaleUtils;
-import com.android.inputmethod.latin.R;
-
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
@@ -30,6 +26,10 @@ 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 java.util.ArrayList;
import java.util.Locale;
import java.util.TreeSet;
@@ -65,6 +65,8 @@ public class UserDictionaryAddWordContents {
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);
@@ -76,7 +78,9 @@ public class UserDictionaryAddWordContents {
final String word = args.getString(EXTRA_WORD);
if (null != word) {
mWordEditText.setText(word);
- mWordEditText.setSelection(word.length());
+ // Use getText in case the edit text modified the text we set. This happens when
+ // it's too long to be edited.
+ mWordEditText.setSelection(mWordEditText.getText().length());
}
final String shortcut;
if (UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
@@ -94,6 +98,16 @@ public class UserDictionaryAddWordContents {
updateLocale(args.getString(EXTRA_LOCALE));
}
+ /* 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);
+ }
+
// locale may be null, this means default locale
// It may also be the empty string, which means "all locales"
/* package */ void updateLocale(final String locale) {
@@ -147,6 +161,8 @@ public class UserDictionaryAddWordContents {
// 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
@@ -258,4 +274,8 @@ public class UserDictionaryAddWordContents {
localesList.add(new LocaleRenderer(activity, null)); // meaning: select another locale
return localesList;
}
+
+ public String getCurrentUserDictionaryLocale() {
+ return mLocale;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
index 58c8f266c..4fc132f68 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
@@ -57,23 +57,39 @@ public class UserDictionaryAddWordFragment extends Fragment
private boolean mIsDeleting = false;
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
+ 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(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+ 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(Menu menu, MenuInflater inflater) {
+ 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(
@@ -87,7 +103,7 @@ public class UserDictionaryAddWordFragment extends Fragment
/**
* Callback for the framework when a menu option is pressed.
*
- * @param MenuItem the item that was pressed
+ * @param item the item that was pressed
* @return false to allow normal menu processing to proceed, true to consume it here
*/
@Override
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
index 6e64882b6..32c4950da 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -16,10 +16,8 @@
package com.android.inputmethod.latin.userdictionary;
-import com.android.inputmethod.latin.LocaleUtils;
-import com.android.inputmethod.latin.R;
-
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
@@ -28,7 +26,14 @@ import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.provider.UserDictionary;
import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+import java.util.List;
import java.util.Locale;
import java.util.TreeSet;
@@ -52,8 +57,7 @@ public class UserDictionaryList extends PreferenceFragment {
final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI,
new String[] { UserDictionary.Words.LOCALE },
null, null, null);
- final TreeSet<String> localeList = new TreeSet<String>();
- boolean addedAllLocale = false;
+ final TreeSet<String> localeSet = new TreeSet<String>();
if (null == cursor) {
// The user dictionary service is not present or disabled. Return null.
return null;
@@ -61,20 +65,39 @@ public class UserDictionaryList extends PreferenceFragment {
final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);
do {
final String locale = cursor.getString(columnIndex);
- final boolean allLocale = TextUtils.isEmpty(locale);
- localeList.add(allLocale ? "" : locale);
- if (allLocale) {
- addedAllLocale = true;
- }
+ localeSet.add(null != locale ? locale : "");
} while (cursor.moveToNext());
}
- if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED && !addedAllLocale) {
+ if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
// For ICS, we need to show "For all languages" in case that the keyboard locale
// is different from the system locale
- localeList.add("");
+ localeSet.add("");
+ }
+
+ final InputMethodManager imm =
+ (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ final List<InputMethodInfo> imis = imm.getEnabledInputMethodList();
+ for (final InputMethodInfo imi : imis) {
+ final List<InputMethodSubtype> subtypes =
+ imm.getEnabledInputMethodSubtypeList(
+ imi, true /* allowsImplicitlySelectedSubtypes */);
+ for (InputMethodSubtype subtype : subtypes) {
+ final String locale = subtype.getLocale();
+ if (!TextUtils.isEmpty(locale)) {
+ localeSet.add(locale);
+ }
+ }
+ }
+
+ // We come here after we have collected locales from existing user dictionary entries and
+ // enabled subtypes. If we already have the locale-without-country version of the system
+ // locale, we don't add the system locale to avoid confusion even though it's technically
+ // correct to add it.
+ if (!localeSet.contains(Locale.getDefault().getLanguage().toString())) {
+ localeSet.add(Locale.getDefault().toString());
}
- localeList.add(Locale.getDefault().toString());
- return localeList;
+
+ return localeSet;
}
/**
@@ -84,13 +107,19 @@ public class UserDictionaryList extends PreferenceFragment {
protected void createUserDictSettings(PreferenceGroup userDictGroup) {
final Activity activity = getActivity();
userDictGroup.removeAll();
- final TreeSet<String> localeList =
+ final TreeSet<String> localeSet =
UserDictionaryList.getUserDictionaryLocalesSet(activity);
- if (localeList.isEmpty()) {
+ if (localeSet.size() > 1) {
+ // Have an "All languages" entry in the languages list if there are two or more active
+ // languages
+ localeSet.add("");
+ }
+
+ if (localeSet.isEmpty()) {
userDictGroup.addPreference(createUserDictionaryPreference(null, activity));
} else {
- for (String locale : localeList) {
+ for (String locale : localeSet) {
userDictGroup.addPreference(createUserDictionaryPreference(locale, activity));
}
}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
index 50dda9663..7571e87c5 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
@@ -1,17 +1,17 @@
-/**
- * Copyright (C) 2013 Google Inc.
+/*
+ * 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
+ * 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
+ * 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.
+ * 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;
@@ -150,7 +150,9 @@ public class UserDictionarySettings extends ListFragment {
listView.setEmptyView(emptyView);
setHasOptionsMenu(true);
-
+ // Show the language as a subtitle of the action bar
+ getActivity().getActionBar().setSubtitle(
+ UserDictionarySettingsUtils.getLocaleDisplayName(getActivity(), mLocale));
}
@SuppressWarnings("deprecation")
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java
new file mode 100644
index 000000000..e58727ec4
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java
@@ -0,0 +1,42 @@
+/*
+ * 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.utils.LocaleUtils;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import java.util.Locale;
+
+/**
+ * Utilities of the user dictionary settings
+ * TODO: We really want to move these utilities to a static library.
+ */
+public class UserDictionarySettingsUtils {
+ public static String getLocaleDisplayName(Context context, String localeStr) {
+ if (TextUtils.isEmpty(localeStr)) {
+ // CAVEAT: localeStr should not be null because a null locale stands for the system
+ // locale in UserDictionary.Words.addWord.
+ return context.getResources().getString(R.string.user_dict_settings_all_languages);
+ }
+ final Locale locale = LocaleUtils.constructLocaleFromString(localeStr);
+ final Locale systemLocale = context.getResources().getConfiguration().locale;
+ return locale.getDisplayName(systemLocale);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index 99b95ea98..215faa0c7 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+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.IS_ADDITIONAL_SUBTYPE;
@@ -25,12 +25,14 @@ import android.os.Build;
import android.text.TextUtils;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.latin.R;
+
import java.util.ArrayList;
-public final class AdditionalSubtype {
+public final class AdditionalSubtypeUtils {
private static final InputMethodSubtype[] EMPTY_SUBTYPE_ARRAY = new InputMethodSubtype[0];
- private AdditionalSubtype() {
+ private AdditionalSubtypeUtils() {
// This utility class is not publicly instantiable.
}
@@ -46,17 +48,18 @@ public final class AdditionalSubtype {
final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName;
final String layoutDisplayNameExtraValue;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
- && SubtypeLocale.isExceptionalLocale(localeString)) {
- final String layoutDisplayName = SubtypeLocale.getKeyboardLayoutSetDisplayName(
+ && SubtypeLocaleUtils.isExceptionalLocale(localeString)) {
+ final String layoutDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(
keyboardLayoutSetName);
- layoutDisplayNameExtraValue = StringUtils.appendToCsvIfNotExists(
+ layoutDisplayNameExtraValue = StringUtils.appendToCommaSplittableTextIfNotExists(
UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + layoutDisplayName, extraValue);
} else {
layoutDisplayNameExtraValue = extraValue;
}
- final String additionalSubtypeExtraValue = StringUtils.appendToCsvIfNotExists(
- IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue);
- final int nameId = SubtypeLocale.getSubtypeNameId(localeString, keyboardLayoutSetName);
+ final String additionalSubtypeExtraValue =
+ StringUtils.appendToCommaSplittableTextIfNotExists(
+ IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue);
+ final int nameId = SubtypeLocaleUtils.getSubtypeNameId(localeString, keyboardLayoutSetName);
return new InputMethodSubtype(nameId, R.drawable.ic_subtype_keyboard,
localeString, KEYBOARD_MODE,
layoutExtraValue + "," + additionalSubtypeExtraValue, false, false);
@@ -64,10 +67,11 @@ public final class AdditionalSubtype {
public static String getPrefSubtype(final InputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
- final String keyboardLayoutSetName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
+ final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName;
- final String extraValue = StringUtils.removeFromCsvIfExists(layoutExtraValue,
- StringUtils.removeFromCsvIfExists(IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue()));
+ final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists(
+ layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists(
+ IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue()));
final String basePrefSubtype = localeString + LOCALE_AND_LAYOUT_SEPARATOR
+ keyboardLayoutSetName;
return extraValue.isEmpty() ? basePrefSubtype
@@ -94,7 +98,7 @@ public final class AdditionalSubtype {
CollectionUtils.newArrayList(prefSubtypeArray.length);
for (final String prefSubtype : prefSubtypeArray) {
final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
- if (subtype.getNameResId() == SubtypeLocale.UNKNOWN_KEYBOARD_LAYOUT) {
+ if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) {
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
// layout has been removed.
continue;
diff --git a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
new file mode 100644
index 000000000..08a2a8c5a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+public final class ApplicationUtils {
+ private static final String TAG = ApplicationUtils.class.getSimpleName();
+
+ private ApplicationUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static int getAcitivityTitleResId(final Context context,
+ final Class<? extends Activity> cls) {
+ final ComponentName cn = new ComponentName(context, cls);
+ try {
+ final ActivityInfo ai = context.getPackageManager().getActivityInfo(cn, 0);
+ if (ai != null) {
+ return ai.labelRes;
+ }
+ } catch (final NameNotFoundException e) {
+ Log.e(TAG, "Failed to get settings activity title res id.", e);
+ }
+ return 0;
+ }
+
+ /**
+ * A utility method to get the application's PackageInfo.versionName
+ * @return the application's PackageInfo.versionName
+ */
+ public static String getVersionName(final Context context) {
+ try {
+ if (context == null) {
+ return "";
+ }
+ final String packageName = context.getPackageName();
+ final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+ return info.versionName;
+ } catch (final NameNotFoundException e) {
+ Log.e(TAG, "Could not find version info.", e);
+ }
+ return "";
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
index fa35922b0..066c5fd32 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -14,8 +14,12 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import android.text.TextUtils;
@@ -23,21 +27,22 @@ import android.util.Log;
import java.util.concurrent.ConcurrentHashMap;
-public final class AutoCorrection {
+public final class AutoCorrectionUtils {
private static final boolean DBG = LatinImeLogger.sDBG;
- private static final String TAG = AutoCorrection.class.getSimpleName();
+ private static final String TAG = AutoCorrectionUtils.class.getSimpleName();
private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
- private AutoCorrection() {
+ private AutoCorrectionUtils() {
// Purely static class: can't instantiate.
}
- public static boolean isValidWord(final ConcurrentHashMap<String, Dictionary> dictionaries,
- final String word, final boolean ignoreCase) {
+ public static boolean isValidWord(final Suggest suggest, final String word,
+ final boolean ignoreCase) {
if (TextUtils.isEmpty(word)) {
return false;
}
- final String lowerCasedWord = word.toLowerCase();
+ final ConcurrentHashMap<String, Dictionary> dictionaries = suggest.getUnigramDictionaries();
+ final String lowerCasedWord = word.toLowerCase(suggest.mLocale);
for (final String key : dictionaries.keySet()) {
final Dictionary dictionary = dictionaries.get(key);
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
@@ -73,13 +78,6 @@ public final class AutoCorrection {
return maxFreq;
}
- // Returns true if this is in any of the dictionaries.
- public static boolean isInTheDictionary(
- final ConcurrentHashMap<String, Dictionary> dictionaries,
- final String word, final boolean ignoreCase) {
- return isValidWord(dictionaries, word, ignoreCase);
- }
-
public static boolean suggestionExceedsAutoCorrectionThreshold(
final SuggestedWordInfo suggestion, final String consideredWord,
final float autoCorrectionThreshold) {
diff --git a/java/src/com/android/inputmethod/latin/utils/Base64Reader.java b/java/src/com/android/inputmethod/latin/utils/Base64Reader.java
new file mode 100644
index 000000000..3eca6e744
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/Base64Reader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.LineNumberReader;
+
+@UsedForTesting
+public class Base64Reader {
+ private final LineNumberReader mReader;
+
+ private String mLine;
+ private int mCharPos;
+ private int mByteCount;
+
+ @UsedForTesting
+ public Base64Reader(final LineNumberReader reader) {
+ mReader = reader;
+ reset();
+ }
+
+ @UsedForTesting
+ public void reset() {
+ mLine = null;
+ mCharPos = 0;
+ mByteCount = 0;
+ }
+
+ @UsedForTesting
+ public int getLineNumber() {
+ return mReader.getLineNumber();
+ }
+
+ @UsedForTesting
+ public int getByteCount() {
+ return mByteCount;
+ }
+
+ private void fillBuffer() throws IOException {
+ if (mLine == null || mCharPos >= mLine.length()) {
+ mLine = mReader.readLine();
+ mCharPos = 0;
+ }
+ if (mLine == null) {
+ throw new EOFException();
+ }
+ }
+
+ private int peekUint8() throws IOException {
+ fillBuffer();
+ final char c = mLine.charAt(mCharPos);
+ if (c >= 'A' && c <= 'Z')
+ return c - 'A' + 0;
+ if (c >= 'a' && c <= 'z')
+ return c - 'a' + 26;
+ if (c >= '0' && c <= '9')
+ return c - '0' + 52;
+ if (c == '+')
+ return 62;
+ if (c == '/')
+ return 63;
+ if (c == '=')
+ return 0;
+ throw new RuntimeException("Unknown character '" + c + "' in base64 at line "
+ + mReader.getLineNumber());
+ }
+
+ private int getUint8() throws IOException {
+ final int value = peekUint8();
+ mCharPos++;
+ return value;
+ }
+
+ @UsedForTesting
+ public int readUint8() throws IOException {
+ final int value1, value2;
+ switch (mByteCount % 3) {
+ case 0:
+ value1 = getUint8() << 2;
+ value2 = value1 | (peekUint8() >> 4);
+ break;
+ case 1:
+ value1 = (getUint8() & 0x0f) << 4;
+ value2 = value1 | (peekUint8() >> 2);
+ break;
+ default:
+ value1 = (getUint8() & 0x03) << 6;
+ value2 = value1 | getUint8();
+ break;
+ }
+ mByteCount++;
+ return value2;
+ }
+
+ @UsedForTesting
+ public short readInt16() throws IOException {
+ final int data = readUint8() << 8;
+ return (short)(data | readUint8());
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java
index 489a74ef1..ae1fd3f79 100644
--- a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
+++ b/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
diff --git a/java/src/com/android/inputmethod/latin/utils/ByteArrayWrapper.java b/java/src/com/android/inputmethod/latin/utils/ByteArrayWrapper.java
new file mode 100644
index 000000000..1bb27aa2b
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ByteArrayWrapper.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
+
+/**
+ * This class provides an implementation for the FusionDictionary buffer interface that is backed
+ * by a simpled byte array. It allows to create a binary dictionary in memory.
+ */
+public final class ByteArrayWrapper implements FusionDictionaryBufferInterface {
+ private byte[] mBuffer;
+ private int mPosition;
+
+ public ByteArrayWrapper(final byte[] buffer) {
+ mBuffer = buffer;
+ mPosition = 0;
+ }
+
+ @Override
+ public int readUnsignedByte() {
+ return mBuffer[mPosition++] & 0xFF;
+ }
+
+ @Override
+ public int readUnsignedShort() {
+ final int retval = readUnsignedByte();
+ return (retval << 8) + readUnsignedByte();
+ }
+
+ @Override
+ public int readUnsignedInt24() {
+ final int retval = readUnsignedShort();
+ return (retval << 8) + readUnsignedByte();
+ }
+
+ @Override
+ public int readInt() {
+ final int retval = readUnsignedShort();
+ return (retval << 16) + readUnsignedShort();
+ }
+
+ @Override
+ public int position() {
+ return mPosition;
+ }
+
+ @Override
+ public void position(int position) {
+ mPosition = position;
+ }
+
+ @Override
+ public void put(final byte b) {
+ mBuffer[mPosition++] = b;
+ }
+
+ @Override
+ public int limit() {
+ return mBuffer.length - 1;
+ }
+
+ @Override
+ public int capacity() {
+ return mBuffer.length;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 4b8d1ac11..2f91c5743 100644
--- a/java/src/com/android/inputmethod/latin/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -14,11 +14,14 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+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 java.util.Locale;
public final class CapsModeUtils {
diff --git a/java/src/com/android/inputmethod/latin/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
index a8623cc63..98f0d8b68 100644
--- a/java/src/com/android/inputmethod/latin/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.util.SparseArray;
diff --git a/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java
index 792a446c9..5ccf0e079 100644
--- a/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.text.TextUtils;
import android.view.inputmethod.CompletionInfo;
diff --git a/java/src/com/android/inputmethod/latin/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
index af270e1e4..72f2cd2d9 100644
--- a/java/src/com/android/inputmethod/latin/CoordinateUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
public final class CoordinateUtils {
private static final int INDEX_X = 0;
diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
new file mode 100644
index 000000000..36b927eea
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * Utility methods for parsing and serializing Comma-Separated Values. The public APIs of this
+ * utility class are {@link #split(String)}, {@link #split(int,String)}, {@link #join(String...)},
+ * {@link #join(int,String...)}, and {@link #join(int,int[],String...)}.
+ *
+ * This class implements CSV parsing and serializing methods conforming to RFC 4180 with an
+ * exception:
+ * These methods can't handle new line code escaped in double quotes.
+ */
+@UsedForTesting
+public final class CsvUtils {
+ private CsvUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static final int SPLIT_FLAGS_NONE = 0x0;
+ /**
+ * A flag for {@link #split(int,String)}. If this flag is specified, the method will trim
+ * spaces around fields before splitting. Note that this behavior doesn't conform to RFC 4180.
+ */
+ public static final int SPLIT_FLAGS_TRIM_SPACES = 0x1;
+
+ public static final int JOIN_FLAGS_NONE = 0x0;
+ /**
+ * A flag for {@link #join(int,String...)} and {@link #join(int,int[],String...)}. If this
+ * flag is specified, these methods surround each field with double quotes before joining.
+ */
+ public static final int JOIN_FLAGS_ALWAYS_QUOTED = 0x1;
+ /**
+ * A flag for {@link #join(int,String...)} and {@link #join(int,int[],String...)}. If this
+ * flag is specified, these methods add an extra space just after the comma separator. Note that
+ * this behavior doesn't conform to RFC 4180.
+ */
+ public static final int JOIN_FLAGS_EXTRA_SPACE = 0x2;
+
+ // Note that none of these characters match high or low surrogate characters, so we need not
+ // take care of matching by code point.
+ private static final char COMMA = ',';
+ private static final char SPACE = ' ';
+ private static final char QUOTE = '"';
+
+ @SuppressWarnings("serial")
+ public static class CsvParseException extends RuntimeException {
+ public CsvParseException(final String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Find the first non-space character in the text.
+ *
+ * @param text the text to be searched.
+ * @param fromIndex the index to start the search from, inclusive.
+ * @return the index of the first occurrence of the non-space character in the
+ * <code>text</code> that is greater than or equal to <code>fromIndex</code>, or the length of
+ * the <code>text</code> if the character does not occur.
+ */
+ private static int indexOfNonSpace(final String text, final int fromIndex) {
+ final int length = text.length();
+ if (fromIndex < 0 || fromIndex > length) {
+ throw new IllegalArgumentException("text=" + text + " fromIndex=" + fromIndex);
+ }
+ int index = fromIndex;
+ while (index < length && text.charAt(index) == SPACE) {
+ index++;
+ }
+ return index;
+ }
+
+ /**
+ * Find the last non-space character in the text.
+ *
+ * @param text the text to be searched.
+ * @param fromIndex the index to start the search from, exclusive.
+ * @param toIndex the index to end the search at, inclusive. Usually <code>toIndex</code>
+ * points a non-space character.
+ * @return the index of the last occurrence of the non-space character in the
+ * <code>text</code>, exclusive. It is less than <code>fromIndex</code> and greater than
+ * <code>toIndex</code>, or <code>toIndex</code> if the character does not occur.
+ */
+ private static int lastIndexOfNonSpace(final String text, final int fromIndex,
+ final int toIndex) {
+ if (toIndex < 0 || fromIndex > text.length() || fromIndex < toIndex) {
+ throw new IllegalArgumentException(
+ "text=" + text + " fromIndex=" + fromIndex + " toIndex=" + toIndex);
+ }
+ int index = fromIndex;
+ while (index > toIndex && text.charAt(index - 1) == SPACE) {
+ index--;
+ }
+ return index;
+ }
+
+ /**
+ * Find the index of a comma separator. The search takes account of quoted fields and escape
+ * quotes.
+ *
+ * @param text the text to be searched.
+ * @param fromIndex the index to start the search from, inclusive.
+ * @return the index of the comma separator, exclusive.
+ */
+ private static int indexOfSeparatorComma(final String text, final int fromIndex) {
+ final int length = text.length();
+ if (fromIndex < 0 || fromIndex > length) {
+ throw new IllegalArgumentException("text=" + text + " fromIndex=" + fromIndex);
+ }
+ final boolean isQuoted = (length - fromIndex > 0 && text.charAt(fromIndex) == QUOTE);
+ for (int index = fromIndex + (isQuoted ? 1 : 0); index < length; index++) {
+ final char c = text.charAt(index);
+ if (c == COMMA && !isQuoted) {
+ return index;
+ }
+ if (c == QUOTE) {
+ final int nextIndex = index + 1;
+ if (nextIndex < length && text.charAt(nextIndex) == QUOTE) {
+ // Quoted quote.
+ index = nextIndex;
+ continue;
+ }
+ // Closing quote.
+ final int endIndex = text.indexOf(COMMA, nextIndex);
+ return endIndex < 0 ? length : endIndex;
+ }
+ }
+ return length;
+ }
+
+ /**
+ * Removing any enclosing QUOTEs (U+0022), and convert any two consecutive QUOTEs into
+ * one QUOTE.
+ *
+ * @param text the CSV field text that may have enclosing QUOTEs and escaped QUOTE character.
+ * @return the text that has been removed enclosing quotes and converted two consecutive QUOTEs
+ * into one QUOTE.
+ */
+ @UsedForTesting
+ /* private */ static String unescapeField(final String text) {
+ StringBuilder sb = null;
+ final int length = text.length();
+ final boolean isQuoted = (length > 0 && text.charAt(0) == QUOTE);
+ int start = isQuoted ? 1 : 0;
+ int end = start;
+ while (start <= length && (end = text.indexOf(QUOTE, start)) >= start) {
+ final int nextIndex = end + 1;
+ if (nextIndex == length && isQuoted) {
+ // Closing quote.
+ break;
+ }
+ if (nextIndex < length && text.charAt(nextIndex) == QUOTE) {
+ if (!isQuoted) {
+ throw new CsvParseException("Escaped quote in text");
+ }
+ // Quoted quote.
+ if (sb == null) {
+ sb = new StringBuilder();
+ }
+ sb.append(text.substring(start, nextIndex));
+ start = nextIndex + 1;
+ } else {
+ throw new CsvParseException(
+ isQuoted ? "Raw quote in quoted text" : "Raw quote in text");
+ }
+ }
+ if (end < 0 && isQuoted) {
+ throw new CsvParseException("Unterminated quote");
+ }
+ if (end < 0) {
+ end = length;
+ }
+ if (sb != null && start < length) {
+ sb.append(text.substring(start, end));
+ }
+ return sb == null ? text.substring(start, end) : sb.toString();
+ }
+
+ /**
+ * Split the CSV text into fields. The leading and trailing spaces of the each field can be
+ * trimmed optionally.
+ *
+ * @param splitFlags flags for split behavior. {@link #SPLIT_FLAGS_TRIM_SPACES} will trim
+ * spaces around each fields.
+ * @param line the text of CSV fields.
+ * @return the array of unescaped CVS fields.
+ * @throws CsvParseException
+ */
+ @UsedForTesting
+ public static String[] split(final int splitFlags, final String line) throws CsvParseException {
+ final boolean trimSpaces = (splitFlags & SPLIT_FLAGS_TRIM_SPACES) != 0;
+ final ArrayList<String> fields = CollectionUtils.newArrayList();
+ final int length = line.length();
+ int start = 0;
+ do {
+ final int csvStart = trimSpaces ? indexOfNonSpace(line, start) : start;
+ final int end = indexOfSeparatorComma(line, csvStart);
+ final int csvEnd = trimSpaces ? lastIndexOfNonSpace(line, end, csvStart) : end;
+ final String csvText = unescapeField(line.substring(csvStart, csvEnd));
+ fields.add(csvText);
+ start = end + 1;
+ } while (start <= length);
+ return fields.toArray(new String[fields.size()]);
+ }
+
+ @UsedForTesting
+ public static String[] split(final String line) throws CsvParseException {
+ return split(SPLIT_FLAGS_NONE, line);
+ }
+
+ /**
+ * Convert the raw CSV field text to the escaped text. It adds enclosing QUOTEs (U+0022) if the
+ * raw value contains any QUOTE or comma. Also it converts any QUOTE character into two
+ * consecutive QUOTE characters.
+ *
+ * @param text the raw CSV field text to be escaped.
+ * @param alwaysQuoted true if the escaped text should always be enclosed by QUOTEs.
+ * @return the escaped text.
+ */
+ @UsedForTesting
+ /* private */ static String escapeField(final String text, final boolean alwaysQuoted) {
+ StringBuilder sb = null;
+ boolean needsQuoted = alwaysQuoted;
+ final int length = text.length();
+ int indexToBeAppended = 0;
+ for (int index = indexToBeAppended; index < length; index++) {
+ final char c = text.charAt(index);
+ if (c == COMMA) {
+ needsQuoted = true;
+ } else if (c == QUOTE) {
+ needsQuoted = true;
+ if (sb == null) {
+ sb = new StringBuilder();
+ }
+ sb.append(text.substring(indexToBeAppended, index));
+ indexToBeAppended = index + 1;
+ sb.append(QUOTE); // escaping quote.
+ sb.append(QUOTE); // escaped quote.
+ }
+ }
+ if (sb != null && indexToBeAppended < length) {
+ sb.append(text.substring(indexToBeAppended));
+ }
+ final String escapedText = (sb == null) ? text : sb.toString();
+ return needsQuoted ? QUOTE + escapedText + QUOTE : escapedText;
+ }
+
+ private static final String SPACES = " ";
+
+ private static void padToColumn(final StringBuilder sb, final int column) {
+ int padding;
+ while ((padding = column - sb.length()) > 0) {
+ final String spaces = SPACES.substring(0, Math.min(padding, SPACES.length()));
+ sb.append(spaces);
+ }
+ }
+
+ /**
+ * Join CSV text fields with comma. The column positions of the fields can be specified
+ * optionally. Surround each fields with double quotes before joining.
+ *
+ * @param joinFlags flags for join behavior. {@link #JOIN_FLAGS_EXTRA_SPACE} will add an extra
+ * space after each comma separator. {@link #JOIN_FLAGS_ALWAYS_QUOTED} will always add
+ * surrounding quotes to each element.
+ * @param columnPositions the array of column positions of the fields. It can be shorter than
+ * <code>fields</code> or null. Note that specifying the array column positions of the fields
+ * doesn't conform to RFC 4180.
+ * @param fields the CSV text fields.
+ * @return the string of the joined and escaped <code>fields</code>.
+ */
+ @UsedForTesting
+ public static String join(final int joinFlags, final int columnPositions[],
+ final String... fields) {
+ final boolean alwaysQuoted = (joinFlags & JOIN_FLAGS_ALWAYS_QUOTED) != 0;
+ final String separator = COMMA + ((joinFlags & JOIN_FLAGS_EXTRA_SPACE) != 0 ? " " : "");
+ final StringBuilder sb = new StringBuilder();
+ for (int index = 0; index < fields.length; index++) {
+ if (index > 0) {
+ sb.append(separator);
+ }
+ if (columnPositions != null && index < columnPositions.length) {
+ padToColumn(sb, columnPositions[index]);
+ }
+ final String escapedText = escapeField(fields[index], alwaysQuoted);
+ sb.append(escapedText);
+ }
+ return sb.toString();
+ }
+
+ @UsedForTesting
+ public static String join(final int joinFlags, final String... fields) {
+ return join(joinFlags, null, fields);
+ }
+
+ @UsedForTesting
+ public static String join(final String... fields) {
+ return join(JOIN_FLAGS_NONE, null, fields);
+ }
+}
diff --git a/java/src/com/android/inputmethod/dictionarypack/Utils.java b/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java
index c4a42dbbf..c4ead0ad1 100644
--- a/java/src/com/android/inputmethod/dictionarypack/Utils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java
@@ -14,16 +14,18 @@
* the License.
*/
-package com.android.inputmethod.dictionarypack;
+package com.android.inputmethod.latin.utils;
import android.util.Log;
+import com.android.inputmethod.latin.LatinImeLogger;
+
/**
- * A class for various utility methods, especially debugging.
+ * A class for logging and debugging utility methods.
*/
-public final class Utils {
- private final static String TAG = Utils.class.getSimpleName() + ":DEBUG --";
- private final static boolean DEBUG = DictionaryProvider.DEBUG;
+public final class DebugLogUtils {
+ private final static String TAG = DebugLogUtils.class.getSimpleName();
+ private final static boolean sDBG = LatinImeLogger.sDBG;
/**
* Calls .toString() on its non-null argument or returns "null"
@@ -39,13 +41,22 @@ public final class Utils {
* @return a readable, carriage-return-separated string for the current stack trace.
*/
public static String getStackTrace() {
+ return getStackTrace(Integer.MAX_VALUE - 1);
+ }
+
+ /**
+ * Get the string representation of the current stack trace, for debugging purposes.
+ * @param limit the maximum number of stack frames to be returned.
+ * @return a readable, carriage-return-separated string for the current stack trace.
+ */
+ public static String getStackTrace(final int limit) {
final StringBuilder sb = new StringBuilder();
try {
throw new RuntimeException();
- } catch (RuntimeException e) {
- StackTraceElement[] frames = e.getStackTrace();
+ } catch (final RuntimeException e) {
+ final StackTraceElement[] frames = e.getStackTrace();
// Start at 1 because the first frame is here and we don't care about it
- for (int j = 1; j < frames.length; ++j) {
+ for (int j = 1; j < frames.length && j < limit + 1; ++j) {
sb.append(frames[j].toString() + "\n");
}
}
@@ -75,7 +86,7 @@ public final class Utils {
* @param args the stuff to send to the log
*/
public static void l(final Object... args) {
- if (!DEBUG) return;
+ if (!sDBG) return;
final StringBuilder sb = new StringBuilder();
for (final Object o : args) {
sb.append(s(o).toString());
@@ -92,7 +103,7 @@ public final class Utils {
* @param args the stuff to send to the log
*/
public static void r(final Object... args) {
- if (!DEBUG) return;
+ if (!sDBG) return;
final StringBuilder sb = new StringBuilder("\u001B[31m");
for (final Object o : args) {
sb.append(s(o).toString());
diff --git a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index df7bad8d0..34eccd65b 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -14,15 +14,17 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
-import android.text.format.DateUtils;
import android.util.Log;
+import com.android.inputmethod.latin.AssetFileAddress;
+import com.android.inputmethod.latin.BinaryDictionaryGetter;
+import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
@@ -30,16 +32,16 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.Locale;
+import java.util.concurrent.TimeUnit;
/**
* This class encapsulates the logic for the Latin-IME side of dictionary information management.
*/
public class DictionaryInfoUtils {
private static final String TAG = DictionaryInfoUtils.class.getSimpleName();
- // This class must be located in the same package as LatinIME.java.
- private static final String RESOURCE_PACKAGE_NAME =
- DictionaryInfoUtils.class.getPackage().getName();
+ private static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
private static final String DEFAULT_MAIN_DICT = "main";
private static final String MAIN_DICT_PREFIX = "main_";
// 6 digits - unicode is limited to 21 bits
@@ -72,8 +74,8 @@ public class DictionaryInfoUtils {
values.put(LOCALE_COLUMN, mLocale.toString());
values.put(DESCRIPTION_COLUMN, mDescription);
values.put(LOCAL_FILENAME_COLUMN, mFileAddress.mFilename);
- values.put(DATE_COLUMN,
- new File(mFileAddress.mFilename).lastModified() / DateUtils.SECOND_IN_MILLIS);
+ values.put(DATE_COLUMN, TimeUnit.MILLISECONDS.toSeconds(
+ new File(mFileAddress.mFilename).lastModified()));
values.put(FILESIZE_COLUMN, mFileAddress.mLength);
values.put(VERSION_COLUMN, mVersion);
return values;
@@ -301,12 +303,14 @@ public class DictionaryInfoUtils {
private static void addOrUpdateDictInfo(final ArrayList<DictionaryInfo> dictList,
final DictionaryInfo newElement) {
- for (final DictionaryInfo info : dictList) {
- if (info.mLocale.equals(newElement.mLocale)) {
- if (newElement.mVersion <= info.mVersion) {
+ final Iterator<DictionaryInfo> iter = dictList.iterator();
+ while (iter.hasNext()) {
+ final DictionaryInfo thisDictInfo = iter.next();
+ if (thisDictInfo.mLocale.equals(newElement.mLocale)) {
+ if (newElement.mVersion <= thisDictInfo.mVersion) {
return;
}
- dictList.remove(info);
+ iter.remove();
}
}
dictList.add(newElement);
diff --git a/java/src/com/android/inputmethod/latin/FeedbackUtils.java b/java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java
index 0582763fe..ec7eaf4a0 100644
--- a/java/src/com/android/inputmethod/latin/FeedbackUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.content.Context;
import android.content.Intent;
diff --git a/java/src/com/android/inputmethod/latin/FileTransforms.java b/java/src/com/android/inputmethod/latin/utils/FileTransforms.java
index 692f3c7c1..9f4584ec9 100644
--- a/java/src/com/android/inputmethod/latin/FileTransforms.java
+++ b/java/src/com/android/inputmethod/latin/utils/FileTransforms.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import java.io.IOException;
import java.io.InputStream;
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java
index 46194f6e4..19cd34011 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.text.InputType;
import android.view.inputmethod.EditorInfo;
diff --git a/java/src/com/android/inputmethod/latin/IntentUtils.java b/java/src/com/android/inputmethod/latin/utils/IntentUtils.java
index d175af504..ea0168117 100644
--- a/java/src/com/android/inputmethod/latin/IntentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/IntentUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.content.Intent;
import android.text.TextUtils;
diff --git a/java/src/com/android/inputmethod/latin/JniUtils.java b/java/src/com/android/inputmethod/latin/utils/JniUtils.java
index 8aedee576..e7fdafaeb 100644
--- a/java/src/com/android/inputmethod/latin/JniUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/JniUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.util.Log;
diff --git a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
new file mode 100644
index 000000000..e958a7e71
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.text.TextUtils;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.WordComposer;
+
+public final class LatinImeLoggerUtils {
+ private LatinImeLoggerUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static void onNonSeparator(final char code, final int x, final int y) {
+ UserLogRingCharBuffer.getInstance().push(code, x, y);
+ LatinImeLogger.logOnInputChar();
+ }
+
+ public static void onSeparator(final int code, final int x, final int y) {
+ // Helper method to log a single code point separator
+ // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
+ onSeparator(new String(new int[]{code}, 0, 1), x, y);
+ }
+
+ public static void onSeparator(final String separator, final int x, final int y) {
+ final int length = separator.length();
+ for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
+ int codePoint = Character.codePointAt(separator, i);
+ // TODO: accept code points
+ UserLogRingCharBuffer.getInstance().push((char)codePoint, x, y);
+ }
+ LatinImeLogger.logOnInputSeparator();
+ }
+
+ public static void onAutoCorrection(final String typedWord, final String correctedWord,
+ final String separatorString, final WordComposer wordComposer) {
+ final boolean isBatchMode = wordComposer.isBatchMode();
+ if (!isBatchMode && TextUtils.isEmpty(typedWord)) {
+ return;
+ }
+ // TODO: this fails when the separator is more than 1 code point long, but
+ // the backend can't handle it yet. The only case when this happens is with
+ // smileys and other multi-character keys.
+ final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
+ : separatorString.codePointAt(0);
+ if (!isBatchMode) {
+ LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
+ } else {
+ if (!TextUtils.isEmpty(correctedWord)) {
+ // We must make sure that InputPointer contains only the relative timestamps,
+ // not actual timestamps.
+ LatinImeLogger.logOnAutoCorrectionForGeometric(
+ "", correctedWord, codePoint, wordComposer.getInputPointers());
+ }
+ }
+ }
+
+ public static void onAutoCorrectionCancellation() {
+ LatinImeLogger.logOnAutoCorrectionCancelled();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
index 5fde8158a..22045aa38 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.text.TextUtils;
import java.util.HashMap;
@@ -148,7 +146,7 @@ public final class LocaleUtils {
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("%02d", MATCH_LEVEL_MAX - matchLevel);
+ return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel);
}
/**
@@ -164,39 +162,6 @@ public final class LocaleUtils {
return LOCALE_MATCH <= level;
}
- static final Object sLockForRunInLocale = new Object();
-
- public abstract static class RunInLocale<T> {
- protected abstract T job(Resources res);
-
- /**
- * Execute {@link #job(Resources)} method in specified system locale exclusively.
- *
- * @param res the resources to use. Pass current resources.
- * @param newLocale the locale to change to
- * @return the value returned from {@link #job(Resources)}.
- */
- public T runInLocale(final Resources res, final Locale newLocale) {
- synchronized (sLockForRunInLocale) {
- final Configuration conf = res.getConfiguration();
- final Locale oldLocale = conf.locale;
- final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale));
- try {
- if (needsChange) {
- conf.locale = newLocale;
- res.updateConfiguration(conf, null);
- }
- return job(res);
- } finally {
- if (needsChange) {
- conf.locale = oldLocale;
- res.updateConfiguration(conf, null);
- }
- }
- }
- }
- }
-
private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap();
/**
diff --git a/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java b/java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java
index a98ecc7b6..9ad319da6 100644
--- a/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java
+++ b/java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
+
+import com.android.inputmethod.latin.R;
import android.content.Context;
diff --git a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java
index a8800007a..1fc7eccc6 100644
--- a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
+++ b/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.view.inputmethod.EditorInfo;
+import com.android.inputmethod.latin.RichInputConnection;
+
import java.util.Locale;
/**
diff --git a/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
index 8a704ab42..0f5cd80db 100644
--- a/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
+++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
@@ -14,9 +14,7 @@
* the License.
*/
-package com.android.inputmethod.latin;
-
-import com.android.inputmethod.latin.StringUtils;
+package com.android.inputmethod.latin.utils;
import java.util.Locale;
@@ -163,7 +161,10 @@ public class RecapitalizeStatus {
final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd);
if (!Character.isWhitespace(codePoint)) break;
}
- if (0 != nonWhitespaceStart || len != nonWhitespaceEnd) {
+ // If nonWhitespaceStart >= nonWhitespaceEnd, that means the selection contained only
+ // whitespace, so we leave it as is.
+ if ((0 != nonWhitespaceStart || len != nonWhitespaceEnd)
+ && nonWhitespaceStart < nonWhitespaceEnd) {
mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd;
mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart;
mStringAfter = mStringBefore =
diff --git a/java/src/com/android/inputmethod/latin/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
index 691f0602a..4c7739a7a 100644
--- a/java/src/com/android/inputmethod/latin/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
index a9fba5348..ffec57548 100644
--- a/java/src/com/android/inputmethod/latin/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -27,6 +27,7 @@ import com.android.inputmethod.annotations.UsedForTesting;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.regex.PatternSyntaxException;
public final class ResourceUtils {
private static final String TAG = ResourceUtils.class.getSimpleName();
@@ -83,22 +84,39 @@ public final class ResourceUtils {
return overrideValue;
}
- final String defaultValue = findDefaultConstant(overrideArray);
- // The defaultValue might be an empty string.
- if (defaultValue == null) {
- Log.w(TAG, "Couldn't find override value nor default value:"
- + " resource="+ res.getResourceEntryName(overrideResId)
- + " build=" + sBuildKeyValuesDebugString);
- } else {
- Log.i(TAG, "Found default value:"
- + " resource="+ res.getResourceEntryName(overrideResId)
- + " build=" + sBuildKeyValuesDebugString
- + " default=" + defaultValue);
+ String defaultValue = null;
+ try {
+ defaultValue = findDefaultConstant(overrideArray);
+ // The defaultValue might be an empty string.
+ if (defaultValue == null) {
+ Log.w(TAG, "Couldn't find override value nor default value:"
+ + " resource="+ res.getResourceEntryName(overrideResId)
+ + " build=" + sBuildKeyValuesDebugString);
+ } else {
+ Log.i(TAG, "Found default value:"
+ + " resource="+ res.getResourceEntryName(overrideResId)
+ + " build=" + sBuildKeyValuesDebugString
+ + " default=" + defaultValue);
+ }
+ } catch (final DeviceOverridePatternSyntaxError e) {
+ Log.w(TAG, "Syntax error, ignored", e);
}
sDeviceOverrideValueMap.put(key, defaultValue);
return defaultValue;
}
+ @SuppressWarnings("serial")
+ static class DeviceOverridePatternSyntaxError extends Exception {
+ public DeviceOverridePatternSyntaxError(final String message, final String expression) {
+ this(message, expression, null);
+ }
+
+ public DeviceOverridePatternSyntaxError(final String message, final String expression,
+ final Throwable throwable) {
+ super(message + ": " + expression, throwable);
+ }
+ }
+
/**
* Find the condition that fulfills specified key value pairs from an array of
* "condition,constant", and return the corresponding string constant. A condition is
@@ -123,10 +141,12 @@ public final class ResourceUtils {
if (conditionConstantArray == null || keyValuePairs == null) {
return null;
}
+ String foundValue = null;
for (final String conditionConstant : conditionConstantArray) {
final int posComma = conditionConstant.indexOf(',');
if (posComma < 0) {
- throw new RuntimeException("Array element has no comma: " + conditionConstant);
+ Log.w(TAG, "Array element has no comma: " + conditionConstant);
+ continue;
}
final String condition = conditionConstant.substring(0, posComma);
if (condition.isEmpty()) {
@@ -134,44 +154,59 @@ public final class ResourceUtils {
// {@link #findConstantForDefault(String[])}.
continue;
}
- if (fulfillsCondition(keyValuePairs, condition)) {
- return conditionConstant.substring(posComma + 1);
+ try {
+ if (fulfillsCondition(keyValuePairs, condition)) {
+ // Take first match
+ if (foundValue == null) {
+ foundValue = conditionConstant.substring(posComma + 1);
+ }
+ // And continue walking through all conditions.
+ }
+ } catch (final DeviceOverridePatternSyntaxError e) {
+ Log.w(TAG, "Syntax error, ignored", e);
}
}
- return null;
+ return foundValue;
}
private static boolean fulfillsCondition(final HashMap<String,String> keyValuePairs,
- final String condition) {
+ final String condition) throws DeviceOverridePatternSyntaxError {
final String[] patterns = condition.split(":");
// Check all patterns in a condition are true
+ boolean matchedAll = true;
for (final String pattern : patterns) {
final int posEqual = pattern.indexOf('=');
if (posEqual < 0) {
- throw new RuntimeException("Pattern has no '=': " + condition);
+ throw new DeviceOverridePatternSyntaxError("Pattern has no '='", condition);
}
final String key = pattern.substring(0, posEqual);
final String value = keyValuePairs.get(key);
if (value == null) {
- throw new RuntimeException("Found unknown key: " + condition);
+ throw new DeviceOverridePatternSyntaxError("Unknown key", condition);
}
final String patternRegexpValue = pattern.substring(posEqual + 1);
- if (!value.matches(patternRegexpValue)) {
- return false;
+ try {
+ if (!value.matches(patternRegexpValue)) {
+ matchedAll = false;
+ // And continue walking through all patterns.
+ }
+ } catch (final PatternSyntaxException e) {
+ throw new DeviceOverridePatternSyntaxError("Syntax error", condition, e);
}
}
- return true;
+ return matchedAll;
}
@UsedForTesting
- static String findDefaultConstant(final String[] conditionConstantArray) {
+ static String findDefaultConstant(final String[] conditionConstantArray)
+ throws DeviceOverridePatternSyntaxError {
if (conditionConstantArray == null) {
return null;
}
for (final String condition : conditionConstantArray) {
final int posComma = condition.indexOf(',');
if (posComma < 0) {
- throw new RuntimeException("Array element has no comma: " + condition);
+ throw new DeviceOverridePatternSyntaxError("Array element has no comma", condition);
}
if (posComma == 0) { // condition is empty.
return condition.substring(posComma + 1);
diff --git a/java/src/com/android/inputmethod/latin/utils/RunInLocale.java b/java/src/com/android/inputmethod/latin/utils/RunInLocale.java
new file mode 100644
index 000000000..2c9e3b191
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/RunInLocale.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.content.res.Configuration;
+import android.content.res.Resources;
+
+import java.util.Locale;
+
+public abstract class RunInLocale<T> {
+ private static final Object sLockForRunInLocale = new Object();
+
+ protected abstract T job(final Resources res);
+
+ /**
+ * Execute {@link #job(Resources)} method in specified system locale exclusively.
+ *
+ * @param res the resources to use.
+ * @param newLocale the locale to change to.
+ * @return the value returned from {@link #job(Resources)}.
+ */
+ public T runInLocale(final Resources res, final Locale newLocale) {
+ synchronized (sLockForRunInLocale) {
+ final Configuration conf = res.getConfiguration();
+ final Locale oldLocale = conf.locale;
+ final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale));
+ try {
+ if (needsChange) {
+ conf.locale = newLocale;
+ res.updateConfiguration(conf, null);
+ }
+ return job(res);
+ } finally {
+ if (needsChange) {
+ conf.locale = oldLocale;
+ res.updateConfiguration(conf, null);
+ }
+ }
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java
index e50af4d2d..44e5d17b4 100644
--- a/java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java
+++ b/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.os.Handler;
import android.os.Looper;
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index ab050d7a3..7406d855a 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.text.TextUtils;
+import com.android.inputmethod.latin.Constants;
+
import java.util.ArrayList;
import java.util.Locale;
@@ -35,33 +37,55 @@ public final class StringUtils {
return text.codePointCount(0, text.length());
}
- public static boolean containsInArray(final String key, final String[] array) {
+ public static boolean containsInArray(final String text, final String[] array) {
for (final String element : array) {
- if (key.equals(element)) return true;
+ if (text.equals(element)) return true;
}
return false;
}
- public static boolean containsInCsv(final String key, final String csv) {
- if (TextUtils.isEmpty(csv)) return false;
- return containsInArray(key, csv.split(","));
+ /**
+ * 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 appendToCsvIfNotExists(final String key, final String csv) {
- if (TextUtils.isEmpty(csv)) return key;
- if (containsInCsv(key, csv)) return csv;
- return csv + "," + key;
+ public static String appendToCommaSplittableTextIfNotExists(final String text,
+ final String extraValues) {
+ if (TextUtils.isEmpty(extraValues)) {
+ return text;
+ }
+ if (containsInCommaSplittableText(text, extraValues)) {
+ return extraValues;
+ }
+ return extraValues + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + text;
}
- public static String removeFromCsvIfExists(final String key, final String csv) {
- if (TextUtils.isEmpty(csv)) return "";
- final String[] elements = csv.split(",");
- if (!containsInArray(key, elements)) return csv;
+ public static String removeFromCommaSplittableTextIfExists(final String text,
+ final String extraValues) {
+ if (TextUtils.isEmpty(extraValues)) {
+ return "";
+ }
+ final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
+ if (!containsInArray(text, elements)) {
+ return extraValues;
+ }
final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
for (final String element : elements) {
- if (!key.equals(element)) result.add(element);
+ if (!text.equals(element)) {
+ result.add(element);
+ }
}
- return TextUtils.join(",", result);
+ return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result);
}
/**
@@ -131,44 +155,6 @@ public final class StringUtils {
return codePoints;
}
- public static String[] parseCsvString(final String text) {
- final int size = text.length();
- if (size == 0) {
- return null;
- }
- if (codePointCount(text) == 1) {
- return text.codePointAt(0) == Constants.CSV_SEPARATOR ? null : new String[] { text };
- }
-
- ArrayList<String> list = null;
- int start = 0;
- for (int pos = 0; pos < size; pos++) {
- final char c = text.charAt(pos);
- if (c == Constants.CSV_SEPARATOR) {
- // Skip empty entry.
- if (pos - start > 0) {
- if (list == null) {
- list = CollectionUtils.newArrayList();
- }
- list.add(text.substring(start, pos));
- }
- // Skip comma
- start = pos + 1;
- } else if (c == Constants.CSV_ESCAPE) {
- // Skip escape character and escaped character.
- pos++;
- }
- }
- final String remain = (size - start > 0) ? text.substring(start) : null;
- if (list == null) {
- return remain != null ? new String[] { remain } : null;
- }
- if (remain != null) {
- list.add(remain);
- }
- return list.toArray(new String[list.size()]);
- }
-
// 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
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 4d88ecc0c..16728092d 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+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;
@@ -25,13 +25,14 @@ import android.os.Build;
import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.R;
import java.util.HashMap;
import java.util.Locale;
-public final class SubtypeLocale {
- static final String TAG = SubtypeLocale.class.getSimpleName();
+public final class SubtypeLocaleUtils {
+ static final String TAG = SubtypeLocaleUtils.class.getSimpleName();
// This class must be located in the same package as LatinIME.java.
private static final String RESOURCE_PACKAGE_NAME =
DictionaryFactory.class.getPackage().getName();
@@ -69,7 +70,7 @@ public final class SubtypeLocale {
private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
CollectionUtils.newHashMap();
- private SubtypeLocale() {
+ private SubtypeLocaleUtils() {
// Intentional empty constructor for utility class.
}
@@ -217,9 +218,11 @@ public final class SubtypeLocale {
return getSubtypeDisplayNameInternal(subtype, displayLocale);
}
- public static String getSubtypeDisplayName(final InputMethodSubtype subtype) {
- final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(subtype.getLocale());
- return getSubtypeDisplayNameInternal(subtype, displayLocale);
+ public static String getSubtypeNameForLogging(final InputMethodSubtype subtype) {
+ if (subtype == null) {
+ return "<null subtype>";
+ }
+ return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype);
}
private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype,
@@ -238,7 +241,7 @@ public final class SubtypeLocale {
+ " nameResId=" + subtype.getNameResId()
+ " locale=" + subtype.getLocale()
+ " extra=" + subtype.getExtraValue()
- + "\n" + Utils.getStackTrace());
+ + "\n" + DebugLogUtils.getStackTrace());
return "";
}
}
@@ -284,4 +287,46 @@ public final class SubtypeLocale {
}
return keyboardLayoutSet;
}
+
+ // InputMethodSubtype's display name for spacebar text in its locale.
+ // isAdditionalSubtype (T=true, F=false)
+ // locale layout | Short Middle Full
+ // ------ ------- - ---- --------- ----------------------
+ // en_US qwerty F En English English (US) exception
+ // en_GB qwerty F En English English (UK) exception
+ // es_US spanish F Es Español Español (EE.UU.) exception
+ // fr azerty F Fr Français Français
+ // fr_CA qwerty F Fr Français Français (Canada)
+ // de qwertz F De Deutsch Deutsch
+ // zz qwerty F QWERTY QWERTY
+ // fr qwertz T Fr Français Français
+ // de qwerty T De Deutsch Deutsch
+ // en_US azerty T En English English (US)
+ // zz azerty T AZERTY AZERTY
+
+ // Get InputMethodSubtype's full display name in its locale.
+ public static String getFullDisplayName(final InputMethodSubtype subtype) {
+ if (isNoLanguage(subtype)) {
+ return getKeyboardLayoutSetDisplayName(subtype);
+ }
+ return getSubtypeLocaleDisplayName(subtype.getLocale());
+ }
+
+ // Get InputMethodSubtype's middle display name in its locale.
+ public static String getMiddleDisplayName(final InputMethodSubtype subtype) {
+ if (isNoLanguage(subtype)) {
+ return getKeyboardLayoutSetDisplayName(subtype);
+ }
+ final Locale locale = getSubtypeLocale(subtype);
+ return getSubtypeLocaleDisplayName(locale.getLanguage());
+ }
+
+ // Get InputMethodSubtype's short display name in its locale.
+ public static String getShortDisplayName(final InputMethodSubtype subtype) {
+ if (isNoLanguage(subtype)) {
+ return "";
+ }
+ final Locale locale = getSubtypeLocale(subtype);
+ return StringUtils.capitalizeFirstCodePoint(locale.getLanguage(), locale);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
index 947b0c586..afbe2ecad 100644
--- a/java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java
+++ b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.content.Context;
import android.content.pm.PackageInfo;
diff --git a/java/src/com/android/inputmethod/latin/utils/TextRange.java b/java/src/com/android/inputmethod/latin/utils/TextRange.java
new file mode 100644
index 000000000..5793e4170
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/TextRange.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.text.Spanned;
+import android.text.style.SuggestionSpan;
+
+import java.util.Arrays;
+
+/**
+ * Represents a range of text, relative to the current cursor position.
+ */
+public final class TextRange {
+ private final CharSequence mTextAtCursor;
+ private final int mWordAtCursorStartIndex;
+ private final int mWordAtCursorEndIndex;
+ private final int mCursorIndex;
+
+ public final CharSequence mWord;
+
+ public int getNumberOfCharsInWordBeforeCursor() {
+ return mCursorIndex - mWordAtCursorStartIndex;
+ }
+
+ public int getNumberOfCharsInWordAfterCursor() {
+ return mWordAtCursorEndIndex - mCursorIndex;
+ }
+
+ /**
+ * Gets the suggestion spans that are put squarely on the word, with the exact start
+ * and end of the span matching the boundaries of the word.
+ * @return the list of spans.
+ */
+ public SuggestionSpan[] getSuggestionSpansAtWord() {
+ if (!(mTextAtCursor instanceof Spanned && mWord instanceof Spanned)) {
+ return new SuggestionSpan[0];
+ }
+ final Spanned text = (Spanned)mTextAtCursor;
+ // Note: it's fine to pass indices negative or greater than the length of the string
+ // to the #getSpans() method. The reason we need to get from -1 to +1 is that, the
+ // spans were cut at the cursor position, and #getSpans(start, end) does not return
+ // spans that end at `start' or begin at `end'. Consider the following case:
+ // this| is (The | symbolizes the cursor position
+ // ---- ---
+ // In this case, the cursor is in position 4, so the 0~7 span has been split into
+ // a 0~4 part and a 4~7 part.
+ // If we called #getSpans(0, 4) in this case, we would only get the part from 0 to 4
+ // of the span, and not the part from 4 to 7, so we would not realize the span actually
+ // extends from 0 to 7. But if we call #getSpans(-1, 5) we'll get both the 0~4 and
+ // the 4~7 spans and we can merge them accordingly.
+ // Any span starting more than 1 char away from the word boundaries in any direction
+ // does not touch the word, so we don't need to consider it. That's why requesting
+ // -1 ~ +1 is enough.
+ // Of course this is only relevant if the cursor is at one end of the word. If it's
+ // in the middle, the -1 and +1 are not necessary, but they are harmless.
+ final SuggestionSpan[] spans = text.getSpans(mWordAtCursorStartIndex - 1,
+ mWordAtCursorEndIndex + 1, SuggestionSpan.class);
+ int readIndex = 0;
+ int writeIndex = 0;
+ for (; readIndex < spans.length; ++readIndex) {
+ final SuggestionSpan span = spans[readIndex];
+ // The span may be null, as we null them when we find duplicates. Cf a few lines
+ // down.
+ if (null == span) continue;
+ // Tentative span start and end. This may be modified later if we realize the
+ // same span is also applied to other parts of the string.
+ int spanStart = text.getSpanStart(span);
+ int spanEnd = text.getSpanEnd(span);
+ for (int i = readIndex + 1; i < spans.length; ++i) {
+ if (span.equals(spans[i])) {
+ // We found the same span somewhere else. Read the new extent of this
+ // span, and adjust our values accordingly.
+ spanStart = Math.min(spanStart, text.getSpanStart(spans[i]));
+ spanEnd = Math.max(spanEnd, text.getSpanEnd(spans[i]));
+ // ...and mark the span as processed.
+ spans[i] = null;
+ }
+ }
+ if (spanStart == mWordAtCursorStartIndex && spanEnd == mWordAtCursorEndIndex) {
+ // If the span does not start and stop here, we ignore it. It probably extends
+ // past the start or end of the word, as happens in missing space correction
+ // or EasyEditSpans put by voice input.
+ spans[writeIndex++] = spans[readIndex];
+ }
+ }
+ return writeIndex == readIndex ? spans : Arrays.copyOfRange(spans, 0, writeIndex);
+ }
+
+ public TextRange(final CharSequence textAtCursor, final int wordAtCursorStartIndex,
+ final int wordAtCursorEndIndex, final int cursorIndex) {
+ if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex
+ || cursorIndex > wordAtCursorEndIndex
+ || wordAtCursorEndIndex > textAtCursor.length()) {
+ throw new IndexOutOfBoundsException();
+ }
+ mTextAtCursor = textAtCursor;
+ mWordAtCursorStartIndex = wordAtCursorStartIndex;
+ mWordAtCursorEndIndex = wordAtCursorEndIndex;
+ mCursorIndex = cursorIndex;
+ mWord = mTextAtCursor.subSequence(mWordAtCursorStartIndex, mWordAtCursorEndIndex);
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
index 6a54e119c..544e4d201 100644
--- a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.latin.utils;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.SparseArray;
-import com.android.inputmethod.latin.CollectionUtils;
-
public final class TypefaceUtils {
private TypefaceUtils() {
// This utility class is not publicly instantiable.
diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
new file mode 100644
index 000000000..06826dac0
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.channels.FileChannel;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public final class UsabilityStudyLogUtils {
+ // TODO: remove code duplication with ResearchLog class
+ private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
+ private static final String FILENAME = "log.txt";
+ private final Handler mLoggingHandler;
+ private File mFile;
+ private File mDirectory;
+ private InputMethodService mIms;
+ private PrintWriter mWriter;
+ private final Date mDate;
+ private final SimpleDateFormat mDateFormat;
+
+ private UsabilityStudyLogUtils() {
+ mDate = new Date();
+ mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US);
+
+ HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ handlerThread.start();
+ mLoggingHandler = new Handler(handlerThread.getLooper());
+ }
+
+ // Initialization-on-demand holder
+ private static final class OnDemandInitializationHolder {
+ public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
+ }
+
+ public static UsabilityStudyLogUtils getInstance() {
+ return OnDemandInitializationHolder.sInstance;
+ }
+
+ public void init(final InputMethodService ims) {
+ mIms = ims;
+ mDirectory = ims.getFilesDir();
+ }
+
+ private void createLogFileIfNotExist() {
+ if ((mFile == null || !mFile.exists())
+ && (mDirectory != null && mDirectory.exists())) {
+ try {
+ mWriter = getPrintWriter(mDirectory, FILENAME, false);
+ } catch (final IOException e) {
+ Log.e(USABILITY_TAG, "Can't create log file.");
+ }
+ }
+ }
+
+ public static void writeBackSpace(final int x, final int y) {
+ UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
+ }
+
+ public static void writeChar(final char c, final int x, final int y) {
+ String inputChar = String.valueOf(c);
+ switch (c) {
+ case '\n':
+ inputChar = "<enter>";
+ break;
+ case '\t':
+ inputChar = "<tab>";
+ break;
+ case ' ':
+ inputChar = "<space>";
+ break;
+ }
+ UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
+ LatinImeLogger.onPrintAllUsabilityStudyLogs();
+ }
+
+ public static void writeMotionEvent(final MotionEvent me) {
+ final int action = me.getActionMasked();
+ final long eventTime = me.getEventTime();
+ final int pointerCount = me.getPointerCount();
+ for (int index = 0; index < pointerCount; index++) {
+ final int id = me.getPointerId(index);
+ final int x = (int)me.getX(index);
+ final int y = (int)me.getY(index);
+ final float size = me.getSize(index);
+ final float pressure = me.getPressure(index);
+
+ final String eventTag;
+ switch (action) {
+ case MotionEvent.ACTION_UP:
+ eventTag = "[Up]";
+ break;
+ case MotionEvent.ACTION_DOWN:
+ eventTag = "[Down]";
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ eventTag = "[PointerUp]";
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ eventTag = "[PointerDown]";
+ break;
+ case MotionEvent.ACTION_MOVE:
+ eventTag = "[Move]";
+ break;
+ default:
+ eventTag = "[Action" + action + "]";
+ break;
+ }
+ getInstance().write(eventTag + eventTime + "," + id + "," + x + "," + y + "," + size
+ + "," + pressure);
+ }
+ }
+
+ public void write(final String log) {
+ mLoggingHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ createLogFileIfNotExist();
+ final long currentTime = System.currentTimeMillis();
+ mDate.setTime(currentTime);
+
+ final String printString = String.format(Locale.US, "%s\t%d\t%s\n",
+ mDateFormat.format(mDate), currentTime, log);
+ if (LatinImeLogger.sDBG) {
+ Log.d(USABILITY_TAG, "Write: " + log);
+ }
+ mWriter.print(printString);
+ }
+ });
+ }
+
+ private synchronized String getBufferedLogs() {
+ mWriter.flush();
+ final StringBuilder sb = new StringBuilder();
+ final BufferedReader br = getBufferedReader();
+ String line;
+ try {
+ while ((line = br.readLine()) != null) {
+ sb.append('\n');
+ sb.append(line);
+ }
+ } catch (final IOException e) {
+ Log.e(USABILITY_TAG, "Can't read log file.");
+ } finally {
+ if (LatinImeLogger.sDBG) {
+ Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
+ }
+ try {
+ br.close();
+ } catch (final IOException e) {
+ // ignore.
+ }
+ }
+ return sb.toString();
+ }
+
+ public void emailResearcherLogsAll() {
+ mLoggingHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ final Date date = new Date();
+ date.setTime(System.currentTimeMillis());
+ final String currentDateTimeString =
+ new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date);
+ if (mFile == null) {
+ Log.w(USABILITY_TAG, "No internal log file found.");
+ return;
+ }
+ if (mIms.checkCallingOrSelfPermission(
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
+ return;
+ }
+ mWriter.flush();
+ final String destPath = Environment.getExternalStorageDirectory()
+ + "/research-" + currentDateTimeString + ".log";
+ final File destFile = new File(destPath);
+ try {
+ final FileInputStream srcStream = new FileInputStream(mFile);
+ final FileOutputStream destStream = new FileOutputStream(destFile);
+ final FileChannel src = srcStream.getChannel();
+ final FileChannel dest = destStream.getChannel();
+ src.transferTo(0, src.size(), dest);
+ src.close();
+ srcStream.close();
+ dest.close();
+ destStream.close();
+ } catch (final FileNotFoundException e1) {
+ Log.w(USABILITY_TAG, e1);
+ return;
+ } catch (final IOException e2) {
+ Log.w(USABILITY_TAG, e2);
+ return;
+ }
+ if (!destFile.exists()) {
+ Log.w(USABILITY_TAG, "Dest file doesn't exist.");
+ return;
+ }
+ final Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (LatinImeLogger.sDBG) {
+ Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
+ }
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
+ intent.putExtra(Intent.EXTRA_SUBJECT,
+ "[Research Logs] " + currentDateTimeString);
+ mIms.startActivity(intent);
+ }
+ });
+ }
+
+ public void printAll() {
+ mLoggingHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
+ }
+ });
+ }
+
+ public void clearAll() {
+ mLoggingHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mFile != null && mFile.exists()) {
+ if (LatinImeLogger.sDBG) {
+ Log.d(USABILITY_TAG, "Delete log file.");
+ }
+ mFile.delete();
+ mWriter.close();
+ }
+ }
+ });
+ }
+
+ private BufferedReader getBufferedReader() {
+ createLogFileIfNotExist();
+ try {
+ return new BufferedReader(new FileReader(mFile));
+ } catch (final FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ private PrintWriter getPrintWriter(final File dir, final String filename,
+ final boolean renew) throws IOException {
+ mFile = new File(dir, filename);
+ if (mFile.exists()) {
+ if (renew) {
+ mFile.delete();
+ }
+ }
+ return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
index 10931555e..d02f7187e 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.util.Log;
@@ -27,6 +27,7 @@ import com.android.inputmethod.latin.makedict.FusionDictionary;
import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
import com.android.inputmethod.latin.makedict.PendingAttribute;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
import java.io.IOException;
import java.io.OutputStream;
@@ -53,64 +54,6 @@ public final class UserHistoryDictIOUtils {
public int getFrequency(final String word1, final String word2);
}
- public static final class ByteArrayWrapper implements FusionDictionaryBufferInterface {
- private byte[] mBuffer;
- private int mPosition;
-
- public ByteArrayWrapper(final byte[] buffer) {
- mBuffer = buffer;
- mPosition = 0;
- }
-
- @Override
- public int readUnsignedByte() {
- return mBuffer[mPosition++] & 0xFF;
- }
-
- @Override
- public int readUnsignedShort() {
- final int retval = readUnsignedByte();
- return (retval << 8) + readUnsignedByte();
- }
-
- @Override
- public int readUnsignedInt24() {
- final int retval = readUnsignedShort();
- return (retval << 8) + readUnsignedByte();
- }
-
- @Override
- public int readInt() {
- final int retval = readUnsignedShort();
- return (retval << 16) + readUnsignedShort();
- }
-
- @Override
- public int position() {
- return mPosition;
- }
-
- @Override
- public void position(int position) {
- mPosition = position;
- }
-
- @Override
- public void put(final byte b) {
- mBuffer[mPosition++] = b;
- }
-
- @Override
- public int limit() {
- return mBuffer.length - 1;
- }
-
- @Override
- public int capacity() {
- return mBuffer.length;
- }
- }
-
/**
* Writes dictionary to file.
*/
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
index 9053d709b..713a45bda 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
-import android.text.format.DateUtils;
import android.util.Log;
+import java.util.concurrent.TimeUnit;
+
public final class UserHistoryForgettingCurveUtils {
private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -27,8 +28,8 @@ public final class UserHistoryForgettingCurveUtils {
private static final int FC_LEVEL_MAX = 3;
/* package */ static final int ELAPSED_TIME_MAX = 15;
private static final int ELAPSED_TIME_INTERVAL_HOURS = 6;
- private static final long ELAPSED_TIME_INTERVAL_MILLIS = ELAPSED_TIME_INTERVAL_HOURS
- * DateUtils.HOUR_IN_MILLIS;
+ private static final long ELAPSED_TIME_INTERVAL_MILLIS =
+ TimeUnit.HOURS.toMillis(ELAPSED_TIME_INTERVAL_HOURS);
private static final int HALF_LIFE_HOURS = 48;
private static final int MAX_PUSH_ELAPSED = (FC_LEVEL_MAX + 1) * (ELAPSED_TIME_MAX + 1);
diff --git a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
new file mode 100644
index 000000000..161386e2e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.inputmethodservice.InputMethodService;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.settings.Settings;
+
+public final class UserLogRingCharBuffer {
+ public /* for test */ static final int BUFSIZE = 20;
+ public /* for test */ int mLength = 0;
+
+ private static UserLogRingCharBuffer sUserLogRingCharBuffer = new UserLogRingCharBuffer();
+ private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
+ private static final int INVALID_COORDINATE = -2;
+ private boolean mEnabled = false;
+ private int mEnd = 0;
+ private char[] mCharBuf = new char[BUFSIZE];
+ private int[] mXBuf = new int[BUFSIZE];
+ private int[] mYBuf = new int[BUFSIZE];
+
+ private UserLogRingCharBuffer() {
+ // Intentional empty constructor for singleton.
+ }
+
+ @UsedForTesting
+ public static UserLogRingCharBuffer getInstance() {
+ return sUserLogRingCharBuffer;
+ }
+
+ public static UserLogRingCharBuffer init(final InputMethodService context,
+ final boolean enabled, final boolean usabilityStudy) {
+ if (!(enabled || usabilityStudy)) {
+ return null;
+ }
+ sUserLogRingCharBuffer.mEnabled = true;
+ UsabilityStudyLogUtils.getInstance().init(context);
+ return sUserLogRingCharBuffer;
+ }
+
+ private static int normalize(final int in) {
+ int ret = in % BUFSIZE;
+ return ret < 0 ? ret + BUFSIZE : ret;
+ }
+
+ // TODO: accept code points
+ @UsedForTesting
+ public void push(final char c, final int x, final int y) {
+ if (!mEnabled) {
+ return;
+ }
+ mCharBuf[mEnd] = c;
+ mXBuf[mEnd] = x;
+ mYBuf[mEnd] = y;
+ mEnd = normalize(mEnd + 1);
+ if (mLength < BUFSIZE) {
+ ++mLength;
+ }
+ }
+
+ public char pop() {
+ if (mLength < 1) {
+ return PLACEHOLDER_DELIMITER_CHAR;
+ }
+ mEnd = normalize(mEnd - 1);
+ --mLength;
+ return mCharBuf[mEnd];
+ }
+
+ public char getBackwardNthChar(final int n) {
+ if (mLength <= n || n < 0) {
+ return PLACEHOLDER_DELIMITER_CHAR;
+ }
+ return mCharBuf[normalize(mEnd - n - 1)];
+ }
+
+ public int getPreviousX(final char c, final int back) {
+ final int index = normalize(mEnd - 2 - back);
+ if (mLength <= back
+ || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
+ return INVALID_COORDINATE;
+ }
+ return mXBuf[index];
+ }
+
+ public int getPreviousY(final char c, final int back) {
+ int index = normalize(mEnd - 2 - back);
+ if (mLength <= back
+ || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
+ return INVALID_COORDINATE;
+ }
+ return mYBuf[index];
+ }
+
+ public String getLastWord(final int ignoreCharCount) {
+ final StringBuilder sb = new StringBuilder();
+ int i = ignoreCharCount;
+ for (; i < mLength; ++i) {
+ final char c = mCharBuf[normalize(mEnd - 1 - i)];
+ if (!Settings.getInstance().isWordSeparator(c)) {
+ break;
+ }
+ }
+ for (; i < mLength; ++i) {
+ char c = mCharBuf[normalize(mEnd - 1 - i)];
+ if (!Settings.getInstance().isWordSeparator(c)) {
+ sb.append(c);
+ } else {
+ break;
+ }
+ }
+ return sb.reverse().toString();
+ }
+
+ public void reset() {
+ mLength = 0;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
index dc12fa468..f9d853493 100644
--- a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.latin.utils;
import android.view.View;
import android.view.ViewGroup;
@@ -27,7 +27,8 @@ public final class ViewLayoutUtils {
// This utility class is not publicly instantiable.
}
- public static MarginLayoutParams newLayoutParam(ViewGroup placer, int width, int height) {
+ public static MarginLayoutParams newLayoutParam(final ViewGroup placer, final int width,
+ final int height) {
if (placer instanceof FrameLayout) {
return new FrameLayout.LayoutParams(width, height);
} else if (placer instanceof RelativeLayout) {
@@ -40,7 +41,8 @@ public final class ViewLayoutUtils {
}
}
- public static void placeViewAt(View view, int x, int y, int w, int h) {
+ public static void placeViewAt(final View view, final int x, final int y, final int w,
+ final int h) {
final ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp instanceof MarginLayoutParams) {
final MarginLayoutParams marginLayoutParams = (MarginLayoutParams)lp;
diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java
index 48e5ed30a..bdad16652 100644
--- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
import android.content.res.TypedArray;
diff --git a/java/src/com/android/inputmethod/research/FeedbackActivity.java b/java/src/com/android/inputmethod/research/FeedbackActivity.java
index b985fda21..520b88d2f 100644
--- a/java/src/com/android/inputmethod/research/FeedbackActivity.java
+++ b/java/src/com/android/inputmethod/research/FeedbackActivity.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.research;
import android.app.Activity;
import android.os.Bundle;
-import android.widget.CheckBox;
import com.android.inputmethod.latin.R;
diff --git a/java/src/com/android/inputmethod/research/FeedbackFragment.java b/java/src/com/android/inputmethod/research/FeedbackFragment.java
index a0738292e..75fbbf1ba 100644
--- a/java/src/com/android/inputmethod/research/FeedbackFragment.java
+++ b/java/src/com/android/inputmethod/research/FeedbackFragment.java
@@ -16,7 +16,6 @@
package com.android.inputmethod.research;
-import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.text.Editable;
diff --git a/java/src/com/android/inputmethod/research/FeedbackLog.java b/java/src/com/android/inputmethod/research/FeedbackLog.java
new file mode 100644
index 000000000..5af194c32
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/FeedbackLog.java
@@ -0,0 +1,32 @@
+/*
+ * 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.research;
+
+import android.content.Context;
+
+import java.io.File;
+
+public class FeedbackLog extends ResearchLog {
+ public FeedbackLog(final File outputFile, final Context context) {
+ super(outputFile, context);
+ }
+
+ @Override
+ public boolean isFeedbackLog() {
+ return true;
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java
index 24cd8d935..63d524df7 100644
--- a/java/src/com/android/inputmethod/research/JsonUtils.java
+++ b/java/src/com/android/inputmethod/research/JsonUtils.java
@@ -94,12 +94,17 @@ import java.util.Map;
.value(words.mIsPunctuationSuggestions);
jsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
jsonWriter.name("isPrediction").value(words.mIsPrediction);
- jsonWriter.name("words");
+ jsonWriter.name("suggestedWords");
jsonWriter.beginArray();
final int size = words.size();
for (int j = 0; j < size; j++) {
final SuggestedWordInfo wordInfo = words.getInfo(j);
- jsonWriter.value(wordInfo.toString());
+ jsonWriter.beginObject();
+ jsonWriter.name("word").value(wordInfo.toString());
+ jsonWriter.name("score").value(wordInfo.mScore);
+ jsonWriter.name("kind").value(wordInfo.mKind);
+ jsonWriter.name("sourceDict").value(wordInfo.mSourceDict);
+ jsonWriter.endObject();
}
jsonWriter.endArray();
jsonWriter.endObject();
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index cf1388f46..3366df12a 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -67,7 +67,7 @@ public class LogUnit {
private String[] mWordArray = EMPTY_STRING_ARRAY;
private boolean mMayContainDigit;
private boolean mIsPartOfMegaword;
- private boolean mContainsCorrection;
+ private boolean mContainsUserDeletions;
// mCorrectionType indicates whether the word was corrected at all, and if so, the nature of the
// correction.
@@ -146,7 +146,8 @@ public class LogUnit {
if (size != 0) {
// Note that jsonWriter is only set to a non-null value if the logUnit start text is
// output and at least one logStatement is output.
- JsonWriter jsonWriter = null;
+ JsonWriter jsonWriter = researchLog.getInitializedJsonWriterLocked();
+ outputLogUnitStart(jsonWriter, canIncludePrivateData);
for (int i = 0; i < size; i++) {
final LogStatement logStatement = mLogStatementList.get(i);
if (!canIncludePrivateData && logStatement.isPotentiallyPrivate()) {
@@ -155,42 +156,35 @@ public class LogUnit {
if (mIsPartOfMegaword && logStatement.isPotentiallyRevealing()) {
continue;
}
- // Only retrieve the jsonWriter if we need to. If we don't get this far, then
- // researchLog.getInitializedJsonWriterLocked() will not ever be called, and the
- // file will not have been opened for writing.
- if (jsonWriter == null) {
- jsonWriter = researchLog.getInitializedJsonWriterLocked();
- outputLogUnitStart(jsonWriter, canIncludePrivateData);
- }
logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i));
}
- if (jsonWriter != null) {
- // We must have called logUnitStart earlier, so emit a logUnitStop.
- outputLogUnitStop(jsonWriter);
- }
+ outputLogUnitStop(jsonWriter);
}
}
private static final String WORD_KEY = "_wo";
+ private static final String NUM_WORDS_KEY = "_nw";
private static final String CORRECTION_TYPE_KEY = "_corType";
private static final String LOG_UNIT_BEGIN_KEY = "logUnitStart";
private static final String LOG_UNIT_END_KEY = "logUnitEnd";
final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA =
new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */,
- false /* isPotentiallyRevealing */, WORD_KEY, CORRECTION_TYPE_KEY);
+ false /* isPotentiallyRevealing */, WORD_KEY, CORRECTION_TYPE_KEY,
+ NUM_WORDS_KEY);
final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA =
new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */,
- false /* isPotentiallyRevealing */);
+ false /* isPotentiallyRevealing */, NUM_WORDS_KEY);
private void outputLogUnitStart(final JsonWriter jsonWriter,
final boolean canIncludePrivateData) {
final LogStatement logStatement;
if (canIncludePrivateData) {
LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA.outputToLocked(jsonWriter,
- SystemClock.uptimeMillis(), getWordsAsString(), getCorrectionType());
+ SystemClock.uptimeMillis(), getWordsAsString(), getCorrectionType(),
+ getNumWords());
} else {
LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA.outputToLocked(jsonWriter,
- SystemClock.uptimeMillis());
+ SystemClock.uptimeMillis(), getNumWords());
}
}
@@ -277,13 +271,13 @@ public class LogUnit {
}
// TODO: Refactor to eliminate getter/setters
- public void setContainsCorrection() {
- mContainsCorrection = true;
+ public void setContainsUserDeletions() {
+ mContainsUserDeletions = true;
}
// TODO: Refactor to eliminate getter/setters
- public boolean containsCorrection() {
- return mContainsCorrection;
+ public boolean containsUserDeletions() {
+ return mContainsUserDeletions;
}
// TODO: Refactor to eliminate getter/setters
@@ -323,7 +317,7 @@ public class LogUnit {
true /* isPartOfMegaword */);
newLogUnit.mWords = null;
newLogUnit.mMayContainDigit = mMayContainDigit;
- newLogUnit.mContainsCorrection = mContainsCorrection;
+ newLogUnit.mContainsUserDeletions = mContainsUserDeletions;
// Purge the logStatements and associated data from this LogUnit.
laterLogStatements.clear();
@@ -346,7 +340,7 @@ public class LogUnit {
setWords(logUnit.mWords);
}
mMayContainDigit = mMayContainDigit || logUnit.mMayContainDigit;
- mContainsCorrection = mContainsCorrection || logUnit.mContainsCorrection;
+ mContainsUserDeletions = mContainsUserDeletions || logUnit.mContainsUserDeletions;
mIsPartOfMegaword = false;
}
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 9aa349906..6df7c1708 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -63,6 +63,15 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
private static final boolean DEBUG = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
+ // Keep consistent with switch statement in Statistics.recordPublishabilityResultCode()
+ public static final int PUBLISHABILITY_PUBLISHABLE = 0;
+ public static final int PUBLISHABILITY_UNPUBLISHABLE_STOPPING = 1;
+ public static final int PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT = 2;
+ public static final int PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY = 3;
+ public static final int PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE = 4;
+ public static final int PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT = 5;
+ public static final int PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY = 6;
+
// The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams.
public static final int N_GRAM_SIZE = 2;
@@ -105,21 +114,24 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
}
/**
- * Determines whether uploading the n words at the front the MainLogBuffer will not violate
- * user privacy.
+ * Determines whether the string determined by a series of LogUnits will not violate user
+ * privacy if published.
+ *
+ * @param logUnits a LogUnit list to check for publishability
+ * @param nGramSize the smallest n-gram acceptable to be published. if
+ * {@link ResearchLogger#IS_LOGGING_EVERYTHING} is true, then publish if there are more than
+ * {@code minNGramSize} words in the logUnits, otherwise wait. if {@link
+ * ResearchLogger#IS_LOGGING_EVERYTHING} is false, then ensure that there are exactly nGramSize
+ * words in the LogUnits.
*
- * The size of the MainLogBuffer is just enough to hold one n-gram, its corrections, and any
- * non-character data that is typed between words. The decision about privacy is made based on
- * the buffer's entire content. If it is decided that the privacy risks are too great to upload
- * the contents of this buffer, a censored version of the LogItems may still be uploaded. E.g.,
- * the screen orientation and other characteristics about the device can be uploaded without
- * revealing much about the user.
+ * @return one of the {@code PUBLISHABILITY_*} result codes defined in this class.
*/
- private boolean isSafeNGram(final ArrayList<LogUnit> logUnits, final int minNGramSize) {
+ private int getPublishabilityResultCode(final ArrayList<LogUnit> logUnits,
+ final int nGramSize) {
// Bypass privacy checks when debugging.
if (ResearchLogger.IS_LOGGING_EVERYTHING) {
if (mIsStopping) {
- return true;
+ return PUBLISHABILITY_UNPUBLISHABLE_STOPPING;
}
// Only check that it is the right length. If not, wait for later words to make
// complete n-grams.
@@ -129,13 +141,17 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
final LogUnit logUnit = logUnits.get(i);
numWordsInLogUnitList += logUnit.getNumWords();
}
- return numWordsInLogUnitList >= minNGramSize;
+ if (numWordsInLogUnitList >= nGramSize) {
+ return PUBLISHABILITY_PUBLISHABLE;
+ } else {
+ return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT;
+ }
}
// Check that we are not sampling too frequently. Having sampled recently might disclose
// too much of the user's intended meaning.
if (mNumWordsUntilSafeToSample > 0) {
- return false;
+ return PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY;
}
// Reload the dictionary in case it has changed (e.g., because the user has changed
// languages).
@@ -144,7 +160,7 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
// Main dictionary is unavailable. Since we cannot check it, we cannot tell if a
// word is out-of-vocabulary or not. Therefore, we must judge the entire buffer
// contents to potentially pose a privacy risk.
- return false;
+ return PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE;
}
// Check each word in the buffer. If any word poses a privacy threat, we cannot upload
@@ -155,7 +171,7 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
if (!logUnit.hasOneOrMoreWords()) {
// Digits outside words are a privacy threat.
if (logUnit.mayContainDigit()) {
- return false;
+ return PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT;
}
} else {
numWordsInLogUnitList += logUnit.getNumWords();
@@ -168,14 +184,18 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
+ ResearchLogger.hasLetters(word)
+ ", isValid: " + (dictionary.isValidWord(word)));
}
- return false;
+ return PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY;
}
}
}
}
// Finally, only return true if the ngram is the right size.
- return numWordsInLogUnitList == minNGramSize;
+ if (numWordsInLogUnitList == nGramSize) {
+ return PUBLISHABILITY_PUBLISHABLE;
+ } else {
+ return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT;
+ }
}
public void shiftAndPublishAll() throws IOException {
@@ -196,11 +216,29 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
}
}
+ /**
+ * If there is a safe n-gram at the front of this log buffer, publish it with all details, and
+ * remove the LogUnits that constitute it.
+ *
+ * An n-gram might not be "safe" if it violates privacy controls. E.g., it might contain
+ * numbers, an out-of-vocabulary word, or another n-gram may have been published recently. If
+ * there is no safe n-gram, then the LogUnits up through the first word-containing LogUnit are
+ * published, but without disclosing any privacy-related details, such as the word the LogUnit
+ * generated, motion data, etc.
+ *
+ * Note that a LogUnit can hold more than one word if the user types without explicit spaces.
+ * In this case, the words may be grouped together in such a way that pulling an n-gram off the
+ * front would require splitting a LogUnit. Splitting a LogUnit is not possible, so this case
+ * is treated just as the unsafe n-gram case. This may cause n-grams to be sampled at slightly
+ * less than the target frequency.
+ */
protected final void publishLogUnitsAtFrontOfBuffer() throws IOException {
// TODO: Refactor this method to require fewer passes through the LogUnits. Should really
// require only one pass.
ArrayList<LogUnit> logUnits = peekAtFirstNWords(N_GRAM_SIZE);
- if (isSafeNGram(logUnits, N_GRAM_SIZE)) {
+ final int publishabilityResultCode = getPublishabilityResultCode(logUnits, N_GRAM_SIZE);
+ ResearchLogger.recordPublishabilityResultCode(publishabilityResultCode);
+ if (publishabilityResultCode == MainLogBuffer.PUBLISHABILITY_PUBLISHABLE) {
// Good n-gram at the front of the buffer. Publish it, disclosing details.
publish(logUnits, true /* canIncludePrivateData */);
shiftOutWords(N_GRAM_SIZE);
diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java
index fbfd9b531..3388645b7 100644
--- a/java/src/com/android/inputmethod/research/MotionEventReader.java
+++ b/java/src/com/android/inputmethod/research/MotionEventReader.java
@@ -315,16 +315,6 @@ public class MotionEventReader {
return pointerCoords;
}
- /**
- * Tests that {@code x} is uninitialized.
- *
- * Assumes that {@code x} will never be given a valid value less than 0, and that
- * UNINITIALIZED_FLOAT is less than 0.0f.
- */
- private boolean isUninitializedFloat(final float x) {
- return x < 0.0f;
- }
-
private void addMotionEventData(final ReplayData replayData, final int actionType,
final long time, final PointerProperties[] pointerProperties,
final PointerCoords[] pointerCoords) {
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 3e82139a6..46e620ae5 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -27,7 +27,6 @@ import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
@@ -56,7 +55,7 @@ public class ResearchLog {
private static final String TAG = ResearchLog.class.getSimpleName();
private static final boolean DEBUG = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
+ private static final long FLUSH_DELAY_IN_MS = TimeUnit.SECONDS.toMillis(5);
/* package */ final ScheduledExecutorService mExecutor;
/* package */ final File mFile;
@@ -81,6 +80,17 @@ public class ResearchLog {
}
/**
+ * Returns true if this is a FeedbackLog.
+ *
+ * FeedbackLogs record only the data associated with a Feedback dialog. Instead of normal
+ * logging, they contain a LogStatement with the complete feedback string and optionally a
+ * recording of the user's supplied demo of the problem.
+ */
+ public boolean isFeedbackLog() {
+ return false;
+ }
+
+ /**
* Waits for any publication requests to finish and closes the {@link JsonWriter} used for
* output.
*
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 8b8ea21e9..25187ced1 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -20,11 +20,7 @@ import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOAR
import android.accounts.Account;
import android.accounts.AccountManager;
-import android.app.AlertDialog;
-import android.app.Dialog;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
@@ -34,7 +30,6 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
-import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -42,12 +37,9 @@ import android.os.IBinder;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.TextUtils;
-import android.text.format.DateUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.Window;
-import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -61,15 +53,16 @@ import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.InputTypeUtils;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputConnection;
-import com.android.inputmethod.latin.RichInputConnection.Range;
import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.TextRange;
import com.android.inputmethod.research.MotionEventReader.ReplayData;
+import com.android.inputmethod.research.ui.SplashScreen;
import java.io.File;
import java.io.FileInputStream;
@@ -81,8 +74,11 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
+// TODO: Add a unit test for every "logging" method (i.e. that is called from the IME and calls
+// enqueueEvent to record a LogStatement).
/**
* Logs the use of the LatinIME keyboard.
*
@@ -92,12 +88,12 @@ import java.util.regex.Pattern;
* This functionality is off by default. See
* {@link ProductionFlag#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}.
*/
-public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
+public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener,
+ SplashScreen.UserConsentListener {
// TODO: This class has grown quite large and combines several concerns that should be
// separated. The following refactorings will be applied as soon as possible after adding
// support for replaying historical events, fixing some replay bugs, adding some ui constraints
// on the feedback dialog, and adding the survey dialog.
- // TODO: Refactor. Move splash screen code into separate class.
// TODO: Refactor. Move feedback screen code into separate class.
// TODO: Refactor. Move logging invocations into their own class.
// TODO: Refactor. Move currentLogUnit management into separate class.
@@ -141,10 +137,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
private static final String PREF_RESEARCH_SAVED_CHANNEL = "pref_research_saved_channel";
- private static final long RESEARCHLOG_CLOSE_TIMEOUT_IN_MS = 5 * 1000;
- private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = 5 * 1000;
- private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = DateUtils.DAY_IN_MILLIS;
- private static final long MAX_LOGFILE_AGE_IN_MS = 4 * DateUtils.DAY_IN_MILLIS;
+ private static final long RESEARCHLOG_CLOSE_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5);
+ private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5);
+ private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = TimeUnit.DAYS.toMillis(1);
+ private static final long MAX_LOGFILE_AGE_IN_MS = TimeUnit.DAYS.toMillis(4);
private static final ResearchLogger sInstance = new ResearchLogger();
private static String sAccountType = null;
@@ -182,8 +178,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private final MotionEventReader mMotionEventReader = new MotionEventReader();
private final Replayer mReplayer = Replayer.getInstance();
private ResearchLogDirectory mResearchLogDirectory;
+ private SplashScreen mSplashScreen;
- private Intent mUploadIntent;
private Intent mUploadNowIntent;
/* package for test */ LogUnit mCurrentLogUnit = new LogUnit();
@@ -194,9 +190,16 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// gesture, and when committing the earlier word, split the LogUnit.
private long mSavedDownEventTime;
private Bundle mFeedbackDialogBundle = null;
+ // Whether the feedback dialog is visible, and the user is typing into it. Normal logging is
+ // not performed on text that the user types into the feedback dialog.
private boolean mInFeedbackDialog = false;
private Handler mUserRecordingTimeoutHandler;
- private static final long USER_RECORDING_TIMEOUT_MS = 30L * DateUtils.SECOND_IN_MILLIS;
+ private static final long USER_RECORDING_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30);
+
+ // Stores a temporary LogUnit while generating a phantom space. Needed because phantom spaces
+ // are issued out-of-order, immediately before the characters generated by other operations that
+ // have already outputted LogStatements.
+ private LogUnit mPhantomSpaceLogUnit = null;
private ResearchLogger() {
mStatistics = Statistics.getInstance();
@@ -229,7 +232,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
resetLogBuffers();
// Initialize external services
- mUploadIntent = new Intent(mLatinIME, UploaderService.class);
mUploadNowIntent = new Intent(mLatinIME, UploaderService.class);
mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -253,14 +255,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (DEBUG) {
final String wordsString = logUnit.getWordsAsString();
Log.d(TAG, "onPublish: '" + wordsString
- + "', hc: " + logUnit.containsCorrection()
+ + "', hc: " + logUnit.containsUserDeletions()
+ ", cipd: " + canIncludePrivateData);
}
for (final String word : logUnit.getWordsAsStringArray()) {
final Dictionary dictionary = getDictionary();
mStatistics.recordWordEntered(
dictionary != null && dictionary.isValidWord(word),
- logUnit.containsCorrection());
+ logUnit.containsUserDeletions());
}
}
publishLogUnits(logUnits, mMainResearchLog, canIncludePrivateData);
@@ -292,62 +294,19 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
}
- private Dialog mSplashDialog = null;
-
private void maybeShowSplashScreen() {
- if (ResearchSettings.readHasSeenSplash(mPrefs)) {
- return;
- }
- if (mSplashDialog != null && mSplashDialog.isShowing()) {
- return;
- }
- final IBinder windowToken = mMainKeyboardView != null
- ? mMainKeyboardView.getWindowToken() : null;
- if (windowToken == null) {
- return;
- }
- final AlertDialog.Builder builder = new AlertDialog.Builder(mLatinIME)
- .setTitle(R.string.research_splash_title)
- .setMessage(R.string.research_splash_content)
- .setPositiveButton(android.R.string.yes,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- onUserLoggingConsent();
- mSplashDialog.dismiss();
- }
- })
- .setNegativeButton(android.R.string.no,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- final String packageName = mLatinIME.getPackageName();
- final Uri packageUri = Uri.parse("package:" + packageName);
- final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE,
- packageUri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mLatinIME.startActivity(intent);
- }
- })
- .setCancelable(true)
- .setOnCancelListener(
- new OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- mLatinIME.requestHideSelf(0);
- }
- });
- mSplashDialog = builder.create();
- final Window w = mSplashDialog.getWindow();
- final WindowManager.LayoutParams lp = w.getAttributes();
- lp.token = windowToken;
- lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
- w.setAttributes(lp);
- w.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
- mSplashDialog.show();
- }
-
- public void onUserLoggingConsent() {
+ if (ResearchSettings.readHasSeenSplash(mPrefs)) return;
+ if (mSplashScreen != null && mSplashScreen.isShowing()) return;
+ if (mMainKeyboardView == null) return;
+ final IBinder windowToken = mMainKeyboardView.getWindowToken();
+ if (windowToken == null) return;
+
+ mSplashScreen = new SplashScreen(mLatinIME, this);
+ mSplashScreen.showSplashScreen(windowToken);
+ }
+
+ @Override
+ public void onSplashScreenUserClickedOk() {
if (mPrefs == null) {
mPrefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME);
if (mPrefs == null) return;
@@ -358,12 +317,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
restart();
}
- private void setLoggingAllowed(final boolean enableLogging) {
- if (mPrefs == null) return;
- sIsLogging = enableLogging;
- ResearchSettings.writeResearchLoggerEnabledFlag(mPrefs, enableLogging);
- }
-
private void checkForEmptyEditor() {
if (mLatinIME == null) {
return;
@@ -420,6 +373,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
resetLogBuffers();
+ cancelFeedbackDialog();
}
public void abort() {
@@ -456,6 +410,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
presentFeedbackDialog(latinIME);
}
+ public void presentFeedbackDialogFromSettings() {
+ if (mLatinIME != null) {
+ presentFeedbackDialog(mLatinIME);
+ }
+ }
+
public void presentFeedbackDialog(final LatinIME latinIME) {
if (isMakingUserRecording()) {
saveRecording();
@@ -574,8 +534,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
toast.show();
boolean isLogDeleted = abort();
final long currentTime = System.currentTimeMillis();
- final long resumeTime = currentTime + 1000 * 60 *
- SUSPEND_DURATION_IN_MINUTES;
+ final long resumeTime = currentTime
+ + TimeUnit.MINUTES.toMillis(SUSPEND_DURATION_IN_MINUTES);
suspendLoggingUntil(resumeTime);
toast.cancel();
Toast.makeText(latinIME, R.string.research_notify_logging_suspended,
@@ -650,7 +610,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
feedbackLogUnit.addLogStatement(LOGSTATEMENT_FEEDBACK, SystemClock.uptimeMillis(),
feedbackContents, accountName, recording);
- final ResearchLog feedbackLog = new ResearchLog(mResearchLogDirectory.getLogFilePath(
+ final ResearchLog feedbackLog = new FeedbackLog(mResearchLogDirectory.getLogFilePath(
System.currentTimeMillis(), System.nanoTime()), mLatinIME);
final LogBuffer feedbackLogBuffer = new LogBuffer();
feedbackLogBuffer.shiftIn(feedbackLogUnit);
@@ -667,7 +627,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mMotionEventReader.readMotionEventData(mUserRecordingFile);
mReplayer.replay(replayData, null);
}
- }, 1000);
+ }, TimeUnit.SECONDS.toMillis(1));
}
if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) {
@@ -692,13 +652,19 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mInFeedbackDialog = false;
}
+ private void cancelFeedbackDialog() {
+ if (isMakingUserRecording()) {
+ cancelRecording();
+ }
+ mInFeedbackDialog = false;
+ }
+
public void initSuggest(final Suggest suggest) {
mSuggest = suggest;
// MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create
// a new one.
if (mMainLogBuffer != null) {
- stop();
- start();
+ restart();
}
}
@@ -713,8 +679,28 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mIsPasswordView = isPasswordView;
}
- private boolean isAllowedToLog() {
- return !mIsPasswordView && sIsLogging && !mInFeedbackDialog;
+ /**
+ * Returns true if logging is permitted.
+ *
+ * This method is called when adding a LogStatement to a LogUnit, and when adding a LogUnit to a
+ * ResearchLog. It is checked in both places in case conditions change between these times, and
+ * as a defensive measure in case refactoring changes the logging pipeline.
+ */
+ private boolean isAllowedToLogTo(final ResearchLog researchLog) {
+ // Logging is never allowed in these circumstances
+ if (mIsPasswordView) return false;
+ if (!sIsLogging) return false;
+ if (mInFeedbackDialog) {
+ // The FeedbackDialog is up. Normal logging should not happen (the user might be trying
+ // out things while the dialog is up, and their reporting of an issue may not be
+ // representative of what they normally type). However, after the user has finished
+ // entering their feedback, the logger packs their comments and an encoded version of
+ // any demonstration of the issue into a special "FeedbackLog". So if the FeedbackLog
+ // is the destination, we do want to allow logging to it.
+ return researchLog.isFeedbackLog();
+ }
+ // No other exclusions. Logging is permitted.
+ return true;
}
public void requestIndicatorRedraw() {
@@ -747,7 +733,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// and remove this method.
// The check for MainKeyboardView ensures that the indicator only decorates the main
// keyboard, not every keyboard.
- if (IS_SHOWING_INDICATOR && (isAllowedToLog() || isReplaying())
+ if (IS_SHOWING_INDICATOR && (isAllowedToLogTo(mMainResearchLog) || isReplaying())
&& view instanceof MainKeyboardView) {
final int savedColor = paint.getColor();
paint.setColor(getIndicatorColor());
@@ -782,7 +768,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private synchronized void enqueueEvent(final LogUnit logUnit, final LogStatement logStatement,
final Object... values) {
assert values.length == logStatement.getKeys().length;
- if (isAllowedToLog() && logUnit != null) {
+ if (isAllowedToLogTo(mMainResearchLog) && logUnit != null) {
final long time = SystemClock.uptimeMillis();
logUnit.addLogStatement(logStatement, time, values);
}
@@ -792,8 +778,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mCurrentLogUnit.setMayContainDigit();
}
- private void setCurrentLogUnitContainsCorrection() {
- mCurrentLogUnit.setContainsCorrection();
+ private void setCurrentLogUnitContainsUserDeletions() {
+ mCurrentLogUnit.setContainsUserDeletions();
}
private void setCurrentLogUnitCorrectionType(final int correctionType) {
@@ -825,20 +811,22 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// The user has deleted this word and returned to the previous. Check that the word in the
// logUnit matches the expected word. If so, restore the last log unit committed to be the
// current logUnit. I.e., pull out the last LogUnit from all the LogBuffers, and make
- // restore it to mCurrentLogUnit so the new edits are captured with the word. Optionally
- // dump the contents of mCurrentLogUnit (useful if they contain deletions of the next word
- // that should not be reported to protect user privacy)
+ // it the mCurrentLogUnit so the new edits are captured with the word. Optionally dump the
+ // contents of mCurrentLogUnit (useful if they contain deletions of the next word that
+ // should not be reported to protect user privacy)
//
// Note that we don't use mLastLogUnit here, because it only goes one word back and is only
// needed for reverts, which only happen one back.
final LogUnit oldLogUnit = mMainLogBuffer.peekLastLogUnit();
- // Check that expected word matches.
+ // Check that expected word matches. It's ok if both strings are null, because this is the
+ // case where the LogUnit is storing a non-word, e.g. a separator.
if (oldLogUnit != null) {
+ // Because the word is stored in the LogUnit with digits scrubbed, the comparison must
+ // be made on a scrubbed version of the expectedWord as well.
+ final String scrubbedExpectedWord = scrubDigitsFromString(expectedWord);
final String oldLogUnitWords = oldLogUnit.getWordsAsString();
- if (oldLogUnitWords != null && !oldLogUnitWords.equals(expectedWord)) {
- return;
- }
+ if (!TextUtils.equals(scrubbedExpectedWord, oldLogUnitWords)) return;
}
// Uncommit, merging if necessary.
@@ -881,7 +869,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final ResearchLog researchLog, final boolean canIncludePrivateData) {
final LogUnit openingLogUnit = new LogUnit();
if (logUnits.isEmpty()) return;
- if (!isAllowedToLog()) return;
+ if (!isAllowedToLogTo(researchLog)) return;
// LogUnits not containing private data, such as contextual data for the log, do not require
// logSegment boundary statements.
if (canIncludePrivateData) {
@@ -893,7 +881,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (DEBUG) {
Log.d(TAG, "publishLogBuffer: " + (logUnit.hasOneOrMoreWords()
? logUnit.getWordsAsString() : "<wordless>")
- + ", correction?: " + logUnit.containsCorrection());
+ + ", correction?: " + logUnit.containsUserDeletions());
}
researchLog.publish(logUnit, canIncludePrivateData);
}
@@ -954,7 +942,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return Character.isDigit(codePoint) ? DIGIT_REPLACEMENT_CODEPOINT : codePoint;
}
- /* package for test */ static String scrubDigitsFromString(String s) {
+ /* package for test */ static String scrubDigitsFromString(final String s) {
+ if (s == null) return null;
StringBuilder sb = null;
final int length = s.length();
for (int i = 0; i < length; i = s.offsetByCodePoints(i, 1)) {
@@ -1077,22 +1066,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private static final LogStatement LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT =
new LogStatement("MotionEvent", true, false, "action",
LogStatement.KEY_IS_LOGGING_RELATED, "motionEvent");
- public static void mainKeyboardView_processMotionEvent(final MotionEvent me, final int action,
- final long eventTime, final int index, final int id, final int x, final int y) {
- if (me != null) {
- final String actionString = LoggingUtils.getMotionEventActionTypeString(action);
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT,
- actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me));
- if (action == MotionEvent.ACTION_DOWN) {
- // Subtract 1 from eventTime so the down event is included in the later
- // LogUnit, not the earlier (the test is for inequality).
- researchLogger.setSavedDownEventTime(eventTime - 1);
- }
- // Refresh the timer in case we are capturing user feedback.
- if (researchLogger.isMakingUserRecording()) {
- researchLogger.resetRecordingTimer();
- }
+ public static void mainKeyboardView_processMotionEvent(final MotionEvent me) {
+ if (me == null) {
+ return;
+ }
+ final int action = me.getActionMasked();
+ final long eventTime = me.getEventTime();
+ final String actionString = LoggingUtils.getMotionEventActionTypeString(action);
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT,
+ actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me));
+ if (action == MotionEvent.ACTION_DOWN) {
+ // Subtract 1 from eventTime so the down event is included in the later
+ // LogUnit, not the earlier (the test is for inequality).
+ researchLogger.setSavedDownEventTime(eventTime - 1);
+ }
+ // Refresh the timer in case we are capturing user feedback.
+ if (researchLogger.isMakingUserRecording()) {
+ researchLogger.resetRecordingTimer();
}
}
@@ -1223,7 +1214,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final RichInputConnection connection) {
String word = "";
if (connection != null) {
- Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
+ TextRange range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
if (range != null) {
word = range.mWord.toString();
}
@@ -1247,26 +1238,50 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
/**
+ * Log a revert of onTextInput() (known in the IME as "EnteredText").
+ *
+ * SystemResponse: Remove the LogUnit recording the textInput
+ */
+ public static void latinIME_handleBackspace_cancelTextInput(final String text) {
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.uncommitCurrentLogUnit(text, true /* dumpCurrentLogUnit */);
+ }
+
+ /**
* Log a call to LatinIME.pickSuggestionManually().
*
* UserAction: The user has chosen a specific word from the suggestion strip.
*/
private static final LogStatement LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY =
new LogStatement("LatinIMEPickSuggestionManually", true, false, "replacedWord", "index",
- "suggestion", "x", "y", "isBatchMode");
+ "suggestion", "x", "y", "isBatchMode", "score", "kind", "sourceDict");
+ /**
+ * Log a call to LatinIME.pickSuggestionManually().
+ *
+ * @param replacedWord the typed word that this manual suggestion replaces. May not be null.
+ * @param index the index in the suggestion strip
+ * @param suggestion the committed suggestion. May not be null.
+ * @param isBatchMode whether this was input in batch mode, aka gesture.
+ * @param score the internal score of the suggestion, as output by the dictionary
+ * @param kind the kind of suggestion, as one of the SuggestedWordInfo#KIND_* constants
+ * @param sourceDict the source origin of this word, as one of the Dictionary#TYPE_* constants.
+ */
public static void latinIME_pickSuggestionManually(final String replacedWord,
- final int index, final String suggestion, final boolean isBatchMode) {
+ final int index, final String suggestion, final boolean isBatchMode,
+ final int score, final int kind, final String sourceDict) {
final ResearchLogger researchLogger = getInstance();
+ // Note : suggestion can't be null here, because it's only called in a place where it
+ // can't be null.
if (!replacedWord.equals(suggestion.toString())) {
// The user chose something other than what was already there.
- researchLogger.setCurrentLogUnitContainsCorrection();
+ researchLogger.setCurrentLogUnitContainsUserDeletions();
researchLogger.setCurrentLogUnitCorrectionType(LogUnit.CORRECTIONTYPE_TYPO);
}
final String scrubbedWord = scrubDigitsFromString(suggestion);
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY,
scrubDigitsFromString(replacedWord), index,
- suggestion == null ? null : scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE,
- Constants.SUGGESTION_STRIP_COORDINATE, isBatchMode);
+ scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE,
+ Constants.SUGGESTION_STRIP_COORDINATE, isBatchMode, score, kind, sourceDict);
researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode);
researchLogger.mStatistics.recordManualSuggestion(SystemClock.uptimeMillis());
}
@@ -1291,17 +1306,32 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
/**
* Log a call to LatinIME.sendKeyCodePoint().
*
- * SystemResponse: The IME is inserting text into the TextView for numbers, fixed strings, or
- * some other unusual mechanism.
+ * SystemResponse: The IME is inserting text into the TextView for non-word-constituent,
+ * strings (separators, numbers, other symbols).
*/
private static final LogStatement LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT =
new LogStatement("LatinIMESendKeyCodePoint", true, false, "code");
public static void latinIME_sendKeyCodePoint(final int code) {
final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT,
- Constants.printableCode(scrubDigitFromCodePoint(code)));
- if (Character.isDigit(code)) {
- researchLogger.setCurrentLogUnitContainsDigitFlag();
+ final LogUnit phantomSpaceLogUnit = researchLogger.mPhantomSpaceLogUnit;
+ if (phantomSpaceLogUnit == null) {
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT,
+ Constants.printableCode(scrubDigitFromCodePoint(code)));
+ if (Character.isDigit(code)) {
+ researchLogger.setCurrentLogUnitContainsDigitFlag();
+ }
+ researchLogger.commitCurrentLogUnit();
+ } else {
+ researchLogger.enqueueEvent(phantomSpaceLogUnit, LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT,
+ Constants.printableCode(scrubDigitFromCodePoint(code)));
+ if (Character.isDigit(code)) {
+ phantomSpaceLogUnit.setMayContainDigit();
+ }
+ researchLogger.mMainLogBuffer.shiftIn(phantomSpaceLogUnit);
+ if (researchLogger.mUserRecordingLogBuffer != null) {
+ researchLogger.mUserRecordingLogBuffer.shiftIn(phantomSpaceLogUnit);
+ }
+ researchLogger.mPhantomSpaceLogUnit = null;
}
}
@@ -1311,12 +1341,18 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
* SystemResponse: The IME is inserting a real space in place of a phantom space.
*/
private static final LogStatement LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE =
- new LogStatement("LatinIMEPromotPhantomSpace", false, false);
+ new LogStatement("LatinIMEPromotePhantomSpace", false, false);
public static void latinIME_promotePhantomSpace() {
+ // A phantom space is always added before the text that triggered it. The triggering text
+ // and the events that created it will be in mCurrentLogUnit, but the phantom space should
+ // be in its own LogUnit, committed before the triggering text. Although it is created
+ // here, it is not added to the LogBuffer until the following call to
+ // latinIME_sendKeyCodePoint, because SENDKEYCODEPOINT LogStatement also must go into that
+ // LogUnit.
final ResearchLogger researchLogger = getInstance();
- final LogUnit logUnit;
- logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
- researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE);
+ researchLogger.mPhantomSpaceLogUnit = new LogUnit();
+ researchLogger.enqueueEvent(researchLogger.mPhantomSpaceLogUnit,
+ LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE);
}
/**
@@ -1402,23 +1438,40 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public static void latinIME_revertCommit(final String committedWord,
final String originallyTypedWord, final boolean isBatchMode,
final String separatorString) {
+ // TODO: Prioritize adding a unit test for this method (as it is especially complex)
+ // TODO: Update the UserRecording LogBuffer as well as the MainLogBuffer
final ResearchLogger researchLogger = getInstance();
- // TODO: Verify that mCurrentLogUnit has been restored and contains the reverted word.
- final LogUnit logUnit;
- logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
- if (originallyTypedWord.length() > 0 && hasLetters(originallyTypedWord)) {
- if (logUnit != null) {
- logUnit.setWords(originallyTypedWord);
- }
- }
- researchLogger.enqueueEvent(logUnit != null ? logUnit : researchLogger.mCurrentLogUnit,
- LOGSTATEMENT_LATINIME_REVERTCOMMIT, committedWord, originallyTypedWord,
- separatorString);
- if (logUnit != null) {
- logUnit.setContainsCorrection();
+ //
+ // 1. Remove separator LogUnit
+ final LogUnit lastLogUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
+ // Check that we're not at the beginning of input
+ if (lastLogUnit == null) return;
+ // Check that we're after a separator
+ if (lastLogUnit.getWordsAsString() != null) return;
+ // Remove separator
+ final LogUnit separatorLogUnit = researchLogger.mMainLogBuffer.unshiftIn();
+
+ // 2. Add revert LogStatement
+ final LogUnit revertedLogUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
+ if (revertedLogUnit == null) return;
+ if (!revertedLogUnit.getWordsAsString().equals(scrubDigitsFromString(committedWord))) {
+ // Any word associated with the reverted LogUnit has already had its digits scrubbed, so
+ // any digits in the committedWord argument must also be scrubbed for an accurate
+ // comparison.
+ return;
}
+ researchLogger.enqueueEvent(revertedLogUnit, LOGSTATEMENT_LATINIME_REVERTCOMMIT,
+ committedWord, originallyTypedWord, separatorString);
+
+ // 3. Update the word associated with the LogUnit
+ revertedLogUnit.setWords(originallyTypedWord);
+ revertedLogUnit.setContainsUserDeletions();
+
+ // 4. Re-add the separator LogUnit
+ researchLogger.mMainLogBuffer.shiftIn(separatorLogUnit);
+
+ // 5. Record stats
researchLogger.mStatistics.recordRevertCommit(SystemClock.uptimeMillis());
- researchLogger.commitCurrentLogUnitAsWord(originallyTypedWord, Long.MAX_VALUE, isBatchMode);
}
/**
@@ -1528,7 +1581,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD =
new LogStatement("RichInputConnectionRevertDoubleSpacePeriod", false, false);
public static void richInputConnection_revertDoubleSpacePeriod() {
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD);
+ final ResearchLogger researchLogger = getInstance();
+ // An extra LogUnit is added for the period; this is removed here because of the revert.
+ researchLogger.uncommitCurrentLogUnit(null, true /* dumpCurrentLogUnit */);
+ // TODO: This will probably be lost as the user backspaces further. Figure out how to put
+ // it into the right logUnit.
+ researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD);
}
/**
@@ -1571,25 +1629,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
private boolean isExpectingCommitText = false;
- /**
- * Log a call to (UnknownClass).commitPartialText
- *
- * SystemResponse: The IME is committing part of a word. This happens if a space is
- * automatically inserted to split a single typed string into two or more words.
- */
- // TODO: This method is currently unused. Find where it should be called from in the IME and
- // add invocations.
- private static final LogStatement LOGSTATEMENT_COMMIT_PARTIAL_TEXT =
- new LogStatement("CommitPartialText", true, false, "newCursorPosition");
- public static void commitPartialText(final String committedWord,
- final long lastTimestampOfWordData, final boolean isBatchMode) {
- final ResearchLogger researchLogger = getInstance();
- final String scrubbedWord = scrubDigitsFromString(committedWord);
- researchLogger.enqueueEvent(LOGSTATEMENT_COMMIT_PARTIAL_TEXT);
- researchLogger.mStatistics.recordAutoCorrection(SystemClock.uptimeMillis());
- researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, lastTimestampOfWordData,
- isBatchMode);
- }
/**
* Log a call to RichInputConnection.commitText().
@@ -1613,12 +1652,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
/**
- * Shared event for logging committed text.
+ * Shared events for logging committed text.
+ *
+ * The "CommitTextEventHappened" LogStatement is written to the log even if privacy rules
+ * indicate that the word contents should not be logged. It has no contents, and only serves to
+ * record the event and thereby make it easier to calculate word-level statistics even when the
+ * word contents are unknown.
*/
private static final LogStatement LOGSTATEMENT_COMMITTEXT =
- new LogStatement("CommitText", true, false, "committedText", "isBatchMode");
+ new LogStatement("CommitText", true /* isPotentiallyPrivate */,
+ false /* isPotentiallyRevealing */, "committedText", "isBatchMode");
+ private static final LogStatement LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED =
+ new LogStatement("CommitTextEventHappened", false /* isPotentiallyPrivate */,
+ false /* isPotentiallyRevealing */);
private void enqueueCommitText(final String word, final boolean isBatchMode) {
+ // Event containing the word; will be published only if privacy checks pass
enqueueEvent(LOGSTATEMENT_COMMITTEXT, word, isBatchMode);
+ // Event not containing the word; will always be published
+ enqueueEvent(LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED);
}
/**
@@ -1716,7 +1767,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public static void suddenJumpingTouchEventHandler_onTouchEvent(final MotionEvent me) {
if (me != null) {
getInstance().enqueueEvent(LOGSTATEMENT_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT,
- me.toString());
+ MotionEvent.obtain(me));
}
}
@@ -1752,7 +1803,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
*/
private static final LogStatement LOGSTATEMENT_LATINIME_ONENDBATCHINPUT =
new LogStatement("LatinIMEOnEndBatchInput", true, false, "enteredText",
- "enteredWordPos");
+ "enteredWordPos", "suggestedWords");
public static void latinIME_onEndBatchInput(final CharSequence enteredText,
final int enteredWordPos, final SuggestedWords suggestedWords) {
final ResearchLogger researchLogger = getInstance();
@@ -1760,23 +1811,32 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
researchLogger.mCurrentLogUnit.setWords(enteredText.toString());
}
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONENDBATCHINPUT, enteredText,
- enteredWordPos);
+ enteredWordPos, suggestedWords);
researchLogger.mCurrentLogUnit.initializeSuggestions(suggestedWords);
researchLogger.mStatistics.recordGestureInput(enteredText.length(),
SystemClock.uptimeMillis());
}
+ private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE =
+ new LogStatement("LatinIMEHandleBackspace", true, false, "numCharacters");
/**
* Log a call to LatinIME.handleBackspace() that is not a batch delete.
*
* UserInput: The user is deleting one or more characters by hitting the backspace key once.
* The covers single character deletes as well as deleting selections.
+ *
+ * @param numCharacters how many characters the backspace operation deleted
+ * @param shouldUncommitLogUnit whether to uncommit the last {@code LogUnit} in the
+ * {@code LogBuffer}
*/
- private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE =
- new LogStatement("LatinIMEHandleBackspace", true, false, "numCharacters");
- public static void latinIME_handleBackspace(final int numCharacters) {
+ public static void latinIME_handleBackspace(final int numCharacters,
+ final boolean shouldUncommitLogUnit) {
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE, numCharacters);
+ if (shouldUncommitLogUnit) {
+ ResearchLogger.getInstance().uncommitCurrentLogUnit(
+ null, true /* dumpCurrentLogUnit */);
+ }
}
/**
@@ -1794,6 +1854,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
numCharacters);
researchLogger.mStatistics.recordGestureDelete(deletedText.length(),
SystemClock.uptimeMillis());
+ researchLogger.uncommitCurrentLogUnit(deletedText.toString(),
+ false /* dumpCurrentLogUnit */);
}
/**
@@ -1837,6 +1899,20 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
/**
+ * Call this method when the logging system has attempted publication of an n-gram.
+ *
+ * Statistics are gathered about the success or failure.
+ *
+ * @param publishabilityResultCode a result code as defined by
+ * {@code MainLogBuffer.PUBLISHABILITY_*}
+ */
+ static void recordPublishabilityResultCode(final int publishabilityResultCode) {
+ final ResearchLogger researchLogger = getInstance();
+ final Statistics statistics = researchLogger.mStatistics;
+ statistics.recordPublishabilityResultCode(publishabilityResultCode);
+ }
+
+ /**
* Log statistics.
*
* ContextualData, recorded at the end of a session.
@@ -1848,7 +1924,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
"averageTimeDuringRepeatedDelete", "averageTimeAfterDelete",
"dictionaryWordCount", "splitWordsCount", "gestureInputCount",
"gestureCharsCount", "gesturesDeletedCount", "manualSuggestionsCount",
- "revertCommitsCount", "correctedWordsCount", "autoCorrectionsCount");
+ "revertCommitsCount", "correctedWordsCount", "autoCorrectionsCount",
+ "publishableCount", "unpublishableStoppingCount",
+ "unpublishableIncorrectWordCount", "unpublishableSampledTooRecentlyCount",
+ "unpublishableDictionaryUnavailableCount", "unpublishableMayContainDigitCount",
+ "unpublishableNotInDictionaryCount");
private static void logStatistics() {
final ResearchLogger researchLogger = getInstance();
final Statistics statistics = researchLogger.mStatistics;
@@ -1863,6 +1943,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
statistics.mGesturesInputCount, statistics.mGesturesCharsCount,
statistics.mGesturesDeletedCount, statistics.mManualSuggestionsCount,
statistics.mRevertCommitsCount, statistics.mCorrectedWordsCount,
- statistics.mAutoCorrectionsCount);
+ statistics.mAutoCorrectionsCount, statistics.mPublishableCount,
+ statistics.mUnpublishableStoppingCount, statistics.mUnpublishableIncorrectWordCount,
+ statistics.mUnpublishableSampledTooRecently,
+ statistics.mUnpublishableDictionaryUnavailable,
+ statistics.mUnpublishableMayContainDigit, statistics.mUnpublishableNotInDictionary);
}
}
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index 7f6c851bb..fd323a104 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -21,6 +21,8 @@ import android.util.Log;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.define.ProductionFlag;
+import java.util.concurrent.TimeUnit;
+
public class Statistics {
private static final String TAG = Statistics.class.getSimpleName();
private static final boolean DEBUG = false
@@ -61,6 +63,16 @@ public class Statistics {
boolean mIsEmptyUponStarting;
boolean mIsEmptinessStateKnown;
+ // Counts of how often an n-gram is collected or not, and the reasons for the decision.
+ // Keep consistent with publishability result code list in MainLogBuffer
+ int mPublishableCount;
+ int mUnpublishableStoppingCount;
+ int mUnpublishableIncorrectWordCount;
+ int mUnpublishableSampledTooRecently;
+ int mUnpublishableDictionaryUnavailable;
+ int mUnpublishableMayContainDigit;
+ int mUnpublishableNotInDictionary;
+
// Timers to count average time to enter a key, first press a delete key,
// between delete keys, and then to return typing after a delete key.
final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
@@ -92,8 +104,8 @@ public class Statistics {
// To account for the interruptions when the user's attention is directed elsewhere, times
// longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
- public static final int MIN_TYPING_INTERMISSION = 2 * 1000; // in milliseconds
- public static final int MIN_DELETION_INTERMISSION = 10 * 1000; // in milliseconds
+ public static final long MIN_TYPING_INTERMISSION = TimeUnit.SECONDS.toMillis(2);
+ public static final long MIN_DELETION_INTERMISSION = TimeUnit.SECONDS.toMillis(10);
// The last time that a tap was performed
private long mLastTapTime;
@@ -133,6 +145,13 @@ public class Statistics {
mAfterDeleteKeyCounter.reset();
mGesturesCharsCount = 0;
mGesturesDeletedCount = 0;
+ mPublishableCount = 0;
+ mUnpublishableStoppingCount = 0;
+ mUnpublishableIncorrectWordCount = 0;
+ mUnpublishableSampledTooRecently = 0;
+ mUnpublishableDictionaryUnavailable = 0;
+ mUnpublishableMayContainDigit = 0;
+ mUnpublishableNotInDictionary = 0;
mLastTapTime = 0;
mIsLastKeyDeleteKey = false;
@@ -230,4 +249,31 @@ public class Statistics {
mIsLastKeyDeleteKey = isDeletion;
mLastTapTime = time;
}
+
+ public void recordPublishabilityResultCode(final int publishabilityResultCode) {
+ // Keep consistent with publishability result code list in MainLogBuffer
+ switch (publishabilityResultCode) {
+ case MainLogBuffer.PUBLISHABILITY_PUBLISHABLE:
+ mPublishableCount++;
+ break;
+ case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_STOPPING:
+ mUnpublishableStoppingCount++;
+ break;
+ case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT:
+ mUnpublishableIncorrectWordCount++;
+ break;
+ case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY:
+ mUnpublishableSampledTooRecently++;
+ break;
+ case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE:
+ mUnpublishableDictionaryUnavailable++;
+ break;
+ case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT:
+ mUnpublishableMayContainDigit++;
+ break;
+ case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY:
+ mUnpublishableNotInDictionary++;
+ break;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/research/Uploader.java b/java/src/com/android/inputmethod/research/Uploader.java
index ba05ec12b..c7ea3e69d 100644
--- a/java/src/com/android/inputmethod/research/Uploader.java
+++ b/java/src/com/android/inputmethod/research/Uploader.java
@@ -49,7 +49,7 @@ public final class Uploader {
private static final boolean DEBUG = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
// Set IS_INHIBITING_AUTO_UPLOAD to true for local testing
- private static final boolean IS_INHIBITING_AUTO_UPLOAD = false
+ private static final boolean IS_INHIBITING_UPLOAD = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
private static final int BUF_SIZE = 1024 * 8;
@@ -76,7 +76,7 @@ public final class Uploader {
}
public boolean isPossibleToUpload() {
- return hasUploadingPermission() && mUrl != null && !IS_INHIBITING_AUTO_UPLOAD;
+ return hasUploadingPermission() && mUrl != null && !IS_INHIBITING_UPLOAD;
}
private boolean hasUploadingPermission() {
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
index d2db34927..fd3f2f60e 100644
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -24,8 +24,6 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
/**
* Service to invoke the uploader.
*
@@ -33,12 +31,9 @@ import com.android.inputmethod.latin.define.ProductionFlag;
*/
public final class UploaderService extends IntentService {
private static final String TAG = UploaderService.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
public static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
+ ".extra.UPLOAD_UNCONDITIONALLY";
- protected static final int TIMEOUT_IN_MS = 1000 * 4;
public UploaderService() {
super("Research Uploader Service");
diff --git a/java/src/com/android/inputmethod/research/ui/SplashScreen.java b/java/src/com/android/inputmethod/research/ui/SplashScreen.java
new file mode 100644
index 000000000..78ed668d1
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/ui/SplashScreen.java
@@ -0,0 +1,111 @@
+/*
+ * 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.research.ui;
+
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
+import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
+import android.os.IBinder;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+
+import com.android.inputmethod.latin.R.string;
+
+/**
+ * Show a dialog when the user first opens the keyboard.
+ *
+ * The splash screen is a modal dialog box presented when the user opens this keyboard for the first
+ * time. It is useful for giving specific warnings that must be shown to the user before use.
+ *
+ * While the splash screen does share with the setup wizard the common goal of presenting
+ * information to the user before use, they are presented at different times and with different
+ * capabilities. The setup wizard is launched by tapping on the icon, and walks the user through
+ * the setup process. It can, however, be bypassed by enabling the keyboard from Settings directly.
+ * The splash screen cannot be bypassed, and is therefore more appropriate for obtaining user
+ * consent.
+ */
+public class SplashScreen {
+ public interface UserConsentListener {
+ public void onSplashScreenUserClickedOk();
+ }
+
+ final UserConsentListener mListener;
+ final Dialog mSplashDialog;
+
+ public SplashScreen(final InputMethodService inputMethodService,
+ final UserConsentListener listener) {
+ mListener = listener;
+ final Builder builder = new Builder(inputMethodService)
+ .setTitle(string.research_splash_title)
+ .setMessage(string.research_splash_content)
+ .setPositiveButton(android.R.string.yes,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mListener.onSplashScreenUserClickedOk();
+ mSplashDialog.dismiss();
+ }
+ })
+ .setNegativeButton(android.R.string.no,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final String packageName = inputMethodService.getPackageName();
+ final Uri packageUri = Uri.parse("package:" + packageName);
+ final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE,
+ packageUri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ inputMethodService.startActivity(intent);
+ }
+ })
+ .setCancelable(true)
+ .setOnCancelListener(
+ new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ inputMethodService.requestHideSelf(0);
+ }
+ });
+ mSplashDialog = builder.create();
+ }
+
+ /**
+ * Show the splash screen.
+ *
+ * The user must consent to the terms presented in the SplashScreen before they can use the
+ * keyboard. If they cancel instead, they are given the option to uninstall the keybard.
+ *
+ * @param windowToken {@link IBinder} to attach dialog to
+ */
+ public void showSplashScreen(final IBinder windowToken) {
+ final Window window = mSplashDialog.getWindow();
+ final LayoutParams lp = window.getAttributes();
+ lp.token = windowToken;
+ lp.type = LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+ window.setAttributes(lp);
+ window.addFlags(LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ mSplashDialog.show();
+ }
+
+ public boolean isShowing() {
+ return mSplashDialog.isShowing();
+ }
+}