aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityLongPressTimer.java67
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java3
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java127
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java275
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java37
-rw-r--r--java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java135
-rw-r--r--java/src/com/android/inputmethod/accessibility/MoreKeysKeyboardAccessibilityDelegate.java120
-rw-r--r--java/src/com/android/inputmethod/accessibility/MoreSuggestionsAccessibilityDelegate.java37
-rw-r--r--java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java5
-rw-r--r--java/src/com/android/inputmethod/compat/ViewCompatUtils.java21
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ActionBatch.java3
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java6
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java13
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java8
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataParser.java4
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java10
-rw-r--r--java/src/com/android/inputmethod/event/CombinerChain.java14
-rw-r--r--java/src/com/android/inputmethod/event/MyanmarReordering.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java28
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java9
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java9
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java39
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardTheme.java59
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java76
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java84
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java89
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java41
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityInfo.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java7
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java14
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java17
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java56
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java15
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java34
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java7
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java12
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java7
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java14
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeysCache.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java4
-rw-r--r--java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java55
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java163
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java5
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java5
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java20
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java16
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java21
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java29
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitator.java (renamed from java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java)119
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java3
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java95
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java32
-rw-r--r--java/src/com/android/inputmethod/latin/InputView.java27
-rw-r--r--java/src/com/android/inputmethod/latin/LastComposedWord.java2
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java285
-rw-r--r--java/src/com/android/inputmethod/latin/LatinImeLogger.java74
-rw-r--r--java/src/com/android/inputmethod/latin/PrevWordsInfo.java35
-rw-r--r--java/src/com/android/inputmethod/latin/PunctuationSuggestions.java4
-rw-r--r--java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java16
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java66
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java22
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java3
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java53
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java56
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java9
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java78
-rw-r--r--java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java3
-rw-r--r--java/src/com/android/inputmethod/latin/define/ProductionFlag.java7
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java308
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java5
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/WordProperty.java9
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/AccountUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java10
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java7
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java37
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java8
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java47
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdater.java43
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java15
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java17
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java9
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettings.java88
-rw-r--r--java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java7
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java9
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java33
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java52
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java6
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java6
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java3
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java10
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java68
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java21
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java6
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java5
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java185
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java6
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java29
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java14
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java82
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java6
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java4
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java3
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java9
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java43
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CollectionUtils.java76
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CsvUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilter.java182
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java129
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java59
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java36
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FragmentUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java13
-rw-r--r--java/src/com/android/inputmethod/latin/utils/JsonUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java45
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java77
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LocaleUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java122
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java34
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResourceUtils.java7
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java21
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java13
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java3
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java293
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java137
-rw-r--r--java/src/com/android/inputmethod/research/BootBroadcastReceiver.java34
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackActivity.java38
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackFragment.java131
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackLayout.java62
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackLog.java32
-rw-r--r--java/src/com/android/inputmethod/research/FixedLogBuffer.java174
-rw-r--r--java/src/com/android/inputmethod/research/JsonUtils.java162
-rw-r--r--java/src/com/android/inputmethod/research/LogBuffer.java73
-rw-r--r--java/src/com/android/inputmethod/research/LogStatement.java225
-rw-r--r--java/src/com/android/inputmethod/research/LogUnit.java496
-rw-r--r--java/src/com/android/inputmethod/research/LoggingUtils.java38
-rw-r--r--java/src/com/android/inputmethod/research/MainLogBuffer.java287
-rw-r--r--java/src/com/android/inputmethod/research/MotionEventReader.java326
-rw-r--r--java/src/com/android/inputmethod/research/Replayer.java150
-rw-r--r--java/src/com/android/inputmethod/research/ReplayerService.java65
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLog.java298
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogDirectory.java113
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java1885
-rw-r--r--java/src/com/android/inputmethod/research/ResearchSettings.java72
-rw-r--r--java/src/com/android/inputmethod/research/Statistics.java279
-rw-r--r--java/src/com/android/inputmethod/research/Uploader.java171
-rw-r--r--java/src/com/android/inputmethod/research/UploaderService.java88
-rw-r--r--java/src/com/android/inputmethod/research/ui/SplashScreen.java111
166 files changed, 2637 insertions, 8025 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityLongPressTimer.java b/java/src/com/android/inputmethod/accessibility/AccessibilityLongPressTimer.java
new file mode 100644
index 000000000..967cafad0
--- /dev/null
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityLongPressTimer.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.accessibility;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.R;
+
+// Handling long press timer to show a more keys keyboard.
+final class AccessibilityLongPressTimer extends Handler {
+ public interface LongPressTimerCallback {
+ public void onLongPressed(Key key);
+ }
+
+ private static final int MSG_LONG_PRESS = 1;
+
+ private final LongPressTimerCallback mCallback;
+ private final long mConfigAccessibilityLongPressTimeout;
+
+ public AccessibilityLongPressTimer(final LongPressTimerCallback callback,
+ final Context context) {
+ super();
+ mCallback = callback;
+ mConfigAccessibilityLongPressTimeout = context.getResources().getInteger(
+ R.integer.config_accessibility_long_press_key_timeout);
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ switch (msg.what) {
+ case MSG_LONG_PRESS:
+ cancelLongPress();
+ mCallback.onLongPressed((Key)msg.obj);
+ return;
+ default:
+ super.handleMessage(msg);
+ return;
+ }
+ }
+
+ public void startLongPress(final Key key) {
+ cancelLongPress();
+ final Message longPressMessage = obtainMessage(MSG_LONG_PRESS, key);
+ sendMessageDelayed(longPressMessage, mConfigAccessibilityLongPressTimeout);
+ }
+
+ public void cancelLongPress() {
+ removeMessages(MSG_LONG_PRESS);
+ }
+}
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index d50dd3ee6..2762a9f25 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -67,7 +67,6 @@ public final class AccessibilityUtils {
// These only need to be initialized if the kill switch is off.
sInstance.initInternal(context);
- KeyCodeDescriptionMapper.init();
}
public static AccessibilityUtils getInstance() {
@@ -114,7 +113,7 @@ public final class AccessibilityUtils {
* @param event The event to check.
* @return {@true} is the event is a touch exploration event
*/
- public boolean isTouchExplorationEvent(final MotionEvent event) {
+ public static boolean isTouchExplorationEvent(final MotionEvent event) {
final int action = event.getAction();
return action == MotionEvent.ACTION_HOVER_ENTER
|| action == MotionEvent.ACTION_HOVER_EXIT
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 2c87fc1e9..7a3510ee1 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -28,35 +28,29 @@ import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.StringUtils;
import java.util.Locale;
-public final class KeyCodeDescriptionMapper {
+final class KeyCodeDescriptionMapper {
private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName();
private static final String SPOKEN_LETTER_RESOURCE_NAME_FORMAT = "spoken_accented_letter_%04X";
+ private static final String SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT = "spoken_symbol_%04X";
private static final String SPOKEN_EMOJI_RESOURCE_NAME_FORMAT = "spoken_emoji_%04X";
// The resource ID of the string spoken for obscured keys
private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot;
- private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
-
- // Sparse array of spoken description resource IDs indexed by key codes
- private final SparseIntArray mKeyCodeMap;
-
- public static void init() {
- sInstance.initInternal();
- }
+ private static final KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
public static KeyCodeDescriptionMapper getInstance() {
return sInstance;
}
- private KeyCodeDescriptionMapper() {
- mKeyCodeMap = new SparseIntArray();
- }
+ // Sparse array of spoken description resource IDs indexed by key codes
+ private final SparseIntArray mKeyCodeMap = new SparseIntArray();
- private void initInternal() {
+ private KeyCodeDescriptionMapper() {
// Special non-character codes defined in Keyboard
mKeyCodeMap.put(Constants.CODE_SPACE, R.string.spoken_description_space);
mKeyCodeMap.put(Constants.CODE_DELETE, R.string.spoken_description_delete);
@@ -86,14 +80,6 @@ public final class KeyCodeDescriptionMapper {
/**
* Returns the localized description of the action performed by a specified
* key based on the current keyboard state.
- * <p>
- * The order of precedence for key descriptions is:
- * <ol>
- * <li>Manually-defined based on the key label</li>
- * <li>Automatic or manually-defined based on the key code</li>
- * <li>Automatically based on the key label</li>
- * <li>{code null} for keys with no label or key code defined</li>
- * </p>
*
* @param context The package's context.
* @param keyboard The keyboard on which the key resides.
@@ -128,7 +114,20 @@ public final class KeyCodeDescriptionMapper {
// Just attempt to speak the description.
if (code != Constants.CODE_UNSPECIFIED) {
- return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
+ // If the key description should be obscured, now is the time to do it.
+ final boolean isDefinedNonCtrl = Character.isDefined(code)
+ && !Character.isISOControl(code);
+ if (shouldObscure && isDefinedNonCtrl) {
+ return context.getString(OBSCURED_KEY_RES_ID);
+ }
+ final String description = getDescriptionForCodePoint(context, code);
+ if (description != null) {
+ return description;
+ }
+ if (!TextUtils.isEmpty(key.getLabel())) {
+ return key.getLabel();
+ }
+ return context.getString(R.string.spoken_description_unknown);
}
return null;
}
@@ -254,57 +253,39 @@ public final class KeyCodeDescriptionMapper {
/**
* Returns a localized character sequence describing what will happen when
- * the specified key is pressed based on its key code.
- * <p>
- * The order of precedence for key code descriptions is:
- * <ol>
- * <li>Manually-defined shift-locked description</li>
- * <li>Manually-defined shifted description</li>
- * <li>Manually-defined normal description</li>
- * <li>Automatic based on the character represented by the key code</li>
- * <li>Fall-back for undefined or control characters</li>
- * </ol>
- * </p>
+ * the specified key is pressed based on its key code point.
*
* @param context The package's context.
- * @param keyboard The keyboard on which the key resides.
- * @param key The key from which to obtain a description.
- * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured.
- * @return a character sequence describing the action performed by pressing the key
+ * @param codePoint The code point from which to obtain a description.
+ * @return a character sequence describing the code point.
*/
- private String getDescriptionForKeyCode(final Context context, final Keyboard keyboard,
- final Key key, final boolean shouldObscure) {
- final int code = key.getCode();
-
+ public String getDescriptionForCodePoint(final Context context, final int codePoint) {
// If the key description should be obscured, now is the time to do it.
- final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code);
- if (shouldObscure && isDefinedNonCtrl) {
- return context.getString(OBSCURED_KEY_RES_ID);
- }
- final int index = mKeyCodeMap.indexOfKey(code);
+ final int index = mKeyCodeMap.indexOfKey(codePoint);
if (index >= 0) {
return context.getString(mKeyCodeMap.valueAt(index));
}
- final String accentedLetter = getSpokenAccentedLetterDescriptionId(context, code);
+ final String accentedLetter = getSpokenAccentedLetterDescription(context, codePoint);
if (accentedLetter != null) {
return accentedLetter;
}
- // Here, <code>code</code> may be a base letter.
- final int spokenEmojiId = getSpokenDescriptionId(
- context, code, SPOKEN_EMOJI_RESOURCE_NAME_FORMAT);
- if (spokenEmojiId != 0) {
- return context.getString(spokenEmojiId);
+ // Here, <code>code</code> may be a base (non-accented) letter.
+ final String unsupportedSymbol = getSpokenSymbolDescription(context, codePoint);
+ if (unsupportedSymbol != null) {
+ return unsupportedSymbol;
}
- if (isDefinedNonCtrl) {
- return Character.toString((char) code);
+ final String emojiDescription = getSpokenEmojiDescription(context, codePoint);
+ if (emojiDescription != null) {
+ return emojiDescription;
}
- if (!TextUtils.isEmpty(key.getLabel())) {
- return key.getLabel();
+ if (Character.isDefined(codePoint) && !Character.isISOControl(codePoint)) {
+ return StringUtils.newSingleCodePointString(codePoint);
}
- return context.getString(R.string.spoken_description_unknown, code);
+ return null;
}
- private String getSpokenAccentedLetterDescriptionId(final Context context, final int code) {
+ // TODO: Remove this method once TTS supports those accented letters' verbalization.
+ private String getSpokenAccentedLetterDescription(final Context context, final int code) {
final boolean isUpperCase = Character.isUpperCase(code);
final int baseCode = isUpperCase ? Character.toLowerCase(code) : code;
final int baseIndex = mKeyCodeMap.indexOfKey(baseCode);
@@ -314,10 +295,38 @@ public final class KeyCodeDescriptionMapper {
return null;
}
final String spokenText = context.getString(resId);
- return isUpperCase ? context.getString(R.string.spoke_description_upper_case, spokenText)
+ return isUpperCase ? context.getString(R.string.spoken_description_upper_case, spokenText)
: spokenText;
}
+ // TODO: Remove this method once TTS supports those symbols' verbalization.
+ private String getSpokenSymbolDescription(final Context context, final int code) {
+ final int resId = getSpokenDescriptionId(context, code, SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT);
+ if (resId == 0) {
+ return null;
+ }
+ final String spokenText = context.getString(resId);
+ if (!TextUtils.isEmpty(spokenText)) {
+ return spokenText;
+ }
+ // If a translated description is empty, fall back to unknown symbol description.
+ return context.getString(R.string.spoken_symbol_unknown);
+ }
+
+ // TODO: Remove this method once TTS supports emoji verbalization.
+ private String getSpokenEmojiDescription(final Context context, final int code) {
+ final int resId = getSpokenDescriptionId(context, code, SPOKEN_EMOJI_RESOURCE_NAME_FORMAT);
+ if (resId == 0) {
+ return null;
+ }
+ final String spokenText = context.getString(resId);
+ if (!TextUtils.isEmpty(spokenText)) {
+ return spokenText;
+ }
+ // If a translated description is empty, fall back to unknown emoji description.
+ return context.getString(R.string.spoken_emoji_unknown);
+ }
+
private int getSpokenDescriptionId(final Context context, final int code,
final String resourceNameFormat) {
final String resourceName = String.format(Locale.ROOT, resourceNameFormat, code);
diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java
index eed40f4a9..d67d9dc4b 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java
@@ -16,11 +16,12 @@
package com.android.inputmethod.accessibility;
-import android.os.SystemClock;
+import android.content.Context;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
@@ -30,15 +31,31 @@ import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.keyboard.PointerTracker;
+/**
+ * This class represents a delegate that can be registered in a class that extends
+ * {@link KeyboardView} to enhance accessibility support via composition rather via inheritance.
+ *
+ * To implement accessibility mode, the target keyboard view has to:<p>
+ * - Call {@link #setKeyboard(Keyboard)} when a new keyboard is set to the keyboard view.
+ * - Dispatch a hover event by calling {@link #onHoverEnter(MotionEvent)}.
+ *
+ * @param <KV> The keyboard view class type.
+ */
public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
extends AccessibilityDelegateCompat {
+ private static final String TAG = KeyboardAccessibilityDelegate.class.getSimpleName();
+ protected static final boolean DEBUG_HOVER = false;
+
protected final KV mKeyboardView;
protected final KeyDetector mKeyDetector;
private Keyboard mKeyboard;
private KeyboardAccessibilityNodeProvider mAccessibilityNodeProvider;
private Key mLastHoverKey;
+ public static final int HOVER_EVENT_POINTER_ID = 0;
+
public KeyboardAccessibilityDelegate(final KV keyboardView, final KeyDetector keyDetector) {
super();
mKeyboardView = keyboardView;
@@ -65,10 +82,31 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
mKeyboard = keyboard;
}
- protected Keyboard getKeyboard() {
+ protected final Keyboard getKeyboard() {
return mKeyboard;
}
+ protected final void setLastHoverKey(final Key key) {
+ mLastHoverKey = key;
+ }
+
+ protected final Key getLastHoverKey() {
+ return mLastHoverKey;
+ }
+
+ /**
+ * Sends a window state change event with the specified string resource id.
+ *
+ * @param resId The string resource id of the text to send with the event.
+ */
+ protected void sendWindowStateChanged(final int resId) {
+ if (resId == 0) {
+ return;
+ }
+ final Context context = mKeyboardView.getContext();
+ sendWindowStateChanged(context.getString(resId));
+ }
+
/**
* Sends a window state change event with the specified text.
*
@@ -114,119 +152,182 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
}
/**
- * Receives hover events when touch exploration is turned on in SDK versions ICS and higher.
+ * Get a key that a hover event is on.
*
* @param event The hover event.
- * @return {@code true} if the event is handled
+ * @return key The key that the <code>event</code> is on.
*/
- public boolean dispatchHoverEvent(final MotionEvent event) {
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- final Key previousKey = mLastHoverKey;
- final Key key = mKeyDetector.detectHitKey(x, y);
- mLastHoverKey = key;
+ protected final Key getHoverKeyOf(final MotionEvent event) {
+ final int actionIndex = event.getActionIndex();
+ final int x = (int)event.getX(actionIndex);
+ final int y = (int)event.getY(actionIndex);
+ return mKeyDetector.detectHitKey(x, y);
+ }
- switch (event.getAction()) {
- case MotionEvent.ACTION_HOVER_EXIT:
- // Make sure we're not getting an EXIT event because the user slid
- // off the keyboard area, then force a key press.
- if (key != null) {
- final long downTime = simulateKeyPress(key);
- simulateKeyRelease(key, downTime);
- }
- //$FALL-THROUGH$
+ /**
+ * Receives hover events when touch exploration is turned on in SDK versions ICS and higher.
+ *
+ * @param event The hover event.
+ * @return {@code true} if the event is handled.
+ */
+ public boolean onHoverEvent(final MotionEvent event) {
+ switch (event.getActionMasked()) {
case MotionEvent.ACTION_HOVER_ENTER:
- return onHoverKey(key, event);
+ onHoverEnter(event);
+ break;
case MotionEvent.ACTION_HOVER_MOVE:
- if (key != previousKey) {
- return onTransitionKey(key, previousKey, event);
+ onHoverMove(event);
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ onHoverExit(event);
+ break;
+ default:
+ Log.w(getClass().getSimpleName(), "Unknown hover event: " + event);
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Process {@link MotionEvent#ACTION_HOVER_ENTER} event.
+ *
+ * @param event A hover enter event.
+ */
+ protected void onHoverEnter(final MotionEvent event) {
+ final Key key = getHoverKeyOf(event);
+ if (DEBUG_HOVER) {
+ Log.d(TAG, "onHoverEnter: key=" + key);
+ }
+ if (key != null) {
+ onHoverEnterTo(key);
+ }
+ setLastHoverKey(key);
+ }
+
+ /**
+ * Process {@link MotionEvent#ACTION_HOVER_MOVE} event.
+ *
+ * @param event A hover move event.
+ */
+ protected void onHoverMove(final MotionEvent event) {
+ final Key lastKey = getLastHoverKey();
+ final Key key = getHoverKeyOf(event);
+ if (key != lastKey) {
+ if (lastKey != null) {
+ onHoverExitFrom(lastKey);
}
- return onHoverKey(key, event);
+ if (key != null) {
+ onHoverEnterTo(key);
+ }
+ }
+ if (key != null) {
+ onHoverMoveWithin(key);
}
- return false;
+ setLastHoverKey(key);
}
/**
- * Simulates a key press by injecting touch an event into the keyboard view.
- * This avoids the complexity of trackers and listeners within the keyboard.
+ * Process {@link MotionEvent#ACTION_HOVER_EXIT} event.
*
- * @param key The key to press.
+ * @param event A hover exit event.
*/
- private long simulateKeyPress(final Key key) {
- final int x = key.getHitBox().centerX();
- final int y = key.getHitBox().centerY();
- final long downTime = SystemClock.uptimeMillis();
- final MotionEvent downEvent = MotionEvent.obtain(
- downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0);
- mKeyboardView.onTouchEvent(downEvent);
- downEvent.recycle();
- return downTime;
+ protected void onHoverExit(final MotionEvent event) {
+ final Key lastKey = getLastHoverKey();
+ if (DEBUG_HOVER) {
+ Log.d(TAG, "onHoverExit: key=" + getHoverKeyOf(event) + " last=" + lastKey);
+ }
+ if (lastKey != null) {
+ onHoverExitFrom(lastKey);
+ }
+ final Key key = getHoverKeyOf(event);
+ // Make sure we're not getting an EXIT event because the user slid
+ // off the keyboard area, then force a key press.
+ if (key != null) {
+ onRegisterHoverKey(key, event);
+ onHoverExitFrom(key);
+ }
+ setLastHoverKey(null);
+ }
+
+ /**
+ * Register a key that is selected by a hover event
+ *
+ * @param key A key to be registered.
+ * @param event A hover exit event that triggers key registering.
+ */
+ protected void onRegisterHoverKey(final Key key, final MotionEvent event) {
+ if (DEBUG_HOVER) {
+ Log.d(TAG, "onRegisterHoverKey: key=" + key);
+ }
+ simulateTouchEvent(MotionEvent.ACTION_DOWN, event);
+ simulateTouchEvent(MotionEvent.ACTION_UP, event);
}
/**
- * Simulates a key release by injecting touch an event into the keyboard view.
- * This avoids the complexity of trackers and listeners within the keyboard.
+ * Simulating a touch event by injecting a synthesized touch event into {@link PointerTracker}.
*
- * @param key The key to release.
+ * @param touchAction The action of the synthesizing touch event.
+ * @param hoverEvent The base hover event from that the touch event is synthesized.
*/
- private void simulateKeyRelease(final Key key, final long downTime) {
- final int x = key.getHitBox().centerX();
- final int y = key.getHitBox().centerY();
- final MotionEvent upEvent = MotionEvent.obtain(
- downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0);
- mKeyboardView.onTouchEvent(upEvent);
- upEvent.recycle();
+ protected void simulateTouchEvent(final int touchAction, final MotionEvent hoverEvent) {
+ final MotionEvent touchEvent = synthesizeTouchEvent(touchAction, hoverEvent);
+ final int actionIndex = touchEvent.getActionIndex();
+ final int pointerId = touchEvent.getPointerId(actionIndex);
+ final PointerTracker tracker = PointerTracker.getPointerTracker(pointerId);
+ tracker.processMotionEvent(touchEvent, mKeyDetector);
+ touchEvent.recycle();
}
/**
- * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT on the previous key,
- * a HOVER_ENTER on the current key, and a HOVER_MOVE on the current key.
+ * Synthesize a touch event from a hover event.
*
- * @param currentKey The currently hovered key.
- * @param previousKey The previously hovered key.
- * @param event The event that triggered the transition.
- * @return {@code true} if the event was handled.
- */
- private boolean onTransitionKey(final Key currentKey, final Key previousKey,
- final MotionEvent event) {
- final int savedAction = event.getAction();
- event.setAction(MotionEvent.ACTION_HOVER_EXIT);
- onHoverKey(previousKey, event);
- event.setAction(MotionEvent.ACTION_HOVER_ENTER);
- onHoverKey(currentKey, event);
- event.setAction(MotionEvent.ACTION_HOVER_MOVE);
- final boolean handled = onHoverKey(currentKey, event);
- event.setAction(savedAction);
- return handled;
- }
-
- /**
- * Handles a hover event on a key. If {@link Key} extended View, this would be analogous to
- * calling View.onHoverEvent(MotionEvent).
+ * @param touchAction The action of the synthesizing touch event.
+ * @param hoverEvent The base hover event from that the touch event is synthesized.
+ * @return The synthesized touch event of <code>touchAction</code> that has pointer information
+ * of <code>event</code>.
+ */
+ protected static MotionEvent synthesizeTouchEvent(final int touchAction,
+ final MotionEvent hoverEvent) {
+ final MotionEvent touchEvent = MotionEvent.obtain(hoverEvent);
+ touchEvent.setAction(touchAction);
+ return touchEvent;
+ }
+
+ /**
+ * Handles a hover enter event on a key.
*
* @param key The currently hovered key.
- * @param event The hover event.
- * @return {@code true} if the event was handled.
*/
- private boolean onHoverKey(final Key key, final MotionEvent event) {
- // Null keys can't receive events.
- if (key == null) {
- return false;
+ protected void onHoverEnterTo(final Key key) {
+ if (DEBUG_HOVER) {
+ Log.d(TAG, "onHoverEnterTo: key=" + key);
}
+ key.onPressed();
+ mKeyboardView.invalidateKey(key);
final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
+ provider.sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
+ provider.performActionForKey(key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
+ }
- switch (event.getAction()) {
- case MotionEvent.ACTION_HOVER_ENTER:
- provider.sendAccessibilityEventForKey(
- key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
- provider.performActionForKey(
- key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- provider.sendAccessibilityEventForKey(
- key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
- break;
+ /**
+ * Handles a hover move event on a key.
+ *
+ * @param key The currently hovered key.
+ */
+ protected void onHoverMoveWithin(final Key key) { }
+
+ /**
+ * Handles a hover exit event on a key.
+ *
+ * @param key The currently hovered key.
+ */
+ protected void onHoverExitFrom(final Key key) {
+ if (DEBUG_HOVER) {
+ Log.d(TAG, "onHoverExitFrom: key=" + key);
}
- return true;
+ key.onReleased();
+ mKeyboardView.invalidateKey(key);
+ final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
+ provider.sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
}
}
diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
index cddd1c7ed..cb13483f2 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
@@ -47,9 +47,11 @@ import java.util.List;
* virtual views, thus conveying their logical structure.
* </p>
*/
-public final class KeyboardAccessibilityNodeProvider extends AccessibilityNodeProviderCompat {
+final class KeyboardAccessibilityNodeProvider extends AccessibilityNodeProviderCompat {
private static final String TAG = KeyboardAccessibilityNodeProvider.class.getSimpleName();
- private static final int UNDEFINED = Integer.MIN_VALUE;
+
+ // From {@link android.view.accessibility.AccessibilityNodeInfo#UNDEFINED_ITEM_ID}.
+ private static final int UNDEFINED = Integer.MAX_VALUE;
private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper;
private final AccessibilityUtils mAccessibilityUtils;
@@ -134,7 +136,7 @@ public final class KeyboardAccessibilityNodeProvider extends AccessibilityNodePr
event.setClassName(key.getClass().getName());
event.setContentDescription(keyDescription);
event.setEnabled(true);
- final AccessibilityRecordCompat record = new AccessibilityRecordCompat(event);
+ final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
record.setSource(mKeyboardView, virtualViewId);
return event;
}
@@ -167,22 +169,10 @@ public final class KeyboardAccessibilityNodeProvider extends AccessibilityNodePr
}
if (virtualViewId == View.NO_ID) {
// We are requested to create an AccessibilityNodeInfo describing
- // this View, i.e. the root of the virtual sub-tree.
+ // this View. Returning an empty info is sufficient for a keyboard.
final AccessibilityNodeInfoCompat rootInfo =
AccessibilityNodeInfoCompat.obtain(mKeyboardView);
ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, rootInfo);
-
- // Add the virtual children of the root View.
- final List<Key> sortedKeys = mKeyboard.getSortedKeys();
- final int size = sortedKeys.size();
- for (int index = 0; index < size; index++) {
- final Key key = sortedKeys.get(index);
- if (key.isSpacer()) {
- continue;
- }
- // Use an index of the sorted keys list as a virtual view id.
- rootInfo.addChild(mKeyboardView, index);
- }
return rootInfo;
}
@@ -229,7 +219,7 @@ public final class KeyboardAccessibilityNodeProvider extends AccessibilityNodePr
if (key == null) {
return false;
}
- return performActionForKey(key, action, arguments);
+ return performActionForKey(key, action);
}
/**
@@ -237,25 +227,16 @@ public final class KeyboardAccessibilityNodeProvider extends AccessibilityNodePr
*
* @param key The on which to perform the action.
* @param action The action to perform.
- * @param arguments The action's arguments.
* @return The result of performing the action, or false if the action is not supported.
*/
- boolean performActionForKey(final Key key, final int action, final Bundle arguments) {
- final int virtualViewId = getVirtualViewIdOf(key);
-
+ boolean performActionForKey(final Key key, final int action) {
switch (action) {
case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
- if (mAccessibilityFocusedView == virtualViewId) {
- return false;
- }
- mAccessibilityFocusedView = virtualViewId;
+ mAccessibilityFocusedView = getVirtualViewIdOf(key);
sendAccessibilityEventForKey(
key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
return true;
case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
- if (mAccessibilityFocusedView != virtualViewId) {
- return false;
- }
mAccessibilityFocusedView = UNDEFINED;
sendAccessibilityEventForKey(
key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
diff --git a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
index c114551c8..96f84dde9 100644
--- a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
+++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
@@ -17,9 +17,9 @@
package com.android.inputmethod.accessibility;
import android.content.Context;
+import android.graphics.Rect;
import android.os.SystemClock;
-import android.support.v4.view.accessibility.AccessibilityEventCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.util.Log;
import android.util.SparseIntArray;
import android.view.MotionEvent;
@@ -28,11 +28,19 @@ import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+/**
+ * This class represents a delegate that can be registered in {@link MainKeyboardView} to enhance
+ * accessibility support via composition rather via inheritance.
+ */
public final class MainKeyboardAccessibilityDelegate
- extends KeyboardAccessibilityDelegate<MainKeyboardView> {
+ extends KeyboardAccessibilityDelegate<MainKeyboardView>
+ implements AccessibilityLongPressTimer.LongPressTimerCallback {
+ private static final String TAG = MainKeyboardAccessibilityDelegate.class.getSimpleName();
+
/** Map of keyboard modes to resource IDs. */
private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray();
@@ -51,10 +59,16 @@ public final class MainKeyboardAccessibilityDelegate
/** The most recently set keyboard mode. */
private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
private static final int KEYBOARD_IS_HIDDEN = -1;
+ // The rectangle region to ignore hover events.
+ private final Rect mBoundsToIgnoreHoverEvent = new Rect();
+
+ private final AccessibilityLongPressTimer mAccessibilityLongPressTimer;
public MainKeyboardAccessibilityDelegate(final MainKeyboardView mainKeyboardView,
final KeyDetector keyDetector) {
super(mainKeyboardView, keyDetector);
+ mAccessibilityLongPressTimer = new AccessibilityLongPressTimer(
+ this /* callback */, mainKeyboardView.getContext());
}
/**
@@ -142,14 +156,28 @@ public final class MainKeyboardAccessibilityDelegate
case KeyboardId.ELEMENT_ALPHABET:
if (lastElementId == KeyboardId.ELEMENT_ALPHABET
|| lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
+ // Transition between alphabet mode and automatic shifted mode should be silently
+ // ignored because it can be determined by each key's talk back announce.
return;
}
resId = R.string.spoken_description_mode_alpha;
break;
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+ if (lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
+ // Resetting automatic shifted mode by pressing the shift key causes the transition
+ // from automatic shifted to manual shifted that should be silently ignored.
+ return;
+ }
resId = R.string.spoken_description_shiftmode_on;
break;
case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+ if (lastElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED) {
+ // Resetting caps locked mode by pressing the shift key causes the transition
+ // from shift locked to shift lock shifted that should be silently ignored.
+ return;
+ }
+ resId = R.string.spoken_description_shiftmode_locked;
+ break;
case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
resId = R.string.spoken_description_shiftmode_locked;
break;
@@ -168,17 +196,108 @@ public final class MainKeyboardAccessibilityDelegate
default:
return;
}
- final String text = mKeyboardView.getContext().getString(resId);
- sendWindowStateChanged(text);
+ sendWindowStateChanged(resId);
}
/**
* Announces that the keyboard has been hidden.
*/
private void announceKeyboardHidden() {
- final Context context = mKeyboardView.getContext();
- final String text = context.getString(R.string.announce_keyboard_hidden);
+ sendWindowStateChanged(R.string.announce_keyboard_hidden);
+ }
- sendWindowStateChanged(text);
+ @Override
+ protected void onRegisterHoverKey(final Key key, final MotionEvent event) {
+ final int x = key.getHitBox().centerX();
+ final int y = key.getHitBox().centerY();
+ if (DEBUG_HOVER) {
+ Log.d(TAG, "onRegisterHoverKey: key=" + key
+ + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y));
+ }
+ if (mBoundsToIgnoreHoverEvent.contains(x, y)) {
+ // This hover exit event points to the key that should be ignored.
+ // Clear the ignoring region to handle further hover events.
+ mBoundsToIgnoreHoverEvent.setEmpty();
+ return;
+ }
+ super.onRegisterHoverKey(key, event);
+ }
+
+ @Override
+ protected void onHoverEnterTo(final Key key) {
+ final int x = key.getHitBox().centerX();
+ final int y = key.getHitBox().centerY();
+ if (DEBUG_HOVER) {
+ Log.d(TAG, "onHoverEnterTo: key=" + key
+ + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y));
+ }
+ mAccessibilityLongPressTimer.cancelLongPress();
+ if (mBoundsToIgnoreHoverEvent.contains(x, y)) {
+ return;
+ }
+ // This hover enter event points to the key that isn't in the ignoring region.
+ // Further hover events should be handled.
+ mBoundsToIgnoreHoverEvent.setEmpty();
+ super.onHoverEnterTo(key);
+ if (key.isLongPressEnabled()) {
+ mAccessibilityLongPressTimer.startLongPress(key);
+ }
+ }
+
+ @Override
+ protected void onHoverExitFrom(final Key key) {
+ final int x = key.getHitBox().centerX();
+ final int y = key.getHitBox().centerY();
+ if (DEBUG_HOVER) {
+ Log.d(TAG, "onHoverExitFrom: key=" + key
+ + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y));
+ }
+ mAccessibilityLongPressTimer.cancelLongPress();
+ super.onHoverExitFrom(key);
+ }
+
+ @Override
+ public void onLongPressed(final Key key) {
+ if (DEBUG_HOVER) {
+ Log.d(TAG, "onLongPressed: key=" + key);
+ }
+ final PointerTracker tracker = PointerTracker.getPointerTracker(HOVER_EVENT_POINTER_ID);
+ final long eventTime = SystemClock.uptimeMillis();
+ final int x = key.getHitBox().centerX();
+ final int y = key.getHitBox().centerY();
+ final MotionEvent downEvent = MotionEvent.obtain(
+ eventTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0 /* metaState */);
+ // Inject a fake down event to {@link PointerTracker} to handle a long press correctly.
+ tracker.processMotionEvent(downEvent, mKeyDetector);
+ // The above fake down event triggers an unnecessary long press timer that should be
+ // canceled.
+ tracker.cancelLongPressTimer();
+ downEvent.recycle();
+ // Invoke {@link MainKeyboardView#onLongPress(PointerTracker)} as if a long press timeout
+ // has passed.
+ mKeyboardView.onLongPress(tracker);
+ // If {@link Key#hasNoPanelAutoMoreKeys()} is true (such as "0 +" key on the phone layout)
+ // or a key invokes IME switcher dialog, we should just ignore the next
+ // {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether
+ // {@link PointerTracker} is in operation or not.
+ if (tracker.isInOperation()) {
+ // This long press shows a more keys keyboard and further hover events should be
+ // handled.
+ mBoundsToIgnoreHoverEvent.setEmpty();
+ return;
+ }
+ // This long press has handled at {@link MainKeyboardView#onLongPress(PointerTracker)}.
+ // We should ignore further hover events on this key.
+ mBoundsToIgnoreHoverEvent.set(key.getHitBox());
+ if (key.hasNoPanelAutoMoreKey()) {
+ // This long press has registered a code point without showing a more keys keyboard.
+ // We should talk back the code point if possible.
+ final int codePointOfNoPanelAutoMoreKey = key.getMoreKeys()[0].mCode;
+ final String text = KeyCodeDescriptionMapper.getInstance().getDescriptionForCodePoint(
+ mKeyboardView.getContext(), codePointOfNoPanelAutoMoreKey);
+ if (text != null) {
+ sendWindowStateChanged(text);
+ }
+ }
}
}
diff --git a/java/src/com/android/inputmethod/accessibility/MoreKeysKeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MoreKeysKeyboardAccessibilityDelegate.java
new file mode 100644
index 000000000..4022da343
--- /dev/null
+++ b/java/src/com/android/inputmethod/accessibility/MoreKeysKeyboardAccessibilityDelegate.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.accessibility;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.MoreKeysKeyboardView;
+import com.android.inputmethod.keyboard.PointerTracker;
+
+/**
+ * This class represents a delegate that can be registered in {@link MoreKeysKeyboardView} to
+ * enhance accessibility support via composition rather via inheritance.
+ */
+public class MoreKeysKeyboardAccessibilityDelegate
+ extends KeyboardAccessibilityDelegate<MoreKeysKeyboardView> {
+ private static final String TAG = MoreKeysKeyboardAccessibilityDelegate.class.getSimpleName();
+
+ private final Rect mMoreKeysKeyboardValidBounds = new Rect();
+ private static final int CLOSING_INSET_IN_PIXEL = 1;
+ private int mOpenAnnounceResId;
+ private int mCloseAnnounceResId;
+
+ public MoreKeysKeyboardAccessibilityDelegate(final MoreKeysKeyboardView moreKeysKeyboardView,
+ final KeyDetector keyDetector) {
+ super(moreKeysKeyboardView, keyDetector);
+ }
+
+ public void setOpenAnnounce(final int resId) {
+ mOpenAnnounceResId = resId;
+ }
+
+ public void setCloseAnnounce(final int resId) {
+ mCloseAnnounceResId = resId;
+ }
+
+ public void onShowMoreKeysKeyboard() {
+ sendWindowStateChanged(mOpenAnnounceResId);
+ }
+
+ public void onDismissMoreKeysKeyboard() {
+ sendWindowStateChanged(mCloseAnnounceResId);
+ }
+
+ @Override
+ protected void onHoverEnter(final MotionEvent event) {
+ if (DEBUG_HOVER) {
+ Log.d(TAG, "onHoverEnter: key=" + getHoverKeyOf(event));
+ }
+ super.onHoverEnter(event);
+ final int actionIndex = event.getActionIndex();
+ final int x = (int)event.getX(actionIndex);
+ final int y = (int)event.getY(actionIndex);
+ final int pointerId = event.getPointerId(actionIndex);
+ final long eventTime = event.getEventTime();
+ mKeyboardView.onDownEvent(x, y, pointerId, eventTime);
+ }
+
+ @Override
+ protected void onHoverMove(final MotionEvent event) {
+ super.onHoverMove(event);
+ final int actionIndex = event.getActionIndex();
+ final int x = (int)event.getX(actionIndex);
+ final int y = (int)event.getY(actionIndex);
+ final int pointerId = event.getPointerId(actionIndex);
+ final long eventTime = event.getEventTime();
+ mKeyboardView.onMoveEvent(x, y, pointerId, eventTime);
+ }
+
+ @Override
+ protected void onHoverExit(final MotionEvent event) {
+ final Key lastKey = getLastHoverKey();
+ if (DEBUG_HOVER) {
+ Log.d(TAG, "onHoverExit: key=" + getHoverKeyOf(event) + " last=" + lastKey);
+ }
+ if (lastKey != null) {
+ super.onHoverExitFrom(lastKey);
+ }
+ setLastHoverKey(null);
+ final int actionIndex = event.getActionIndex();
+ final int x = (int)event.getX(actionIndex);
+ final int y = (int)event.getY(actionIndex);
+ final int pointerId = event.getPointerId(actionIndex);
+ final long eventTime = event.getEventTime();
+ // A hover exit event at one pixel width or height area on the edges of more keys keyboard
+ // are treated as closing.
+ mMoreKeysKeyboardValidBounds.set(0, 0, mKeyboardView.getWidth(), mKeyboardView.getHeight());
+ mMoreKeysKeyboardValidBounds.inset(CLOSING_INSET_IN_PIXEL, CLOSING_INSET_IN_PIXEL);
+ if (mMoreKeysKeyboardValidBounds.contains(x, y)) {
+ // Invoke {@link MoreKeysKeyboardView#onUpEvent(int,int,int,long)} as if this hover
+ // exit event selects a key.
+ mKeyboardView.onUpEvent(x, y, pointerId, eventTime);
+ // TODO: Should fix this reference. This is a hack to clear the state of
+ // {@link PointerTracker}.
+ PointerTracker.dismissAllMoreKeysPanels();
+ return;
+ }
+ // Close the more keys keyboard.
+ // TODO: Should fix this reference. This is a hack to clear the state of
+ // {@link PointerTracker}.
+ PointerTracker.dismissAllMoreKeysPanels();
+ }
+}
diff --git a/java/src/com/android/inputmethod/accessibility/MoreSuggestionsAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MoreSuggestionsAccessibilityDelegate.java
new file mode 100644
index 000000000..dfc866113
--- /dev/null
+++ b/java/src/com/android/inputmethod/accessibility/MoreSuggestionsAccessibilityDelegate.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.accessibility;
+
+import android.view.MotionEvent;
+
+import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.MoreKeysKeyboardView;
+
+public final class MoreSuggestionsAccessibilityDelegate
+ extends MoreKeysKeyboardAccessibilityDelegate {
+ public MoreSuggestionsAccessibilityDelegate(final MoreKeysKeyboardView moreKeysKeyboardView,
+ final KeyDetector keyDetector) {
+ super(moreKeysKeyboardView, keyDetector);
+ }
+
+ @Override
+ protected void simulateTouchEvent(final int touchAction, final MotionEvent hoverEvent) {
+ final MotionEvent touchEvent = synthesizeTouchEvent(touchAction, hoverEvent);
+ mKeyboardView.onTouchEvent(touchEvent);
+ touchEvent.recycle();
+ }
+}
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 60f7e2def..4d51821f2 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -27,7 +27,6 @@ import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -73,13 +72,13 @@ public final class SuggestionSpanUtils {
return pickedWord;
}
- final ArrayList<String> suggestionsList = CollectionUtils.newArrayList();
+ final ArrayList<String> suggestionsList = new ArrayList<>();
for (int i = 0; i < suggestedWords.size(); ++i) {
if (suggestionsList.size() >= SuggestionSpan.SUGGESTIONS_MAX_SIZE) {
break;
}
final SuggestedWordInfo info = suggestedWords.getInfo(i);
- if (info.mKind == SuggestedWordInfo.KIND_PREDICTION) {
+ if (info.isKindOf(SuggestedWordInfo.KIND_PREDICTION)) {
continue;
}
final String word = suggestedWords.getWord(i);
diff --git a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
index dec739d39..767cc423d 100644
--- a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
@@ -24,23 +24,13 @@ import java.lang.reflect.Method;
// Currently {@link #getPaddingEnd(View)} and {@link #setPaddingRelative(View,int,int,int,int)}
// are missing from android-support-v4 static library in KitKat SDK.
public final class ViewCompatUtils {
- // Note that View.LAYOUT_DIRECTION_LTR and View.LAYOUT_DIRECTION_RTL have been introduced in
- // API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1).
- public static final int LAYOUT_DIRECTION_LTR = (Integer)CompatUtils.getFieldValue(null, 0x0,
- CompatUtils.getField(View.class, "LAYOUT_DIRECTION_LTR"));
- public static final int LAYOUT_DIRECTION_RTL = (Integer)CompatUtils.getFieldValue(null, 0x1,
- CompatUtils.getField(View.class, "LAYOUT_DIRECTION_RTL"));
-
- // Note that View.getPaddingEnd(), View.setPaddingRelative(int,int,int,int), and
- // View.getLayoutDirection() have been introduced in API level 17
- // (Build.VERSION_CODE.JELLY_BEAN_MR1).
+ // Note that View.getPaddingEnd(), View.setPaddingRelative(int,int,int,int) have been
+ // introduced in API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1).
private static final Method METHOD_getPaddingEnd = CompatUtils.getMethod(
View.class, "getPaddingEnd");
private static final Method METHOD_setPaddingRelative = CompatUtils.getMethod(
View.class, "setPaddingRelative",
Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE);
- private static final Method METHOD_getLayoutDirection = CompatUtils.getMethod(
- View.class, "getLayoutDirection");
private ViewCompatUtils() {
// This utility class is not publicly instantiable.
@@ -61,11 +51,4 @@ public final class ViewCompatUtils {
}
CompatUtils.invoke(view, null, METHOD_setPaddingRelative, start, top, end, bottom);
}
-
- public static int getLayoutDirection(final View view) {
- if (METHOD_getLayoutDirection == null) {
- return LAYOUT_DIRECTION_LTR;
- }
- return (Integer)CompatUtils.invoke(view, 0, METHOD_getLayoutDirection);
- }
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index 3f69cedee..3d294acd7 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -16,7 +16,6 @@
package com.android.inputmethod.dictionarypack;
-import android.app.DownloadManager;
import android.app.DownloadManager.Request;
import android.content.ContentValues;
import android.content.Context;
@@ -600,7 +599,7 @@ public final class ActionBatch {
private final Queue<Action> mActions;
public ActionBatch() {
- mActions = new LinkedList<Action>();
+ mActions = new LinkedList<>();
}
public void add(final Action a) {
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
index 2623eff56..1d84e5888 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
@@ -29,7 +29,6 @@ import android.view.View;
import android.widget.ProgressBar;
public class DictionaryDownloadProgressBar extends ProgressBar {
- @SuppressWarnings("unused")
private static final String TAG = DictionaryDownloadProgressBar.class.getSimpleName();
private static final int NOT_A_DOWNLOADMANAGER_PENDING_ID = 0;
@@ -119,7 +118,6 @@ public class DictionaryDownloadProgressBar extends ProgressBar {
try {
final UpdateHelper updateHelper = new UpdateHelper();
final Query query = new Query().setFilterById(mId);
- int lastProgress = 0;
setIndeterminate(true);
while (!isInterrupted()) {
final Cursor cursor = mDownloadManagerWrapper.query(query);
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
index 13c07de35..8e026171d 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
@@ -18,8 +18,6 @@ package com.android.inputmethod.dictionarypack;
import android.view.View;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
import java.util.ArrayList;
import java.util.HashMap;
@@ -39,8 +37,8 @@ public class DictionaryListInterfaceState {
public int mStatus = MetadataDbHelper.STATUS_UNKNOWN;
}
- private HashMap<String, State> mWordlistToState = CollectionUtils.newHashMap();
- private ArrayList<View> mViewCache = CollectionUtils.newArrayList();
+ private HashMap<String, State> mWordlistToState = new HashMap<>();
+ private ArrayList<View> mViewCache = new ArrayList<>();
public boolean isOpen(final String wordlistId) {
final State state = mWordlistToState.get(wordlistId);
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
index c35995b24..f5bd84c8c 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
@@ -357,7 +357,7 @@ public final class DictionaryProvider extends ContentProvider {
return Collections.<WordListInfo>emptyList();
}
try {
- final HashMap<String, WordListInfo> dicts = new HashMap<String, WordListInfo>();
+ final HashMap<String, WordListInfo> dicts = new HashMap<>();
final int idIndex = results.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN);
final int localeIndex = results.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
final int localFileNameIndex =
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index dae2f22a4..11982fa65 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -33,13 +33,13 @@ import android.preference.PreferenceGroup;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
-import android.view.animation.AnimationUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
import com.android.inputmethod.latin.R;
@@ -67,8 +67,8 @@ public final class DictionarySettingsFragment extends PreferenceFragment
private boolean mChangedSettings;
private DictionaryListInterfaceState mDictionaryListInterfaceState =
new DictionaryListInterfaceState();
- private TreeMap<String, WordListPreference> mCurrentPreferenceMap =
- new TreeMap<String, WordListPreference>(); // never null
+ // never null
+ private TreeMap<String, WordListPreference> mCurrentPreferenceMap = new TreeMap<>();
private final BroadcastReceiver mConnectivityChangedReceiver = new BroadcastReceiver() {
@Override
@@ -280,19 +280,18 @@ public final class DictionarySettingsFragment extends PreferenceFragment
: activity.getContentResolver().query(contentUri, null, null, null, null);
if (null == cursor) {
- final ArrayList<Preference> result = new ArrayList<Preference>();
+ final ArrayList<Preference> result = new ArrayList<>();
result.add(createErrorMessage(activity, R.string.cannot_connect_to_dict_service));
return result;
}
try {
if (!cursor.moveToFirst()) {
- final ArrayList<Preference> result = new ArrayList<Preference>();
+ final ArrayList<Preference> result = new ArrayList<>();
result.add(createErrorMessage(activity, R.string.no_dictionaries_available));
return result;
} else {
final String systemLocaleString = Locale.getDefault().toString();
- final TreeMap<String, WordListPreference> prefMap =
- new TreeMap<String, WordListPreference>();
+ final TreeMap<String, WordListPreference> prefMap = new TreeMap<>();
final int idIndex = cursor.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN);
final int versionIndex = cursor.getColumnIndex(MetadataDbHelper.VERSION_COLUMN);
final int localeIndex = cursor.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
diff --git a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
index 77f67b8a3..4f0805c5c 100644
--- a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
@@ -175,7 +175,7 @@ public final class LocaleUtils {
return saveLocale;
}
- private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>();
+ private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();
/**
* Creates a locale from a string specification.
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
index 668eb925b..17dd781d5 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -47,7 +47,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// used to identify the versions for upgrades. This should never change going forward.
private static final int METADATA_DATABASE_VERSION_WITH_CLIENTID = 6;
// The current database version.
- private static final int CURRENT_METADATA_DATABASE_VERSION = 8;
+ private static final int CURRENT_METADATA_DATABASE_VERSION = 9;
private final static long NOT_A_DOWNLOAD_ID = -1;
@@ -160,7 +160,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// this legacy database. New clients should make sure to always pass a client ID so as
// to avoid conflicts.
final String clientId = null != clientIdOrNull ? clientIdOrNull : "";
- if (null == sInstanceMap) sInstanceMap = new TreeMap<String, MetadataDbHelper>();
+ if (null == sInstanceMap) sInstanceMap = new TreeMap<>();
MetadataDbHelper helper = sInstanceMap.get(clientId);
if (null == helper) {
helper = new MetadataDbHelper(context, clientId);
@@ -639,7 +639,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
public static ArrayList<DownloadRecord> getDownloadRecordsForDownloadId(final Context context,
final long downloadId) {
final SQLiteDatabase defaultDb = getDb(context, "");
- final ArrayList<DownloadRecord> results = new ArrayList<DownloadRecord>();
+ final ArrayList<DownloadRecord> results = new ArrayList<>();
final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME, CLIENT_TABLE_COLUMNS,
null, null, null, null, null);
try {
@@ -923,7 +923,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// - Remove the old entry from the table
// - Erase the old file
// We start by gathering the names of the files we should delete.
- final List<String> filenames = new LinkedList<String>();
+ final List<String> filenames = new LinkedList<>();
final Cursor c = db.query(METADATA_TABLE_NAME,
new String[] { LOCAL_FILENAME_COLUMN },
LOCALE_COLUMN + " = ? AND " +
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
index 63e419871..d66b69050 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
@@ -43,7 +43,7 @@ public class MetadataHandler {
* @return the constructed list of wordlist metadata.
*/
private static List<WordListMetadata> makeMetadataObject(final Cursor results) {
- final ArrayList<WordListMetadata> buildingMetadata = new ArrayList<WordListMetadata>();
+ final ArrayList<WordListMetadata> buildingMetadata = new ArrayList<>();
if (null != results && results.moveToFirst()) {
final int localeColumn = results.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
final int typeColumn = results.getColumnIndex(MetadataDbHelper.TYPE_COLUMN);
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java b/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
index a88173e8e..52290cadc 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
@@ -52,7 +52,7 @@ public class MetadataParser {
*/
private static WordListMetadata parseOneWordList(final JsonReader reader)
throws IOException, BadFormatException {
- final TreeMap<String, String> arguments = new TreeMap<String, String>();
+ final TreeMap<String, String> arguments = new TreeMap<>();
reader.beginObject();
while (reader.hasNext()) {
final String name = reader.nextName();
@@ -100,7 +100,7 @@ public class MetadataParser {
public static List<WordListMetadata> parseMetadata(final InputStreamReader input)
throws IOException, BadFormatException {
JsonReader reader = new JsonReader(input);
- final ArrayList<WordListMetadata> readInfo = new ArrayList<WordListMetadata>();
+ final ArrayList<WordListMetadata> readInfo = new ArrayList<>();
reader.beginArray();
while (reader.hasNext()) {
final WordListMetadata thisMetadata = parseOneWordList(reader);
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index dcff490db..95a094232 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -177,7 +177,7 @@ public final class UpdateHandler {
*/
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 TreeSet<String> uris = new TreeSet<>();
final Cursor cursor = MetadataDbHelper.queryClientIds(context);
if (null == cursor) return false;
try {
@@ -557,7 +557,7 @@ public final class UpdateHandler {
// Instantiation of a parameterized type is not possible in Java, so it's not possible to
// return the same type of list that was passed - probably the same reason why Collections
// does not do it. So we need to decide statically which concrete type to return.
- return new LinkedList<T>(src);
+ return new LinkedList<>(src);
}
/**
@@ -740,10 +740,10 @@ public final class UpdateHandler {
final ActionBatch actions = new ActionBatch();
// Upgrade existing word lists
DebugLogUtils.l("Comparing dictionaries");
- final Set<String> wordListIds = new TreeSet<String>();
+ final Set<String> wordListIds = new TreeSet<>();
// TODO: Can these be null?
- if (null == from) from = new ArrayList<WordListMetadata>();
- if (null == to) to = new ArrayList<WordListMetadata>();
+ if (null == from) from = new ArrayList<>();
+ if (null == to) to = new ArrayList<>();
for (WordListMetadata wlData : from) wordListIds.add(wlData.mId);
for (WordListMetadata wlData : to) wordListIds.add(wlData.mId);
for (String id : wordListIds) {
diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java
index 9e7f04d4f..61bc11b39 100644
--- a/java/src/com/android/inputmethod/event/CombinerChain.java
+++ b/java/src/com/android/inputmethod/event/CombinerChain.java
@@ -20,7 +20,6 @@ import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.HashMap;
@@ -44,8 +43,8 @@ public class CombinerChain {
private SpannableStringBuilder mStateFeedback;
private final ArrayList<Combiner> mCombiners;
- private static final HashMap<String, Class> IMPLEMENTED_COMBINERS
- = new HashMap<String, Class>();
+ private static final HashMap<String, Class<? extends Combiner>> IMPLEMENTED_COMBINERS =
+ new HashMap<>();
static {
IMPLEMENTED_COMBINERS.put("MyanmarReordering", MyanmarReordering.class);
}
@@ -63,7 +62,7 @@ public class CombinerChain {
* @param combinerList A list of combiners to be applied in order.
*/
public CombinerChain(final String initialText, final Combiner... combinerList) {
- mCombiners = CollectionUtils.newArrayList();
+ mCombiners = new ArrayList<>();
// The dead key combiner is always active, and always first
mCombiners.add(new DeadKeyCombiner());
for (final Combiner combiner : combinerList) {
@@ -87,7 +86,7 @@ public class CombinerChain {
* @param newEvent the new event to process
*/
public void processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {
- final ArrayList<Event> modifiablePreviousEvents = new ArrayList<Event>(previousEvents);
+ final ArrayList<Event> modifiablePreviousEvents = new ArrayList<>(previousEvents);
Event event = newEvent;
for (final Combiner combiner : mCombiners) {
// A combiner can never return more than one event; it can return several
@@ -136,12 +135,13 @@ public class CombinerChain {
final Combiner[] combiners = new Combiner[combinerDescriptors.length];
int i = 0;
for (final String combinerDescriptor : combinerDescriptors) {
- final Class combinerClass = IMPLEMENTED_COMBINERS.get(combinerDescriptor);
+ final Class<? extends Combiner> combinerClass =
+ IMPLEMENTED_COMBINERS.get(combinerDescriptor);
if (null == combinerClass) {
throw new RuntimeException("Unknown combiner descriptor: " + combinerDescriptor);
}
try {
- combiners[i++] = (Combiner)combinerClass.newInstance();
+ combiners[i++] = combinerClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor,
e);
diff --git a/java/src/com/android/inputmethod/event/MyanmarReordering.java b/java/src/com/android/inputmethod/event/MyanmarReordering.java
index da0228bd2..32919932d 100644
--- a/java/src/com/android/inputmethod/event/MyanmarReordering.java
+++ b/java/src/com/android/inputmethod/event/MyanmarReordering.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.event;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -32,7 +31,7 @@ public class MyanmarReordering implements Combiner {
// U+200B ZERO WIDTH SPACE
private final static int ZERO_WIDTH_NON_JOINER = 0x200B; // should be 0x200C
- private final ArrayList<Event> mCurrentEvents = CollectionUtils.newArrayList();
+ private final ArrayList<Event> mCurrentEvents = new ArrayList<>();
// List of consonants :
// U+1000 MYANMAR LETTER KA
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index cf68c565d..88cde1111 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -195,7 +195,8 @@ public class Key implements Comparable<Key> {
mHintLabel = hintLabel;
mLabelFlags = labelFlags;
mBackgroundType = backgroundType;
- mActionFlags = 0;
+ // TODO: Pass keyActionFlags as an argument.
+ mActionFlags = ACTION_FLAGS_NO_KEY_PREVIEW;
mMoreKeys = null;
mMoreKeysColumnAndFlags = 0;
mLabel = label;
@@ -467,15 +468,24 @@ public class Key implements Comparable<Key> {
@Override
public String toString() {
- final String label;
- if (StringUtils.codePointCount(mLabel) == 1 && mLabel.codePointAt(0) == mCode) {
- label = "";
- } else {
- label = "/" + mLabel;
+ return toShortString() + " " + getX() + "," + getY() + " " + getWidth() + "x" + getHeight();
+ }
+
+ public String toShortString() {
+ final int code = getCode();
+ if (code == Constants.CODE_OUTPUT_TEXT) {
+ return getOutputText();
}
- 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));
+ return Constants.printableCode(code);
+ }
+
+ public String toLongString() {
+ final int iconId = getIconId();
+ final String topVisual = (iconId == KeyboardIconsSet.ICON_UNDEFINED)
+ ? KeyboardIconsSet.PREFIX_ICON + KeyboardIconsSet.getIconName(iconId) : getLabel();
+ final String hintLabel = getHintLabel();
+ final String visual = (hintLabel == null) ? topVisual : topVisual + "^" + hintLabel;
+ return toString() + " " + visual + "/" + backgroundName(mBackgroundType);
}
private static String backgroundName(final int backgroundType) {
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index f646a03c2..85dfea4e7 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -22,9 +22,9 @@ import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -83,7 +83,7 @@ public class Keyboard {
public final List<Key> mAltCodeKeysWhileTyping;
public final KeyboardIconsSet mIconsSet;
- private final SparseArray<Key> mKeyCache = CollectionUtils.newSparseArray();
+ private final SparseArray<Key> mKeyCache = new SparseArray<>();
private final ProximityInfo mProximityInfo;
private final boolean mProximityCharsCorrectionEnabled;
@@ -103,8 +103,7 @@ public class Keyboard {
mTopPadding = params.mTopPadding;
mVerticalGap = params.mVerticalGap;
- mSortedKeys = Collections.unmodifiableList(
- CollectionUtils.newArrayList(params.mSortedKeys));
+ mSortedKeys = Collections.unmodifiableList(new ArrayList<>(params.mSortedKeys));
mShiftKeys = Collections.unmodifiableList(params.mShiftKeys);
mAltCodeKeysWhileTyping = Collections.unmodifiableList(params.mAltCodeKeysWhileTyping);
mIconsSet = params.mIconsSet;
@@ -159,7 +158,7 @@ public class Keyboard {
/**
* Return the sorted list of keys of this keyboard.
* The keys are sorted from top-left to bottom-right order.
- * The list may contain {@link Spacer} object as well.
+ * The list may contain {@link Key.Spacer} object as well.
* @return the sorted unmodifiable list of {@link Key}s of this keyboard.
*/
public List<Key> getSortedKeys() {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 93a55fe6a..3c1167538 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -70,7 +70,6 @@ public final class KeyboardId {
public final int mElementId;
public final EditorInfo mEditorInfo;
public final boolean mClobberSettingsKey;
- public final boolean mSupportsSwitchingToShortcutIme;
public final boolean mLanguageSwitchKeyEnabled;
public final String mCustomActionLabel;
public final boolean mHasShortcutKey;
@@ -86,11 +85,10 @@ public final class KeyboardId {
mElementId = elementId;
mEditorInfo = params.mEditorInfo;
mClobberSettingsKey = params.mNoSettingsKey;
- mSupportsSwitchingToShortcutIme = params.mSupportsSwitchingToShortcutIme;
mLanguageSwitchKeyEnabled = params.mLanguageSwitchKeyEnabled;
mCustomActionLabel = (mEditorInfo.actionLabel != null)
? mEditorInfo.actionLabel.toString() : null;
- mHasShortcutKey = mSupportsSwitchingToShortcutIme && params.mShowsVoiceInputKey;
+ mHasShortcutKey = params.mVoiceInputKeyEnabled;
mHashCode = computeHashCode(this);
}
@@ -103,7 +101,6 @@ public final class KeyboardId {
id.mHeight,
id.passwordInput(),
id.mClobberSettingsKey,
- id.mSupportsSwitchingToShortcutIme,
id.mHasShortcutKey,
id.mLanguageSwitchKeyEnabled,
id.isMultiLine(),
@@ -124,7 +121,6 @@ public final class KeyboardId {
&& other.mHeight == mHeight
&& other.passwordInput() == passwordInput()
&& other.mClobberSettingsKey == mClobberSettingsKey
- && other.mSupportsSwitchingToShortcutIme == mSupportsSwitchingToShortcutIme
&& other.mHasShortcutKey == mHasShortcutKey
&& other.mLanguageSwitchKeyEnabled == mLanguageSwitchKeyEnabled
&& other.isMultiLine() == isMultiLine()
@@ -179,7 +175,7 @@ public final class KeyboardId {
@Override
public String toString() {
- return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s%s]",
+ return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s]",
elementIdToName(mElementId),
mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
mWidth, mHeight,
@@ -189,7 +185,6 @@ public final class KeyboardId {
(navigatePrevious() ? " navigatePrevious" : ""),
(mClobberSettingsKey ? " clobberSettingsKey" : ""),
(passwordInput() ? " passwordInput" : ""),
- (mSupportsSwitchingToShortcutIme ? " supportsSwitchingToShortcutIme" : ""),
(mHasShortcutKey ? " hasShortcutKey" : ""),
(mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
(isMultiLine() ? " isMultiLine" : "")
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index cde5091c4..3e5cfc11a 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -17,8 +17,6 @@
package com.android.inputmethod.keyboard;
import static com.android.inputmethod.latin.Constants.ImeOption.FORCE_ASCII;
-import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE;
-import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT;
import static com.android.inputmethod.latin.Constants.ImeOption.NO_SETTINGS_KEY;
import android.content.Context;
@@ -41,7 +39,6 @@ import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.latin.utils.XmlParseUtils;
@@ -81,7 +78,7 @@ public final class KeyboardLayoutSet {
// 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();
+ new HashMap<>();
private static final KeysCache sKeysCache = new KeysCache();
@SuppressWarnings("serial")
@@ -103,12 +100,11 @@ public final class KeyboardLayoutSet {
public static final class Params {
String mKeyboardLayoutSetName;
int mMode;
- EditorInfo mEditorInfo;
boolean mDisableTouchPositionCorrectionDataForTest;
+ // TODO: Use {@link InputAttributes} instead of these variables.
+ EditorInfo mEditorInfo;
boolean mIsPasswordField;
- boolean mSupportsSwitchingToShortcutIme;
- boolean mShowsVoiceInputKey;
- boolean mNoMicrophoneKey;
+ boolean mVoiceInputKeyEnabled;
boolean mNoSettingsKey;
boolean mLanguageSwitchKeyEnabled;
InputMethodSubtype mSubtype;
@@ -117,7 +113,7 @@ public final class KeyboardLayoutSet {
int mKeyboardHeight;
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
- CollectionUtils.newSparseArray();
+ new SparseArray<>();
}
public static void clearKeyboardCache() {
@@ -181,7 +177,7 @@ public final class KeyboardLayoutSet {
}
final KeyboardBuilder<KeyboardParams> builder =
- new KeyboardBuilder<KeyboardParams>(mContext, new KeyboardParams());
+ new KeyboardBuilder<>(mContext, new KeyboardParams());
if (id.isAlphabetKeyboard()) {
builder.setAutoGenerate(sKeysCache);
}
@@ -192,7 +188,7 @@ public final class KeyboardLayoutSet {
}
builder.setProximityCharsCorrectionEnabled(elementParams.mProximityCharsCorrectionEnabled);
final Keyboard keyboard = builder.build();
- sKeyboardCache.put(id, new SoftReference<Keyboard>(keyboard));
+ sKeyboardCache.put(id, new SoftReference<>(keyboard));
if ((id.mElementId == KeyboardId.ELEMENT_ALPHABET
|| id.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)
&& !mParams.mIsSpellChecker) {
@@ -229,14 +225,9 @@ public final class KeyboardLayoutSet {
final EditorInfo editorInfo = (ei != null) ? ei : EMPTY_EDITOR_INFO;
params.mMode = getKeyboardMode(editorInfo);
+ // TODO: Consolidate those with {@link InputAttributes}.
params.mEditorInfo = editorInfo;
params.mIsPasswordField = InputTypeUtils.isPasswordInputType(editorInfo.inputType);
- @SuppressWarnings("deprecation")
- final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
- null, NO_MICROPHONE_COMPAT, editorInfo);
- params.mNoMicrophoneKey = InputAttributes.inPrivateImeOptions(
- mPackageName, NO_MICROPHONE, editorInfo)
- || deprecatedNoMicrophone;
params.mNoSettingsKey = InputAttributes.inPrivateImeOptions(
mPackageName, NO_SETTINGS_KEY, editorInfo);
}
@@ -249,6 +240,7 @@ public final class KeyboardLayoutSet {
public Builder setSubtype(final InputMethodSubtype subtype) {
final boolean asciiCapable = InputMethodSubtypeCompatUtils.isAsciiCapable(subtype);
+ // TODO: Consolidate with {@link InputAttributes}.
@SuppressWarnings("deprecation")
final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions(
mPackageName, FORCE_ASCII, mParams.mEditorInfo);
@@ -269,12 +261,13 @@ public final class KeyboardLayoutSet {
return this;
}
- public Builder setOptions(final boolean isShortcutImeEnabled,
- final boolean showsVoiceInputKey, final boolean languageSwitchKeyEnabled) {
- mParams.mSupportsSwitchingToShortcutIme =
- isShortcutImeEnabled && !mParams.mNoMicrophoneKey && !mParams.mIsPasswordField;
- mParams.mShowsVoiceInputKey = showsVoiceInputKey;
- mParams.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled;
+ public Builder setVoiceInputKeyEnabled(final boolean enabled) {
+ mParams.mVoiceInputKeyEnabled = enabled;
+ return this;
+ }
+
+ public Builder setLanguageSwitchKeyEnabled(final boolean enabled) {
+ mParams.mLanguageSwitchKeyEnabled = enabled;
return this;
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 61d51d1c9..6aeff189f 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -33,7 +33,6 @@ import com.android.inputmethod.keyboard.internal.KeyboardState;
import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
import com.android.inputmethod.latin.InputView;
import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
import com.android.inputmethod.latin.SubtypeSwitcher;
@@ -116,10 +115,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
- builder.setOptions(
- mSubtypeSwitcher.isShortcutImeEnabled(),
- settingsValues.mShowsVoiceInputKey,
- mLatinIME.shouldShowLanguageSwitchKey());
+ builder.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey);
+ builder.setLanguageSwitchKeyEnabled(mLatinIME.shouldShowLanguageSwitchKey());
mKeyboardLayoutSet = builder.build();
mCurrentSettingsValues = settingsValues;
try {
@@ -127,7 +124,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
mKeyboardTextsSet.setLocale(mSubtypeSwitcher.getCurrentSubtypeLocale(), mThemeContext);
} catch (KeyboardLayoutSetException e) {
Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
- LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
return;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
index 7a8c0c80b..7b41dfef6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
@@ -21,46 +21,43 @@ import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.util.Log;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.R;
import java.util.Arrays;
-import java.util.Comparator;
-public final class KeyboardTheme {
+public final class KeyboardTheme implements Comparable<KeyboardTheme> {
private static final String TAG = KeyboardTheme.class.getSimpleName();
static final String KLP_KEYBOARD_THEME_KEY = "pref_keyboard_layout_20110916";
static final String LXX_KEYBOARD_THEME_KEY = "pref_keyboard_theme_20140509";
- static final int THEME_ID_ICS = 0;
- static final int THEME_ID_KLP = 2;
- static final int THEME_ID_LXX_DARK = 3;
- static final int DEFAULT_THEME_ID = THEME_ID_KLP;
+ public static final int THEME_ID_ICS = 0;
+ public static final int THEME_ID_KLP = 2;
+ public static final int THEME_ID_LXX_DARK = 3;
+ public static final int DEFAULT_THEME_ID = THEME_ID_KLP;
private static final KeyboardTheme[] KEYBOARD_THEMES = {
new KeyboardTheme(THEME_ID_ICS, R.style.KeyboardTheme_ICS,
+ // This has never been selected because we support ICS or later.
VERSION_CODES.BASE),
new KeyboardTheme(THEME_ID_KLP, R.style.KeyboardTheme_KLP,
+ // Default theme for ICS, JB, and KLP.
VERSION_CODES.ICE_CREAM_SANDWICH),
new KeyboardTheme(THEME_ID_LXX_DARK, R.style.KeyboardTheme_LXX_Dark,
+ // Default theme for LXX.
// TODO: Update this constant once the *next* version becomes available.
VERSION_CODES.CUR_DEVELOPMENT),
};
+
static {
// Sort {@link #KEYBOARD_THEME} by descending order of {@link #mMinApiVersion}.
- Arrays.sort(KEYBOARD_THEMES, new Comparator<KeyboardTheme>() {
- @Override
- public int compare(final KeyboardTheme lhs, final KeyboardTheme rhs) {
- if (lhs.mMinApiVersion > rhs.mMinApiVersion) return -1;
- if (lhs.mMinApiVersion < rhs.mMinApiVersion) return 1;
- return 0;
- }
- });
+ Arrays.sort(KEYBOARD_THEMES);
}
public final int mThemeId;
public final int mStyleId;
- final int mMinApiVersion;
+ private final int mMinApiVersion;
// Note: The themeId should be aligned with "themeId" attribute of Keyboard style
// in values/themes-<style>.xml.
@@ -71,6 +68,13 @@ public final class KeyboardTheme {
}
@Override
+ public int compareTo(final KeyboardTheme rhs) {
+ if (mMinApiVersion > rhs.mMinApiVersion) return -1;
+ if (mMinApiVersion < rhs.mMinApiVersion) return 1;
+ return 0;
+ }
+
+ @Override
public boolean equals(final Object o) {
if (o == this) return true;
return (o instanceof KeyboardTheme) && ((KeyboardTheme)o).mThemeId == mThemeId;
@@ -81,21 +85,8 @@ public final class KeyboardTheme {
return mThemeId;
}
- // TODO: This method should be removed when {@link LatinImeLogger} is removed.
- public int getCompatibleThemeIdForLogging() {
- switch (mThemeId) {
- case THEME_ID_ICS:
- return 5;
- case THEME_ID_KLP:
- return 9;
- case THEME_ID_LXX_DARK:
- return 10;
- default: // Invalid theme
- return -1;
- }
- }
-
- private static KeyboardTheme searchKeyboardThemeById(final int themeId) {
+ @UsedForTesting
+ static KeyboardTheme searchKeyboardThemeById(final int themeId) {
// TODO: This search algorithm isn't optimal if there are many themes.
for (final KeyboardTheme theme : KEYBOARD_THEMES) {
if (theme.mThemeId == themeId) {
@@ -114,6 +105,7 @@ public final class KeyboardTheme {
return sdkVersion;
}
+ @UsedForTesting
static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs,
final int sdkVersion) {
final String klpThemeIdString = prefs.getString(KLP_KEYBOARD_THEME_KEY, null);
@@ -148,6 +140,7 @@ public final class KeyboardTheme {
saveKeyboardThemeId(themeIdString, prefs, getSdkVersion());
}
+ @UsedForTesting
static String getPreferenceKey(final int sdkVersion) {
if (sdkVersion <= VERSION_CODES.KITKAT) {
return KLP_KEYBOARD_THEME_KEY;
@@ -155,8 +148,9 @@ public final class KeyboardTheme {
return LXX_KEYBOARD_THEME_KEY;
}
- static void saveKeyboardThemeId(final String themeIdString, final SharedPreferences prefs,
- final int sdkVersion) {
+ @UsedForTesting
+ static void saveKeyboardThemeId(final String themeIdString,
+ final SharedPreferences prefs, final int sdkVersion) {
final String prefKey = getPreferenceKey(sdkVersion);
prefs.edit().putString(prefKey, themeIdString).apply();
}
@@ -165,6 +159,7 @@ public final class KeyboardTheme {
return getKeyboardTheme(prefs, getSdkVersion());
}
+ @UsedForTesting
static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion) {
final String lxxThemeIdString = prefs.getString(LXX_KEYBOARD_THEME_KEY, null);
if (lxxThemeIdString == null) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index edfc5fded..c4ca1c495 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -35,12 +35,8 @@ import android.view.View;
import com.android.inputmethod.keyboard.internal.KeyDrawParams;
import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.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;
@@ -110,7 +106,7 @@ public class KeyboardView extends View {
/** True if all keys should be drawn */
private boolean mInvalidateAllKeys;
/** The keys that should be drawn */
- private final HashSet<Key> mInvalidatedKeys = CollectionUtils.newHashSet();
+ private final HashSet<Key> mInvalidatedKeys = new HashSet<>();
/** The working rectangle variable */
private final Rect mWorkingRect = new Rect();
/** The keyboard bitmap buffer for faster updates */
@@ -188,7 +184,6 @@ public class KeyboardView extends View {
*/
public void setKeyboard(final Keyboard keyboard) {
mKeyboard = keyboard;
- LatinImeLogger.onSetKeyboard(keyboard);
final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
mKeyDrawParams.updateParams(keyHeight, keyboard.mKeyVisualAttributes);
@@ -318,13 +313,6 @@ public class KeyboardView extends View {
}
}
- // Research Logging (Development Only Diagnostics) indicator.
- // TODO: Reimplement using a keyboard background image specific to the ResearchLogger,
- // and remove this call.
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().paintIndicator(this, paint, canvas, width, height);
- }
-
mInvalidatedKeys.clear();
mInvalidateAllKeys = false;
}
@@ -363,9 +351,6 @@ public class KeyboardView extends View {
}
canvas.translate(bgX, bgY);
background.draw(canvas);
- if (LatinImeLogger.sVISUALDEBUG) {
- drawRectangle(canvas, 0.0f, 0.0f, bgWidth, bgHeight, 0x80c00000, new Paint());
- }
canvas.translate(-bgX, -bgY);
}
@@ -377,10 +362,6 @@ public class KeyboardView extends View {
final float centerX = keyWidth * 0.5f;
final float centerY = keyHeight * 0.5f;
- if (LatinImeLogger.sVISUALDEBUG) {
- drawRectangle(canvas, 0.0f, 0.0f, keyWidth, keyHeight, 0x800000c0, new Paint());
- }
-
// Draw key label.
final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha);
float positionX = centerX;
@@ -462,12 +443,6 @@ public class KeyboardView extends View {
drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
}
}
-
- if (LatinImeLogger.sVISUALDEBUG) {
- final Paint line = new Paint();
- drawHorizontalLine(canvas, baseline, keyWidth, 0xc0008000, line);
- drawVerticalLine(canvas, positionX, keyHeight, 0xc0800080, line);
- }
}
// Draw hint label.
@@ -507,12 +482,6 @@ public class KeyboardView extends View {
paint.setTextAlign(Align.CENTER);
}
canvas.drawText(hintLabel, 0, hintLabel.length(), hintX, hintY + adjustmentY, paint);
-
- if (LatinImeLogger.sVISUALDEBUG) {
- final Paint line = new Paint();
- drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
- drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
- }
}
// Draw key icon.
@@ -524,26 +493,17 @@ public class KeyboardView extends View {
iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth);
}
final int iconHeight = icon.getIntrinsicHeight();
- final int iconX, alignX;
final int iconY = key.isAlignButtom() ? keyHeight - iconHeight
: (keyHeight - iconHeight) / 2;
+ final int iconX;
if (key.isAlignLeft()) {
iconX = mKeyLabelHorizontalPadding;
- alignX = iconX;
} else if (key.isAlignRight()) {
iconX = keyWidth - mKeyLabelHorizontalPadding - iconWidth;
- alignX = iconX + iconWidth;
} else { // Align center
iconX = (keyWidth - iconWidth) / 2;
- alignX = iconX + iconWidth / 2;
}
drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
-
- if (LatinImeLogger.sVISUALDEBUG) {
- final Paint line = new Paint();
- drawVerticalLine(canvas, alignX, keyHeight, 0xc0800080, line);
- drawRectangle(canvas, iconX, iconY, iconWidth, iconHeight, 0x80c00000, line);
- }
}
if (key.hasPopupHint() && key.getMoreKeys() != null) {
@@ -565,12 +525,6 @@ public class KeyboardView extends View {
- TypefaceUtils.getReferenceCharWidth(paint) / 2.0f;
final float hintY = keyHeight - mKeyPopupHintLetterPadding;
canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
-
- if (LatinImeLogger.sVISUALDEBUG) {
- final Paint line = new Paint();
- drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
- drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
- }
}
protected static void drawIcon(final Canvas canvas, final Drawable icon, final int x,
@@ -581,32 +535,6 @@ public class KeyboardView extends View {
canvas.translate(-x, -y);
}
- private static void drawHorizontalLine(final Canvas canvas, final float y, final float w,
- final int color, final Paint paint) {
- paint.setStyle(Paint.Style.STROKE);
- paint.setStrokeWidth(1.0f);
- paint.setColor(color);
- canvas.drawLine(0.0f, y, w, y, paint);
- }
-
- private static void drawVerticalLine(final Canvas canvas, final float x, final float h,
- final int color, final Paint paint) {
- paint.setStyle(Paint.Style.STROKE);
- paint.setStrokeWidth(1.0f);
- paint.setColor(color);
- canvas.drawLine(x, 0.0f, x, h, paint);
- }
-
- private static void drawRectangle(final Canvas canvas, final float x, final float y,
- final float w, final float h, final int color, final Paint paint) {
- paint.setStyle(Paint.Style.STROKE);
- paint.setStrokeWidth(1.0f);
- paint.setColor(color);
- canvas.translate(x, y);
- canvas.drawRect(0.0f, 0.0f, w, h, paint);
- canvas.translate(-x, -y);
- }
-
public Paint newLabelPaint(final Key key) {
final Paint paint = new Paint();
paint.setAntiAlias(true);
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 86036ccc1..9a859bfdb 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -52,17 +52,12 @@ import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
import com.android.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview;
import com.android.inputmethod.keyboard.internal.TimerHandler;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
-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.SpacebarLanguageUtils;
import com.android.inputmethod.latin.utils.TypefaceUtils;
-import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.WeakHashMap;
@@ -150,8 +145,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private final Paint mBackgroundDimAlphaPaint = new Paint();
private boolean mNeedsToDimEntireKeyboard;
private final View mMoreKeysKeyboardContainer;
- private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache =
- CollectionUtils.newWeakHashMap();
+ private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = new WeakHashMap<>();
private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
// More keys panel (used by both more keys keyboard and more suggestions view)
// TODO: Consider extending to support multiple more keys panels
@@ -167,10 +161,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private final TimerHandler mKeyTimerHandler;
private final int mLanguageOnSpacebarHorizontalMargin;
- private final DrawingHandler mDrawingHandler =
- new DrawingHandler(this);
+ private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
- private final MainKeyboardAccessibilityDelegate mAccessibilityDelegate;
+ private MainKeyboardAccessibilityDelegate mAccessibilityDelegate;
public MainKeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.mainKeyboardViewStyle);
@@ -268,8 +261,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension(
R.dimen.config_language_on_spacebar_horizontal_margin);
-
- mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this, mKeyDetector);
}
@Override
@@ -389,12 +380,15 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio;
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final int orientation = getContext().getResources().getConfiguration().orientation;
- ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation);
- }
- mAccessibilityDelegate.setKeyboard(keyboard);
+ if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
+ if (mAccessibilityDelegate == null) {
+ mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this, mKeyDetector);
+ }
+ mAccessibilityDelegate.setKeyboard(keyboard);
+ } else {
+ mAccessibilityDelegate = null;
+ }
}
/**
@@ -452,15 +446,15 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
public void showKeyPreview(final Key key) {
- // If key is invalid or IME is already closed, we must not show key preview.
- // Trying to show key preview while root window is closed causes
- // WindowManager.BadTokenException.
- if (key == null) {
+ // If the key is invalid or has no key preview, we must not show key preview.
+ if (key == null || key.noKeyPreview()) {
return;
}
-
- final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
final Keyboard keyboard = getKeyboard();
+ if (keyboard == null) {
+ return;
+ }
+ final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
if (!previewParams.isPopupEnabled()) {
previewParams.setVisibleOffset(-keyboard.mVerticalGap);
return;
@@ -554,24 +548,12 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
protected void onAttachedToWindow() {
super.onAttachedToWindow();
installPreviewPlacerView();
- // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
- // been attached. This is needed to properly show the splash screen, which requires that
- // the window token of the KeyboardView be non-null.
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
- }
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mDrawingPreviewPlacerView.removeAllViews();
- // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
- // been detached. This is needed to invalidate the reference of {@link MainKeyboardView}
- // to null.
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
- }
}
private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) {
@@ -607,9 +589,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (key == null) {
return;
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.mainKeyboardView_onLongPress();
- }
final KeyboardActionListener listener = mKeyboardActionListener;
if (key.hasNoPanelAutoMoreKey()) {
final int moreKeyCode = key.getMoreKeys()[0].mCode;
@@ -722,14 +701,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
public boolean processMotionEvent(final MotionEvent me) {
- if (LatinImeLogger.sUsabilityStudy) {
- UsabilityStudyLogUtils.writeMotionEvent(me);
- }
- // Currently the same "move" event is being logged twice.
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.mainKeyboardView_processMotionEvent(me);
- }
-
final int index = me.getActionIndex();
final int id = me.getPointerId(index);
final PointerTracker tracker = PointerTracker.getPointerTracker(id);
@@ -759,25 +730,22 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
public void onHideWindow() {
- if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
- mAccessibilityDelegate.onHideWindow();
+ final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
+ if (accessibilityDelegate != null) {
+ accessibilityDelegate.onHideWindow();
}
}
/**
- * Receives hover events from the input framework.
- *
- * @param event The motion event to be dispatched.
- * @return {@code true} if the event was handled by the view, {@code false}
- * otherwise
+ * {@inheritDoc}
*/
@Override
- public boolean dispatchHoverEvent(final MotionEvent event) {
- if (!AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
- // Reflection doesn't support calling superclass methods.
- return false;
+ public boolean onHoverEvent(final MotionEvent event) {
+ final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
+ if (accessibilityDelegate != null) {
+ return accessibilityDelegate.onHoverEvent(event);
}
- return mAccessibilityDelegate.dispatchHoverEvent(event);
+ return super.onHoverEvent(event);
}
public void updateShortcutKey(final boolean available) {
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 4a2b37e4c..3994487aa 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -17,12 +17,13 @@
package com.android.inputmethod.keyboard;
import android.content.Context;
-import android.content.res.Resources;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import com.android.inputmethod.accessibility.AccessibilityUtils;
+import com.android.inputmethod.accessibility.MoreKeysKeyboardAccessibilityDelegate;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.CoordinateUtils;
@@ -43,6 +44,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
private int mActivePointerId;
+ protected MoreKeysKeyboardAccessibilityDelegate mAccessibilityDelegate;
+
public MoreKeysKeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.moreKeysKeyboardViewStyle);
}
@@ -50,10 +53,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
public MoreKeysKeyboardView(final Context context, final AttributeSet attrs,
final int defStyle) {
super(context, attrs, defStyle);
-
- final Resources res = context.getResources();
- mKeyDetector = new MoreKeysDetector(
- res.getDimension(R.dimen.config_more_keys_keyboard_slide_allowance));
+ mKeyDetector = new MoreKeysDetector(getResources().getDimension(
+ R.dimen.config_more_keys_keyboard_slide_allowance));
}
@Override
@@ -71,8 +72,19 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
@Override
public void setKeyboard(final Keyboard keyboard) {
super.setKeyboard(keyboard);
- mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
- -getPaddingTop() + getVerticalCorrection());
+ mKeyDetector.setKeyboard(
+ keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
+ if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+ if (mAccessibilityDelegate == null) {
+ mAccessibilityDelegate = new MoreKeysKeyboardAccessibilityDelegate(
+ this, mKeyDetector);
+ mAccessibilityDelegate.setOpenAnnounce(R.string.spoken_open_more_keys_keyboard);
+ mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_keys_keyboard);
+ }
+ mAccessibilityDelegate.setKeyboard(keyboard);
+ } else {
+ mAccessibilityDelegate = null;
+ }
}
@Override
@@ -98,6 +110,10 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
mOriginX = x + container.getPaddingLeft();
mOriginY = y + container.getPaddingTop();
controller.onShowMoreKeysPanel(this);
+ final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
+ if (accessibilityDelegate != null) {
+ accessibilityDelegate.onShowMoreKeysKeyboard();
+ }
}
/**
@@ -110,25 +126,31 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
@Override
public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime) {
mActivePointerId = pointerId;
- onMoveKeyInternal(x, y, pointerId);
+ mCurrentKey = detectKey(x, y, pointerId);
}
@Override
- public void onMoveEvent(int x, int y, final int pointerId, long eventTime) {
+ public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime) {
if (mActivePointerId != pointerId) {
return;
}
final boolean hasOldKey = (mCurrentKey != null);
- onMoveKeyInternal(x, y, pointerId);
+ mCurrentKey = detectKey(x, y, pointerId);
if (hasOldKey && mCurrentKey == null) {
- // If the pointer has moved too far away from any target then cancel the panel.
+ // A more keys keyboard is canceled when detecting no key.
mController.onCancelMoreKeysPanel();
}
}
@Override
public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) {
- if (mCurrentKey != null && mActivePointerId == pointerId) {
+ if (mActivePointerId != pointerId) {
+ return;
+ }
+ // Calling {@link #detectKey(int,int,int)} here is harmless because the last move event and
+ // the following up event share the same coordinates.
+ mCurrentKey = detectKey(x, y, pointerId);
+ if (mCurrentKey != null) {
updateReleaseKeyGraphics(mCurrentKey);
onKeyInput(mCurrentKey, x, y);
mCurrentKey = null;
@@ -152,23 +174,22 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
}
}
- private void onMoveKeyInternal(int x, int y, int pointerId) {
- if (mActivePointerId != pointerId) {
- // Ignore old pointers when newer pointer is active.
- return;
- }
+ private Key detectKey(int x, int y, int pointerId) {
final Key oldKey = mCurrentKey;
final Key newKey = mKeyDetector.detectHitKey(x, y);
- if (newKey != oldKey) {
- mCurrentKey = newKey;
- invalidateKey(mCurrentKey);
- if (oldKey != null) {
- updateReleaseKeyGraphics(oldKey);
- }
- if (newKey != null) {
- updatePressKeyGraphics(newKey);
- }
+ if (newKey == oldKey) {
+ return newKey;
+ }
+ // A new key is detected.
+ if (oldKey != null) {
+ updateReleaseKeyGraphics(oldKey);
+ invalidateKey(oldKey);
}
+ if (newKey != null) {
+ updatePressKeyGraphics(newKey);
+ invalidateKey(newKey);
+ }
+ return newKey;
}
private void updateReleaseKeyGraphics(final Key key) {
@@ -186,6 +207,10 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
if (!isShowingInParent()) {
return;
}
+ final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
+ if (accessibilityDelegate != null) {
+ accessibilityDelegate.onDismissMoreKeysKeyboard();
+ }
mController.onDismissMoreKeysPanel();
}
@@ -223,6 +248,18 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
return true;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onHoverEvent(final MotionEvent event) {
+ final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
+ if (accessibilityDelegate != null) {
+ return accessibilityDelegate.onHoverEvent(event);
+ }
+ return super.onHoverEvent(event);
+ }
+
private View getContainerView() {
return (View)getParent();
}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 4777166ea..b6905bc1c 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -35,12 +35,9 @@ import com.android.inputmethod.latin.Constants;
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;
@@ -144,7 +141,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
// TODO: Device specific parameter would be better for device specific hack?
private static final float PHANTOM_SUDDEN_MOVE_THRESHOLD = 0.25f; // in keyWidth
- private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
+ private static final ArrayList<PointerTracker> sTrackers = new ArrayList<>();
private static final PointerTrackerQueue sPointerTrackerQueue = new PointerTrackerQueue();
public final int mPointerId;
@@ -336,10 +333,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
output, ignoreModifierKey ? " ignoreModifier" : "",
altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled"));
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey,
- altersCode, code);
- }
if (ignoreModifierKey) {
return;
}
@@ -374,10 +367,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "",
key.isEnabled() ? "": " disabled"));
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding,
- ignoreModifierKey);
- }
if (ignoreModifierKey) {
return;
}
@@ -397,9 +386,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onCancelInput", mPointerId));
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.pointerTracker_callListenerOnCancelInput();
- }
sListener.onCancelInput();
}
@@ -690,10 +676,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
private void onDownEvent(final int x, final int y, final long eventTime,
final KeyDetector keyDetector) {
+ setKeyDetectorInner(keyDetector);
if (DEBUG_EVENT) {
printTouchEvent("onDownEvent:", x, y, eventTime);
}
- setKeyDetectorInner(keyDetector);
// Naive up-to-down noise filter.
final long deltaT = eventTime - mUpTime;
if (deltaT < sParams.mTouchNoiseThresholdTime) {
@@ -703,9 +689,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
Log.w(TAG, String.format("[%d] onDownEvent:"
+ " ignore potential noise: time=%d distance=%d",
mPointerId, deltaT, distance));
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance);
- }
cancelTrackingForAction();
return;
}
@@ -877,10 +860,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
lastX, lastY, Constants.printableCode(oldKey.getCode()),
x, y, Constants.printableCode(key.getCode())));
}
- // TODO: This should be moved to outside of this nested if-clause?
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY);
- }
onUpEventInternal(x, y, eventTime);
onDownEventInternal(x, y, eventTime);
}
@@ -1054,8 +1033,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
final int translatedY = mMoreKeysPanel.translateY(y);
mMoreKeysPanel.onUpEvent(translatedX, translatedY, mPointerId, eventTime);
}
- mMoreKeysPanel.dismissMoreKeysPanel();
- mMoreKeysPanel = null;
+ dismissMoreKeysPanel();
return;
}
@@ -1100,6 +1078,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
mIsTrackingForActionDisabled = true;
}
+ public boolean isInOperation() {
+ return !mIsTrackingForActionDisabled;
+ }
+
+ public void cancelLongPressTimer() {
+ sTimerProxy.cancelLongPressTimerOf(this);
+ }
+
public void onLongPressed() {
resetKeySelectionByDraggingFinger();
cancelTrackingForAction();
@@ -1122,10 +1108,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
sTimerProxy.cancelKeyTimersOf(this);
setReleasedKeyGraphics(mCurrentKey);
resetKeySelectionByDraggingFinger();
- if (isShowingMoreKeysPanel()) {
- mMoreKeysPanel.dismissMoreKeysPanel();
- mMoreKeysPanel = null;
- }
+ dismissMoreKeysPanel();
}
private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index c89bda40e..c19cd671a 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -22,7 +22,6 @@ import android.util.Log;
import com.android.inputmethod.keyboard.internal.TouchPositionCorrection;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import java.util.ArrayList;
@@ -55,6 +54,7 @@ public class ProximityInfo {
private final List<Key>[] mGridNeighbors;
private final String mLocaleStr;
+ @SuppressWarnings("unchecked")
ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight,
final int minWidth, final int height, final int mostCommonKeyWidth,
final int mostCommonKeyHeight, final List<Key> sortedKeys,
@@ -360,7 +360,7 @@ y |---+---+---+---+-v-+-|-+---+---+---+---+---| | thresholdBase and get
for (int i = 0; i < gridSize; ++i) {
final int indexStart = i * keyCount;
final int indexEnd = indexStart + neighborCountPerCell[i];
- final ArrayList<Key> neighbors = CollectionUtils.newArrayList(indexEnd - indexStart);
+ final ArrayList<Key> neighbors = new ArrayList<>(indexEnd - indexStart);
for (int index = indexStart; index < indexEnd; index++) {
neighbors.add(neighborsFlatBuffer[index]);
}
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java
index c7a9025c0..daeb1f928 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java
@@ -23,7 +23,6 @@ import android.util.Log;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.settings.Settings;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.JsonUtils;
import java.util.ArrayDeque;
@@ -47,8 +46,8 @@ final class DynamicGridKeyboard extends Keyboard {
private final int mColumnsNum;
private final int mMaxKeyCount;
private final boolean mIsRecents;
- private final ArrayDeque<GridKey> mGridKeys = CollectionUtils.newArrayDeque();
- private final ArrayDeque<Key> mPendingKeys = CollectionUtils.newArrayDeque();
+ private final ArrayDeque<GridKey> mGridKeys = new ArrayDeque<>();
+ private final ArrayDeque<Key> mPendingKeys = new ArrayDeque<>();
private List<Key> mCachedGridKeys;
@@ -131,7 +130,7 @@ final class DynamicGridKeyboard extends Keyboard {
}
private void saveRecentKeys() {
- final ArrayList<Object> keys = CollectionUtils.newArrayList();
+ final ArrayList<Object> keys = new ArrayList<>();
for (final Key key : mGridKeys) {
if (key.getOutputText() != null) {
keys.add(key.getOutputText());
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
index 859099110..512d4615d 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
@@ -31,7 +31,6 @@ import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.settings.Settings;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
@@ -101,12 +100,11 @@ final class EmojiCategory {
private final Resources mRes;
private final int mMaxPageKeyCount;
private final KeyboardLayoutSet mLayoutSet;
- private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
+ private final HashMap<String, Integer> mCategoryNameToIdMap = new HashMap<>();
private final int[] mCategoryTabIconId = new int[sCategoryName.length];
- private final ArrayList<CategoryProperties> mShownCategories =
- CollectionUtils.newArrayList();
- private final ConcurrentHashMap<Long, DynamicGridKeyboard>
- mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>();
+ private final ArrayList<CategoryProperties> mShownCategories = new ArrayList<>();
+ private final ConcurrentHashMap<Long, DynamicGridKeyboard> mCategoryKeyboardMap =
+ new ConcurrentHashMap<>();
private int mCurrentCategoryId = EmojiCategory.ID_UNSPECIFIED;
private int mCurrentCategoryPageId = 0;
@@ -257,7 +255,7 @@ final class EmojiCategory {
final int temp = sum;
sum += properties.mPageCount;
if (sum > position) {
- return new Pair<Integer, Integer>(properties.mCategoryId, position - temp);
+ return new Pair<>(properties.mCategoryId, position - temp);
}
}
return null;
@@ -343,7 +341,7 @@ final class EmojiCategory {
};
private static Key[][] sortKeysIntoPages(final List<Key> inKeys, final int maxPageCount) {
- final ArrayList<Key> keys = CollectionUtils.newArrayList(inKeys);
+ final ArrayList<Key> keys = new ArrayList<>(inKeys);
Collections.sort(keys, EMOJI_KEY_COMPARATOR);
final int pageCount = (keys.size() - 1) / maxPageCount + 1;
final Key[][] retval = new Key[pageCount][maxPageCount];
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java
index a6b089169..43d62c71a 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java
@@ -17,14 +17,11 @@
package com.android.inputmethod.keyboard.emoji;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
-import com.android.inputmethod.latin.R;
-
public final class EmojiCategoryPageIndicatorView extends View {
private static final float BOTTOM_MARGIN_RATIO = 1.0f;
private final Paint mPaint = new Paint();
@@ -33,19 +30,17 @@ public final class EmojiCategoryPageIndicatorView extends View {
private float mOffset = 0.0f;
public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs) {
- this(context, attrs, R.attr.emojiCategoryPageIndicatorViewStyle);
+ this(context, attrs, 0);
}
public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs,
final int defStyle) {
super(context, attrs, defStyle);
- final TypedArray indicatorViewAttr = context.obtainStyledAttributes(attrs,
- R.styleable.EmojiCategoryPageIndicatorView, defStyle,
- R.style.EmojiCategoryPageIndicatorView);
- final int indicatorColor = indicatorViewAttr.getColor(
- R.styleable.EmojiCategoryPageIndicatorView_emojiCategoryPageIndicatorColor, 0);
- indicatorViewAttr.recycle();
- mPaint.setColor(indicatorColor);
+ }
+
+ public void setColors(final int foregroundColor, final int backgroundColor) {
+ mPaint.setColor(foregroundColor);
+ setBackgroundColor(backgroundColor);
}
public void setCategoryPageId(final int size, final int id, final float offset) {
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
index 48efa17ad..a34dbef4b 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
@@ -55,7 +55,22 @@ final class EmojiPageKeyboardView extends KeyboardView implements
private OnKeyEventListener mListener = EMPTY_LISTENER;
private final KeyDetector mKeyDetector = new KeyDetector();
private final GestureDetector mGestureDetector;
- private final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate;
+ private EmojiPageKeyboardAccessibilityDelegate mAccessibilityDelegate;
+
+ private static final class EmojiPageKeyboardAccessibilityDelegate
+ extends KeyboardAccessibilityDelegate<EmojiPageKeyboardView> {
+ public EmojiPageKeyboardAccessibilityDelegate(final EmojiPageKeyboardView keyboardView,
+ final KeyDetector keyDetector) {
+ super(keyboardView, keyDetector);
+ }
+
+ @Override
+ protected void simulateTouchEvent(int touchAction, MotionEvent hoverEvent) {
+ final MotionEvent touchEvent = synthesizeTouchEvent(touchAction, hoverEvent);
+ mKeyboardView.onTouchEvent(touchEvent);
+ touchEvent.recycle();
+ }
+ }
public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
@@ -67,7 +82,6 @@ final class EmojiPageKeyboardView extends KeyboardView implements
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */);
mHandler = new Handler();
- mAccessibilityDelegate = new KeyboardAccessibilityDelegate<>(this, mKeyDetector);
}
public void setOnKeyEventListener(final OnKeyEventListener listener) {
@@ -81,15 +95,28 @@ final class EmojiPageKeyboardView extends KeyboardView implements
public void setKeyboard(final Keyboard keyboard) {
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */);
+ if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
+ if (mAccessibilityDelegate == null) {
+ mAccessibilityDelegate = new EmojiPageKeyboardAccessibilityDelegate(
+ this, mKeyDetector);
+ }
+ mAccessibilityDelegate.setKeyboard(keyboard);
+ } else {
+ mAccessibilityDelegate = null;
+ }
}
+ /**
+ * {@inheritDoc}
+ */
@Override
- public boolean dispatchHoverEvent(final MotionEvent event) {
- if (!AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
- // Reflection doesn't support calling superclass methods.
- return false;
+ public boolean onHoverEvent(final MotionEvent event) {
+ final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> accessibilityDelegate =
+ mAccessibilityDelegate;
+ if (accessibilityDelegate != null) {
+ return accessibilityDelegate.onHoverEvent(event);
}
- return mAccessibilityDelegate.dispatchHoverEvent(event);
+ return super.onHoverEvent(event);
}
/**
@@ -102,7 +129,7 @@ final class EmojiPageKeyboardView extends KeyboardView implements
}
final Key key = getKey(e);
if (key != null && key != mCurrentKey) {
- releaseCurrentKey();
+ releaseCurrentKey(false /* withKeyRegistering */);
}
return true;
}
@@ -119,7 +146,7 @@ final class EmojiPageKeyboardView extends KeyboardView implements
return mKeyDetector.detectHitKey(x, y);
}
- public void releaseCurrentKey() {
+ public void releaseCurrentKey(final boolean withKeyRegistering) {
mHandler.removeCallbacks(mPendingKeyDown);
mPendingKeyDown = null;
final Key currentKey = mCurrentKey;
@@ -128,13 +155,16 @@ final class EmojiPageKeyboardView extends KeyboardView implements
}
currentKey.onReleased();
invalidateKey(currentKey);
+ if (withKeyRegistering) {
+ mListener.onReleaseKey(currentKey);
+ }
mCurrentKey = null;
}
@Override
public boolean onDown(final MotionEvent e) {
final Key key = getKey(e);
- releaseCurrentKey();
+ releaseCurrentKey(false /* withKeyRegistering */);
mCurrentKey = key;
if (key == null) {
return false;
@@ -163,7 +193,7 @@ final class EmojiPageKeyboardView extends KeyboardView implements
final Key key = getKey(e);
final Runnable pendingKeyDown = mPendingKeyDown;
final Key currentKey = mCurrentKey;
- releaseCurrentKey();
+ releaseCurrentKey(false /* withKeyRegistering */);
if (key == null) {
return false;
}
@@ -189,14 +219,14 @@ final class EmojiPageKeyboardView extends KeyboardView implements
@Override
public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX,
final float distanceY) {
- releaseCurrentKey();
+ releaseCurrentKey(false /* withKeyRegistering */);
return false;
}
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX,
final float velocityY) {
- releaseCurrentKey();
+ releaseCurrentKey(false /* withKeyRegistering */);
return false;
}
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java
index 52a4dde97..68056e0eb 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java
@@ -27,7 +27,6 @@ import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.CollectionUtils;
final class EmojiPalettesAdapter extends PagerAdapter {
private static final String TAG = EmojiPalettesAdapter.class.getSimpleName();
@@ -35,8 +34,7 @@ final class EmojiPalettesAdapter extends PagerAdapter {
private final EmojiPageKeyboardView.OnKeyEventListener mListener;
private final DynamicGridKeyboard mRecentsKeyboard;
- private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews =
- CollectionUtils.newSparseArray();
+ private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews = new SparseArray<>();
private final EmojiCategory mEmojiCategory;
private int mActivePosition = 0;
@@ -70,13 +68,18 @@ final class EmojiPalettesAdapter extends PagerAdapter {
}
public void onPageScrolled() {
+ releaseCurrentKey(false /* withKeyRegistering */);
+ }
+
+ public void releaseCurrentKey(final boolean withKeyRegistering) {
// Make sure the delayed key-down event (highlight effect and haptic feedback) will be
// canceled.
final EmojiPageKeyboardView currentKeyboardView =
mActiveKeyboardViews.get(mActivePosition);
- if (currentKeyboardView != null) {
- currentKeyboardView.releaseCurrentKey();
+ if (currentKeyboardView == null) {
+ return;
}
+ currentKeyboardView.releaseCurrentKey(withKeyRegistering);
}
@Override
@@ -92,7 +95,7 @@ final class EmojiPalettesAdapter extends PagerAdapter {
}
final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
if (oldKeyboardView != null) {
- oldKeyboardView.releaseCurrentKey();
+ oldKeyboardView.releaseCurrentKey(false /* withKeyRegistering */);
oldKeyboardView.deallocateMemory();
}
mActivePosition = position;
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
index c0c9e205a..e37cd2369 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
@@ -36,6 +36,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;
+import android.widget.TabWidget;
import android.widget.TextView;
import com.android.inputmethod.keyboard.Key;
@@ -45,6 +46,7 @@ import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.internal.KeyDrawParams;
import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
@@ -68,6 +70,11 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
EmojiPageKeyboardView.OnKeyEventListener {
private final int mFunctionalKeyBackgroundId;
private final int mSpacebarBackgroundId;
+ private final boolean mCategoryIndicatorEnabled;
+ private final int mCategoryIndicatorDrawableResId;
+ private final int mCategoryIndicatorBackgroundResId;
+ private final int mCategoryPageIndicatorColor;
+ private final int mCategoryPageIndicatorBackground;
private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener;
private EmojiPalettesAdapter mEmojiPalettesAdapter;
private final EmojiLayoutParams mEmojiLayoutParams;
@@ -109,13 +116,21 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype());
builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res),
mEmojiLayoutParams.mEmojiKeyboardHeight);
- builder.setOptions(false /* shortcutImeEnabled */, false /* showsVoiceInputKey */,
- false /* languageSwitchKeyEnabled */);
final KeyboardLayoutSet layoutSet = builder.build();
final TypedArray emojiPalettesViewAttr = context.obtainStyledAttributes(attrs,
R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView);
mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context),
res, layoutSet, emojiPalettesViewAttr);
+ mCategoryIndicatorEnabled = emojiPalettesViewAttr.getBoolean(
+ R.styleable.EmojiPalettesView_categoryIndicatorEnabled, false);
+ mCategoryIndicatorDrawableResId = emojiPalettesViewAttr.getResourceId(
+ R.styleable.EmojiPalettesView_categoryIndicatorDrawable, 0);
+ mCategoryIndicatorBackgroundResId = emojiPalettesViewAttr.getResourceId(
+ R.styleable.EmojiPalettesView_categoryIndicatorBackground, 0);
+ mCategoryPageIndicatorColor = emojiPalettesViewAttr.getColor(
+ R.styleable.EmojiPalettesView_categoryPageIndicatorColor, 0);
+ mCategoryPageIndicatorBackground = emojiPalettesViewAttr.getColor(
+ R.styleable.EmojiPalettesView_categoryPageIndicatorBackground, 0);
emojiPalettesViewAttr.recycle();
mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener(context);
}
@@ -154,7 +169,15 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
addTab(mTabHost, properties.mCategoryId);
}
mTabHost.setOnTabChangedListener(this);
- mTabHost.getTabWidget().setStripEnabled(true);
+ final TabWidget tabWidget = mTabHost.getTabWidget();
+ tabWidget.setStripEnabled(mCategoryIndicatorEnabled);
+ if (mCategoryIndicatorEnabled) {
+ // On TabWidget's strip, what looks like an indicator is actually a background.
+ // And what looks like a background are actually left and right drawables.
+ tabWidget.setBackgroundResource(mCategoryIndicatorDrawableResId);
+ tabWidget.setLeftStripDrawable(mCategoryIndicatorBackgroundResId);
+ tabWidget.setRightStripDrawable(mCategoryIndicatorBackgroundResId);
+ }
mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, this);
@@ -167,6 +190,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
mEmojiCategoryPageIndicatorView =
(EmojiCategoryPageIndicatorView)findViewById(R.id.emoji_category_page_id_view);
+ mEmojiCategoryPageIndicatorView.setColors(
+ mCategoryPageIndicatorColor, mCategoryPageIndicatorBackground);
mEmojiLayoutParams.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView);
setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */);
@@ -216,6 +241,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
@Override
public void onTabChanged(final String tabId) {
+ AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
+ Constants.CODE_UNSPECIFIED, this);
final int categoryId = mEmojiCategory.getCategoryId(tabId);
setCurrentCategoryId(categoryId, false /* force */);
updateEmojiCategoryPageIdView();
@@ -364,6 +391,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
public void stopEmojiPalettes() {
+ mEmojiPalettesAdapter.releaseCurrentKey(true /* withKeyRegistering */);
mEmojiPalettesAdapter.flushPendingRecentKeys();
mEmojiPager.setAdapter(null);
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java
index fdc2458d4..3b4c43418 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java
@@ -24,7 +24,6 @@ import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
import java.util.ArrayList;
@@ -32,7 +31,7 @@ import java.util.ArrayList;
public final class DrawingPreviewPlacerView extends RelativeLayout {
private final int[] mKeyboardViewOrigin = CoordinateUtils.newInstance();
- private final ArrayList<AbstractDrawingPreview> mPreviews = CollectionUtils.newArrayList();
+ private final ArrayList<AbstractDrawingPreview> mPreviews = new ArrayList<>();
public DrawingPreviewPlacerView(final Context context, final AttributeSet attrs) {
super(context, attrs);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java
index d8b00c707..72628e38a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java
@@ -29,15 +29,13 @@ import android.util.SparseArray;
import android.view.View;
import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
/**
* Draw preview graphics of multiple gesture trails during gesture input.
*/
public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview {
- private final SparseArray<GestureTrailDrawingPoints> mGestureTrails =
- CollectionUtils.newSparseArray();
+ private final SparseArray<GestureTrailDrawingPoints> mGestureTrails = new SparseArray<>();
private final GestureTrailDrawingParams mDrawingParams;
private final Paint mGesturePaint;
private int mOffscreenWidth;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
index 625d1f0a4..605519b02 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
@@ -32,7 +32,6 @@ import android.widget.TextView;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.ViewLayoutUtils;
@@ -48,9 +47,9 @@ import java.util.HashSet;
*/
public final class KeyPreviewChoreographer {
// Free {@link TextView} pool that can be used for key preview.
- private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = CollectionUtils.newArrayDeque();
+ private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = new ArrayDeque<>();
// Map from {@link Key} to {@link TextView} that is currently being displayed as key preview.
- private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = CollectionUtils.newHashMap();
+ private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = new HashMap<>();
private final KeyPreviewDrawParams mParams;
@@ -83,7 +82,7 @@ public final class KeyPreviewChoreographer {
}
public void dismissAllKeyPreviews() {
- for (final Key key : new HashSet<Key>(mShowingKeyPreviewTextViews.keySet())) {
+ for (final Key key : new HashSet<>(mShowingKeyPreviewTextViews.keySet())) {
dismissKeyPreview(key, false /* withAnimation */);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index 700c9b07c..0b0e761d2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -21,7 +21,6 @@ import android.util.Log;
import android.util.SparseArray;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.XmlParseUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -34,7 +33,7 @@ public final class KeyStylesSet {
private static final String TAG = KeyStylesSet.class.getSimpleName();
private static final boolean DEBUG = false;
- private final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap();
+ private final HashMap<String, KeyStyle> mStyles = new HashMap<>();
private final KeyboardTextsSet mTextsSet;
private final KeyStyle mEmptyKeyStyle;
@@ -75,7 +74,7 @@ public final class KeyStylesSet {
private static final class DeclaredKeyStyle extends KeyStyle {
private final HashMap<String, KeyStyle> mStyles;
private final String mParentStyleName;
- private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
+ private final SparseArray<Object> mStyleAttributes = new SparseArray<>();
public DeclaredKeyStyle(final String parentStyleName, final KeyboardTextsSet textsSet,
final HashMap<String, KeyStyle> styles) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index 2aeeed87f..8bff27574 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -444,6 +444,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
continue;
}
final int labelFlags = row.getDefaultKeyLabelFlags();
+ // TODO: Should be able to assign default keyActionFlags as well.
final int backgroundType = row.getDefaultBackgroundType();
final int x = (int)row.getKeyX(null);
final int y = row.getKeyY();
@@ -652,9 +653,6 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
final boolean clobberSettingsKeyMatched = matchBoolean(caseAttr,
R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
- final boolean supportsSwitchingToShortcutImeMatched = matchBoolean(caseAttr,
- R.styleable.Keyboard_Case_supportsSwitchingToShortcutIme,
- id.mSupportsSwitchingToShortcutIme);
final boolean hasShortcutKeyMatched = matchBoolean(caseAttr,
R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
final boolean languageSwitchKeyEnabledMatched = matchBoolean(caseAttr,
@@ -674,14 +672,13 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
final boolean selected = keyboardLayoutSetMatched && keyboardLayoutSetElementMatched
&& modeMatched && navigateNextMatched && navigatePreviousMatched
- && passwordInputMatched && clobberSettingsKeyMatched
- && supportsSwitchingToShortcutImeMatched && hasShortcutKeyMatched
+ && passwordInputMatched && clobberSettingsKeyMatched && hasShortcutKeyMatched
&& languageSwitchKeyEnabledMatched && isMultiLineMatched && imeActionMatched
&& isIconDefinedMatched && localeCodeMatched && languageCodeMatched
&& countryCodeMatched;
if (DEBUG) {
- startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+ startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
textAttr(caseAttr.getString(
R.styleable.Keyboard_Case_keyboardLayoutSet), "keyboardLayoutSet"),
textAttr(caseAttr.getString(
@@ -698,9 +695,6 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
"clobberSettingsKey"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_passwordInput,
"passwordInput"),
- booleanAttr(
- caseAttr, R.styleable.Keyboard_Case_supportsSwitchingToShortcutIme,
- "supportsSwitchingToShortcutIme"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_hasShortcutKey,
"hasShortcutKey"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index 06da5719b..62b69dcc9 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -17,14 +17,13 @@
package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.HashMap;
public final class KeyboardCodesSet {
public static final String PREFIX_CODE = "!code/";
- private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sNameToIdMap = new HashMap<>();
private KeyboardCodesSet() {
// This utility class is not publicly instantiable.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index f79bde017..7146deb4b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -23,7 +23,6 @@ import android.util.Log;
import android.util.SparseIntArray;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.HashMap;
@@ -60,7 +59,7 @@ public final class KeyboardIconsSet {
private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
// Icon name to icon id map.
- private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sNameToIdsMap = new HashMap<>();
private static final Object[] NAMES_AND_ATTR_IDS = {
NAME_UNDEFINED, ATTR_UNDEFINED,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
index a61a79b85..5df9d3ece 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -21,7 +21,6 @@ import android.util.SparseIntArray;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.Comparator;
@@ -61,9 +60,9 @@ public class KeyboardParams {
public int GRID_HEIGHT;
// Keys are sorted from top-left to bottom-right order.
- public final SortedSet<Key> mSortedKeys = new TreeSet<Key>(ROW_COLUMN_COMPARATOR);
- public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
- public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
+ public final SortedSet<Key> mSortedKeys = new TreeSet<>(ROW_COLUMN_COMPARATOR);
+ public final ArrayList<Key> mShiftKeys = new ArrayList<>();
+ public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<>();
public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
index 0f9497c27..92daf0742 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
@@ -23,7 +23,6 @@ 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.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -44,8 +43,9 @@ public final class KeyboardRow {
/** The height of this row. */
private final int mRowHeight;
- private final ArrayDeque<RowAttributes> mRowAttributesStack = CollectionUtils.newArrayDeque();
+ private final ArrayDeque<RowAttributes> mRowAttributesStack = new ArrayDeque<>();
+ // TODO: Add keyActionFlags.
private static class RowAttributes {
/** Default width of a key in this row. */
public final float mDefaultKeyWidth;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 0047aa4a1..cd6abeed3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -22,7 +22,6 @@ import android.text.TextUtils;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.RunInLocale;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
@@ -38,7 +37,7 @@ public final class KeyboardTextsSet {
private String[] mTextsTable;
// Resource name to text map.
- private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
+ private HashMap<String, String> mResourceNameToTextsMap = new HashMap<>();
public void setLocale(final Locale locale, final Context context) {
mTextsTable = KeyboardTextsTable.getTextsTable(locale);
@@ -141,6 +140,7 @@ public final class KeyboardTextsSet {
"label_send_key",
"label_next_key",
"label_done_key",
+ "label_search_key",
"label_previous_key",
// Other labels.
"label_pause_key",
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
index 7e6181a4e..ab2555802 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
@@ -16,8 +16,6 @@
package com.android.inputmethod.keyboard.internal;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
import java.util.HashMap;
import java.util.Locale;
@@ -44,14 +42,12 @@ import java.util.Locale;
*/
public final class KeyboardTextsTable {
// Name to index map.
- private static final HashMap<String, Integer> sNameToIndexesMap = CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sNameToIndexesMap = new HashMap<>();
// Locale to texts table map.
- private static final HashMap<String, String[]> sLocaleToTextsTableMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, String[]> sLocaleToTextsTableMap = new HashMap<>();
// TODO: Remove this variable after debugging.
// Texts table to locale maps.
- private static final HashMap<String[], String> sTextsTableToLocaleMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String[], String> sTextsTableToLocaleMap = new HashMap<>();
public static String getText(final String name, final String[] textsTable) {
final Integer indexObj = sNameToIndexesMap.get(name);
@@ -606,7 +602,7 @@ public final class KeyboardTextsTable {
/* keyspec_right_double_angle_quote */ "\u00BB|\u00AB",
/* keyspec_left_single_angle_quote */ "\u2039|\u203A",
/* keyspec_right_single_angle_quote */ "\u203A|\u2039",
- /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\",\'",
+ /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,\",\'",
// U+0651: "Ù‘" ARABIC SHADDA
/* keyhintlabel_period */ "\u0651",
/* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics",
@@ -1555,7 +1551,7 @@ public final class KeyboardTextsTable {
/* keyspec_right_double_angle_quote */ "\u00BB|\u00AB",
/* keyspec_left_single_angle_quote */ "\u2039|\u203A",
/* keyspec_right_single_angle_quote */ "\u203A|\u2039",
- /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,!text/keyspec_left_double_angle_quote,!text/keyspec_right_double_angle_quote",
+ /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,!text/keyspec_left_double_angle_quote,!text/keyspec_right_double_angle_quote",
// U+064B: "Ù‹" ARABIC FATHATAN
/* keyhintlabel_period */ "\u064B",
/* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics",
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
index 7c2e3e174..7743d4744 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
@@ -17,12 +17,11 @@
package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.HashMap;
public final class KeysCache {
- private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
+ private final HashMap<Key, Key> mMap = new HashMap<>();
public void clear() {
mMap.clear();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
index 56ef4767f..e0d5173ac 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -149,7 +149,7 @@ public final class MoreKeySpec {
// Skip empty entry.
if (pos - start > 0) {
if (list == null) {
- list = CollectionUtils.newArrayList();
+ list = new ArrayList<>();
}
list.add(text.substring(start, pos));
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 5ac34188c..8e89e61ea 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -18,8 +18,6 @@ package com.android.inputmethod.keyboard.internal;
import android.util.Log;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
import java.util.ArrayList;
public final class PointerTrackerQueue {
@@ -37,7 +35,7 @@ public final class PointerTrackerQueue {
// Note: {@link #mExpandableArrayOfActivePointers} and {@link #mArraySize} are synchronized by
// {@link #mExpandableArrayOfActivePointers}
private final ArrayList<Element> mExpandableArrayOfActivePointers =
- CollectionUtils.newArrayList(INITIAL_CAPACITY);
+ new ArrayList<>(INITIAL_CAPACITY);
private int mArraySize = 0;
public int size() {
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 54bc29559..eb8b34ccd 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -16,14 +16,14 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.latin.settings.SettingsValues;
-
import android.content.Context;
import android.media.AudioManager;
import android.os.Vibrator;
import android.view.HapticFeedbackConstants;
import android.view.View;
+import com.android.inputmethod.latin.settings.SettingsValues;
+
/**
* This class gathers audio feedback and haptic feedback functions.
*
@@ -86,40 +86,41 @@ public final class AudioAndHapticFeedbackManager {
if (mAudioManager == null) {
return;
}
- if (mSoundOn) {
- final int sound;
- switch (code) {
- case Constants.CODE_DELETE:
- sound = AudioManager.FX_KEYPRESS_DELETE;
- break;
- case Constants.CODE_ENTER:
- sound = AudioManager.FX_KEYPRESS_RETURN;
- break;
- case Constants.CODE_SPACE:
- sound = AudioManager.FX_KEYPRESS_SPACEBAR;
- break;
- default:
- sound = AudioManager.FX_KEYPRESS_STANDARD;
- break;
- }
- mAudioManager.playSoundEffect(sound, mSettingsValues.mKeypressSoundVolume);
+ if (!mSoundOn) {
+ return;
+ }
+ final int sound;
+ switch (code) {
+ case Constants.CODE_DELETE:
+ sound = AudioManager.FX_KEYPRESS_DELETE;
+ break;
+ case Constants.CODE_ENTER:
+ sound = AudioManager.FX_KEYPRESS_RETURN;
+ break;
+ case Constants.CODE_SPACE:
+ sound = AudioManager.FX_KEYPRESS_SPACEBAR;
+ break;
+ default:
+ sound = AudioManager.FX_KEYPRESS_STANDARD;
+ break;
}
+ mAudioManager.playSoundEffect(sound, mSettingsValues.mKeypressSoundVolume);
}
public void performHapticFeedback(final View viewToPerformHapticFeedbackOn) {
if (!mSettingsValues.mVibrateOn) {
return;
}
- if (mSettingsValues.mKeypressVibrationDuration < 0) {
- // Go ahead with the system default
- if (viewToPerformHapticFeedbackOn != null) {
- viewToPerformHapticFeedbackOn.performHapticFeedback(
- HapticFeedbackConstants.KEYBOARD_TAP,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
- }
+ if (mSettingsValues.mKeypressVibrationDuration >= 0) {
+ vibrate(mSettingsValues.mKeypressVibrationDuration);
return;
}
- vibrate(mSettingsValues.mKeypressVibrationDuration);
+ // Go ahead with the system default
+ if (viewToPerformHapticFeedbackOn != null) {
+ viewToPerformHapticFeedbackOn.performHapticFeedback(
+ HapticFeedbackConstants.KEYBOARD_TAP,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
+ }
}
public void onSettingsChanged(final SettingsValues settingsValues) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index e7ab02ac1..543f74fc4 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -30,7 +30,6 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.makedict.WordProperty;
import com.android.inputmethod.latin.settings.NativeSuggestOptions;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.FileUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LanguageModelParam;
@@ -104,8 +103,7 @@ public final class BinaryDictionary extends Dictionary {
private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
- private final SparseArray<DicTraverseSession> mDicTraverseSessions =
- CollectionUtils.newSparseArray();
+ private final SparseArray<DicTraverseSession> mDicTraverseSessions = new SparseArray<>();
// TODO: There should be a way to remove used DicTraverseSession objects from
// {@code mDicTraverseSessions}.
@@ -185,13 +183,15 @@ public final class BinaryDictionary extends Dictionary {
private static native void getHeaderInfoNative(long dict, int[] outHeaderSize,
int[] outFormatVersion, ArrayList<int[]> outAttributeKeys,
ArrayList<int[]> outAttributeValues);
- private static native void flushNative(long dict, String filePath);
+ private static native boolean flushNative(long dict, String filePath);
private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
- private static native void flushWithGCNative(long dict, String filePath);
+ private static native boolean flushWithGCNative(long dict, String filePath);
private static native void closeNative(long dict);
private static native int getFormatVersionNative(long dict);
private static native int getProbabilityNative(long dict, int[] word);
- private static native int getBigramProbabilityNative(long dict, int[] word0, int[] word1);
+ private static native int getMaxProbabilityOfExactMatchesNative(long dict, int[] word);
+ private static native int getBigramProbabilityNative(long dict, int[] word0,
+ boolean isBeginningOfSentence, int[] word1);
private static native void getWordPropertyNative(long dict, int[] word,
int[] outCodePoints, boolean[] outFlags, int[] outProbabilityInfo,
ArrayList<int[]> outBigramTargets, ArrayList<int[]> outBigramProbabilityInfo,
@@ -200,15 +200,18 @@ public final class BinaryDictionary extends Dictionary {
private static native void getSuggestionsNative(long dict, long proximityInfo,
long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions,
- int[] prevWordCodePointArray, int[] outputSuggestionCount, int[] outputCodePoints,
- int[] outputScores, int[] outputIndices, int[] outputTypes,
- int[] outputAutoCommitFirstWordConfidence, float[] inOutLanguageWeight);
- private static native void addUnigramWordNative(long dict, int[] word, int probability,
- int[] shortcutTarget, int shortcutProbability, boolean isNotAWord,
- boolean isBlacklisted, int timestamp);
- private static native void addBigramWordsNative(long dict, int[] word0, int[] word1,
- int probability, int timestamp);
- private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
+ int[] prevWordCodePointArray, boolean isBeginningOfSentence,
+ int[] outputSuggestionCount, int[] outputCodePoints, int[] outputScores,
+ int[] outputIndices, int[] outputTypes, int[] outputAutoCommitFirstWordConfidence,
+ float[] inOutLanguageWeight);
+ private static native boolean addUnigramWordNative(long dict, int[] word, int probability,
+ int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence,
+ boolean isNotAWord, boolean isBlacklisted, int timestamp);
+ private static native boolean removeUnigramWordNative(long dict, int[] word);
+ private static native boolean addBigramWordsNative(long dict, int[] word0,
+ boolean isBeginningOfSentence, int[] word1, int probability, int timestamp);
+ private static native boolean removeBigramWordsNative(long dict, int[] word0,
+ boolean isBeginningOfSentence, int[] word1);
private static native int addMultipleDictionaryEntriesNative(long dict,
LanguageModelParam[] languageModelParams, int startIndex);
private static native String getPropertyNative(long dict, String query);
@@ -245,11 +248,11 @@ public final class BinaryDictionary extends Dictionary {
}
final int[] outHeaderSize = new int[1];
final int[] outFormatVersion = new int[1];
- final ArrayList<int[]> outAttributeKeys = CollectionUtils.newArrayList();
- final ArrayList<int[]> outAttributeValues = CollectionUtils.newArrayList();
+ final ArrayList<int[]> outAttributeKeys = new ArrayList<>();
+ final ArrayList<int[]> outAttributeValues = new ArrayList<>();
getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys,
outAttributeValues);
- final HashMap<String, String> attributes = new HashMap<String, String>();
+ final HashMap<String, String> attributes = new HashMap<>();
for (int i = 0; i < outAttributeKeys.size(); i++) {
final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray(
outAttributeKeys.get(i));
@@ -290,6 +293,7 @@ public final class BinaryDictionary extends Dictionary {
}
mNativeSuggestOptions.setIsGesture(isGesture);
+ mNativeSuggestOptions.setBlockOffensiveWords(blockOffensiveWords);
mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions);
if (inOutLanguageWeight != null) {
mInputOutputLanguageWeight[0] = inOutLanguageWeight[0];
@@ -301,14 +305,15 @@ public final class BinaryDictionary extends Dictionary {
getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(),
inputPointers.getYCoordinates(), inputPointers.getTimes(),
inputPointers.getPointerIds(), mInputCodePoints, inputSize,
- mNativeSuggestOptions.getOptions(), prevWordCodePointArray, mOutputSuggestionCount,
+ mNativeSuggestOptions.getOptions(), prevWordCodePointArray,
+ prevWordsInfo.mIsBeginningOfSentence, mOutputSuggestionCount,
mOutputCodePoints, mOutputScores, mSpaceIndices, mOutputTypes,
mOutputAutoCommitFirstWordConfidence, mInputOutputLanguageWeight);
if (inOutLanguageWeight != null) {
inOutLanguageWeight[0] = mInputOutputLanguageWeight[0];
}
final int count = mOutputSuggestionCount[0];
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
for (int j = 0; j < count; ++j) {
final int start = j * MAX_WORD_LENGTH;
int len = 0;
@@ -316,21 +321,8 @@ public final class BinaryDictionary extends Dictionary {
++len;
}
if (len > 0) {
- final int flags = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_FLAGS;
- if (blockOffensiveWords
- && 0 != (flags & SuggestedWordInfo.KIND_FLAG_POSSIBLY_OFFENSIVE)
- && 0 == (flags & SuggestedWordInfo.KIND_FLAG_EXACT_MATCH)) {
- // If we block potentially offensive words, and if the word is possibly
- // offensive, then we don't output it unless it's also an exact match.
- continue;
- }
- final int kind = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_KIND;
- final int score = SuggestedWordInfo.KIND_WHITELIST == kind
- ? SuggestedWordInfo.MAX_SCORE : mOutputScores[j];
- // TODO: check that all users of the `kind' parameter are ready to accept
- // flags too and pass mOutputTypes[j] instead of kind
suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
- score, kind, this /* sourceDict */,
+ mOutputScores[j], mOutputTypes[j], this /* sourceDict */,
mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
mOutputAutoCommitFirstWordConfidence[0]));
}
@@ -347,29 +339,37 @@ public final class BinaryDictionary extends Dictionary {
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
return getFrequency(word) != NOT_A_PROBABILITY;
}
@Override
public int getFrequency(final String word) {
- if (word == null) return NOT_A_PROBABILITY;
+ if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
int[] codePoints = StringUtils.toCodePointArray(word);
return getProbabilityNative(mNativeDict, codePoints);
}
+ @Override
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
+ int[] codePoints = StringUtils.toCodePointArray(word);
+ return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints);
+ }
+
@UsedForTesting
public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY;
}
public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) {
- if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) {
+ if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
return NOT_A_PROBABILITY;
}
final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
final int[] codePoints1 = StringUtils.toCodePointArray(word);
- return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1);
+ return getBigramProbabilityNative(mNativeDict, codePoints0,
+ prevWordsInfo.mIsBeginningOfSentence, codePoints1);
}
public WordProperty getWordProperty(final String word) {
@@ -381,10 +381,10 @@ public final class BinaryDictionary extends Dictionary {
final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT];
final int[] outProbabilityInfo =
new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT];
- final ArrayList<int[]> outBigramTargets = CollectionUtils.newArrayList();
- final ArrayList<int[]> outBigramProbabilityInfo = CollectionUtils.newArrayList();
- final ArrayList<int[]> outShortcutTargets = CollectionUtils.newArrayList();
- final ArrayList<Integer> outShortcutProbabilities = CollectionUtils.newArrayList();
+ final ArrayList<int[]> outBigramTargets = new ArrayList<>();
+ final ArrayList<int[]> outBigramProbabilityInfo = new ArrayList<>();
+ final ArrayList<int[]> outShortcutTargets = new ArrayList<>();
+ final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>();
getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo,
outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
outShortcutProbabilities);
@@ -419,42 +419,66 @@ public final class BinaryDictionary extends Dictionary {
}
// Add a unigram entry to binary dictionary with unigram attributes in native code.
- public void addUnigramEntry(final String word, final int probability,
- final String shortcutTarget, final int shortcutProbability, final boolean isNotAWord,
+ public boolean addUnigramEntry(final String word, final int probability,
+ final String shortcutTarget, final int shortcutProbability,
+ final boolean isBeginningOfSentence, final boolean isNotAWord,
final boolean isBlacklisted, final int timestamp) {
- if (TextUtils.isEmpty(word)) {
- return;
+ if (word == null || (word.isEmpty() && !isBeginningOfSentence)) {
+ return false;
}
final int[] codePoints = StringUtils.toCodePointArray(word);
final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
StringUtils.toCodePointArray(shortcutTarget) : null;
- addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
- shortcutProbability, isNotAWord, isBlacklisted, timestamp);
+ if (!addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
+ shortcutProbability, isBeginningOfSentence, isNotAWord, isBlacklisted, timestamp)) {
+ return false;
+ }
+ mHasUpdated = true;
+ return true;
+ }
+
+ // Remove a unigram entry from the binary dictionary in native code.
+ public boolean removeUnigramEntry(final String word) {
+ if (TextUtils.isEmpty(word)) {
+ return false;
+ }
+ final int[] codePoints = StringUtils.toCodePointArray(word);
+ if (!removeUnigramWordNative(mNativeDict, codePoints)) {
+ return false;
+ }
mHasUpdated = true;
+ return true;
}
// Add an n-gram entry to the binary dictionary with timestamp in native code.
- public void addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
- final int probability,
- final int timestamp) {
- if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) {
- return;
+ public boolean addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
+ final int probability, final int timestamp) {
+ if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
+ return false;
}
final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
final int[] codePoints1 = StringUtils.toCodePointArray(word);
- addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp);
+ if (!addBigramWordsNative(mNativeDict, codePoints0, prevWordsInfo.mIsBeginningOfSentence,
+ codePoints1, probability, timestamp)) {
+ return false;
+ }
mHasUpdated = true;
+ return true;
}
// Remove an n-gram entry from the binary dictionary in native code.
- public void removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) {
- if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) {
- return;
+ public boolean removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) {
+ if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
+ return false;
}
final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
final int[] codePoints1 = StringUtils.toCodePointArray(word);
- removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
+ if (!removeBigramWordsNative(mNativeDict, codePoints0, prevWordsInfo.mIsBeginningOfSentence,
+ codePoints1)) {
+ return false;
+ }
mHasUpdated = true;
+ return true;
}
public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
@@ -484,26 +508,33 @@ public final class BinaryDictionary extends Dictionary {
}
// Flush to dict file if the dictionary has been updated.
- public void flush() {
- if (!isValidDictionary()) return;
+ public boolean flush() {
+ if (!isValidDictionary()) return false;
if (mHasUpdated) {
- flushNative(mNativeDict, mDictFilePath);
+ if (!flushNative(mNativeDict, mDictFilePath)) {
+ return false;
+ }
reopen();
}
+ return true;
}
// Run GC and flush to dict file if the dictionary has been updated.
- public void flushWithGCIfHasUpdated() {
+ public boolean flushWithGCIfHasUpdated() {
if (mHasUpdated) {
- flushWithGC();
+ return flushWithGC();
}
+ return true;
}
// Run GC and flush to dict file.
- public void flushWithGC() {
- if (!isValidDictionary()) return;
- flushWithGCNative(mNativeDict, mDictFilePath);
+ public boolean flushWithGC() {
+ if (!isValidDictionary()) return false;
+ if (!flushWithGCNative(mNativeDict, mDictFilePath)) {
+ return false;
+ }
reopen();
+ return true;
}
/**
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 72757e086..10b1f1b77 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -29,7 +29,6 @@ import android.util.Log;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
import com.android.inputmethod.dictionarypack.MD5Calculator;
-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;
@@ -44,8 +43,8 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -165,7 +164,7 @@ public final class BinaryDictionaryFileDumper {
if (cursor.getCount() <= 0 || !cursor.moveToFirst()) {
return Collections.<WordListInfo>emptyList();
}
- final ArrayList<WordListInfo> list = CollectionUtils.newArrayList();
+ final ArrayList<WordListInfo> list = new ArrayList<>();
do {
final String wordListId = cursor.getString(0);
final String wordListLocale = cursor.getString(1);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 4c49cb31c..867c18686 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -24,7 +24,6 @@ import android.util.Log;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
@@ -160,7 +159,7 @@ final public class BinaryDictionaryGetter {
public static File[] getCachedWordLists(final String locale, final Context context) {
final File[] directoryList = DictionaryInfoUtils.getCachedDirectoryList(context);
if (null == directoryList) return EMPTY_FILE_ARRAY;
- final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap();
+ final HashMap<String, FileAndMatchLevel> cacheFiles = new HashMap<>();
for (File directory : directoryList) {
if (!directory.isDirectory()) continue;
final String dirLocale =
@@ -273,7 +272,7 @@ final public class BinaryDictionaryGetter {
final DictPackSettings dictPackSettings = new DictPackSettings(context);
boolean foundMainDict = false;
- final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList();
+ final ArrayList<AssetFileAddress> fileList = new ArrayList<>();
// cachedWordLists may not be null, see doc for getCachedDictionaryList
for (final File f : cachedWordLists) {
final String wordListId = DictionaryInfoUtils.getWordListIdFromFileName(f.getName());
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 67ca59540..f9339361a 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -158,6 +158,10 @@ public final class Constants {
// A hint on how many characters to cache from the TextView. A good value of this is given by
// how many characters we need to be able to almost always find the caps mode.
public static final int EDITOR_CONTENTS_CACHE_SIZE = 1024;
+ // How many characters we accept for the recapitalization functionality. This needs to be
+ // large enough for all reasonable purposes, but avoid purposeful attacks. 100k sounds about
+ // right for this.
+ public static final int MAX_CHARACTERS_FOR_RECAPITALIZATION = 1024 * 100;
// Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
@@ -192,7 +196,6 @@ public final class Constants {
public static final int CODE_SPACE = ' ';
public static final int CODE_PERIOD = '.';
public static final int CODE_COMMA = ',';
- public static final int CODE_ARMENIAN_PERIOD = 0x0589;
public static final int CODE_DASH = '-';
public static final int CODE_SINGLE_QUOTE = '\'';
public static final int CODE_DOUBLE_QUOTE = '"';
@@ -208,6 +211,11 @@ public final class Constants {
public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
public static final int CODE_CLOSING_CURLY_BRACKET = '}';
public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
+ public static final int CODE_INVERTED_QUESTION_MARK = 0xBF; // ¿
+ public static final int CODE_INVERTED_EXCLAMATION_MARK = 0xA1; // ¡
+
+ public static final String REGEXP_PERIOD = "\\.";
+ public static final String STRING_SPACE = " ";
/**
* Special keys code. Must be negative.
@@ -249,14 +257,16 @@ public final class Constants {
case CODE_LANGUAGE_SWITCH: return "languageSwitch";
case CODE_EMOJI: return "emoji";
case CODE_SHIFT_ENTER: return "shiftEnter";
+ case CODE_ALPHA_FROM_EMOJI: return "alpha";
case CODE_UNSPECIFIED: return "unspec";
case CODE_TAB: return "tab";
case CODE_ENTER: return "enter";
- case CODE_ALPHA_FROM_EMOJI: return "alpha";
+ case CODE_SPACE: return "space";
default:
- if (code < CODE_SPACE) return String.format("'\\u%02x'", code);
- if (code < 0x100) return String.format("'%c'", code);
- return String.format("'\\u%04x'", code);
+ if (code < CODE_SPACE) return String.format("\\u%02X", code);
+ if (code < 0x100) return String.format("%c", code);
+ if (code < 0x10000) return String.format("\\u%04X", code);
+ return String.format("\\U%05X", code);
}
}
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 3fb76b142..96160fa4e 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -31,7 +31,6 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.personalization.AccountUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.ExecutorUtils;
import com.android.inputmethod.latin.utils.StringUtils;
@@ -75,11 +74,6 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
*/
private final boolean mUseFirstLastBigrams;
- private ContactsBinaryDictionary(final Context context, final Locale locale,
- final File dictFile) {
- this(context, locale, dictFile, NAME);
- }
-
protected ContactsBinaryDictionary(final Context context, final Locale locale,
final File dictFile, final String name) {
super(context, getDictName(name, locale, dictFile), locale, Dictionary.TYPE_CONTACTS,
@@ -91,8 +85,8 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
@UsedForTesting
public static ContactsBinaryDictionary getDictionary(final Context context, final Locale locale,
- final File dictFile) {
- return new ContactsBinaryDictionary(context, locale, dictFile);
+ final File dictFile, final String dictNamePrefix) {
+ return new ContactsBinaryDictionary(context, locale, dictFile, dictNamePrefix + NAME);
}
private synchronized void registerObserver(final Context context) {
@@ -180,7 +174,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private void addWordsLocked(final Cursor cursor) {
int count = 0;
- final ArrayList<String> names = CollectionUtils.newArrayList();
+ final ArrayList<String> names = new ArrayList<>();
while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) {
String name = cursor.getString(INDEX_NAME);
if (isValidName(name)) {
@@ -224,7 +218,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
*/
private void addNameLocked(final String name) {
int len = StringUtils.codePointCount(name);
- PrevWordsInfo prevWordsInfo = new PrevWordsInfo(null);
+ PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
// TODO: Better tokenization for non-Latin writing systems
for (int i = 0; i < len; i++) {
if (Character.isLetter(name.codePointAt(i))) {
@@ -298,7 +292,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
if (null == cursor) {
return false;
}
- final ArrayList<String> names = CollectionUtils.newArrayList();
+ final ArrayList<String> names = new ArrayList<>();
try {
if (cursor.moveToFirst()) {
while (!cursor.isAfterLast()) {
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index aab16653e..b55ed125f 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -85,16 +86,28 @@ public abstract class Dictionary {
final int sessionId, final float[] inOutLanguageWeight);
/**
- * Checks if the given word occurs in the dictionary
+ * Checks if the given word has to be treated as a valid word. Please note that some
+ * dictionaries have entries that should be treated as invalid words.
* @param word the word to search for. The search should be case-insensitive.
- * @return true if the word exists, false otherwise
+ * @return true if the word is valid, false otherwise
*/
- abstract public boolean isValidWord(final String word);
+ public boolean isValidWord(final String word) {
+ return isInDictionary(word);
+ }
+
+ /**
+ * Checks if the given word is in the dictionary regardless of it being valid or not.
+ */
+ abstract public boolean isInDictionary(final String word);
public int getFrequency(final String word) {
return NOT_A_PROBABILITY;
}
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ return NOT_A_PROBABILITY;
+ }
+
/**
* Compares the contents of the character array with the typed word and returns true if they
* are the same.
@@ -161,7 +174,7 @@ public abstract class Dictionary {
}
@Override
- public boolean isValidWord(String word) {
+ public boolean isInDictionary(String word) {
return false;
}
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index e6e4e0938..89d61ce2a 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -20,7 +20,6 @@ import android.util.Log;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.Collection;
@@ -36,22 +35,22 @@ public final class DictionaryCollection extends Dictionary {
public DictionaryCollection(final String dictType) {
super(dictType);
- mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
+ mDictionaries = new CopyOnWriteArrayList<>();
}
public DictionaryCollection(final String dictType, final Dictionary... dictionaries) {
super(dictType);
if (null == dictionaries) {
- mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
+ mDictionaries = new CopyOnWriteArrayList<>();
} else {
- mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
+ mDictionaries = new CopyOnWriteArrayList<>(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
}
public DictionaryCollection(final String dictType, final Collection<Dictionary> dictionaries) {
super(dictType);
- mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
+ mDictionaries = new CopyOnWriteArrayList<>(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
@@ -67,7 +66,7 @@ public final class DictionaryCollection extends Dictionary {
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
prevWordsInfo, proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
sessionId, inOutLanguageWeight);
- if (null == suggestions) suggestions = CollectionUtils.newArrayList();
+ if (null == suggestions) suggestions = new ArrayList<>();
final int length = dictionaries.size();
for (int i = 1; i < length; ++ i) {
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
@@ -79,9 +78,9 @@ public final class DictionaryCollection extends Dictionary {
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
for (int i = mDictionaries.size() - 1; i >= 0; --i)
- if (mDictionaries.get(i).isValidWord(word)) return true;
+ if (mDictionaries.get(i).isInDictionary(word)) return true;
return false;
}
@@ -90,9 +89,17 @@ public final class DictionaryCollection extends Dictionary {
int maxFreq = -1;
for (int i = mDictionaries.size() - 1; i >= 0; --i) {
final int tempFreq = mDictionaries.get(i).getFrequency(word);
- if (tempFreq >= maxFreq) {
- maxFreq = tempFreq;
- }
+ maxFreq = Math.max(tempFreq, maxFreq);
+ }
+ return maxFreq;
+ }
+
+ @Override
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ int maxFreq = -1;
+ for (int i = mDictionaries.size() - 1; i >= 0; --i) {
+ final int tempFreq = mDictionaries.get(i).getMaxFrequencyOfExactMatches(word);
+ maxFreq = Math.max(tempFreq, maxFreq);
}
return maxFreq;
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 301b832b6..e6e2bcbc7 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -19,14 +19,18 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
+import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.personalization.ContextualDictionary;
+import com.android.inputmethod.latin.personalization.PersonalizationDataChunk;
import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
-import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary;
import com.android.inputmethod.latin.utils.ExecutorUtils;
import com.android.inputmethod.latin.utils.LanguageModelParam;
import com.android.inputmethod.latin.utils.SuggestionResults;
@@ -37,16 +41,17 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
// TODO: Consolidate dictionaries in native code.
-public class DictionaryFacilitatorForSuggest {
- public static final String TAG = DictionaryFacilitatorForSuggest.class.getSimpleName();
+public class DictionaryFacilitator {
+ public static final String TAG = DictionaryFacilitator.class.getSimpleName();
// HACK: This threshold is being used when adding a capitalized entry in the User History
// dictionary.
@@ -57,8 +62,9 @@ public class DictionaryFacilitatorForSuggest {
private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
// To synchronize assigning mDictionaries to ensure closing dictionaries.
private final Object mLock = new Object();
+ private final DistracterFilter mDistracterFilter;
- private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTION =
+ private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS =
new String[] {
Dictionary.TYPE_MAIN,
Dictionary.TYPE_USER_HISTORY,
@@ -68,8 +74,8 @@ public class DictionaryFacilitatorForSuggest {
Dictionary.TYPE_CONTEXTUAL
};
- private static final Map<String, Class<? extends ExpandableBinaryDictionary>>
- DICT_TYPE_TO_CLASS = CollectionUtils.newHashMap();
+ public static final Map<String, Class<? extends ExpandableBinaryDictionary>>
+ DICT_TYPE_TO_CLASS = new HashMap<>();
static {
DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class);
@@ -81,11 +87,11 @@ public class DictionaryFacilitatorForSuggest {
private static final String DICT_FACTORY_METHOD_NAME = "getDictionary";
private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
- new Class[] { Context.class, Locale.class, File.class };
+ new Class[] { Context.class, Locale.class, File.class, String.class };
private static final String[] SUB_DICT_TYPES =
- Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTION, 1 /* start */,
- DICT_TYPES_ORDERED_TO_GET_SUGGESTION.length);
+ Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS, 1 /* start */,
+ DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length);
/**
* Class contains dictionaries for a locale.
@@ -94,7 +100,7 @@ public class DictionaryFacilitatorForSuggest {
public final Locale mLocale;
private Dictionary mMainDict;
public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
- CollectionUtils.newConcurrentHashMap();
+ new ConcurrentHashMap<>();
public Dictionaries() {
mLocale = null;
@@ -162,14 +168,25 @@ public class DictionaryFacilitatorForSuggest {
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
}
- public DictionaryFacilitatorForSuggest() {}
+ public DictionaryFacilitator() {
+ mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER;
+ }
+
+ public DictionaryFacilitator(final DistracterFilter distracterFilter) {
+ mDistracterFilter = distracterFilter;
+ }
+
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
+ mDistracterFilter.updateEnabledSubtypes(enabledSubtypes);
+ }
public Locale getLocale() {
return mDictionaries.mLocale;
}
private static ExpandableBinaryDictionary getSubDict(final String dictType,
- final Context context, final Locale locale, final File dictFile) {
+ final Context context, final Locale locale, final File dictFile,
+ final String dictNamePrefix) {
final Class<? extends ExpandableBinaryDictionary> dictClass =
DICT_TYPE_TO_CLASS.get(dictType);
if (dictClass == null) {
@@ -179,7 +196,7 @@ public class DictionaryFacilitatorForSuggest {
final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME,
DICT_FACTORY_METHOD_ARG_TYPES);
final Object dict = factoryMethod.invoke(null /* obj */,
- new Object[] { context, locale, dictFile });
+ new Object[] { context, locale, dictFile, dictNamePrefix });
return (ExpandableBinaryDictionary) dict;
} catch (final NoSuchMethodException | SecurityException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException e) {
@@ -192,11 +209,20 @@ public class DictionaryFacilitatorForSuggest {
final boolean useContactsDict, final boolean usePersonalizedDicts,
final boolean forceReloadMainDictionary,
final DictionaryInitializationListener listener) {
+ resetDictionariesWithDictNamePrefix(context, newLocale, useContactsDict,
+ usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */);
+ }
+
+ public void resetDictionariesWithDictNamePrefix(final Context context, final Locale newLocale,
+ final boolean useContactsDict, final boolean usePersonalizedDicts,
+ final boolean forceReloadMainDictionary,
+ final DictionaryInitializationListener listener,
+ final String dictNamePrefix) {
final boolean localeHasBeenChanged = !newLocale.equals(mDictionaries.mLocale);
// We always try to have the main dictionary. Other dictionaries can be unused.
final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary;
// TODO: Make subDictTypesToUse configurable by resource or a static final list.
- final Set<String> subDictTypesToUse = CollectionUtils.newHashSet();
+ final HashSet<String> subDictTypesToUse = new HashSet<>();
if (useContactsDict) {
subDictTypesToUse.add(Dictionary.TYPE_CONTACTS);
}
@@ -215,7 +241,7 @@ public class DictionaryFacilitatorForSuggest {
newMainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
}
- final Map<String, ExpandableBinaryDictionary> subDicts = CollectionUtils.newHashMap();
+ final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
for (final String dictType : SUB_DICT_TYPES) {
if (!subDictTypesToUse.contains(dictType)) {
// This dictionary will not be used.
@@ -227,7 +253,8 @@ public class DictionaryFacilitatorForSuggest {
dict = mDictionaries.getSubDict(dictType);
} else {
// Start to use new dictionary.
- dict = getSubDict(dictType, context, newLocale, null /* dictFile */);
+ dict = getSubDict(dictType, context, newLocale, null /* dictFile */,
+ dictNamePrefix);
}
subDicts.put(dictType, dict);
}
@@ -288,7 +315,7 @@ public class DictionaryFacilitatorForSuggest {
final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles,
final Map<String, Map<String, String>> additionalDictAttributes) {
Dictionary mainDictionary = null;
- final Map<String, ExpandableBinaryDictionary> subDicts = CollectionUtils.newHashMap();
+ final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
for (final String dictType : dictionaryTypes) {
if (dictType.equals(Dictionary.TYPE_MAIN)) {
@@ -296,7 +323,7 @@ public class DictionaryFacilitatorForSuggest {
} else {
final File dictFile = dictionaryFiles.get(dictType);
final ExpandableBinaryDictionary dict = getSubDict(
- dictType, context, locale, dictFile);
+ dictType, context, locale, dictFile, "" /* dictNamePrefix */);
if (additionalDictAttributes.containsKey(dictType)) {
dict.clearAndFlushDictionaryWithAdditionalAttributes(
additionalDictAttributes.get(dictType));
@@ -318,9 +345,10 @@ public class DictionaryFacilitatorForSuggest {
dictionaries = mDictionaries;
mDictionaries = new Dictionaries();
}
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
dictionaries.closeDict(dictType);
}
+ mDistracterFilter.close();
}
// The main dictionary could have been loaded asynchronously. Don't cache the return value
@@ -392,7 +420,7 @@ public class DictionaryFacilitatorForSuggest {
if (userHistoryDictionary == null) {
return;
}
- final int maxFreq = getMaxFrequency(word);
+ final int maxFreq = getFrequency(word);
if (maxFreq == 0 && blockPotentiallyOffensive) {
return;
}
@@ -432,7 +460,7 @@ public class DictionaryFacilitatorForSuggest {
// We don't add words with 0-frequency (assuming they would be profanity etc.).
final boolean isValid = maxFreq > 0;
UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWordsInfo, secondWord,
- isValid, timeStampInSeconds);
+ isValid, timeStampInSeconds, mDistracterFilter);
}
public void cancelAddingUserHistory(final PrevWordsInfo prevWordsInfo,
@@ -453,7 +481,7 @@ public class DictionaryFacilitatorForSuggest {
final SuggestionResults suggestionResults =
new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS);
final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT };
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
final Dictionary dictionary = dictionaries.getDict(dictType);
if (null == dictionary) continue;
final ArrayList<SuggestedWordInfo> dictionarySuggestions =
@@ -486,7 +514,7 @@ public class DictionaryFacilitatorForSuggest {
return false;
}
final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
final Dictionary dictionary = dictionaries.getDict(dictType);
// Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
// would be immutable once it's finished initializing, but concretely a null test is
@@ -500,16 +528,22 @@ public class DictionaryFacilitatorForSuggest {
return false;
}
- private int getMaxFrequency(final String word) {
+ private int getFrequencyInternal(final String word,
+ final boolean isGettingMaxFrequencyOfExactMatches) {
if (TextUtils.isEmpty(word)) {
return Dictionary.NOT_A_PROBABILITY;
}
- int maxFreq = -1;
+ int maxFreq = Dictionary.NOT_A_PROBABILITY;
final Dictionaries dictionaries = mDictionaries;
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
final Dictionary dictionary = dictionaries.getDict(dictType);
if (dictionary == null) continue;
- final int tempFreq = dictionary.getFrequency(word);
+ final int tempFreq;
+ if (isGettingMaxFrequencyOfExactMatches) {
+ tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
+ } else {
+ tempFreq = dictionary.getFrequency(word);
+ }
if (tempFreq >= maxFreq) {
maxFreq = tempFreq;
}
@@ -517,6 +551,14 @@ public class DictionaryFacilitatorForSuggest {
return maxFreq;
}
+ public int getFrequency(final String word) {
+ return getFrequencyInternal(word, false /* isGettingMaxFrequencyOfExactMatches */);
+ }
+
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ return getFrequencyInternal(word, true /* isGettingMaxFrequencyOfExactMatches */);
+ }
+
public void clearUserHistoryDictionary() {
final ExpandableBinaryDictionary userHistoryDict =
mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
@@ -537,13 +579,26 @@ public class DictionaryFacilitatorForSuggest {
personalizationDict.clear();
}
- public void addMultipleDictionaryEntriesToPersonalizationDictionary(
- final ArrayList<LanguageModelParam> languageModelParams,
+ public void addEntriesToPersonalizationDictionary(
+ final PersonalizationDataChunk personalizationDataChunk,
+ final SpacingAndPunctuations spacingAndPunctuations,
final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
final ExpandableBinaryDictionary personalizationDict =
mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
- if (personalizationDict == null || languageModelParams == null
- || languageModelParams.isEmpty()) {
+ if (personalizationDict == null) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ final ArrayList<LanguageModelParam> languageModelParams =
+ LanguageModelParam.createLanguageModelParamsFrom(
+ personalizationDataChunk.mTokens,
+ personalizationDataChunk.mTimestampInSeconds,
+ this /* dictionaryFacilitator */, spacingAndPunctuations,
+ new DistracterFilterCheckingIsInDictionary(
+ mDistracterFilter, personalizationDict));
+ if (languageModelParams == null || languageModelParams.isEmpty()) {
if (callback != null) {
callback.onFinished();
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index e09c309ea..59de4f82a 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -23,7 +23,6 @@ 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;
@@ -55,7 +54,7 @@ public final class DictionaryFactory {
createReadOnlyBinaryDictionary(context, locale));
}
- final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList();
+ final LinkedList<Dictionary> dictList = new LinkedList<>();
final ArrayList<AssetFileAddress> assetFileList =
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
if (null != assetFileList) {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index d67253c3b..b1966bffc 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -27,6 +27,7 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.makedict.WordProperty;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.utils.CombinedFormatUtils;
+import com.android.inputmethod.latin.utils.DistracterFilter;
import com.android.inputmethod.latin.utils.ExecutorUtils;
import com.android.inputmethod.latin.utils.FileUtils;
import com.android.inputmethod.latin.utils.LanguageModelParam;
@@ -48,12 +49,12 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
* queries in native code. This binary dictionary is written to internal storage.
*/
abstract public class ExpandableBinaryDictionary extends Dictionary {
+ private static final boolean DEBUG = false;
/** Used for Log actions from this class */
private static final String TAG = ExpandableBinaryDictionary.class.getSimpleName();
/** Whether to print debug output to log */
- private static boolean DEBUG = false;
private static final boolean DBG_STRESS_TEST = false;
private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
@@ -121,6 +122,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return mBinaryDictionary.isValidDictionary();
}
+ // TODO: Remove and always enable beginning of sentence prediction. Currently, this is enabled
+ // only for ContextualDictionary.
+ protected boolean enableBeginningOfSentencePrediction() {
+ return false;
+ }
+
/**
* Creates a new expandable binary dictionary.
*
@@ -191,7 +198,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
protected Map<String, String> getHeaderAttributeMap() {
- HashMap<String, String> attributeMap = new HashMap<String, String>();
+ HashMap<String, String> attributeMap = new HashMap<>();
if (mAdditionalAttributeMap != null) {
attributeMap.putAll(mAdditionalAttributeMap);
}
@@ -271,9 +278,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/**
* Adds unigram information of a word to the dictionary. May overwrite an existing entry.
*/
- public void addUnigramEntry(final String word, final int frequency,
+ public void addUnigramEntryWithCheckingDistracter(final String word, final int frequency,
final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
- final boolean isBlacklisted, final int timestamp) {
+ final boolean isBlacklisted, final int timestamp,
+ final DistracterFilter distracterFilter) {
reloadDictionaryIfRequired();
asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
@@ -281,6 +289,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
if (mBinaryDictionary == null) {
return;
}
+ if (distracterFilter.isDistracterToWordsInDictionaries(
+ PrevWordsInfo.EMPTY_PREV_WORDS_INFO, word, mLocale)) {
+ // The word is a distracter.
+ return;
+ }
runGCIfRequiredLocked(true /* mindsBlockByGC */);
addUnigramLocked(word, frequency, shortcutTarget, shortcutFreq,
isNotAWord, isBlacklisted, timestamp);
@@ -291,8 +304,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected void addUnigramLocked(final String word, final int frequency,
final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
final boolean isBlacklisted, final int timestamp) {
- mBinaryDictionary.addUnigramEntry(word, frequency, shortcutTarget, shortcutFreq,
- isNotAWord, isBlacklisted, timestamp);
+ if (!mBinaryDictionary.addUnigramEntry(word, frequency, shortcutTarget, shortcutFreq,
+ false /* isBeginningOfSentence */, isNotAWord, isBlacklisted, timestamp)) {
+ Log.e(TAG, "Cannot add unigram entry. word: " + word);
+ }
}
/**
@@ -315,13 +330,18 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected void addNgramEntryLocked(final PrevWordsInfo prevWordsInfo, final String word,
final int frequency, final int timestamp) {
- mBinaryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp);
+ if (!mBinaryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp)) {
+ if (DEBUG) {
+ Log.i(TAG, "Cannot add n-gram entry.");
+ Log.i(TAG, " PrevWordsInfo: " + prevWordsInfo + ", word: " + word);
+ }
+ }
}
/**
* Dynamically remove the n-gram entry in the dictionary.
*/
- public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word1) {
+ public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word) {
reloadDictionaryIfRequired();
asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
@@ -330,7 +350,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return;
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- mBinaryDictionary.removeNgramEntry(prevWordsInfo, word1);
+ if (!mBinaryDictionary.removeNgramEntry(prevWordsInfo, word)) {
+ if (DEBUG) {
+ Log.i(TAG, "Cannot remove n-gram entry.");
+ Log.i(TAG, " PrevWordsInfo: " + prevWordsInfo + ", word: " + word);
+ }
+ }
}
});
}
@@ -379,6 +404,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
if (mBinaryDictionary == null) {
return null;
}
+ if (composer.size() == 0 && prevWordsInfo.mIsBeginningOfSentence
+ && !enableBeginningOfSentencePrediction()) {
+ return null;
+ }
final ArrayList<SuggestedWordInfo> suggestions =
mBinaryDictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
blockOffensiveWords, additionalFeaturesOptions, sessionId,
@@ -401,7 +430,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
reloadDictionaryIfRequired();
boolean lockAcquired = false;
try {
@@ -411,10 +440,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
if (mBinaryDictionary == null) {
return false;
}
- return isValidWordLocked(word);
+ return isInDictionaryLocked(word);
}
} catch (final InterruptedException e) {
- Log.e(TAG, "Interrupted tryLock() in isValidWord().", e);
+ Log.e(TAG, "Interrupted tryLock() in isInDictionary().", e);
} finally {
if (lockAcquired) {
mLock.readLock().unlock();
@@ -423,11 +452,35 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return false;
}
- protected boolean isValidWordLocked(final String word) {
+ protected boolean isInDictionaryLocked(final String word) {
if (mBinaryDictionary == null) return false;
- return mBinaryDictionary.isValidWord(word);
+ return mBinaryDictionary.isInDictionary(word);
}
+ @Override
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ reloadDictionaryIfRequired();
+ boolean lockAcquired = false;
+ try {
+ lockAcquired = mLock.readLock().tryLock(
+ TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+ if (lockAcquired) {
+ if (mBinaryDictionary == null) {
+ return NOT_A_PROBABILITY;
+ }
+ return mBinaryDictionary.getMaxFrequencyOfExactMatches(word);
+ }
+ } catch (final InterruptedException e) {
+ Log.e(TAG, "Interrupted tryLock() in getMaxFrequencyOfExactMatches().", e);
+ } finally {
+ if (lockAcquired) {
+ mLock.readLock().unlock();
+ }
+ }
+ return NOT_A_PROBABILITY;
+ }
+
+
protected boolean isValidNgramLocked(final PrevWordsInfo prevWordsInfo, final String word) {
if (mBinaryDictionary == null) return false;
return mBinaryDictionary.isValidNgram(prevWordsInfo, word);
@@ -553,20 +606,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
});
}
- // TODO: Implement BinaryDictionary.isInDictionary().
- @UsedForTesting
- public boolean isInUnderlyingBinaryDictionaryForTests(final String word) {
- mLock.readLock().lock();
- try {
- if (mBinaryDictionary != null && mDictType == Dictionary.TYPE_USER_HISTORY) {
- return mBinaryDictionary.isValidWord(word);
- }
- return false;
- } finally {
- mLock.readLock().unlock();
- }
- }
-
@UsedForTesting
public void waitAllTasksForTests() {
final CountDownLatch countDownLatch = new CountDownLatch(1);
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index df4948322..e1ae3dfe3 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -16,11 +16,13 @@
package com.android.inputmethod.latin;
+import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE;
+import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT;
+
import android.text.InputType;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.StringUtils;
@@ -36,12 +38,17 @@ public final class InputAttributes {
final public String mTargetApplicationPackageName;
final public boolean mInputTypeNoAutoCorrect;
final public boolean mIsPasswordField;
- final public boolean mIsSettingsSuggestionStripOn;
+ final public boolean mShouldShowSuggestions;
final public boolean mApplicationSpecifiedCompletionOn;
final public boolean mShouldInsertSpacesAutomatically;
final private int mInputType;
+ final private EditorInfo mEditorInfo;
+ final private String mPackageNameForPrivateImeOptions;
- public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
+ public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode,
+ final String packageNameForPrivateImeOptions) {
+ mEditorInfo = editorInfo;
+ mPackageNameForPrivateImeOptions = packageNameForPrivateImeOptions;
mTargetApplicationPackageName = null != editorInfo ? editorInfo.packageName : null;
final int inputType = null != editorInfo ? editorInfo.inputType : 0;
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
@@ -63,7 +70,7 @@ public final class InputAttributes {
Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
+ " imeOptions=0x%08x", inputType, editorInfo.imeOptions));
}
- mIsSettingsSuggestionStripOn = false;
+ mShouldShowSuggestions = false;
mInputTypeNoAutoCorrect = false;
mApplicationSpecifiedCompletionOn = false;
mShouldInsertSpacesAutomatically = false;
@@ -82,13 +89,13 @@ public final class InputAttributes {
// TODO: Have a helper method in InputTypeUtils
// Make sure that passwords are not displayed in {@link SuggestionStripView}.
- final boolean noSuggestionStrip = mIsPasswordField
+ final boolean shouldSuppressSuggestions = mIsPasswordField
|| InputTypeUtils.isEmailVariation(variation)
|| InputType.TYPE_TEXT_VARIATION_URI == variation
|| InputType.TYPE_TEXT_VARIATION_FILTER == variation
|| flagNoSuggestions
|| flagAutoComplete;
- mIsSettingsSuggestionStripOn = !noSuggestionStrip;
+ mShouldShowSuggestions = !shouldSuppressSuggestions;
mShouldInsertSpacesAutomatically = InputTypeUtils.isAutoSpaceFriendlyType(inputType);
@@ -112,6 +119,15 @@ public final class InputAttributes {
return editorInfo.inputType == mInputType;
}
+ public boolean hasNoMicrophoneKeyOption() {
+ @SuppressWarnings("deprecation")
+ final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
+ null, NO_MICROPHONE_COMPAT, mEditorInfo);
+ final boolean noMicrophone = InputAttributes.inPrivateImeOptions(
+ mPackageNameForPrivateImeOptions, NO_MICROPHONE, mEditorInfo);
+ return noMicrophone || deprecatedNoMicrophone;
+ }
+
@SuppressWarnings("unused")
private void dumpFlags(final int inputType) {
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
@@ -214,7 +230,7 @@ public final class InputAttributes {
}
private static String toFlagsString(final int flags) {
- final ArrayList<String> flagsArray = CollectionUtils.newArrayList();
+ final ArrayList<String> flagsArray = new ArrayList<>();
if (0 != (flags & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS))
flagsArray.add("TYPE_TEXT_FLAG_NO_SUGGESTIONS");
if (0 != (flags & InputType.TYPE_TEXT_FLAG_MULTI_LINE))
@@ -242,7 +258,7 @@ public final class InputAttributes {
mInputType,
(mInputTypeNoAutoCorrect ? " noAutoCorrect" : ""),
(mIsPasswordField ? " password" : ""),
- (mIsSettingsSuggestionStripOn ? " suggestionStrip" : ""),
+ (mShouldShowSuggestions ? " shouldShowSuggestions" : ""),
(mApplicationSpecifiedCompletionOn ? " appSpecified" : ""),
(mShouldInsertSpacesAutomatically ? " insertSpaces" : ""),
mTargetApplicationPackageName);
diff --git a/java/src/com/android/inputmethod/latin/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java
index ea7859e60..e9e12f09f 100644
--- a/java/src/com/android/inputmethod/latin/InputView.java
+++ b/java/src/com/android/inputmethod/latin/InputView.java
@@ -23,12 +23,14 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
+import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.suggestions.MoreSuggestionsView;
import com.android.inputmethod.latin.suggestions.SuggestionStripView;
public final class InputView extends LinearLayout {
private final Rect mInputViewRect = new Rect();
+ private MainKeyboardView mMainKeyboardView;
private KeyboardTopPaddingForwarder mKeyboardTopPaddingForwarder;
private MoreSuggestionsViewCanceler mMoreSuggestionsViewCanceler;
private MotionEventForwarder<?, ?> mActiveForwarder;
@@ -41,12 +43,11 @@ public final class InputView extends LinearLayout {
protected void onFinishInflate() {
final SuggestionStripView suggestionStripView =
(SuggestionStripView)findViewById(R.id.suggestion_strip_view);
- final MainKeyboardView mainKeyboardView =
- (MainKeyboardView)findViewById(R.id.keyboard_view);
+ mMainKeyboardView = (MainKeyboardView)findViewById(R.id.keyboard_view);
mKeyboardTopPaddingForwarder = new KeyboardTopPaddingForwarder(
- mainKeyboardView, suggestionStripView);
+ mMainKeyboardView, suggestionStripView);
mMoreSuggestionsViewCanceler = new MoreSuggestionsViewCanceler(
- mainKeyboardView, suggestionStripView);
+ mMainKeyboardView, suggestionStripView);
}
public void setKeyboardTopPadding(final int keyboardTopPadding) {
@@ -54,6 +55,17 @@ public final class InputView extends LinearLayout {
}
@Override
+ protected boolean dispatchHoverEvent(final MotionEvent event) {
+ if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()
+ && mMainKeyboardView.isShowingMoreKeysPanel()) {
+ // With accessibility mode on, discard hover events while a more keys keyboard is shown.
+ // The {@link MoreKeysKeyboard} receives hover events directly from the platform.
+ return true;
+ }
+ return super.dispatchHoverEvent(event);
+ }
+
+ @Override
public boolean onInterceptTouchEvent(final MotionEvent me) {
final Rect rect = mInputViewRect;
getGlobalVisibleRect(rect);
@@ -190,7 +202,12 @@ public final class InputView extends LinearLayout {
@Override
protected boolean needsToForward(final int x, final int y) {
- return isInKeyboardTopPadding(y);
+ // Forwarding an event only when {@link MainKeyboardView} is visible.
+ // Because the visibility of {@link MainKeyboardView} is controlled by its parent
+ // view in {@link KeyboardSwitcher#setMainKeyboardFrame()}, we should check the
+ // visibility of the parent view.
+ final View mainKeyboardFrame = (View)mSenderView.getParent();
+ return mainKeyboardFrame.getVisibility() == View.VISIBLE && isInKeyboardTopPadding(y);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 9caec3e01..8cbf8379b 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -69,7 +69,7 @@ public final class LastComposedWord {
mInputPointers.copy(inputPointers);
}
mTypedWord = typedWord;
- mEvents = new ArrayList<Event>(events);
+ mEvents = new ArrayList<>(events);
mCommittedWord = committedWord;
mSeparatorString = separatorString;
mActive = true;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index ab7e66a09..b0774c49d 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -28,7 +28,6 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -38,7 +37,6 @@ import android.net.ConnectivityManager;
import android.os.Debug;
import android.os.IBinder;
import android.os.Message;
-import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
@@ -72,7 +70,7 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.inputlogic.InputLogic;
import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever;
-import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegistrar;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionaryUpdater;
import com.android.inputmethod.latin.personalization.PersonalizationHelper;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsActivity;
@@ -83,18 +81,18 @@ import com.android.inputmethod.latin.utils.ApplicationUtils;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.DialogUtils;
-import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatches;
import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
-import com.android.inputmethod.research.ResearchLogger;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
@@ -103,7 +101,7 @@ import java.util.concurrent.TimeUnit;
*/
public class LatinIME extends InputMethodService implements KeyboardActionListener,
SuggestionStripView.Listener, SuggestionStripViewAccessor,
- DictionaryFacilitatorForSuggest.DictionaryInitializationListener,
+ DictionaryFacilitator.DictionaryInitializationListener,
ImportantNoticeDialog.ImportantNoticeDialogListener {
private static final String TAG = LatinIME.class.getSimpleName();
private static final boolean TRACE = false;
@@ -122,12 +120,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final String SCHEME_PACKAGE = "package";
private final Settings mSettings;
+ private final DictionaryFacilitator mDictionaryFacilitator =
+ new DictionaryFacilitator(new DistracterFilterCheckingExactMatches(this /* context */));
+ // TODO: Move from LatinIME.
+ private final PersonalizationDictionaryUpdater mPersonalizationDictionaryUpdater =
+ new PersonalizationDictionaryUpdater(this /* context */, mDictionaryFacilitator);
private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
- this /* SuggestionStripViewAccessor */);
+ this /* SuggestionStripViewAccessor */, mDictionaryFacilitator);
// We expect to have only one decoder in almost all cases, hence the default capacity of 1.
// If it turns out we need several, it will get grown seamlessly.
- final SparseArray<HardwareEventDecoder> mHardwareEventDecoders
- = new SparseArray<HardwareEventDecoder>(1);
+ final SparseArray<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1);
private View mExtractArea;
private View mKeyPreviewBackingView;
@@ -167,6 +169,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
private static final int ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT = 2;
private static final int ARG2_UNUSED = 0;
+ private static final int ARG1_FALSE = 0;
+ private static final int ARG1_TRUE = 1;
private int mDelayUpdateSuggestions;
private int mDelayUpdateShiftState;
@@ -214,7 +218,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
case MSG_RESUME_SUGGESTIONS:
latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
latinIme.mSettings.getCurrent(),
- false /* includeResumedWordInSuggestions */);
+ msg.arg1 == ARG1_TRUE /* shouldIncludeResumedWordInSuggestions */);
break;
case MSG_REOPEN_DICTIONARIES:
latinIme.resetSuggest();
@@ -251,16 +255,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES));
}
- public void postResumeSuggestions() {
+ public void postResumeSuggestions(final boolean shouldIncludeResumedWordInSuggestions) {
final LatinIME latinIme = getOwnerInstance();
if (latinIme == null) {
return;
}
- if (!latinIme.mSettings.getCurrent().isSuggestionStripVisible()) {
+ if (!latinIme.mSettings.getCurrent()
+ .isCurrentOrientationAllowingSuggestionsPerUserSettings()) {
return;
}
removeMessages(MSG_RESUME_SUGGESTIONS);
- sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
+ sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS,
+ shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
+ 0 /* ignored */),
+ mDelayUpdateSuggestions);
}
public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
@@ -491,12 +499,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
loadSettings();
resetSuggest();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().init(this, mKeyboardSwitcher);
- ResearchLogger.getInstance().initDictionary(
- mInputLogic.mSuggest.mDictionaryFacilitator);
- }
-
// Register to receive ringer mode change and network state change.
// Also receive installation and removal of a dictionary pack.
final IntentFilter filter = new IntentFilter();
@@ -528,7 +530,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
void loadSettings() {
final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale();
final EditorInfo editorInfo = getCurrentInputEditorInfo();
- final InputAttributes inputAttributes = new InputAttributes(editorInfo, isFullscreenMode());
+ final InputAttributes inputAttributes = new InputAttributes(
+ editorInfo, isFullscreenMode(), getPackageName());
mSettings.loadSettings(this, locale, inputAttributes);
final SettingsValues currentSettingsValues = mSettings.getCurrent();
AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
@@ -538,37 +541,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!mHandler.hasPendingReopenDictionaries()) {
resetSuggestForLocale(locale);
}
- refreshPersonalizationDictionarySession();
+ mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList(
+ true /* allowsImplicitlySelectedSubtypes */));
+ refreshPersonalizationDictionarySession(currentSettingsValues);
StatsUtils.onLoadSettings(currentSettingsValues);
}
- private void refreshPersonalizationDictionarySession() {
- final DictionaryFacilitatorForSuggest dictionaryFacilitator =
- mInputLogic.mSuggest.mDictionaryFacilitator;
+ private void refreshPersonalizationDictionarySession(
+ final SettingsValues currentSettingsValues) {
+ mPersonalizationDictionaryUpdater.onLoadSettings(
+ currentSettingsValues.mUsePersonalizedDicts,
+ mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes());
final boolean shouldKeepUserHistoryDictionaries;
- final boolean shouldKeepPersonalizationDictionaries;
if (mSettings.getCurrent().mUsePersonalizedDicts) {
shouldKeepUserHistoryDictionaries = true;
- // TODO: Eliminate this restriction
- shouldKeepPersonalizationDictionaries =
- mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes();
} else {
shouldKeepUserHistoryDictionaries = false;
- shouldKeepPersonalizationDictionaries = false;
}
if (!shouldKeepUserHistoryDictionaries) {
// Remove user history dictionaries.
PersonalizationHelper.removeAllUserHistoryDictionaries(this);
- dictionaryFacilitator.clearUserHistoryDictionary();
- }
- if (!shouldKeepPersonalizationDictionaries) {
- // Remove personalization dictionaries.
- PersonalizationHelper.removeAllPersonalizationDictionaries(this);
- PersonalizationDictionarySessionRegistrar.resetAll(this);
- } else {
- final DistracterFilter distracterFilter = createDistracterFilter();
- PersonalizationDictionarySessionRegistrar.init(
- this, dictionaryFacilitator, distracterFilter);
+ mDictionaryFacilitator.clearUserHistoryDictionary();
}
}
@@ -606,13 +599,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
* @param locale the locale
*/
private void resetSuggestForLocale(final Locale locale) {
- final DictionaryFacilitatorForSuggest dictionaryFacilitator =
- mInputLogic.mSuggest.mDictionaryFacilitator;
final SettingsValues settingsValues = mSettings.getCurrent();
- dictionaryFacilitator.resetDictionaries(this /* context */, locale,
+ mDictionaryFacilitator.resetDictionaries(this /* context */, locale,
settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
false /* forceReloadMainDictionary */, this);
- if (settingsValues.mCorrectionEnabled) {
+ if (settingsValues.mAutoCorrectionEnabled) {
mInputLogic.mSuggest.setAutoCorrectionThreshold(
settingsValues.mAutoCorrectionThreshold);
}
@@ -622,27 +613,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
* Reset suggest by loading the main dictionary of the current locale.
*/
/* package private */ void resetSuggestMainDict() {
- final DictionaryFacilitatorForSuggest dictionaryFacilitator =
- mInputLogic.mSuggest.mDictionaryFacilitator;
final SettingsValues settingsValues = mSettings.getCurrent();
- dictionaryFacilitator.resetDictionaries(this /* context */,
- dictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict,
+ mDictionaryFacilitator.resetDictionaries(this /* context */,
+ mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict,
settingsValues.mUsePersonalizedDicts, true /* forceReloadMainDictionary */, this);
}
@Override
public void onDestroy() {
- mInputLogic.mSuggest.mDictionaryFacilitator.closeDictionaries();
+ mDictionaryFacilitator.closeDictionaries();
+ mPersonalizationDictionaryUpdater.onDestroy();
mSettings.onDestroy();
unregisterReceiver(mConnectivityAndRingerModeChangeReceiver);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().onDestroy();
- }
unregisterReceiver(mDictionaryPackInstallReceiver);
unregisterReceiver(mDictionaryDumpBroadcastReceiver);
- PersonalizationDictionarySessionRegistrar.close(this);
- LatinImeLogger.commit();
- LatinImeLogger.onDestroy();
StatsUtils.onDestroy();
super.onDestroy();
}
@@ -657,18 +641,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onConfigurationChanged(final Configuration conf) {
- // If orientation changed while predicting, commit the change
final SettingsValues settingsValues = mSettings.getCurrent();
if (settingsValues.mDisplayOrientation != conf.orientation) {
mHandler.startOrientationChanging();
- mInputLogic.mConnection.beginBatchEdit();
- mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR);
- mInputLogic.mConnection.finishComposingText();
- mInputLogic.mConnection.endBatchEdit();
- }
- final DistracterFilter distracterFilter = createDistracterFilter();
- PersonalizationDictionarySessionRegistrar.onConfigurationChanged(this, conf,
- mInputLogic.mSuggest.mDictionaryFacilitator, distracterFilter);
+ // If !isComposingWord, #commitTyped() is a no-op, but still, it's better to avoid
+ // the useless IPC of {begin,end}BatchEdit.
+ if (mInputLogic.mWordComposer.isComposingWord()) {
+ mInputLogic.mConnection.beginBatchEdit();
+ // If we had a composition in progress, we need to commit the word so that the
+ // suggestionsSpan will be added. This will allow resuming on the same suggestions
+ // after rotation is finished.
+ mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR);
+ mInputLogic.mConnection.endBatchEdit();
+ }
+ }
+ // TODO: Remove this test.
+ if (!conf.locale.equals(mPersonalizationDictionaryUpdater.getLocale())) {
+ refreshPersonalizationDictionarySession(settingsValues);
+ }
super.onConfigurationChanged(conf);
}
@@ -687,9 +677,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (hasSuggestionStripView()) {
mSuggestionStripView.setListener(this, view);
}
- if (LatinImeLogger.sVISUALDEBUG) {
- mKeyPreviewBackingView.setBackgroundColor(0x10FF0000);
- }
}
@Override
@@ -762,10 +749,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
Log.i(TAG, "Starting input. Cursor position = "
+ editorInfo.initialSelStart + "," + editorInfo.initialSelEnd);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, prefs);
- }
+ // TODO: Consolidate these checks with {@link InputAttributes}.
if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead");
@@ -775,7 +759,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead");
}
- LatinImeLogger.onStartInputView(editorInfo);
// In landscape mode, this method gets called without the input view being created.
if (mainKeyboardView == null) {
return;
@@ -828,7 +811,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// When rotating, initialSelStart and initialSelEnd sometimes are lying. Make a best
// effort to work around this bug.
mInputLogic.mConnection.tryFixLyingCursorPosition();
- mHandler.postResumeSuggestions();
+ mHandler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */);
canReachInputConnection = true;
}
@@ -840,8 +823,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mainKeyboardView.closing();
currentSettingsValues = mSettings.getCurrent();
- if (currentSettingsValues.mCorrectionEnabled) {
- suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
+ if (currentSettingsValues.mAutoCorrectionEnabled) {
+ suggest.setAutoCorrectionThreshold(
+ currentSettingsValues.mAutoCorrectionThreshold);
}
switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(),
@@ -870,7 +854,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip();
mainKeyboardView.setMainDictionaryAvailability(
- suggest.mDictionaryFacilitator.hasInitializedMainDictionary());
+ mDictionaryFacilitator.hasInitializedMainDictionary());
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setSlidingKeyInputPreviewEnabled(
@@ -895,7 +879,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void onFinishInputInternal() {
super.onFinishInput();
- LatinImeLogger.commit();
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
mainKeyboardView.closing();
@@ -909,10 +892,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip();
// Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
mInputLogic.finishInput();
- // Notify ResearchLogger
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput);
- }
}
@Override
@@ -926,11 +905,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ ", nss=" + newSelStart + ", nse=" + newSelEnd
+ ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd);
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onUpdateSelection(oldSelStart, oldSelEnd,
- oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart,
- composingSpanEnd, mInputLogic.mConnection);
- }
// 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.
@@ -991,7 +965,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void hideWindow() {
- LatinImeLogger.commit();
mKeyboardSwitcher.onHideWindow();
if (TRACE) Debug.stopMethodTracing();
@@ -1017,9 +990,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
if (applicationSpecifiedCompletions == null) {
setNeutralSuggestionStrip();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onDisplayCompletions(null);
- }
return;
}
@@ -1030,10 +1000,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
null /* rawSuggestions */, false /* typedWordValid */, false /* willAutoCorrect */,
false /* isObsoleteSuggestions */, false /* isPrediction */);
// When in fullscreen mode, show completions generated by the application forcibly
- setSuggestedWords(suggestedWords, true /* isSuggestionStripVisible */);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
- }
+ setSuggestedWords(suggestedWords);
}
private int getAdjustedBackingViewHeight() {
@@ -1167,8 +1134,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
wordToEdit = word;
}
- mInputLogic.mSuggest.mDictionaryFacilitator.addWordToUserDictionary(
- this /* context */, wordToEdit);
+ mDictionaryFacilitator.addWordToUserDictionary(this /* context */, wordToEdit);
}
// Callback for the {@link SuggestionStripView}, to call when the important notice strip is
@@ -1337,30 +1303,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Nothing to do so far.
}
- private boolean isSuggestionStripVisible() {
- if (!hasSuggestionStripView()) {
- return false;
- }
- if (mSuggestionStripView.isShowingAddToDictionaryHint()) {
- return true;
- }
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (null == currentSettings) {
- return false;
- }
- if (ImportantNoticeUtils.shouldShowImportantNotice(this,
- currentSettings.mInputAttributes)) {
- return true;
- }
- if (!currentSettings.isSuggestionStripVisible()) {
- return false;
- }
- if (currentSettings.isApplicationSpecifiedCompletionsOn()) {
- return true;
- }
- return currentSettings.isSuggestionsRequested();
- }
-
public boolean hasSuggestionStripView() {
return null != mSuggestionStripView;
}
@@ -1378,9 +1320,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSuggestionStripView.dismissAddToDictionaryHint();
}
- // TODO[IL]: Define a clear interface for this
- public void setSuggestedWords(final SuggestedWords suggestedWords,
- final boolean isSuggestionStripVisible) {
+ private void setSuggestedWords(final SuggestedWords suggestedWords) {
mInputLogic.setSuggestedWords(suggestedWords);
// TODO: Modify this when we support suggestions with hard keyboard
if (!hasSuggestionStripView()) {
@@ -1389,22 +1329,40 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!onEvaluateInputViewShown()) {
return;
}
- if (!isSuggestionStripVisible) {
- mSuggestionStripView.setVisibility(isFullscreenMode() ? View.GONE : View.INVISIBLE);
+
+ final SettingsValues currentSettingsValues = mSettings.getCurrent();
+ final boolean shouldShowImportantNotice =
+ ImportantNoticeUtils.shouldShowImportantNotice(this);
+ final boolean shouldShowSuggestionCandidates =
+ currentSettingsValues.mInputAttributes.mShouldShowSuggestions
+ && currentSettingsValues.isCurrentOrientationAllowingSuggestionsPerUserSettings();
+ final boolean shouldShowSuggestionsStripUnlessPassword = shouldShowImportantNotice
+ || currentSettingsValues.mShowsVoiceInputKey
+ || shouldShowSuggestionCandidates
+ || currentSettingsValues.isApplicationSpecifiedCompletionsOn();
+ final boolean shouldShowSuggestionsStrip = shouldShowSuggestionsStripUnlessPassword
+ && !currentSettingsValues.mInputAttributes.mIsPasswordField;
+ mSuggestionStripView.updateVisibility(shouldShowSuggestionsStrip, isFullscreenMode());
+ if (!shouldShowSuggestionsStrip) {
return;
}
- mSuggestionStripView.setVisibility(View.VISIBLE);
- final SettingsValues currentSettings = mSettings.getCurrent();
- final boolean showSuggestions;
- if (SuggestedWords.EMPTY == suggestedWords || suggestedWords.isPunctuationSuggestions()
- || !currentSettings.isSuggestionsRequested()) {
- showSuggestions = !mSuggestionStripView.maybeShowImportantNoticeTitle(
- currentSettings.mInputAttributes);
- } else {
- showSuggestions = true;
+ final boolean isEmptyApplicationSpecifiedCompletions =
+ currentSettingsValues.isApplicationSpecifiedCompletionsOn()
+ && suggestedWords.isEmpty();
+ final boolean noSuggestionsToShow = (SuggestedWords.EMPTY == suggestedWords)
+ || suggestedWords.isPunctuationSuggestions()
+ || isEmptyApplicationSpecifiedCompletions;
+ if (shouldShowImportantNotice && noSuggestionsToShow) {
+ if (mSuggestionStripView.maybeShowImportantNoticeTitle()) {
+ return;
+ }
}
- if (showSuggestions) {
+
+ if (currentSettingsValues.isCurrentOrientationAllowingSuggestionsPerUserSettings()
+ // We should clear suggestions if there is no suggestion to show.
+ || noSuggestionsToShow
+ || currentSettingsValues.isApplicationSpecifiedCompletionsOn()) {
mSuggestionStripView.setSuggestions(suggestedWords,
SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype()));
}
@@ -1418,36 +1376,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
callback.onGetSuggestedWords(SuggestedWords.EMPTY);
return;
}
- // 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.
final SettingsValues currentSettings = mSettings.getCurrent();
final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues;
-
- if (DEBUG) {
- if (mInputLogic.mWordComposer.isComposingWord()
- || mInputLogic.mWordComposer.isBatchMode()) {
- final PrevWordsInfo prevWordsInfo
- = mInputLogic.mWordComposer.getPrevWordsInfoForSuggestion();
- // TODO: this is for checking consistency with older versions. Remove this when
- // we are confident this is stable.
- // We're checking the previous word in the text field against the memorized previous
- // word. If we are composing a word we should have the second word before the cursor
- // memorized, otherwise we should have the first.
- final PrevWordsInfo rereadPrevWordsInfo =
- mInputLogic.getPrevWordsInfoFromNthPreviousWordForSuggestion(
- currentSettings.mSpacingAndPunctuations,
- mInputLogic.mWordComposer.isComposingWord() ? 2 : 1);
- if (!TextUtils.equals(prevWordsInfo.mPrevWord, rereadPrevWordsInfo.mPrevWord)) {
- throw new RuntimeException("Unexpected previous word: "
- + prevWordsInfo.mPrevWord + " <> " + rereadPrevWordsInfo.mPrevWord);
- }
- }
- }
mInputLogic.mSuggest.getSuggestedWords(mInputLogic.mWordComposer,
- mInputLogic.mWordComposer.getPrevWordsInfoForSuggestion(),
+ mInputLogic.getPrevWordsInfoFromNthPreviousWordForSuggestion(
+ currentSettings.mSpacingAndPunctuations,
+ // Get the word on which we should search the bigrams. If we are composing
+ // a word, it's whatever is *before* the half-committed word in the buffer,
+ // hence 2; if we aren't, we should just skip whitespace if any, so 1.
+ mInputLogic.mWordComposer.isComposingWord() ? 2 : 1),
keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive,
- currentSettings.mCorrectionEnabled, additionalFeaturesOptions, sessionId,
+ currentSettings.mAutoCorrectionEnabled, additionalFeaturesOptions, sessionId,
sequenceNumber, callback);
}
@@ -1467,7 +1406,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
setNeutralSuggestionStrip();
} else {
mInputLogic.mWordComposer.setAutoCorrection(autoCorrection);
- setSuggestedWords(suggestedWords, isSuggestionStripVisible());
+ setSuggestedWords(suggestedWords);
}
// Cache the auto-correction in accessibility code so we can speak it if the user
// touches a key that will insert it.
@@ -1500,7 +1439,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final SettingsValues currentSettings = mSettings.getCurrent();
final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled
? SuggestedWords.EMPTY : currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
- setSuggestedWords(neutralSuggestions, isSuggestionStripVisible());
+ setSuggestedWords(neutralSuggestions);
}
// TODO: Make this private
@@ -1725,15 +1664,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@UsedForTesting
/* package for test */ void waitForLoadingDictionaries(final long timeout, final TimeUnit unit)
throws InterruptedException {
- mInputLogic.mSuggest.mDictionaryFacilitator.waitForLoadingDictionariesForTesting(
- timeout, unit);
+ mDictionaryFacilitator.waitForLoadingDictionariesForTesting(timeout, unit);
}
// DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
@UsedForTesting
/* package for test */ void replaceDictionariesForTest(final Locale locale) {
final SettingsValues settingsValues = mSettings.getCurrent();
- mInputLogic.mSuggest.mDictionaryFacilitator.resetDictionaries(this, locale,
+ mDictionaryFacilitator.resetDictionaries(this, locale,
settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
false /* forceReloadMainDictionary */, this /* listener */);
}
@@ -1741,24 +1679,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// DO NOT USE THIS for any other purpose than testing.
@UsedForTesting
/* package for test */ void clearPersonalizedDictionariesForTest() {
- mInputLogic.mSuggest.mDictionaryFacilitator.clearUserHistoryDictionary();
- mInputLogic.mSuggest.mDictionaryFacilitator.clearPersonalizationDictionary();
+ mDictionaryFacilitator.clearUserHistoryDictionary();
+ mDictionaryFacilitator.clearPersonalizationDictionary();
}
@UsedForTesting
- /* package for test */ DistracterFilter createDistracterFilter() {
- return new DistracterFilter(this /* Context */,
- mRichImm.getMyEnabledInputMethodSubtypeList(
- true /* allowsImplicitlySelectedSubtypes */));
+ /* package for test */ List<InputMethodSubtype> getEnabledSubtypesForTest() {
+ return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList(
+ true /* allowsImplicitlySelectedSubtypes */) : new ArrayList<InputMethodSubtype>();
}
public void dumpDictionaryForDebug(final String dictName) {
- final DictionaryFacilitatorForSuggest dictionaryFacilitator =
- mInputLogic.mSuggest.mDictionaryFacilitator;
- if (dictionaryFacilitator.getLocale() == null) {
+ if (mDictionaryFacilitator.getLocale() == null) {
resetSuggest();
}
- mInputLogic.mSuggest.mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
+ mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
}
public void debugDumpStateAndCrashWithException(final String context) {
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index 3f2b0a3f4..8fd36b937 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -16,76 +16,12 @@
package com.android.inputmethod.latin;
-import android.content.SharedPreferences;
-import android.view.inputmethod.EditorInfo;
+import android.content.Context;
-import com.android.inputmethod.keyboard.Keyboard;
+// TODO: Rename this class name to make it more relevant.
+public final class LatinImeLogger {
+ public static final boolean sDBG = false;
-public final class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
-
- public static boolean sDBG = false;
- public static boolean sVISUALDEBUG = false;
- public static boolean sUsabilityStudy = false;
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- }
-
- public static void init(LatinIME context) {
- }
-
- public static void commit() {
- }
-
- public static boolean getUsabilityStudyMode(final SharedPreferences prefs) {
- return false;
- }
-
- public static void onDestroy() {
- }
-
- public static void logOnManualSuggestion(
- String before, String after, int position, SuggestedWords suggestedWords) {
- }
-
- public static void logOnAutoCorrectionForTyping(
- String before, String after, int separatorCode) {
- }
-
- public static void logOnAutoCorrectionForGeometric(String before, String after,
- int separatorCode, InputPointers inputPointers) {
- }
-
- public static void logOnAutoCorrectionCancelled() {
- }
-
- public static void logOnDelete(int x, int y) {
- }
-
- public static void logOnInputChar() {
- }
-
- public static void logOnInputSeparator() {
- }
-
- public static void logOnException(String metaData, Throwable e) {
- }
-
- public static void logOnWarning(String warning) {
- }
-
- public static void onStartInputView(EditorInfo editorInfo) {
- }
-
- public static void onStartSuggestion(CharSequence previousWords) {
- }
-
- public static void onAddSuggestedWord(String word, String sourceDictionaryId) {
- }
-
- public static void onSetKeyboard(Keyboard kb) {
- }
-
- public static void onPrintAllUsabilityStudyLogs() {
+ public static void init(Context context) {
}
}
diff --git a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
index ecc8947db..42b311c69 100644
--- a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
+++ b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
@@ -16,23 +16,32 @@
package com.android.inputmethod.latin;
-import android.util.Log;
-
+/**
+ * Class to represent information of previous words. This class is used to add n-gram entries
+ * into binary dictionaries, to get predictions, and to get suggestions.
+ */
// TODO: Support multiple previous words for n-gram.
public class PrevWordsInfo {
- // The previous word. May be null after resetting and before starting a new composing word, or
- // when there is no context like at the start of text for example. It can also be set to null
- // externally when the user enters a separator that does not let bigrams across, like a period
- // or a comma.
+ public static final PrevWordsInfo EMPTY_PREV_WORDS_INFO = new PrevWordsInfo(null);
+ public static final PrevWordsInfo BEGINNING_OF_SENTENCE = new PrevWordsInfo();
+
+ // The word immediately before the considered word. null means we don't have any context
+ // including the "beginning of sentence context" - we just don't know what to predict.
+ // An example of that is after a comma.
+ // For simplicity of implementation, this may also be null transiently after the WordComposer
+ // was reset and before starting a new composing word, but we should never be calling
+ // getSuggetions* in this situation.
+ // This is an empty string when mIsBeginningOfSentence is true.
public final String mPrevWord;
// TODO: Have sentence separator.
- // Whether the current context is beginning of sentence or not.
+ // Whether the current context is beginning of sentence or not. This is true when composing at
+ // the beginning of an input field or composing a word after a sentence separator.
public final boolean mIsBeginningOfSentence;
// Beginning of sentence.
public PrevWordsInfo() {
- mPrevWord = null;
+ mPrevWord = "";
mIsBeginningOfSentence = true;
}
@@ -40,4 +49,14 @@ public class PrevWordsInfo {
mPrevWord = prevWord;
mIsBeginningOfSentence = false;
}
+
+ public boolean isValid() {
+ return mPrevWord != null;
+ }
+
+ @Override
+ public String toString() {
+ return "PrevWord: " + mPrevWord + ", isBeginningOfSentence: "
+ + mIsBeginningOfSentence + ".";
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
index 4911bcdf6..0fba37c8a 100644
--- a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
@@ -17,8 +17,6 @@
package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.internal.KeySpecParser;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
@@ -49,7 +47,7 @@ public final class PunctuationSuggestions extends SuggestedWords {
*/
public static PunctuationSuggestions newPunctuationSuggestions(
final String[] punctuationSpecs) {
- final ArrayList<SuggestedWordInfo> puncuationsList = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> puncuationsList = new ArrayList<>();
for (final String puncSpec : punctuationSpecs) {
puncuationsList.add(newHardCodedWordInfo(puncSpec));
}
diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
index 8f744bef8..e59ef7563 100644
--- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
@@ -66,10 +66,10 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
if (mLock.readLock().tryLock()) {
try {
- return mBinaryDictionary.isValidWord(word);
+ return mBinaryDictionary.isInDictionary(word);
} finally {
mLock.readLock().unlock();
}
@@ -102,6 +102,18 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
}
@Override
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ if (mLock.readLock().tryLock()) {
+ try {
+ return mBinaryDictionary.getMaxFrequencyOfExactMatches(word);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+ return NOT_A_PROBABILITY;
+ }
+
+ @Override
public void close() {
mLock.writeLock().lock();
try {
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 2c54e10aa..96476b2ee 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -26,14 +26,12 @@ import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
import com.android.inputmethod.latin.utils.SpannableStringUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.Arrays;
import java.util.regex.Pattern;
@@ -174,9 +172,6 @@ public final class RichInputConnection {
}
if (null != mIC && shouldFinishComposition) {
mIC.finishComposingText();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_finishComposingText();
- }
}
return true;
}
@@ -223,9 +218,6 @@ public final class RichInputConnection {
mComposingText.setLength(0);
if (null != mIC) {
mIC.finishComposingText();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_finishComposingText();
- }
}
}
@@ -363,9 +355,6 @@ public final class RichInputConnection {
}
if (null != mIC) {
mIC.deleteSurroundingText(beforeLength, afterLength);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_deleteSurroundingText(beforeLength, afterLength);
- }
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -374,9 +363,6 @@ public final class RichInputConnection {
mIC = mParent.getCurrentInputConnection();
if (null != mIC) {
mIC.performEditorAction(actionId);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_performEditorAction(actionId);
- }
}
}
@@ -429,9 +415,6 @@ public final class RichInputConnection {
}
if (null != mIC) {
mIC.sendKeyEvent(keyEvent);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_sendKeyEvent(keyEvent);
- }
}
}
@@ -469,9 +452,6 @@ public final class RichInputConnection {
// newCursorPosition != 1.
if (null != mIC) {
mIC.setComposingText(text, newCursorPosition);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_setComposingText(text, newCursorPosition);
- }
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -500,9 +480,6 @@ public final class RichInputConnection {
if (!isIcValid) {
return false;
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_setSelection(start, end);
- }
}
return reloadTextCache();
}
@@ -530,9 +507,6 @@ public final class RichInputConnection {
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitCompletion(completionInfo);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_commitCompletion(completionInfo);
- }
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -542,7 +516,7 @@ public final class RichInputConnection {
final SpacingAndPunctuations spacingAndPunctuations, final int n) {
mIC = mParent.getCurrentInputConnection();
if (null == mIC) {
- return new PrevWordsInfo(null);
+ return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
}
final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
if (DEBUG_PREVIOUS_TEXT && null != prev) {
@@ -573,14 +547,17 @@ public final class RichInputConnection {
// Get information of the nth word before cursor. n = 1 retrieves the word immediately before
// the cursor, n = 2 retrieves the word before that, and so on. This splits on whitespace only.
// Also, it won't return words that end in a separator (if the nth word before the cursor
- // ends in a separator, it returns information represents beginning-of-sentence).
+ // ends in a separator, it returns information representing beginning-of-sentence).
// Example :
// (n = 1) "abc def|" -> def
// (n = 1) "abc def |" -> def
+ // (n = 1) "abc 'def|" -> 'def
// (n = 1) "abc def. |" -> beginning-of-sentence
// (n = 1) "abc def . |" -> beginning-of-sentence
// (n = 2) "abc def|" -> abc
// (n = 2) "abc def |" -> abc
+ // (n = 2) "abc 'def|" -> empty. The context is different from "abc def", but we cannot
+ // represent this situation using PrevWordsInfo. See TODO in the method.
// (n = 2) "abc def. |" -> abc
// (n = 2) "abc def . |" -> def
// (n = 2) "abc|" -> beginning-of-sentence
@@ -588,30 +565,43 @@ public final class RichInputConnection {
// (n = 2) "abc. def|" -> beginning-of-sentence
public static PrevWordsInfo getPrevWordsInfoFromNthPreviousWord(final CharSequence prev,
final SpacingAndPunctuations spacingAndPunctuations, final int n) {
- if (prev == null) return new PrevWordsInfo(null);
+ if (prev == null) return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
final String[] w = spaceRegex.split(prev);
+ // Referring to the word after the nth word.
+ if ((n - 1) > 0 && (n - 1) <= w.length) {
+ final String wordFollowingTheNthPrevWord = w[w.length - n + 1];
+ if (!wordFollowingTheNthPrevWord.isEmpty()) {
+ final char firstChar = wordFollowingTheNthPrevWord.charAt(0);
+ if (spacingAndPunctuations.isWordConnector(firstChar)) {
+ // The word following the n-th prev word is starting with a word connector.
+ // TODO: Return meaningful context for this case.
+ return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ }
+ }
+ }
+
// If we can't find n words, or we found an empty word, the context is
// beginning-of-sentence.
if (w.length < n) {
- return new PrevWordsInfo();
+ return PrevWordsInfo.BEGINNING_OF_SENTENCE;
}
final String nthPrevWord = w[w.length - n];
final int length = nthPrevWord.length();
if (length <= 0) {
- return new PrevWordsInfo();
+ return PrevWordsInfo.BEGINNING_OF_SENTENCE;
}
// If ends in a sentence separator, the context is beginning-of-sentence.
final char lastChar = nthPrevWord.charAt(length - 1);
if (spacingAndPunctuations.isSentenceSeparator(lastChar)) {
- new PrevWordsInfo();
+ return PrevWordsInfo.BEGINNING_OF_SENTENCE;
}
// If ends in a word separator or connector, the context is unclear.
// TODO: Return meaningful context for this case.
if (spacingAndPunctuations.isWordSeparator(lastChar)
|| spacingAndPunctuations.isWordConnector(lastChar)) {
- return new PrevWordsInfo(null);
+ return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
}
return new PrevWordsInfo(nthPrevWord);
}
@@ -765,9 +755,6 @@ public final class RichInputConnection {
deleteSurroundingText(2, 0);
final String singleSpace = " ";
commitText(singleSpace, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_revertDoubleSpacePeriod();
- }
return true;
}
@@ -790,9 +777,6 @@ public final class RichInputConnection {
deleteSurroundingText(2, 0);
final String text = " " + textBeforeCursor.subSequence(0, 1);
commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_revertSwapPunctuation();
- }
return true;
}
@@ -907,4 +891,8 @@ public final class RichInputConnection {
public boolean hasSelection() {
return mExpectedSelEnd != mExpectedSelStart;
}
+
+ public boolean isCursorPositionKnown() {
+ return INVALID_CURSOR_POSITION != mExpectedSelStart;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 64cc562c8..7758ac78e 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -31,7 +31,6 @@ 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;
@@ -53,9 +52,9 @@ public final class RichInputMethodManager {
private InputMethodManagerCompatWrapper mImmWrapper;
private InputMethodInfoCache mInputMethodInfoCache;
final HashMap<InputMethodInfo, List<InputMethodSubtype>>
- mSubtypeListCacheWithImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
+ mSubtypeListCacheWithImplicitlySelectedSubtypes = new HashMap<>();
final HashMap<InputMethodInfo, List<InputMethodSubtype>>
- mSubtypeListCacheWithoutImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
+ mSubtypeListCacheWithoutImplicitlySelectedSubtypes = new HashMap<>();
private static final int INDEX_NOT_FOUND = -1;
@@ -410,21 +409,12 @@ public final class RichInputMethodManager {
public boolean shouldOfferSwitchingToNextInputMethod(final IBinder binder,
boolean defaultValue) {
- // Use the default value instead on Jelly Bean MR2 and previous, where
- // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} isn't yet available.
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ // Use the default value instead on Jelly Bean MR2 and previous where
+ // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} isn't yet available
+ // and on KitKat where the API is still just a stub to return true always.
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
return defaultValue;
}
- // Use the default value instead on KitKat as well, where
- // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is still just a stub to
- // return true always.
- if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
- // Make sure this is actually KitKat.
- // TODO: Consider to remove this check once the *next* version becomes available.
- if (Build.VERSION.CODENAME.equals("REL")) {
- return defaultValue;
- }
- }
return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder);
}
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index c8a2fb2f9..a3d09565c 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -255,8 +255,7 @@ public final class SubtypeSwitcher {
public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
final Locale systemLocale = mResources.getConfiguration().locale;
- final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes =
- new HashSet<InputMethodSubtype>();
+ final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
final InputMethodManager inputMethodManager = mRichImm.getInputMethodManager();
final List<InputMethodInfo> enabledInputMethodInfoList =
inputMethodManager.getEnabledInputMethodList();
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 43daee4d2..1ba5d5ea6 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -23,7 +23,6 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.SuggestionResults;
@@ -52,11 +51,14 @@ public final class Suggest {
private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
private static final boolean DBG = LatinImeLogger.sDBG;
- public final DictionaryFacilitatorForSuggest mDictionaryFacilitator =
- new DictionaryFacilitatorForSuggest();
+ private final DictionaryFacilitator mDictionaryFacilitator;
private float mAutoCorrectionThreshold;
+ public Suggest(final DictionaryFacilitator dictionaryFacilitator) {
+ mDictionaryFacilitator = dictionaryFacilitator;
+ }
+
public Locale getLocale() {
return mDictionaryFacilitator.getLocale();
}
@@ -74,7 +76,6 @@ public final class Suggest {
final boolean blockOffensiveWords, final boolean isCorrectionEnabled,
final int[] additionalFeaturesOptions, final int sessionId, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
- LatinImeLogger.onStartSuggestion(prevWordsInfo.mPrevWord);
if (wordComposer.isBatchMode()) {
getSuggestedWordsForBatchInput(wordComposer, prevWordsInfo, proximityInfo,
blockOffensiveWords, additionalFeaturesOptions, sessionId, sequenceNumber,
@@ -98,11 +99,10 @@ public final class Suggest {
final String consideredWord = trailingSingleQuotesCount > 0
? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
: typedWord;
- LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED);
final ArrayList<SuggestedWordInfo> rawSuggestions;
if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) {
- rawSuggestions = CollectionUtils.newArrayList();
+ rawSuggestions = new ArrayList<>();
} else {
rawSuggestions = null;
}
@@ -110,7 +110,7 @@ public final class Suggest {
wordComposer, prevWordsInfo, proximityInfo, blockOffensiveWords,
additionalFeaturesOptions, SESSION_TYPING, rawSuggestions);
- final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
+ final boolean isOnlyFirstCharCapitalized = wordComposer.isOnlyFirstCharCapitalized();
// If resumed, then we don't want to upcase everything: resuming on a fully-capitalized
// words is rarely done to switch to another fully-capitalized word, but usually to a
// normal, non-capitalized suggestion.
@@ -122,9 +122,9 @@ public final class Suggest {
} else {
final SuggestedWordInfo firstSuggestedWordInfo = getTransformedSuggestedWordInfo(
suggestionResults.first(), suggestionResults.mLocale, isAllUpperCase,
- isFirstCharCapitalized, trailingSingleQuotesCount);
+ isOnlyFirstCharCapitalized, trailingSingleQuotesCount);
firstSuggestion = firstSuggestedWordInfo.mWord;
- if (SuggestedWordInfo.KIND_WHITELIST != firstSuggestedWordInfo.mKind) {
+ if (!firstSuggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
whitelistedWord = null;
} else {
whitelistedWord = firstSuggestion;
@@ -142,7 +142,7 @@ public final class Suggest {
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
&& !whitelistedWord.equals(typedWord))
|| (consideredWord.length() > 1 && !mDictionaryFacilitator.isValidWord(
- consideredWord, wordComposer.isFirstCharCapitalized())
+ consideredWord, isOnlyFirstCharCapitalized)
&& !typedWord.equals(firstSuggestion));
final boolean hasAutoCorrection;
@@ -155,7 +155,7 @@ public final class Suggest {
|| suggestionResults.isEmpty() || wordComposer.hasDigits()
|| wordComposer.isMostlyCaps() || wordComposer.isResumed()
|| !mDictionaryFacilitator.hasInitializedMainDictionary()
- || SuggestedWordInfo.KIND_SHORTCUT == suggestionResults.first().mKind) {
+ || suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) {
// If we don't have a main dictionary, we never want to auto-correct. The reason for
// this is, the user may have a contact whose name happens to match a valid word in
// their language, and it will unexpectedly auto-correct. For example, if the user
@@ -171,24 +171,18 @@ public final class Suggest {
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- CollectionUtils.newArrayList(suggestionResults);
+ new ArrayList<>(suggestionResults);
final int suggestionsCount = suggestionsContainer.size();
- if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
+ if (isOnlyFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized,
- trailingSingleQuotesCount);
+ wordInfo, suggestionResults.mLocale, isAllUpperCase,
+ isOnlyFirstCharCapitalized, trailingSingleQuotesCount);
suggestionsContainer.set(i, transformedWordInfo);
}
}
- for (int i = 0; i < suggestionsCount; ++i) {
- final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
- LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(),
- wordInfo.mSourceDict.mDictType);
- }
-
if (!TextUtils.isEmpty(typedWord)) {
suggestionsContainer.add(0, new SuggestedWordInfo(typedWord,
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED,
@@ -223,19 +217,15 @@ public final class Suggest {
final OnGetSuggestedWordsCallback callback) {
final ArrayList<SuggestedWordInfo> rawSuggestions;
if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) {
- rawSuggestions = CollectionUtils.newArrayList();
+ rawSuggestions = new ArrayList<>();
} else {
rawSuggestions = null;
}
final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
wordComposer, prevWordsInfo, proximityInfo, blockOffensiveWords,
additionalFeaturesOptions, sessionId, rawSuggestions);
- for (SuggestedWordInfo wordInfo : suggestionResults) {
- LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType);
- }
-
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- CollectionUtils.newArrayList(suggestionResults);
+ new ArrayList<>(suggestionResults);
final int suggestionsCount = suggestionsContainer.size();
final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
final boolean isAllUpperCase = wordComposer.isAllUpperCase();
@@ -278,8 +268,7 @@ public final class Suggest {
final SuggestedWordInfo typedWordInfo = suggestions.get(0);
typedWordInfo.setDebugString("+");
final int suggestionsSize = suggestions.size();
- final ArrayList<SuggestedWordInfo> suggestionsList =
- CollectionUtils.newArrayList(suggestionsSize);
+ final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>(suggestionsSize);
suggestionsList.add(typedWordInfo);
// Note: i here is the index in mScores[], but the index in mSuggestions is one more
// than i because we added the typed word to mSuggestions without touching mScores.
@@ -303,11 +292,11 @@ public final class Suggest {
/* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo(
final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
- final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
+ final boolean isOnlyFirstCharCapitalized, final int trailingSingleQuotesCount) {
final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
if (isAllUpperCase) {
sb.append(wordInfo.mWord.toUpperCase(locale));
- } else if (isFirstCharCapitalized) {
+ } else if (isOnlyFirstCharCapitalized) {
sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale));
} else {
sb.append(wordInfo.mWord);
@@ -320,7 +309,7 @@ public final class Suggest {
for (int i = quotesToAppend - 1; i >= 0; --i) {
sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE);
}
- return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKind,
+ return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKindAndFlags,
wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord,
wordInfo.mAutoCommitFirstWordConfidence);
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index dc2c9fd0e..72461e17a 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -19,7 +19,6 @@ 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;
@@ -34,8 +33,7 @@ public class SuggestedWords {
// The maximum number of suggestions available.
public static final int MAX_SUGGESTIONS = 18;
- private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
- CollectionUtils.newArrayList(0);
+ private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = new ArrayList<>(0);
public static final SuggestedWords EMPTY = new SuggestedWords(
EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, false, false, false, false);
@@ -165,7 +163,7 @@ public class SuggestedWords {
public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
final CompletionInfo[] infos) {
- final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> result = new ArrayList<>();
for (final CompletionInfo info : infos) {
if (null == info || null == info.getText()) {
continue;
@@ -179,8 +177,8 @@ public class SuggestedWords {
// and replace it with what the user currently typed.
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
final String typedWord, final SuggestedWords previousSuggestions) {
- final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList();
- final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
+ final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>();
+ final HashSet<String> alreadySeen = new HashSet<>();
suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
@@ -209,7 +207,8 @@ public class SuggestedWords {
public static final int NOT_AN_INDEX = -1;
public static final int NOT_A_CONFIDENCE = -1;
public static final int MAX_SCORE = Integer.MAX_VALUE;
- public static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind
+
+ private static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind
public static final int KIND_TYPED = 0; // What user typed
public static final int KIND_CORRECTION = 1; // Simple correction/suggestion
public static final int KIND_COMPLETION = 2; // Completion (suggestion with appended chars)
@@ -224,16 +223,16 @@ public class SuggestedWords {
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;
public static final int KIND_FLAG_EXACT_MATCH = 0x40000000;
+ public static final int KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION = 0x20000000;
public final String mWord;
// The completion info from the application. Null for suggestions that don't come from
// the application (including keyboard-computed ones, so this is almost always null)
public final CompletionInfo mApplicationSpecifiedCompletionInfo;
public final int mScore;
- public final int mKind; // one of the KIND_* constants above
+ public final int mKindAndFlags;
public final int mCodePointCount;
public final Dictionary mSourceDict;
// For auto-commit. This keeps track of the index inside the touch coordinates array
@@ -249,18 +248,19 @@ public class SuggestedWords {
* Create a new suggested word info.
* @param word The string to suggest.
* @param score A measure of how likely this suggestion is.
- * @param kind The kind of suggestion, as one of the above KIND_* constants.
+ * @param kindAndFlags The kind of suggestion, as one of the above KIND_* constants with
+ * flags.
* @param sourceDict What instance of Dictionary produced this suggestion.
* @param indexOfTouchPointOfSecondWord See mIndexOfTouchPointOfSecondWord.
* @param autoCommitFirstWordConfidence See mAutoCommitFirstWordConfidence.
*/
- public SuggestedWordInfo(final String word, final int score, final int kind,
+ public SuggestedWordInfo(final String word, final int score, final int kindAndFlags,
final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord,
final int autoCommitFirstWordConfidence) {
mWord = word;
mApplicationSpecifiedCompletionInfo = null;
mScore = score;
- mKind = kind;
+ mKindAndFlags = kindAndFlags;
mSourceDict = sourceDict;
mCodePointCount = StringUtils.codePointCount(mWord);
mIndexOfTouchPointOfSecondWord = indexOfTouchPointOfSecondWord;
@@ -276,7 +276,7 @@ public class SuggestedWords {
mWord = applicationSpecifiedCompletion.getText().toString();
mApplicationSpecifiedCompletionInfo = applicationSpecifiedCompletion;
mScore = SuggestedWordInfo.MAX_SCORE;
- mKind = SuggestedWordInfo.KIND_APP_DEFINED;
+ mKindAndFlags = SuggestedWordInfo.KIND_APP_DEFINED;
mSourceDict = Dictionary.DICTIONARY_APPLICATION_DEFINED;
mCodePointCount = StringUtils.codePointCount(mWord);
mIndexOfTouchPointOfSecondWord = SuggestedWordInfo.NOT_AN_INDEX;
@@ -284,7 +284,27 @@ public class SuggestedWords {
}
public boolean isEligibleForAutoCommit() {
- return (KIND_CORRECTION == mKind && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord);
+ return (isKindOf(KIND_CORRECTION) && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord);
+ }
+
+ public int getKind() {
+ return (mKindAndFlags & KIND_MASK_KIND);
+ }
+
+ public boolean isKindOf(final int kind) {
+ return getKind() == kind;
+ }
+
+ public boolean isPossiblyOffensive() {
+ return (mKindAndFlags & KIND_FLAG_POSSIBLY_OFFENSIVE) != 0;
+ }
+
+ public boolean isExactMatch() {
+ return (mKindAndFlags & KIND_FLAG_EXACT_MATCH) != 0;
+ }
+
+ public boolean isExactMatchWithIntentionalOmission() {
+ return (mKindAndFlags & KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION) != 0;
}
public void setDebugString(final String str) {
@@ -337,11 +357,11 @@ public class SuggestedWords {
// SuggestedWords is an immutable object, as much as possible. We must not just remove
// words from the member ArrayList as some other parties may expect the object to never change.
public SuggestedWords getSuggestedWordsExcludingTypedWord() {
- final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> newSuggestions = new ArrayList<>();
String typedWord = null;
for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
- if (SuggestedWordInfo.KIND_TYPED != info.mKind) {
+ if (!info.isKindOf(SuggestedWordInfo.KIND_TYPED)) {
newSuggestions.add(info);
} else {
assert(null == typedWord);
@@ -361,12 +381,12 @@ public class SuggestedWords {
// we should only suggest replacements for this last word.
// TODO: make this work with languages without spaces.
public SuggestedWords getSuggestedWordsForLastWordOfPhraseGesture() {
- final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> newSuggestions = new ArrayList<>();
for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
final int indexOfLastSpace = info.mWord.lastIndexOf(Constants.CODE_SPACE) + 1;
final String lastWord = info.mWord.substring(indexOfLastSpace);
- newSuggestions.add(new SuggestedWordInfo(lastWord, info.mScore, info.mKind,
+ newSuggestions.add(new SuggestedWordInfo(lastWord, info.mScore, info.mKindAndFlags,
info.mSourceDict, SuggestedWordInfo.NOT_AN_INDEX,
SuggestedWordInfo.NOT_A_CONFIDENCE));
}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index b89ab84b2..debaad13e 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -67,10 +67,6 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
final private String mLocale;
final private boolean mAlsoUseMoreRestrictiveLocales;
- private UserBinaryDictionary(final Context context, final Locale locale, final File dictFile) {
- this(context, locale, false /* alsoUseMoreRestrictiveLocales */, dictFile, NAME);
- }
-
protected UserBinaryDictionary(final Context context, final Locale locale,
final boolean alsoUseMoreRestrictiveLocales, final File dictFile, final String name) {
super(context, getDictName(name, locale, dictFile), locale, Dictionary.TYPE_USER, dictFile);
@@ -107,8 +103,9 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
@UsedForTesting
public static UserBinaryDictionary getDictionary(final Context context, final Locale locale,
- final File dictFile) {
- return new UserBinaryDictionary(context, locale, dictFile);
+ final File dictFile, final String dictNamePrefix) {
+ return new UserBinaryDictionary(context, locale, false /* alsoUseMoreRestrictiveLocales */,
+ dictFile, dictNamePrefix + NAME);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 6ecb37346..6ce1f85c5 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin;
import com.android.inputmethod.event.CombinerChain;
import com.android.inputmethod.event.Event;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.StringUtils;
@@ -46,9 +45,6 @@ public final class WordComposer {
// The list of events that served to compose this string.
private final ArrayList<Event> mEvents;
private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
- // The information of previous words (before the composing word). Must not be null. Used as
- // context for suggestions.
- private PrevWordsInfo mPrevWordsInfo;
private String mAutoCorrection;
private boolean mIsResumed;
private boolean mIsBatchMode;
@@ -73,19 +69,18 @@ public final class WordComposer {
private int mCursorPositionWithinWord;
/**
- * Whether the user chose to capitalize the first char of the word.
+ * Whether the composing word has the only first char capitalized.
*/
- private boolean mIsFirstCharCapitalized;
+ private boolean mIsOnlyFirstCharCapitalized;
public WordComposer() {
mCombinerChain = new CombinerChain("");
- mEvents = CollectionUtils.newArrayList();
+ mEvents = new ArrayList<>();
mAutoCorrection = null;
mIsResumed = false;
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null;
- mPrevWordsInfo = new PrevWordsInfo(null);
refreshTypedWordCache();
}
@@ -112,12 +107,11 @@ public final class WordComposer {
mAutoCorrection = null;
mCapsCount = 0;
mDigitsCount = 0;
- mIsFirstCharCapitalized = false;
+ mIsOnlyFirstCharCapitalized = false;
mIsResumed = false;
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null;
- mPrevWordsInfo = new PrevWordsInfo(null);
refreshTypedWordCache();
}
@@ -146,9 +140,12 @@ public final class WordComposer {
*/
public int copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
final int[] destination) {
+ // This method can be called on a separate thread and mTypedWordCache can change while we
+ // are executing this method.
+ final String typedWord = mTypedWordCache.toString();
// lastIndex is exclusive
- final int lastIndex = mTypedWordCache.length()
- - StringUtils.getTrailingSingleQuotesCount(mTypedWordCache);
+ final int lastIndex = typedWord.length()
+ - StringUtils.getTrailingSingleQuotesCount(typedWord);
if (lastIndex <= 0) {
// The string is empty or contains only single quotes.
return 0;
@@ -156,11 +153,11 @@ public final class WordComposer {
// The following function counts the number of code points in the text range which begins
// at index 0 and extends to the character at lastIndex.
- final int codePointSize = Character.codePointCount(mTypedWordCache, 0, lastIndex);
+ final int codePointSize = Character.codePointCount(typedWord, 0, lastIndex);
if (codePointSize > destination.length) {
return -1;
}
- return StringUtils.copyCodePointsAndReturnCodePointCount(destination, mTypedWordCache, 0,
+ return StringUtils.copyCodePointsAndReturnCodePointCount(destination, typedWord, 0,
lastIndex, true /* downCase */);
}
@@ -176,12 +173,6 @@ public final class WordComposer {
return mInputPointers;
}
- private static boolean isFirstCharCapitalized(final int index, final int codePoint,
- final boolean previous) {
- if (index == 0) return Character.isUpperCase(codePoint);
- return previous && !Character.isUpperCase(codePoint);
- }
-
/**
* Process an input event.
*
@@ -201,7 +192,7 @@ public final class WordComposer {
mCursorPositionWithinWord = mCodePointSize;
// We may have deleted the last one.
if (0 == mCodePointSize) {
- mIsFirstCharCapitalized = false;
+ mIsOnlyFirstCharCapitalized = false;
}
if (Constants.CODE_DELETE != event.mKeyCode) {
if (newIndex < MAX_WORD_LENGTH) {
@@ -213,8 +204,12 @@ public final class WordComposer {
mInputPointers.addPointerAt(newIndex, keyX, keyY, 0, 0);
}
}
- mIsFirstCharCapitalized = isFirstCharCapitalized(
- newIndex, primaryCode, mIsFirstCharCapitalized);
+ if (0 == newIndex) {
+ mIsOnlyFirstCharCapitalized = Character.isUpperCase(primaryCode);
+ } else {
+ mIsOnlyFirstCharCapitalized = mIsOnlyFirstCharCapitalized
+ && !Character.isUpperCase(primaryCode);
+ }
if (Character.isUpperCase(primaryCode)) mCapsCount++;
if (Character.isDigit(primaryCode)) mDigitsCount++;
}
@@ -294,10 +289,8 @@ public final class WordComposer {
* This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
* @param codePoints the code points to set as the composing word.
* @param coordinates the x, y coordinates of the key in the CoordinateUtils format
- * @param prevWordsInfo the information of previous words, to use as context for suggestions
*/
- public void setComposingWord(final int[] codePoints, final int[] coordinates,
- final PrevWordsInfo prevWordsInfo) {
+ public void setComposingWord(final int[] codePoints, final int[] coordinates) {
reset();
final int length = codePoints.length;
for (int i = 0; i < length; ++i) {
@@ -306,7 +299,6 @@ public final class WordComposer {
CoordinateUtils.yFromArray(coordinates, i)));
}
mIsResumed = true;
- mPrevWordsInfo = prevWordsInfo;
}
/**
@@ -317,16 +309,13 @@ public final class WordComposer {
return mTypedWordCache.toString();
}
- public PrevWordsInfo getPrevWordsInfoForSuggestion() {
- return mPrevWordsInfo;
- }
-
/**
- * Whether or not the user typed a capital letter as the first letter in the word
+ * Whether or not the user typed a capital letter as the first letter in the word, and no
+ * other letter is capitalized
* @return capitalization preference
*/
- public boolean isFirstCharCapitalized() {
- return mIsFirstCharCapitalized;
+ public boolean isOnlyFirstCharCapitalized() {
+ return mIsOnlyFirstCharCapitalized;
}
/**
@@ -362,7 +351,7 @@ public final class WordComposer {
}
/**
- * Saves the caps mode and the previous word at the start of composing.
+ * Saves the caps mode at the start of composing.
*
* WordComposer needs to know about the caps mode for several reasons. The first is, we need
* to know after the fact what the reason was, to register the correct form into the user
@@ -371,12 +360,9 @@ public final class WordComposer {
* Also, batch input needs to know about the current caps mode to display correctly
* capitalized suggestions.
* @param mode the mode at the time of start
- * @param prevWordsInfo the information of previous words
*/
- public void setCapitalizedModeAndPreviousWordAtStartComposingTime(final int mode,
- final PrevWordsInfo prevWordsInfo) {
+ public void setCapitalizedModeAtStartComposingTime(final int mode) {
mCapitalizedMode = mode;
- mPrevWordsInfo = prevWordsInfo;
}
/**
@@ -427,11 +413,10 @@ public final class WordComposer {
mCapsCount = 0;
mDigitsCount = 0;
mIsBatchMode = false;
- mPrevWordsInfo = new PrevWordsInfo(committedWord.toString());
mCombinerChain.reset();
mEvents.clear();
mCodePointSize = 0;
- mIsFirstCharCapitalized = false;
+ mIsOnlyFirstCharCapitalized = false;
mCapitalizedMode = CAPS_MODE_OFF;
refreshTypedWordCache();
mAutoCorrection = null;
@@ -441,15 +426,7 @@ public final class WordComposer {
return lastComposedWord;
}
- // Call this when the recorded previous word should be discarded. This is typically called
- // when the user inputs a separator that's not whitespace (including the case of the
- // double-space-to-period feature).
- public void discardPreviousWordForSuggestion() {
- mPrevWordsInfo = new PrevWordsInfo(null);
- }
-
- public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord,
- final PrevWordsInfo prevWordsInfo) {
+ public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
mEvents.clear();
Collections.copy(mEvents, lastComposedWord.mEvents);
mInputPointers.set(lastComposedWord.mInputPointers);
@@ -460,7 +437,6 @@ public final class WordComposer {
mCursorPositionWithinWord = mCodePointSize;
mRejectedBatchModeSuggestion = null;
mIsResumed = true;
- mPrevWordsInfo = prevWordsInfo;
}
public boolean isBatchMode() {
diff --git a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
index 139e73aa4..7071d8689 100644
--- a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
+++ b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
@@ -27,7 +27,6 @@ import com.android.inputmethod.latin.BinaryDictionaryFileDumper;
import com.android.inputmethod.latin.BinaryDictionaryGetter;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.DialogUtils;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
@@ -50,7 +49,7 @@ public class ExternalDictionaryGetterForDebug {
private static String[] findDictionariesInTheDownloadedFolder() {
final File[] files = new File(SOURCE_FOLDER).listFiles();
- final ArrayList<String> eligibleList = CollectionUtils.newArrayList();
+ final ArrayList<String> eligibleList = new ArrayList<>();
for (File f : files) {
final DictionaryHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(f);
if (null == header) continue;
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index 761f457ea..972580298 100644
--- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -21,13 +21,6 @@ public final class ProductionFlag {
// This class is not publicly instantiable.
}
- public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS = false;
-
- // When false, USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG suggests that all guarded
- // class-private DEBUG flags should be false, and any privacy controls should be enforced.
- // USES_DEVELOPMENT_ONLY_DIAGNOSTICS must be false for any production build.
- public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG = false;
-
public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false;
// When true, enable {@link InputMethodService#onUpdateCursor} callback with
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 7536ff94c..24cc1ef0d 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -32,7 +32,7 @@ import com.android.inputmethod.event.InputTransaction;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
+import com.android.inputmethod.latin.DictionaryFacilitator;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LastComposedWord;
import com.android.inputmethod.latin.LatinIME;
@@ -44,18 +44,14 @@ import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.InputTypeUtils;
-import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
import java.util.TreeSet;
@@ -79,7 +75,8 @@ public final class InputLogic {
private int mSpaceState;
// Never null
public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
- public final Suggest mSuggest = new Suggest();
+ public final Suggest mSuggest;
+ private final DictionaryFacilitator mDictionaryFacilitator;
public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
public final WordComposer mWordComposer;
@@ -88,7 +85,7 @@ public final class InputLogic {
private int mDeleteCount;
private long mLastKeyTime;
- public final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
+ public final TreeSet<Long> mCurrentlyPressedHardwareKeys = new TreeSet<>();
// Keeps track of most recently inserted text (multi-character key) for reverting
private String mEnteredText;
@@ -102,14 +99,19 @@ public final class InputLogic {
* Create a new instance of the input logic.
* @param latinIME the instance of the parent LatinIME. We should remove this when we can.
* @param suggestionStripViewAccessor an object to access the suggestion strip view.
+ * @param dictionaryFacilitator facilitator for getting suggestions and updating user history
+ * dictionary.
*/
public InputLogic(final LatinIME latinIME,
- final SuggestionStripViewAccessor suggestionStripViewAccessor) {
+ final SuggestionStripViewAccessor suggestionStripViewAccessor,
+ final DictionaryFacilitator dictionaryFacilitator) {
mLatinIME = latinIME;
mSuggestionStripViewAccessor = suggestionStripViewAccessor;
mWordComposer = new WordComposer();
mConnection = new RichInputConnection(latinIME);
mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
+ mSuggest = new Suggest(dictionaryFacilitator);
+ mDictionaryFacilitator = dictionaryFacilitator;
}
/**
@@ -132,7 +134,7 @@ public final class InputLogic {
resetComposingState(true /* alsoResetLastComposedWord */);
mDeleteCount = 0;
mSpaceState = SpaceState.NONE;
- mRecapitalizeStatus.deactivate();
+ mRecapitalizeStatus.disable(); // Do not perform recapitalize until the cursor is moved once
mCurrentlyPressedHardwareKeys.clear();
mSuggestedWords = SuggestedWords.EMPTY;
// In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying
@@ -173,7 +175,7 @@ public final class InputLogic {
final InputLogicHandler inputLogicHandler = mInputLogicHandler;
mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
inputLogicHandler.destroy();
- mSuggest.mDictionaryFacilitator.closeDictionaries();
+ mDictionaryFacilitator.closeDictionaries();
}
/**
@@ -196,19 +198,11 @@ public final class InputLogic {
resetComposingState(true /* alsoResetLastComposedWord */);
}
handler.postUpdateSuggestionStrip();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS
- && ResearchLogger.RESEARCH_KEY_OUTPUT_TEXT.equals(rawText)) {
- ResearchLogger.getInstance().onResearchKeySelected(mLatinIME);
- return;
- }
final String text = performSpecificTldProcessingOnTextInput(rawText);
if (SpaceState.PHANTOM == mSpaceState) {
promotePhantomSpace(settingsValues);
}
mConnection.commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onTextInput(text, false /* isBatchMode */);
- }
mConnection.endBatchEdit();
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.NONE;
@@ -235,14 +229,8 @@ public final class InputLogic {
// If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
if (suggestion.length() == 1 && suggestedWords.isPunctuationSuggestions()) {
// Word separators are suggested before the user inputs something.
- // So, LatinImeLogger logs "" as a user's input.
- LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
final Event event = Event.createPunctuationSuggestionPickedEvent(suggestionInfo);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
- false /* isBatchMode */, suggestedWords.mIsPrediction);
- }
return onCodeInput(settingsValues, event, keyboardShiftState, handler);
}
@@ -265,7 +253,7 @@ public final class InputLogic {
// code path as for other kinds, use commitChosenWord, and do everything normally. We will
// however need to reset the suggestion strip right away, because we know we can't take
// the risk of calling commitCompletion twice because we don't know how the app will react.
- if (SuggestedWordInfo.KIND_APP_DEFINED == suggestionInfo.mKind) {
+ if (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_APP_DEFINED)) {
mSuggestedWords = SuggestedWords.EMPTY;
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
@@ -278,14 +266,8 @@ public final class InputLogic {
// We need to log before we commit, because the word composer will store away the user
// typed word.
final String replacedWord = mWordComposer.getTypedWord();
- LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
commitChosenWord(settingsValues, suggestion,
LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
- mWordComposer.isBatchMode(), suggestionInfo.mScore,
- suggestionInfo.mKind, suggestionInfo.mSourceDict.mDictType);
- }
mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
mLastComposedWord.deactivate();
@@ -295,18 +277,12 @@ public final class InputLogic {
// We should show the "Touch again to save" hint if the user pressed the first entry
// AND it's in none of our current dictionaries (main, user or otherwise).
- final DictionaryFacilitatorForSuggest dictionaryFacilitator =
- mSuggest.mDictionaryFacilitator;
final boolean showingAddToDictionaryHint =
- (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
- || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
- && !dictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */);
+ (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_TYPED)
+ || suggestionInfo.isKindOf(SuggestedWordInfo.KIND_OOV_CORRECTION))
+ && !mDictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */);
- if (settingsValues.mIsInternal) {
- LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
- Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
- }
- if (showingAddToDictionaryHint && dictionaryFacilitator.isUserDictionaryEnabled()) {
+ if (showingAddToDictionaryHint && mDictionaryFacilitator.isUserDictionaryEnabled()) {
mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
} else {
// If we're not showing the "Touch again to save", then update the suggestion strip.
@@ -343,8 +319,16 @@ public final class InputLogic {
|| !mWordComposer.isComposingWord(); // safe to reset
final boolean hasOrHadSelection = (oldSelStart != oldSelEnd || newSelStart != newSelEnd);
final int moveAmount = newSelStart - oldSelStart;
- if (selectionChangedOrSafeToReset && (hasOrHadSelection
- || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
+ // As an added small gift from the framework, it happens upon rotation when there
+ // is a selection that we get a wrong cursor position delivered to startInput() that
+ // does not get reflected in the oldSel{Start,End} parameters to the next call to
+ // onUpdateSelection. In this case, we may have set a composition, and when we're here
+ // we realize we shouldn't have. In theory, in this case, selectionChangedOrSafeToReset
+ // should be true, but that is if the framework had taken that wrong cursor position
+ // into account, which means we have to reset the entire composing state whenever there
+ // is or was a selection regardless of whether it changed or not.
+ if (hasOrHadSelection || (selectionChangedOrSafeToReset
+ && !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
@@ -369,10 +353,12 @@ public final class InputLogic {
newSelStart, newSelEnd, false /* shouldFinishComposition */);
}
+ // The cursor has been moved : we now accept to perform recapitalization
+ mRecapitalizeStatus.enable();
// We moved the cursor. If we are touching a word, we need to resume suggestion.
- mLatinIME.mHandler.postResumeSuggestions();
- // Reset the last recapitalization.
- mRecapitalizeStatus.deactivate();
+ mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */);
+ // Stop the last recapitalization, if started.
+ mRecapitalizeStatus.stop();
return true;
}
@@ -393,16 +379,9 @@ public final class InputLogic {
final int keyboardShiftMode,
// TODO: remove this argument
final LatinIME.UIHandler handler) {
- // TODO: rework the following to not squash the keycode and the code point into the same
- // var because it's confusing. Instead the switch() should handle this in a readable manner.
- final int code =
- Event.NOT_A_CODE_POINT == event.mCodePoint ? event.mKeyCode : event.mCodePoint;
final InputTransaction inputTransaction = new InputTransaction(settingsValues, event,
SystemClock.uptimeMillis(), mSpaceState,
getActualCapsMode(settingsValues, keyboardShiftMode));
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onCodeInput(code, event.mX, event.mY);
- }
if (event.mKeyCode != Constants.CODE_DELETE
|| inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
mDeleteCount = 0;
@@ -424,7 +403,6 @@ public final class InputLogic {
switch (event.mKeyCode) {
case Constants.CODE_DELETE:
handleBackspace(inputTransaction);
- LatinImeLogger.logOnDelete(event.mX, event.mY);
break;
case Constants.CODE_SHIFT:
performRecapitalization(inputTransaction.mSettingsValues);
@@ -530,12 +508,6 @@ public final class InputLogic {
++mAutoCommitSequenceNumber;
mConnection.beginBatchEdit();
if (mWordComposer.isComposingWord()) {
- if (settingsValues.mIsInternal) {
- if (mWordComposer.isBatchMode()) {
- LatinImeLoggerUtils.onAutoCorrection("", mWordComposer.getTypedWord(), " ",
- mWordComposer);
- }
- }
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the batch input at the current cursor position.
@@ -572,11 +544,8 @@ public final class InputLogic {
}
}
mConnection.endBatchEdit();
- mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
- getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()),
- // Prev word is 1st word before cursor
- getPrevWordsInfoFromNthPreviousWordForSuggestion(
- settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */));
+ mWordComposer.setCapitalizedModeAtStartComposingTime(
+ getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()));
}
/* The sequence number member is only used in onUpdateBatchInput. It is increased each time
@@ -612,10 +581,8 @@ public final class InputLogic {
mSpaceState = SpaceState.PHANTOM;
keyboardSwitcher.requestUpdatingShiftState(
getCurrentAutoCapsState(settingsValues), getCurrentRecapitalizeState());
- mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
- getActualCapsMode(settingsValues,
- keyboardSwitcher.getKeyboardShiftMode()),
- new PrevWordsInfo(commitParts[0]));
+ mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode(
+ settingsValues, keyboardSwitcher.getKeyboardShiftMode()));
++mAutoCommitSequenceNumber;
}
}
@@ -675,19 +642,9 @@ public final class InputLogic {
|| Character.getType(codePoint) == Character.OTHER_SYMBOL) {
didAutoCorrect = handleSeparator(inputTransaction,
inputTransaction.mEvent.isSuggestionStripPress(), handler);
- if (inputTransaction.mSettingsValues.mIsInternal) {
- LatinImeLoggerUtils.onSeparator((char)codePoint,
- inputTransaction.mEvent.mX, inputTransaction.mEvent.mY);
- }
} else {
didAutoCorrect = false;
if (SpaceState.PHANTOM == inputTransaction.mSpaceState) {
- if (inputTransaction.mSettingsValues.mIsInternal) {
- if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
- LatinImeLoggerUtils.onAutoCorrection("", mWordComposer.getTypedWord(), " ",
- mWordComposer);
- }
- }
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the character at the current cursor position.
@@ -748,11 +705,10 @@ public final class InputLogic {
(!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)
|| !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces)) {
// Reset entirely the composing state anyway, then start composing a new word unless
- // the character is a single quote or a dash. The idea here is, single quote and dash
- // are not separators and they should be treated as normal characters, except in the
- // first position where they should not start composing a word.
- isComposingWord = (Constants.CODE_SINGLE_QUOTE != codePoint
- && Constants.CODE_DASH != codePoint);
+ // the character is a word connector. The idea here is, word connectors are not
+ // separators and they should be treated as normal characters, except in the first
+ // position where they should not start composing a word.
+ isComposingWord = !settingsValues.mSpacingAndPunctuations.isWordConnector(codePoint);
// Here we don't need to reset the last composed word. It will be reset
// when we commit this one, if we ever do; if on the other hand we backspace
// it entirely and resume suggestions on the previous word, we'd like to still
@@ -763,12 +719,7 @@ public final class InputLogic {
mWordComposer.processEvent(inputTransaction.mEvent);
// If it's the first letter, make note of auto-caps state
if (mWordComposer.isSingleLetter()) {
- // We pass 1 to getPreviousWordForSuggestion because we were not composing a word
- // yet, so the word we want is the 1st word before the cursor.
- mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
- inputTransaction.mShiftState,
- getPrevWordsInfoFromNthPreviousWordForSuggestion(
- settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */));
+ mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.mShiftState);
}
mConnection.setComposingText(getTextWithUnderline(
mWordComposer.getTypedWord()), 1);
@@ -786,10 +737,6 @@ public final class InputLogic {
mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
}
inputTransaction.setRequiresUpdateSuggestions();
- if (settingsValues.mIsInternal) {
- LatinImeLoggerUtils.onNonSeparator((char)codePoint, inputTransaction.mEvent.mX,
- inputTransaction.mEvent.mY);
- }
}
/**
@@ -818,7 +765,7 @@ public final class InputLogic {
}
// isComposingWord() may have changed since we stored wasComposing
if (mWordComposer.isComposingWord()) {
- if (settingsValues.mCorrectionEnabled) {
+ if (settingsValues.mAutoCorrectionEnabled) {
final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
: StringUtils.newSingleCodePointString(codePoint);
commitCurrentAutoCorrection(settingsValues, separator, handler);
@@ -852,9 +799,6 @@ public final class InputLogic {
if (needsPrecedingSpace) {
promotePhantomSpace(settingsValues);
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleSeparator(codePoint, wasComposingWord);
- }
if (!shouldAvoidSendingCode) {
sendKeyCodePoint(settingsValues, codePoint);
@@ -863,6 +807,7 @@ public final class InputLogic {
if (Constants.CODE_SPACE == codePoint) {
if (maybeDoubleSpacePeriod(inputTransaction)) {
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
+ inputTransaction.setRequiresUpdateSuggestions();
mSpaceState = SpaceState.DOUBLE;
} else if (!mSuggestedWords.isPunctuationSuggestions()) {
mSpaceState = SpaceState.WEAK;
@@ -932,10 +877,6 @@ public final class InputLogic {
}
if (mWordComposer.isComposingWord()) {
if (mWordComposer.isBatchMode()) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final String word = mWordComposer.getTypedWord();
- ResearchLogger.latinIME_handleBackspace_batch(word, 1);
- }
final String rejectedSuggestion = mWordComposer.getTypedWord();
mWordComposer.reset();
mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
@@ -950,9 +891,6 @@ public final class InputLogic {
inputTransaction.setRequiresUpdateSuggestions();
} else {
if (mLastComposedWord.canRevertCommit()) {
- if (inputTransaction.mSettingsValues.mIsInternal) {
- LatinImeLoggerUtils.onAutoCorrectionCancellation();
- }
revertCommit(inputTransaction);
return;
}
@@ -961,9 +899,6 @@ public final class InputLogic {
// This is triggered on backspace after a key that inputs multiple characters,
// like the smiley key or the .com key.
mConnection.deleteSurroundingText(mEnteredText.length(), 0);
- 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
@@ -975,6 +910,9 @@ public final class InputLogic {
if (mConnection.revertDoubleSpacePeriod()) {
// No need to reset mSpaceState, it has already be done (that's why we
// receive it as a parameter)
+ inputTransaction.setRequiresUpdateSuggestions();
+ mWordComposer.setCapitalizedModeAtStartComposingTime(
+ WordComposer.CAPS_MODE_OFF);
return;
}
} else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
@@ -993,10 +931,6 @@ public final class InputLogic {
mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
mConnection.getExpectedSelectionEnd());
mConnection.deleteSurroundingText(numCharsDeleted, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(numCharsDeleted,
- false /* shouldUncommitLogUnit */);
- }
} else {
// There is no selection, just delete one character.
if (Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) {
@@ -1031,10 +965,6 @@ public final class InputLogic {
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDelete, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(lengthToDelete,
- true /* shouldUncommitLogUnit */);
- }
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor();
@@ -1042,21 +972,18 @@ public final class InputLogic {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
codePointBeforeCursorToDeleteAgain) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain,
- true /* shouldUncommitLogUnit */);
- }
}
}
}
}
- if (inputTransaction.mSettingsValues.isSuggestionStripVisible()
+ if (inputTransaction.mSettingsValues
+ .isCurrentOrientationAllowingSuggestionsPerUserSettings()
&& inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mCurrentLanguageHasSpaces
&& !mConnection.isCursorFollowedByWordCharacter(
inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
- true /* includeResumedWordInSuggestions */);
+ true /* shouldIncludeResumedWordInSuggestions */);
}
}
}
@@ -1082,9 +1009,6 @@ public final class InputLogic {
mConnection.deleteSurroundingText(2, 0);
final String text = lastTwo.charAt(1) + " ";
mConnection.commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text);
- }
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
}
}
@@ -1168,11 +1092,6 @@ public final class InputLogic {
final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mSentenceSeparatorAndSpace;
mConnection.commitText(textToInsert, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
- false /* isBatchMode */);
- }
- mWordComposer.discardPreviousWordForSuggestion();
return true;
}
return false;
@@ -1209,18 +1128,24 @@ public final class InputLogic {
* @param settingsValues The current settings values.
*/
private void performRecapitalization(final SettingsValues settingsValues) {
- if (!mConnection.hasSelection()) {
- return; // No selection
+ if (!mConnection.hasSelection() || !mRecapitalizeStatus.mIsEnabled()) {
+ return; // No selection or recapitalize is disabled for now
+ }
+ final int selectionStart = mConnection.getExpectedSelectionStart();
+ final int selectionEnd = mConnection.getExpectedSelectionEnd();
+ final int numCharsSelected = selectionEnd - selectionStart;
+ if (numCharsSelected > Constants.MAX_CHARACTERS_FOR_RECAPITALIZATION) {
+ // We bail out if we have too many characters for performance reasons. We don't want
+ // to suck possibly multiple-megabyte data.
+ return;
}
- // If we have a recapitalize in progress, use it; otherwise, create a new one.
- if (!mRecapitalizeStatus.isActive()
- || !mRecapitalizeStatus.isSetAt(mConnection.getExpectedSelectionStart(),
- mConnection.getExpectedSelectionEnd())) {
+ // If we have a recapitalize in progress, use it; otherwise, start a new one.
+ if (!mRecapitalizeStatus.isStarted()
+ || !mRecapitalizeStatus.isSetAt(selectionStart, selectionEnd)) {
final CharSequence selectedText =
mConnection.getSelectedText(0 /* flags, 0 for no styles */);
if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection
- mRecapitalizeStatus.initialize(mConnection.getExpectedSelectionStart(),
- mConnection.getExpectedSelectionEnd(), selectedText.toString(),
+ mRecapitalizeStatus.start(selectionStart, selectionEnd, selectedText.toString(),
settingsValues.mLocale,
settingsValues.mSpacingAndPunctuations.mSortedWordSeparators);
// We trim leading and trailing whitespace.
@@ -1228,11 +1153,8 @@ public final class InputLogic {
}
mConnection.finishComposingText();
mRecapitalizeStatus.rotate();
- final int numCharsDeleted = mConnection.getExpectedSelectionEnd()
- - mConnection.getExpectedSelectionStart();
- mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
- mConnection.getExpectedSelectionEnd());
- mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ mConnection.setSelection(selectionEnd, selectionEnd);
+ mConnection.deleteSurroundingText(numCharsSelected, 0);
mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0);
mConnection.setSelection(mRecapitalizeStatus.getNewCursorStart(),
mRecapitalizeStatus.getNewCursorEnd());
@@ -1243,14 +1165,14 @@ public final class InputLogic {
// If correction is not enabled, we don't add words to the user history dictionary.
// That's to avoid unintended additions in some sensitive fields, or fields that
// expect to receive non-words.
- if (!settingsValues.mCorrectionEnabled) return;
+ if (!settingsValues.mAutoCorrectionEnabled) return;
if (TextUtils.isEmpty(suggestion)) return;
final boolean wasAutoCapitalized =
mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps();
final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
System.currentTimeMillis());
- mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized,
+ mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized,
prevWordsInfo, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
}
@@ -1269,7 +1191,7 @@ public final class InputLogic {
return;
}
- final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<SuggestedWords>();
+ final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<>();
mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING,
SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
@Override
@@ -1300,12 +1222,12 @@ public final class InputLogic {
* do nothing.
*
* @param settingsValues the current values of the settings.
- * @param includeResumedWordInSuggestions whether to include the word on which we resume
+ * @param shouldIncludeResumedWordInSuggestions whether to include the word on which we resume
* suggestions in the suggestion list.
*/
// TODO: make this private.
public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues,
- final boolean includeResumedWordInSuggestions) {
+ final boolean shouldIncludeResumedWordInSuggestions) {
// HACK: We may want to special-case some apps that exhibit bad behavior in case of
// recorrection. This is a temporary, stopgap measure that will be removed later.
// TODO: remove this.
@@ -1329,10 +1251,7 @@ public final class InputLogic {
final int expectedCursorPosition = mConnection.getExpectedSelectionStart();
if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)) {
// Show predictions.
- mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
- WordComposer.CAPS_MODE_OFF,
- getPrevWordsInfoFromNthPreviousWordForSuggestion(
- settingsValues.mSpacingAndPunctuations, 1));
+ mWordComposer.setCapitalizedModeAtStartComposingTime(WordComposer.CAPS_MODE_OFF);
mLatinIME.mHandler.postUpdateSuggestionStrip();
return;
}
@@ -1349,9 +1268,9 @@ public final class InputLogic {
// we just do not resume because it's safer.
final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor();
if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return;
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
final String typedWord = range.mWord.toString();
- if (includeResumedWordInSuggestions) {
+ if (shouldIncludeResumedWordInSuggestions) {
suggestions.add(new SuggestedWordInfo(typedWord,
SuggestedWords.MAX_SUGGESTIONS + 1,
SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
@@ -1384,14 +1303,15 @@ public final class InputLogic {
settingsValues.mSpacingAndPunctuations,
0 == numberOfCharsInWordBeforeCursor ? 1 : 2);
mWordComposer.setComposingWord(codePoints,
- mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), prevWordsInfo);
+ mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
mWordComposer.setCursorPositionWithinWord(
typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor());
- if (suggestions.isEmpty()) {
- // We come here if there weren't any suggestion spans on this word. We will try to
- // compute suggestions for it instead.
+ if (suggestions.size() <= (shouldIncludeResumedWordInSuggestions ? 1 : 0)) {
+ // If there weren't any suggestion spans on this word, suggestions#size() will be 1
+ // if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we
+ // have no useful suggestions, so we will try to compute some for it instead.
mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING,
SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
@Override
@@ -1399,7 +1319,7 @@ public final class InputLogic {
final SuggestedWords suggestedWordsIncludingTypedWord) {
final SuggestedWords suggestedWords;
if (suggestedWordsIncludingTypedWord.size() > 1
- && !includeResumedWordInSuggestions) {
+ && !shouldIncludeResumedWordInSuggestions) {
// We were able to compute new suggestions for this word.
// Remove the typed word, since we don't want to display it in this
// case. The #getSuggestedWordsExcludingTypedWord() method sets
@@ -1462,8 +1382,7 @@ public final class InputLogic {
}
mConnection.deleteSurroundingText(deleteLength, 0);
if (!TextUtils.isEmpty(prevWordsInfo.mPrevWord) && !TextUtils.isEmpty(committedWord)) {
- mSuggest.mDictionaryFacilitator.cancelAddingUserHistory(
- prevWordsInfo, committedWordString);
+ mDictionaryFacilitator.cancelAddingUserHistory(prevWordsInfo, committedWordString);
}
final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
final SpannableString textToCommit = new SpannableString(stringToCommit);
@@ -1473,7 +1392,7 @@ public final class InputLogic {
committedWord.length(), Object.class);
final int lastCharIndex = textToCommit.length() - 1;
// We will collect all suggestions in the following array.
- final ArrayList<String> suggestions = CollectionUtils.newArrayList();
+ final ArrayList<String> suggestions = new ArrayList<>();
// First, add the committed word to the list of suggestions.
suggestions.add(committedWordString);
for (final Object span : spans) {
@@ -1512,20 +1431,10 @@ public final class InputLogic {
// with the typed word, so we need to resume suggestions right away.
final int[] codePoints = StringUtils.toCodePointArray(stringToCommit);
mWordComposer.setComposingWord(codePoints,
- mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), prevWordsInfo);
+ mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
mConnection.setComposingText(textToCommit, 1);
}
- if (inputTransaction.mSettingsValues.mIsInternal) {
- LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
- Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
- }
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_revertCommit(committedWord.toString(),
- originallyTypedWord.toString(),
- mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString);
- }
- // Don't restart suggestion yet. We'll restart if the user deletes the
- // separator.
+ // Don't restart suggestion yet. We'll restart if the user deletes the separator.
mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
// We have a separator between the word and the cursor: we should show predictions.
inputTransaction.setRequiresUpdateSuggestions();
@@ -1577,7 +1486,7 @@ public final class InputLogic {
}
public int getCurrentRecapitalizeState() {
- if (!mRecapitalizeStatus.isActive()
+ if (!mRecapitalizeStatus.isStarted()
|| !mRecapitalizeStatus.isSetAt(mConnection.getExpectedSelectionStart(),
mConnection.getExpectedSelectionEnd())) {
// Not recapitalizing at the moment
@@ -1609,8 +1518,9 @@ public final class InputLogic {
return mConnection.getPrevWordsInfoFromNthPreviousWord(
spacingAndPunctuations, nthPreviousWord);
} else {
- return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? new PrevWordsInfo()
- : new PrevWordsInfo(mLastComposedWord.mCommittedWord.toString());
+ return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ?
+ PrevWordsInfo.BEGINNING_OF_SENTENCE :
+ new PrevWordsInfo(mLastComposedWord.mCommittedWord.toString());
}
}
@@ -1788,9 +1698,6 @@ public final class InputLogic {
*/
// TODO: replace these two parameters with an InputTransaction
private void sendKeyCodePoint(final SettingsValues settingsValues, final int codePoint) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_sendKeyCodePoint(codePoint);
- }
// TODO: Remove this special handling of digit letters.
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (codePoint >= '0' && codePoint <= '9') {
@@ -1822,9 +1729,6 @@ public final class InputLogic {
if (settingsValues.shouldInsertSpacesAutomatically()
&& settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
&& !mConnection.textBeforeCursorLooksLikeURL()) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_promotePhantomSpace();
- }
sendKeyCodePoint(settingsValues, Constants.CODE_SPACE);
}
}
@@ -1866,9 +1770,6 @@ public final class InputLogic {
mConnection.setComposingText(batchInputText, 1);
}
mConnection.endBatchEdit();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords);
- }
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.PHANTOM;
keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(settingsValues),
@@ -1895,9 +1796,6 @@ public final class InputLogic {
if (!mWordComposer.isComposingWord()) return;
final String typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
- }
commitChosenWord(settingsValues, typedWord,
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString);
}
@@ -1937,15 +1835,6 @@ public final class InputLogic {
throw new RuntimeException("We have an auto-correction but the typed word "
+ "is empty? Impossible! I must commit suicide.");
}
- if (settingsValues.mIsInternal) {
- LatinImeLoggerUtils.onAutoCorrection(
- typedWord, autoCorrection, separator, mWordComposer);
- }
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final SuggestedWords suggestedWords = mSuggestedWords;
- ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
- separator, mWordComposer.isBatchMode(), suggestedWords);
- }
commitChosenWord(settingsValues, autoCorrection,
LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator);
if (!typedWord.equals(autoCorrection)) {
@@ -1989,21 +1878,6 @@ public final class InputLogic {
// strings.
mLastComposedWord = mWordComposer.commitWord(commitType,
chosenWordWithSuggestions, separatorString, prevWordsInfo);
- final boolean shouldDiscardPreviousWordForSuggestion;
- if (0 == StringUtils.codePointCount(separatorString)) {
- // Separator is 0-length, we can keep the previous word for suggestion. Either this
- // was a manual pick or the language has no spaces in which case we want to keep the
- // previous word, or it was the keyboard closing or the cursor moving in which case it
- // will be reset anyway.
- shouldDiscardPreviousWordForSuggestion = false;
- } else {
- // Otherwise, we discard if the separator contains any non-whitespace.
- shouldDiscardPreviousWordForSuggestion =
- !StringUtils.containsOnlyWhitespace(separatorString);
- }
- if (shouldDiscardPreviousWordForSuggestion) {
- mWordComposer.discardPreviousWordForSuggestion();
- }
}
/**
@@ -2023,9 +1897,11 @@ public final class InputLogic {
final boolean tryResumeSuggestions, final int remainingTries,
// TODO: remove these arguments
final LatinIME.UIHandler handler) {
+ final boolean shouldFinishComposition = mConnection.hasSelection()
+ || !mConnection.isCursorPositionKnown();
if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(
mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd(),
- false /* shouldFinishComposition */)) {
+ shouldFinishComposition)) {
if (0 < remainingTries) {
handler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
return false;
@@ -2035,7 +1911,9 @@ public final class InputLogic {
}
mConnection.tryFixLyingCursorPosition();
if (tryResumeSuggestions) {
- handler.postResumeSuggestions();
+ // This is triggered when starting input anew, so we want to include the resumed
+ // word in suggestions.
+ handler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */);
}
return true;
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index f5f072b7a..a2ae74b20 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -192,8 +192,9 @@ public final class FormatSpec {
public static final int VERSION2 = 2;
// Dictionary version used for testing.
public static final int VERSION4_ONLY_FOR_TESTING = 399;
- public static final int VERSION4 = 401;
- public static final int VERSION4_DEV = 402;
+ public static final int VERSION401 = 401;
+ public static final int VERSION4 = 402;
+ public static final int VERSION4_DEV = 403;
static final int MINIMUM_SUPPORTED_VERSION = VERSION2;
static final int MAXIMUM_SUPPORTED_VERSION = VERSION4_DEV;
diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
index 853392200..31cb59756 100644
--- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
+++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.BinaryDictionary;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.CombinedFormatUtils;
import com.android.inputmethod.latin.utils.StringUtils;
@@ -35,6 +34,8 @@ public final class WordProperty implements Comparable<WordProperty> {
public final ProbabilityInfo mProbabilityInfo;
public final ArrayList<WeightedString> mShortcutTargets;
public final ArrayList<WeightedString> mBigrams;
+ // TODO: Support mIsBeginningOfSentence.
+ public final boolean mIsBeginningOfSentence;
public final boolean mIsNotAWord;
public final boolean mIsBlacklistEntry;
public final boolean mHasShortcuts;
@@ -51,6 +52,7 @@ public final class WordProperty implements Comparable<WordProperty> {
mProbabilityInfo = probabilityInfo;
mShortcutTargets = shortcutTargets;
mBigrams = bigrams;
+ mIsBeginningOfSentence = false;
mIsNotAWord = isNotAWord;
mIsBlacklistEntry = isBlacklistEntry;
mHasBigrams = bigrams != null && !bigrams.isEmpty();
@@ -75,8 +77,9 @@ public final class WordProperty implements Comparable<WordProperty> {
final ArrayList<Integer> shortcutProbabilities) {
mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo);
- mShortcutTargets = CollectionUtils.newArrayList();
- mBigrams = CollectionUtils.newArrayList();
+ mShortcutTargets = new ArrayList<>();
+ mBigrams = new ArrayList<>();
+ mIsBeginningOfSentence = false;
mIsNotAWord = isNotAWord;
mIsBlacklistEntry = isBlacklisted;
mHasShortcuts = hasShortcuts;
diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
index a446672cb..ab3ef964e 100644
--- a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
+++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
@@ -35,7 +35,7 @@ public class AccountUtils {
}
public static List<String> getDeviceAccountsEmailAddresses(final Context context) {
- final ArrayList<String> retval = new ArrayList<String>();
+ final ArrayList<String> retval = new ArrayList<>();
for (final Account account : getAccounts(context)) {
final String name = account.name;
if (Patterns.EMAIL_ADDRESS.matcher(name).matches()) {
@@ -54,7 +54,7 @@ public class AccountUtils {
*/
public static List<String> getDeviceAccountsWithDomain(
final Context context, final String domain) {
- final ArrayList<String> retval = new ArrayList<String>();
+ final ArrayList<String> retval = new ArrayList<>();
final String atDomain = "@" + domain.toLowerCase(Locale.ROOT);
for (final Account account : getAccounts(context)) {
if (account.name.toLowerCase(Locale.ROOT).endsWith(atDomain)) {
diff --git a/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java b/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java
index 96f03f9ff..a96018fe9 100644
--- a/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java
@@ -26,7 +26,7 @@ import java.io.File;
import java.util.Locale;
public class ContextualDictionary extends ExpandableBinaryDictionary {
- /* package */ static final String NAME = PersonalizationDictionary.class.getSimpleName();
+ /* package */ static final String NAME = ContextualDictionary.class.getSimpleName();
private ContextualDictionary(final Context context, final Locale locale,
final File dictFile) {
@@ -35,13 +35,19 @@ public class ContextualDictionary extends ExpandableBinaryDictionary {
// Always reset the contents.
clear();
}
+
@UsedForTesting
public static ContextualDictionary getDictionary(final Context context, final Locale locale,
- final File dictFile) {
+ final File dictFile, final String dictNamePrefix) {
return new ContextualDictionary(context, locale, dictFile);
}
@Override
+ protected boolean enableBeginningOfSentencePrediction() {
+ return true;
+ }
+
+ @Override
public boolean isValidWord(final String word) {
// Strings out of this dictionary should not be considered existing words.
return false;
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 06bdba054..1ba7b366f 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -31,7 +31,6 @@ import java.util.Map;
* model.
*/
public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableBinaryDictionary {
- private static final String TAG = DecayingExpandableBinaryDictionaryBase.class.getSimpleName();
private static final boolean DBG_DUMP_ON_CLOSE = false;
/** Any pair being typed or picked */
@@ -81,4 +80,10 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
/* package */ void runGCIfRequired() {
runGCIfRequired(false /* mindsBlockByGC */);
}
+
+ @Override
+ public boolean isValidWord(final String word) {
+ // Strings out of this dictionary should not be considered existing words.
+ return false;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
new file mode 100644
index 000000000..9d72de8c5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+public class PersonalizationDataChunk {
+ public final boolean mInputByUser;
+ public final List<String> mTokens;
+ public final int mTimestampInSeconds;
+ public final String mPackageName;
+ public final Locale mlocale = null;
+
+ public PersonalizationDataChunk(boolean inputByUser, final List<String> tokens,
+ final int timestampInSeconds, final String packageName) {
+ mInputByUser = inputByUser;
+ mTokens = Collections.unmodifiableList(tokens);
+ mTimestampInSeconds = timestampInSeconds;
+ mPackageName = packageName;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
index 1423fceff..f2ad22ac7 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -35,13 +35,7 @@ public class PersonalizationDictionary extends DecayingExpandableBinaryDictionar
@UsedForTesting
public static PersonalizationDictionary getDictionary(final Context context,
- final Locale locale, final File dictFile) {
+ final Locale locale, final File dictFile, final String dictNamePrefix) {
return PersonalizationHelper.getPersonalizationDictionary(context, locale);
}
-
- @Override
- public boolean isValidWord(final String word) {
- // Strings out of this dictionary should not be considered existing words.
- return false;
- }
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
deleted file mode 100644
index 9bef7a198..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.personalization;
-
-import android.content.Context;
-import android.content.res.Configuration;
-
-import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
-import com.android.inputmethod.latin.utils.DistracterFilter;
-
-public class PersonalizationDictionarySessionRegistrar {
- public static void init(final Context context,
- final DictionaryFacilitatorForSuggest dictionaryFacilitator,
- final DistracterFilter distracterFilter) {
- }
-
- public static void onConfigurationChanged(final Context context, final Configuration conf,
- final DictionaryFacilitatorForSuggest dictionaryFacilitator,
- final DistracterFilter distracterFilter) {
- }
-
- public static void onUpdateData(final Context context, final String type) {
- }
-
- public static void onRemoveData(final Context context, final String type) {
- }
-
- public static void resetAll(final Context context) {
- }
-
- public static void close(final Context context) {
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdater.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdater.java
new file mode 100644
index 000000000..07bcf98cb
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdater.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import java.util.Locale;
+
+import android.content.Context;
+
+import com.android.inputmethod.latin.DictionaryFacilitator;
+
+public class PersonalizationDictionaryUpdater {
+ public PersonalizationDictionaryUpdater(final Context context,
+ final DictionaryFacilitator dictionaryFacilitator) {
+ // Clear and never update the personalization dictionary.
+ PersonalizationHelper.removeAllPersonalizationDictionaries(context);
+ dictionaryFacilitator.clearPersonalizationDictionary();
+ }
+
+ public Locale getLocale() {
+ return null;
+ }
+
+ public void onLoadSettings(final boolean usePersonalizedDicts,
+ final boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes) {
+ }
+
+ public void onDestroy() {
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 6ef505e76..aac40940b 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -16,12 +16,11 @@
package com.android.inputmethod.latin.personalization;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.FileUtils;
-
import android.content.Context;
import android.util.Log;
+import com.android.inputmethod.latin.utils.FileUtils;
+
import java.io.File;
import java.io.FilenameFilter;
import java.lang.ref.SoftReference;
@@ -33,9 +32,9 @@ public class PersonalizationHelper {
private static final String TAG = PersonalizationHelper.class.getSimpleName();
private static final boolean DEBUG = false;
private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
- sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
+ sLangUserHistoryDictCache = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>>
- sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
+ sLangPersonalizationDictCache = new ConcurrentHashMap<>();
public static UserHistoryDictionary getUserHistoryDictionary(
final Context context, final Locale locale) {
@@ -54,8 +53,7 @@ public class PersonalizationHelper {
}
}
final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale);
- sLangUserHistoryDictCache.put(localeStr,
- new SoftReference<UserHistoryDictionary>(dict));
+ sLangUserHistoryDictCache.put(localeStr, new SoftReference<>(dict));
return dict;
}
}
@@ -108,8 +106,7 @@ public class PersonalizationHelper {
}
}
final PersonalizationDictionary dict = new PersonalizationDictionary(context, locale);
- sLangPersonalizationDictCache.put(
- localeStr, new SoftReference<PersonalizationDictionary>(dict));
+ sLangPersonalizationDictCache.put(localeStr, new SoftReference<>(dict));
return dict;
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index f89caf921..3916fc24c 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -23,6 +23,7 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.ExpandableBinaryDictionary;
import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.utils.DistracterFilter;
import java.io.File;
import java.util.Locale;
@@ -42,16 +43,10 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas
@UsedForTesting
public static UserHistoryDictionary getDictionary(final Context context, final Locale locale,
- final File dictFile) {
+ final File dictFile, final String dictNamePrefix) {
return PersonalizationHelper.getUserHistoryDictionary(context, locale);
}
- @Override
- public boolean isValidWord(final String word) {
- // Strings out of this dictionary should not be considered existing words.
- return false;
- }
-
/**
* Add a word to the user history dictionary.
*
@@ -60,10 +55,11 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas
* @param word the word the user inputted
* @param isValid whether the word is valid or not
* @param timestamp the timestamp when the word has been inputted
+ * @param distracterFilter the filter to check whether the word is a distracter
*/
public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
final PrevWordsInfo prevWordsInfo, final String word, final boolean isValid,
- final int timestamp) {
+ final int timestamp, final DistracterFilter distracterFilter) {
final String prevWord = prevWordsInfo.mPrevWord;
if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
(prevWord != null && prevWord.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
@@ -71,8 +67,9 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas
}
final int frequency = isValid ?
FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
- userHistoryDictionary.addUnigramEntry(word, frequency, null /* shortcutTarget */,
- 0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */, timestamp);
+ userHistoryDictionary.addUnigramEntryWithCheckingDistracter(word, frequency,
+ null /* shortcutTarget */, 0 /* shortcutFreq */, false /* isNotAWord */,
+ false /* isBlacklisted */, timestamp, distracterFilter);
// Do not insert a word as a bigram of itself
if (word.equals(prevWord)) {
return;
diff --git a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
index 39977e76f..31fa86774 100644
--- a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
@@ -47,7 +47,6 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.DialogUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
@@ -101,7 +100,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
super(context, android.R.layout.simple_spinner_item);
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet();
+ final TreeSet<SubtypeLocaleItem> items = new TreeSet<>();
final InputMethodInfo imi = RichInputMethodManager.getInstance()
.getInputMethodInfoOfThisIme();
final int count = imi.getSubtypeCount();
@@ -369,7 +368,6 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
mSubtype = (InputMethodSubtype)source.readParcelable(null);
}
- @SuppressWarnings("hiding")
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
@@ -516,8 +514,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
localeString, keyboardLayoutSetName);
}
- private AlertDialog createDialog(
- @SuppressWarnings("unused") final SubtypePreference subtypePref) {
+ private AlertDialog createDialog(final SubtypePreference subtypePref) {
final AlertDialog.Builder builder = new AlertDialog.Builder(
DialogUtils.getPlatformDialogThemeContext(getActivity()));
builder.setTitle(R.string.custom_input_styles_title)
@@ -555,7 +552,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
private InputMethodSubtype[] getSubtypes() {
final PreferenceGroup group = getPreferenceScreen();
- final ArrayList<InputMethodSubtype> subtypes = CollectionUtils.newArrayList();
+ final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
final int count = group.getPreferenceCount();
for (int i = 0; i < count; i++) {
final Preference pref = group.getPreference(i);
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index c4c1234fc..845ddb377 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -25,11 +25,12 @@ import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceFragment;
+import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryDumpBroadcastReceiver;
-import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.DictionaryFacilitator;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.debug.ExternalDictionaryGetterForDebug;
import com.android.inputmethod.latin.utils.ApplicationUtils;
@@ -40,8 +41,6 @@ public final class DebugSettings extends PreferenceFragment
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_KEY_PREVIEW_SHOW_UP_START_SCALE =
"pref_key_preview_show_up_start_scale";
public static final String PREF_KEY_PREVIEW_DISMISS_END_SCALE =
@@ -51,18 +50,14 @@ public final class DebugSettings extends PreferenceFragment
public static final String PREF_KEY_PREVIEW_DISMISS_DURATION =
"pref_key_preview_dismiss_duration";
private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary";
- private static final String PREF_DUMP_CONTACTS_DICT = "dump_contacts_dict";
- private static final String PREF_DUMP_USER_DICT = "dump_user_dict";
- private static final String PREF_DUMP_USER_HISTORY_DICT = "dump_user_history_dict";
- private static final String PREF_DUMP_PERSONALIZATION_DICT = "dump_personalization_dict";
+ private static final String PREF_KEY_DUMP_DICTS = "pref_key_dump_dictionaries";
+ private static final String PREF_KEY_DUMP_DICT_PREFIX = "pref_key_dump_dictionaries";
+ private static final String DICT_NAME_KEY_FOR_EXTRAS = "dict_name";
public static final String PREF_SLIDING_KEY_INPUT_PREVIEW = "pref_sliding_key_input_preview";
public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout";
- private static final boolean SHOW_STATISTICS_LOGGING = false;
-
private boolean mServiceNeedsRestart = false;
private CheckBoxPreference mDebugMode;
- private CheckBoxPreference mStatisticsLoggingPref;
@Override
public void onCreate(Bundle icicle) {
@@ -71,21 +66,6 @@ public final class DebugSettings extends PreferenceFragment
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
- final Preference usabilityStudyPref = findPreference(PREF_USABILITY_STUDY_MODE);
- if (usabilityStudyPref instanceof CheckBoxPreference) {
- final CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref;
- checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE,
- LatinImeLogger.getUsabilityStudyMode(prefs)));
- checkbox.setSummary(R.string.settings_warning_researcher_mode);
- }
- final Preference statisticsLoggingPref = findPreference(PREF_STATISTICS_LOGGING);
- if (statisticsLoggingPref instanceof CheckBoxPreference) {
- mStatisticsLoggingPref = (CheckBoxPreference) statisticsLoggingPref;
- if (!SHOW_STATISTICS_LOGGING) {
- getPreferenceScreen().removePreference(statisticsLoggingPref);
- }
- }
-
final PreferenceScreen readExternalDictionary =
(PreferenceScreen) findPreference(PREF_READ_EXTERNAL_DICTIONARY);
if (null != readExternalDictionary) {
@@ -101,16 +81,18 @@ public final class DebugSettings extends PreferenceFragment
});
}
+ final PreferenceGroup dictDumpPreferenceGroup =
+ (PreferenceGroup)findPreference(PREF_KEY_DUMP_DICTS);
final OnPreferenceClickListener dictDumpPrefClickListener =
new DictDumpPrefClickListener(this);
- findPreference(PREF_DUMP_CONTACTS_DICT).setOnPreferenceClickListener(
- dictDumpPrefClickListener);
- findPreference(PREF_DUMP_USER_DICT).setOnPreferenceClickListener(
- dictDumpPrefClickListener);
- findPreference(PREF_DUMP_USER_HISTORY_DICT).setOnPreferenceClickListener(
- dictDumpPrefClickListener);
- findPreference(PREF_DUMP_PERSONALIZATION_DICT).setOnPreferenceClickListener(
- dictDumpPrefClickListener);
+ for (final String dictName : DictionaryFacilitator.DICT_TYPE_TO_CLASS.keySet()) {
+ final Preference preference = new Preference(getActivity());
+ preference.setKey(PREF_KEY_DUMP_DICT_PREFIX + dictName);
+ preference.setTitle("Dump " + dictName + " dictionary");
+ preference.setOnPreferenceClickListener(dictDumpPrefClickListener);
+ preference.getExtras().putString(DICT_NAME_KEY_FOR_EXTRAS, dictName);
+ dictDumpPreferenceGroup.addPreference(preference);
+ }
final Resources res = getResources();
setupKeyLongpressTimeoutSettings(prefs, res);
setupKeyPreviewAnimationDuration(prefs, res, PREF_KEY_PREVIEW_SHOW_UP_DURATION,
@@ -138,18 +120,7 @@ public final class DebugSettings extends PreferenceFragment
@Override
public boolean onPreferenceClick(final Preference arg0) {
- final String dictName;
- if (arg0.getKey().equals(PREF_DUMP_CONTACTS_DICT)) {
- dictName = Dictionary.TYPE_CONTACTS;
- } else if (arg0.getKey().equals(PREF_DUMP_USER_DICT)) {
- dictName = Dictionary.TYPE_USER;
- } else if (arg0.getKey().equals(PREF_DUMP_USER_HISTORY_DICT)) {
- dictName = Dictionary.TYPE_USER_HISTORY;
- } else if (arg0.getKey().equals(PREF_DUMP_PERSONALIZATION_DICT)) {
- dictName = Dictionary.TYPE_PERSONALIZATION;
- } else {
- dictName = null;
- }
+ final String dictName = arg0.getExtras().getString(DICT_NAME_KEY_FOR_EXTRAS);
if (dictName != null) {
final Intent intent =
new Intent(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
@@ -163,27 +134,22 @@ public final class DebugSettings extends PreferenceFragment
@Override
public void onStop() {
super.onStop();
- if (mServiceNeedsRestart) Process.killProcess(Process.myPid());
+ if (mServiceNeedsRestart) {
+ Process.killProcess(Process.myPid());
+ }
}
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
- if (key.equals(PREF_DEBUG_MODE)) {
- if (mDebugMode != null) {
- mDebugMode.setChecked(prefs.getBoolean(PREF_DEBUG_MODE, false));
- final boolean checked = mDebugMode.isChecked();
- if (mStatisticsLoggingPref != null) {
- if (checked) {
- getPreferenceScreen().addPreference(mStatisticsLoggingPref);
- } else {
- getPreferenceScreen().removePreference(mStatisticsLoggingPref);
- }
- }
- updateDebugMode();
- mServiceNeedsRestart = true;
- }
- } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)) {
+ if (key.equals(PREF_DEBUG_MODE) && mDebugMode != null) {
+ mDebugMode.setChecked(prefs.getBoolean(PREF_DEBUG_MODE, false));
+ updateDebugMode();
mServiceNeedsRestart = true;
+ return;
+ }
+ if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)) {
+ mServiceNeedsRestart = true;
+ return;
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
index cd726c969..04a2ee3ce 100644
--- a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
+++ b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
@@ -20,7 +20,8 @@ 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 static final int BLOCK_OFFENSIVE_WORDS = 2;
+ private static final int OPTIONS_SIZE = 3;
private final int[] mOptions = new int[OPTIONS_SIZE
+ AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
@@ -33,6 +34,10 @@ public class NativeSuggestOptions {
setBooleanOption(USE_FULL_EDIT_DISTANCE, value);
}
+ public void setBlockOffensiveWords(final boolean value) {
+ setBooleanOption(BLOCK_OFFENSIVE_WORDS, value);
+ }
+
public void setAdditionalFeaturesOptions(final int[] additionalOptions) {
if (additionalOptions == null) {
return;
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index d4f6bcd10..235847799 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
+import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
@@ -60,6 +61,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
"pref_key_use_double_space_period";
public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE =
"pref_key_block_potentially_offensive";
+ public static final boolean ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS =
+ (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
+ || (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT
+ && Build.VERSION.CODENAME.equals("REL"));
public static final String PREF_SHOW_LANGUAGE_SWITCH_KEY =
"pref_show_language_switch_key";
public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST =
@@ -327,10 +332,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
R.array.keypress_vibration_durations, DEFAULT_KEYPRESS_VIBRATION_DURATION));
}
- public static boolean readUsabilityStudyMode(final SharedPreferences prefs) {
- return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true);
- }
-
public static float readKeyPreviewAnimationScale(final SharedPreferences prefs,
final String prefKey, final float defaultValue) {
final float fraction = prefs.getFloat(prefKey, UNDEFINED_PREFERENCE_VALUE_FLOAT);
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index e1d38e7c4..5eb0377c7 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -59,7 +59,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
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 */;
+ || Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2;
private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) {
final Preference preference = findPreference(preferenceKey);
@@ -152,10 +152,6 @@ 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);
@@ -169,6 +165,13 @@ public final class SettingsFragment extends InputMethodSettingsFragment
removePreference(Settings.PREF_VIBRATE_ON, generalSettings);
removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedSettings);
}
+ if (!Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS) {
+ removePreference(
+ Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, advancedSettings);
+ removePreference(
+ Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, advancedSettings);
+ }
+
// TODO: consolidate key preview dismiss delay with the key preview animation parameters.
if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
@@ -199,9 +202,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON, advancedSettings);
}
- setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST,
- Settings.readShowsLanguageSwitchKey(prefs));
-
final PreferenceGroup textCorrectionGroup =
(PreferenceGroup) findPreference(Settings.PREF_CORRECTION_SETTINGS);
final PreferenceScreen dictionaryLink =
@@ -213,6 +213,20 @@ public final class SettingsFragment extends InputMethodSettingsFragment
textCorrectionGroup.removePreference(dictionaryLink);
}
+ if (ProductionFlag.IS_METRICS_LOGGING_SUPPORTED) {
+ final Preference enableMetricsLogging =
+ findPreference(Settings.PREF_ENABLE_METRICS_LOGGING);
+ if (enableMetricsLogging != null) {
+ final int applicationLabelRes = context.getApplicationInfo().labelRes;
+ final String applicationName = res.getString(applicationLabelRes);
+ final String enableMetricsLoggingTitle = res.getString(
+ R.string.enable_metrics_logging, applicationName);
+ enableMetricsLogging.setTitle(enableMetricsLoggingTitle);
+ }
+ } else {
+ removePreference(Settings.PREF_ENABLE_METRICS_LOGGING, textCorrectionGroup);
+ }
+
final Preference editPersonalDictionary =
findPreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY);
final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent();
@@ -299,9 +313,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
if (key.equals(Settings.PREF_POPUP_ON)) {
setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
Settings.readKeyPreviewPopupEnabled(prefs, res));
- } else if (key.equals(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY)) {
- setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST,
- Settings.readShowsLanguageSwitchKey(prefs));
} else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 16fd05877..44104019b 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -28,6 +28,7 @@ import com.android.inputmethod.compat.AppWorkaroundsUtils;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
@@ -84,7 +85,8 @@ public final class SettingsValues {
public final int mKeyPreviewPopupDismissDelay;
private final boolean mAutoCorrectEnabled;
public final float mAutoCorrectionThreshold;
- public final boolean mCorrectionEnabled;
+ // TODO: Rename this to mAutoCorrectionEnabledPerUserSettings.
+ public final boolean mAutoCorrectionEnabled;
public final int mSuggestionVisibility;
public final int mDisplayOrientation;
private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds;
@@ -109,7 +111,8 @@ public final class SettingsValues {
// Store the input attributes
if (null == inputAttributes) {
- mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */);
+ mInputAttributes = new InputAttributes(
+ null, false /* isFullscreenMode */, context.getPackageName());
} else {
mInputAttributes = inputAttributes;
}
@@ -121,13 +124,18 @@ public final class SettingsValues {
mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res);
mSlidingKeyInputPreviewEnabled = prefs.getBoolean(
DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true);
- mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res);
+ mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res)
+ && !mInputAttributes.mIsPasswordField
+ && !mInputAttributes.hasNoMicrophoneKeyOption()
+ && SubtypeSwitcher.getInstance().isShortcutImeEnabled();
final String autoCorrectionThresholdRawValue = prefs.getString(
Settings.PREF_AUTO_CORRECTION_THRESHOLD,
res.getString(R.string.auto_correction_threshold_mode_index_modest));
- mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean(
- Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false);
- mShowsLanguageSwitchKey = Settings.readShowsLanguageSwitchKey(prefs);
+ mIncludesOtherImesInLanguageSwitchList = Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS
+ ? prefs.getBoolean(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false)
+ : true /* forcibly */;
+ mShowsLanguageSwitchKey = Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS
+ ? Settings.readShowsLanguageSwitchKey(prefs) : true /* forcibly */;
mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
mUsePersonalizedDicts = prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true);
mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true);
@@ -148,7 +156,7 @@ public final class SettingsValues {
mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
mPhraseGestureEnabled = Settings.readPhraseGestureEnabled(prefs, res);
- mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
+ mAutoCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
final String showSuggestionsSetting = prefs.getString(
Settings.PREF_SHOW_SUGGESTIONS_SETTING,
res.getString(R.string.prefs_suggestion_visibility_default_value));
@@ -171,7 +179,7 @@ public final class SettingsValues {
ResourceUtils.getFloatFromFraction(
res, R.fraction.config_key_preview_dismiss_end_scale));
mDisplayOrientation = res.getConfiguration().orientation;
- mAppWorkarounds = new AsyncResultHolder<AppWorkaroundsUtils>();
+ mAppWorkarounds = new AsyncResultHolder<>();
final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo(
mInputAttributes.mTargetApplicationPackageName);
if (null != packageInfo) {
@@ -186,12 +194,14 @@ public final class SettingsValues {
return mInputAttributes.mApplicationSpecifiedCompletionOn;
}
+ // TODO: Rename this to needsToLookupSuggestions().
public boolean isSuggestionsRequested() {
- return mInputAttributes.mIsSettingsSuggestionStripOn
- && (mCorrectionEnabled || isSuggestionStripVisible());
+ return mInputAttributes.mShouldShowSuggestions
+ && (mAutoCorrectionEnabled
+ || isCurrentOrientationAllowingSuggestionsPerUserSettings());
}
- public boolean isSuggestionStripVisible() {
+ public boolean isCurrentOrientationAllowingSuggestionsPerUserSettings() {
return (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_VALUE)
|| (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE
&& mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT);
@@ -315,18 +325,18 @@ public final class SettingsValues {
private static boolean needsToShowVoiceInputKey(final SharedPreferences prefs,
final Resources res) {
- if (!prefs.contains(Settings.PREF_VOICE_INPUT_KEY)) {
- // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to
- // {@link Settings#PREF_VOICE_INPUT_KEY}.
+ // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to
+ // {@link Settings#PREF_VOICE_INPUT_KEY}.
+ if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) {
final String voiceModeMain = res.getString(R.string.voice_mode_main);
final String voiceMode = prefs.getString(
Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain);
final boolean shouldShowVoiceInputKey = voiceModeMain.equals(voiceMode);
- prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey).apply();
- }
- // Remove the obsolete preference if exists.
- if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) {
- prefs.edit().remove(Settings.PREF_VOICE_MODE_OBSOLETE).apply();
+ prefs.edit()
+ .putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey)
+ // Remove the obsolete preference if exists.
+ .remove(Settings.PREF_VOICE_MODE_OBSOLETE)
+ .apply();
}
return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true);
}
@@ -387,8 +397,8 @@ public final class SettingsValues {
sb.append("" + mAutoCorrectEnabled);
sb.append("\n mAutoCorrectionThreshold = ");
sb.append("" + mAutoCorrectionThreshold);
- sb.append("\n mCorrectionEnabled = ");
- sb.append("" + mCorrectionEnabled);
+ sb.append("\n mAutoCorrectionEnabled = ");
+ sb.append("" + mAutoCorrectionEnabled);
sb.append("\n mSuggestionVisibility = ");
sb.append("" + mSuggestionVisibility);
sb.append("\n mDisplayOrientation = ");
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java
index 974dfddd3..73d25f6aa 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java
@@ -21,13 +21,13 @@ import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
+import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
-import com.android.inputmethod.compat.ViewCompatUtils;
import com.android.inputmethod.latin.R;
public final class SetupStartIndicatorView extends LinearLayout {
@@ -96,13 +96,13 @@ public final class SetupStartIndicatorView extends LinearLayout {
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
- final int layoutDirection = ViewCompatUtils.getLayoutDirection(this);
+ final int layoutDirection = ViewCompat.getLayoutDirection(this);
final int width = getWidth();
final int height = getHeight();
final float halfHeight = height / 2.0f;
final Path path = mIndicatorPath;
path.rewind();
- if (layoutDirection == ViewCompatUtils.LAYOUT_DIRECTION_RTL) {
+ if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
// Left arrow
path.moveTo(width, 0.0f);
path.lineTo(0.0f, halfHeight);
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
index c909507c6..6734e61b8 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
@@ -20,10 +20,10 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
+import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
-import com.android.inputmethod.compat.ViewCompatUtils;
import com.android.inputmethod.latin.R;
public final class SetupStepIndicatorView extends View {
@@ -38,12 +38,12 @@ public final class SetupStepIndicatorView extends View {
}
public void setIndicatorPosition(final int stepPos, final int totalStepNum) {
- final int layoutDirection = ViewCompatUtils.getLayoutDirection(this);
+ final int layoutDirection = ViewCompat.getLayoutDirection(this);
// The indicator position is the center of the partition that is equally divided into
// the total step number.
final float partionWidth = 1.0f / totalStepNum;
final float pos = stepPos * partionWidth + partionWidth / 2.0f;
- mXRatio = (layoutDirection == ViewCompatUtils.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos;
+ mXRatio = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos;
invalidate();
}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
index 5072fabd6..bcac05a6a 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
@@ -37,7 +37,6 @@ import com.android.inputmethod.compat.TextViewCompatUtils;
import com.android.inputmethod.compat.ViewCompatUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.settings.SettingsActivity;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import java.util.ArrayList;
@@ -482,7 +481,7 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
static final class SetupStepGroup {
private final SetupStepIndicatorView mIndicatorView;
- private final ArrayList<SetupStep> mGroup = CollectionUtils.newArrayList();
+ private final ArrayList<SetupStep> mGroup = new ArrayList<>();
public SetupStepGroup(final SetupStepIndicatorView indicatorView) {
mIndicatorView = indicatorView;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 65ebcf5f1..8d495646d 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -27,7 +27,6 @@ import android.view.inputmethod.InputMethodSubtype;
import android.view.textservice.SuggestionsInfo;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.latin.BinaryDictionary;
import com.android.inputmethod.latin.ContactsBinaryDictionary;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryCollection;
@@ -77,7 +76,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
private final Object mUseContactsLock = new Object();
private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
- CollectionUtils.newHashSet();
+ new HashSet<>();
public static final int SCRIPT_LATIN = 0;
public static final int SCRIPT_CYRILLIC = 1;
@@ -94,7 +93,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
// proximity to pass to the dictionary descent algorithm.
// IMPORTANT: this only contains languages - do not write countries in there.
// Only the language is searched from the map.
- mLanguageToScript = CollectionUtils.newTreeMap();
+ mLanguageToScript = new TreeMap<>();
mLanguageToScript.put("cs", SCRIPT_LATIN);
mLanguageToScript.put("da", SCRIPT_LATIN);
mLanguageToScript.put("de", SCRIPT_LATIN);
@@ -255,7 +254,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
mOriginalText = originalText;
mRecommendedThreshold = recommendedThreshold;
mMaxLength = maxLength;
- mSuggestions = CollectionUtils.newArrayList(maxLength + 1);
+ mSuggestions = new ArrayList<>(maxLength + 1);
mScores = new int[mMaxLength];
}
@@ -441,8 +440,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
}
}
dictionaryCollection.addDictionary(mContactsDictionary);
- mDictionaryCollectionsList.add(
- new WeakReference<DictionaryCollection>(dictionaryCollection));
+ mDictionaryCollectionsList.add(new WeakReference<>(dictionaryCollection));
}
return new DictAndKeyboard(dictionaryCollection, keyboardLayoutSet);
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index e951f5a89..55274cfe2 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin.spellcheck;
+import android.content.res.Resources;
import android.os.Binder;
import android.text.TextUtils;
import android.util.Log;
@@ -24,17 +25,20 @@ import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
import com.android.inputmethod.latin.PrevWordsInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
+import java.util.Locale;
public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName();
private static final boolean DBG = false;
private final static String[] EMPTY_STRING_ARRAY = new String[0];
+ private final Resources mResources;
+ private SentenceLevelAdapter mSentenceLevelAdapter;
public AndroidSpellCheckerSession(AndroidSpellCheckerService service) {
super(service);
+ mResources = service.getResources();
}
private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote(TextInfo ti,
@@ -44,10 +48,9 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
return null;
}
final int N = ssi.getSuggestionsCount();
- final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList();
- final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList();
- final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
- CollectionUtils.newArrayList();
+ final ArrayList<Integer> additionalOffsets = new ArrayList<>();
+ final ArrayList<Integer> additionalLengths = new ArrayList<>();
+ final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = new ArrayList<>();
String currentWord = null;
for (int i = 0; i < N; ++i) {
final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
@@ -117,8 +120,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
@Override
public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos,
int suggestionsLimit) {
- final SentenceSuggestionsInfo[] retval =
- super.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit);
+ final SentenceSuggestionsInfo[] retval = splitAndSuggest(textInfos, suggestionsLimit);
if (retval == null || retval.length != textInfos.length) {
return retval;
}
@@ -132,6 +134,58 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
return retval;
}
+ /**
+ * Get sentence suggestions for specified texts in an array of TextInfo. This is taken from
+ * SpellCheckerService#onGetSentenceSuggestionsMultiple that we can't use because it's
+ * using private variables.
+ * The default implementation splits the input text to words and returns
+ * {@link SentenceSuggestionsInfo} which contains suggestions for each word.
+ * This function will run on the incoming IPC thread.
+ * So, this is not called on the main thread,
+ * but will be called in series on another thread.
+ * @param textInfos an array of the text metadata
+ * @param suggestionsLimit the maximum number of suggestions to be returned
+ * @return an array of {@link SentenceSuggestionsInfo} returned by
+ * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)}
+ */
+ private SentenceSuggestionsInfo[] splitAndSuggest(TextInfo[] textInfos, int suggestionsLimit) {
+ if (textInfos == null || textInfos.length == 0) {
+ return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
+ }
+ SentenceLevelAdapter sentenceLevelAdapter;
+ synchronized(this) {
+ sentenceLevelAdapter = mSentenceLevelAdapter;
+ if (sentenceLevelAdapter == null) {
+ final String localeStr = getLocale();
+ if (!TextUtils.isEmpty(localeStr)) {
+ sentenceLevelAdapter = new SentenceLevelAdapter(mResources,
+ new Locale(localeStr));
+ mSentenceLevelAdapter = sentenceLevelAdapter;
+ }
+ }
+ }
+ if (sentenceLevelAdapter == null) {
+ return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
+ }
+ final int infosSize = textInfos.length;
+ final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[infosSize];
+ for (int i = 0; i < infosSize; ++i) {
+ final SentenceLevelAdapter.SentenceTextInfoParams textInfoParams =
+ sentenceLevelAdapter.getSplitWords(textInfos[i]);
+ final ArrayList<SentenceLevelAdapter.SentenceWordItem> mItems =
+ textInfoParams.mItems;
+ final int itemsSize = mItems.size();
+ final TextInfo[] splitTextInfos = new TextInfo[itemsSize];
+ for (int j = 0; j < itemsSize; ++j) {
+ splitTextInfos[j] = mItems.get(j).mTextInfo;
+ }
+ retval[i] = SentenceLevelAdapter.reconstructSuggestions(
+ textInfoParams, onGetSuggestionsMultiple(
+ splitTextInfos, suggestionsLimit, true));
+ }
+ return retval;
+ }
+
@Override
public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos,
int suggestionsLimit, boolean sequentialWords) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index cf26000d5..54eebe399 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -28,7 +28,6 @@ import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
-import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.PrevWordsInfo;
@@ -69,7 +68,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
private static final char CHAR_DELIMITER = '\uFFFC';
private static final int MAX_CACHE_SIZE = 50;
private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache =
- new LruCache<String, SuggestionsParams>(MAX_CACHE_SIZE);
+ new LruCache<>(MAX_CACHE_SIZE);
// TODO: Support n-gram input
private static String generateKey(final String query, final PrevWordsInfo prevWordsInfo) {
@@ -283,6 +282,22 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
false /* reportAsTypo */);
}
+ if (CHECKABILITY_CONTAINS_PERIOD == checkability) {
+ final String[] splitText = inText.split(Constants.REGEXP_PERIOD);
+ boolean allWordsAreValid = true;
+ for (final String word : splitText) {
+ if (!dictInfo.mDictionary.isValidWord(word)) {
+ allWordsAreValid = false;
+ break;
+ }
+ }
+ if (allWordsAreValid) {
+ return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
+ | SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS,
+ new String[] {
+ TextUtils.join(Constants.STRING_SPACE, splitText) });
+ }
+ }
return dictInfo.mDictionary.isValidWord(inText)
? AndroidSpellCheckerService.getInDictEmptySuggestions()
: AndroidSpellCheckerService.getNotInDictEmptySuggestions(
@@ -324,7 +339,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
} else {
coordinates = dictInfo.mKeyboard.getCoordinates(codePoints);
}
- composer.setComposingWord(codePoints, coordinates, null /* previousWord */);
+ composer.setComposingWord(codePoints, coordinates);
// TODO: make a spell checker option to block offensive words or not
final ArrayList<SuggestedWordInfo> suggestions =
dictInfo.mDictionary.getSuggestions(composer, prevWordsInfo,
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
index 1ffe50681..b33739fc1 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
@@ -16,11 +16,11 @@
package com.android.inputmethod.latin.spellcheck;
-import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.Dictionary;
/**
* A container for a Dictionary and a Keyboard.
@@ -28,19 +28,15 @@ import com.android.inputmethod.keyboard.ProximityInfo;
public final class DictAndKeyboard {
public final Dictionary mDictionary;
public final Keyboard mKeyboard;
- private final Keyboard mManualShiftedKeyboard;
public DictAndKeyboard(
final Dictionary dictionary, final KeyboardLayoutSet keyboardLayoutSet) {
mDictionary = dictionary;
if (keyboardLayoutSet == null) {
mKeyboard = null;
- mManualShiftedKeyboard = null;
return;
}
mKeyboard = keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
- mManualShiftedKeyboard =
- keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED);
}
public ProximityInfo getProximityInfo() {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index ba2e0c309..cc52a3e0f 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -23,7 +23,6 @@ import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.PrevWordsInfo;
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;
@@ -47,7 +46,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> {
private final Locale mLocale;
private int mSize;
private volatile boolean mClosed;
- final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
+ final static ArrayList<SuggestedWordInfo> noSuggestions = new ArrayList<>();
private final static DictAndKeyboard dummyDict = new DictAndKeyboard(
new Dictionary(Dictionary.TYPE_MAIN) {
// TODO: this dummy dictionary should be a singleton in the Dictionary class.
@@ -59,7 +58,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> {
return noSuggestions;
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
// This is never called. However if for some strange reason it ever gets
// called, returning true is less destructive (it will not underline the
// word in red).
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
new file mode 100644
index 000000000..13352f39e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.spellcheck;
+
+import android.content.res.Resources;
+import android.view.textservice.SentenceSuggestionsInfo;
+import android.view.textservice.SuggestionsInfo;
+import android.view.textservice.TextInfo;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.RunInLocale;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * This code is mostly lifted directly from android.service.textservice.SpellCheckerService in
+ * the framework; maybe that should be protected instead, so that implementers don't have to
+ * rewrite everything for any small change.
+ */
+public class SentenceLevelAdapter {
+ public static final SentenceSuggestionsInfo[] EMPTY_SENTENCE_SUGGESTIONS_INFOS =
+ new SentenceSuggestionsInfo[] {};
+ private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, null);
+ /**
+ * Container for split TextInfo parameters
+ */
+ public static class SentenceWordItem {
+ public final TextInfo mTextInfo;
+ public final int mStart;
+ public final int mLength;
+ public SentenceWordItem(TextInfo ti, int start, int end) {
+ mTextInfo = ti;
+ mStart = start;
+ mLength = end - start;
+ }
+ }
+
+ /**
+ * Container for originally queried TextInfo and parameters
+ */
+ public static class SentenceTextInfoParams {
+ final TextInfo mOriginalTextInfo;
+ final ArrayList<SentenceWordItem> mItems;
+ final int mSize;
+ public SentenceTextInfoParams(TextInfo ti, ArrayList<SentenceWordItem> items) {
+ mOriginalTextInfo = ti;
+ mItems = items;
+ mSize = items.size();
+ }
+ }
+
+ private static class WordIterator {
+ private final SpacingAndPunctuations mSpacingAndPunctuations;
+ public WordIterator(final Resources res, final Locale locale) {
+ final RunInLocale<SpacingAndPunctuations> job
+ = new RunInLocale<SpacingAndPunctuations>() {
+ @Override
+ protected SpacingAndPunctuations job(final Resources res) {
+ return new SpacingAndPunctuations(res);
+ }
+ };
+ mSpacingAndPunctuations = job.runInLocale(res, locale);
+ }
+
+ public int getEndOfWord(final CharSequence sequence, int index) {
+ final int length = sequence.length();
+ index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1);
+ while (index < length) {
+ final int codePoint = Character.codePointAt(sequence, index);
+ if (mSpacingAndPunctuations.isWordSeparator(codePoint)) {
+ // If it's a period, we want to stop here only if it's followed by another
+ // word separator. In all other cases we stop here.
+ if (Constants.CODE_PERIOD == codePoint) {
+ final int indexOfNextCodePoint =
+ index + Character.charCount(Constants.CODE_PERIOD);
+ if (indexOfNextCodePoint < length
+ && mSpacingAndPunctuations.isWordSeparator(
+ Character.codePointAt(sequence, indexOfNextCodePoint))) {
+ return index;
+ }
+ } else {
+ return index;
+ }
+ }
+ index += Character.charCount(codePoint);
+ }
+ return index;
+ }
+
+ public int getBeginningOfNextWord(final CharSequence sequence, int index) {
+ final int length = sequence.length();
+ if (index >= length) {
+ return -1;
+ }
+ index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1);
+ while (index < length) {
+ final int codePoint = Character.codePointAt(sequence, index);
+ if (!mSpacingAndPunctuations.isWordSeparator(codePoint)) {
+ return index;
+ }
+ index += Character.charCount(codePoint);
+ }
+ return -1;
+ }
+ }
+
+ private final WordIterator mWordIterator;
+ public SentenceLevelAdapter(final Resources res, final Locale locale) {
+ mWordIterator = new WordIterator(res, locale);
+ }
+
+ public SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) {
+ final WordIterator wordIterator = mWordIterator;
+ final CharSequence originalText = originalTextInfo.getText();
+ final int cookie = originalTextInfo.getCookie();
+ final int start = -1;
+ final int end = originalText.length();
+ final ArrayList<SentenceWordItem> wordItems = new ArrayList<SentenceWordItem>();
+ int wordStart = wordIterator.getBeginningOfNextWord(originalText, start);
+ int wordEnd = wordIterator.getEndOfWord(originalText, wordStart);
+ while (wordStart <= end && wordEnd != -1 && wordStart != -1) {
+ if (wordEnd >= start && wordEnd > wordStart) {
+ final String query = originalText.subSequence(wordStart, wordEnd).toString();
+ final TextInfo ti = new TextInfo(query, cookie, query.hashCode());
+ wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd));
+ }
+ wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd);
+ if (wordStart == -1) {
+ break;
+ }
+ wordEnd = wordIterator.getEndOfWord(originalText, wordStart);
+ }
+ return new SentenceTextInfoParams(originalTextInfo, wordItems);
+ }
+
+ public static SentenceSuggestionsInfo reconstructSuggestions(
+ SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) {
+ if (results == null || results.length == 0) {
+ return null;
+ }
+ if (originalTextInfoParams == null) {
+ return null;
+ }
+ final int originalCookie = originalTextInfoParams.mOriginalTextInfo.getCookie();
+ final int originalSequence =
+ originalTextInfoParams.mOriginalTextInfo.getSequence();
+
+ final int querySize = originalTextInfoParams.mSize;
+ final int[] offsets = new int[querySize];
+ final int[] lengths = new int[querySize];
+ final SuggestionsInfo[] reconstructedSuggestions = new SuggestionsInfo[querySize];
+ for (int i = 0; i < querySize; ++i) {
+ final SentenceWordItem item = originalTextInfoParams.mItems.get(i);
+ SuggestionsInfo result = null;
+ for (int j = 0; j < results.length; ++j) {
+ final SuggestionsInfo cur = results[j];
+ if (cur != null && cur.getSequence() == item.mTextInfo.getSequence()) {
+ result = cur;
+ result.setCookieAndSequence(originalCookie, originalSequence);
+ break;
+ }
+ }
+ offsets[i] = item.mStart;
+ lengths[i] = item.mLength;
+ reconstructedSuggestions[i] = result != null ? result : EMPTY_SUGGESTIONS_INFO;
+ }
+ return new SentenceSuggestionsInfo(reconstructedSuggestions, offsets, lengths);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java
index 75075664f..a6437bac3 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -47,9 +47,9 @@ public final class SynchronouslyLoadedContactsBinaryDictionary extends ContactsB
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
synchronized (mLock) {
- return super.isValidWord(word);
+ return super.isInDictionary(word);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java
index f2d981a9d..8c9d5d681 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java
@@ -52,9 +52,9 @@ public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDic
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
synchronized (mLock) {
- return super.isValidWord(word);
+ return super.isInDictionary(word);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index e90b15ca5..346aea34a 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -23,23 +23,17 @@ 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.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.utils.TypefaceUtils;
public final class MoreSuggestions extends Keyboard {
public final SuggestedWords mSuggestedWords;
- public static abstract class MoreSuggestionsListener extends KeyboardActionListener.Adapter {
- public abstract void onSuggestionSelected(final int index, final SuggestedWordInfo info);
- }
-
MoreSuggestions(final MoreSuggestionsParam params, final SuggestedWords suggestedWords) {
super(params);
mSuggestedWords = suggestedWords;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 7fd64c4bf..528d500d2 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -20,13 +20,16 @@ import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
+import com.android.inputmethod.accessibility.AccessibilityUtils;
+import com.android.inputmethod.accessibility.MoreSuggestionsAccessibilityDelegate;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.MoreKeysKeyboardView;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionKey;
-import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
/**
* A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting
@@ -35,6 +38,10 @@ import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestions
public final class MoreSuggestionsView extends MoreKeysKeyboardView {
private static final String TAG = MoreSuggestionsView.class.getSimpleName();
+ public static abstract class MoreSuggestionsListener extends KeyboardActionListener.Adapter {
+ public abstract void onSuggestionSelected(final int index, final SuggestedWordInfo info);
+ }
+
public MoreSuggestionsView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.moreKeysKeyboardViewStyle);
}
@@ -45,6 +52,26 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
}
@Override
+ public void setKeyboard(final Keyboard keyboard) {
+ super.setKeyboard(keyboard);
+ // With accessibility mode off, {@link #mAccessibilityDelegate} is set to null at the
+ // above {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call.
+ // With accessibility mode on, {@link #mAccessibilityDelegate} is set to a
+ // {@link MoreKeysKeyboardAccessibilityDelegate} object at the above
+ // {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call. And the object has to be
+ // overwritten by a {@link MoreSuggestionsAccessibilityDelegate} object here.
+ if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
+ if (!(mAccessibilityDelegate instanceof MoreSuggestionsAccessibilityDelegate)) {
+ mAccessibilityDelegate = new MoreSuggestionsAccessibilityDelegate(
+ this, mKeyDetector);
+ mAccessibilityDelegate.setOpenAnnounce(R.string.spoken_open_more_suggestions);
+ mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_suggestions);
+ }
+ mAccessibilityDelegate.setKeyboard(keyboard);
+ }
+ }
+
+ @Override
protected int getDefaultCoordX() {
final MoreSuggestions pane = (MoreSuggestions)getKeyboard();
return pane.mOccupiedWidth / 2;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 810bda758..19b48f081 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -379,10 +379,9 @@ final class SuggestionStripLayoutHelper {
} else {
wordView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
}
-
- // Disable this suggestion if the suggestion is null or empty.
- // TODO: Fix disabled {@link TextView}'s content description.
- wordView.setEnabled(!TextUtils.isEmpty(word));
+ // {@link StyleSpan} in a content description may cause an issue of TTS/TalkBack.
+ // Use a simple {@link String} to avoid the issue.
+ wordView.setContentDescription(TextUtils.isEmpty(word) ? null : word.toString());
final CharSequence text = getEllipsizedText(word, width, wordView.getPaint());
final float scaleX = getTextScaleX(word, width, wordView.getPaint());
wordView.setText(text); // TextView.setText() resets text scale x to 1.0.
@@ -461,14 +460,15 @@ final class SuggestionStripLayoutHelper {
}
final TextView wordView = mWordViews.get(positionInStrip);
- wordView.setEnabled(true);
- wordView.setTextColor(mColorAutoCorrect);
+ final String punctuation = punctuationSuggestions.getLabel(positionInStrip);
// {@link TextView#getTag()} is used to get the index in suggestedWords at
// {@link SuggestionStripView#onClick(View)}.
wordView.setTag(positionInStrip);
- wordView.setText(punctuationSuggestions.getLabel(positionInStrip));
+ wordView.setText(punctuation);
+ wordView.setContentDescription(punctuation);
wordView.setTextScaleX(1.0f);
wordView.setCompoundDrawables(null, null, null, null);
+ wordView.setTextColor(mColorAutoCorrect);
stripView.addView(wordView);
setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight);
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 619804afa..aebfb7d4c 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -33,6 +33,7 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -42,17 +43,14 @@ import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.keyboard.MoreKeysPanel;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.Settings;
-import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
-import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.settings.SettingsValues;
+import com.android.inputmethod.latin.suggestions.MoreSuggestionsView.MoreSuggestionsListener;
import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
@@ -78,9 +76,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private final MoreSuggestionsView mMoreSuggestionsView;
private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
- private final ArrayList<TextView> mWordViews = CollectionUtils.newArrayList();
- private final ArrayList<TextView> mDebugInfoViews = CollectionUtils.newArrayList();
- private final ArrayList<View> mDividerViews = CollectionUtils.newArrayList();
+ private final ArrayList<TextView> mWordViews = new ArrayList<>();
+ private final ArrayList<TextView> mDebugInfoViews = new ArrayList<>();
+ private final ArrayList<View> mDividerViews = new ArrayList<>();
Listener mListener;
private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
@@ -90,43 +88,44 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private final StripVisibilityGroup mStripVisibilityGroup;
private static class StripVisibilityGroup {
+ private final View mSuggestionStripView;
private final View mSuggestionsStrip;
- private final View mVoiceKey;
private final View mAddToDictionaryStrip;
private final View mImportantNoticeStrip;
- public StripVisibilityGroup(final View suggestionsStrip, final View voiceKey,
- final View addToDictionaryStrip, final View importantNoticeStrip) {
+ public StripVisibilityGroup(final View suggestionStripView,
+ final ViewGroup suggestionsStrip, final ViewGroup addToDictionaryStrip,
+ final View importantNoticeStrip) {
+ mSuggestionStripView = suggestionStripView;
mSuggestionsStrip = suggestionsStrip;
- mVoiceKey = voiceKey;
mAddToDictionaryStrip = addToDictionaryStrip;
mImportantNoticeStrip = importantNoticeStrip;
- showSuggestionsStrip(false /* voiceKeyEnabled */);
+ showSuggestionsStrip();
}
- public void setLayoutDirection(final int layoutDirection) {
+ public void setLayoutDirection(final boolean isRtlLanguage) {
+ final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL
+ : ViewCompat.LAYOUT_DIRECTION_LTR;
+ ViewCompat.setLayoutDirection(mSuggestionStripView, layoutDirection);
ViewCompat.setLayoutDirection(mSuggestionsStrip, layoutDirection);
ViewCompat.setLayoutDirection(mAddToDictionaryStrip, layoutDirection);
ViewCompat.setLayoutDirection(mImportantNoticeStrip, layoutDirection);
}
- public void showSuggestionsStrip(final boolean enableVoiceKey) {
+ public void showSuggestionsStrip() {
mSuggestionsStrip.setVisibility(VISIBLE);
- mVoiceKey.setVisibility(enableVoiceKey ? VISIBLE : INVISIBLE);
mAddToDictionaryStrip.setVisibility(INVISIBLE);
mImportantNoticeStrip.setVisibility(INVISIBLE);
}
public void showAddToDictionaryStrip() {
mSuggestionsStrip.setVisibility(INVISIBLE);
- mVoiceKey.setVisibility(INVISIBLE);
mAddToDictionaryStrip.setVisibility(VISIBLE);
mImportantNoticeStrip.setVisibility(INVISIBLE);
}
- public void showImportantNoticeStrip(final boolean enableVoiceKey) {
+ public void showImportantNoticeStrip() {
mSuggestionsStrip.setVisibility(INVISIBLE);
- mVoiceKey.setVisibility(enableVoiceKey ? VISIBLE : INVISIBLE);
mAddToDictionaryStrip.setVisibility(INVISIBLE);
mImportantNoticeStrip.setVisibility(VISIBLE);
}
@@ -156,7 +155,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mVoiceKey = (ImageButton)findViewById(R.id.suggestions_strip_voice_key);
mAddToDictionaryStrip = (ViewGroup)findViewById(R.id.add_to_dictionary_strip);
mImportantNoticeStrip = findViewById(R.id.important_notice_strip);
- mStripVisibilityGroup = new StripVisibilityGroup(mSuggestionsStrip, mVoiceKey,
+ mStripVisibilityGroup = new StripVisibilityGroup(this, mSuggestionsStrip,
mAddToDictionaryStrip, mImportantNoticeStrip);
for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) {
@@ -165,7 +164,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
word.setOnLongClickListener(this);
mWordViews.add(word);
final View divider = inflater.inflate(R.layout.suggestion_divider, null);
- divider.setOnClickListener(this);
mDividerViews.add(divider);
final TextView info = new TextView(context, null, R.attr.suggestionWordStyle);
info.setTextColor(Color.WHITE);
@@ -204,30 +202,20 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view);
}
- private boolean isVoiceKeyEnabled() {
- if (mMainKeyboardView == null) {
- return false;
- }
- final Keyboard keyboard = mMainKeyboardView.getKeyboard();
- if (keyboard == null) {
- return false;
- }
- return keyboard.mId.mHasShortcutKey;
+ public void updateVisibility(final boolean shouldBeVisible, final boolean isFullscreenMode) {
+ final int visibility = shouldBeVisible ? VISIBLE : (isFullscreenMode ? GONE : INVISIBLE);
+ setVisibility(visibility);
+ final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent();
+ mVoiceKey.setVisibility(currentSettingsValues.mShowsVoiceInputKey ? VISIBLE : INVISIBLE);
}
public void setSuggestions(final SuggestedWords suggestedWords, final boolean isRtlLanguage) {
clear();
- final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL
- : ViewCompat.LAYOUT_DIRECTION_LTR;
- setLayoutDirection(layoutDirection);
- mStripVisibilityGroup.setLayoutDirection(layoutDirection);
+ mStripVisibilityGroup.setLayoutDirection(isRtlLanguage);
mSuggestedWords = suggestedWords;
mSuggestionsCountInStrip = mLayoutHelper.layoutAndReturnSuggestionCountInStrip(
mSuggestedWords, mSuggestionsStrip, this);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords);
- }
- mStripVisibilityGroup.showSuggestionsStrip(isVoiceKeyEnabled());
+ mStripVisibilityGroup.showSuggestionsStrip();
}
public int setMoreSuggestionsHeight(final int remainingHeight) {
@@ -258,8 +246,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
// This method checks if we should show the important notice (checks on permanent storage if
// it has been shown once already or not, and if in the setup wizard). If applicable, it shows
// the notice. In all cases, it returns true if it was shown, false otherwise.
- public boolean maybeShowImportantNoticeTitle(final InputAttributes inputAttributes) {
- if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext(), inputAttributes)) {
+ public boolean maybeShowImportantNoticeTitle() {
+ if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext())) {
return false;
}
if (getWidth() <= 0) {
@@ -274,7 +262,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
dismissMoreSuggestionsPanel();
}
mLayoutHelper.layoutImportantNotice(mImportantNoticeStrip, importantNoticeTitle);
- mStripVisibilityGroup.showImportantNoticeStrip(isVoiceKeyEnabled());
+ mStripVisibilityGroup.showImportantNoticeStrip();
mImportantNoticeStrip.setOnClickListener(this);
return true;
}
@@ -282,7 +270,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
public void clear() {
mSuggestionsStrip.removeAllViews();
removeAllDebugInfoViews();
- mStripVisibilityGroup.showSuggestionsStrip(false /* enableVoiceKey */);
+ mStripVisibilityGroup.showSuggestionsStrip();
dismissMoreSuggestionsPanel();
}
@@ -428,6 +416,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
@Override
+ public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) {
+ // Don't populate accessibility event with suggested words and voice key.
+ return true;
+ }
+
+ @Override
public boolean onTouchEvent(final MotionEvent me) {
// In the sliding input mode. {@link MotionEvent} should be forwarded to
// {@link MoreSuggestionsView}.
@@ -441,6 +435,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
public void onClick(final View view) {
+ AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
+ Constants.CODE_UNSPECIFIED, this);
if (view == mImportantNoticeStrip) {
mListener.showImportantNoticeContents();
return;
@@ -484,7 +480,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
// Called by the framework when the size is known. Show the important notice if applicable.
// This may be overriden by showing suggestions later, if applicable.
if (oldw <= 0 && w > 0) {
- maybeShowImportantNoticeTitle(Settings.getInstance().getCurrent().mInputAttributes);
+ maybeShowImportantNoticeTitle();
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index 21426d1eb..eda81940f 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -167,7 +167,9 @@ public class UserDictionaryAddWordContents {
// should not insert, because either A. the word exists with no shortcut, in which
// case the exact same thing we want to insert is already there, or B. the word
// exists with at least one shortcut, in which case it has priority on our word.
- if (hasWord(newWord, context)) return CODE_ALREADY_PRESENT;
+ if (TextUtils.isEmpty(newShortcut) && hasWord(newWord, context)) {
+ return CODE_ALREADY_PRESENT;
+ }
// Disallow duplicates. If the same word with no shortcut is defined, remove it; if
// the same word with the same shortcut is defined, remove it; but we don't mind if
@@ -256,7 +258,7 @@ public class UserDictionaryAddWordContents {
// The system locale should be inside. We want it at the 2nd spot.
locales.remove(systemLocale); // system locale may not be null
locales.remove(""); // Remove the empty string if it's there
- final ArrayList<LocaleRenderer> localesList = new ArrayList<LocaleRenderer>();
+ final ArrayList<LocaleRenderer> localesList = new ArrayList<>();
// Add the passed locale, then the system locale at the top of the list. Add an
// "all languages" entry at the bottom of the list.
addLocaleDisplayNameToList(activity, localesList, mLocale);
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
index 4fc132f68..163443036 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
@@ -134,8 +134,8 @@ public class UserDictionaryAddWordFragment extends Fragment
final Spinner localeSpinner =
(Spinner)mRootView.findViewById(R.id.user_dictionary_add_locale);
- final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<LocaleRenderer>(getActivity(),
- android.R.layout.simple_spinner_item, localesList);
+ final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<>(
+ getActivity(), android.R.layout.simple_spinner_item, localesList);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
localeSpinner.setAdapter(adapter);
localeSpinner.setOnItemSelectedListener(this);
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
index 97a924d7b..624783a70 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -56,7 +56,7 @@ public class UserDictionaryList extends PreferenceFragment {
final Cursor cursor = activity.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
new String[] { UserDictionary.Words.LOCALE },
null, null, null);
- final TreeSet<String> localeSet = new TreeSet<String>();
+ final TreeSet<String> localeSet = new TreeSet<>();
if (null == cursor) {
// The user dictionary service is not present or disabled. Return null.
return null;
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index 2bb30a2ba..3ca7c7e1c 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -95,8 +95,7 @@ public final class AdditionalSubtypeUtils {
return EMPTY_SUBTYPE_ARRAY;
}
final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
- final ArrayList<InputMethodSubtype> subtypesList =
- CollectionUtils.newArrayList(prefSubtypeArray.length);
+ final ArrayList<InputMethodSubtype> subtypesList = new ArrayList<>(prefSubtypeArray.length);
for (final String prefSubtype : prefSubtypeArray) {
final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE
diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
index 22b9b77d2..34ee2152a 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -16,12 +16,11 @@
package com.android.inputmethod.latin.utils;
-import com.android.inputmethod.latin.BinaryDictionary;
+import android.util.Log;
+
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import android.util.Log;
-
public final class AutoCorrectionUtils {
private static final boolean DBG = LatinImeLogger.sDBG;
private static final String TAG = AutoCorrectionUtils.class.getSimpleName();
@@ -36,7 +35,9 @@ public final class AutoCorrectionUtils {
final float autoCorrectionThreshold) {
if (null != suggestion) {
// Shortlist a whitelisted word
- if (suggestion.mKind == SuggestedWordInfo.KIND_WHITELIST) return true;
+ if (suggestion.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
+ return true;
+ }
final int autoCorrectionSuggestionScore = suggestion.mScore;
// TODO: when the normalized score of the first suggestion is nearly equals to
// the normalized score of the second suggestion, behave less aggressive.
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 702688f93..936219332 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -62,6 +62,22 @@ public final class CapsModeUtils {
}
/**
+ * Helper method to find out if a code point is starting punctuation.
+ *
+ * This include the Unicode START_PUNCTUATION category, but also some other symbols that are
+ * starting, like the inverted question mark or the double quote.
+ *
+ * @param codePoint the code point
+ * @return true if it's starting punctuation, false otherwise.
+ */
+ private static boolean isStartPunctuation(final int codePoint) {
+ return (codePoint == Constants.CODE_DOUBLE_QUOTE || codePoint == Constants.CODE_SINGLE_QUOTE
+ || codePoint == Constants.CODE_INVERTED_QUESTION_MARK
+ || codePoint == Constants.CODE_INVERTED_EXCLAMATION_MARK
+ || Character.getType(codePoint) == Character.START_PUNCTUATION);
+ }
+
+ /**
* Determine what caps mode should be in effect at the current offset in
* the text. Only the mode bits set in <var>reqModes</var> will be
* checked. Note that the caps mode flags here are explicitly defined
@@ -115,8 +131,7 @@ public final class CapsModeUtils {
} else {
for (i = cs.length(); i > 0; i--) {
final char c = cs.charAt(i - 1);
- if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE
- && Character.getType(c) != Character.START_PUNCTUATION) {
+ if (!isStartPunctuation(c)) {
break;
}
}
@@ -210,11 +225,14 @@ public final class CapsModeUtils {
// We found out that we have a period. We need to determine if this is a full stop or
// otherwise sentence-ending period, or an abbreviation like "e.g.". An abbreviation
- // looks like (\w\.){2,}
+ // looks like (\w\.){2,}. Moreover, in German, you put periods after digits for dates
+ // and some other things, and in German specifically we need to not go into autocaps after
+ // a whitespace-digits-period sequence.
// To find out, we will have a simple state machine with the following states :
- // START, WORD, PERIOD, ABBREVIATION
+ // START, WORD, PERIOD, ABBREVIATION, NUMBER
// On START : (just before the first period)
// letter => WORD
+ // digit => NUMBER if German; end with caps otherwise
// whitespace => end with no caps (it was a stand-alone period)
// otherwise => end with caps (several periods/symbols in a row)
// On WORD : (within the word just before the first period)
@@ -228,6 +246,11 @@ public final class CapsModeUtils {
// letter => LETTER
// period => PERIOD
// otherwise => end with no caps (it was an abbreviation)
+ // On NUMBER : (period immediately preceded by one or more digits)
+ // digit => NUMBER
+ // letter => LETTER (promote to word)
+ // otherwise => end with no caps (it was a whitespace-digits-period sequence,
+ // or a punctuation-digits-period sequence like "11.11.")
// "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This
// should capitalize.
@@ -235,6 +258,7 @@ public final class CapsModeUtils {
final int WORD = 1;
final int PERIOD = 2;
final int LETTER = 3;
+ final int NUMBER = 4;
final int caps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
| TextUtils.CAP_MODE_SENTENCES) & reqModes;
final int noCaps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
@@ -247,6 +271,8 @@ public final class CapsModeUtils {
state = WORD;
} else if (Character.isWhitespace(c)) {
return noCaps;
+ } else if (Character.isDigit(c) && spacingAndPunctuations.mUsesGermanRules) {
+ state = NUMBER;
} else {
return caps;
}
@@ -275,6 +301,15 @@ public final class CapsModeUtils {
} else {
return noCaps;
}
+ break;
+ case NUMBER:
+ if (Character.isLetter(c)) {
+ state = WORD;
+ } else if (Character.isDigit(c)) {
+ state = NUMBER;
+ } else {
+ return noCaps;
+ }
}
}
// Here we arrived at the start of the line. This should behave exactly like whitespace.
diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
index bbfa0f091..e3aef29ba 100644
--- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
@@ -16,93 +16,21 @@
package com.android.inputmethod.latin.utils;
-import android.util.SparseArray;
-
-import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.WeakHashMap;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
public final class CollectionUtils {
private CollectionUtils() {
// This utility class is not publicly instantiable.
}
- public static <K,V> HashMap<K,V> newHashMap() {
- return new HashMap<K,V>();
- }
-
- public static <K, V> WeakHashMap<K, V> newWeakHashMap() {
- return new WeakHashMap<K, V>();
- }
-
- public static <K,V> TreeMap<K,V> newTreeMap() {
- return new TreeMap<K,V>();
- }
-
public static <K, V> Map<K,V> newSynchronizedTreeMap() {
- final TreeMap<K,V> treeMap = newTreeMap();
+ final TreeMap<K,V> treeMap = new TreeMap<>();
return Collections.synchronizedMap(treeMap);
}
- public static <K,V> ConcurrentHashMap<K,V> newConcurrentHashMap() {
- return new ConcurrentHashMap<K,V>();
- }
-
- public static <E> HashSet<E> newHashSet() {
- return new HashSet<E>();
- }
-
- public static <E> TreeSet<E> newTreeSet() {
- return new TreeSet<E>();
- }
-
- public static <E> ArrayList<E> newArrayList() {
- return new ArrayList<E>();
- }
-
- public static <E> ArrayList<E> newArrayList(final int initialCapacity) {
- return new ArrayList<E>(initialCapacity);
- }
-
- public static <E> ArrayList<E> newArrayList(final Collection<E> collection) {
- return new ArrayList<E>(collection);
- }
-
- public static <E> LinkedList<E> newLinkedList() {
- return new LinkedList<E>();
- }
-
- public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList() {
- return new CopyOnWriteArrayList<E>();
- }
-
- public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(
- final Collection<E> collection) {
- return new CopyOnWriteArrayList<E>(collection);
- }
-
- public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(final E[] array) {
- return new CopyOnWriteArrayList<E>(array);
- }
-
- public static <E> ArrayDeque<E> newArrayDeque() {
- return new ArrayDeque<E>();
- }
-
- public static <E> SparseArray<E> newSparseArray() {
- return new SparseArray<E>();
- }
-
public static <E> ArrayList<E> arrayAsList(final E[] array, final int start, final int end) {
if (array == null) {
throw new NullPointerException();
@@ -111,7 +39,7 @@ public final class CollectionUtils {
throw new IllegalArgumentException();
}
- final ArrayList<E> list = newArrayList(end - start);
+ final ArrayList<E> list = new ArrayList<>(end - start);
for (int i = start; i < end; i++) {
list.add(array[i]);
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
index b18a1d83b..a21a1373b 100644
--- a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
@@ -209,7 +209,7 @@ public final class CsvUtils {
@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 ArrayList<String> fields = new ArrayList<>();
final int length = line.length();
int start = 0;
do {
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 315913e2f..d76ea10c0 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -336,7 +336,7 @@ public class DictionaryInfoUtils {
public static ArrayList<DictionaryInfo> getCurrentDictionaryFileNameAndVersionInfo(
final Context context) {
- final ArrayList<DictionaryInfo> dictList = CollectionUtils.newArrayList();
+ final ArrayList<DictionaryInfo> dictList = new ArrayList<>();
// Retrieve downloaded dictionaries
final File[] directoryList = getCachedDirectoryList(context);
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index f1057da0b..787e4a59d 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -16,129 +16,14 @@
package com.android.inputmethod.latin.utils;
-import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import android.content.Context;
-import android.content.res.Resources;
-import android.text.InputType;
-import android.util.Log;
-import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.PrevWordsInfo;
-import com.android.inputmethod.latin.Suggest;
-import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.WordComposer;
-
-/**
- * This class is used to prevent distracters being added to personalization
- * or user history dictionaries
- */
-public class DistracterFilter {
- private static final String TAG = DistracterFilter.class.getSimpleName();
-
- private static final long TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS = 120;
-
- private final Context mContext;
- private final Map<Locale, InputMethodSubtype> mLocaleToSubtypeMap;
- private final Map<Locale, Keyboard> mLocaleToKeyboardMap;
- private final Suggest mSuggest;
- private Keyboard mKeyboard;
-
- // If the score of the top suggestion exceeds this value, the tested word (e.g.,
- // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distracter to
- // words in dictionary. The greater the threshold is, the less likely the tested word would
- // become a distracter, which means the tested word will be more likely to be added to
- // the dictionary.
- private static final float DISTRACTER_WORD_SCORE_THRESHOLD = 2.0f;
-
- // Create empty distracter filter.
- public DistracterFilter() {
- this(null, new ArrayList<InputMethodSubtype>());
- }
-
- /**
- * Create a DistracterFilter instance.
- *
- * @param context the context.
- * @param enabledSubtypes the enabled subtypes.
- */
- public DistracterFilter(final Context context, final List<InputMethodSubtype> enabledSubtypes) {
- mContext = context;
- mLocaleToSubtypeMap = new HashMap<>();
- if (enabledSubtypes != null) {
- for (final InputMethodSubtype subtype : enabledSubtypes) {
- final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
- if (mLocaleToSubtypeMap.containsKey(locale)) {
- // Multiple subtypes are enabled for one locale.
- // TODO: Investigate what we should do for this case.
- continue;
- }
- mLocaleToSubtypeMap.put(locale, subtype);
- }
- }
- mLocaleToKeyboardMap = new HashMap<>();
- mSuggest = new Suggest();
- mKeyboard = null;
- }
-
- private static boolean suggestionExceedsDistracterThreshold(
- final SuggestedWordInfo suggestion, final String consideredWord,
- final float distracterThreshold) {
- if (null != suggestion) {
- final int suggestionScore = suggestion.mScore;
- final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
- consideredWord, suggestion.mWord, suggestionScore);
- if (normalizedScore > distracterThreshold) {
- return true;
- }
- }
- return false;
- }
-
- private void loadKeyboardForLocale(final Locale newLocale) {
- final Keyboard cachedKeyboard = mLocaleToKeyboardMap.get(newLocale);
- if (cachedKeyboard != null) {
- mKeyboard = cachedKeyboard;
- return;
- }
- final InputMethodSubtype subtype = mLocaleToSubtypeMap.get(newLocale);
- if (subtype == null) {
- return;
- }
- final EditorInfo editorInfo = new EditorInfo();
- editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
- final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
- mContext, editorInfo);
- final Resources res = mContext.getResources();
- final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
- final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
- builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
- builder.setSubtype(subtype);
- builder.setIsSpellChecker(false /* isSpellChecker */);
- final KeyboardLayoutSet layoutSet = builder.build();
- mKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
- }
-
- private void loadDictionariesForLocale(final Locale newlocale) throws InterruptedException {
- mSuggest.mDictionaryFacilitator.resetDictionaries(mContext, newlocale,
- false /* useContactsDict */, false /* usePersonalizedDicts */,
- false /* forceReloadMainDictionary */, null /* listener */);
- mSuggest.mDictionaryFacilitator.waitForLoadingMainDictionary(
- TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS, TimeUnit.SECONDS);
- }
+public interface DistracterFilter {
/**
* Determine whether a word is a distracter to words in dictionaries.
*
@@ -149,56 +34,25 @@ public class DistracterFilter {
* @return true if testedWord is a distracter, otherwise false.
*/
public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
- final String testedWord, final Locale locale) {
- if (locale == null) {
- return false;
- }
- if (!locale.equals(mSuggest.mDictionaryFacilitator.getLocale())) {
- if (!mLocaleToSubtypeMap.containsKey(locale)) {
- Log.e(TAG, "Locale " + locale + " is not enabled.");
- // TODO: Investigate what we should do for disabled locales.
- return false;
- }
- loadKeyboardForLocale(locale);
- // Reset dictionaries for the locale.
- try {
- loadDictionariesForLocale(locale);
- } catch (final InterruptedException e) {
- Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter", e);
- return false;
- }
- }
- if (mKeyboard == null) {
+ final String testedWord, final Locale locale);
+
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes);
+
+ public void close();
+
+ public static final DistracterFilter EMPTY_DISTRACTER_FILTER = new DistracterFilter() {
+ @Override
+ public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
+ String testedWord, Locale locale) {
return false;
}
- final WordComposer composer = new WordComposer();
- final int[] codePoints = StringUtils.toCodePointArray(testedWord);
- final int[] coordinates = mKeyboard.getCoordinates(codePoints);
- composer.setComposingWord(codePoints, coordinates, prevWordsInfo);
- final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(testedWord);
- final String consideredWord = trailingSingleQuotesCount > 0 ?
- testedWord.substring(0, testedWord.length() - trailingSingleQuotesCount) :
- testedWord;
- final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
- final OnGetSuggestedWordsCallback callback = new OnGetSuggestedWordsCallback() {
- @Override
- public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
- if (suggestedWords != null && suggestedWords.size() > 1) {
- // The suggestedWordInfo at 0 is the typed word. The 1st suggestion from
- // the decoder is at index 1.
- final SuggestedWordInfo firstSuggestion = suggestedWords.getInfo(1);
- final boolean hasStrongDistractor = suggestionExceedsDistracterThreshold(
- firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
- holder.set(hasStrongDistractor);
- }
- }
- };
- mSuggest.getSuggestedWords(composer, prevWordsInfo, mKeyboard.getProximityInfo(),
- true /* blockOffensiveWords */, true /* isCorrectionEnbaled */,
- null /* additionalFeaturesOptions */, 0 /* sessionId */,
- SuggestedWords.NOT_A_SEQUENCE_NUMBER, callback);
+ @Override
+ public void close() {
+ }
- return holder.get(false /* defaultValue */, Constants.GET_SUGGESTED_WORDS_TIMEOUT);
- }
+ @Override
+ public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
+ }
+ };
}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java
new file mode 100644
index 000000000..0ee6236b1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.LruCache;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.DictionaryFacilitator;
+import com.android.inputmethod.latin.PrevWordsInfo;
+
+/**
+ * This class is used to prevent distracters being added to personalization
+ * or user history dictionaries
+ */
+public class DistracterFilterCheckingExactMatches implements DistracterFilter {
+ private static final String TAG = DistracterFilterCheckingExactMatches.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final long TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS = 120;
+ private static final int MAX_DISTRACTERS_CACHE_SIZE = 512;
+
+ private final Context mContext;
+ private final DictionaryFacilitator mDictionaryFacilitator;
+ private final LruCache<String, Boolean> mDistractersCache;
+ private final Object mLock = new Object();
+
+ /**
+ * Create a DistracterFilter instance.
+ *
+ * @param context the context.
+ */
+ public DistracterFilterCheckingExactMatches(final Context context) {
+ mContext = context;
+ mDictionaryFacilitator = new DictionaryFacilitator();
+ mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
+ }
+
+ @Override
+ public void close() {
+ mDictionaryFacilitator.closeDictionaries();
+ }
+
+ @Override
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
+ }
+
+ private void loadDictionariesForLocale(final Locale newlocale) throws InterruptedException {
+ mDictionaryFacilitator.resetDictionaries(mContext, newlocale,
+ false /* useContactsDict */, false /* usePersonalizedDicts */,
+ false /* forceReloadMainDictionary */, null /* listener */);
+ mDictionaryFacilitator.waitForLoadingMainDictionary(
+ TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Determine whether a word is a distracter to words in dictionaries.
+ *
+ * @param prevWordsInfo the information of previous words. Not used for now.
+ * @param testedWord the word that will be tested to see whether it is a distracter to words
+ * in dictionaries.
+ * @param locale the locale of word.
+ * @return true if testedWord is a distracter, otherwise false.
+ */
+ @Override
+ public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
+ final String testedWord, final Locale locale) {
+ if (locale == null) {
+ return false;
+ }
+ if (!locale.equals(mDictionaryFacilitator.getLocale())) {
+ synchronized (mLock) {
+ // Reset dictionaries for the locale.
+ try {
+ mDistractersCache.evictAll();
+ loadDictionariesForLocale(locale);
+ } catch (final InterruptedException e) {
+ Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter",
+ e);
+ return false;
+ }
+ }
+ }
+
+ final Boolean isCachedDistracter = mDistractersCache.get(testedWord);
+ if (isCachedDistracter != null && isCachedDistracter) {
+ if (DEBUG) {
+ Log.d(TAG, "testedWord: " + testedWord);
+ Log.d(TAG, "isDistracter: true (cache hit)");
+ }
+ return true;
+ }
+ // The tested word is a distracter when there is a word that is exact matched to the tested
+ // word and its probability is higher than the tested word's probability.
+ final int perfectMatchFreq = mDictionaryFacilitator.getFrequency(testedWord);
+ final int exactMatchFreq = mDictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord);
+ final boolean isDistracter = perfectMatchFreq < exactMatchFreq;
+ if (DEBUG) {
+ Log.d(TAG, "testedWord: " + testedWord);
+ Log.d(TAG, "perfectMatchFreq: " + perfectMatchFreq);
+ Log.d(TAG, "exactMatchFreq: " + exactMatchFreq);
+ Log.d(TAG, "isDistracter: " + isDistracter);
+ }
+ if (isDistracter) {
+ // Add the word to the cache.
+ mDistractersCache.put(testedWord, Boolean.TRUE);
+ }
+ return isDistracter;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
new file mode 100644
index 000000000..4ad4ba784
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.util.List;
+import java.util.Locale;
+
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
+
+public class DistracterFilterCheckingIsInDictionary implements DistracterFilter {
+ private final DistracterFilter mDistracterFilter;
+ private final Dictionary mDictionary;
+
+ public DistracterFilterCheckingIsInDictionary(final DistracterFilter distracterFilter,
+ final Dictionary dictionary) {
+ mDistracterFilter = distracterFilter;
+ mDictionary = dictionary;
+ }
+
+ @Override
+ public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
+ String testedWord, Locale locale) {
+ if (mDictionary.isInDictionary(testedWord)) {
+ // This filter treats entries that are already in the dictionary as non-distracters
+ // because they have passed the filtering in the past.
+ return false;
+ } else {
+ return mDistracterFilter.isDistracterToWordsInDictionaries(
+ prevWordsInfo, testedWord, locale);
+ }
+ }
+
+ @Override
+ public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
+ // Do nothing.
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
index ed502ed3d..61da1b789 100644
--- a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
@@ -19,22 +19,42 @@ package com.android.inputmethod.latin.utils;
import com.android.inputmethod.annotations.UsedForTesting;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
/**
* Utilities to manage executors.
*/
public class ExecutorUtils {
- private static final ConcurrentHashMap<String, PrioritizedSerialExecutor>
- sExecutorMap = CollectionUtils.newConcurrentHashMap();
+ private static final ConcurrentHashMap<String, ExecutorService> sExecutorMap =
+ new ConcurrentHashMap<>();
+
+ private static class ThreadFactoryWithId implements ThreadFactory {
+ private final String mId;
+
+ public ThreadFactoryWithId(final String id) {
+ mId = id;
+ }
+
+ @Override
+ public Thread newThread(final Runnable r) {
+ return new Thread(r, "Executor - " + mId);
+ }
+ }
+
/**
- * Gets the executor for the given dictionary name.
+ * Gets the executor for the given id.
*/
- public static PrioritizedSerialExecutor getExecutor(final String dictName) {
- PrioritizedSerialExecutor executor = sExecutorMap.get(dictName);
+ public static ExecutorService getExecutor(final String id) {
+ ExecutorService executor = sExecutorMap.get(id);
if (executor == null) {
synchronized(sExecutorMap) {
- executor = new PrioritizedSerialExecutor();
- sExecutorMap.put(dictName, executor);
+ executor = sExecutorMap.get(id);
+ if (executor == null) {
+ executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithId(id));
+ sExecutorMap.put(id, executor);
+ }
}
}
return executor;
@@ -46,7 +66,7 @@ public class ExecutorUtils {
@UsedForTesting
public static void shutdownAllExecutors() {
synchronized(sExecutorMap) {
- for (final PrioritizedSerialExecutor executor : sExecutorMap.values()) {
+ for (final ExecutorService executor : sExecutorMap.values()) {
executor.execute(new Runnable() {
@Override
public void run() {
diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
index ee2b97b2a..e300bd1d3 100644
--- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
@@ -26,12 +26,11 @@ import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordFragmen
import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker;
import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
-import com.android.inputmethod.research.FeedbackFragment;
import java.util.HashSet;
public class FragmentUtils {
- private static final HashSet<String> sLatinImeFragments = new HashSet<String>();
+ private static final HashSet<String> sLatinImeFragments = new HashSet<>();
static {
sLatinImeFragments.add(DictionarySettingsFragment.class.getName());
sLatinImeFragments.add(AboutPreferences.class.getName());
@@ -43,7 +42,6 @@ public class FragmentUtils {
sLatinImeFragments.add(UserDictionaryList.class.getName());
sLatinImeFragments.add(UserDictionaryLocalePicker.class.getName());
sLatinImeFragments.add(UserDictionarySettings.class.getName());
- sLatinImeFragments.add(FeedbackFragment.class.getName());
}
public static boolean isValidFragment(String fragmentName) {
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
index 7d937a9d2..8b7077879 100644
--- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -23,7 +23,6 @@ import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.util.Log;
-import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
public final class ImportantNoticeUtils {
@@ -78,14 +77,7 @@ public final class ImportantNoticeUtils {
return getCurrentImportantNoticeVersion(context) > lastVersion;
}
- public static boolean shouldShowImportantNotice(final Context context,
- final InputAttributes inputAttributes) {
- if (inputAttributes == null || inputAttributes.mIsPasswordField) {
- return false;
- }
- if (isInSystemSetupWizard(context)) {
- return false;
- }
+ public static boolean shouldShowImportantNotice(final Context context) {
if (!hasNewImportantNotice(context)) {
return false;
}
@@ -93,6 +85,9 @@ public final class ImportantNoticeUtils {
if (TextUtils.isEmpty(importantNoticeTitle)) {
return false;
}
+ if (isInSystemSetupWizard(context)) {
+ return false;
+ }
return true;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
index 764ef72ce..6dd8d9711 100644
--- a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
@@ -37,7 +37,7 @@ public final class JsonUtils {
private static final String EMPTY_STRING = "";
public static List<Object> jsonStrToList(final String s) {
- final ArrayList<Object> list = CollectionUtils.newArrayList();
+ final ArrayList<Object> list = new ArrayList<>();
final JsonReader reader = new JsonReader(new StringReader(s));
try {
reader.beginArray();
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
index aaf4a4064..4248bebf6 100644
--- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -19,11 +19,12 @@ package com.android.inputmethod.latin.utils;
import android.util.Log;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
+import com.android.inputmethod.latin.DictionaryFacilitator;
import com.android.inputmethod.latin.PrevWordsInfo;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
// Note: this class is used as a parameter type of a native method. You should be careful when you
@@ -79,14 +80,13 @@ public final class LanguageModelParam {
// Process a list of words and return a list of {@link LanguageModelParam} objects.
public static ArrayList<LanguageModelParam> createLanguageModelParamsFrom(
- final ArrayList<String> tokens, final int timestamp,
- final DictionaryFacilitatorForSuggest dictionaryFacilitator,
+ final List<String> tokens, final int timestamp,
+ final DictionaryFacilitator dictionaryFacilitator,
final SpacingAndPunctuations spacingAndPunctuations,
final DistracterFilter distracterFilter) {
- final ArrayList<LanguageModelParam> languageModelParams =
- CollectionUtils.newArrayList();
+ final ArrayList<LanguageModelParam> languageModelParams = new ArrayList<>();
final int N = tokens.size();
- PrevWordsInfo prevWordsInfo = new PrevWordsInfo(null);
+ PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
for (int i = 0; i < N; ++i) {
final String tempWord = tokens.get(i);
if (StringUtils.isEmptyStringOrWhiteSpaces(tempWord)) {
@@ -103,7 +103,7 @@ public final class LanguageModelParam {
+ tempWord + "\"");
}
// Sentence terminator found. Split.
- prevWordsInfo = new PrevWordsInfo(null);
+ prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
continue;
}
if (DEBUG_TOKEN) {
@@ -124,43 +124,33 @@ public final class LanguageModelParam {
private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam(
final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
- final DictionaryFacilitatorForSuggest dictionaryFacilitator,
+ final DictionaryFacilitator dictionaryFacilitator,
final DistracterFilter distracterFilter) {
final Locale locale = dictionaryFacilitator.getLocale();
if (locale == null) {
return null;
}
- // TODO: Though targetWord is an IV (in-vocabulary) word, we should still apply
- // distracterFilter in the following code. If targetWord is a distracter,
- // it should be filtered out.
if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) {
return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
- true /* isValidWord */, locale);
+ true /* isValidWord */, locale, distracterFilter);
}
final String lowerCaseTargetWord = targetWord.toLowerCase(locale);
if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
// Add the lower-cased word.
return createAndGetLanguageModelParamOfWord(prevWordsInfo, lowerCaseTargetWord,
- timestamp, true /* isValidWord */, locale);
+ timestamp, true /* isValidWord */, locale, distracterFilter);
}
- // Treat the word as an OOV word. The following statement checks whether this OOV
- // is a distracter to words in dictionaries. Being a distracter means the OOV word is
- // too close to a common word in dictionaries (e.g., the OOV "mot" is very close to "not").
- // Adding such a word to dictonaries would interfere with entering in-dictionary words. For
- // example, adding "mot" to dictionaries might interfere with entering "not".
- // This kind of OOV should be filtered out.
- if (distracterFilter.isDistracterToWordsInDictionaries(prevWordsInfo, targetWord, locale)) {
- return null;
- }
+ // Treat the word as an OOV word.
return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
- false /* isValidWord */, locale);
+ false /* isValidWord */, locale, distracterFilter);
}
private static LanguageModelParam createAndGetLanguageModelParamOfWord(
final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
- final boolean isValidWord, final Locale locale) {
+ final boolean isValidWord, final Locale locale,
+ final DistracterFilter distracterFilter) {
final String word;
if (StringUtils.getCapitalizationType(targetWord) == StringUtils.CAPITALIZE_FIRST
&& prevWordsInfo.mPrevWord == null && !isValidWord) {
@@ -168,6 +158,13 @@ public final class LanguageModelParam {
} else {
word = targetWord;
}
+ // Check whether the word is a distracter to words in the dictionaries.
+ if (distracterFilter.isDistracterToWordsInDictionaries(prevWordsInfo, word, locale)) {
+ if (DEBUG) {
+ Log.d(TAG, "The word (" + word + ") is a distracter. Skip this word.");
+ }
+ return null;
+ }
final int unigramProbability = isValidWord ?
UNIGRAM_PROBABILITY_FOR_VALID_WORD : UNIGRAM_PROBABILITY_FOR_OOV_WORD;
if (prevWordsInfo.mPrevWord == null) {
diff --git a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
deleted file mode 100644
index d14ba508b..000000000
--- a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.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(StringUtils.newSingleCodePointString(code), 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/utils/LeakGuardHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
index 8469c87b0..dd6fac671 100644
--- a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
+++ b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
@@ -33,7 +33,7 @@ public class LeakGuardHandlerWrapper<T> extends Handler {
if (ownerInstance == null) {
throw new NullPointerException("ownerInstance is null");
}
- mOwnerInstanceRef = new WeakReference<T>(ownerInstance);
+ mOwnerInstanceRef = new WeakReference<>(ownerInstance);
}
public T getOwnerInstance() {
diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
index 0c55484b4..c519a0de6 100644
--- a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
@@ -159,7 +159,7 @@ public final class LocaleUtils {
return LOCALE_MATCH <= level;
}
- private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap();
+ private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();
/**
* Creates a locale from a string specification.
diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
deleted file mode 100644
index bf38abc95..000000000
--- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-
-import java.util.Queue;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * An object that executes submitted tasks using a thread.
- */
-public class PrioritizedSerialExecutor {
- public static final String TAG = PrioritizedSerialExecutor.class.getSimpleName();
-
- private final Object mLock = new Object();
-
- private final Queue<Runnable> mTasks;
- private final Queue<Runnable> mPrioritizedTasks;
- private boolean mIsShutdown;
- private final ThreadPoolExecutor mThreadPoolExecutor;
-
- // The task which is running now.
- private Runnable mActive;
-
- public PrioritizedSerialExecutor() {
- mTasks = new ConcurrentLinkedQueue<Runnable>();
- mPrioritizedTasks = new ConcurrentLinkedQueue<Runnable>();
- mIsShutdown = false;
- mThreadPoolExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
- 0 /* keepAliveTime */, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1));
- }
-
- /**
- * Enqueues the given task into the task queue.
- * @param r the enqueued task
- */
- public void execute(final Runnable r) {
- synchronized(mLock) {
- if (!mIsShutdown) {
- mTasks.offer(new Runnable() {
- @Override
- public void run() {
- try {
- r.run();
- } finally {
- scheduleNext();
- }
- }
- });
- if (mActive == null) {
- scheduleNext();
- }
- }
- }
- }
-
- /**
- * Enqueues the given task into the prioritized task queue.
- * @param r the enqueued task
- */
- @UsedForTesting
- public void executePrioritized(final Runnable r) {
- synchronized(mLock) {
- if (!mIsShutdown) {
- mPrioritizedTasks.offer(new Runnable() {
- @Override
- public void run() {
- try {
- r.run();
- } finally {
- scheduleNext();
- }
- }
- });
- if (mActive == null) {
- scheduleNext();
- }
- }
- }
- }
-
- private boolean fetchNextTasksLocked() {
- mActive = mPrioritizedTasks.poll();
- if (mActive == null) {
- mActive = mTasks.poll();
- }
- return mActive != null;
- }
-
- private void scheduleNext() {
- synchronized(mLock) {
- if (fetchNextTasksLocked()) {
- mThreadPoolExecutor.execute(mActive);
- }
- }
- }
-
- public void shutdown() {
- synchronized(mLock) {
- mIsShutdown = true;
- mThreadPoolExecutor.shutdown();
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
index 4521ec531..e3cac97f0 100644
--- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
+++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
@@ -62,18 +62,22 @@ public class RecapitalizeStatus {
private Locale mLocale;
private int[] mSortedSeparators;
private String mStringAfter;
- private boolean mIsActive;
+ private boolean mIsStarted;
+ private boolean mIsEnabled = true;
private static final int[] EMPTY_STORTED_SEPARATORS = {};
public RecapitalizeStatus() {
// By default, initialize with dummy values that won't match any real recapitalize.
- initialize(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS);
- deactivate();
+ start(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS);
+ stop();
}
- public void initialize(final int cursorStart, final int cursorEnd, final String string,
+ public void start(final int cursorStart, final int cursorEnd, final String string,
final Locale locale, final int[] sortedSeparators) {
+ if (!mIsEnabled) {
+ return;
+ }
mCursorStartBefore = cursorStart;
mStringBefore = string;
mCursorStartAfter = cursorStart;
@@ -96,15 +100,27 @@ public class RecapitalizeStatus {
mRotationStyleCurrentIndex = currentMode;
mSkipOriginalMixedCaseMode = true;
}
- mIsActive = true;
+ mIsStarted = true;
+ }
+
+ public void stop() {
+ mIsStarted = false;
+ }
+
+ public boolean isStarted() {
+ return mIsStarted;
+ }
+
+ public void enable() {
+ mIsEnabled = true;
}
- public void deactivate() {
- mIsActive = false;
+ public void disable() {
+ mIsEnabled = false;
}
- public boolean isActive() {
- return mIsActive;
+ public boolean mIsEnabled() {
+ return mIsEnabled;
}
public boolean isSetAt(final int cursorStart, final int cursorEnd) {
diff --git a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
index 49f4929b4..093c5a6c1 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
@@ -41,8 +41,7 @@ public final class ResourceUtils {
// This utility class is not publicly instantiable.
}
- private static final HashMap<String, String> sDeviceOverrideValueMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, String> sDeviceOverrideValueMap = new HashMap<>();
private static final String[] BUILD_KEYS_AND_VALUES = {
"HARDWARE", Build.HARDWARE,
@@ -54,8 +53,8 @@ public final class ResourceUtils {
private static final String sBuildKeyValuesDebugString;
static {
- sBuildKeyValues = CollectionUtils.newHashMap();
- final ArrayList<String> keyValuePairs = CollectionUtils.newArrayList();
+ sBuildKeyValues = new HashMap<>();
+ final ArrayList<String> keyValuePairs = new ArrayList<>();
final int keyCount = BUILD_KEYS_AND_VALUES.length / 2;
for (int i = 0; i < keyCount; i++) {
final int index = i * 2;
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index 73ac9a573..e4237a7f2 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -28,7 +28,6 @@ import java.util.Arrays;
import java.util.Locale;
public final class StringUtils {
- private static final String TAG = StringUtils.class.getSimpleName();
public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
public static final int CAPITALIZE_FIRST = 1; // First only
public static final int CAPITALIZE_ALL = 2; // All caps
@@ -110,7 +109,7 @@ public final class StringUtils {
if (!containsInArray(text, elements)) {
return extraValues;
}
- final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
+ final ArrayList<String> result = new ArrayList<>(elements.length - 1);
for (final String element : elements) {
if (!text.equals(element)) {
result.add(element);
@@ -316,24 +315,6 @@ public final class StringUtils {
return true;
}
- /**
- * Returns true if all code points in text are whitespace, false otherwise. Empty is true.
- */
- // Interestingly enough, U+00A0 NO-BREAK SPACE and U+200B ZERO-WIDTH SPACE are not considered
- // whitespace, while EN SPACE, EM SPACE and IDEOGRAPHIC SPACES are.
- public static boolean containsOnlyWhitespace(final String text) {
- final int length = text.length();
- int i = 0;
- while (i < length) {
- final int codePoint = text.codePointAt(i);
- if (!Character.isWhitespace(codePoint)) {
- return false;
- }
- i += Character.charCount(codePoint);
- }
- return true;
- }
-
public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
final int[] sortedSeparators) {
boolean needsCapsNext = true;
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 938d27122..351d01400 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -49,17 +49,14 @@ public final class SubtypeLocaleUtils {
private static Resources sResources;
private static String[] sPredefinedKeyboardLayoutSet;
// Keyboard layout to its display name map.
- private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = new HashMap<>();
// Keyboard layout to subtype name resource id map.
- private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>();
// Exceptional locale to subtype name resource id map.
- private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>();
// Exceptional locale to subtype name with layout resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
- CollectionUtils.newHashMap();
+ new HashMap<>();
private static final String SUBTYPE_NAME_RESOURCE_PREFIX =
"string/subtype_";
private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
@@ -71,7 +68,7 @@ public final class SubtypeLocaleUtils {
// Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
// This is for compatibility to keep the same subtype ids as pre-JellyBean.
private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
- CollectionUtils.newHashMap();
+ new HashMap<>();
private SubtypeLocaleUtils() {
// Intentional empty constructor for utility class.
diff --git a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
index 42ea3c959..ab2b00e36 100644
--- a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
+++ b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
@@ -27,8 +27,7 @@ import com.android.inputmethod.compat.AppWorkaroundsUtils;
public final class TargetPackageInfoGetterTask extends
AsyncTask<String, Void, PackageInfo> {
private static final int MAX_CACHE_ENTRIES = 64; // arbitrary
- private static final LruCache<String, PackageInfo> sCache =
- new LruCache<String, PackageInfo>(MAX_CACHE_ENTRIES);
+ private static final LruCache<String, PackageInfo> sCache = new LruCache<>(MAX_CACHE_ENTRIES);
public static PackageInfo getCachedPackageInfo(final String packageName) {
if (null == packageName) return null;
diff --git a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
index 087a7f255..fafba79c2 100644
--- a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -30,7 +30,7 @@ public final class TypefaceUtils {
}
// This sparse array caches key label text height in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
+ private static final SparseArray<Float> sTextHeightCache = new SparseArray<>();
// Working variable for the following method.
private static final Rect sTextHeightBounds = new Rect();
@@ -50,7 +50,7 @@ public final class TypefaceUtils {
}
// This sparse array caches key label text width in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
+ private static final SparseArray<Float> sTextWidthCache = new SparseArray<>();
// Working variable for the following method.
private static final Rect sTextWidthBounds = new Rect();
diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
deleted file mode 100644
index 06826dac0..000000000
--- a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * 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/utils/UserLogRingCharBuffer.java b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
deleted file mode 100644
index a75d353c9..000000000
--- a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.inputmethodservice.InputMethodService;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.LatinImeLogger;
-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;
- }
- if (LatinImeLogger.sUsabilityStudy) {
- UsabilityStudyLogUtils.getInstance().writeChar(c, x, y);
- }
- 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/research/BootBroadcastReceiver.java b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
deleted file mode 100644
index 4f86526a7..000000000
--- a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Arrange for the uploading service to be run on regular intervals.
- */
-public final class BootBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
- UploaderService.cancelAndRescheduleUploadingService(context,
- true /* needsRescheduling */);
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/FeedbackActivity.java b/java/src/com/android/inputmethod/research/FeedbackActivity.java
deleted file mode 100644
index 520b88d2f..000000000
--- a/java/src/com/android/inputmethod/research/FeedbackActivity.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import com.android.inputmethod.latin.R;
-
-public class FeedbackActivity extends Activity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.research_feedback_activity);
- final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout);
- layout.setActivity(this);
- }
-
- @Override
- public void onBackPressed() {
- ResearchLogger.getInstance().onLeavingSendFeedbackDialog();
- super.onBackPressed();
- }
-}
diff --git a/java/src/com/android/inputmethod/research/FeedbackFragment.java b/java/src/com/android/inputmethod/research/FeedbackFragment.java
deleted file mode 100644
index 75fbbf1ba..000000000
--- a/java/src/com/android/inputmethod/research/FeedbackFragment.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.Toast;
-
-import com.android.inputmethod.latin.R;
-
-public class FeedbackFragment extends Fragment implements OnClickListener {
- private static final String TAG = FeedbackFragment.class.getSimpleName();
-
- public static final String KEY_FEEDBACK_STRING = "FeedbackString";
- public static final String KEY_INCLUDE_ACCOUNT_NAME = "IncludeAccountName";
- public static final String KEY_HAS_USER_RECORDING = "HasRecording";
-
- private EditText mEditText;
- private CheckBox mIncludingAccountNameCheckBox;
- private CheckBox mIncludingUserRecordingCheckBox;
- private Button mSendButton;
- private Button mCancelButton;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.research_feedback_fragment_layout, container,
- false);
- mEditText = (EditText) view.findViewById(R.id.research_feedback_contents);
- mEditText.requestFocus();
- mIncludingAccountNameCheckBox = (CheckBox) view.findViewById(
- R.id.research_feedback_include_account_name);
- mIncludingUserRecordingCheckBox = (CheckBox) view.findViewById(
- R.id.research_feedback_include_recording_checkbox);
- mIncludingUserRecordingCheckBox.setOnClickListener(this);
-
- mSendButton = (Button) view.findViewById(R.id.research_feedback_send_button);
- mSendButton.setOnClickListener(this);
- mCancelButton = (Button) view.findViewById(R.id.research_feedback_cancel_button);
- mCancelButton.setOnClickListener(this);
-
- if (savedInstanceState != null) {
- restoreState(savedInstanceState);
- } else {
- final Bundle bundle = getActivity().getIntent().getExtras();
- if (bundle != null) {
- restoreState(bundle);
- }
- }
- return view;
- }
-
- @Override
- public void onClick(final View view) {
- final ResearchLogger researchLogger = ResearchLogger.getInstance();
- if (view == mIncludingUserRecordingCheckBox) {
- if (mIncludingUserRecordingCheckBox.isChecked()) {
- final Bundle bundle = new Bundle();
- onSaveInstanceState(bundle);
-
- // Let the user make a recording
- getActivity().finish();
-
- researchLogger.setFeedbackDialogBundle(bundle);
- researchLogger.onLeavingSendFeedbackDialog();
- researchLogger.startRecording();
- }
- } else if (view == mSendButton) {
- final Editable editable = mEditText.getText();
- final String feedbackContents = editable.toString();
- if (TextUtils.isEmpty(feedbackContents)) {
- Toast.makeText(getActivity(),
- R.string.research_feedback_empty_feedback_error_message,
- Toast.LENGTH_LONG).show();
- } else {
- final boolean isIncludingAccountName = mIncludingAccountNameCheckBox.isChecked();
- researchLogger.sendFeedback(feedbackContents, false /* isIncludingHistory */,
- isIncludingAccountName, mIncludingUserRecordingCheckBox.isChecked());
- getActivity().finish();
- researchLogger.setFeedbackDialogBundle(null);
- researchLogger.onLeavingSendFeedbackDialog();
- }
- } else if (view == mCancelButton) {
- Log.d(TAG, "Finishing");
- getActivity().finish();
- researchLogger.setFeedbackDialogBundle(null);
- researchLogger.onLeavingSendFeedbackDialog();
- } else {
- Log.e(TAG, "Unknown view passed to FeedbackFragment.onClick()");
- }
- }
-
- @Override
- public void onSaveInstanceState(final Bundle bundle) {
- final String savedFeedbackString = mEditText.getText().toString();
-
- bundle.putString(KEY_FEEDBACK_STRING, savedFeedbackString);
- bundle.putBoolean(KEY_INCLUDE_ACCOUNT_NAME, mIncludingAccountNameCheckBox.isChecked());
- bundle.putBoolean(KEY_HAS_USER_RECORDING, mIncludingUserRecordingCheckBox.isChecked());
- }
-
- private void restoreState(final Bundle bundle) {
- mEditText.setText(bundle.getString(KEY_FEEDBACK_STRING));
- mIncludingAccountNameCheckBox.setChecked(bundle.getBoolean(KEY_INCLUDE_ACCOUNT_NAME));
- mIncludingUserRecordingCheckBox.setChecked(bundle.getBoolean(KEY_HAS_USER_RECORDING));
- }
-}
diff --git a/java/src/com/android/inputmethod/research/FeedbackLayout.java b/java/src/com/android/inputmethod/research/FeedbackLayout.java
deleted file mode 100644
index d283d14b2..000000000
--- a/java/src/com/android/inputmethod/research/FeedbackLayout.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.app.Activity;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.widget.LinearLayout;
-
-public class FeedbackLayout extends LinearLayout {
- private Activity mActivity;
-
- public FeedbackLayout(Context context) {
- super(context);
- }
-
- public FeedbackLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public FeedbackLayout(Context context, AttributeSet attrs, int defstyle) {
- super(context, attrs, defstyle);
- }
-
- public void setActivity(Activity activity) {
- mActivity = activity;
- }
-
- @Override
- public boolean dispatchKeyEventPreIme(KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
- if (state != null) {
- if (event.getAction() == KeyEvent.ACTION_DOWN
- && event.getRepeatCount() == 0) {
- state.startTracking(event, this);
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP
- && !event.isCanceled() && state.isTracking(event)) {
- mActivity.onBackPressed();
- return true;
- }
- }
- }
- return super.dispatchKeyEventPreIme(event);
- }
-}
diff --git a/java/src/com/android/inputmethod/research/FeedbackLog.java b/java/src/com/android/inputmethod/research/FeedbackLog.java
deleted file mode 100644
index 5af194c32..000000000
--- a/java/src/com/android/inputmethod/research/FeedbackLog.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.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/FixedLogBuffer.java b/java/src/com/android/inputmethod/research/FixedLogBuffer.java
deleted file mode 100644
index 8b64de8ae..000000000
--- a/java/src/com/android/inputmethod/research/FixedLogBuffer.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-
-/**
- * A buffer that holds a fixed number of LogUnits.
- *
- * LogUnits are added in and shifted out in temporal order. Only a subset of the LogUnits are
- * actual words; the other LogUnits do not count toward the word limit. Once the buffer reaches
- * capacity, adding another LogUnit that is a word evicts the oldest LogUnits out one at a time to
- * stay under the capacity limit.
- *
- * This variant of a LogBuffer has a limited memory footprint because of its limited size. This
- * makes it useful, for example, for recording a window of the user's most recent actions in case
- * they want to report an observed error that they do not know how to reproduce.
- */
-public class FixedLogBuffer extends LogBuffer {
- /* package for test */ int mWordCapacity;
- // The number of members of mLogUnits that are actual words.
- private int mNumActualWords;
-
- /**
- * Create a new LogBuffer that can hold a fixed number of LogUnits that are words (and
- * unlimited number of non-word LogUnits), and that outputs its result to a researchLog.
- *
- * @param wordCapacity maximum number of words
- */
- public FixedLogBuffer(final int wordCapacity) {
- super();
- if (wordCapacity <= 0) {
- throw new IllegalArgumentException("wordCapacity must be 1 or greater.");
- }
- mWordCapacity = wordCapacity;
- mNumActualWords = 0;
- }
-
- /**
- * Adds a new LogUnit to the front of the LIFO queue, evicting existing LogUnit's
- * (oldest first) if word capacity is reached.
- */
- @Override
- public void shiftIn(final LogUnit newLogUnit) {
- if (!newLogUnit.hasOneOrMoreWords()) {
- // This LogUnit doesn't contain any word, so it doesn't count toward the word-limit.
- super.shiftIn(newLogUnit);
- return;
- }
- final int numWordsIncoming = newLogUnit.getNumWords();
- if (mNumActualWords >= mWordCapacity) {
- // Give subclass a chance to handle the buffer full condition by shifting out logUnits.
- // TODO: Tell onBufferFull() how much space it needs to make to avoid forced eviction.
- onBufferFull();
- // If still full, evict.
- if (mNumActualWords >= mWordCapacity) {
- shiftOutWords(numWordsIncoming);
- }
- }
- super.shiftIn(newLogUnit);
- mNumActualWords += numWordsIncoming;
- }
-
- @Override
- public LogUnit unshiftIn() {
- final LogUnit logUnit = super.unshiftIn();
- if (logUnit != null && logUnit.hasOneOrMoreWords()) {
- mNumActualWords -= logUnit.getNumWords();
- }
- return logUnit;
- }
-
- public int getNumWords() {
- return mNumActualWords;
- }
-
- /**
- * Removes all LogUnits from the buffer without calling onShiftOut().
- */
- @Override
- public void clear() {
- super.clear();
- mNumActualWords = 0;
- }
-
- /**
- * Called when the buffer has just shifted in one more word than its maximum, and its about to
- * shift out LogUnits to bring it back down to the maximum.
- *
- * Base class does nothing; subclasses may override if they want to record non-privacy sensitive
- * events that fall off the end.
- */
- protected void onBufferFull() {
- }
-
- @Override
- public LogUnit shiftOut() {
- final LogUnit logUnit = super.shiftOut();
- if (logUnit != null && logUnit.hasOneOrMoreWords()) {
- mNumActualWords -= logUnit.getNumWords();
- }
- return logUnit;
- }
-
- /**
- * Remove LogUnits from the front of the LogBuffer until {@code numWords} have been removed.
- *
- * If there are less than {@code numWords} in the buffer, shifts out all {@code LogUnit}s.
- *
- * @param numWords the minimum number of words in {@link LogUnit}s to shift out
- * @return the number of actual words LogUnit}s shifted out
- */
- protected int shiftOutWords(final int numWords) {
- int numWordsShiftedOut = 0;
- do {
- final LogUnit logUnit = shiftOut();
- if (logUnit == null) break;
- numWordsShiftedOut += logUnit.getNumWords();
- } while (numWordsShiftedOut < numWords);
- return numWordsShiftedOut;
- }
-
- public void shiftOutAll() {
- final LinkedList<LogUnit> logUnits = getLogUnits();
- while (!logUnits.isEmpty()) {
- shiftOut();
- }
- mNumActualWords = 0;
- }
-
- /**
- * Returns a list of {@link LogUnit}s at the front of the buffer that have words associated with
- * them.
- *
- * There will be no more than {@code n} words in the returned list. So if 2 words are
- * requested, and the first LogUnit has 3 words, it is not returned. If 2 words are requested,
- * and the first LogUnit has only 1 word, and the next LogUnit 2 words, only the first LogUnit
- * is returned. If the first LogUnit has no words associated with it, and the second LogUnit
- * has three words, then only the first LogUnit (which has no associated words) is returned. If
- * there are not enough LogUnits in the buffer to meet the word requirement, then all LogUnits
- * will be returned.
- *
- * @param n The maximum number of {@link LogUnit}s with words to return.
- * @return The list of the {@link LogUnit}s containing the first n words
- */
- public ArrayList<LogUnit> peekAtFirstNWords(int n) {
- final LinkedList<LogUnit> logUnits = getLogUnits();
- // Allocate space for n*2 logUnits. There will be at least n, one for each word, and
- // there may be additional for punctuation, between-word commands, etc. This should be
- // enough that reallocation won't be necessary.
- final ArrayList<LogUnit> resultList = new ArrayList<LogUnit>(n * 2);
- for (final LogUnit logUnit : logUnits) {
- n -= logUnit.getNumWords();
- if (n < 0) break;
- resultList.add(logUnit);
- }
- return resultList;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java
deleted file mode 100644
index 6170b4339..000000000
--- a/java/src/com/android/inputmethod/research/JsonUtils.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.content.SharedPreferences;
-import android.util.JsonWriter;
-import android.view.MotionEvent;
-import android.view.inputmethod.CompletionInfo;
-
-import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
-import java.io.IOException;
-import java.util.Map;
-
-/**
- * Routines for mapping classes and variables to JSON representations for logging.
- */
-/* package */ class JsonUtils {
- private JsonUtils() {
- // This utility class is not publicly instantiable.
- }
-
- /* package */ static void writeJson(final CompletionInfo[] ci, final JsonWriter jsonWriter)
- throws IOException {
- jsonWriter.beginArray();
- for (int j = 0; j < ci.length; j++) {
- jsonWriter.value(ci[j].toString());
- }
- jsonWriter.endArray();
- }
-
- /* package */ static void writeJson(final SharedPreferences prefs, final JsonWriter jsonWriter)
- throws IOException {
- jsonWriter.beginObject();
- for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) {
- jsonWriter.name(entry.getKey());
- final Object innerValue = entry.getValue();
- if (innerValue == null) {
- jsonWriter.nullValue();
- } else if (innerValue instanceof Boolean) {
- jsonWriter.value((Boolean) innerValue);
- } else if (innerValue instanceof Number) {
- jsonWriter.value((Number) innerValue);
- } else {
- jsonWriter.value(innerValue.toString());
- }
- }
- jsonWriter.endObject();
- }
-
- /* package */ static void writeJson(final Key[] keys, final JsonWriter jsonWriter)
- throws IOException {
- jsonWriter.beginArray();
- for (Key key : keys) {
- writeJson(key, jsonWriter);
- }
- jsonWriter.endArray();
- }
-
- private static void writeJson(final Key key, final JsonWriter jsonWriter) throws IOException {
- jsonWriter.beginObject();
- jsonWriter.name("code").value(key.getCode());
- jsonWriter.name("altCode").value(key.getAltCode());
- jsonWriter.name("x").value(key.getX());
- jsonWriter.name("y").value(key.getY());
- jsonWriter.name("w").value(key.getWidth());
- jsonWriter.name("h").value(key.getHeight());
- jsonWriter.endObject();
- }
-
- /* package */ static void writeJson(final SuggestedWords words, final JsonWriter jsonWriter)
- throws IOException {
- jsonWriter.beginObject();
- jsonWriter.name("typedWordValid").value(words.mTypedWordValid);
- jsonWriter.name("willAutoCorrect")
- .value(words.mWillAutoCorrect);
- jsonWriter.name("isPunctuationSuggestions")
- .value(words.isPunctuationSuggestions());
- jsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
- jsonWriter.name("isPrediction").value(words.mIsPrediction);
- jsonWriter.name("suggestedWords");
- jsonWriter.beginArray();
- final int size = words.size();
- for (int j = 0; j < size; j++) {
- final SuggestedWordInfo wordInfo = words.getInfo(j);
- 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.mDictType);
- jsonWriter.endObject();
- }
- jsonWriter.endArray();
- jsonWriter.endObject();
- }
-
- /* package */ static void writeJson(final MotionEvent me, final JsonWriter jsonWriter)
- throws IOException {
- jsonWriter.beginObject();
- jsonWriter.name("pointerIds");
- jsonWriter.beginArray();
- final int pointerCount = me.getPointerCount();
- for (int index = 0; index < pointerCount; index++) {
- jsonWriter.value(me.getPointerId(index));
- }
- jsonWriter.endArray();
-
- jsonWriter.name("xyt");
- jsonWriter.beginArray();
- final int historicalSize = me.getHistorySize();
- for (int index = 0; index < historicalSize; index++) {
- jsonWriter.beginObject();
- jsonWriter.name("t");
- jsonWriter.value(me.getHistoricalEventTime(index));
- jsonWriter.name("d");
- jsonWriter.beginArray();
- for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
- jsonWriter.beginObject();
- jsonWriter.name("x");
- jsonWriter.value(me.getHistoricalX(pointerIndex, index));
- jsonWriter.name("y");
- jsonWriter.value(me.getHistoricalY(pointerIndex, index));
- jsonWriter.endObject();
- }
- jsonWriter.endArray();
- jsonWriter.endObject();
- }
- jsonWriter.beginObject();
- jsonWriter.name("t");
- jsonWriter.value(me.getEventTime());
- jsonWriter.name("d");
- jsonWriter.beginArray();
- for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
- jsonWriter.beginObject();
- jsonWriter.name("x");
- jsonWriter.value(me.getX(pointerIndex));
- jsonWriter.name("y");
- jsonWriter.value(me.getY(pointerIndex));
- jsonWriter.endObject();
- }
- jsonWriter.endArray();
- jsonWriter.endObject();
- jsonWriter.endArray();
- jsonWriter.endObject();
- }
-}
diff --git a/java/src/com/android/inputmethod/research/LogBuffer.java b/java/src/com/android/inputmethod/research/LogBuffer.java
deleted file mode 100644
index b07b761f0..000000000
--- a/java/src/com/android/inputmethod/research/LogBuffer.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import java.util.LinkedList;
-
-/**
- * Maintain a FIFO queue of LogUnits.
- *
- * This class provides an unbounded queue. This is useful when the user is aware that their actions
- * are being recorded, such as when they are trying to reproduce a bug. In this case, there should
- * not be artificial restrictions on how many events that can be saved.
- */
-public class LogBuffer {
- // TODO: Gracefully handle situations in which this LogBuffer is consuming too much memory.
- // This may happen, for example, if the user has forgotten that data is being logged.
- private final LinkedList<LogUnit> mLogUnits;
-
- public LogBuffer() {
- mLogUnits = new LinkedList<LogUnit>();
- }
-
- protected LinkedList<LogUnit> getLogUnits() {
- return mLogUnits;
- }
-
- public void clear() {
- mLogUnits.clear();
- }
-
- public void shiftIn(final LogUnit logUnit) {
- mLogUnits.add(logUnit);
- }
-
- public LogUnit unshiftIn() {
- if (mLogUnits.isEmpty()) {
- return null;
- }
- return mLogUnits.removeLast();
- }
-
- public LogUnit peekLastLogUnit() {
- if (mLogUnits.isEmpty()) {
- return null;
- }
- return mLogUnits.peekLast();
- }
-
- public boolean isEmpty() {
- return mLogUnits.isEmpty();
- }
-
- public LogUnit shiftOut() {
- if (isEmpty()) {
- return null;
- }
- return mLogUnits.removeFirst();
- }
-}
diff --git a/java/src/com/android/inputmethod/research/LogStatement.java b/java/src/com/android/inputmethod/research/LogStatement.java
deleted file mode 100644
index 06b918af5..000000000
--- a/java/src/com/android/inputmethod/research/LogStatement.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.content.SharedPreferences;
-import android.util.JsonWriter;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.inputmethod.CompletionInfo;
-
-import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.io.IOException;
-
-/**
- * A template for typed information stored in the logs.
- *
- * A LogStatement contains a name, keys, and flags about whether the {@code Object[] values}
- * associated with the {@code String[] keys} are likely to reveal information about the user. The
- * actual values are stored separately.
- */
-public class LogStatement {
- private static final String TAG = LogStatement.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
-
- // Constants for particular statements
- public static final String TYPE_POINTER_TRACKER_CALL_LISTENER_ON_CODE_INPUT =
- "PointerTrackerCallListenerOnCodeInput";
- public static final String KEY_CODE = "code";
- public static final String VALUE_RESEARCH = "research";
- public static final String TYPE_MAIN_KEYBOARD_VIEW_ON_LONG_PRESS =
- "MainKeyboardViewOnLongPress";
- public static final String ACTION = "action";
- public static final String VALUE_DOWN = "DOWN";
- public static final String TYPE_MOTION_EVENT = "MotionEvent";
- public static final String KEY_IS_LOGGING_RELATED = "isLoggingRelated";
-
- // Keys for internal key/value pairs
- private static final String CURRENT_TIME_KEY = "_ct";
- private static final String UPTIME_KEY = "_ut";
- private static final String EVENT_TYPE_KEY = "_ty";
-
- // Name specifying the LogStatement type.
- private final String mType;
-
- // mIsPotentiallyPrivate indicates that event contains potentially private information. If
- // the word that this event is a part of is determined to be privacy-sensitive, then this
- // event should not be included in the output log. The system waits to output until the
- // containing word is known.
- private final boolean mIsPotentiallyPrivate;
-
- // mIsPotentiallyRevealing indicates that this statement may disclose details about other
- // words typed in other LogUnits. This can happen if the user is not inserting spaces, and
- // data from Suggestions and/or Composing text reveals the entire "megaword". For example,
- // say the user is typing "for the win", and the system wants to record the bigram "the
- // win". If the user types "forthe", omitting the space, the system will give "for the" as
- // a suggestion. If the user accepts the autocorrection, the suggestion for "for the" is
- // included in the log for the word "the", disclosing that the previous word had been "for".
- // For now, we simply do not include this data when logging part of a "megaword".
- private final boolean mIsPotentiallyRevealing;
-
- // mKeys stores the names that are the attributes in the output json objects
- private final String[] mKeys;
- private static final String[] NULL_KEYS = new String[0];
-
- LogStatement(final String name, final boolean isPotentiallyPrivate,
- final boolean isPotentiallyRevealing, final String... keys) {
- mType = name;
- mIsPotentiallyPrivate = isPotentiallyPrivate;
- mIsPotentiallyRevealing = isPotentiallyRevealing;
- mKeys = (keys == null) ? NULL_KEYS : keys;
- }
-
- public String getType() {
- return mType;
- }
-
- public boolean isPotentiallyPrivate() {
- return mIsPotentiallyPrivate;
- }
-
- public boolean isPotentiallyRevealing() {
- return mIsPotentiallyRevealing;
- }
-
- public String[] getKeys() {
- return mKeys;
- }
-
- /**
- * Utility function to test whether a key-value pair exists in a LogStatement.
- *
- * A LogStatement is really just a template -- it does not contain the values, only the
- * keys. So the values must be passed in as an argument.
- *
- * @param queryKey the String that is tested by {@code String.equals()} to the keys in the
- * LogStatement
- * @param queryValue an Object that must be {@code Object.equals()} to the key's corresponding
- * value in the {@code values} array
- * @param values the values corresponding to mKeys
- *
- * @returns {@true} if {@code queryKey} exists in the keys for this LogStatement, and {@code
- * queryValue} matches the corresponding value in {@code values}
- *
- * @throws IllegalArgumentException if {@code values.length} is not equal to keys().length()
- */
- public boolean containsKeyValuePair(final String queryKey, final Object queryValue,
- final Object[] values) {
- if (mKeys.length != values.length) {
- throw new IllegalArgumentException("Mismatched number of keys and values.");
- }
- final int length = mKeys.length;
- for (int i = 0; i < length; i++) {
- if (mKeys[i].equals(queryKey) && values[i].equals(queryValue)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Utility function to set a value in a LogStatement.
- *
- * A LogStatement is really just a template -- it does not contain the values, only the
- * keys. So the values must be passed in as an argument.
- *
- * @param queryKey the String that is tested by {@code String.equals()} to the keys in the
- * LogStatement
- * @param values the array of values corresponding to mKeys
- * @param newValue the replacement value to go into the {@code values} array
- *
- * @returns {@true} if the key exists and the value was successfully set, {@false} otherwise
- *
- * @throws IllegalArgumentException if {@code values.length} is not equal to keys().length()
- */
- public boolean setValue(final String queryKey, final Object[] values, final Object newValue) {
- if (mKeys.length != values.length) {
- throw new IllegalArgumentException("Mismatched number of keys and values.");
- }
- final int length = mKeys.length;
- for (int i = 0; i < length; i++) {
- if (mKeys[i].equals(queryKey)) {
- values[i] = newValue;
- return true;
- }
- }
- return false;
- }
-
- /**
- * Write the contents out through jsonWriter.
- *
- * The JsonWriter class must have already had {@code JsonWriter.beginArray} called on it.
- *
- * Note that this method is not thread safe for the same jsonWriter. Callers must ensure
- * thread safety.
- */
- public boolean outputToLocked(final JsonWriter jsonWriter, final Long time,
- final Object... values) {
- if (DEBUG) {
- if (mKeys.length != values.length) {
- Log.d(TAG, "Key and Value list sizes do not match. " + mType);
- }
- }
- try {
- jsonWriter.beginObject();
- jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
- jsonWriter.name(UPTIME_KEY).value(time);
- jsonWriter.name(EVENT_TYPE_KEY).value(mType);
- final int length = values.length;
- for (int i = 0; i < length; i++) {
- jsonWriter.name(mKeys[i]);
- final Object value = values[i];
- if (value instanceof CharSequence) {
- jsonWriter.value(value.toString());
- } else if (value instanceof Number) {
- jsonWriter.value((Number) value);
- } else if (value instanceof Boolean) {
- jsonWriter.value((Boolean) value);
- } else if (value instanceof CompletionInfo[]) {
- JsonUtils.writeJson((CompletionInfo[]) value, jsonWriter);
- } else if (value instanceof SharedPreferences) {
- JsonUtils.writeJson((SharedPreferences) value, jsonWriter);
- } else if (value instanceof Key[]) {
- JsonUtils.writeJson((Key[]) value, jsonWriter);
- } else if (value instanceof SuggestedWords) {
- JsonUtils.writeJson((SuggestedWords) value, jsonWriter);
- } else if (value instanceof MotionEvent) {
- JsonUtils.writeJson((MotionEvent) value, jsonWriter);
- } else if (value == null) {
- jsonWriter.nullValue();
- } else {
- if (DEBUG) {
- Log.w(TAG, "Unrecognized type to be logged: "
- + (value == null ? "<null>" : value.getClass().getName()));
- }
- jsonWriter.nullValue();
- }
- }
- jsonWriter.endObject();
- } catch (IOException e) {
- e.printStackTrace();
- Log.w(TAG, "Error in JsonWriter; skipping LogStatement");
- return false;
- }
- return true;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
deleted file mode 100644
index 3366df12a..000000000
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.JsonWriter;
-import android.util.Log;
-
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * A group of log statements related to each other.
- *
- * A LogUnit is collection of LogStatements, each of which is generated by at a particular point
- * in the code. (There is no LogStatement class; the data is stored across the instance variables
- * here.) A single LogUnit's statements can correspond to all the calls made while in the same
- * composing region, or all the calls between committing the last composing region, and the first
- * character of the next composing region.
- *
- * Individual statements in a log may be marked as potentially private. If so, then they are only
- * published to a ResearchLog if the ResearchLogger determines that publishing the entire LogUnit
- * will not violate the user's privacy. Checks for this may include whether other LogUnits have
- * been published recently, or whether the LogUnit contains numbers, etc.
- */
-public class LogUnit {
- private static final String TAG = LogUnit.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
-
- private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");
- private static final String[] EMPTY_STRING_ARRAY = new String[0];
-
- private final ArrayList<LogStatement> mLogStatementList;
- private final ArrayList<Object[]> mValuesList;
- // Assume that mTimeList is sorted in increasing order. Do not insert null values into
- // mTimeList.
- private final ArrayList<Long> mTimeList;
- // Words that this LogUnit generates. Should be null if the data in the LogUnit does not
- // generate a genuine word (i.e. separators alone do not count as a word). Should never be
- // empty. Note that if the user types spaces explicitly, then normally mWords should contain
- // only a single word; it will only contain space-separate multiple words if the user does not
- // enter a space, and the system enters one automatically.
- private String mWords;
- private String[] mWordArray = EMPTY_STRING_ARRAY;
- private boolean mMayContainDigit;
- private boolean mIsPartOfMegaword;
- private boolean mContainsUserDeletions;
-
- // mCorrectionType indicates whether the word was corrected at all, and if so, the nature of the
- // correction.
- private int mCorrectionType;
- // LogUnits start in this state. If a word is entered without being corrected, it will have
- // this CorrectiontType.
- public static final int CORRECTIONTYPE_NO_CORRECTION = 0;
- // The LogUnit was corrected manually by the user in an unspecified way.
- public static final int CORRECTIONTYPE_CORRECTION = 1;
- // The LogUnit was corrected manually by the user to a word not in the list of suggestions of
- // the first word typed here. (Note: this is a heuristic value, it may be incorrect, for
- // example, if the user repositions the cursor).
- public static final int CORRECTIONTYPE_DIFFERENT_WORD = 2;
- // The LogUnit was corrected manually by the user to a word that was in the list of suggestions
- // of the first word typed here. (Again, a heuristic). It is probably a typo correction.
- public static final int CORRECTIONTYPE_TYPO = 3;
- // TODO: Rather than just tracking the current state, keep a historical record of the LogUnit's
- // state and statistics. This should include how many times it has been corrected, whether
- // other LogUnit edits were done between edits to this LogUnit, etc. Also track when a LogUnit
- // previously contained a word, but was corrected to empty (because it was deleted, and there is
- // no known replacement).
-
- private SuggestedWords mSuggestedWords;
-
- public LogUnit() {
- mLogStatementList = new ArrayList<LogStatement>();
- mValuesList = new ArrayList<Object[]>();
- mTimeList = new ArrayList<Long>();
- mIsPartOfMegaword = false;
- mCorrectionType = CORRECTIONTYPE_NO_CORRECTION;
- mSuggestedWords = null;
- }
-
- private LogUnit(final ArrayList<LogStatement> logStatementList,
- final ArrayList<Object[]> valuesList,
- final ArrayList<Long> timeList,
- final boolean isPartOfMegaword) {
- mLogStatementList = logStatementList;
- mValuesList = valuesList;
- mTimeList = timeList;
- mIsPartOfMegaword = isPartOfMegaword;
- mCorrectionType = CORRECTIONTYPE_NO_CORRECTION;
- mSuggestedWords = null;
- }
-
- private static final Object[] NULL_VALUES = new Object[0];
- /**
- * Adds a new log statement. The time parameter in successive calls to this method must be
- * monotonically increasing, or splitByTime() will not work.
- */
- public void addLogStatement(final LogStatement logStatement, final long time,
- Object... values) {
- if (values == null) {
- values = NULL_VALUES;
- }
- mLogStatementList.add(logStatement);
- mValuesList.add(values);
- mTimeList.add(time);
- }
-
- /**
- * Publish the contents of this LogUnit to {@code researchLog}.
- *
- * For each publishable {@code LogStatement}, invoke {@link LogStatement#outputToLocked}.
- *
- * @param researchLog where to publish the contents of this {@code LogUnit}
- * @param canIncludePrivateData whether the private data in this {@code LogUnit} should be
- * included
- *
- * @throws IOException if publication to the log file is not possible
- */
- public synchronized void publishTo(final ResearchLog researchLog,
- final boolean canIncludePrivateData) throws IOException {
- // Write out any logStatement that passes the privacy filter.
- final int size = mLogStatementList.size();
- 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 = researchLog.getInitializedJsonWriterLocked();
- outputLogUnitStart(jsonWriter, canIncludePrivateData);
- for (int i = 0; i < size; i++) {
- final LogStatement logStatement = mLogStatementList.get(i);
- if (!canIncludePrivateData && logStatement.isPotentiallyPrivate()) {
- continue;
- }
- if (mIsPartOfMegaword && logStatement.isPotentiallyRevealing()) {
- continue;
- }
- logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i));
- }
- 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,
- NUM_WORDS_KEY);
- final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA =
- new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */,
- 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(),
- getNumWords());
- } else {
- LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA.outputToLocked(jsonWriter,
- SystemClock.uptimeMillis(), getNumWords());
- }
- }
-
- final LogStatement LOGSTATEMENT_LOG_UNIT_END =
- new LogStatement(LOG_UNIT_END_KEY, false /* isPotentiallyPrivate */,
- false /* isPotentiallyRevealing */);
- private void outputLogUnitStop(final JsonWriter jsonWriter) {
- LOGSTATEMENT_LOG_UNIT_END.outputToLocked(jsonWriter, SystemClock.uptimeMillis());
- }
-
- /**
- * Mark the current logUnit as containing data to generate {@code newWords}.
- *
- * If {@code setWord()} was previously called for this LogUnit, then the method will try to
- * determine what kind of correction it is, and update its internal state of the correctionType
- * accordingly.
- *
- * @param newWords The words this LogUnit generates. Caller should not pass null or the empty
- * string.
- */
- public void setWords(final String newWords) {
- if (hasOneOrMoreWords()) {
- // The word was already set once, and it is now being changed. See if the new word
- // is close to the old word. If so, then the change is probably a typo correction.
- // If not, the user may have decided to enter a different word, so flag it.
- if (mSuggestedWords != null) {
- if (isInSuggestedWords(newWords, mSuggestedWords)) {
- mCorrectionType = CORRECTIONTYPE_TYPO;
- } else {
- mCorrectionType = CORRECTIONTYPE_DIFFERENT_WORD;
- }
- } else {
- // No suggested words, so it's not clear whether it's a typo or different word.
- // Mark it as a generic correction.
- mCorrectionType = CORRECTIONTYPE_CORRECTION;
- }
- } else {
- mCorrectionType = CORRECTIONTYPE_NO_CORRECTION;
- }
- mWords = newWords;
-
- // Update mWordArray
- mWordArray = (TextUtils.isEmpty(mWords)) ? EMPTY_STRING_ARRAY
- : WHITESPACE_PATTERN.split(mWords);
- if (mWordArray.length > 0 && TextUtils.isEmpty(mWordArray[0])) {
- // Empty string at beginning of array. Must have been whitespace at the start of the
- // word. Remove the empty string.
- mWordArray = Arrays.copyOfRange(mWordArray, 1, mWordArray.length);
- }
- }
-
- public String getWordsAsString() {
- return mWords;
- }
-
- /**
- * Retuns the words generated by the data in this LogUnit.
- *
- * The first word may be an empty string, if the data in the LogUnit started by generating
- * whitespace.
- *
- * @return the array of words. an empty list of there are no words associated with this LogUnit.
- */
- public String[] getWordsAsStringArray() {
- return mWordArray;
- }
-
- public boolean hasOneOrMoreWords() {
- return mWordArray.length >= 1;
- }
-
- public int getNumWords() {
- return mWordArray.length;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public void setMayContainDigit() {
- mMayContainDigit = true;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public boolean mayContainDigit() {
- return mMayContainDigit;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public void setContainsUserDeletions() {
- mContainsUserDeletions = true;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public boolean containsUserDeletions() {
- return mContainsUserDeletions;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public void setCorrectionType(final int correctionType) {
- mCorrectionType = correctionType;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public int getCorrectionType() {
- return mCorrectionType;
- }
-
- public boolean isEmpty() {
- return mLogStatementList.isEmpty();
- }
-
- /**
- * Split this logUnit, with all events before maxTime staying in the current logUnit, and all
- * events after maxTime going into a new LogUnit that is returned.
- */
- public LogUnit splitByTime(final long maxTime) {
- // Assume that mTimeList is in sorted order.
- final int length = mTimeList.size();
- // TODO: find time by binary search, e.g. using Collections#binarySearch()
- for (int index = 0; index < length; index++) {
- if (mTimeList.get(index) > maxTime) {
- final List<LogStatement> laterLogStatements =
- mLogStatementList.subList(index, length);
- final List<Object[]> laterValues = mValuesList.subList(index, length);
- final List<Long> laterTimes = mTimeList.subList(index, length);
-
- // Create the LogUnit containing the later logStatements and associated data.
- final LogUnit newLogUnit = new LogUnit(
- new ArrayList<LogStatement>(laterLogStatements),
- new ArrayList<Object[]>(laterValues),
- new ArrayList<Long>(laterTimes),
- true /* isPartOfMegaword */);
- newLogUnit.mWords = null;
- newLogUnit.mMayContainDigit = mMayContainDigit;
- newLogUnit.mContainsUserDeletions = mContainsUserDeletions;
-
- // Purge the logStatements and associated data from this LogUnit.
- laterLogStatements.clear();
- laterValues.clear();
- laterTimes.clear();
- mIsPartOfMegaword = true;
-
- return newLogUnit;
- }
- }
- return new LogUnit();
- }
-
- public void append(final LogUnit logUnit) {
- mLogStatementList.addAll(logUnit.mLogStatementList);
- mValuesList.addAll(logUnit.mValuesList);
- mTimeList.addAll(logUnit.mTimeList);
- mWords = null;
- if (logUnit.mWords != null) {
- setWords(logUnit.mWords);
- }
- mMayContainDigit = mMayContainDigit || logUnit.mMayContainDigit;
- mContainsUserDeletions = mContainsUserDeletions || logUnit.mContainsUserDeletions;
- mIsPartOfMegaword = false;
- }
-
- public SuggestedWords getSuggestions() {
- return mSuggestedWords;
- }
-
- /**
- * Initialize the suggestions.
- *
- * Once set to a non-null value, the suggestions may not be changed again. This is to keep
- * track of the list of words that are close to the user's initial effort to type the word.
- * Only words that are close to the initial effort are considered typo corrections.
- */
- public void initializeSuggestions(final SuggestedWords suggestedWords) {
- if (mSuggestedWords == null) {
- mSuggestedWords = suggestedWords;
- }
- }
-
- private static boolean isInSuggestedWords(final String queryWord,
- final SuggestedWords suggestedWords) {
- if (TextUtils.isEmpty(queryWord)) {
- return false;
- }
- final int size = suggestedWords.size();
- for (int i = 0; i < size; i++) {
- final SuggestedWordInfo wordInfo = suggestedWords.getInfo(i);
- if (queryWord.equals(wordInfo.mWord)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Remove data associated with selecting the Research button.
- *
- * A LogUnit will capture all user interactions with the IME, including the "meta-interactions"
- * of using the Research button to control the logging (e.g. by starting and stopping recording
- * of a test case). Because meta-interactions should not be part of the normal log, calling
- * this method will set a field in the LogStatements of the motion events to indiciate that
- * they should be disregarded.
- *
- * This implementation assumes that the data recorded by the meta-interaction takes the
- * form of all events following the first MotionEvent.ACTION_DOWN before the first long-press
- * before the last onCodeEvent containing a code matching {@code LogStatement.VALUE_RESEARCH}.
- *
- * @returns true if data was removed
- */
- public boolean removeResearchButtonInvocation() {
- // This method is designed to be idempotent.
-
- // First, find last invocation of "research" key
- final int indexOfLastResearchKey = findLastIndexContainingKeyValue(
- LogStatement.TYPE_POINTER_TRACKER_CALL_LISTENER_ON_CODE_INPUT,
- LogStatement.KEY_CODE, LogStatement.VALUE_RESEARCH);
- if (indexOfLastResearchKey < 0) {
- // Could not find invocation of "research" key. Leave log as is.
- if (DEBUG) {
- Log.d(TAG, "Could not find research key");
- }
- return false;
- }
-
- // Look for the long press that started the invocation of the research key code input.
- final int indexOfLastLongPressBeforeResearchKey =
- findLastIndexBefore(LogStatement.TYPE_MAIN_KEYBOARD_VIEW_ON_LONG_PRESS,
- indexOfLastResearchKey);
-
- // Look for DOWN event preceding the long press
- final int indexOfLastDownEventBeforeLongPress =
- findLastIndexContainingKeyValueBefore(LogStatement.TYPE_MOTION_EVENT,
- LogStatement.ACTION, LogStatement.VALUE_DOWN,
- indexOfLastLongPressBeforeResearchKey);
-
- // Flag all LatinKeyboardViewProcessMotionEvents from the DOWN event to the research key as
- // logging-related
- final int startingIndex = indexOfLastDownEventBeforeLongPress == -1 ? 0
- : indexOfLastDownEventBeforeLongPress;
- for (int index = startingIndex; index < indexOfLastResearchKey; index++) {
- final LogStatement logStatement = mLogStatementList.get(index);
- final String type = logStatement.getType();
- final Object[] values = mValuesList.get(index);
- if (type.equals(LogStatement.TYPE_MOTION_EVENT)) {
- logStatement.setValue(LogStatement.KEY_IS_LOGGING_RELATED, values, true);
- }
- }
- return true;
- }
-
- /**
- * Find the index of the last LogStatement before {@code startingIndex} of type {@code type}.
- *
- * @param queryType a String that must be {@code String.equals()} to the LogStatement type
- * @param startingIndex the index to start the backward search from. Must be less than the
- * length of mLogStatementList, or an IndexOutOfBoundsException is thrown. Can be negative,
- * in which case -1 is returned.
- *
- * @return The index of the last LogStatement, -1 if none exists.
- */
- private int findLastIndexBefore(final String queryType, final int startingIndex) {
- return findLastIndexContainingKeyValueBefore(queryType, null, null, startingIndex);
- }
-
- /**
- * Find the index of the last LogStatement before {@code startingIndex} of type {@code type}
- * containing the given key-value pair.
- *
- * @param queryType a String that must be {@code String.equals()} to the LogStatement type
- * @param queryKey a String that must be {@code String.equals()} to a key in the LogStatement
- * @param queryValue an Object that must be {@code String.equals()} to the key's corresponding
- * value
- *
- * @return The index of the last LogStatement, -1 if none exists.
- */
- private int findLastIndexContainingKeyValue(final String queryType, final String queryKey,
- final Object queryValue) {
- return findLastIndexContainingKeyValueBefore(queryType, queryKey, queryValue,
- mLogStatementList.size() - 1);
- }
-
- /**
- * Find the index of the last LogStatement before {@code startingIndex} of type {@code type}
- * containing the given key-value pair.
- *
- * @param queryType a String that must be {@code String.equals()} to the LogStatement type
- * @param queryKey a String that must be {@code String.equals()} to a key in the LogStatement
- * @param queryValue an Object that must be {@code String.equals()} to the key's corresponding
- * value
- * @param startingIndex the index to start the backward search from. Must be less than the
- * length of mLogStatementList, or an IndexOutOfBoundsException is thrown. Can be negative,
- * in which case -1 is returned.
- *
- * @return The index of the last LogStatement, -1 if none exists.
- */
- private int findLastIndexContainingKeyValueBefore(final String queryType, final String queryKey,
- final Object queryValue, final int startingIndex) {
- if (startingIndex < 0) {
- return -1;
- }
- for (int index = startingIndex; index >= 0; index--) {
- final LogStatement logStatement = mLogStatementList.get(index);
- final String type = logStatement.getType();
- if (type.equals(queryType) && (queryKey == null
- || logStatement.containsKeyValuePair(queryKey, queryValue,
- mValuesList.get(index)))) {
- return index;
- }
- }
- return -1;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/LoggingUtils.java b/java/src/com/android/inputmethod/research/LoggingUtils.java
deleted file mode 100644
index 1261d6780..000000000
--- a/java/src/com/android/inputmethod/research/LoggingUtils.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.view.MotionEvent;
-
-/* package */ class LoggingUtils {
- private LoggingUtils() {
- // This utility class is not publicly instantiable.
- }
-
- /* package */ static String getMotionEventActionTypeString(final int actionType) {
- switch (actionType) {
- case MotionEvent.ACTION_CANCEL: return "CANCEL";
- case MotionEvent.ACTION_UP: return "UP";
- case MotionEvent.ACTION_DOWN: return "DOWN";
- case MotionEvent.ACTION_POINTER_UP: return "POINTER_UP";
- case MotionEvent.ACTION_POINTER_DOWN: return "POINTER_DOWN";
- case MotionEvent.ACTION_MOVE: return "MOVE";
- case MotionEvent.ACTION_OUTSIDE: return "OUTSIDE";
- default: return "ACTION_" + actionType;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
deleted file mode 100644
index ffdb43c15..000000000
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.LinkedList;
-
-/**
- * MainLogBuffer is a FixedLogBuffer that tracks the state of LogUnits to make privacy guarantees.
- *
- * There are three forms of privacy protection: 1) only words in the main dictionary are allowed to
- * be logged in enough detail to determine their contents, 2) only a subset of words are logged
- * in detail, such as 10%, and 3) no numbers are logged.
- *
- * This class maintains a list of LogUnits, each corresponding to a word. As the user completes
- * words, they are added here. But if the user backs up over their current word to edit a word
- * entered earlier, then it is pulled out of this LogBuffer, changes are then added to the end of
- * the LogUnit, and it is pushed back in here when the user is done. Because words may be pulled
- * back out even after they are pushed in, we must not publish the contents of this LogBuffer too
- * quickly. However, we cannot let the contents pile up either, or it will limit the editing that
- * a user can perform.
- *
- * To balance these requirements (keep history so user can edit, flush history so it does not pile
- * up), the LogBuffer is considered "complete" when the user has entered enough words to form an
- * n-gram, followed by enough additional non-detailed words (that are in the 90%, as per above).
- * Once complete, the n-gram may be published to flash storage (via the ResearchLog class).
- * However, the additional non-detailed words are retained, in case the user backspaces to edit
- * them. The MainLogBuffer then continues to add words, publishing individual non-detailed words
- * as new words arrive. After enough non-detailed words have been pushed out to account for the
- * 90% between words, the words at the front of the LogBuffer can be published as an n-gram again.
- *
- * If the words that would form the valid n-gram are not in the dictionary, then words are pushed
- * through the LogBuffer one at a time until an n-gram is found that is entirely composed of
- * dictionary words.
- *
- * If the user closes a session, then the entire LogBuffer is flushed, publishing any embedded
- * n-gram containing dictionary words.
- */
-public abstract class MainLogBuffer extends FixedLogBuffer {
- private static final String TAG = MainLogBuffer.class.getSimpleName();
- 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;
-
- private final DictionaryFacilitatorForSuggest mDictionaryFacilitator;
- @UsedForTesting
- private Dictionary mDictionaryForTesting;
- private boolean mIsStopping = false;
-
- /* package for test */ int mNumWordsBetweenNGrams;
-
- // Counter for words left to suppress before an n-gram can be sampled. Reset to mMinWordPeriod
- // after a sample is taken.
- /* package for test */ int mNumWordsUntilSafeToSample;
-
- public MainLogBuffer(final int wordsBetweenSamples, final int numInitialWordsToIgnore,
- final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
- super(N_GRAM_SIZE + wordsBetweenSamples);
- mNumWordsBetweenNGrams = wordsBetweenSamples;
- mNumWordsUntilSafeToSample = DEBUG ? 0 : numInitialWordsToIgnore;
- mDictionaryFacilitator = dictionaryFacilitator;
- }
-
- @UsedForTesting
- /* package for test */ void setDictionaryForTesting(final Dictionary dictionary) {
- mDictionaryForTesting = dictionary;
- }
-
- private boolean isValidDictWord(final String word) {
- if (mDictionaryForTesting != null) {
- return mDictionaryForTesting.isValidWord(word);
- }
- if (mDictionaryFacilitator != null) {
- return mDictionaryFacilitator.isValidMainDictWord(word);
- }
- return false;
- }
-
- public void setIsStopping() {
- mIsStopping = true;
- }
-
- /**
- * 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.
- *
- * @return one of the {@code PUBLISHABILITY_*} result codes defined in this class.
- */
- private int getPublishabilityResultCode(final ArrayList<LogUnit> logUnits,
- final int nGramSize) {
- // Bypass privacy checks when debugging.
- if (ResearchLogger.IS_LOGGING_EVERYTHING) {
- if (mIsStopping) {
- return PUBLISHABILITY_UNPUBLISHABLE_STOPPING;
- }
- // Only check that it is the right length. If not, wait for later words to make
- // complete n-grams.
- int numWordsInLogUnitList = 0;
- final int length = logUnits.size();
- for (int i = 0; i < length; i++) {
- final LogUnit logUnit = logUnits.get(i);
- numWordsInLogUnitList += logUnit.getNumWords();
- }
- 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 PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY;
- }
- // Reload the dictionary in case it has changed (e.g., because the user has changed
- // languages).
- if ((mDictionaryFacilitator == null
- || !mDictionaryFacilitator.hasInitializedMainDictionary())
- && mDictionaryForTesting == null) {
- // Main dictionary is unavailable. Since we cannot check it, we cannot tell if a
- // word is out-of-vocabulary or not. Therefore, we must judge the entire buffer
- // contents to potentially pose a privacy risk.
- return PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE;
- }
-
- // Check each word in the buffer. If any word poses a privacy threat, we cannot upload
- // the complete buffer contents in detail.
- int numWordsInLogUnitList = 0;
- for (final LogUnit logUnit : logUnits) {
- if (!logUnit.hasOneOrMoreWords()) {
- // Digits outside words are a privacy threat.
- if (logUnit.mayContainDigit()) {
- return PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT;
- }
- } else {
- numWordsInLogUnitList += logUnit.getNumWords();
- final String[] words = logUnit.getWordsAsStringArray();
- for (final String word : words) {
- // Words not in the dictionary are a privacy threat.
- if (ResearchLogger.hasLetters(word) && !isValidDictWord(word)) {
- if (DEBUG) {
- Log.d(TAG, "\"" + word + "\" NOT SAFE!: hasLetters: "
- + ResearchLogger.hasLetters(word)
- + ", isValid: " + isValidDictWord(word));
- }
- return PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY;
- }
- }
- }
- }
-
- // Finally, only return true if the ngram is the right size.
- if (numWordsInLogUnitList == nGramSize) {
- return PUBLISHABILITY_PUBLISHABLE;
- } else {
- return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT;
- }
- }
-
- public void shiftAndPublishAll() throws IOException {
- final LinkedList<LogUnit> logUnits = getLogUnits();
- while (!logUnits.isEmpty()) {
- publishLogUnitsAtFrontOfBuffer();
- }
- }
-
- @Override
- protected final void onBufferFull() {
- try {
- publishLogUnitsAtFrontOfBuffer();
- } catch (final IOException e) {
- if (DEBUG) {
- Log.w(TAG, "IOException when publishing front of LogBuffer", e);
- }
- }
- }
-
- /**
- * 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);
- 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);
- mNumWordsUntilSafeToSample = mNumWordsBetweenNGrams;
- return;
- }
- // No good n-gram at front, and buffer is full. Shift out up through the first logUnit
- // with associated words (or if there is none, all the existing logUnits).
- logUnits.clear();
- LogUnit logUnit = shiftOut();
- while (logUnit != null) {
- logUnits.add(logUnit);
- final int numWords = logUnit.getNumWords();
- if (numWords > 0) {
- mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWords);
- break;
- }
- logUnit = shiftOut();
- }
- publish(logUnits, false /* canIncludePrivateData */);
- }
-
- /**
- * Called when a list of logUnits should be published.
- *
- * It is the subclass's responsibility to implement the publication.
- *
- * @param logUnits The list of logUnits to be published.
- * @param canIncludePrivateData Whether the private data in the logUnits can be included in
- * publication.
- *
- * @throws IOException if publication to the log file is not possible
- */
- protected abstract void publish(final ArrayList<LogUnit> logUnits,
- final boolean canIncludePrivateData) throws IOException;
-
- @Override
- protected int shiftOutWords(final int numWords) {
- final int numWordsShiftedOut = super.shiftOutWords(numWords);
- mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWordsShiftedOut);
- if (DEBUG) {
- Log.d(TAG, "wordsUntilSafeToSample now at " + mNumWordsUntilSafeToSample);
- }
- return numWordsShiftedOut;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java
deleted file mode 100644
index 3388645b7..000000000
--- a/java/src/com/android/inputmethod/research/MotionEventReader.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.util.JsonReader;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-
-public class MotionEventReader {
- private static final String TAG = MotionEventReader.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- // Assumes that MotionEvent.ACTION_MASK does not have all bits set.`
- private static final int UNINITIALIZED_ACTION = ~MotionEvent.ACTION_MASK;
- // No legitimate int is negative
- private static final int UNINITIALIZED_INT = -1;
- // No legitimate long is negative
- private static final long UNINITIALIZED_LONG = -1L;
- // No legitimate float is negative
- private static final float UNINITIALIZED_FLOAT = -1.0f;
-
- public ReplayData readMotionEventData(final File file) {
- final ReplayData replayData = new ReplayData();
- try {
- // Read file
- final JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader(
- new FileInputStream(file))));
- jsonReader.beginArray();
- while (jsonReader.hasNext()) {
- readLogStatement(jsonReader, replayData);
- }
- jsonReader.endArray();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return replayData;
- }
-
- @UsedForTesting
- static class ReplayData {
- final ArrayList<Integer> mActions = new ArrayList<Integer>();
- final ArrayList<PointerProperties[]> mPointerPropertiesArrays
- = new ArrayList<PointerProperties[]>();
- final ArrayList<PointerCoords[]> mPointerCoordsArrays = new ArrayList<PointerCoords[]>();
- final ArrayList<Long> mTimes = new ArrayList<Long>();
- }
-
- /**
- * Read motion data from a logStatement and store it in {@code replayData}.
- *
- * Two kinds of logStatements can be read. In the first variant, the MotionEvent data is
- * represented as attributes at the top level like so:
- *
- * <pre>
- * {
- * "_ct": 1359590400000,
- * "_ut": 4381933,
- * "_ty": "MotionEvent",
- * "action": "UP",
- * "isLoggingRelated": false,
- * "x": 100,
- * "y": 200
- * }
- * </pre>
- *
- * In the second variant, there is a separate attribute for the MotionEvent that includes
- * historical data if present:
- *
- * <pre>
- * {
- * "_ct": 135959040000,
- * "_ut": 4382702,
- * "_ty": "MotionEvent",
- * "action": "MOVE",
- * "isLoggingRelated": false,
- * "motionEvent": {
- * "pointerIds": [
- * 0
- * ],
- * "xyt": [
- * {
- * "t": 4382551,
- * "d": [
- * {
- * "x": 141.25,
- * "y": 151.8485107421875,
- * "toma": 101.82337188720703,
- * "tomi": 101.82337188720703,
- * "o": 0.0
- * }
- * ]
- * },
- * {
- * "t": 4382559,
- * "d": [
- * {
- * "x": 140.7266082763672,
- * "y": 151.8485107421875,
- * "toma": 101.82337188720703,
- * "tomi": 101.82337188720703,
- * "o": 0.0
- * }
- * ]
- * }
- * ]
- * }
- * },
- * </pre>
- */
- @UsedForTesting
- /* package for test */ void readLogStatement(final JsonReader jsonReader,
- final ReplayData replayData) throws IOException {
- String logStatementType = null;
- int actionType = UNINITIALIZED_ACTION;
- int x = UNINITIALIZED_INT;
- int y = UNINITIALIZED_INT;
- long time = UNINITIALIZED_LONG;
- boolean isLoggingRelated = false;
-
- jsonReader.beginObject();
- while (jsonReader.hasNext()) {
- final String key = jsonReader.nextName();
- if (key.equals("_ty")) {
- logStatementType = jsonReader.nextString();
- } else if (key.equals("_ut")) {
- time = jsonReader.nextLong();
- } else if (key.equals("x")) {
- x = jsonReader.nextInt();
- } else if (key.equals("y")) {
- y = jsonReader.nextInt();
- } else if (key.equals("action")) {
- final String s = jsonReader.nextString();
- if (s.equals("UP")) {
- actionType = MotionEvent.ACTION_UP;
- } else if (s.equals("DOWN")) {
- actionType = MotionEvent.ACTION_DOWN;
- } else if (s.equals("MOVE")) {
- actionType = MotionEvent.ACTION_MOVE;
- }
- } else if (key.equals("loggingRelated")) {
- isLoggingRelated = jsonReader.nextBoolean();
- } else if (logStatementType != null && logStatementType.equals("MotionEvent")
- && key.equals("motionEvent")) {
- if (actionType == UNINITIALIZED_ACTION) {
- Log.e(TAG, "no actionType assigned in MotionEvent json");
- }
- // Second variant of LogStatement.
- if (isLoggingRelated) {
- jsonReader.skipValue();
- } else {
- readEmbeddedMotionEvent(jsonReader, replayData, actionType);
- }
- } else {
- if (DEBUG) {
- Log.w(TAG, "Unknown JSON key in LogStatement: " + key);
- }
- jsonReader.skipValue();
- }
- }
- jsonReader.endObject();
-
- if (logStatementType != null && time != UNINITIALIZED_LONG && x != UNINITIALIZED_INT
- && y != UNINITIALIZED_INT && actionType != UNINITIALIZED_ACTION
- && logStatementType.equals("MotionEvent") && !isLoggingRelated) {
- // First variant of LogStatement.
- final PointerProperties pointerProperties = new PointerProperties();
- pointerProperties.id = 0;
- pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
- final PointerProperties[] pointerPropertiesArray = {
- pointerProperties
- };
- final PointerCoords pointerCoords = new PointerCoords();
- pointerCoords.x = x;
- pointerCoords.y = y;
- pointerCoords.pressure = 1.0f;
- pointerCoords.size = 1.0f;
- final PointerCoords[] pointerCoordsArray = {
- pointerCoords
- };
- addMotionEventData(replayData, actionType, time, pointerPropertiesArray,
- pointerCoordsArray);
- }
- }
-
- private void readEmbeddedMotionEvent(final JsonReader jsonReader, final ReplayData replayData,
- final int actionType) throws IOException {
- jsonReader.beginObject();
- PointerProperties[] pointerPropertiesArray = null;
- while (jsonReader.hasNext()) { // pointerIds/xyt
- final String name = jsonReader.nextName();
- if (name.equals("pointerIds")) {
- pointerPropertiesArray = readPointerProperties(jsonReader);
- } else if (name.equals("xyt")) {
- readPointerData(jsonReader, replayData, actionType, pointerPropertiesArray);
- }
- }
- jsonReader.endObject();
- }
-
- private PointerProperties[] readPointerProperties(final JsonReader jsonReader)
- throws IOException {
- final ArrayList<PointerProperties> pointerPropertiesArrayList =
- new ArrayList<PointerProperties>();
- jsonReader.beginArray();
- while (jsonReader.hasNext()) {
- final PointerProperties pointerProperties = new PointerProperties();
- pointerProperties.id = jsonReader.nextInt();
- pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
- pointerPropertiesArrayList.add(pointerProperties);
- }
- jsonReader.endArray();
- return pointerPropertiesArrayList.toArray(
- new PointerProperties[pointerPropertiesArrayList.size()]);
- }
-
- private void readPointerData(final JsonReader jsonReader, final ReplayData replayData,
- final int actionType, final PointerProperties[] pointerPropertiesArray)
- throws IOException {
- if (pointerPropertiesArray == null) {
- Log.e(TAG, "PointerIDs must be given before xyt data in json for MotionEvent");
- jsonReader.skipValue();
- return;
- }
- long time = UNINITIALIZED_LONG;
- jsonReader.beginArray();
- while (jsonReader.hasNext()) { // Array of historical data
- jsonReader.beginObject();
- final ArrayList<PointerCoords> pointerCoordsArrayList = new ArrayList<PointerCoords>();
- while (jsonReader.hasNext()) { // Time/data object
- final String name = jsonReader.nextName();
- if (name.equals("t")) {
- time = jsonReader.nextLong();
- } else if (name.equals("d")) {
- jsonReader.beginArray();
- while (jsonReader.hasNext()) { // array of data per pointer
- final PointerCoords pointerCoords = readPointerCoords(jsonReader);
- if (pointerCoords != null) {
- pointerCoordsArrayList.add(pointerCoords);
- }
- }
- jsonReader.endArray();
- } else {
- jsonReader.skipValue();
- }
- }
- jsonReader.endObject();
- // Data was recorded as historical events, but must be split apart into
- // separate MotionEvents for replaying
- if (time != UNINITIALIZED_LONG) {
- addMotionEventData(replayData, actionType, time, pointerPropertiesArray,
- pointerCoordsArrayList.toArray(
- new PointerCoords[pointerCoordsArrayList.size()]));
- } else {
- Log.e(TAG, "Time not assigned in json for MotionEvent");
- }
- }
- jsonReader.endArray();
- }
-
- private PointerCoords readPointerCoords(final JsonReader jsonReader) throws IOException {
- jsonReader.beginObject();
- float x = UNINITIALIZED_FLOAT;
- float y = UNINITIALIZED_FLOAT;
- while (jsonReader.hasNext()) { // x,y
- final String name = jsonReader.nextName();
- if (name.equals("x")) {
- x = (float) jsonReader.nextDouble();
- } else if (name.equals("y")) {
- y = (float) jsonReader.nextDouble();
- } else {
- jsonReader.skipValue();
- }
- }
- jsonReader.endObject();
-
- if (Float.compare(x, UNINITIALIZED_FLOAT) == 0
- || Float.compare(y, UNINITIALIZED_FLOAT) == 0) {
- Log.w(TAG, "missing x or y value in MotionEvent json");
- return null;
- }
- final PointerCoords pointerCoords = new PointerCoords();
- pointerCoords.x = x;
- pointerCoords.y = y;
- pointerCoords.pressure = 1.0f;
- pointerCoords.size = 1.0f;
- return pointerCoords;
- }
-
- private void addMotionEventData(final ReplayData replayData, final int actionType,
- final long time, final PointerProperties[] pointerProperties,
- final PointerCoords[] pointerCoords) {
- replayData.mActions.add(actionType);
- replayData.mTimes.add(time);
- replayData.mPointerPropertiesArrays.add(pointerProperties);
- replayData.mPointerCoordsArrays.add(pointerCoords);
- }
-}
diff --git a/java/src/com/android/inputmethod/research/Replayer.java b/java/src/com/android/inputmethod/research/Replayer.java
deleted file mode 100644
index 903875f3c..000000000
--- a/java/src/com/android/inputmethod/research/Replayer.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.MainKeyboardView;
-import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.research.MotionEventReader.ReplayData;
-
-/**
- * Replays a sequence of motion events in realtime on the screen.
- *
- * Useful for user inspection of logged data.
- */
-public class Replayer {
- private static final String TAG = Replayer.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- private static final long START_TIME_DELAY_MS = 500;
-
- private boolean mIsReplaying = false;
- private KeyboardSwitcher mKeyboardSwitcher;
-
- private Replayer() {
- }
-
- private static final Replayer sInstance = new Replayer();
- public static Replayer getInstance() {
- return sInstance;
- }
-
- public void setKeyboardSwitcher(final KeyboardSwitcher keyboardSwitcher) {
- mKeyboardSwitcher = keyboardSwitcher;
- }
-
- private static final int MSG_MOTION_EVENT = 0;
- private static final int MSG_DONE = 1;
- private static final int COMPLETION_TIME_MS = 500;
-
- // TODO: Support historical events and multi-touch.
- public void replay(final ReplayData replayData, final Runnable callback) {
- if (mIsReplaying) {
- return;
- }
- mIsReplaying = true;
- final int numActions = replayData.mActions.size();
- if (DEBUG) {
- Log.d(TAG, "replaying " + numActions + " actions");
- }
- if (numActions == 0) {
- mIsReplaying = false;
- return;
- }
- final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
-
- // The reference time relative to the times stored in events.
- final long origStartTime = replayData.mTimes.get(0);
- // The reference time relative to which events are replayed in the present.
- final long currentStartTime = SystemClock.uptimeMillis() + START_TIME_DELAY_MS;
- // The adjustment needed to translate times from the original recorded time to the current
- // time.
- final long timeAdjustment = currentStartTime - origStartTime;
- final Handler handler = new Handler(Looper.getMainLooper()) {
- // Track the time of the most recent DOWN event, to be passed as a parameter when
- // constructing a MotionEvent. It's initialized here to the origStartTime, but this is
- // only a precaution. The value should be overwritten by the first ACTION_DOWN event
- // before the first use of the variable. Note that this may cause the first few events
- // to have incorrect {@code downTime}s.
- private long mOrigDownTime = origStartTime;
-
- @Override
- public void handleMessage(final Message msg) {
- switch (msg.what) {
- case MSG_MOTION_EVENT:
- final int index = msg.arg1;
- final int action = replayData.mActions.get(index);
- final PointerProperties[] pointerPropertiesArray =
- replayData.mPointerPropertiesArrays.get(index);
- final PointerCoords[] pointerCoordsArray =
- replayData.mPointerCoordsArrays.get(index);
- final long origTime = replayData.mTimes.get(index);
- if (action == MotionEvent.ACTION_DOWN) {
- mOrigDownTime = origTime;
- }
-
- final MotionEvent me = MotionEvent.obtain(mOrigDownTime + timeAdjustment,
- origTime + timeAdjustment, action,
- pointerPropertiesArray.length, pointerPropertiesArray,
- pointerCoordsArray, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0);
- mainKeyboardView.processMotionEvent(me);
- me.recycle();
- break;
- case MSG_DONE:
- mIsReplaying = false;
- ResearchLogger.getInstance().requestIndicatorRedraw();
- break;
- }
- }
- };
-
- handler.post(new Runnable() {
- @Override
- public void run() {
- ResearchLogger.getInstance().requestIndicatorRedraw();
- }
- });
- for (int i = 0; i < numActions; i++) {
- final Message msg = Message.obtain(handler, MSG_MOTION_EVENT, i, 0);
- final long msgTime = replayData.mTimes.get(i) + timeAdjustment;
- handler.sendMessageAtTime(msg, msgTime);
- if (DEBUG) {
- Log.d(TAG, "queuing event at " + msgTime);
- }
- }
-
- final long presentDoneTime = replayData.mTimes.get(numActions - 1) + timeAdjustment
- + COMPLETION_TIME_MS;
- handler.sendMessageAtTime(Message.obtain(handler, MSG_DONE), presentDoneTime);
- if (callback != null) {
- handler.postAtTime(callback, presentDoneTime + 1);
- }
- }
-
- public boolean isReplaying() {
- return mIsReplaying;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ReplayerService.java b/java/src/com/android/inputmethod/research/ReplayerService.java
deleted file mode 100644
index 88d9033cf..000000000
--- a/java/src/com/android/inputmethod/research/ReplayerService.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.app.IntentService;
-import android.content.Intent;
-import android.util.Log;
-
-import com.android.inputmethod.research.MotionEventReader.ReplayData;
-
-import java.io.File;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Provide a mechanism to invoke the replayer from outside.
- *
- * In particular, makes access from a host possible through {@code adb am startservice}.
- */
-public class ReplayerService extends IntentService {
- private static final String TAG = ReplayerService.class.getSimpleName();
- private static final String EXTRA_FILENAME = "com.android.inputmethod.research.extra.FILENAME";
- private static final long MAX_REPLAY_TIME = TimeUnit.SECONDS.toMillis(60);
-
- public ReplayerService() {
- super(ReplayerService.class.getSimpleName());
- }
-
- @Override
- protected void onHandleIntent(final Intent intent) {
- final String filename = intent.getStringExtra(EXTRA_FILENAME);
- if (filename == null) return;
-
- final ReplayData replayData = new MotionEventReader().readMotionEventData(
- new File(filename));
- synchronized (this) {
- Replayer.getInstance().replay(replayData, new Runnable() {
- @Override
- public void run() {
- synchronized (ReplayerService.this) {
- ReplayerService.this.notify();
- }
- }
- });
- try {
- wait(MAX_REPLAY_TIME);
- } catch (InterruptedException e) {
- Log.e(TAG, "Timeout while replaying.", e);
- }
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
deleted file mode 100644
index 46e620ae5..000000000
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.content.Context;
-import android.util.JsonWriter;
-import android.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Logs the use of the LatinIME keyboard.
- *
- * This class logs operations on the IME keyboard, including what the user has typed. Data is
- * written to a {@link JsonWriter}, which will write to a local file.
- *
- * The JsonWriter is created on-demand by calling {@link #getInitializedJsonWriterLocked}.
- *
- * This class uses an executor to perform file-writing operations on a separate thread. It also
- * tries to avoid creating unnecessary files if there is nothing to write. It also handles
- * flushing, making sure it happens, but not too frequently.
- *
- * This functionality is off by default. See
- * {@link ProductionFlag#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}.
- */
-public class ResearchLog {
- // TODO: Automatically initialize the JsonWriter rather than requiring the caller to manage it.
- 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 = TimeUnit.SECONDS.toMillis(5);
-
- /* package */ final ScheduledExecutorService mExecutor;
- /* package */ final File mFile;
- private final Context mContext;
-
- // Earlier implementations used a dummy JsonWriter that just swallowed what it was given, but
- // this was tricky to do well, because JsonWriter throws an exception if it is passed more than
- // one top-level object.
- private JsonWriter mJsonWriter = null;
-
- // true if at least one byte of data has been written out to the log file. This must be
- // remembered because JsonWriter requires that calls matching calls to beginObject and
- // endObject, as well as beginArray and endArray, and the file is opened lazily, only when
- // it is certain that data will be written. Alternatively, the matching call exceptions
- // could be caught, but this might suppress other errors.
- private boolean mHasWrittenData = false;
-
- public ResearchLog(final File outputFile, final Context context) {
- mExecutor = Executors.newSingleThreadScheduledExecutor();
- mFile = outputFile;
- mContext = context;
- }
-
- /**
- * 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.
- *
- * See class comment for details about {@code JsonWriter} construction.
- *
- * @param onClosed run after the close() operation has completed asynchronously
- */
- private synchronized void close(final Runnable onClosed) {
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- try {
- if (mJsonWriter == null) return null;
- // TODO: This is necessary to avoid an exception. Better would be to not even
- // open the JsonWriter if the file is not even opened unless there is valid data
- // to write.
- if (!mHasWrittenData) {
- mJsonWriter.beginArray();
- }
- mJsonWriter.endArray();
- mHasWrittenData = false;
- mJsonWriter.flush();
- mJsonWriter.close();
- if (DEBUG) {
- Log.d(TAG, "closed " + mFile);
- }
- } catch (final Exception e) {
- Log.d(TAG, "error when closing ResearchLog:", e);
- } finally {
- // Marking the file as read-only signals that this log file is ready to be
- // uploaded.
- if (mFile != null && mFile.exists()) {
- mFile.setWritable(false, false);
- }
- if (onClosed != null) {
- onClosed.run();
- }
- }
- return null;
- }
- });
- removeAnyScheduledFlush();
- mExecutor.shutdown();
- }
-
- /**
- * Block until the research log has shut down and spooled out all output or {@code timeout}
- * occurs.
- *
- * @param timeout time to wait for close in milliseconds
- */
- public void blockingClose(final long timeout) {
- close(null);
- awaitTermination(timeout, TimeUnit.MILLISECONDS);
- }
-
- /**
- * Waits for publication requests to finish, closes the JsonWriter, but then deletes the backing
- * output file.
- *
- * @param onAbort run after the abort() operation has completed asynchronously
- */
- private synchronized void abort(final Runnable onAbort) {
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- try {
- if (mJsonWriter == null) return null;
- if (mHasWrittenData) {
- // TODO: This is necessary to avoid an exception. Better would be to not
- // even open the JsonWriter if the file is not even opened unless there is
- // valid data to write.
- if (!mHasWrittenData) {
- mJsonWriter.beginArray();
- }
- mJsonWriter.endArray();
- mJsonWriter.close();
- mHasWrittenData = false;
- }
- } finally {
- if (mFile != null) {
- mFile.delete();
- }
- if (onAbort != null) {
- onAbort.run();
- }
- }
- return null;
- }
- });
- removeAnyScheduledFlush();
- mExecutor.shutdown();
- }
-
- /**
- * Block until the research log has aborted or {@code timeout} occurs.
- *
- * @param timeout time to wait for close in milliseconds
- */
- public void blockingAbort(final long timeout) {
- abort(null);
- awaitTermination(timeout, TimeUnit.MILLISECONDS);
- }
-
- @UsedForTesting
- public void awaitTermination(final long delay, final TimeUnit timeUnit) {
- try {
- if (!mExecutor.awaitTermination(delay, timeUnit)) {
- Log.e(TAG, "ResearchLog executor timed out while awaiting terminaion");
- }
- } catch (final InterruptedException e) {
- Log.e(TAG, "ResearchLog executor interrupted while awaiting terminaion", e);
- }
- }
-
- /* package */ synchronized void flush() {
- removeAnyScheduledFlush();
- mExecutor.submit(mFlushCallable);
- }
-
- private final Callable<Object> mFlushCallable = new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- if (mJsonWriter != null) mJsonWriter.flush();
- return null;
- }
- };
-
- private ScheduledFuture<Object> mFlushFuture;
-
- private void removeAnyScheduledFlush() {
- if (mFlushFuture != null) {
- mFlushFuture.cancel(false);
- mFlushFuture = null;
- }
- }
-
- private void scheduleFlush() {
- removeAnyScheduledFlush();
- mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS);
- }
-
- /**
- * Queues up {@code logUnit} to be published in the background.
- *
- * @param logUnit the {@link LogUnit} to be published
- * @param canIncludePrivateData whether private data in the LogUnit should be included
- */
- public synchronized void publish(final LogUnit logUnit, final boolean canIncludePrivateData) {
- try {
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- logUnit.publishTo(ResearchLog.this, canIncludePrivateData);
- scheduleFlush();
- return null;
- }
- });
- } catch (final RejectedExecutionException e) {
- // TODO: Add code to record loss of data, and report.
- if (DEBUG) {
- Log.d(TAG, "ResearchLog.publish() rejecting scheduled execution", e);
- }
- }
- }
-
- /**
- * Return a JsonWriter for this ResearchLog. It is initialized the first time this method is
- * called. The cached value is returned in future calls.
- *
- * @throws IOException if opening the JsonWriter is not possible
- */
- public JsonWriter getInitializedJsonWriterLocked() throws IOException {
- if (mJsonWriter != null) return mJsonWriter;
- if (mFile == null) throw new FileNotFoundException();
- try {
- final JsonWriter jsonWriter = createJsonWriter(mContext, mFile);
- if (jsonWriter == null) throw new IOException("Could not create JsonWriter");
-
- jsonWriter.beginArray();
- mJsonWriter = jsonWriter;
- mHasWrittenData = true;
- return mJsonWriter;
- } catch (final IOException e) {
- if (DEBUG) {
- Log.w(TAG, "Exception when creating JsonWriter", e);
- Log.w(TAG, "Closing JsonWriter");
- }
- if (mJsonWriter != null) mJsonWriter.close();
- mJsonWriter = null;
- throw e;
- }
- }
-
- /**
- * Create the JsonWriter to write the ResearchLog to.
- *
- * This method may be overriden in testing to redirect the output.
- */
- /* package for test */ JsonWriter createJsonWriter(final Context context, final File file)
- throws IOException {
- return new JsonWriter(new BufferedWriter(new OutputStreamWriter(
- context.openFileOutput(file.getName(), Context.MODE_PRIVATE))));
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogDirectory.java b/java/src/com/android/inputmethod/research/ResearchLogDirectory.java
deleted file mode 100644
index d156068d6..000000000
--- a/java/src/com/android/inputmethod/research/ResearchLogDirectory.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.content.Context;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileFilter;
-
-/**
- * Manages log files.
- *
- * This class handles all aspects where and how research log data is stored. This includes
- * generating log filenames in the correct place with the correct names, and cleaning up log files
- * under this directory.
- */
-public class ResearchLogDirectory {
- public static final String TAG = ResearchLogDirectory.class.getSimpleName();
- /* package */ static final String LOG_FILENAME_PREFIX = "researchLog";
- private static final String FILENAME_SUFFIX = ".txt";
- private static final String USER_RECORDING_FILENAME_PREFIX = "recording";
-
- private static final ReadOnlyLogFileFilter sUploadableLogFileFilter =
- new ReadOnlyLogFileFilter();
-
- private final File mFilesDir;
-
- static class ReadOnlyLogFileFilter implements FileFilter {
- @Override
- public boolean accept(final File pathname) {
- return pathname.getName().startsWith(ResearchLogDirectory.LOG_FILENAME_PREFIX)
- && !pathname.canWrite();
- }
- }
-
- /**
- * Creates a new ResearchLogDirectory, creating the storage directory if it does not exist.
- */
- public ResearchLogDirectory(final Context context) {
- mFilesDir = getLoggingDirectory(context);
- if (mFilesDir == null) {
- throw new NullPointerException("No files directory specified");
- }
- if (!mFilesDir.exists()) {
- mFilesDir.mkdirs();
- }
- }
-
- private File getLoggingDirectory(final Context context) {
- // TODO: Switch to using a subdirectory of getFilesDir().
- return context.getFilesDir();
- }
-
- /**
- * Get an array of log files that are ready for uploading.
- *
- * A file is ready for uploading if it is marked as read-only.
- *
- * @return the array of uploadable files
- */
- public File[] getUploadableLogFiles() {
- try {
- return mFilesDir.listFiles(sUploadableLogFileFilter);
- } catch (final SecurityException e) {
- Log.e(TAG, "Could not cleanup log directory, permission denied", e);
- return new File[0];
- }
- }
-
- public void cleanupLogFilesOlderThan(final long time) {
- try {
- for (final File file : mFilesDir.listFiles()) {
- final String filename = file.getName();
- if ((filename.startsWith(LOG_FILENAME_PREFIX)
- || filename.startsWith(USER_RECORDING_FILENAME_PREFIX))
- && (file.lastModified() < time)) {
- file.delete();
- }
- }
- } catch (final SecurityException e) {
- Log.e(TAG, "Could not cleanup log directory, permission denied", e);
- }
- }
-
- public File getLogFilePath(final long time, final long nanoTime) {
- return new File(mFilesDir, getUniqueFilename(LOG_FILENAME_PREFIX, time, nanoTime));
- }
-
- public File getUserRecordingFilePath(final long time, final long nanoTime) {
- return new File(mFilesDir, getUniqueFilename(USER_RECORDING_FILENAME_PREFIX, time,
- nanoTime));
- }
-
- private static String getUniqueFilename(final String prefix, final long time,
- final long nanoTime) {
- return prefix + "-" + time + "-" + nanoTime + FILENAME_SUFFIX;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
deleted file mode 100644
index d907dd1b0..000000000
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ /dev/null
@@ -1,1885 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.SystemClock;
-import android.preference.PreferenceManager;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.widget.Toast;
-
-import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.KeyboardView;
-import com.android.inputmethod.keyboard.MainKeyboardView;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
-import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.RichInputConnection;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.latin.utils.InputTypeUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
-import com.android.inputmethod.latin.utils.TextRange;
-import com.android.inputmethod.research.MotionEventReader.ReplayData;
-import com.android.inputmethod.research.ui.SplashScreen;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
-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.
- *
- * This class logs operations on the IME keyboard, including what the user has typed.
- * Data is stored locally in a file in app-specific storage.
- *
- * This functionality is off by default. See
- * {@link ProductionFlag#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}.
- */
-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 feedback screen code into separate class.
- // TODO: Refactor. Move logging invocations into their own class.
- // TODO: Refactor. Move currentLogUnit management into separate class.
- private static final String TAG = ResearchLogger.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- private static final boolean DEBUG_REPLAY_AFTER_FEEDBACK = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- // Whether the feedback dialog preserves the editable text across invocations. Should be false
- // for normal research builds so users do not have to delete the same feedback string they
- // entered earlier. Should be true for builds internal to a development team so when the text
- // field holds a channel name, the developer does not have to re-enter it when using the
- // feedback mechanism to generate multiple tests.
- private static final boolean FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD = false;
- /* package */ static boolean sIsLogging = false;
- private static final int OUTPUT_FORMAT_VERSION = 6;
- // Whether all words should be recorded, leaving unsampled word between bigrams. Useful for
- // testing.
- /* package for test */ static final boolean IS_LOGGING_EVERYTHING = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- // The number of words between n-grams to omit from the log.
- private static final int NUMBER_OF_WORDS_BETWEEN_SAMPLES =
- IS_LOGGING_EVERYTHING ? 0 : (DEBUG ? 2 : 18);
-
- // Whether to show an indicator on the screen that logging is on. Currently a very small red
- // dot in the lower right hand corner. Most users should not notice it.
- private static final boolean IS_SHOWING_INDICATOR = true;
- // Change the default indicator to something very visible. Currently two red vertical bars on
- // either side of they keyboard.
- private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false ||
- (IS_LOGGING_EVERYTHING && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG);
- // FEEDBACK_WORD_BUFFER_SIZE should add 1 because it must also hold the feedback LogUnit itself.
- public static final int FEEDBACK_WORD_BUFFER_SIZE = (Integer.MAX_VALUE - 1) + 1;
-
- // The special output text to invoke a research feedback dialog.
- public static final String RESEARCH_KEY_OUTPUT_TEXT = ".research.";
-
- // constants related to specific log points
- private static final int[] WHITESPACE_SEPARATORS =
- StringUtils.toSortedCodePointArray(" \t\n\r");
- private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
- private static final String PREF_RESEARCH_SAVED_CHANNEL = "pref_research_saved_channel";
-
- 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;
- private static String sAllowedAccountDomain = null;
- private ResearchLog mMainResearchLog; // always non-null after init() is called
- // mFeedbackLog records all events for the session, private or not (excepting
- // passwords). It is written to permanent storage only if the user explicitly commands
- // the system to do so.
- // LogUnits are queued in the LogBuffers and published to the ResearchLogs when words are
- // complete.
- /* package for test */ MainLogBuffer mMainLogBuffer; // always non-null after init() is called
- /* package */ ResearchLog mUserRecordingLog;
- /* package */ LogBuffer mUserRecordingLogBuffer;
- private File mUserRecordingFile = null;
-
- private boolean mIsPasswordView = false;
- private SharedPreferences mPrefs;
-
- // digits entered by the user are replaced with this codepoint.
- /* package for test */ static final int DIGIT_REPLACEMENT_CODEPOINT =
- Character.codePointAt("\uE000", 0); // U+E000 is in the "private-use area"
- // U+E001 is in the "private-use area"
- /* package for test */ static final String WORD_REPLACEMENT_STRING = "\uE001";
- protected static final int SUSPEND_DURATION_IN_MINUTES = 1;
-
- // used to check whether words are not unique
- private DictionaryFacilitatorForSuggest mDictionaryFacilitator;
- private MainKeyboardView mMainKeyboardView;
- // TODO: Check whether a superclass can be used instead of LatinIME.
- /* package for test */ LatinIME mLatinIME;
- private final Statistics mStatistics;
- private final MotionEventReader mMotionEventReader = new MotionEventReader();
- private final Replayer mReplayer = Replayer.getInstance();
- private ResearchLogDirectory mResearchLogDirectory;
- private SplashScreen mSplashScreen;
-
- private Intent mUploadNowIntent;
-
- /* package for test */ LogUnit mCurrentLogUnit = new LogUnit();
-
- // Gestured or tapped words may be committed after the gesture of the next word has started.
- // To ensure that the gesture data of the next word is not associated with the previous word,
- // thereby leaking private data, we store the time of the down event that started the second
- // 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 = 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();
- }
-
- public static ResearchLogger getInstance() {
- return sInstance;
- }
-
- public void init(final LatinIME latinIME, final KeyboardSwitcher keyboardSwitcher) {
- assert latinIME != null;
- mLatinIME = latinIME;
- mPrefs = PreferenceManager.getDefaultSharedPreferences(latinIME);
- mPrefs.registerOnSharedPreferenceChangeListener(this);
-
- // Initialize fields from preferences
- sIsLogging = ResearchSettings.readResearchLoggerEnabledFlag(mPrefs);
-
- // Initialize fields from resources
- final Resources res = latinIME.getResources();
- sAccountType = res.getString(R.string.research_account_type);
- sAllowedAccountDomain = res.getString(R.string.research_allowed_account_domain);
-
- // Initialize directory manager
- mResearchLogDirectory = new ResearchLogDirectory(mLatinIME);
- cleanLogDirectoryIfNeeded(mResearchLogDirectory, System.currentTimeMillis());
-
- // Initialize log buffers
- resetLogBuffers();
-
- // Initialize external services
- mUploadNowIntent = new Intent(mLatinIME, UploaderService.class);
- mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- UploaderService.cancelAndRescheduleUploadingService(mLatinIME,
- true /* needsRescheduling */);
- }
- mReplayer.setKeyboardSwitcher(keyboardSwitcher);
- }
-
- private void resetLogBuffers() {
- mMainResearchLog = new ResearchLog(mResearchLogDirectory.getLogFilePath(
- System.currentTimeMillis(), System.nanoTime()), mLatinIME);
- final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1);
- mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore,
- mDictionaryFacilitator) {
- @Override
- protected void publish(final ArrayList<LogUnit> logUnits,
- boolean canIncludePrivateData) {
- canIncludePrivateData |= IS_LOGGING_EVERYTHING;
- for (final LogUnit logUnit : logUnits) {
- if (DEBUG) {
- final String wordsString = logUnit.getWordsAsString();
- Log.d(TAG, "onPublish: '" + wordsString
- + "', hc: " + logUnit.containsUserDeletions()
- + ", cipd: " + canIncludePrivateData);
- }
- for (final String word : logUnit.getWordsAsStringArray()) {
- final boolean isDictionaryWord = mDictionaryFacilitator != null
- && mDictionaryFacilitator.isValidMainDictWord(word);
- mStatistics.recordWordEntered(
- isDictionaryWord, logUnit.containsUserDeletions());
- }
- }
- publishLogUnits(logUnits, mMainResearchLog, canIncludePrivateData);
- }
- };
- }
-
- private void cleanLogDirectoryIfNeeded(final ResearchLogDirectory researchLogDirectory,
- final long now) {
- final long lastCleanupTime = ResearchSettings.readResearchLastDirCleanupTime(mPrefs);
- if (now - lastCleanupTime < DURATION_BETWEEN_DIR_CLEANUP_IN_MS) return;
- final long oldestAllowedFileTime = now - MAX_LOGFILE_AGE_IN_MS;
- mResearchLogDirectory.cleanupLogFilesOlderThan(oldestAllowedFileTime);
- ResearchSettings.writeResearchLastDirCleanupTime(mPrefs, now);
- }
-
- public void mainKeyboardView_onAttachedToWindow(final MainKeyboardView mainKeyboardView) {
- mMainKeyboardView = mainKeyboardView;
- maybeShowSplashScreen();
- }
-
- public void mainKeyboardView_onDetachedFromWindow() {
- mMainKeyboardView = null;
- }
-
- public void onDestroy() {
- if (mPrefs != null) {
- mPrefs.unregisterOnSharedPreferenceChangeListener(this);
- }
- }
-
- private void maybeShowSplashScreen() {
- 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;
- }
- sIsLogging = true;
- ResearchSettings.writeResearchLoggerEnabledFlag(mPrefs, true);
- ResearchSettings.writeHasSeenSplash(mPrefs, true);
- restart();
- }
-
- private void checkForEmptyEditor() {
- if (mLatinIME == null) {
- return;
- }
- final InputConnection ic = mLatinIME.getCurrentInputConnection();
- if (ic == null) {
- return;
- }
- final CharSequence textBefore = ic.getTextBeforeCursor(1, 0);
- if (!TextUtils.isEmpty(textBefore)) {
- mStatistics.setIsEmptyUponStarting(false);
- return;
- }
- final CharSequence textAfter = ic.getTextAfterCursor(1, 0);
- if (!TextUtils.isEmpty(textAfter)) {
- mStatistics.setIsEmptyUponStarting(false);
- return;
- }
- if (textBefore != null && textAfter != null) {
- mStatistics.setIsEmptyUponStarting(true);
- }
- }
-
- private void start() {
- if (DEBUG) {
- Log.d(TAG, "start called");
- }
- maybeShowSplashScreen();
- requestIndicatorRedraw();
- mStatistics.reset();
- checkForEmptyEditor();
- }
-
- /* package */ void stop() {
- if (DEBUG) {
- Log.d(TAG, "stop called");
- }
- // Commit mCurrentLogUnit before closing.
- commitCurrentLogUnit();
-
- try {
- mMainLogBuffer.shiftAndPublishAll();
- } catch (final IOException e) {
- Log.w(TAG, "IOException when publishing LogBuffer", e);
- }
- logStatistics();
- commitCurrentLogUnit();
- mMainLogBuffer.setIsStopping();
- try {
- mMainLogBuffer.shiftAndPublishAll();
- } catch (final IOException e) {
- Log.w(TAG, "IOException when publishing LogBuffer", e);
- }
- mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
-
- resetLogBuffers();
- cancelFeedbackDialog();
- }
-
- public void abort() {
- if (DEBUG) {
- Log.d(TAG, "abort called");
- }
- mMainLogBuffer.clear();
- mMainResearchLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
-
- resetLogBuffers();
- }
-
- private void restart() {
- stop();
- start();
- }
-
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
- if (key == null || prefs == null) {
- return;
- }
- requestIndicatorRedraw();
- mPrefs = prefs;
- prefsChanged(prefs);
- }
-
- public void onResearchKeySelected(final LatinIME latinIME) {
- mCurrentLogUnit.removeResearchButtonInvocation();
- if (mInFeedbackDialog) {
- Toast.makeText(latinIME, R.string.research_please_exit_feedback_form,
- Toast.LENGTH_LONG).show();
- return;
- }
- presentFeedbackDialog(latinIME);
- }
-
- public void presentFeedbackDialogFromSettings() {
- if (mLatinIME != null) {
- presentFeedbackDialog(mLatinIME);
- }
- }
-
- public void presentFeedbackDialog(final LatinIME latinIME) {
- if (isMakingUserRecording()) {
- saveRecording();
- }
- mInFeedbackDialog = true;
-
- final Intent intent = new Intent();
- intent.setClass(mLatinIME, FeedbackActivity.class);
- if (mFeedbackDialogBundle == null) {
- // Restore feedback field with channel name
- final Bundle bundle = new Bundle();
- bundle.putBoolean(FeedbackFragment.KEY_INCLUDE_ACCOUNT_NAME, true);
- bundle.putBoolean(FeedbackFragment.KEY_HAS_USER_RECORDING, false);
- if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) {
- final String savedChannelName = mPrefs.getString(PREF_RESEARCH_SAVED_CHANNEL, "");
- bundle.putString(FeedbackFragment.KEY_FEEDBACK_STRING, savedChannelName);
- }
- mFeedbackDialogBundle = bundle;
- }
- intent.putExtras(mFeedbackDialogBundle);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- latinIME.startActivity(intent);
- }
-
- public void setFeedbackDialogBundle(final Bundle bundle) {
- mFeedbackDialogBundle = bundle;
- }
-
- public void startRecording() {
- final Resources res = mLatinIME.getResources();
- Toast.makeText(mLatinIME,
- res.getString(R.string.research_feedback_demonstration_instructions),
- Toast.LENGTH_LONG).show();
- startRecordingInternal();
- }
-
- private void startRecordingInternal() {
- if (mUserRecordingLog != null) {
- mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
- }
- mUserRecordingFile = mResearchLogDirectory.getUserRecordingFilePath(
- System.currentTimeMillis(), System.nanoTime());
- mUserRecordingLog = new ResearchLog(mUserRecordingFile, mLatinIME);
- mUserRecordingLogBuffer = new LogBuffer();
- resetRecordingTimer();
- }
-
- private boolean isMakingUserRecording() {
- return mUserRecordingLog != null;
- }
-
- private void resetRecordingTimer() {
- if (mUserRecordingTimeoutHandler == null) {
- mUserRecordingTimeoutHandler = new Handler();
- }
- clearRecordingTimer();
- mUserRecordingTimeoutHandler.postDelayed(mRecordingHandlerTimeoutRunnable,
- USER_RECORDING_TIMEOUT_MS);
- }
-
- private void clearRecordingTimer() {
- mUserRecordingTimeoutHandler.removeCallbacks(mRecordingHandlerTimeoutRunnable);
- }
-
- private Runnable mRecordingHandlerTimeoutRunnable = new Runnable() {
- @Override
- public void run() {
- cancelRecording();
- requestIndicatorRedraw();
- final Resources res = mLatinIME.getResources();
- Toast.makeText(mLatinIME, res.getString(R.string.research_feedback_recording_failure),
- Toast.LENGTH_LONG).show();
- }
- };
-
- private void cancelRecording() {
- if (mUserRecordingLog != null) {
- mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
- }
- mUserRecordingLog = null;
- mUserRecordingLogBuffer = null;
- if (mFeedbackDialogBundle != null) {
- mFeedbackDialogBundle.putBoolean("HasRecording", false);
- }
- }
-
- private void saveRecording() {
- commitCurrentLogUnit();
- publishLogBuffer(mUserRecordingLogBuffer, mUserRecordingLog, true);
- mUserRecordingLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
- mUserRecordingLog = null;
- mUserRecordingLogBuffer = null;
-
- if (mFeedbackDialogBundle != null) {
- mFeedbackDialogBundle.putBoolean(FeedbackFragment.KEY_HAS_USER_RECORDING, true);
- }
- clearRecordingTimer();
- }
-
- // TODO: currently unreachable. Remove after being sure enable/disable is
- // not needed.
- /*
- public void enableOrDisable(final boolean showEnable, final LatinIME latinIME) {
- if (showEnable) {
- if (!sIsLogging) {
- setLoggingAllowed(true);
- }
- resumeLogging();
- Toast.makeText(latinIME,
- R.string.research_notify_session_logging_enabled,
- Toast.LENGTH_LONG).show();
- } else {
- Toast toast = Toast.makeText(latinIME,
- R.string.research_notify_session_log_deleting,
- Toast.LENGTH_LONG);
- toast.show();
- boolean isLogDeleted = abort();
- final long currentTime = System.currentTimeMillis();
- final long resumeTime = currentTime
- + TimeUnit.MINUTES.toMillis(SUSPEND_DURATION_IN_MINUTES);
- suspendLoggingUntil(resumeTime);
- toast.cancel();
- Toast.makeText(latinIME, R.string.research_notify_logging_suspended,
- Toast.LENGTH_LONG).show();
- }
- }
- */
-
- /**
- * Get the name of the first allowed account on the device.
- *
- * Allowed accounts must be in the domain given by ALLOWED_ACCOUNT_DOMAIN.
- *
- * @return The user's account name.
- */
- public String getAccountName() {
- if (sAccountType == null || sAccountType.isEmpty()) {
- return null;
- }
- if (sAllowedAccountDomain == null || sAllowedAccountDomain.isEmpty()) {
- return null;
- }
- final AccountManager manager = AccountManager.get(mLatinIME);
- // Filter first by account type.
- final Account[] accounts = manager.getAccountsByType(sAccountType);
-
- for (final Account account : accounts) {
- if (DEBUG) {
- Log.d(TAG, account.name);
- }
- final String[] parts = account.name.split("@");
- if (parts.length > 1 && parts[1].equals(sAllowedAccountDomain)) {
- return parts[0];
- }
- }
- return null;
- }
-
- private static final LogStatement LOGSTATEMENT_FEEDBACK =
- new LogStatement("UserFeedback", false, false, "contents", "accountName", "recording");
- public void sendFeedback(final String feedbackContents, final boolean includeHistory,
- final boolean isIncludingAccountName, final boolean isIncludingRecording) {
- String recording = "";
- if (isIncludingRecording) {
- // Try to read recording from recently written json file
- if (mUserRecordingFile != null) {
- FileChannel channel = null;
- try {
- channel = new FileInputStream(mUserRecordingFile).getChannel();
- final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0,
- channel.size());
- // Android's openFileOutput() creates the file, so we use Android's default
- // Charset (UTF-8) here to read it.
- recording = Charset.defaultCharset().decode(buffer).toString();
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Could not find recording file", e);
- } catch (IOException e) {
- Log.e(TAG, "Error reading recording file", e);
- } finally {
- if (channel != null) {
- try {
- channel.close();
- } catch (IOException e) {
- Log.e(TAG, "Error closing recording file", e);
- }
- }
- }
- }
- }
- final LogUnit feedbackLogUnit = new LogUnit();
- final String accountName = isIncludingAccountName ? getAccountName() : "";
- feedbackLogUnit.addLogStatement(LOGSTATEMENT_FEEDBACK, SystemClock.uptimeMillis(),
- feedbackContents, accountName, recording);
-
- final ResearchLog feedbackLog = new FeedbackLog(mResearchLogDirectory.getLogFilePath(
- System.currentTimeMillis(), System.nanoTime()), mLatinIME);
- final LogBuffer feedbackLogBuffer = new LogBuffer();
- feedbackLogBuffer.shiftIn(feedbackLogUnit);
- publishLogBuffer(feedbackLogBuffer, feedbackLog, true /* isIncludingPrivateData */);
- feedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
- uploadNow();
-
- if (isIncludingRecording && DEBUG_REPLAY_AFTER_FEEDBACK) {
- final Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
- final ReplayData replayData =
- mMotionEventReader.readMotionEventData(mUserRecordingFile);
- mReplayer.replay(replayData, null);
- }
- }, TimeUnit.SECONDS.toMillis(1));
- }
-
- if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) {
- // Use feedback string as a channel name to label feedback strings. Here we record the
- // string for prepopulating the field next time.
- final String channelName = feedbackContents;
- if (mPrefs == null) {
- return;
- }
- mPrefs.edit().putString(PREF_RESEARCH_SAVED_CHANNEL, channelName).apply();
- }
- }
-
- public void uploadNow() {
- if (DEBUG) {
- Log.d(TAG, "calling uploadNow()");
- }
- mLatinIME.startService(mUploadNowIntent);
- }
-
- public void onLeavingSendFeedbackDialog() {
- mInFeedbackDialog = false;
- }
-
- private void cancelFeedbackDialog() {
- if (isMakingUserRecording()) {
- cancelRecording();
- }
- mInFeedbackDialog = false;
- }
-
- public void initDictionary(final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
- mDictionaryFacilitator = dictionaryFacilitator;
- // MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create
- // a new one.
- if (mMainLogBuffer != null) {
- restart();
- }
- }
-
- private void setIsPasswordView(boolean isPasswordView) {
- mIsPasswordView = isPasswordView;
- }
-
- /**
- * 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() {
- if (!IS_SHOWING_INDICATOR) {
- return;
- }
- if (mMainKeyboardView == null) {
- return;
- }
- mMainKeyboardView.invalidateAllKeys();
- }
-
- private boolean isReplaying() {
- return mReplayer.isReplaying();
- }
-
- private int getIndicatorColor() {
- if (isMakingUserRecording()) {
- return Color.YELLOW;
- }
- if (isReplaying()) {
- return Color.GREEN;
- }
- return Color.RED;
- }
-
- public void paintIndicator(KeyboardView view, Paint paint, Canvas canvas, int width,
- int height) {
- // TODO: Reimplement using a keyboard background image specific to the ResearchLogger
- // and remove this method.
- // The check for MainKeyboardView ensures that the indicator only decorates the main
- // keyboard, not every keyboard.
- if (IS_SHOWING_INDICATOR && (isAllowedToLogTo(mMainResearchLog) || isReplaying())
- && view instanceof MainKeyboardView) {
- final int savedColor = paint.getColor();
- paint.setColor(getIndicatorColor());
- final Style savedStyle = paint.getStyle();
- paint.setStyle(Style.STROKE);
- final float savedStrokeWidth = paint.getStrokeWidth();
- if (IS_SHOWING_INDICATOR_CLEARLY) {
- paint.setStrokeWidth(5);
- canvas.drawLine(0, 0, 0, height, paint);
- canvas.drawLine(width, 0, width, height, paint);
- } else {
- // Put a tiny dot on the screen so a knowledgeable user can check whether it is
- // enabled. The dot is actually a zero-width, zero-height rectangle, placed at the
- // lower-right corner of the canvas, painted with a non-zero border width.
- paint.setStrokeWidth(3);
- canvas.drawRect(width - 1, height - 1, width, height, paint);
- }
- paint.setColor(savedColor);
- paint.setStyle(savedStyle);
- paint.setStrokeWidth(savedStrokeWidth);
- }
- }
-
- /**
- * Buffer a research log event, flagging it as privacy-sensitive.
- */
- private synchronized void enqueueEvent(final LogStatement logStatement,
- final Object... values) {
- enqueueEvent(mCurrentLogUnit, logStatement, values);
- }
-
- private synchronized void enqueueEvent(final LogUnit logUnit, final LogStatement logStatement,
- final Object... values) {
- assert values.length == logStatement.getKeys().length;
- if (isAllowedToLogTo(mMainResearchLog) && logUnit != null) {
- final long time = SystemClock.uptimeMillis();
- logUnit.addLogStatement(logStatement, time, values);
- }
- }
-
- private void setCurrentLogUnitContainsDigitFlag() {
- mCurrentLogUnit.setMayContainDigit();
- }
-
- private void setCurrentLogUnitContainsUserDeletions() {
- mCurrentLogUnit.setContainsUserDeletions();
- }
-
- private void setCurrentLogUnitCorrectionType(final int correctionType) {
- mCurrentLogUnit.setCorrectionType(correctionType);
- }
-
- /* package for test */ void commitCurrentLogUnit() {
- if (DEBUG) {
- Log.d(TAG, "commitCurrentLogUnit" + (mCurrentLogUnit.hasOneOrMoreWords() ?
- ": " + mCurrentLogUnit.getWordsAsString() : ""));
- }
- if (!mCurrentLogUnit.isEmpty()) {
- mMainLogBuffer.shiftIn(mCurrentLogUnit);
- if (mUserRecordingLogBuffer != null) {
- mUserRecordingLogBuffer.shiftIn(mCurrentLogUnit);
- }
- mCurrentLogUnit = new LogUnit();
- } else {
- if (DEBUG) {
- Log.d(TAG, "Warning: tried to commit empty log unit.");
- }
- }
- }
-
- private static final LogStatement LOGSTATEMENT_UNCOMMIT_CURRENT_LOGUNIT =
- new LogStatement("UncommitCurrentLogUnit", false, false);
- public void uncommitCurrentLogUnit(final String expectedWord,
- final boolean dumpCurrentLogUnit) {
- // 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
- // 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. 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 (!TextUtils.equals(scrubbedExpectedWord, oldLogUnitWords)) return;
- }
-
- // Uncommit, merging if necessary.
- mMainLogBuffer.unshiftIn();
- if (oldLogUnit != null && !dumpCurrentLogUnit) {
- oldLogUnit.append(mCurrentLogUnit);
- mSavedDownEventTime = Long.MAX_VALUE;
- }
- if (oldLogUnit == null) {
- mCurrentLogUnit = new LogUnit();
- } else {
- mCurrentLogUnit = oldLogUnit;
- }
- enqueueEvent(LOGSTATEMENT_UNCOMMIT_CURRENT_LOGUNIT);
- if (DEBUG) {
- Log.d(TAG, "uncommitCurrentLogUnit (dump=" + dumpCurrentLogUnit + ") back to "
- + (mCurrentLogUnit.hasOneOrMoreWords() ? ": '"
- + mCurrentLogUnit.getWordsAsString() + "'" : ""));
- }
- }
-
- /**
- * Publish all the logUnits in the logBuffer, without doing any privacy filtering.
- */
- /* package for test */ void publishLogBuffer(final LogBuffer logBuffer,
- final ResearchLog researchLog, final boolean canIncludePrivateData) {
- publishLogUnits(logBuffer.getLogUnits(), researchLog, canIncludePrivateData);
- }
-
- private static final LogStatement LOGSTATEMENT_LOG_SEGMENT_OPENING =
- new LogStatement("logSegmentStart", false, false, "isIncludingPrivateData");
- private static final LogStatement LOGSTATEMENT_LOG_SEGMENT_CLOSING =
- new LogStatement("logSegmentEnd", false, false);
- /**
- * Publish all LogUnits in a list.
- *
- * Any privacy checks should be performed before calling this method.
- */
- /* package for test */ void publishLogUnits(final List<LogUnit> logUnits,
- final ResearchLog researchLog, final boolean canIncludePrivateData) {
- final LogUnit openingLogUnit = new LogUnit();
- if (logUnits.isEmpty()) 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) {
- openingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_OPENING,
- SystemClock.uptimeMillis(), canIncludePrivateData);
- researchLog.publish(openingLogUnit, true /* isIncludingPrivateData */);
- }
- for (LogUnit logUnit : logUnits) {
- if (DEBUG) {
- Log.d(TAG, "publishLogBuffer: " + (logUnit.hasOneOrMoreWords()
- ? logUnit.getWordsAsString() : "<wordless>")
- + ", correction?: " + logUnit.containsUserDeletions());
- }
- researchLog.publish(logUnit, canIncludePrivateData);
- }
- if (canIncludePrivateData) {
- final LogUnit closingLogUnit = new LogUnit();
- closingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_CLOSING,
- SystemClock.uptimeMillis());
- researchLog.publish(closingLogUnit, true /* isIncludingPrivateData */);
- }
- }
-
- public static boolean hasLetters(final String word) {
- final int length = word.length();
- for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
- final int codePoint = word.codePointAt(i);
- if (Character.isLetter(codePoint)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Commit the portion of mCurrentLogUnit before maxTime as a worded logUnit.
- *
- * After this operation completes, mCurrentLogUnit will hold any logStatements that happened
- * after maxTime.
- */
- /* package for test */ void commitCurrentLogUnitAsWord(final String word, final long maxTime,
- final boolean isBatchMode) {
- if (word == null) {
- return;
- }
- if (word.length() > 0 && hasLetters(word)) {
- mCurrentLogUnit.setWords(word);
- }
- final LogUnit newLogUnit = mCurrentLogUnit.splitByTime(maxTime);
- enqueueCommitText(word, isBatchMode);
- commitCurrentLogUnit();
- mCurrentLogUnit = newLogUnit;
- }
-
- /**
- * Record the time of a MotionEvent.ACTION_DOWN.
- *
- * Warning: Not thread safe. Only call from the main thread.
- */
- private void setSavedDownEventTime(final long time) {
- mSavedDownEventTime = time;
- }
-
- public void onWordFinished(final String word, final boolean isBatchMode) {
- commitCurrentLogUnitAsWord(word, mSavedDownEventTime, isBatchMode);
- mSavedDownEventTime = Long.MAX_VALUE;
- }
-
- private static int scrubDigitFromCodePoint(int codePoint) {
- return Character.isDigit(codePoint) ? DIGIT_REPLACEMENT_CODEPOINT : codePoint;
- }
-
- /* 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)) {
- final int codePoint = Character.codePointAt(s, i);
- if (Character.isDigit(codePoint)) {
- if (sb == null) {
- sb = new StringBuilder(length);
- sb.append(s.substring(0, i));
- }
- sb.appendCodePoint(DIGIT_REPLACEMENT_CODEPOINT);
- } else {
- if (sb != null) {
- sb.appendCodePoint(codePoint);
- }
- }
- }
- if (sb == null) {
- return s;
- } else {
- return sb.toString();
- }
- }
-
- private String scrubWord(String word) {
- if (mDictionaryFacilitator != null && mDictionaryFacilitator.isValidMainDictWord(word)) {
- return word;
- }
- return WORD_REPLACEMENT_STRING;
- }
-
- // Specific logging methods follow below. The comments for each logging method should
- // indicate what specific method is logged, and how to trigger it from the user interface.
- //
- // Logging methods can be generally classified into two flavors, "UserAction", which should
- // correspond closely to an event that is sensed by the IME, and is usually generated
- // directly by the user, and "SystemResponse" which corresponds to an event that the IME
- // generates, often after much processing of user input. SystemResponses should correspond
- // closely to user-visible events.
- // TODO: Consider exposing the UserAction classification in the log output.
-
- /**
- * Log a call to LatinIME.onStartInputViewInternal().
- *
- * UserAction: called each time the keyboard is opened up.
- */
- private static final LogStatement LOGSTATEMENT_LATIN_IME_ON_START_INPUT_VIEW_INTERNAL =
- new LogStatement("LatinImeOnStartInputViewInternal", false, false, "uuid",
- "packageName", "inputType", "imeOptions", "fieldId", "display", "model",
- "prefs", "versionCode", "versionName", "outputFormatVersion", "logEverything",
- "isDevTeamBuild");
- public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
- final SharedPreferences prefs) {
- final ResearchLogger researchLogger = getInstance();
- if (editorInfo != null) {
- final boolean isPassword = InputTypeUtils.isPasswordInputType(editorInfo.inputType)
- || InputTypeUtils.isVisiblePasswordInputType(editorInfo.inputType);
- getInstance().setIsPasswordView(isPassword);
- researchLogger.start();
- final Context context = researchLogger.mLatinIME;
- try {
- final PackageInfo packageInfo;
- packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),
- 0);
- final Integer versionCode = packageInfo.versionCode;
- final String versionName = packageInfo.versionName;
- final String uuid = ResearchSettings.readResearchLoggerUuid(researchLogger.mPrefs);
- researchLogger.enqueueEvent(LOGSTATEMENT_LATIN_IME_ON_START_INPUT_VIEW_INTERNAL,
- uuid, editorInfo.packageName, Integer.toHexString(editorInfo.inputType),
- Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
- Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
- OUTPUT_FORMAT_VERSION, IS_LOGGING_EVERYTHING,
- researchLogger.isDevTeamBuild());
- // Commit the logUnit so the LatinImeOnStartInputViewInternal event is in its own
- // logUnit at the beginning of the log.
- researchLogger.commitCurrentLogUnit();
- } catch (final NameNotFoundException e) {
- Log.e(TAG, "NameNotFound", e);
- }
- }
- }
-
- // TODO: Update this heuristic pattern to something more reliable. Developer builds tend to
- // have the developer name and year embedded.
- private static final Pattern developerBuildRegex = Pattern.compile("[A-Za-z]\\.20[1-9]");
- private boolean isDevTeamBuild() {
- try {
- final PackageInfo packageInfo;
- packageInfo = mLatinIME.getPackageManager().getPackageInfo(mLatinIME.getPackageName(),
- 0);
- final String versionName = packageInfo.versionName;
- return developerBuildRegex.matcher(versionName).find();
- } catch (final NameNotFoundException e) {
- Log.e(TAG, "Could not determine package name", e);
- return false;
- }
- }
-
- /**
- * Log a change in preferences.
- *
- * UserAction: called when the user changes the settings.
- */
- private static final LogStatement LOGSTATEMENT_PREFS_CHANGED =
- new LogStatement("PrefsChanged", false, false, "prefs");
- public static void prefsChanged(final SharedPreferences prefs) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_PREFS_CHANGED, prefs);
- }
-
- /**
- * Log a call to MainKeyboardView.processMotionEvent().
- *
- * UserAction: called when the user puts their finger onto the screen (ACTION_DOWN).
- *
- */
- 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) {
- 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();
- }
- }
-
- /**
- * Log a call to LatinIME.onCodeInput().
- *
- * SystemResponse: The main processing step for entering text. Called when the user performs a
- * tap, a flick, a long press, releases a gesture, or taps a punctuation suggestion.
- */
- private static final LogStatement LOGSTATEMENT_LATIN_IME_ON_CODE_INPUT =
- new LogStatement("LatinImeOnCodeInput", true, false, "code", "x", "y");
- public static void latinIME_onCodeInput(final int code, final int x, final int y) {
- final long time = SystemClock.uptimeMillis();
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATIN_IME_ON_CODE_INPUT,
- Constants.printableCode(scrubDigitFromCodePoint(code)), x, y);
- if (Character.isDigit(code)) {
- researchLogger.setCurrentLogUnitContainsDigitFlag();
- }
- researchLogger.mStatistics.recordChar(code, time);
- }
- /**
- * Log a call to LatinIME.onDisplayCompletions().
- *
- * SystemResponse: The IME has displayed application-specific completions. They may show up
- * in the suggestion strip, such as a landscape phone.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_ONDISPLAYCOMPLETIONS =
- new LogStatement("LatinIMEOnDisplayCompletions", true, true,
- "applicationSpecifiedCompletions");
- public static void latinIME_onDisplayCompletions(
- final CompletionInfo[] applicationSpecifiedCompletions) {
- // Note; passing an array as a single element in a vararg list. Must create a new
- // dummy array around it or it will get expanded.
- getInstance().enqueueEvent(LOGSTATEMENT_LATINIME_ONDISPLAYCOMPLETIONS,
- new Object[] { applicationSpecifiedCompletions });
- }
-
- /**
- * The IME is finishing; it is either being destroyed, or is about to be hidden.
- *
- * UserAction: The user has performed an action that has caused the IME to be closed. They may
- * have focused on something other than a text field, or explicitly closed it.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL =
- new LogStatement("LatinIMEOnFinishInputViewInternal", false, false, "isTextTruncated",
- "text");
- public static void latinIME_onFinishInputViewInternal(final boolean finishingInput) {
- // The finishingInput flag is set in InputMethodService. It is true if called from
- // doFinishInput(), which can be called as part of doStartInput(). This can happen at times
- // when the IME is not closing, such as when powering up. The finishinInput flag is false
- // if called from finishViews(), which is called from hideWindow() and onDestroy(). These
- // are the situations in which we want to finish up the researchLog.
- if (!finishingInput) {
- final ResearchLogger researchLogger = getInstance();
- // Assume that OUTPUT_ENTIRE_BUFFER is only true when we don't care about privacy (e.g.
- // during a live user test), so the normal isPotentiallyPrivate and
- // isPotentiallyRevealing flags do not apply
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL,
- true /* isTextTruncated */, "" /* text */);
- researchLogger.commitCurrentLogUnit();
- getInstance().stop();
- }
- }
-
- /**
- * Log a call to LatinIME.onUpdateSelection().
- *
- * UserAction/SystemResponse: The user has moved the cursor or selection. This function may
- * be called, however, when the system has moved the cursor, say by inserting a character.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_ONUPDATESELECTION =
- new LogStatement("LatinIMEOnUpdateSelection", true, false, "lastSelectionStart",
- "lastSelectionEnd", "oldSelStart", "oldSelEnd", "newSelStart", "newSelEnd",
- "composingSpanStart", "composingSpanEnd", "expectingUpdateSelection",
- "expectingUpdateSelectionFromLogger", "context");
- public static void latinIME_onUpdateSelection(final int lastSelectionStart,
- final int lastSelectionEnd, final int oldSelStart, final int oldSelEnd,
- final int newSelStart, final int newSelEnd, final int composingSpanStart,
- final int composingSpanEnd, final RichInputConnection connection) {
- String word = "";
- if (connection != null) {
- TextRange range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
- if (range != null) {
- word = range.mWord.toString();
- }
- }
- final ResearchLogger researchLogger = getInstance();
- final String scrubbedWord = researchLogger.scrubWord(word);
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONUPDATESELECTION, lastSelectionStart,
- lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart, newSelEnd,
- composingSpanStart, composingSpanEnd, false /* expectingUpdateSelection */,
- false /* expectingUpdateSelectionFromLogger */, scrubbedWord);
- }
-
- /**
- * Log a call to LatinIME.onTextInput().
- *
- * SystemResponse: Raw text is added to the TextView.
- */
- public static void latinIME_onTextInput(final String text, final boolean isBatchMode) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE, isBatchMode);
- }
-
- /**
- * 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", "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 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.setCurrentLogUnitContainsUserDeletions();
- researchLogger.setCurrentLogUnitCorrectionType(LogUnit.CORRECTIONTYPE_TYPO);
- }
- final String scrubbedWord = scrubDigitsFromString(suggestion);
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY,
- scrubDigitsFromString(replacedWord), index,
- 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());
- }
-
- /**
- * Log a call to LatinIME.punctuationSuggestion().
- *
- * UserAction: The user has chosen punctuation from the punctuation suggestion strip.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION =
- new LogStatement("LatinIMEPunctuationSuggestion", false, false, "index", "suggestion",
- "x", "y", "isPrediction");
- public static void latinIME_punctuationSuggestion(final int index, final String suggestion,
- final boolean isBatchMode, final boolean isPrediction) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION, index, suggestion,
- Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
- isPrediction);
- researchLogger.commitCurrentLogUnitAsWord(suggestion, Long.MAX_VALUE, isBatchMode);
- }
-
- /**
- * Log a call to LatinIME.sendKeyCodePoint().
- *
- * 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();
- 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;
- }
- }
-
- /**
- * Log a call to LatinIME.promotePhantomSpace().
- *
- * SystemResponse: The IME is inserting a real space in place of a phantom space.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE =
- 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();
- researchLogger.mPhantomSpaceLogUnit = new LogUnit();
- researchLogger.enqueueEvent(researchLogger.mPhantomSpaceLogUnit,
- LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE);
- }
-
- /**
- * Log a call to LatinIME.swapSwapperAndSpace().
- *
- * SystemResponse: A symbol has been swapped with a space character. E.g. punctuation may swap
- * if a soft space is inserted after a word.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE =
- new LogStatement("LatinIMESwapSwapperAndSpace", false, false, "originalCharacters",
- "charactersAfterSwap");
- public static void latinIME_swapSwapperAndSpace(final CharSequence originalCharacters,
- final String charactersAfterSwap) {
- final ResearchLogger researchLogger = getInstance();
- final LogUnit logUnit;
- logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
- if (logUnit != null) {
- researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE,
- originalCharacters, charactersAfterSwap);
- }
- }
-
- /**
- * Log a call to LatinIME.maybeDoubleSpacePeriod().
- *
- * SystemResponse: Two spaces have been replaced by period space.
- */
- public static void latinIME_maybeDoubleSpacePeriod(final String text,
- final boolean isBatchMode) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE, isBatchMode);
- }
-
- /**
- * Log a call to MainKeyboardView.onLongPress().
- *
- * UserAction: The user has performed a long-press on a key.
- */
- private static final LogStatement LOGSTATEMENT_MAINKEYBOARDVIEW_ONLONGPRESS =
- new LogStatement("MainKeyboardViewOnLongPress", false, false);
- public static void mainKeyboardView_onLongPress() {
- getInstance().enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_ONLONGPRESS);
- }
-
- /**
- * Log a call to MainKeyboardView.setKeyboard().
- *
- * SystemResponse: The IME has switched to a new keyboard (e.g. French, English).
- * This is typically called right after LatinIME.onStartInputViewInternal (when starting a new
- * IME), but may happen at other times if the user explicitly requests a keyboard change.
- */
- private static final LogStatement LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD =
- new LogStatement("MainKeyboardViewSetKeyboard", false, false, "elementId", "locale",
- "orientation", "width", "modeName", "action", "navigateNext",
- "navigatePrevious", "clobberSettingsKey", "passwordInput",
- "supportsSwitchingToShortcutIme", "hasShortcutKey", "languageSwitchKeyEnabled",
- "isMultiLine", "tw", "th",
- "keys");
- public static void mainKeyboardView_setKeyboard(final Keyboard keyboard,
- final int orientation) {
- final KeyboardId kid = keyboard.mId;
- final boolean isPasswordView = kid.passwordInput();
- final ResearchLogger researchLogger = getInstance();
- researchLogger.setIsPasswordView(isPasswordView);
- researchLogger.enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD,
- KeyboardId.elementIdToName(kid.mElementId),
- kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
- orientation, kid.mWidth, KeyboardId.modeName(kid.mMode), kid.imeAction(),
- kid.navigateNext(), kid.navigatePrevious(), kid.mClobberSettingsKey,
- isPasswordView, kid.mSupportsSwitchingToShortcutIme, kid.mHasShortcutKey,
- kid.mLanguageSwitchKeyEnabled, kid.isMultiLine(), keyboard.mOccupiedWidth,
- keyboard.mOccupiedHeight, keyboard.getSortedKeys());
- }
-
- /**
- * Log a call to LatinIME.revertCommit().
- *
- * SystemResponse: The IME has reverted commited text. This happens when the user enters
- * a word, commits it by pressing space or punctuation, and then reverts the commit by hitting
- * backspace.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_REVERTCOMMIT =
- new LogStatement("LatinIMERevertCommit", true, false, "committedWord",
- "originallyTypedWord", "separatorString");
- 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();
- //
- // 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());
- }
-
- /**
- * Log a call to PointerTracker.callListenerOnCancelInput().
- *
- * UserAction: The user has canceled the input, e.g., by pressing down, but then removing
- * outside the keyboard area.
- * TODO: Verify
- */
- private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCANCELINPUT =
- new LogStatement("PointerTrackerCallListenerOnCancelInput", false, false);
- public static void pointerTracker_callListenerOnCancelInput() {
- getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCANCELINPUT);
- }
-
- /**
- * Log a call to PointerTracker.callListenerOnCodeInput().
- *
- * SystemResponse: The user has entered a key through the normal tapping mechanism.
- * LatinIME.onCodeInput will also be called.
- */
- private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCODEINPUT =
- new LogStatement("PointerTrackerCallListenerOnCodeInput", true, false, "code",
- "outputText", "x", "y", "ignoreModifierKey", "altersCode", "isEnabled");
- public static void pointerTracker_callListenerOnCodeInput(final Key key, final int x,
- final int y, final boolean ignoreModifierKey, final boolean altersCode,
- final int code) {
- if (key != null) {
- String outputText = key.getOutputText();
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCODEINPUT,
- Constants.printableCode(scrubDigitFromCodePoint(code)),
- outputText == null ? null : scrubDigitsFromString(outputText.toString()),
- x, y, ignoreModifierKey, altersCode, key.isEnabled());
- }
- }
-
- /**
- * Log a call to PointerTracker.callListenerCallListenerOnRelease().
- *
- * UserAction: The user has released their finger or thumb from the screen.
- */
- private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONRELEASE =
- new LogStatement("PointerTrackerCallListenerOnRelease", true, false, "code",
- "withSliding", "ignoreModifierKey", "isEnabled");
- public static void pointerTracker_callListenerOnRelease(final Key key, final int primaryCode,
- final boolean withSliding, final boolean ignoreModifierKey) {
- if (key != null) {
- getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONRELEASE,
- Constants.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding,
- ignoreModifierKey, key.isEnabled());
- }
- }
-
- /**
- * Log a call to PointerTracker.onDownEvent().
- *
- * UserAction: The user has pressed down on a key.
- * TODO: Differentiate with LatinIME.processMotionEvent.
- */
- private static final LogStatement LOGSTATEMENT_POINTERTRACKER_ONDOWNEVENT =
- new LogStatement("PointerTrackerOnDownEvent", true, false, "deltaT", "distanceSquared");
- public static void pointerTracker_onDownEvent(long deltaT, int distanceSquared) {
- getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_ONDOWNEVENT, deltaT,
- distanceSquared);
- }
-
- /**
- * Log a call to PointerTracker.onMoveEvent().
- *
- * UserAction: The user has moved their finger while pressing on the screen.
- * TODO: Differentiate with LatinIME.processMotionEvent().
- */
- private static final LogStatement LOGSTATEMENT_POINTERTRACKER_ONMOVEEVENT =
- new LogStatement("PointerTrackerOnMoveEvent", true, false, "x", "y", "lastX", "lastY");
- public static void pointerTracker_onMoveEvent(final int x, final int y, final int lastX,
- final int lastY) {
- getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_ONMOVEEVENT, x, y, lastX, lastY);
- }
-
- /**
- * Log a call to RichInputConnection.commitCompletion().
- *
- * SystemResponse: The IME has committed a completion. A completion is an application-
- * specific suggestion that is presented in a pop-up menu in the TextView.
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_COMMITCOMPLETION =
- new LogStatement("RichInputConnectionCommitCompletion", true, false, "completionInfo");
- public static void richInputConnection_commitCompletion(final CompletionInfo completionInfo) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_COMMITCOMPLETION,
- completionInfo);
- }
-
- /**
- * Log a call to RichInputConnection.revertDoubleSpacePeriod().
- *
- * SystemResponse: The IME has reverted ". ", which had previously replaced two typed spaces.
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD =
- new LogStatement("RichInputConnectionRevertDoubleSpacePeriod", false, false);
- public static void 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);
- }
-
- /**
- * Log a call to RichInputConnection.revertSwapPunctuation().
- *
- * SystemResponse: The IME has reverted a punctuation swap.
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_REVERTSWAPPUNCTUATION =
- new LogStatement("RichInputConnectionRevertSwapPunctuation", false, false);
- public static void richInputConnection_revertSwapPunctuation() {
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_REVERTSWAPPUNCTUATION);
- }
-
- /**
- * Log a call to LatinIME.commitCurrentAutoCorrection().
- *
- * SystemResponse: The IME has committed an auto-correction. An auto-correction changes the raw
- * text input to another word (or words) that the user more likely desired to type.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_COMMITCURRENTAUTOCORRECTION =
- new LogStatement("LatinIMECommitCurrentAutoCorrection", true, true, "typedWord",
- "autoCorrection", "separatorString");
- public static void latinIme_commitCurrentAutoCorrection(final String typedWord,
- final String autoCorrection, final String separatorString, final boolean isBatchMode,
- final SuggestedWords suggestedWords) {
- final String scrubbedTypedWord = scrubDigitsFromString(typedWord);
- final String scrubbedAutoCorrection = scrubDigitsFromString(autoCorrection);
- final ResearchLogger researchLogger = getInstance();
- researchLogger.mCurrentLogUnit.initializeSuggestions(suggestedWords);
- researchLogger.onWordFinished(scrubbedAutoCorrection, isBatchMode);
-
- // Add the autocorrection logStatement at the end of the logUnit for the committed word.
- // We have to do this after calling commitCurrentLogUnitAsWord, because it may split the
- // current logUnit, and then we have to peek to get the logUnit reference back.
- final LogUnit logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
- // TODO: Add test to confirm that the commitCurrentAutoCorrection log statement should
- // always be added to logUnit (if non-null) and not mCurrentLogUnit.
- researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_COMMITCURRENTAUTOCORRECTION,
- scrubbedTypedWord, scrubbedAutoCorrection, separatorString);
- }
-
- private boolean isExpectingCommitText = false;
-
- /**
- * Log a call to RichInputConnection.commitText().
- *
- * SystemResponse: The IME is committing text. This happens after the user has typed a word
- * and then a space or punctuation key.
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTIONCOMMITTEXT =
- new LogStatement("RichInputConnectionCommitText", true, false, "newCursorPosition");
- public static void richInputConnection_commitText(final String committedWord,
- final int newCursorPosition, final boolean isBatchMode) {
- final ResearchLogger researchLogger = getInstance();
- // Only include opening and closing logSegments if private data is included
- final String scrubbedWord = scrubDigitsFromString(committedWord);
- if (!researchLogger.isExpectingCommitText) {
- researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTIONCOMMITTEXT,
- newCursorPosition);
- researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode);
- }
- researchLogger.isExpectingCommitText = false;
- }
-
- /**
- * 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 /* 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);
- }
-
- /**
- * Log a call to RichInputConnection.deleteSurroundingText().
- *
- * SystemResponse: The IME has deleted text.
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT =
- new LogStatement("RichInputConnectionDeleteSurroundingText", true, false,
- "beforeLength", "afterLength");
- public static void richInputConnection_deleteSurroundingText(final int beforeLength,
- final int afterLength) {
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT,
- beforeLength, afterLength);
- }
-
- /**
- * Log a call to RichInputConnection.finishComposingText().
- *
- * SystemResponse: The IME has left the composing text as-is.
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT =
- new LogStatement("RichInputConnectionFinishComposingText", false, false);
- public static void richInputConnection_finishComposingText() {
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT);
- }
-
- /**
- * Log a call to RichInputConnection.performEditorAction().
- *
- * SystemResponse: The IME is invoking an action specific to the editor.
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_PERFORMEDITORACTION =
- new LogStatement("RichInputConnectionPerformEditorAction", false, false,
- "imeActionId");
- public static void richInputConnection_performEditorAction(final int imeActionId) {
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_PERFORMEDITORACTION,
- imeActionId);
- }
-
- /**
- * Log a call to RichInputConnection.sendKeyEvent().
- *
- * SystemResponse: The IME is telling the TextView that a key is being pressed through an
- * alternate channel.
- * TODO: only for hardware keys?
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SENDKEYEVENT =
- new LogStatement("RichInputConnectionSendKeyEvent", true, false, "eventTime", "action",
- "code");
- public static void richInputConnection_sendKeyEvent(final KeyEvent keyEvent) {
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SENDKEYEVENT,
- keyEvent.getEventTime(), keyEvent.getAction(), keyEvent.getKeyCode());
- }
-
- /**
- * Log a call to RichInputConnection.setComposingText().
- *
- * SystemResponse: The IME is setting the composing text. Happens each time a character is
- * entered.
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SETCOMPOSINGTEXT =
- new LogStatement("RichInputConnectionSetComposingText", true, true, "text",
- "newCursorPosition");
- public static void richInputConnection_setComposingText(final CharSequence text,
- final int newCursorPosition) {
- if (text == null) {
- throw new RuntimeException("setComposingText is null");
- }
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SETCOMPOSINGTEXT, text,
- newCursorPosition);
- }
-
- /**
- * Log a call to RichInputConnection.setSelection().
- *
- * SystemResponse: The IME is requesting that the selection change. User-initiated selection-
- * change requests do not go through this method -- it's only when the system wants to change
- * the selection.
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SETSELECTION =
- new LogStatement("RichInputConnectionSetSelection", true, false, "from", "to");
- public static void richInputConnection_setSelection(final int from, final int to) {
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SETSELECTION, from, to);
- }
-
- /**
- * Log a call to SuddenJumpingTouchEventHandler.onTouchEvent().
- *
- * SystemResponse: The IME has filtered input events in case of an erroneous sensor reading.
- */
- private static final LogStatement LOGSTATEMENT_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT =
- new LogStatement("SuddenJumpingTouchEventHandlerOnTouchEvent", true, false,
- "motionEvent");
- public static void suddenJumpingTouchEventHandler_onTouchEvent(final MotionEvent me) {
- if (me != null) {
- getInstance().enqueueEvent(LOGSTATEMENT_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT,
- MotionEvent.obtain(me));
- }
- }
-
- /**
- * Log a call to SuggestionsView.setSuggestions().
- *
- * SystemResponse: The IME is setting the suggestions in the suggestion strip.
- */
- private static final LogStatement LOGSTATEMENT_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS =
- new LogStatement("SuggestionStripViewSetSuggestions", true, true, "suggestedWords");
- public static void suggestionStripView_setSuggestions(final SuggestedWords suggestedWords) {
- if (suggestedWords != null) {
- getInstance().enqueueEvent(LOGSTATEMENT_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS,
- suggestedWords);
- }
- }
-
- /**
- * The user has indicated a particular point in the log that is of interest.
- *
- * UserAction: From direct menu invocation.
- */
- private static final LogStatement LOGSTATEMENT_USER_TIMESTAMP =
- new LogStatement("UserTimestamp", false, false);
- public void userTimestamp() {
- getInstance().enqueueEvent(LOGSTATEMENT_USER_TIMESTAMP);
- }
-
- /**
- * Log a call to LatinIME.onEndBatchInput().
- *
- * SystemResponse: The system has completed a gesture.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_ONENDBATCHINPUT =
- new LogStatement("LatinIMEOnEndBatchInput", true, false, "enteredText",
- "enteredWordPos", "suggestedWords");
- public static void latinIME_onEndBatchInput(final CharSequence enteredText,
- final int enteredWordPos, final SuggestedWords suggestedWords) {
- final ResearchLogger researchLogger = getInstance();
- if (!TextUtils.isEmpty(enteredText) && hasLetters(enteredText.toString())) {
- researchLogger.mCurrentLogUnit.setWords(enteredText.toString());
- }
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONENDBATCHINPUT, enteredText,
- 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}
- */
- 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 */);
- }
- }
-
- /**
- * Log a call to LatinIME.handleBackspace() that is a batch delete.
- *
- * UserInput: The user is deleting a gestured word by hitting the backspace key once.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH =
- new LogStatement("LatinIMEHandleBackspaceBatch", true, false, "deletedText",
- "numCharacters");
- public static void latinIME_handleBackspace_batch(final CharSequence deletedText,
- final int numCharacters) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH, deletedText,
- numCharacters);
- researchLogger.mStatistics.recordGestureDelete(deletedText.length(),
- SystemClock.uptimeMillis());
- researchLogger.uncommitCurrentLogUnit(deletedText.toString(),
- false /* dumpCurrentLogUnit */);
- }
-
- /**
- * Log a long interval between user operation.
- *
- * UserInput: The user has not done anything for a while.
- */
- private static final LogStatement LOGSTATEMENT_ONUSERPAUSE = new LogStatement("OnUserPause",
- false, false, "intervalInMs");
- public static void onUserPause(final long interval) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_ONUSERPAUSE, interval);
- }
-
- /**
- * Record the current time in case the LogUnit is later split.
- *
- * If the current logUnit is split, then tapping, motion events, etc. before this time should
- * be assigned to one LogUnit, and events after this time should go into the following LogUnit.
- */
- public static void recordTimeForLogUnitSplit() {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.setSavedDownEventTime(SystemClock.uptimeMillis());
- researchLogger.mSavedDownEventTime = Long.MAX_VALUE;
- }
-
- /**
- * Log a call to LatinIME.handleSeparator()
- *
- * SystemResponse: The system is inserting a separator character, possibly performing auto-
- * correction or other actions appropriate at the end of a word.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_HANDLESEPARATOR =
- new LogStatement("LatinIMEHandleSeparator", false, false, "primaryCode",
- "isComposingWord");
- public static void latinIME_handleSeparator(final int primaryCode,
- final boolean isComposingWord) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLESEPARATOR, primaryCode,
- isComposingWord);
- }
-
- /**
- * 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.
- */
- private static final LogStatement LOGSTATEMENT_STATISTICS =
- new LogStatement("Statistics", false, false, "charCount", "letterCount", "numberCount",
- "spaceCount", "deleteOpsCount", "wordCount", "isEmptyUponStarting",
- "isEmptinessStateKnown", "averageTimeBetweenKeys", "averageTimeBeforeDelete",
- "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete",
- "dictionaryWordCount", "splitWordsCount", "gestureInputCount",
- "gestureCharsCount", "gesturesDeletedCount", "manualSuggestionsCount",
- "revertCommitsCount", "correctedWordsCount", "autoCorrectionsCount",
- "publishableCount", "unpublishableStoppingCount",
- "unpublishableIncorrectWordCount", "unpublishableSampledTooRecentlyCount",
- "unpublishableDictionaryUnavailableCount", "unpublishableMayContainDigitCount",
- "unpublishableNotInDictionaryCount");
- private static void logStatistics() {
- final ResearchLogger researchLogger = getInstance();
- final Statistics statistics = researchLogger.mStatistics;
- researchLogger.enqueueEvent(LOGSTATEMENT_STATISTICS, statistics.mCharCount,
- statistics.mLetterCount, statistics.mNumberCount, statistics.mSpaceCount,
- statistics.mDeleteKeyCount, statistics.mWordCount, statistics.mIsEmptyUponStarting,
- statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(),
- statistics.mBeforeDeleteKeyCounter.getAverageTime(),
- statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
- statistics.mAfterDeleteKeyCounter.getAverageTime(),
- statistics.mDictionaryWordCount, statistics.mSplitWordsCount,
- statistics.mGesturesInputCount, statistics.mGesturesCharsCount,
- statistics.mGesturesDeletedCount, statistics.mManualSuggestionsCount,
- statistics.mRevertCommitsCount, statistics.mCorrectedWordsCount,
- 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/ResearchSettings.java b/java/src/com/android/inputmethod/research/ResearchSettings.java
deleted file mode 100644
index c0bc03fde..000000000
--- a/java/src/com/android/inputmethod/research/ResearchSettings.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.content.SharedPreferences;
-
-import java.util.UUID;
-
-public final class ResearchSettings {
- public static final String PREF_RESEARCH_LOGGER_UUID = "pref_research_logger_uuid";
- public static final String PREF_RESEARCH_LOGGER_ENABLED_FLAG =
- "pref_research_logger_enabled_flag";
- public static final String PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH =
- "pref_research_logger_has_seen_splash";
- public static final String PREF_RESEARCH_LAST_DIR_CLEANUP_TIME =
- "pref_research_last_dir_cleanup_time";
-
- private ResearchSettings() {
- // Intentional empty constructor for singleton.
- }
-
- public static String readResearchLoggerUuid(final SharedPreferences prefs) {
- if (prefs.contains(PREF_RESEARCH_LOGGER_UUID)) {
- return prefs.getString(PREF_RESEARCH_LOGGER_UUID, null);
- }
- // Generate a random string as uuid if not yet set
- final String newUuid = UUID.randomUUID().toString();
- prefs.edit().putString(PREF_RESEARCH_LOGGER_UUID, newUuid).apply();
- return newUuid;
- }
-
- public static boolean readResearchLoggerEnabledFlag(final SharedPreferences prefs) {
- return prefs.getBoolean(PREF_RESEARCH_LOGGER_ENABLED_FLAG, false);
- }
-
- public static void writeResearchLoggerEnabledFlag(final SharedPreferences prefs,
- final boolean isEnabled) {
- prefs.edit().putBoolean(PREF_RESEARCH_LOGGER_ENABLED_FLAG, isEnabled).apply();
- }
-
- public static boolean readHasSeenSplash(final SharedPreferences prefs) {
- return prefs.getBoolean(PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH, false);
- }
-
- public static void writeHasSeenSplash(final SharedPreferences prefs,
- final boolean hasSeenSplash) {
- prefs.edit().putBoolean(PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH, hasSeenSplash).apply();
- }
-
- public static long readResearchLastDirCleanupTime(final SharedPreferences prefs) {
- return prefs.getLong(PREF_RESEARCH_LAST_DIR_CLEANUP_TIME, 0L);
- }
-
- public static void writeResearchLastDirCleanupTime(final SharedPreferences prefs,
- final long lastDirCleanupTime) {
- prefs.edit().putLong(PREF_RESEARCH_LAST_DIR_CLEANUP_TIME, lastDirCleanupTime).apply();
- }
-}
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
deleted file mode 100644
index fd323a104..000000000
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-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
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
-
- // TODO: Cleanup comments to only including those giving meaningful information.
- // Number of characters entered during a typing session
- int mCharCount;
- // Number of letter characters entered during a typing session
- int mLetterCount;
- // Number of number characters entered
- int mNumberCount;
- // Number of space characters entered
- int mSpaceCount;
- // Number of delete operations entered (taps on the backspace key)
- int mDeleteKeyCount;
- // Number of words entered during a session.
- int mWordCount;
- // Number of words found in the dictionary.
- int mDictionaryWordCount;
- // Number of words split and spaces automatically entered.
- int mSplitWordsCount;
- // Number of words entered during a session.
- int mCorrectedWordsCount;
- // Number of gestures that were input.
- int mGesturesInputCount;
- // Number of gestures that were deleted.
- int mGesturesDeletedCount;
- // Total number of characters in words entered by gesture.
- int mGesturesCharsCount;
- // Number of manual suggestions chosen.
- int mManualSuggestionsCount;
- // Number of times that autocorrection was invoked.
- int mAutoCorrectionsCount;
- // Number of times a commit was reverted in this session.
- int mRevertCommitsCount;
- // Whether the text field was empty upon editing
- 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();
- final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter();
- final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter();
- final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter();
-
- static class AverageTimeCounter {
- int mCount;
- int mTotalTime;
-
- public void reset() {
- mCount = 0;
- mTotalTime = 0;
- }
-
- public void add(long deltaTime) {
- mCount++;
- mTotalTime += deltaTime;
- }
-
- public int getAverageTime() {
- if (mCount == 0) {
- return 0;
- }
- return mTotalTime / mCount;
- }
- }
-
- // 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 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;
- // The type of the last keypress (delete key or not)
- boolean mIsLastKeyDeleteKey;
-
- private static final Statistics sInstance = new Statistics();
-
- public static Statistics getInstance() {
- return sInstance;
- }
-
- private Statistics() {
- reset();
- }
-
- public void reset() {
- mCharCount = 0;
- mLetterCount = 0;
- mNumberCount = 0;
- mSpaceCount = 0;
- mDeleteKeyCount = 0;
- mWordCount = 0;
- mDictionaryWordCount = 0;
- mSplitWordsCount = 0;
- mCorrectedWordsCount = 0;
- mGesturesInputCount = 0;
- mGesturesDeletedCount = 0;
- mManualSuggestionsCount = 0;
- mRevertCommitsCount = 0;
- mAutoCorrectionsCount = 0;
- mIsEmptyUponStarting = true;
- mIsEmptinessStateKnown = false;
- mKeyCounter.reset();
- mBeforeDeleteKeyCounter.reset();
- mDuringRepeatedDeleteKeysCounter.reset();
- mAfterDeleteKeyCounter.reset();
- mGesturesCharsCount = 0;
- mGesturesDeletedCount = 0;
- mPublishableCount = 0;
- mUnpublishableStoppingCount = 0;
- mUnpublishableIncorrectWordCount = 0;
- mUnpublishableSampledTooRecently = 0;
- mUnpublishableDictionaryUnavailable = 0;
- mUnpublishableMayContainDigit = 0;
- mUnpublishableNotInDictionary = 0;
-
- mLastTapTime = 0;
- mIsLastKeyDeleteKey = false;
- }
-
- public void recordChar(int codePoint, long time) {
- if (DEBUG) {
- Log.d(TAG, "recordChar() called");
- }
- if (codePoint == Constants.CODE_DELETE) {
- mDeleteKeyCount++;
- recordUserAction(time, true /* isDeletion */);
- } else {
- mCharCount++;
- if (Character.isDigit(codePoint)) {
- mNumberCount++;
- }
- if (Character.isLetter(codePoint)) {
- mLetterCount++;
- }
- if (Character.isSpaceChar(codePoint)) {
- mSpaceCount++;
- }
- recordUserAction(time, false /* isDeletion */);
- }
- }
-
- public void recordWordEntered(final boolean isDictionaryWord,
- final boolean containsCorrection) {
- mWordCount++;
- if (isDictionaryWord) {
- mDictionaryWordCount++;
- }
- if (containsCorrection) {
- mCorrectedWordsCount++;
- }
- }
-
- public void recordSplitWords() {
- mSplitWordsCount++;
- }
-
- public void recordGestureInput(final int numCharsEntered, final long time) {
- mGesturesInputCount++;
- mGesturesCharsCount += numCharsEntered;
- recordUserAction(time, false /* isDeletion */);
- }
-
- public void setIsEmptyUponStarting(final boolean isEmpty) {
- mIsEmptyUponStarting = isEmpty;
- mIsEmptinessStateKnown = true;
- }
-
- public void recordGestureDelete(final int length, final long time) {
- mGesturesDeletedCount++;
- recordUserAction(time, true /* isDeletion */);
- }
-
- public void recordManualSuggestion(final long time) {
- mManualSuggestionsCount++;
- recordUserAction(time, false /* isDeletion */);
- }
-
- public void recordAutoCorrection(final long time) {
- mAutoCorrectionsCount++;
- recordUserAction(time, false /* isDeletion */);
- }
-
- public void recordRevertCommit(final long time) {
- mRevertCommitsCount++;
- recordUserAction(time, true /* isDeletion */);
- }
-
- private void recordUserAction(final long time, final boolean isDeletion) {
- final long delta = time - mLastTapTime;
- if (isDeletion) {
- if (delta < MIN_DELETION_INTERMISSION) {
- if (mIsLastKeyDeleteKey) {
- mDuringRepeatedDeleteKeysCounter.add(delta);
- } else {
- mBeforeDeleteKeyCounter.add(delta);
- }
- } else {
- ResearchLogger.onUserPause(delta);
- }
- } else {
- if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
- mAfterDeleteKeyCounter.add(delta);
- } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
- mKeyCounter.add(delta);
- } else {
- ResearchLogger.onUserPause(delta);
- }
- }
- 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
deleted file mode 100644
index c7ea3e69d..000000000
--- a/java/src/com/android/inputmethod/research/Uploader.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.BatteryManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-/**
- * Manages the uploading of ResearchLog files.
- */
-public final class Uploader {
- private static final String TAG = Uploader.class.getSimpleName();
- 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_UPLOAD = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- private static final int BUF_SIZE = 1024 * 8;
-
- private final Context mContext;
- private final ResearchLogDirectory mResearchLogDirectory;
- private final URL mUrl;
-
- public Uploader(final Context context) {
- mContext = context;
- mResearchLogDirectory = new ResearchLogDirectory(context);
-
- final String urlString = context.getString(R.string.research_logger_upload_url);
- if (TextUtils.isEmpty(urlString)) {
- mUrl = null;
- return;
- }
- URL url = null;
- try {
- url = new URL(urlString);
- } catch (final MalformedURLException e) {
- Log.e(TAG, "Bad URL for uploading", e);
- }
- mUrl = url;
- }
-
- public boolean isPossibleToUpload() {
- return hasUploadingPermission() && mUrl != null && !IS_INHIBITING_UPLOAD;
- }
-
- private boolean hasUploadingPermission() {
- final PackageManager packageManager = mContext.getPackageManager();
- return packageManager.checkPermission(Manifest.permission.INTERNET,
- mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED;
- }
-
- public boolean isConvenientToUpload() {
- return isExternallyPowered() && hasWifiConnection();
- }
-
- private boolean isExternallyPowered() {
- final Intent intent = mContext.registerReceiver(null, new IntentFilter(
- Intent.ACTION_BATTERY_CHANGED));
- final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
- return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
- || pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
- }
-
- private boolean hasWifiConnection() {
- final ConnectivityManager manager =
- (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
- return wifiInfo.isConnected();
- }
-
- public void doUpload() {
- final File[] files = mResearchLogDirectory.getUploadableLogFiles();
- if (files == null) return;
- for (final File file : files) {
- uploadFile(file);
- }
- }
-
- private void uploadFile(final File file) {
- if (DEBUG) {
- Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
- }
- final int contentLength = (int) file.length();
- HttpURLConnection connection = null;
- InputStream fileInputStream = null;
- try {
- fileInputStream = new FileInputStream(file);
- connection = (HttpURLConnection) mUrl.openConnection();
- connection.setRequestMethod("PUT");
- connection.setDoOutput(true);
- connection.setFixedLengthStreamingMode(contentLength);
- final OutputStream outputStream = connection.getOutputStream();
- uploadContents(fileInputStream, outputStream);
- if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
- Log.d(TAG, "upload failed: " + connection.getResponseCode());
- final InputStream netInputStream = connection.getInputStream();
- final BufferedReader reader = new BufferedReader(new InputStreamReader(
- netInputStream));
- String line;
- while ((line = reader.readLine()) != null) {
- Log.d(TAG, "| " + reader.readLine());
- }
- reader.close();
- return;
- }
- file.delete();
- if (DEBUG) {
- Log.d(TAG, "upload successful");
- }
- } catch (final IOException e) {
- Log.e(TAG, "Exception uploading file", e);
- } finally {
- if (fileInputStream != null) {
- try {
- fileInputStream.close();
- } catch (final IOException e) {
- Log.e(TAG, "Exception closing uploaded file", e);
- }
- }
- if (connection != null) {
- connection.disconnect();
- }
- }
- }
-
- private static void uploadContents(final InputStream is, final OutputStream os)
- throws IOException {
- // TODO: Switch to NIO.
- final byte[] buf = new byte[BUF_SIZE];
- int numBytesRead;
- while ((numBytesRead = is.read(buf)) != -1) {
- os.write(buf, 0, numBytesRead);
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
deleted file mode 100644
index fd3f2f60e..000000000
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.app.AlarmManager;
-import android.app.IntentService;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.SystemClock;
-
-/**
- * Service to invoke the uploader.
- *
- * Can be regularly invoked, invoked on boot, etc.
- */
-public final class UploaderService extends IntentService {
- private static final String TAG = UploaderService.class.getSimpleName();
- public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
- public static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
- + ".extra.UPLOAD_UNCONDITIONALLY";
-
- public UploaderService() {
- super("Research Uploader Service");
- }
-
- @Override
- protected void onHandleIntent(final Intent intent) {
- // We may reach this point either because the alarm fired, or because the system explicitly
- // requested that an Upload occur. In the latter case, we want to cancel the alarm in case
- // it's about to fire.
- cancelAndRescheduleUploadingService(this, false /* needsRescheduling */);
-
- final Uploader uploader = new Uploader(this);
- if (!uploader.isPossibleToUpload()) return;
- if (isUploadingUnconditionally(intent.getExtras()) || uploader.isConvenientToUpload()) {
- uploader.doUpload();
- }
- cancelAndRescheduleUploadingService(this, true /* needsRescheduling */);
- }
-
- private boolean isUploadingUnconditionally(final Bundle bundle) {
- if (bundle == null) return false;
- if (bundle.containsKey(EXTRA_UPLOAD_UNCONDITIONALLY)) {
- return bundle.getBoolean(EXTRA_UPLOAD_UNCONDITIONALLY);
- }
- return false;
- }
-
- /**
- * Arrange for the UploaderService to be run on a regular basis.
- *
- * Any existing scheduled invocation of UploaderService is removed and optionally rescheduled.
- * This may cause problems if this method is called so often that no scheduled invocation is
- * ever run. But if the delay is short enough that it will go off when the user is sleeping,
- * then there should be no starvation.
- *
- * @param context {@link Context} object
- * @param needsRescheduling whether to schedule a future intent to be delivered to this service
- */
- public static void cancelAndRescheduleUploadingService(final Context context,
- final boolean needsRescheduling) {
- final Intent intent = new Intent(context, UploaderService.class);
- final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
- final AlarmManager alarmManager = (AlarmManager) context.getSystemService(
- Context.ALARM_SERVICE);
- alarmManager.cancel(pendingIntent);
- if (needsRescheduling) {
- alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()
- + UploaderService.RUN_INTERVAL, pendingIntent);
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ui/SplashScreen.java b/java/src/com/android/inputmethod/research/ui/SplashScreen.java
deleted file mode 100644
index 78ed668d1..000000000
--- a/java/src/com/android/inputmethod/research/ui/SplashScreen.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.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();
- }
-}