aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
-rw-r--r--java/src/com/android/inputmethod/latin/AdditionalSubtype.java18
-rw-r--r--java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java69
-rw-r--r--java/src/com/android/inputmethod/latin/AssetFileAddress.java25
-rw-r--r--java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java95
-rw-r--r--java/src/com/android/inputmethod/latin/AutoCorrection.java23
-rw-r--r--java/src/com/android/inputmethod/latin/BackupAgent.java23
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java134
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java272
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java210
-rw-r--r--java/src/com/android/inputmethod/latin/BoundedTreeSet.java16
-rw-r--r--java/src/com/android/inputmethod/latin/CapsModeUtils.java266
-rw-r--r--java/src/com/android/inputmethod/latin/CollectionUtils.java5
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java98
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java35
-rw-r--r--java/src/com/android/inputmethod/latin/CoordinateUtils.java49
-rw-r--r--java/src/com/android/inputmethod/latin/DebugSettings.java68
-rw-r--r--java/src/com/android/inputmethod/latin/DebugSettingsActivity.java16
-rw-r--r--java/src/com/android/inputmethod/latin/DicTraverseSession.java6
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java31
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java26
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java81
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java355
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java16
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java44
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java97
-rw-r--r--java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java163
-rw-r--r--java/src/com/android/inputmethod/latin/FileTransforms.java16
-rw-r--r--java/src/com/android/inputmethod/latin/ImfUtils.java186
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java96
-rw-r--r--java/src/com/android/inputmethod/latin/InputPointers.java46
-rw-r--r--java/src/com/android/inputmethod/latin/InputTypeUtils.java29
-rw-r--r--java/src/com/android/inputmethod/latin/InputView.java16
-rw-r--r--java/src/com/android/inputmethod/latin/IntentUtils.java45
-rw-r--r--java/src/com/android/inputmethod/latin/LastComposedWord.java30
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java959
-rw-r--r--java/src/com/android/inputmethod/latin/LatinImeLogger.java2
-rw-r--r--java/src/com/android/inputmethod/latin/LocaleUtils.java21
-rw-r--r--java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java32
-rw-r--r--java/src/com/android/inputmethod/latin/ResizableIntArray.java16
-rw-r--r--java/src/com/android/inputmethod/latin/ResourceUtils.java56
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java186
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java250
-rw-r--r--java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java147
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java548
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsActivity.java18
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsFragment.java363
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsValues.java417
-rw-r--r--java/src/com/android/inputmethod/latin/StringUtils.java340
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeLocale.java156
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java109
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java74
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java51
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java9
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/TargetApplicationGetter.java16
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java51
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java38
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictionary.java69
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java3
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java19
-rw-r--r--java/src/com/android/inputmethod/latin/VibratorUtils.java50
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java64
-rw-r--r--java/src/com/android/inputmethod/latin/WordListInfo.java18
-rw-r--r--java/src/com/android/inputmethod/latin/XmlParseUtils.java16
-rw-r--r--java/src/com/android/inputmethod/latin/define/JniLibName.java16
-rw-r--r--java/src/com/android/inputmethod/latin/define/ProductionFlag.java23
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java786
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java224
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java16
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java62
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java96
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/MakedictLog.java16
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java16
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java16
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Word.java16
-rw-r--r--java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java122
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupActivity.java357
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java56
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java64
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java16
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java16
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java72
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java16
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java20
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java285
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java18
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java18
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java27
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java193
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java274
91 files changed, 6160 insertions, 3492 deletions
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
index 509fc1ba3..99b95ea98 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -45,7 +45,7 @@ public final class AdditionalSubtype {
final String keyboardLayoutSetName, final String extraValue) {
final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName;
final String layoutDisplayNameExtraValue;
- if (Build.VERSION.SDK_INT >= /* JELLY_BEAN */ 15
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
&& SubtypeLocale.isExceptionalLocale(localeString)) {
final String layoutDisplayName = SubtypeLocale.getKeyboardLayoutSetDisplayName(
keyboardLayoutSetName);
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
index 939451899..ff5e33949 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -1,17 +1,17 @@
-/**
+/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -44,12 +44,11 @@ import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.Toast;
-import com.android.inputmethod.compat.CompatUtils;
-
import java.util.ArrayList;
import java.util.TreeSet;
public final class AdditionalSubtypeSettings extends PreferenceFragment {
+ private RichInputMethodManager mRichImm;
private SharedPreferences mPrefs;
private SubtypeLocaleAdapter mSubtypeLocaleAdapter;
private KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter;
@@ -63,6 +62,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
private static final String KEY_IS_SUBTYPE_ENABLER_NOTIFICATION_DIALOG_OPEN =
"is_subtype_enabler_notification_dialog_open";
private static final String KEY_SUBTYPE_FOR_SUBTYPE_ENABLER = "subtype_for_subtype_enabler";
+
static final class SubtypeLocaleItem extends Pair<String, String>
implements Comparable<SubtypeLocaleItem> {
public SubtypeLocaleItem(final String localeString, final String displayName) {
@@ -70,7 +70,8 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
}
public SubtypeLocaleItem(final String localeString) {
- this(localeString, SubtypeLocale.getSubtypeLocaleDisplayName(localeString));
+ this(localeString,
+ SubtypeLocale.getSubtypeLocaleDisplayNameInSystemLocale(localeString));
}
@Override
@@ -93,14 +94,15 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet();
- final InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context);
+ final InputMethodInfo imi = RichInputMethodManager.getInstance()
+ .getInputMethodInfoOfThisIme();
final int count = imi.getSubtypeCount();
for (int i = 0; i < count; i++) {
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
if (DEBUG_SUBTYPE_ID) {
android.util.Log.d(TAG, String.format("%-6s 0x%08x %11d %s",
subtype.getLocale(), subtype.hashCode(), subtype.hashCode(),
- SubtypeLocale.getSubtypeDisplayName(subtype, context.getResources())));
+ SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype)));
}
if (subtype.containsExtraValueKey(ASCII_CAPABLE)) {
items.add(createItem(context, subtype.getLocale()));
@@ -202,8 +204,8 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
setDialogTitle(R.string.add_style);
setKey(KEY_NEW_SUBTYPE);
} else {
- final String displayName = SubtypeLocale.getSubtypeDisplayName(
- subtype, getContext().getResources());
+ final String displayName =
+ SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype);
setTitle(displayName);
setDialogTitle(displayName);
setKey(KEY_PREFIX + subtype.getLocale() + "_"
@@ -383,10 +385,11 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mPrefs = getPreferenceManager().getSharedPreferences();
+ RichInputMethodManager.init(getActivity());
+ mRichImm = RichInputMethodManager.getInstance();
addPreferencesFromResource(R.xml.additional_subtype_settings);
setHasOptionsMenu(true);
-
- mPrefs = getPreferenceManager().getSharedPreferences();
}
@Override
@@ -396,7 +399,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
mKeyboardLayoutSetAdapter = new KeyboardLayoutSetAdapter(context);
final String prefSubtypes =
- SettingsValues.getPrefAdditionalSubtypes(mPrefs, getResources());
+ Settings.readPrefAdditionalSubtypes(mPrefs, getResources());
setPrefSubtypes(prefSubtypes, context);
mIsAddingNewSubtype = (savedInstanceState != null)
@@ -439,7 +442,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
mIsAddingNewSubtype = false;
final PreferenceGroup group = getPreferenceScreen();
group.removePreference(subtypePref);
- ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), getSubtypes());
+ mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
}
@Override
@@ -449,7 +452,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
return;
}
if (findDuplicatedSubtype(subtype) == null) {
- ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), getSubtypes());
+ mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
return;
}
@@ -466,7 +469,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
mIsAddingNewSubtype = false;
final InputMethodSubtype subtype = subtypePref.getSubtype();
if (findDuplicatedSubtype(subtype) == null) {
- ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), getSubtypes());
+ mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
mSubtypePreferenceKeyForSubtypeEnabler = subtypePref.getKey();
mSubtypeEnablerNotificationDialog = createDialog(subtypePref);
mSubtypeEnablerNotificationDialog.show();
@@ -494,15 +497,15 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
final Context context = getActivity();
final Resources res = context.getResources();
final String message = res.getString(R.string.custom_input_style_already_exists,
- SubtypeLocale.getSubtypeDisplayName(subtype, res));
+ SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype));
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
private InputMethodSubtype findDuplicatedSubtype(final InputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
final String keyboardLayoutSetName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
- return ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet(
- getActivity(), localeString, keyboardLayoutSetName);
+ return mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+ localeString, keyboardLayoutSetName);
}
private AlertDialog createDialog(
@@ -514,8 +517,8 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
.setPositiveButton(R.string.enable, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- final Intent intent = CompatUtils.getInputLanguageSelectionIntent(
- ImfUtils.getInputMethodIdOfThisIme(getActivity()),
+ final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
+ mRichImm.getInputMethodIdOfThisIme(),
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -560,20 +563,14 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
@Override
public void onPause() {
super.onPause();
- final String oldSubtypes = SettingsValues.getPrefAdditionalSubtypes(mPrefs, getResources());
+ final String oldSubtypes = Settings.readPrefAdditionalSubtypes(mPrefs, getResources());
final InputMethodSubtype[] subtypes = getSubtypes();
final String prefSubtypes = AdditionalSubtype.createPrefSubtypes(subtypes);
if (prefSubtypes.equals(oldSubtypes)) {
return;
}
-
- final SharedPreferences.Editor editor = mPrefs.edit();
- try {
- editor.putString(Settings.PREF_CUSTOM_INPUT_STYLES, prefSubtypes);
- } finally {
- editor.apply();
- }
- ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), subtypes);
+ Settings.writePrefAdditionalSubtypes(mPrefs, prefSubtypes);
+ mRichImm.setAdditionalInputMethodSubtypes(subtypes);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
index 29c733ba6..47c750f54 100644
--- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -35,11 +35,14 @@ final class AssetFileAddress {
mLength = length;
}
+ public static AssetFileAddress makeFromFile(final File file) {
+ if (!file.isFile()) return null;
+ return new AssetFileAddress(file.getAbsolutePath(), 0L, file.length());
+ }
+
public static AssetFileAddress makeFromFileName(final String filename) {
if (null == filename) return null;
- final File f = new File(filename);
- if (!f.isFile()) return null;
- return new AssetFileAddress(filename, 0l, f.length());
+ return makeFromFile(new File(filename));
}
public static AssetFileAddress makeFromFileNameAndOffset(final String filename,
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 59ef5e09f..986b1a178 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -1,29 +1,27 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
import android.content.Context;
import android.media.AudioManager;
+import android.os.Vibrator;
import android.view.HapticFeedbackConstants;
import android.view.View;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.latin.VibratorUtils;
-
/**
* This class gathers audio feedback and haptic feedback functions.
*
@@ -31,58 +29,82 @@ import com.android.inputmethod.latin.VibratorUtils;
* complexity of settings and the like.
*/
public final class AudioAndHapticFeedbackManager {
- final private SettingsValues mSettingsValues;
- final private AudioManager mAudioManager;
- final private VibratorUtils mVibratorUtils;
+ private AudioManager mAudioManager;
+ private Vibrator mVibrator;
+
+ private SettingsValues mSettingsValues;
private boolean mSoundOn;
- public AudioAndHapticFeedbackManager(final LatinIME latinIme,
- final SettingsValues settingsValues) {
- mSettingsValues = settingsValues;
- mVibratorUtils = VibratorUtils.getInstance(latinIme);
- mAudioManager = (AudioManager) latinIme.getSystemService(Context.AUDIO_SERVICE);
- mSoundOn = reevaluateIfSoundIsOn();
+ private static final AudioAndHapticFeedbackManager sInstance =
+ new AudioAndHapticFeedbackManager();
+
+ public static AudioAndHapticFeedbackManager getInstance() {
+ return sInstance;
+ }
+
+ private AudioAndHapticFeedbackManager() {
+ // Intentional empty constructor for singleton.
+ }
+
+ public static void init(final Context context) {
+ sInstance.initInternal(context);
+ }
+
+ private void initInternal(final Context context) {
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
}
public void hapticAndAudioFeedback(final int primaryCode,
final View viewToPerformHapticFeedbackOn) {
- vibrate(viewToPerformHapticFeedbackOn);
+ vibrateInternal(viewToPerformHapticFeedbackOn);
playKeyClick(primaryCode);
}
+ public boolean hasVibrator() {
+ return mVibrator != null && mVibrator.hasVibrator();
+ }
+
+ public void vibrate(final long milliseconds) {
+ if (mVibrator == null) {
+ return;
+ }
+ mVibrator.vibrate(milliseconds);
+ }
+
private boolean reevaluateIfSoundIsOn() {
- if (!mSettingsValues.mSoundOn || mAudioManager == null) {
+ if (mSettingsValues == null || !mSettingsValues.mSoundOn || mAudioManager == null) {
return false;
- } else {
- return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL;
}
+ return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL;
}
- private void playKeyClick(int primaryCode) {
+ private void playKeyClick(final int primaryCode) {
// if mAudioManager is null, we can't play a sound anyway, so return
- if (mAudioManager == null) return;
+ if (mAudioManager == null) {
+ return;
+ }
if (mSoundOn) {
final int sound;
switch (primaryCode) {
- case Keyboard.CODE_DELETE:
+ case Constants.CODE_DELETE:
sound = AudioManager.FX_KEYPRESS_DELETE;
break;
- case Keyboard.CODE_ENTER:
+ case Constants.CODE_ENTER:
sound = AudioManager.FX_KEYPRESS_RETURN;
break;
- case Keyboard.CODE_SPACE:
+ case Constants.CODE_SPACE:
sound = AudioManager.FX_KEYPRESS_SPACEBAR;
break;
default:
sound = AudioManager.FX_KEYPRESS_STANDARD;
break;
}
- mAudioManager.playSoundEffect(sound, mSettingsValues.mFxVolume);
+ mAudioManager.playSoundEffect(sound, mSettingsValues.mKeypressSoundVolume);
}
}
- // TODO: make this private when LatinIME does not call it any more
- public void vibrate(final View viewToPerformHapticFeedbackOn) {
+ private void vibrateInternal(final View viewToPerformHapticFeedbackOn) {
if (!mSettingsValues.mVibrateOn) {
return;
}
@@ -93,9 +115,14 @@ public final class AudioAndHapticFeedbackManager {
HapticFeedbackConstants.KEYBOARD_TAP,
HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
- } else if (mVibratorUtils != null) {
- mVibratorUtils.vibrate(mSettingsValues.mKeypressVibrationDuration);
+ return;
}
+ vibrate(mSettingsValues.mKeypressVibrationDuration);
+ }
+
+ public void onSettingsChanged(final SettingsValues settingsValues) {
+ mSettingsValues = settingsValues;
+ mSoundOn = reevaluateIfSoundIsOn();
}
public void onRingerModeChanged() {
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index 84fad158f..fa35922b0 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -33,11 +33,11 @@ public final class AutoCorrection {
}
public static boolean isValidWord(final ConcurrentHashMap<String, Dictionary> dictionaries,
- CharSequence word, boolean ignoreCase) {
+ final String word, final boolean ignoreCase) {
if (TextUtils.isEmpty(word)) {
return false;
}
- final CharSequence lowerCasedWord = word.toString().toLowerCase();
+ final String lowerCasedWord = word.toLowerCase();
for (final String key : dictionaries.keySet()) {
final Dictionary dictionary = dictionaries.get(key);
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
@@ -57,7 +57,7 @@ public final class AutoCorrection {
}
public static int getMaxFrequency(final ConcurrentHashMap<String, Dictionary> dictionaries,
- CharSequence word) {
+ final String word) {
if (TextUtils.isEmpty(word)) {
return Dictionary.NOT_A_PROBABILITY;
}
@@ -76,12 +76,13 @@ public final class AutoCorrection {
// Returns true if this is in any of the dictionaries.
public static boolean isInTheDictionary(
final ConcurrentHashMap<String, Dictionary> dictionaries,
- final CharSequence word, final boolean ignoreCase) {
+ final String word, final boolean ignoreCase) {
return isValidWord(dictionaries, word, ignoreCase);
}
- public static boolean suggestionExceedsAutoCorrectionThreshold(SuggestedWordInfo suggestion,
- CharSequence consideredWord, float autoCorrectionThreshold) {
+ public static boolean suggestionExceedsAutoCorrectionThreshold(
+ final SuggestedWordInfo suggestion, final String consideredWord,
+ final float autoCorrectionThreshold) {
if (null != suggestion) {
// Shortlist a whitelisted word
if (suggestion.mKind == SuggestedWordInfo.KIND_WHITELIST) return true;
@@ -89,8 +90,7 @@ public final class AutoCorrection {
// TODO: when the normalized score of the first suggestion is nearly equals to
// the normalized score of the second suggestion, behave less aggressive.
final float normalizedScore = BinaryDictionary.calcNormalizedScore(
- consideredWord.toString(), suggestion.mWord.toString(),
- autoCorrectionSuggestionScore);
+ consideredWord, suggestion.mWord, autoCorrectionSuggestionScore);
if (DBG) {
Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + ","
+ autoCorrectionSuggestionScore + ", " + normalizedScore
@@ -100,8 +100,7 @@ public final class AutoCorrection {
if (DBG) {
Log.d(TAG, "Auto corrected by S-threshold.");
}
- return !shouldBlockAutoCorrectionBySafetyNet(consideredWord.toString(),
- suggestion.mWord);
+ return !shouldBlockAutoCorrectionBySafetyNet(consideredWord, suggestion.mWord);
}
}
return false;
@@ -110,7 +109,7 @@ public final class AutoCorrection {
// TODO: Resolve the inconsistencies between the native auto correction algorithms and
// this safety net
public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord,
- final CharSequence suggestion) {
+ final String suggestion) {
// Safety net for auto correction.
// Actually if we hit this safety net, it's a bug.
// If user selected aggressive auto correction mode, there is no need to use the safety
@@ -123,7 +122,7 @@ public final class AutoCorrection {
}
final int maxEditDistanceOfNativeDictionary =
(typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
- final int distance = BinaryDictionary.editDistance(typedWord, suggestion.toString());
+ final int distance = BinaryDictionary.editDistance(typedWord, suggestion);
if (DBG) {
Log.d(TAG, "Autocorrected edit distance = " + distance
+ ", " + maxEditDistanceOfNativeDictionary);
diff --git a/java/src/com/android/inputmethod/latin/BackupAgent.java b/java/src/com/android/inputmethod/latin/BackupAgent.java
index 0beb088ac..1f044618a 100644
--- a/java/src/com/android/inputmethod/latin/BackupAgent.java
+++ b/java/src/com/android/inputmethod/latin/BackupAgent.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2008 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
- *
+ *
+ * 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.
+ * 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;
@@ -23,7 +23,6 @@ import android.app.backup.SharedPreferencesBackupHelper;
* Backs up the Latin IME shared preferences.
*/
public final class BackupAgent extends BackupAgentHelper {
-
@Override
public void onCreate() {
addHelper("shared_pref", new SharedPreferencesBackupHelper(this,
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 7184f1d8a..ab2a12fd0 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -1,22 +1,21 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
-import android.content.Context;
import android.text.TextUtils;
import android.util.SparseArray;
@@ -31,32 +30,20 @@ import java.util.Locale;
* Implements a static, compacted, binary dictionary of standard words.
*/
public final class BinaryDictionary extends Dictionary {
-
+ private static final String TAG = BinaryDictionary.class.getSimpleName();
public static final String DICTIONARY_PACK_AUTHORITY =
"com.android.inputmethod.latin.dictionarypack";
- /**
- * There is a difference between what java and native code can handle.
- * This value should only be used in BinaryDictionary.java
- * It is necessary to keep it at this value because some languages e.g. German have
- * really long words.
- */
- public static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
- public static final int MAX_WORDS = 18;
- public static final int MAX_SPACES = 16;
-
- private static final String TAG = BinaryDictionary.class.getSimpleName();
- private static final int MAX_PREDICTIONS = 60;
- private static final int MAX_RESULTS = Math.max(MAX_PREDICTIONS, MAX_WORDS);
-
- private static final int TYPED_LETTER_MULTIPLIER = 2;
+ // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
+ private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
+ // Must be equal to MAX_RESULTS in native/jni/src/defines.h
+ private static final int MAX_RESULTS = 18;
private long mNativeDict;
private final Locale mLocale;
private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
- // TODO: The below should be int[] mOutputCodePoints
- private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_RESULTS];
- private final int[] mSpaceIndices = new int[MAX_SPACES];
+ private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS];
+ private final int[] mSpaceIndices = new int[MAX_RESULTS];
private final int[] mOutputScores = new int[MAX_RESULTS];
private final int[] mOutputTypes = new int[MAX_RESULTS];
@@ -67,7 +54,7 @@ public final class BinaryDictionary extends Dictionary {
// TODO: There should be a way to remove used DicTraverseSession objects from
// {@code mDicTraverseSessions}.
- private DicTraverseSession getTraverseSession(int traverseSessionId) {
+ private DicTraverseSession getTraverseSession(final int traverseSessionId) {
synchronized(mDicTraverseSessions) {
DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
if (traverseSession == null) {
@@ -84,16 +71,13 @@ public final class BinaryDictionary extends Dictionary {
/**
* Constructor for the binary dictionary. This is supposed to be called from the
* dictionary factory.
- * All implementations should pass null into flagArray, except for testing purposes.
- * @param context the context to access the environment from.
* @param filename the name of the file to read through native code.
* @param offset the offset of the dictionary data within the file.
* @param length the length of the binary data.
* @param useFullEditDistance whether to use the full edit distance in suggestions
* @param dictType the dictionary type, as a human-readable string
*/
- public BinaryDictionary(final Context context,
- final String filename, final long offset, final long length,
+ public BinaryDictionary(final String filename, final long offset, final long length,
final boolean useFullEditDistance, final Locale locale, final String dictType) {
super(dictType);
mLocale = locale;
@@ -105,41 +89,39 @@ public final class BinaryDictionary extends Dictionary {
JniUtils.loadNativeLibrary();
}
- private native long openNative(String sourceDir, long dictOffset, long dictSize,
- int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords,
- int maxPredictions);
- private native void closeNative(long dict);
- private native int getFrequencyNative(long dict, int[] word);
- private native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
- private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession,
- int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds,
- int[] inputCodePoints, int codesSize, int commitPoint, boolean isGesture,
- int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars,
- int[] outputScores, int[] outputIndices, int[] outputTypes);
- private static native float calcNormalizedScoreNative(char[] before, char[] after, int score);
- private static native int editDistanceNative(char[] before, char[] after);
+ private static native long openNative(String sourceDir, long dictOffset, long dictSize);
+ private static native void closeNative(long dict);
+ private static native int getFrequencyNative(long dict, int[] word);
+ private static native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
+ private static native int getSuggestionsNative(long dict, long proximityInfo,
+ long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
+ int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint,
+ boolean isGesture, int[] prevWordCodePointArray, boolean useFullEditDistance,
+ int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes);
+ private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
+ private static native int editDistanceNative(int[] before, int[] after);
// TODO: Move native dict into session
- private final void loadDictionary(String path, long startOffset, long length) {
- mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER,
- FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PREDICTIONS);
+ private final void loadDictionary(final String path, final long startOffset,
+ final long length) {
+ mNativeDict = openNative(path, startOffset, length);
}
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo) {
+ final String prevWord, final ProximityInfo proximityInfo) {
return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, 0);
}
@Override
public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) {
+ final String prevWord, final ProximityInfo proximityInfo, int sessionId) {
if (!isValidDictionary()) return null;
Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
// TODO: toLowerCase in the native code
final int[] prevWordCodePointArray = (null == prevWord)
- ? null : StringUtils.toCodePointArray(prevWord.toString());
+ ? null : StringUtils.toCodePointArray(prevWord);
final int composerSize = composer.size();
final boolean isGesture = composer.isBatchMode();
@@ -151,67 +133,69 @@ public final class BinaryDictionary extends Dictionary {
}
final InputPointers ips = composer.getInputPointers();
- final int codesSize = isGesture ? ips.getPointerSize() : composerSize;
+ final int inputSize = isGesture ? ips.getPointerSize() : composerSize;
// proximityInfo and/or prevWordForBigrams may not be null.
- final int tmpCount = getSuggestionsNative(mNativeDict,
- proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(),
- ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
- mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
- mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes);
- final int count = Math.min(tmpCount, MAX_PREDICTIONS);
-
+ final int count = getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
+ getTraverseSession(sessionId).getSession(), ips.getXCoordinates(),
+ ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints,
+ inputSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
+ mUseFullEditDistance, mOutputCodePoints, mOutputScores, mSpaceIndices,
+ mOutputTypes);
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
for (int j = 0; j < count; ++j) {
if (composerSize > 0 && mOutputScores[j] < 1) break;
final int start = j * MAX_WORD_LENGTH;
int len = 0;
- while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
+ while (len < MAX_WORD_LENGTH && mOutputCodePoints[start + len] != 0) {
++len;
}
if (len > 0) {
final int score = SuggestedWordInfo.KIND_WHITELIST == mOutputTypes[j]
? SuggestedWordInfo.MAX_SCORE : mOutputScores[j];
- suggestions.add(new SuggestedWordInfo(
- new String(mOutputChars, start, len), score, mOutputTypes[j], mDictType));
+ suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
+ score, mOutputTypes[j], mDictType));
}
}
return suggestions;
}
- /* package for test */ boolean isValidDictionary() {
+ public boolean isValidDictionary() {
return mNativeDict != 0;
}
- public static float calcNormalizedScore(String before, String after, int score) {
- return calcNormalizedScoreNative(before.toCharArray(), after.toCharArray(), score);
+ public static float calcNormalizedScore(final String before, final String after,
+ final int score) {
+ return calcNormalizedScoreNative(StringUtils.toCodePointArray(before),
+ StringUtils.toCodePointArray(after), score);
}
- public static int editDistance(String before, String after) {
+ public static int editDistance(final String before, final String after) {
if (before == null || after == null) {
throw new IllegalArgumentException();
}
- return editDistanceNative(before.toCharArray(), after.toCharArray());
+ return editDistanceNative(StringUtils.toCodePointArray(before),
+ StringUtils.toCodePointArray(after));
}
@Override
- public boolean isValidWord(CharSequence word) {
+ public boolean isValidWord(final String word) {
return getFrequency(word) >= 0;
}
@Override
- public int getFrequency(CharSequence word) {
+ public int getFrequency(final String word) {
if (word == null) return -1;
- int[] codePoints = StringUtils.toCodePointArray(word.toString());
+ int[] codePoints = StringUtils.toCodePointArray(word);
return getFrequencyNative(mNativeDict, codePoints);
}
// TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
// calls when checking for changes in an entire dictionary.
- public boolean isValidBigram(CharSequence word1, CharSequence word2) {
+ public boolean isValidBigram(final String word1, final String word2) {
if (TextUtils.isEmpty(word1) || TextUtils.isEmpty(word2)) return false;
- int[] chars1 = StringUtils.toCodePointArray(word1.toString());
- int[] chars2 = StringUtils.toCodePointArray(word2.toString());
- return isValidBigramNative(mNativeDict, chars1, chars2);
+ final int[] codePoints1 = StringUtils.toCodePointArray(word1);
+ final int[] codePoints2 = StringUtils.toCodePointArray(word2);
+ return isValidBigramNative(mNativeDict, codePoints1, codePoints2);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 9a3f88f52..0d0ce5756 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -1,36 +1,43 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
+import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.latin.DictionaryInfoUtils.DictionaryInfo;
+
import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.File;
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.Collections;
import java.util.List;
import java.util.Locale;
@@ -55,11 +62,24 @@ public final class BinaryDictionaryFileDumper {
private static final String DICTIONARY_PROJECTION[] = { "id" };
- public static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt";
- public static final String QUERY_PARAMETER_TRUE = "true";
- public static final String QUERY_PARAMETER_DELETE_RESULT = "result";
- public static final String QUERY_PARAMETER_SUCCESS = "success";
- public static final String QUERY_PARAMETER_FAILURE = "failure";
+ private static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt";
+ private static final String QUERY_PARAMETER_TRUE = "true";
+ private static final String QUERY_PARAMETER_DELETE_RESULT = "result";
+ private static final String QUERY_PARAMETER_SUCCESS = "success";
+ private static final String QUERY_PARAMETER_FAILURE = "failure";
+
+ // Using protocol version 2 to communicate with the dictionary pack
+ private static final String QUERY_PARAMETER_PROTOCOL = "protocol";
+ private static final String QUERY_PARAMETER_PROTOCOL_VALUE = "2";
+
+ // The path fragment to append after the client ID for dictionary info requests.
+ private static final String QUERY_PATH_DICT_INFO = "dict";
+ // The path fragment to append after the client ID for dictionary datafile requests.
+ private static final String QUERY_PATH_DATAFILE = "datafile";
+ // The path fragment to append after the client ID for updating the metadata URI.
+ private static final String QUERY_PATH_METADATA = "metadata";
+ private static final String INSERT_METADATA_CLIENT_ID_COLUMN = "clientid";
+ private static final String INSERT_METADATA_METADATA_URI_COLUMN = "uri";
// Prevents this class to be accidentally instantiated.
private BinaryDictionaryFileDumper() {
@@ -78,27 +98,67 @@ public final class BinaryDictionaryFileDumper {
}
/**
+ * Gets the content URI builder for a specified type.
+ *
+ * Supported types include QUERY_PATH_DICT_INFO, which takes the locale as
+ * the extraPath argument, and QUERY_PATH_DATAFILE, which needs a wordlist ID
+ * as the extraPath argument.
+ *
+ * @param clientId the clientId to use
+ * @param contentProviderClient the instance of content provider client
+ * @param queryPathType the path element encoding the type
+ * @param extraPath optional extra argument for this type (typically word list id)
+ * @return a builder that can build the URI for the best supported protocol version
+ * @throws RemoteException if the client can't be contacted
+ */
+ private static Uri.Builder getContentUriBuilderForType(final String clientId,
+ final ContentProviderClient contentProviderClient, final String queryPathType,
+ final String extraPath) throws RemoteException {
+ // Check whether protocol v2 is supported by building a v2 URI and calling getType()
+ // on it. If this returns null, v2 is not supported.
+ final Uri.Builder uriV2Builder = getProviderUriBuilder(clientId);
+ uriV2Builder.appendPath(queryPathType);
+ uriV2Builder.appendPath(extraPath);
+ uriV2Builder.appendQueryParameter(QUERY_PARAMETER_PROTOCOL,
+ QUERY_PARAMETER_PROTOCOL_VALUE);
+ if (null != contentProviderClient.getType(uriV2Builder.build())) return uriV2Builder;
+ // Protocol v2 is not supported, so create and return the protocol v1 uri.
+ return getProviderUriBuilder(extraPath);
+ }
+
+ /**
* Queries a content provider for the list of word lists for a specific locale
* available to copy into Latin IME.
*/
private static List<WordListInfo> getWordListWordListInfos(final Locale locale,
final Context context, final boolean hasDefaultWordList) {
- final ContentResolver resolver = context.getContentResolver();
- final Uri.Builder builder = getProviderUriBuilder(locale.toString());
- if (!hasDefaultWordList) {
- builder.appendQueryParameter(QUERY_PARAMETER_MAY_PROMPT_USER, QUERY_PARAMETER_TRUE);
- }
- final Uri dictionaryPackUri = builder.build();
-
- final Cursor c = resolver.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null, null);
- if (null == c) return Collections.<WordListInfo>emptyList();
- if (c.getCount() <= 0 || !c.moveToFirst()) {
- c.close();
- return Collections.<WordListInfo>emptyList();
- }
+ final String clientId = context.getString(R.string.dictionary_pack_client_id);
+ final ContentProviderClient client = context.getContentResolver().
+ acquireContentProviderClient(getProviderUriBuilder("").build());
+ if (null == client) return Collections.<WordListInfo>emptyList();
try {
- final List<WordListInfo> list = CollectionUtils.newArrayList();
+ final Uri.Builder builder = getContentUriBuilderForType(clientId, client,
+ QUERY_PATH_DICT_INFO, locale.toString());
+ if (!hasDefaultWordList) {
+ builder.appendQueryParameter(QUERY_PARAMETER_MAY_PROMPT_USER,
+ QUERY_PARAMETER_TRUE);
+ }
+ final Uri queryUri = builder.build();
+ final boolean isProtocolV2 = (QUERY_PARAMETER_PROTOCOL_VALUE.equals(
+ queryUri.getQueryParameter(QUERY_PARAMETER_PROTOCOL)));
+
+ Cursor c = client.query(queryUri, DICTIONARY_PROJECTION, null, null, null);
+ if (isProtocolV2 && null == c) {
+ reinitializeClientRecordInDictionaryContentProvider(context, client, clientId);
+ c = client.query(queryUri, DICTIONARY_PROJECTION, null, null, null);
+ }
+ if (null == c) return Collections.<WordListInfo>emptyList();
+ if (c.getCount() <= 0 || !c.moveToFirst()) {
+ c.close();
+ return Collections.<WordListInfo>emptyList();
+ }
+ final ArrayList<WordListInfo> list = CollectionUtils.newArrayList();
do {
final String wordListId = c.getString(0);
final String wordListLocale = c.getString(1);
@@ -107,11 +167,20 @@ public final class BinaryDictionaryFileDumper {
} while (c.moveToNext());
c.close();
return list;
+ } catch (RemoteException e) {
+ // The documentation is unclear as to in which cases this may happen, but it probably
+ // happens when the content provider got suddenly killed because it crashed or because
+ // the user disabled it through Settings.
+ Log.e(TAG, "RemoteException: communication with the dictionary pack cut", e);
+ return Collections.<WordListInfo>emptyList();
} catch (Exception e) {
- // Just in case we hit a problem in communication with the dictionary pack.
- // We don't want to die.
- Log.e(TAG, "Exception communicating with the dictionary pack : " + e);
+ // A crash here is dangerous because crashing here would brick any encrypted device -
+ // we need the keyboard to be up and working to enter the password, so we don't want
+ // to die no matter what. So let's be as safe as possible.
+ Log.e(TAG, "Unexpected exception communicating with the dictionary pack", e);
return Collections.<WordListInfo>emptyList();
+ } finally {
+ client.release();
}
}
@@ -119,13 +188,18 @@ public final class BinaryDictionaryFileDumper {
/**
* Helper method to encapsulate exception handling.
*/
- private static AssetFileDescriptor openAssetFileDescriptor(final ContentResolver resolver,
- final Uri uri) {
+ private static AssetFileDescriptor openAssetFileDescriptor(
+ final ContentProviderClient providerClient, final Uri uri) {
try {
- return resolver.openAssetFileDescriptor(uri, "r");
+ return providerClient.openAssetFile(uri, "r");
} catch (FileNotFoundException e) {
- // I don't want to log the word list URI here for security concerns
- Log.e(TAG, "Could not find a word list from the dictionary provider.");
+ // I don't want to log the word list URI here for security concerns. The exception
+ // contains the name of the file, so let's not pass it to Log.e here.
+ Log.e(TAG, "Could not find a word list from the dictionary provider."
+ /* intentionally don't pass the exception (see comment above) */);
+ return null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't communicate with the dictionary pack", e);
return null;
}
}
@@ -135,9 +209,8 @@ public final class BinaryDictionaryFileDumper {
* to the cache file name designated by its id and locale, overwriting it if already present
* and creating it (and its containing directory) if necessary.
*/
- private static AssetFileAddress cacheWordList(final String id, final String locale,
- final ContentResolver resolver, final Context context) {
-
+ private static AssetFileAddress cacheWordList(final String wordlistId, final String locale,
+ final ContentProviderClient providerClient, final Context context) {
final int COMPRESSED_CRYPTED_COMPRESSED = 0;
final int CRYPTED_COMPRESSED = 1;
final int COMPRESSED_CRYPTED = 2;
@@ -147,23 +220,38 @@ public final class BinaryDictionaryFileDumper {
final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED;
final int MODE_MAX = NONE;
- final Uri.Builder wordListUriBuilder = getProviderUriBuilder(id);
- final String finalFileName = BinaryDictionaryGetter.getCacheFileName(id, locale, context);
- final String tempFileName = BinaryDictionaryGetter.getTempFileName(id, context);
+ final String clientId = context.getString(R.string.dictionary_pack_client_id);
+ final Uri.Builder wordListUriBuilder;
+ try {
+ wordListUriBuilder = getContentUriBuilderForType(clientId,
+ providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't communicate with the dictionary pack", e);
+ return null;
+ }
+ final String finalFileName =
+ DictionaryInfoUtils.getCacheFileName(wordlistId, locale, context);
+ String tempFileName;
+ try {
+ tempFileName = BinaryDictionaryGetter.getTempFileName(wordlistId, context);
+ } catch (IOException e) {
+ Log.e(TAG, "Can't open the temporary file", e);
+ return null;
+ }
for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) {
InputStream originalSourceStream = null;
InputStream inputStream = null;
InputStream uncompressedStream = null;
InputStream decryptedStream = null;
- BufferedInputStream bufferedStream = null;
+ BufferedInputStream bufferedInputStream = null;
File outputFile = null;
- FileOutputStream outputStream = null;
+ BufferedOutputStream bufferedOutputStream = null;
AssetFileDescriptor afd = null;
final Uri wordListUri = wordListUriBuilder.build();
try {
// Open input.
- afd = openAssetFileDescriptor(resolver, wordListUri);
+ afd = openAssetFileDescriptor(providerClient, wordListUri);
// If we can't open it at all, don't even try a number of times.
if (null == afd) return null;
originalSourceStream = afd.createInputStream();
@@ -172,7 +260,6 @@ public final class BinaryDictionaryFileDumper {
// Just to be sure, delete the file. This may fail silently, and return false: this
// is the right thing to do, as we just want to continue anyway.
outputFile.delete();
- outputStream = new FileOutputStream(outputFile);
// Get the appropriate decryption method for this try
switch (mode) {
case COMPRESSED_CRYPTED_COMPRESSED:
@@ -200,10 +287,11 @@ public final class BinaryDictionaryFileDumper {
inputStream = originalSourceStream;
break;
}
- bufferedStream = new BufferedInputStream(inputStream);
- checkMagicAndCopyFileTo(bufferedStream, outputStream);
- outputStream.flush();
- outputStream.close();
+ bufferedInputStream = new BufferedInputStream(inputStream);
+ bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
+ checkMagicAndCopyFileTo(bufferedInputStream, bufferedOutputStream);
+ bufferedOutputStream.flush();
+ bufferedOutputStream.close();
final File finalFile = new File(finalFileName);
finalFile.delete();
if (!outputFile.renameTo(finalFile)) {
@@ -211,15 +299,15 @@ public final class BinaryDictionaryFileDumper {
}
wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
QUERY_PARAMETER_SUCCESS);
- if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) {
+ if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
Log.e(TAG, "Could not have the dictionary pack delete a word list");
}
- BinaryDictionaryGetter.removeFilesWithIdExcept(context, id, finalFile);
+ BinaryDictionaryGetter.removeFilesWithIdExcept(context, wordlistId, finalFile);
// Success! Close files (through the finally{} clause) and return.
return AssetFileAddress.makeFromFileName(finalFileName);
} catch (Exception e) {
if (DEBUG) {
- Log.i(TAG, "Can't open word list in mode " + mode + " : " + e);
+ Log.i(TAG, "Can't open word list in mode " + mode, e);
}
if (null != outputFile) {
// This may or may not fail. The file may not have been created if the
@@ -235,14 +323,14 @@ public final class BinaryDictionaryFileDumper {
if (null != inputStream) inputStream.close();
if (null != uncompressedStream) uncompressedStream.close();
if (null != decryptedStream) decryptedStream.close();
- if (null != bufferedStream) bufferedStream.close();
+ if (null != bufferedInputStream) bufferedInputStream.close();
} catch (Exception e) {
- Log.e(TAG, "Exception while closing a file descriptor : " + e);
+ Log.e(TAG, "Exception while closing a file descriptor", e);
}
try {
- if (null != outputStream) outputStream.close();
+ if (null != bufferedOutputStream) bufferedOutputStream.close();
} catch (Exception e) {
- Log.e(TAG, "Exception while closing a file : " + e);
+ Log.e(TAG, "Exception while closing a file", e);
}
}
}
@@ -254,8 +342,12 @@ public final class BinaryDictionaryFileDumper {
// as invalid.
wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
QUERY_PARAMETER_FAILURE);
- if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) {
- Log.e(TAG, "In addition, we were unable to delete it.");
+ try {
+ if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
+ Log.e(TAG, "In addition, we were unable to delete it.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "In addition, communication with the dictionary provider was cut", e);
}
return null;
}
@@ -272,17 +364,27 @@ public final class BinaryDictionaryFileDumper {
*/
public static List<AssetFileAddress> cacheWordListsFromContentProvider(final Locale locale,
final Context context, final boolean hasDefaultWordList) {
- final ContentResolver resolver = context.getContentResolver();
- final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
- hasDefaultWordList);
- final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
- for (WordListInfo id : idList) {
- final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
- if (null != afd) {
- fileAddressList.add(afd);
+ final ContentProviderClient providerClient = context.getContentResolver().
+ acquireContentProviderClient(getProviderUriBuilder("").build());
+ if (null == providerClient) {
+ Log.e(TAG, "Can't establish communication with the dictionary provider");
+ return CollectionUtils.newArrayList();
+ }
+ try {
+ final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
+ hasDefaultWordList);
+ final ArrayList<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
+ for (WordListInfo id : idList) {
+ final AssetFileAddress afd =
+ cacheWordList(id.mId, id.mLocale, providerClient, context);
+ if (null != afd) {
+ fileAddressList.add(afd);
+ }
}
+ return fileAddressList;
+ } finally {
+ providerClient.release();
}
- return fileAddressList;
}
/**
@@ -295,9 +397,8 @@ public final class BinaryDictionaryFileDumper {
* @param input the stream to be copied.
* @param output an output stream to copy the data to.
*/
- // TODO: make output a BufferedOutputStream
- private static void checkMagicAndCopyFileTo(final BufferedInputStream input,
- final FileOutputStream output) throws FileNotFoundException, IOException {
+ public static void checkMagicAndCopyFileTo(final BufferedInputStream input,
+ final BufferedOutputStream output) throws FileNotFoundException, IOException {
// Check the magic number
final int length = MAGIC_NUMBER_VERSION_2.length;
final byte[] magicNumberBuffer = new byte[length];
@@ -318,4 +419,35 @@ public final class BinaryDictionaryFileDumper {
output.write(buffer, 0, readBytes);
input.close();
}
+
+ private static void reinitializeClientRecordInDictionaryContentProvider(final Context context,
+ final ContentProviderClient client, final String clientId) throws RemoteException {
+ final String metadataFileUri = context.getString(R.string.dictionary_pack_metadata_uri);
+ if (TextUtils.isEmpty(metadataFileUri)) return;
+ // Tell the content provider to reset all information about this client id
+ final Uri metadataContentUri = getProviderUriBuilder(clientId)
+ .appendPath(QUERY_PATH_METADATA)
+ .appendQueryParameter(QUERY_PARAMETER_PROTOCOL, QUERY_PARAMETER_PROTOCOL_VALUE)
+ .build();
+ client.delete(metadataContentUri, null, null);
+ // Update the metadata URI
+ final ContentValues metadataValues = new ContentValues();
+ metadataValues.put(INSERT_METADATA_CLIENT_ID_COLUMN, clientId);
+ metadataValues.put(INSERT_METADATA_METADATA_URI_COLUMN, metadataFileUri);
+ client.insert(metadataContentUri, metadataValues);
+
+ // Update the dictionary list.
+ final Uri dictionaryContentUriBase = getProviderUriBuilder(clientId)
+ .appendPath(QUERY_PATH_DICT_INFO)
+ .appendQueryParameter(QUERY_PARAMETER_PROTOCOL, QUERY_PARAMETER_PROTOCOL_VALUE)
+ .build();
+ final ArrayList<DictionaryInfo> dictionaryList =
+ DictionaryInfoUtils.getCurrentDictionaryFileNameAndVersionInfo(context);
+ final int length = dictionaryList.size();
+ for (int i = 0; i < length; ++i) {
+ final DictionaryInfo info = dictionaryList.get(i);
+ client.insert(Uri.withAppendedPath(dictionaryContentUriBase, info.mId),
+ info.toContentValues());
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index c747dc673..e913f2852 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -1,21 +1,22 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
import com.android.inputmethod.latin.makedict.FormatSpec;
@@ -55,7 +56,7 @@ final class BinaryDictionaryGetter {
private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs";
// Name of the category for the main dictionary
- private static final String MAIN_DICTIONARY_CATEGORY = "main";
+ public static final String MAIN_DICTIONARY_CATEGORY = "main";
public static final String ID_CATEGORY_SEPARATOR = ":";
// The key considered to read the version attribute in a dictionary file.
@@ -65,120 +66,21 @@ final class BinaryDictionaryGetter {
private BinaryDictionaryGetter() {}
/**
- * Returns whether we may want to use this character as part of a file name.
- *
- * This basically only accepts ascii letters and numbers, and rejects everything else.
- */
- private static boolean isFileNameCharacter(int codePoint) {
- if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit
- if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase
- if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase
- return codePoint == '_'; // Underscore
- }
-
- /**
- * Escapes a string for any characters that may be suspicious for a file or directory name.
- *
- * Concretely this does a sort of URL-encoding except it will encode everything that's not
- * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which
- * we cannot allow here)
- */
- // TODO: create a unit test for this method
- private static String replaceFileNameDangerousCharacters(final String name) {
- // This assumes '%' is fully available as a non-separator, normal
- // character in a file name. This is probably true for all file systems.
- final StringBuilder sb = new StringBuilder();
- final int nameLength = name.length();
- for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) {
- final int codePoint = name.codePointAt(i);
- if (isFileNameCharacter(codePoint)) {
- sb.appendCodePoint(codePoint);
- } else {
- // 6 digits - unicode is limited to 21 bits
- sb.append(String.format((Locale)null, "%%%1$06x", codePoint));
- }
- }
- return sb.toString();
- }
-
- /**
- * Reverse escaping done by replaceFileNameDangerousCharacters.
- */
- private static String getWordListIdFromFileName(final String fname) {
- final StringBuilder sb = new StringBuilder();
- final int fnameLength = fname.length();
- for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) {
- final int codePoint = fname.codePointAt(i);
- if ('%' != codePoint) {
- sb.appendCodePoint(codePoint);
- } else {
- final int encodedCodePoint = Integer.parseInt(fname.substring(i + 1, i + 7), 16);
- i += 6;
- sb.appendCodePoint(encodedCodePoint);
- }
- }
- return sb.toString();
- }
-
- /**
- * Helper method to get the top level cache directory.
- */
- private static String getWordListCacheDirectory(final Context context) {
- return context.getFilesDir() + File.separator + "dicts";
- }
-
- /**
- * Find out the cache directory associated with a specific locale.
- */
- private static String getCacheDirectoryForLocale(final String locale, final Context context) {
- final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale);
- final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator
- + relativeDirectoryName;
- final File directory = new File(absoluteDirectoryName);
- if (!directory.exists()) {
- if (!directory.mkdirs()) {
- Log.e(TAG, "Could not create the directory for locale" + locale);
- }
- }
- return absoluteDirectoryName;
- }
-
- /**
- * Generates a file name for the id and locale passed as an argument.
- *
- * In the current implementation the file name returned will always be unique for
- * any id/locale pair, but please do not expect that the id can be the same for
- * different dictionaries with different locales. An id should be unique for any
- * dictionary.
- * The file name is pretty much an URL-encoded version of the id inside a directory
- * named like the locale, except it will also escape characters that look dangerous
- * to some file systems.
- * @param id the id of the dictionary for which to get a file name
- * @param locale the locale for which to get the file name as a string
- * @param context the context to use for getting the directory
- * @return the name of the file to be created
- */
- public static String getCacheFileName(String id, String locale, Context context) {
- final String fileName = replaceFileNameDangerousCharacters(id);
- return getCacheDirectoryForLocale(locale, context) + File.separator + fileName;
- }
-
- /**
* Generates a unique temporary file name in the app cache directory.
- *
- * This is unique as long as it doesn't get called twice in the same millisecond by the same
- * thread, which should be more than enough for our purposes.
*/
- public static String getTempFileName(String id, Context context) {
- final String fileName = replaceFileNameDangerousCharacters(id);
- return context.getCacheDir() + File.separator + fileName + "."
- + Thread.currentThread().getId() + "." + System.currentTimeMillis();
+ public static String getTempFileName(final String id, final Context context)
+ throws IOException {
+ final String safeId = DictionaryInfoUtils.replaceFileNameDangerousCharacters(id);
+ // If the first argument is less than three chars, createTempFile throws a
+ // RuntimeException. We don't really care about what name we get, so just
+ // put a three-chars prefix makes us safe.
+ return File.createTempFile("xxx" + safeId, null).getAbsolutePath();
}
/**
* Returns a file address from a resource, or null if it cannot be opened.
*/
- private static AssetFileAddress loadFallbackResource(final Context context,
+ public static AssetFileAddress loadFallbackResource(final Context context,
final int fallbackResId) {
final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId);
if (afd == null) {
@@ -226,27 +128,6 @@ final class BinaryDictionaryGetter {
}
/**
- * Helper method to the list of cache directories, one for each distinct locale.
- */
- private static File[] getCachedDirectoryList(final Context context) {
- return new File(getWordListCacheDirectory(context)).listFiles();
- }
-
- /**
- * Returns the category for a given file name.
- *
- * This parses the file name, extracts the category, and returns it. See
- * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}.
- * @return The category as a string or null if it can't be found in the file name.
- */
- private static String getCategoryFromFileName(final String fileName) {
- final String id = getWordListIdFromFileName(fileName);
- final String[] idArray = id.split(ID_CATEGORY_SEPARATOR);
- if (2 != idArray.length) return null;
- return idArray[0];
- }
-
- /**
* Utility class for the {@link #getCachedWordLists} method
*/
private static final class FileAndMatchLevel {
@@ -272,20 +153,21 @@ final class BinaryDictionaryGetter {
* @param context the context on which to open the files upon.
* @return an array of binary dictionary files, which may be empty but may not be null.
*/
- private static File[] getCachedWordLists(final String locale,
- final Context context) {
- final File[] directoryList = getCachedDirectoryList(context);
+ 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();
for (File directory : directoryList) {
if (!directory.isDirectory()) continue;
- final String dirLocale = getWordListIdFromFileName(directory.getName());
+ final String dirLocale =
+ DictionaryInfoUtils.getWordListIdFromFileName(directory.getName());
final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale);
if (LocaleUtils.isMatch(matchLevel)) {
final File[] wordLists = directory.listFiles();
if (null != wordLists) {
for (File wordList : wordLists) {
- final String category = getCategoryFromFileName(wordList.getName());
+ final String category =
+ DictionaryInfoUtils.getCategoryFromFileName(wordList.getName());
final FileAndMatchLevel currentBestMatch = cacheFiles.get(category);
if (null == currentBestMatch || currentBestMatch.mMatchLevel < matchLevel) {
cacheFiles.put(category, new FileAndMatchLevel(wordList, matchLevel));
@@ -314,7 +196,7 @@ final class BinaryDictionaryGetter {
final File fileToKeep) {
try {
final File canonicalFileToKeep = fileToKeep.getCanonicalFile();
- final File[] directoryList = getCachedDirectoryList(context);
+ final File[] directoryList = DictionaryInfoUtils.getCachedDirectoryList(context);
if (null == directoryList) return;
for (File directory : directoryList) {
// There is one directory per locale. See #getCachedDirectoryList
@@ -322,7 +204,8 @@ final class BinaryDictionaryGetter {
final File[] wordLists = directory.listFiles();
if (null == wordLists) continue;
for (File wordList : wordLists) {
- final String fileId = getWordListIdFromFileName(wordList.getName());
+ final String fileId =
+ DictionaryInfoUtils.getWordListIdFromFileName(wordList.getName());
if (fileId.equals(id)) {
if (!canonicalFileToKeep.equals(wordList.getCanonicalFile())) {
wordList.delete();
@@ -331,32 +214,10 @@ final class BinaryDictionaryGetter {
}
}
} catch (java.io.IOException e) {
- Log.e(TAG, "IOException trying to cleanup files : " + e);
+ Log.e(TAG, "IOException trying to cleanup files", e);
}
}
-
- /**
- * Returns the id associated with the main word list for a specified locale.
- *
- * Word lists stored in Android Keyboard's resources are referred to as the "main"
- * word lists. Since they can be updated like any other list, we need to assign a
- * unique ID to them. This ID is just the name of the language (locale-wise) they
- * are for, and this method returns this ID.
- */
- private static String getMainDictId(final Locale locale) {
- // This works because we don't include by default different dictionaries for
- // different countries. This actually needs to return the id that we would
- // like to use for word lists included in resources, and the following is okay.
- return MAIN_DICTIONARY_CATEGORY + ID_CATEGORY_SEPARATOR + locale.getLanguage().toString();
- }
-
- private static boolean isMainWordListId(final String id) {
- final String[] idArray = id.split(ID_CATEGORY_SEPARATOR);
- if (2 != idArray.length) return false;
- return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
- }
-
// ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason
// for this is, since those do not include whitelist entries, the new code with an old version
// of the dictionary would lose whitelist functionality.
@@ -427,19 +288,22 @@ final class BinaryDictionaryGetter {
// cacheWordListsFromContentProvider returns the list of files it copied to local
// storage, but we don't really care about what was copied NOW: what we want is the
// list of everything we ever cached, so we ignore the return value.
- BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context,
- hasDefaultWordList);
+ // TODO: The experimental version is not supported by the Dictionary Pack Service yet
+ if (!ProductionFlag.IS_EXPERIMENTAL) {
+ BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context,
+ hasDefaultWordList);
+ }
final File[] cachedWordLists = getCachedWordLists(locale.toString(), context);
- final String mainDictId = getMainDictId(locale);
+ final String mainDictId = DictionaryInfoUtils.getMainDictId(locale);
final DictPackSettings dictPackSettings = new DictPackSettings(context);
boolean foundMainDict = false;
final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList();
// cachedWordLists may not be null, see doc for getCachedDictionaryList
for (final File f : cachedWordLists) {
- final String wordListId = getWordListIdFromFileName(f.getName());
+ final String wordListId = DictionaryInfoUtils.getWordListIdFromFileName(f.getName());
final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f);
- if (canUse && isMainWordListId(wordListId)) {
+ if (canUse && DictionaryInfoUtils.isMainWordListId(wordListId)) {
foundMainDict = true;
}
if (!dictPackSettings.isWordListActive(wordListId)) continue;
@@ -452,7 +316,7 @@ final class BinaryDictionaryGetter {
if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) {
final int fallbackResId =
- DictionaryFactory.getMainDictionaryResourceId(context.getResources(), locale);
+ DictionaryInfoUtils.getMainDictionaryResourceId(context.getResources(), locale);
final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId);
if (null != fallbackAsset) {
fileList.add(fallbackAsset);
diff --git a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
index 7f7ff31c8..489a74ef1 100644
--- a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
+++ b/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
diff --git a/java/src/com/android/inputmethod/latin/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/CapsModeUtils.java
new file mode 100644
index 000000000..1012cd519
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/CapsModeUtils.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.text.InputType;
+import android.text.TextUtils;
+
+import java.util.Locale;
+
+public final class CapsModeUtils {
+ private CapsModeUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ /**
+ * Apply an auto-caps mode to a string.
+ *
+ * This intentionally does NOT apply manual caps mode. It only changes the capitalization if
+ * the mode is one of the auto-caps modes.
+ * @param s The string to capitalize.
+ * @param capitalizeMode The mode in which to capitalize.
+ * @param locale The locale for capitalizing.
+ * @return The capitalized string.
+ */
+ public static String applyAutoCapsMode(final String s, final int capitalizeMode,
+ final Locale locale) {
+ if (WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == capitalizeMode) {
+ return s.toUpperCase(locale);
+ } else if (WordComposer.CAPS_MODE_AUTO_SHIFTED == capitalizeMode) {
+ return StringUtils.toTitleCase(s, locale);
+ } else {
+ return s;
+ }
+ }
+
+ /**
+ * Return whether a constant represents an auto-caps mode (either auto-shift or auto-shift-lock)
+ * @param mode The mode to test for
+ * @return true if this represents an auto-caps mode, false otherwise
+ */
+ public static boolean isAutoCapsMode(final int mode) {
+ return WordComposer.CAPS_MODE_AUTO_SHIFTED == mode
+ || WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == mode;
+ }
+
+ /**
+ * 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
+ * to match those in {@link InputType}.
+ *
+ * This code is a straight copy of TextUtils.getCapsMode (modulo namespace and formatting
+ * issues). This will change in the future as we simplify the code for our use and fix bugs.
+ *
+ * @param cs The text that should be checked for caps modes.
+ * @param reqModes The modes to be checked: may be any combination of
+ * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
+ * {@link TextUtils#CAP_MODE_SENTENCES}.
+ * @param locale The locale to consider for capitalization rules
+ * @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs
+ *
+ * @return Returns the actual capitalization modes that can be in effect
+ * at the current position, which is any combination of
+ * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
+ * {@link TextUtils#CAP_MODE_SENTENCES}.
+ */
+ public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale,
+ final boolean hasSpaceBefore) {
+ // Quick description of what we want to do:
+ // CAP_MODE_CHARACTERS is always on.
+ // CAP_MODE_WORDS is on if there is some whitespace before the cursor.
+ // CAP_MODE_SENTENCES is on if there is some whitespace before the cursor, and the end
+ // of a sentence just before that.
+ // We ignore opening parentheses and the like just before the cursor for purposes of
+ // finding whitespace for WORDS and SENTENCES modes.
+ // The end of a sentence ends with a period, question mark or exclamation mark. If it's
+ // a period, it also needs not to be an abbreviation, which means it also needs to either
+ // be immediately preceded by punctuation, or by a string of only letters with single
+ // periods interleaved.
+
+ // Step 1 : check for cap MODE_CHARACTERS. If it's looked for, it's always on.
+ if ((reqModes & (TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES)) == 0) {
+ // Here we are not looking for MODE_WORDS or MODE_SENTENCES, so since we already
+ // evaluated MODE_CHARACTERS, we can return.
+ return TextUtils.CAP_MODE_CHARACTERS & reqModes;
+ }
+
+ // Step 2 : Skip (ignore at the end of input) any opening punctuation. This includes
+ // opening parentheses, brackets, opening quotes, everything that *opens* a span of
+ // text in the linguistic sense. In RTL languages, this is still an opening sign, although
+ // it may look like a right parenthesis for example. We also include double quote and
+ // single quote since they aren't start punctuation in the unicode sense, but should still
+ // be skipped for English. TODO: does this depend on the language?
+ int i;
+ if (hasSpaceBefore) {
+ i = cs.length() + 1;
+ } 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) {
+ break;
+ }
+ }
+ }
+
+ // We are now on the character that precedes any starting punctuation, so in the most
+ // frequent case this will be whitespace or a letter, although it may occasionally be a
+ // start of line, or some symbol.
+
+ // Step 3 : Search for the start of a paragraph. From the starting point computed in step 2,
+ // we go back over any space or tab char sitting there. We find the start of a paragraph
+ // if the first char that's not a space or tab is a start of line (as in \n, start of text,
+ // or some other similar characters).
+ int j = i;
+ char prevChar = Constants.CODE_SPACE;
+ if (hasSpaceBefore) --j;
+ while (j > 0) {
+ prevChar = cs.charAt(j - 1);
+ if (!Character.isSpaceChar(prevChar) && prevChar != Constants.CODE_TAB) break;
+ j--;
+ }
+ if (j <= 0 || Character.isWhitespace(prevChar)) {
+ // There are only spacing chars between the start of the paragraph and the cursor,
+ // defined as a isWhitespace() char that is neither a isSpaceChar() nor a tab. Both
+ // MODE_WORDS and MODE_SENTENCES should be active.
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+ | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ }
+ if (i == j) {
+ // If we don't have whitespace before index i, it means neither MODE_WORDS
+ // nor mode sentences should be on so we can return right away.
+ return TextUtils.CAP_MODE_CHARACTERS & reqModes;
+ }
+ if ((reqModes & TextUtils.CAP_MODE_SENTENCES) == 0) {
+ // Here we know we have whitespace before the cursor (if not, we returned in the above
+ // if i == j clause), so we need MODE_WORDS to be on. And we don't need to evaluate
+ // MODE_SENTENCES so we can return right away.
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ }
+ // Please note that because of the reqModes & CAP_MODE_SENTENCES test a few lines above,
+ // we know that MODE_SENTENCES is being requested.
+
+ // Step 4 : Search for MODE_SENTENCES.
+ // English is a special case in that "American typography" rules, which are the most common
+ // in English, state that a sentence terminator immediately following a quotation mark
+ // should be swapped with it and de-duplicated (included in the quotation mark),
+ // e.g. <<Did he say, "let's go home?">>
+ // No other language has such a rule as far as I know, instead putting inside the quotation
+ // mark as the exact thing quoted and handling the surrounding punctuation independently,
+ // e.g. <<Did he say, "let's go home"?>>
+ // Hence, specifically for English, we treat this special case here.
+ if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) {
+ for (; j > 0; j--) {
+ // Here we look to go over any closing punctuation. This is because in dominant
+ // variants of English, the final period is placed within double quotes and maybe
+ // other closing punctuation signs. This is generally not true in other languages.
+ final char c = cs.charAt(j - 1);
+ if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE
+ && Character.getType(c) != Character.END_PUNCTUATION) {
+ break;
+ }
+ }
+ }
+
+ if (j <= 0) return TextUtils.CAP_MODE_CHARACTERS & reqModes;
+ char c = cs.charAt(--j);
+
+ // We found the next interesting chunk of text ; next we need to determine if it's the
+ // end of a sentence. If we have a question mark or an exclamation mark, it's the end of
+ // a sentence. If it's neither, the only remaining case is the period so we get the opposite
+ // case out of the way.
+ if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ }
+ if (c != Constants.CODE_PERIOD || j <= 0) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ }
+
+ // 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,}
+ // To find out, we will have a simple state machine with the following states :
+ // START, WORD, PERIOD, ABBREVIATION
+ // On START : (just before the first period)
+ // letter => WORD
+ // 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)
+ // letter => WORD
+ // period => PERIOD
+ // otherwise => end with caps (it was a word with a full stop at the end)
+ // On PERIOD : (period within a potential abbreviation)
+ // letter => LETTER
+ // otherwise => end with caps (it was not an abbreviation)
+ // On LETTER : (letter within a potential abbreviation)
+ // letter => LETTER
+ // period => PERIOD
+ // otherwise => end with no caps (it was an abbreviation)
+ // "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This
+ // should capitalize.
+
+ final int START = 0;
+ final int WORD = 1;
+ final int PERIOD = 2;
+ final int LETTER = 3;
+ 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;
+ int state = START;
+ while (j > 0) {
+ c = cs.charAt(--j);
+ switch (state) {
+ case START:
+ if (Character.isLetter(c)) {
+ state = WORD;
+ } else if (Character.isWhitespace(c)) {
+ return noCaps;
+ } else {
+ return caps;
+ }
+ break;
+ case WORD:
+ if (Character.isLetter(c)) {
+ state = WORD;
+ } else if (c == Constants.CODE_PERIOD) {
+ state = PERIOD;
+ } else {
+ return caps;
+ }
+ break;
+ case PERIOD:
+ if (Character.isLetter(c)) {
+ state = LETTER;
+ } else {
+ return caps;
+ }
+ break;
+ case LETTER:
+ if (Character.isLetter(c)) {
+ state = LETTER;
+ } else if (c == Constants.CODE_PERIOD) {
+ state = PERIOD;
+ } else {
+ return noCaps;
+ }
+ }
+ }
+ // Here we arrived at the start of the line. This should behave exactly like whitespace.
+ return (START == state || LETTER == state) ? noCaps : caps;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/CollectionUtils.java b/java/src/com/android/inputmethod/latin/CollectionUtils.java
index c75f2df5c..a8623cc63 100644
--- a/java/src/com/android/inputmethod/latin/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/CollectionUtils.java
@@ -27,6 +27,7 @@ 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;
@@ -39,6 +40,10 @@ public final class CollectionUtils {
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>();
}
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 57e12a64f..50e50233e 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -111,7 +111,7 @@ public final class Constants {
}
}
- public static class TextUtils {
+ public static final class TextUtils {
/**
* Capitalization mode for {@link android.text.TextUtils#getCapsMode}: don't capitalize
* characters. This value may be used with
@@ -126,7 +126,8 @@ public final class Constants {
}
}
- public static class Dictionary {
+ public static final class Dictionary {
+ // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
public static final int MAX_WORD_LENGTH = 48;
private Dictionary() {
@@ -136,10 +137,85 @@ public final class Constants {
public static final int NOT_A_CODE = -1;
- // See {@link KeyboardActionListener.Adapter#isInvalidCoordinate(int)}.
public static final int NOT_A_COORDINATE = -1;
public static final int SUGGESTION_STRIP_COORDINATE = -2;
public static final int SPELL_CHECKER_COORDINATE = -3;
+ public static final int EXTERNAL_KEYBOARD_COORDINATE = -4;
+
+ public static boolean isValidCoordinate(final int coordinate) {
+ // Detect {@link NOT_A_COORDINATE}, {@link SUGGESTION_STRIP_COORDINATE},
+ // and {@link SPELL_CHECKER_COORDINATE}.
+ return coordinate >= 0;
+ }
+
+ /**
+ * Some common keys code. Must be positive.
+ */
+ public static final int CODE_ENTER = '\n';
+ public static final int CODE_TAB = '\t';
+ public static final int CODE_SPACE = ' ';
+ public static final int CODE_PERIOD = '.';
+ public static final int CODE_DASH = '-';
+ public static final int CODE_SINGLE_QUOTE = '\'';
+ public static final int CODE_DOUBLE_QUOTE = '"';
+ public static final int CODE_QUESTION_MARK = '?';
+ public static final int CODE_EXCLAMATION_MARK = '!';
+ // TODO: Check how this should work for right-to-left languages. It seems to stand
+ // that for rtl languages, a closing parenthesis is a left parenthesis. Is this
+ // managed by the font? Or is it a different char?
+ public static final int CODE_CLOSING_PARENTHESIS = ')';
+ public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
+ public static final int CODE_CLOSING_CURLY_BRACKET = '}';
+ public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
+
+ /**
+ * Special keys code. Must be negative.
+ * These should be aligned with KeyboardCodesSet.ID_TO_NAME[],
+ * KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[]
+ */
+ public static final int CODE_SHIFT = -1;
+ public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
+ public static final int CODE_OUTPUT_TEXT = -3;
+ public static final int CODE_DELETE = -4;
+ public static final int CODE_SETTINGS = -5;
+ public static final int CODE_SHORTCUT = -6;
+ public static final int CODE_ACTION_NEXT = -7;
+ public static final int CODE_ACTION_PREVIOUS = -8;
+ public static final int CODE_LANGUAGE_SWITCH = -9;
+ public static final int CODE_RESEARCH = -10;
+ public static final int CODE_SHIFT_ENTER = -11;
+ // Code value representing the code is not specified.
+ public static final int CODE_UNSPECIFIED = -12;
+
+ public static boolean isLetterCode(final int code) {
+ return code >= CODE_SPACE;
+ }
+
+ public static String printableCode(final int code) {
+ switch (code) {
+ case CODE_SHIFT: return "shift";
+ case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";
+ case CODE_OUTPUT_TEXT: return "text";
+ case CODE_DELETE: return "delete";
+ case CODE_SETTINGS: return "settings";
+ case CODE_SHORTCUT: return "shortcut";
+ case CODE_ACTION_NEXT: return "actionNext";
+ case CODE_ACTION_PREVIOUS: return "actionPrevious";
+ case CODE_LANGUAGE_SWITCH: return "languageSwitch";
+ case CODE_UNSPECIFIED: return "unspec";
+ case CODE_TAB: return "tab";
+ case CODE_ENTER: return "enter";
+ case CODE_RESEARCH: return "research";
+ 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);
+ }
+ }
+
+ // Constants for CSV parsing.
+ public static final char CSV_SEPARATOR = ',';
+ public static final char CSV_ESCAPE = '\\';
private Constants() {
// This utility class is not publicly instantiable.
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 5edc4314f..8b5a76a17 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -1,15 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
+ * 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;
@@ -24,8 +26,6 @@ import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import android.util.Log;
-import com.android.inputmethod.keyboard.Keyboard;
-
import java.util.Locale;
public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
@@ -62,7 +62,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
*/
private final boolean mUseFirstLastBigrams;
- public ContactsBinaryDictionary(final Context context, Locale locale) {
+ public ContactsBinaryDictionary(final Context context, final Locale locale) {
super(context, getFilenameWithLocale(NAME, locale.toString()), Dictionary.TYPE_CONTACTS);
mLocale = locale;
mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale);
@@ -120,7 +120,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
}
}
- private boolean useFirstLastBigramsForLocale(Locale locale) {
+ private boolean useFirstLastBigramsForLocale(final Locale locale) {
// TODO: Add firstname/lastname bigram rules for other languages.
if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
return true;
@@ -128,7 +128,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
return false;
}
- private void addWords(Cursor cursor) {
+ private void addWords(final Cursor cursor) {
clearFusionDictionary();
int count = 0;
while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) {
@@ -160,7 +160,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
* Adds the words in a name (e.g., firstname/lastname) to the binary dictionary along with their
* bigrams depending on locale.
*/
- private void addName(String name) {
+ private void addName(final String name) {
int len = StringUtils.codePointCount(name);
String prevWord = null;
// TODO: Better tokenization for non-Latin writing systems
@@ -188,12 +188,13 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
/**
* Returns the index of the last letter in the word, starting from position startIndex.
*/
- private static int getWordEndPosition(String string, int len, int startIndex) {
+ private static int getWordEndPosition(final String string, final int len,
+ final int startIndex) {
int end;
int cp = 0;
for (end = startIndex + 1; end < len; end += Character.charCount(cp)) {
cp = string.codePointAt(end);
- if (!(cp == Keyboard.CODE_DASH || cp == Keyboard.CODE_SINGLE_QUOTE
+ if (!(cp == Constants.CODE_DASH || cp == Constants.CODE_SINGLE_QUOTE
|| Character.isLetter(cp))) {
break;
}
@@ -249,7 +250,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
return false;
}
- private static boolean isValidName(String name) {
+ private static boolean isValidName(final String name) {
if (name != null && -1 == name.indexOf('@')) {
return true;
}
@@ -259,7 +260,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
/**
* Checks if the words in a name are in the current binary dictionary.
*/
- private boolean isNameInDictionary(String name) {
+ private boolean isNameInDictionary(final String name) {
int len = StringUtils.codePointCount(name);
String prevWord = null;
for (int i = 0; i < len; i++) {
diff --git a/java/src/com/android/inputmethod/latin/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/CoordinateUtils.java
new file mode 100644
index 000000000..af270e1e4
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/CoordinateUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+public final class CoordinateUtils {
+ private static final int INDEX_X = 0;
+ private static final int INDEX_Y = 1;
+ private static final int ARRAY_SIZE = INDEX_Y + 1;
+
+ private CoordinateUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static int[] newInstance() {
+ return new int[ARRAY_SIZE];
+ }
+
+ public static int x(final int[] coords) {
+ return coords[INDEX_X];
+ }
+
+ public static int y(final int[] coords) {
+ return coords[INDEX_Y];
+ }
+
+ public static void set(final int[] coords, final int x, final int y) {
+ coords[INDEX_X] = x;
+ coords[INDEX_Y] = y;
+ }
+
+ public static void copy(final int[] destination, final int[] source) {
+ destination[INDEX_X] = source[INDEX_X];
+ destination[INDEX_Y] = source[INDEX_Y];
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 3af3cab2c..7df266ef2 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -25,6 +25,7 @@ import android.os.Process;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
import android.util.Log;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
@@ -32,14 +33,18 @@ import com.android.inputmethod.research.ResearchLogger;
public final class DebugSettings extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
-
private static final String TAG = DebugSettings.class.getSimpleName();
- private static final String DEBUG_MODE_KEY = "debug_mode";
- public static final String FORCE_NON_DISTINCT_MULTITOUCH_KEY = "force_non_distinct_multitouch";
+
+ 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";
+ private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary";
+ private static final boolean SHOW_STATISTICS_LOGGING = false;
private boolean mServiceNeedsRestart = false;
private CheckBoxPreference mDebugMode;
+ private CheckBoxPreference mStatisticsLoggingPref;
@Override
public void onCreate(Bundle icicle) {
@@ -55,9 +60,31 @@ public final class DebugSettings extends PreferenceFragment
ResearchLogger.DEFAULT_USABILITY_STUDY_MODE));
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);
+ }
+ }
+
+ PreferenceScreen readExternalDictionary =
+ (PreferenceScreen) findPreference(PREF_READ_EXTERNAL_DICTIONARY);
+ if (null != readExternalDictionary) {
+ readExternalDictionary.setOnPreferenceClickListener(
+ new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(final Preference arg0) {
+ ExternalDictionaryGetterForDebug.chooseAndInstallDictionary(
+ getActivity());
+ mServiceNeedsRestart = true;
+ return true;
+ }
+ });
+ }
mServiceNeedsRestart = false;
- mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY);
+ mDebugMode = (CheckBoxPreference) findPreference(PREF_DEBUG_MODE);
updateDebugMode();
}
@@ -69,13 +96,21 @@ public final class DebugSettings extends PreferenceFragment
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
- if (key.equals(DEBUG_MODE_KEY)) {
+ if (key.equals(PREF_DEBUG_MODE)) {
if (mDebugMode != null) {
- mDebugMode.setChecked(prefs.getBoolean(DEBUG_MODE_KEY, false));
+ 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(FORCE_NON_DISTINCT_MULTITOUCH_KEY)
+ } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)
|| key.equals(KeyboardSwitcher.PREF_KEYBOARD_LAYOUT)) {
mServiceNeedsRestart = true;
}
@@ -89,6 +124,9 @@ public final class DebugSettings extends PreferenceFragment
String version = "";
try {
final Context context = getActivity();
+ if (context == null) {
+ return;
+ }
final String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
version = "Version " + info.versionName;
diff --git a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java b/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
index 6ef19ee82..2a501a665 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index ce1b64660..534e2116b 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -23,10 +23,10 @@ public final class DicTraverseSession {
JniUtils.loadNativeLibrary();
}
- private native long setDicTraverseSessionNative(String locale);
- private native void initDicTraverseSessionNative(long nativeDicTraverseSession,
+ private static native long setDicTraverseSessionNative(String locale);
+ private static native void initDicTraverseSessionNative(long nativeDicTraverseSession,
long dictionary, int[] previousWord, int previousWordLength);
- private native void releaseDicTraverseSessionNative(long nativeDicTraverseSession);
+ private static native void releaseDicTraverseSessionNative(long nativeDicTraverseSession);
private long mNativeDicTraverseSession;
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 88d0c09dd..ff3d83fad 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -26,11 +26,6 @@ import java.util.ArrayList;
* strokes.
*/
public abstract class Dictionary {
- /**
- * The weight to give to a word if it's length is the same as the number of typed characters.
- */
- protected static final int FULL_WORD_SCORE_MULTIPLIER = 2;
-
public static final int NOT_A_PROBABILITY = -1;
public static final String TYPE_USER_TYPED = "user_typed";
@@ -59,12 +54,12 @@ public abstract class Dictionary {
// TODO: pass more context than just the previous word, to enable better suggestions (n-gram
// and more)
abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo);
+ final String prevWord, final ProximityInfo proximityInfo);
// The default implementation of this method ignores sessionId.
// Subclasses that want to use sessionId need to override this method.
public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) {
+ final String prevWord, final ProximityInfo proximityInfo, final int sessionId) {
return getSuggestions(composer, prevWord, proximityInfo);
}
@@ -73,9 +68,9 @@ public abstract class Dictionary {
* @param word the word to search for. The search should be case-insensitive.
* @return true if the word exists, false otherwise
*/
- abstract public boolean isValidWord(CharSequence word);
+ abstract public boolean isValidWord(final String word);
- public int getFrequency(CharSequence word) {
+ public int getFrequency(final String word) {
return NOT_A_PROBABILITY;
}
@@ -87,7 +82,7 @@ public abstract class Dictionary {
* @param typedWord the word to compare with
* @return true if they are the same, false otherwise.
*/
- protected boolean same(final char[] word, final int length, final CharSequence typedWord) {
+ protected boolean same(final char[] word, final int length, final String typedWord) {
if (typedWord.length() != length) {
return false;
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index d3b120989..2832ad43c 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -38,7 +38,7 @@ public final class DictionaryCollection extends Dictionary {
mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
}
- public DictionaryCollection(final String dictType, Dictionary... dictionaries) {
+ public DictionaryCollection(final String dictType, final Dictionary... dictionaries) {
super(dictType);
if (null == dictionaries) {
mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
@@ -48,7 +48,7 @@ public final class DictionaryCollection extends Dictionary {
}
}
- public DictionaryCollection(final String dictType, Collection<Dictionary> dictionaries) {
+ public DictionaryCollection(final String dictType, final Collection<Dictionary> dictionaries) {
super(dictType);
mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
@@ -56,7 +56,7 @@ public final class DictionaryCollection extends Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo) {
+ final String prevWord, final ProximityInfo proximityInfo) {
final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries;
if (dictionaries.isEmpty()) return null;
// To avoid creating unnecessary objects, we get the list out of the first
@@ -74,14 +74,14 @@ public final class DictionaryCollection extends Dictionary {
}
@Override
- public boolean isValidWord(CharSequence word) {
+ public boolean isValidWord(final String word) {
for (int i = mDictionaries.size() - 1; i >= 0; --i)
if (mDictionaries.get(i).isValidWord(word)) return true;
return false;
}
@Override
- public int getFrequency(CharSequence word) {
+ public int getFrequency(final String word) {
int maxFreq = -1;
for (int i = mDictionaries.size() - 1; i >= 0; --i) {
final int tempFreq = mDictionaries.get(i).getFrequency(word);
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index f381973ae..40e51672a 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -31,9 +31,6 @@ import java.util.Locale;
*/
public final class DictionaryFactory {
private static final String TAG = DictionaryFactory.class.getSimpleName();
- // This class must be located in the same package as LatinIME.java.
- private static final String RESOURCE_PACKAGE_NAME =
- DictionaryFactory.class.getPackage().getName();
/**
* Initializes a main dictionary collection from a dictionary pack, with explicit flags.
@@ -58,9 +55,8 @@ public final class DictionaryFactory {
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
if (null != assetFileList) {
for (final AssetFileAddress f : assetFileList) {
- final BinaryDictionary binaryDictionary =
- new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength,
- useFullEditDistance, locale, Dictionary.TYPE_MAIN);
+ final BinaryDictionary binaryDictionary = new BinaryDictionary(f.mFilename,
+ f.mOffset, f.mLength, useFullEditDistance, locale, Dictionary.TYPE_MAIN);
if (binaryDictionary.isValidDictionary()) {
dictList.add(binaryDictionary);
}
@@ -97,8 +93,8 @@ public final class DictionaryFactory {
final Locale locale) {
AssetFileDescriptor afd = null;
try {
- final int resId =
- getMainDictionaryResourceIdIfAvailableForLocale(context.getResources(), locale);
+ final int resId = DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
+ context.getResources(), locale);
if (0 == resId) return null;
afd = context.getResources().openRawResourceFd(resId);
if (afd == null) {
@@ -112,7 +108,7 @@ public final class DictionaryFactory {
Log.e(TAG, "sourceDir is not a file: " + sourceDir);
return null;
}
- return new BinaryDictionary(context, sourceDir, afd.getStartOffset(), afd.getLength(),
+ return new BinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(),
false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
} catch (android.content.res.Resources.NotFoundException e) {
Log.e(TAG, "Could not find the resource");
@@ -130,17 +126,16 @@ public final class DictionaryFactory {
/**
* Create a dictionary from passed data. This is intended for unit tests only.
- * @param context the test context to create this data from.
* @param dictionary the file to read
* @param startOffset the offset in the file where the data starts
* @param length the length of the data
* @param useFullEditDistance whether to use the full edit distance in suggestions
* @return the created dictionary, or null.
*/
- public static Dictionary createDictionaryForTest(Context context, File dictionary,
- long startOffset, long length, final boolean useFullEditDistance, Locale locale) {
+ public static Dictionary createDictionaryForTest(File dictionary, long startOffset, long length,
+ final boolean useFullEditDistance, Locale locale) {
if (dictionary.isFile()) {
- return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length,
+ return new BinaryDictionary(dictionary.getAbsolutePath(), startOffset, length,
useFullEditDistance, locale, Dictionary.TYPE_MAIN);
} else {
Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
@@ -156,47 +151,7 @@ public final class DictionaryFactory {
*/
public static boolean isDictionaryAvailable(Context context, Locale locale) {
final Resources res = context.getResources();
- return 0 != getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
- }
-
- private static final String DEFAULT_MAIN_DICT = "main";
- private static final String MAIN_DICT_PREFIX = "main_";
-
- /**
- * Helper method to return a dictionary res id for a locale, or 0 if none.
- * @param locale dictionary locale
- * @return main dictionary resource id
- */
- private static int getMainDictionaryResourceIdIfAvailableForLocale(final Resources res,
- final Locale locale) {
- int resId;
- // Try to find main_language_country dictionary.
- if (!locale.getCountry().isEmpty()) {
- final String dictLanguageCountry = MAIN_DICT_PREFIX + locale.toString().toLowerCase();
- if ((resId = res.getIdentifier(
- dictLanguageCountry, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
- return resId;
- }
- }
-
- // Try to find main_language dictionary.
- final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage();
- if ((resId = res.getIdentifier(dictLanguage, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
- return resId;
- }
-
- // Not found, return 0
- return 0;
- }
-
- /**
- * Returns a main dictionary resource id
- * @param locale dictionary locale
- * @return main dictionary resource id
- */
- public static int getMainDictionaryResourceId(final Resources res, final Locale locale) {
- int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
- if (0 != resourceId) return resourceId;
- return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", RESOURCE_PACKAGE_NAME);
+ return 0 != DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
+ res, locale);
}
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
new file mode 100644
index 000000000..dcfa483f8
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * This class encapsulates the logic for the Latin-IME side of dictionary information management.
+ */
+public class DictionaryInfoUtils {
+ private static final String TAG = DictionaryInfoUtils.class.getSimpleName();
+ // This class must be located in the same package as LatinIME.java.
+ private static final String RESOURCE_PACKAGE_NAME =
+ DictionaryInfoUtils.class.getPackage().getName();
+ private static final String DEFAULT_MAIN_DICT = "main";
+ private static final String MAIN_DICT_PREFIX = "main_";
+ // 6 digits - unicode is limited to 21 bits
+ private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
+
+ public static class DictionaryInfo {
+ private static final String LOCALE_COLUMN = "locale";
+ private static final String WORDLISTID_COLUMN = "id";
+ private static final String LOCAL_FILENAME_COLUMN = "filename";
+ private static final String DESCRIPTION_COLUMN = "description";
+ private static final String DATE_COLUMN = "date";
+ private static final String FILESIZE_COLUMN = "filesize";
+ private static final String VERSION_COLUMN = "version";
+ public final String mId;
+ public final Locale mLocale;
+ public final String mDescription;
+ public final AssetFileAddress mFileAddress;
+ public final int mVersion;
+ public DictionaryInfo(final String id, final Locale locale, final String description,
+ final AssetFileAddress fileAddress, final int version) {
+ mId = id;
+ mLocale = locale;
+ mDescription = description;
+ mFileAddress = fileAddress;
+ mVersion = version;
+ }
+ public ContentValues toContentValues() {
+ final ContentValues values = new ContentValues();
+ values.put(WORDLISTID_COLUMN, mId);
+ values.put(LOCALE_COLUMN, mLocale.toString());
+ values.put(DESCRIPTION_COLUMN, mDescription);
+ values.put(LOCAL_FILENAME_COLUMN, mFileAddress.mFilename);
+ values.put(DATE_COLUMN,
+ new File(mFileAddress.mFilename).lastModified() / DateUtils.SECOND_IN_MILLIS);
+ values.put(FILESIZE_COLUMN, mFileAddress.mLength);
+ values.put(VERSION_COLUMN, mVersion);
+ return values;
+ }
+ }
+
+ private DictionaryInfoUtils() {
+ // Private constructor to forbid instantation of this helper class.
+ }
+
+ /**
+ * Returns whether we may want to use this character as part of a file name.
+ *
+ * This basically only accepts ascii letters and numbers, and rejects everything else.
+ */
+ private static boolean isFileNameCharacter(int codePoint) {
+ if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit
+ if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase
+ if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase
+ return codePoint == '_'; // Underscore
+ }
+
+ /**
+ * Escapes a string for any characters that may be suspicious for a file or directory name.
+ *
+ * Concretely this does a sort of URL-encoding except it will encode everything that's not
+ * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which
+ * we cannot allow here)
+ */
+ // TODO: create a unit test for this method
+ public static String replaceFileNameDangerousCharacters(final String name) {
+ // This assumes '%' is fully available as a non-separator, normal
+ // character in a file name. This is probably true for all file systems.
+ final StringBuilder sb = new StringBuilder();
+ final int nameLength = name.length();
+ for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) {
+ final int codePoint = name.codePointAt(i);
+ if (DictionaryInfoUtils.isFileNameCharacter(codePoint)) {
+ sb.appendCodePoint(codePoint);
+ } else {
+ sb.append(String.format((Locale)null, "%%%1$0" + MAX_HEX_DIGITS_FOR_CODEPOINT + "x",
+ codePoint));
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Helper method to get the top level cache directory.
+ */
+ private static String getWordListCacheDirectory(final Context context) {
+ return context.getFilesDir() + File.separator + "dicts";
+ }
+
+ /**
+ * Reverse escaping done by replaceFileNameDangerousCharacters.
+ */
+ public static String getWordListIdFromFileName(final String fname) {
+ final StringBuilder sb = new StringBuilder();
+ final int fnameLength = fname.length();
+ for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) {
+ final int codePoint = fname.codePointAt(i);
+ if ('%' != codePoint) {
+ sb.appendCodePoint(codePoint);
+ } else {
+ // + 1 to pass the % sign
+ final int encodedCodePoint = Integer.parseInt(
+ fname.substring(i + 1, i + 1 + MAX_HEX_DIGITS_FOR_CODEPOINT), 16);
+ i += MAX_HEX_DIGITS_FOR_CODEPOINT;
+ sb.appendCodePoint(encodedCodePoint);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Helper method to the list of cache directories, one for each distinct locale.
+ */
+ public static File[] getCachedDirectoryList(final Context context) {
+ return new File(DictionaryInfoUtils.getWordListCacheDirectory(context)).listFiles();
+ }
+
+ /**
+ * Returns the category for a given file name.
+ *
+ * This parses the file name, extracts the category, and returns it. See
+ * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}.
+ * @return The category as a string or null if it can't be found in the file name.
+ */
+ public static String getCategoryFromFileName(final String fileName) {
+ final String id = getWordListIdFromFileName(fileName);
+ final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
+ // An id is supposed to be in format category:locale, so splitting on the separator
+ // should yield a 2-elements array
+ if (2 != idArray.length) return null;
+ return idArray[0];
+ }
+
+ /**
+ * Find out the cache directory associated with a specific locale.
+ */
+ private static String getCacheDirectoryForLocale(final String locale, final Context context) {
+ final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale);
+ final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator
+ + relativeDirectoryName;
+ final File directory = new File(absoluteDirectoryName);
+ if (!directory.exists()) {
+ if (!directory.mkdirs()) {
+ Log.e(TAG, "Could not create the directory for locale" + locale);
+ }
+ }
+ return absoluteDirectoryName;
+ }
+
+ /**
+ * Generates a file name for the id and locale passed as an argument.
+ *
+ * In the current implementation the file name returned will always be unique for
+ * any id/locale pair, but please do not expect that the id can be the same for
+ * different dictionaries with different locales. An id should be unique for any
+ * dictionary.
+ * The file name is pretty much an URL-encoded version of the id inside a directory
+ * named like the locale, except it will also escape characters that look dangerous
+ * to some file systems.
+ * @param id the id of the dictionary for which to get a file name
+ * @param locale the locale for which to get the file name as a string
+ * @param context the context to use for getting the directory
+ * @return the name of the file to be created
+ */
+ public static String getCacheFileName(String id, String locale, Context context) {
+ final String fileName = replaceFileNameDangerousCharacters(id);
+ return getCacheDirectoryForLocale(locale, context) + File.separator + fileName;
+ }
+
+ public static boolean isMainWordListId(final String id) {
+ final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
+ // An id is supposed to be in format category:locale, so splitting on the separator
+ // should yield a 2-elements array
+ if (2 != idArray.length) return false;
+ return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
+ }
+
+ /**
+ * Helper method to return a dictionary res id for a locale, or 0 if none.
+ * @param locale dictionary locale
+ * @return main dictionary resource id
+ */
+ public static int getMainDictionaryResourceIdIfAvailableForLocale(final Resources res,
+ final Locale locale) {
+ int resId;
+ // Try to find main_language_country dictionary.
+ if (!locale.getCountry().isEmpty()) {
+ final String dictLanguageCountry =
+ MAIN_DICT_PREFIX + locale.toString().toLowerCase(Locale.ROOT);
+ if ((resId = res.getIdentifier(
+ dictLanguageCountry, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
+ return resId;
+ }
+ }
+
+ // Try to find main_language dictionary.
+ final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage();
+ if ((resId = res.getIdentifier(dictLanguage, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
+ return resId;
+ }
+
+ // Not found, return 0
+ return 0;
+ }
+
+ /**
+ * Returns a main dictionary resource id
+ * @param locale dictionary locale
+ * @return main dictionary resource id
+ */
+ public static int getMainDictionaryResourceId(final Resources res, final Locale locale) {
+ int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
+ if (0 != resourceId) return resourceId;
+ return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", RESOURCE_PACKAGE_NAME);
+ }
+
+ /**
+ * Returns the id associated with the main word list for a specified locale.
+ *
+ * Word lists stored in Android Keyboard's resources are referred to as the "main"
+ * word lists. Since they can be updated like any other list, we need to assign a
+ * unique ID to them. This ID is just the name of the language (locale-wise) they
+ * are for, and this method returns this ID.
+ */
+ public static String getMainDictId(final Locale locale) {
+ // This works because we don't include by default different dictionaries for
+ // different countries. This actually needs to return the id that we would
+ // like to use for word lists included in resources, and the following is okay.
+ return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY +
+ BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.getLanguage().toString();
+ }
+
+ public static FileHeader getDictionaryFileHeaderOrNull(final File file) {
+ try {
+ return BinaryDictIOUtils.getDictionaryFileHeader(file, 0, file.length());
+ } catch (UnsupportedFormatException e) {
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ private static DictionaryInfo createDictionaryInfoFromFileAddress(
+ final AssetFileAddress fileAddress) {
+ final FileHeader header = BinaryDictIOUtils.getDictionaryFileHeaderOrNull(
+ new File(fileAddress.mFilename), fileAddress.mOffset, fileAddress.mLength);
+ final String id = header.getId();
+ final Locale locale = LocaleUtils.constructLocaleFromString(header.getLocaleString());
+ final String description = header.getDescription();
+ final String version = header.getVersion();
+ return new DictionaryInfo(id, locale, description, fileAddress, Integer.parseInt(version));
+ }
+
+ private static void addOrUpdateDictInfo(final ArrayList<DictionaryInfo> dictList,
+ final DictionaryInfo newElement) {
+ for (final DictionaryInfo info : dictList) {
+ if (info.mLocale.equals(newElement.mLocale)) {
+ if (newElement.mVersion <= info.mVersion) {
+ return;
+ }
+ dictList.remove(info);
+ }
+ }
+ dictList.add(newElement);
+ }
+
+ public static ArrayList<DictionaryInfo> getCurrentDictionaryFileNameAndVersionInfo(
+ final Context context) {
+ final ArrayList<DictionaryInfo> dictList = CollectionUtils.newArrayList();
+
+ // Retrieve downloaded dictionaries
+ final File[] directoryList = getCachedDirectoryList(context);
+ if (null != directoryList) {
+ for (final File directory : directoryList) {
+ final String localeString = getWordListIdFromFileName(directory.getName());
+ File[] dicts = BinaryDictionaryGetter.getCachedWordLists(localeString, context);
+ for (final File dict : dicts) {
+ final String wordListId = getWordListIdFromFileName(dict.getName());
+ if (!DictionaryInfoUtils.isMainWordListId(wordListId)) continue;
+ final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+ final AssetFileAddress fileAddress = AssetFileAddress.makeFromFile(dict);
+ final DictionaryInfo dictionaryInfo =
+ createDictionaryInfoFromFileAddress(fileAddress);
+ // Protect against cases of a less-specific dictionary being found, like an
+ // en dictionary being used for an en_US locale. In this case, the en dictionary
+ // should be used for en_US but discounted for listing purposes.
+ if (!dictionaryInfo.mLocale.equals(locale)) continue;
+ addOrUpdateDictInfo(dictList, dictionaryInfo);
+ }
+ }
+ }
+
+ // Retrieve files from assets
+ final Resources resources = context.getResources();
+ final AssetManager assets = resources.getAssets();
+ for (final String localeString : assets.getLocales()) {
+ final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+ final int resourceId =
+ DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
+ context.getResources(), locale);
+ if (0 == resourceId) continue;
+ final AssetFileAddress fileAddress =
+ BinaryDictionaryGetter.loadFallbackResource(context, resourceId);
+ final DictionaryInfo dictionaryInfo = createDictionaryInfoFromFileAddress(fileAddress);
+ // Protect against cases of a less-specific dictionary being found, like an
+ // en dictionary being used for an en_US locale. In this case, the en dictionary
+ // should be used for en_US but discounted for listing purposes.
+ if (!dictionaryInfo.mLocale.equals(locale)) continue;
+ addOrUpdateDictInfo(dictList, dictionaryInfo);
+ }
+
+ return dictList;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
index f2f3fbded..a8513ff45 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index b93c17f11..97dc6a8ac 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -1,15 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
+ * 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;
@@ -51,10 +53,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private static boolean DEBUG = false;
/**
- * The maximum length of a word in this dictionary. This is the same value as the binary
- * dictionary.
+ * The maximum length of a word in this dictionary.
*/
- protected static final int MAX_WORD_LENGTH = BinaryDictionary.MAX_WORD_LENGTH;
+ protected static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
/**
* A static map of locks, each of which controls access to a single binary dictionary file. They
@@ -198,7 +199,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo) {
+ final String prevWord, final ProximityInfo proximityInfo) {
asyncReloadDictionaryIfRequired();
if (mLocalDictionaryController.tryLock()) {
try {
@@ -213,12 +214,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
@Override
- public boolean isValidWord(final CharSequence word) {
+ public boolean isValidWord(final String word) {
asyncReloadDictionaryIfRequired();
return isValidWordInner(word);
}
- protected boolean isValidWordInner(final CharSequence word) {
+ protected boolean isValidWordInner(final String word) {
if (mLocalDictionaryController.tryLock()) {
try {
return isValidWordLocked(word);
@@ -229,17 +230,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return false;
}
- protected boolean isValidWordLocked(final CharSequence word) {
+ protected boolean isValidWordLocked(final String word) {
if (mBinaryDictionary == null) return false;
return mBinaryDictionary.isValidWord(word);
}
- protected boolean isValidBigram(final CharSequence word1, final CharSequence word2) {
+ protected boolean isValidBigram(final String word1, final String word2) {
if (mBinaryDictionary == null) return false;
return mBinaryDictionary.isValidBigram(word1, word2);
}
- protected boolean isValidBigramInner(final CharSequence word1, final CharSequence word2) {
+ protected boolean isValidBigramInner(final String word1, final String word2) {
if (mLocalDictionaryController.tryLock()) {
try {
return isValidBigramLocked(word1, word2);
@@ -250,7 +251,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return false;
}
- protected boolean isValidBigramLocked(final CharSequence word1, final CharSequence word2) {
+ protected boolean isValidBigramLocked(final String word1, final String word2) {
if (mBinaryDictionary == null) return false;
return mBinaryDictionary.isValidBigram(word1, word2);
}
@@ -280,9 +281,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
final long length = file.length();
// Build the new binary dictionary
- final BinaryDictionary newBinaryDictionary =
- new BinaryDictionary(mContext, filename, 0, length, true /* useFullEditDistance */,
- null, mDictType);
+ final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0, length,
+ true /* useFullEditDistance */, null, mDictType);
if (mBinaryDictionary != null) {
// Ensure all threads accessing the current dictionary have finished before swapping in
@@ -321,9 +321,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
tempFile.renameTo(file);
clearFusionDictionary();
} catch (IOException e) {
- Log.e(TAG, "IO exception while writing file: " + e);
+ Log.e(TAG, "IO exception while writing file", e);
} catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported format: " + e);
+ Log.e(TAG, "Unsupported format", e);
} finally {
if (out != null) {
try {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 8cdc2a0af..ae2ee577f 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -19,7 +19,6 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.text.TextUtils;
-import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
@@ -32,12 +31,16 @@ import java.util.LinkedList;
* be searched for suggestions and valid words.
*/
public class ExpandableDictionary extends Dictionary {
+ /**
+ * The weight to give to a word if it's length is the same as the number of typed characters.
+ */
+ private static final int FULL_WORD_SCORE_MULTIPLIER = 2;
// Bigram frequency is a fixed point number with 1 meaning 1.2 and 255 meaning 1.8.
protected static final int BIGRAM_MAX_FREQUENCY = 255;
private Context mContext;
- private char[] mWordBuilder = new char[BinaryDictionary.MAX_WORD_LENGTH];
+ private char[] mWordBuilder = new char[Constants.Dictionary.MAX_WORD_LENGTH];
private int mMaxDepth;
private int mInputLength;
@@ -69,7 +72,7 @@ public class ExpandableDictionary extends Dictionary {
mData = new Node[INCREMENT];
}
- void add(Node n) {
+ void add(final Node n) {
if (mLength + 1 > mData.length) {
Node[] tempData = new Node[mLength + INCREMENT];
if (mLength > 0) {
@@ -155,7 +158,7 @@ public class ExpandableDictionary extends Dictionary {
super(dictType);
mContext = context;
clearDictionary();
- mCodes = new int[BinaryDictionary.MAX_WORD_LENGTH][];
+ mCodes = new int[Constants.Dictionary.MAX_WORD_LENGTH][];
}
public void loadDictionary() {
@@ -172,7 +175,7 @@ public class ExpandableDictionary extends Dictionary {
}
}
- public void setRequiresReload(boolean reload) {
+ public void setRequiresReload(final boolean reload) {
synchronized (mUpdatingLock) {
mRequiresReload = reload;
}
@@ -192,18 +195,18 @@ public class ExpandableDictionary extends Dictionary {
}
public int getMaxWordLength() {
- return BinaryDictionary.MAX_WORD_LENGTH;
+ return Constants.Dictionary.MAX_WORD_LENGTH;
}
public void addWord(final String word, final String shortcutTarget, final int frequency) {
- if (word.length() >= BinaryDictionary.MAX_WORD_LENGTH) {
+ if (word.length() >= Constants.Dictionary.MAX_WORD_LENGTH) {
return;
}
addWordRec(mRoots, word, 0, shortcutTarget, frequency, null);
}
- private void addWordRec(NodeArray children, final String word, final int depth,
- final String shortcutTarget, final int frequency, Node parentNode) {
+ private void addWordRec(final NodeArray children, final String word, final int depth,
+ final String shortcutTarget, final int frequency, final Node parentNode) {
final int wordLength = word.length();
if (wordLength <= depth) return;
final char c = word.charAt(depth);
@@ -248,10 +251,10 @@ public class ExpandableDictionary extends Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo) {
+ final String prevWord, final ProximityInfo proximityInfo) {
if (reloadDictionaryIfRequired()) return null;
if (composer.size() > 1) {
- if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
+ if (composer.size() >= Constants.Dictionary.MAX_WORD_LENGTH) {
return null;
}
final ArrayList<SuggestedWordInfo> suggestions =
@@ -267,8 +270,7 @@ public class ExpandableDictionary extends Dictionary {
// This reloads the dictionary if required, and returns whether it's currently updating its
// contents or not.
- // @VisibleForTesting
- boolean reloadDictionaryIfRequired() {
+ private boolean reloadDictionaryIfRequired() {
synchronized (mUpdatingLock) {
// If we need to update, start off a background task
if (mRequiresReload) startDictionaryLoadingTaskLocked();
@@ -277,7 +279,7 @@ public class ExpandableDictionary extends Dictionary {
}
protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
- final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
+ final String prevWordForBigrams, final ProximityInfo proximityInfo) {
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
mInputLength = codes.size();
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
@@ -305,7 +307,7 @@ public class ExpandableDictionary extends Dictionary {
}
@Override
- public synchronized boolean isValidWord(CharSequence word) {
+ public synchronized boolean isValidWord(final String word) {
synchronized (mUpdatingLock) {
// If we need to update, start off a background task
if (mRequiresReload) startDictionaryLoadingTaskLocked();
@@ -320,7 +322,7 @@ public class ExpandableDictionary extends Dictionary {
return (node == null) ? false : !node.mShortcutOnly;
}
- protected boolean removeBigram(String word1, String word2) {
+ protected boolean removeBigram(final String word1, final String word2) {
// Refer to addOrSetBigram() about word1.toLowerCase()
final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null);
final Node secondWord = searchWord(mRoots, word2, 0, null);
@@ -345,13 +347,13 @@ public class ExpandableDictionary extends Dictionary {
/**
* Returns the word's frequency or -1 if not found
*/
- protected int getWordFrequency(CharSequence word) {
+ protected int getWordFrequency(final String word) {
// Case-sensitive search
final Node node = searchNode(mRoots, word, 0, word.length());
return (node == null) ? -1 : node.mFrequency;
}
- protected NextWord getBigramWord(String word1, String word2) {
+ protected NextWord getBigramWord(final String word1, final String word2) {
// Refer to addOrSetBigram() about word1.toLowerCase()
final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null);
final Node secondWord = searchWord(mRoots, word2, 0, null);
@@ -368,7 +370,8 @@ public class ExpandableDictionary extends Dictionary {
return null;
}
- private static int computeSkippedWordFinalFreq(int freq, int snr, int inputLength) {
+ private static int computeSkippedWordFinalFreq(final int freq, final int snr,
+ final int inputLength) {
// The computation itself makes sense for >= 2, but the == 2 case returns 0
// anyway so we may as well test against 3 instead and return the constant
if (inputLength >= 3) {
@@ -431,9 +434,9 @@ public class ExpandableDictionary extends Dictionary {
* @param suggestions the list in which to add suggestions
*/
// TODO: Share this routine with the native code for BinaryDictionary
- protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word,
- final int depth, final boolean completion, int snr, int inputIndex, int skipPos,
- final ArrayList<SuggestedWordInfo> suggestions) {
+ protected void getWordsRec(final NodeArray roots, final WordComposer codes, final char[] word,
+ final int depth, final boolean completion, final int snr, final int inputIndex,
+ final int skipPos, final ArrayList<SuggestedWordInfo> suggestions) {
final int count = roots.mLength;
final int codeSize = mInputLength;
// Optimization: Prune out words that are too long compared to how much was typed.
@@ -472,8 +475,8 @@ public class ExpandableDictionary extends Dictionary {
getWordsRec(children, codes, word, depth + 1, true, snr, inputIndex,
skipPos, suggestions);
}
- } else if ((c == Keyboard.CODE_SINGLE_QUOTE
- && currentChars[0] != Keyboard.CODE_SINGLE_QUOTE) || depth == skipPos) {
+ } else if ((c == Constants.CODE_SINGLE_QUOTE
+ && currentChars[0] != Constants.CODE_SINGLE_QUOTE) || depth == skipPos) {
// Skip the ' and continue deeper
word[depth] = c;
if (children != null) {
@@ -524,11 +527,13 @@ public class ExpandableDictionary extends Dictionary {
}
}
- public int setBigramAndGetFrequency(String word1, String word2, int frequency) {
+ public int setBigramAndGetFrequency(final String word1, final String word2,
+ final int frequency) {
return setBigramAndGetFrequency(word1, word2, frequency, null /* unused */);
}
- public int setBigramAndGetFrequency(String word1, String word2, ForgettingCurveParams fcp) {
+ public int setBigramAndGetFrequency(final String word1, final String word2,
+ final ForgettingCurveParams fcp) {
return setBigramAndGetFrequency(word1, word2, 0 /* unused */, fcp);
}
@@ -540,8 +545,8 @@ public class ExpandableDictionary extends Dictionary {
* @param fcp an instance of ForgettingCurveParams to use for decay policy
* @return returns the final bigram frequency
*/
- private int setBigramAndGetFrequency(
- String word1, String word2, int frequency, ForgettingCurveParams fcp) {
+ private int setBigramAndGetFrequency(final String word1, final String word2,
+ final int frequency, final ForgettingCurveParams fcp) {
// We don't want results to be different according to case of the looked up left hand side
// word. We do want however to return the correct case for the right hand side.
// So we want to squash the case of the left hand side, and preserve that of the right
@@ -572,7 +577,8 @@ public class ExpandableDictionary extends Dictionary {
* Searches for the word and add the word if it does not exist.
* @return Returns the terminal node of the word we are searching for.
*/
- private Node searchWord(NodeArray children, String word, int depth, Node parentNode) {
+ private Node searchWord(final NodeArray children, final String word, final int depth,
+ final Node parentNode) {
final int wordLength = word.length();
final char c = word.charAt(depth);
// Does children have the current character?
@@ -602,38 +608,19 @@ public class ExpandableDictionary extends Dictionary {
return searchWord(childNode.mChildren, word, depth + 1, childNode);
}
- private void runBigramReverseLookUp(final CharSequence previousWord,
+ private void runBigramReverseLookUp(final String previousWord,
final ArrayList<SuggestedWordInfo> suggestions) {
// Search for the lowercase version of the word only, because that's where bigrams
// store their sons.
- Node prevWord = searchNode(mRoots, previousWord.toString().toLowerCase(), 0,
+ final Node prevWord = searchNode(mRoots, previousWord.toLowerCase(), 0,
previousWord.length());
if (prevWord != null && prevWord.mNGrams != null) {
reverseLookUp(prevWord.mNGrams, suggestions);
}
}
- /**
- * Used for testing purposes and in the spell checker
- * This function will wait for loading from database to be done
- */
- void waitForDictionaryLoading() {
- while (mUpdatingDictionary) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- //
- }
- }
- }
-
- protected final void blockingReloadDictionaryIfRequired() {
- reloadDictionaryIfRequired();
- waitForDictionaryLoading();
- }
-
// Local to reverseLookUp, but do not allocate each time.
- private final char[] mLookedUpString = new char[BinaryDictionary.MAX_WORD_LENGTH];
+ private final char[] mLookedUpString = new char[Constants.Dictionary.MAX_WORD_LENGTH];
/**
* reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
@@ -641,14 +628,14 @@ public class ExpandableDictionary extends Dictionary {
* @param terminalNodes list of terminal nodes we want to add
* @param suggestions the suggestion collection to add the word to
*/
- private void reverseLookUp(LinkedList<NextWord> terminalNodes,
+ private void reverseLookUp(final LinkedList<NextWord> terminalNodes,
final ArrayList<SuggestedWordInfo> suggestions) {
Node node;
int freq;
for (NextWord nextWord : terminalNodes) {
node = nextWord.getWordNode();
freq = nextWord.getFrequency();
- int index = BinaryDictionary.MAX_WORD_LENGTH;
+ int index = Constants.Dictionary.MAX_WORD_LENGTH;
do {
--index;
mLookedUpString[index] = node.mCode;
@@ -660,7 +647,7 @@ public class ExpandableDictionary extends Dictionary {
// to ignore the word in this case.
if (freq >= 0 && node == null) {
suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index,
- BinaryDictionary.MAX_WORD_LENGTH - index),
+ Constants.Dictionary.MAX_WORD_LENGTH - index),
freq, SuggestedWordInfo.KIND_CORRECTION, mDictType));
}
}
@@ -714,7 +701,7 @@ public class ExpandableDictionary extends Dictionary {
}
}
- private static char toLowerCase(char c) {
+ private static char toLowerCase(final char c) {
char baseChar = c;
if (c < BASE_CHARS.length) {
baseChar = BASE_CHARS[c];
diff --git a/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java
new file mode 100644
index 000000000..9f91639a2
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Environment;
+
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * A class to read a local file as a dictionary for debugging purposes.
+ */
+public class ExternalDictionaryGetterForDebug {
+ private static final String SOURCE_FOLDER = Environment.getExternalStorageDirectory().getPath()
+ + "/Download";
+
+ private static String[] findDictionariesInTheDownloadedFolder() {
+ final File[] files = new File(SOURCE_FOLDER).listFiles();
+ final ArrayList<String> eligibleList = CollectionUtils.newArrayList();
+ for (File f : files) {
+ final FileHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(f);
+ if (null == header) continue;
+ eligibleList.add(f.getName());
+ }
+ return eligibleList.toArray(new String[0]);
+ }
+
+ public static void chooseAndInstallDictionary(final Context context) {
+ final String[] fileNames = findDictionariesInTheDownloadedFolder();
+ if (0 == fileNames.length) {
+ showNoFileDialog(context);
+ } else if (1 == fileNames.length) {
+ askInstallFile(context, fileNames[0]);
+ } else {
+ showChooseFileDialog(context, fileNames);
+ }
+ }
+
+ private static void showNoFileDialog(final Context context) {
+ new AlertDialog.Builder(context)
+ .setMessage(R.string.read_external_dictionary_no_files_message)
+ .setPositiveButton(android.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ dialog.dismiss();
+ }
+ }).create().show();
+ }
+
+ private static void showChooseFileDialog(final Context context, final String[] fileNames) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.read_external_dictionary_multiple_files_title)
+ .setItems(fileNames, new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ askInstallFile(context, fileNames[which]);
+ }
+ })
+ .create().show();
+ }
+
+ private static void askInstallFile(final Context context, final String fileName) {
+ final File file = new File(SOURCE_FOLDER, fileName.toString());
+ final FileHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file);
+ final StringBuilder message = new StringBuilder();
+ final String locale = header.getLocaleString();
+ for (String key : header.mDictionaryOptions.mAttributes.keySet()) {
+ message.append(key + " = " + header.mDictionaryOptions.mAttributes.get(key));
+ message.append("\n");
+ }
+ final String languageName = LocaleUtils.constructLocaleFromString(locale)
+ .getDisplayName(Locale.getDefault());
+ final String title = String.format(
+ context.getString(R.string.read_external_dictionary_confirm_install_message),
+ languageName);
+ new AlertDialog.Builder(context)
+ .setTitle(title)
+ .setMessage(message)
+ .setNegativeButton(android.R.string.cancel, new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ dialog.dismiss();
+ }
+ }).setPositiveButton(android.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ installFile(context, file, header);
+ dialog.dismiss();
+ }
+ }).create().show();
+ }
+
+ private static void installFile(final Context context, final File file,
+ final FileHeader header) {
+ BufferedOutputStream outputStream = null;
+ File tempFile = null;
+ try {
+ final String locale = header.getLocaleString();
+ // Create the id for a main dictionary for this locale
+ final String id = BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY
+ + BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale;
+ final String finalFileName = DictionaryInfoUtils.getCacheFileName(id, locale, context);
+ final String tempFileName = BinaryDictionaryGetter.getTempFileName(id, context);
+ tempFile = new File(tempFileName);
+ tempFile.delete();
+ outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));
+ final BufferedInputStream bufferedStream = new BufferedInputStream(
+ new FileInputStream(file));
+ BinaryDictionaryFileDumper.checkMagicAndCopyFileTo(bufferedStream, outputStream);
+ outputStream.flush();
+ final File finalFile = new File(finalFileName);
+ finalFile.delete();
+ if (!tempFile.renameTo(finalFile)) {
+ throw new IOException("Can't move the file to its final name");
+ }
+ } catch (IOException e) {
+ // There was an error: show a dialog
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.error)
+ .setMessage(e.toString())
+ .setPositiveButton(android.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ dialog.dismiss();
+ }
+ }).create().show();
+ return;
+ } finally {
+ try {
+ if (null != outputStream) outputStream.close();
+ if (null != tempFile) tempFile.delete();
+ } catch (IOException e) {
+ // Don't do anything
+ }
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/FileTransforms.java b/java/src/com/android/inputmethod/latin/FileTransforms.java
index 09cf23a9b..692f3c7c1 100644
--- a/java/src/com/android/inputmethod/latin/FileTransforms.java
+++ b/java/src/com/android/inputmethod/latin/FileTransforms.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
diff --git a/java/src/com/android/inputmethod/latin/ImfUtils.java b/java/src/com/android/inputmethod/latin/ImfUtils.java
deleted file mode 100644
index 2674e4575..000000000
--- a/java/src/com/android/inputmethod/latin/ImfUtils.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE;
-
-import android.content.Context;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Utility class for Input Method Framework
- */
-public final class ImfUtils {
- private ImfUtils() {
- // This utility class is not publicly instantiable.
- }
-
- private static InputMethodManager sInputMethodManager;
-
- public static InputMethodManager getInputMethodManager(Context context) {
- if (sInputMethodManager == null) {
- sInputMethodManager = (InputMethodManager)context.getSystemService(
- Context.INPUT_METHOD_SERVICE);
- }
- return sInputMethodManager;
- }
-
- private static InputMethodInfo sInputMethodInfoOfThisIme;
-
- public static InputMethodInfo getInputMethodInfoOfThisIme(Context context) {
- if (sInputMethodInfoOfThisIme == null) {
- final InputMethodManager imm = getInputMethodManager(context);
- final String packageName = context.getPackageName();
- for (final InputMethodInfo imi : imm.getInputMethodList()) {
- if (imi.getPackageName().equals(packageName))
- return imi;
- }
- throw new RuntimeException("Can not find input method id for " + packageName);
- }
- return sInputMethodInfoOfThisIme;
- }
-
- public static String getInputMethodIdOfThisIme(Context context) {
- return getInputMethodInfoOfThisIme(context).getId();
- }
-
- public static boolean checkIfSubtypeBelongsToThisImeAndEnabled(Context context,
- InputMethodSubtype ims) {
- final InputMethodInfo myImi = getInputMethodInfoOfThisIme(context);
- final InputMethodManager imm = getInputMethodManager(context);
- // TODO: Cache all subtypes of this IME for optimization
- final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(myImi, true);
- for (final InputMethodSubtype subtype : subtypes) {
- if (subtype.equals(ims)) {
- return true;
- }
- }
- return false;
- }
-
- public static boolean checkIfSubtypeBelongsToThisIme(Context context,
- InputMethodSubtype ims) {
- final InputMethodInfo myImi = getInputMethodInfoOfThisIme(context);
- final int count = myImi.getSubtypeCount();
- for (int i = 0; i < count; i++) {
- final InputMethodSubtype subtype = myImi.getSubtypeAt(i);
- if (subtype.equals(ims)) {
- return true;
- }
- }
- return false;
- }
-
- public static InputMethodSubtype getCurrentInputMethodSubtype(Context context,
- InputMethodSubtype defaultSubtype) {
- final InputMethodManager imm = getInputMethodManager(context);
- final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype();
- return (currentSubtype != null) ? currentSubtype : defaultSubtype;
- }
-
- public static boolean hasMultipleEnabledIMEsOrSubtypes(Context context,
- final boolean shouldIncludeAuxiliarySubtypes) {
- final InputMethodManager imm = getInputMethodManager(context);
- final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
- return hasMultipleEnabledSubtypes(context, shouldIncludeAuxiliarySubtypes, enabledImis);
- }
-
- public static boolean hasMultipleEnabledSubtypesInThisIme(Context context,
- final boolean shouldIncludeAuxiliarySubtypes) {
- final InputMethodInfo myImi = getInputMethodInfoOfThisIme(context);
- final List<InputMethodInfo> imiList = Collections.singletonList(myImi);
- return hasMultipleEnabledSubtypes(context, shouldIncludeAuxiliarySubtypes, imiList);
- }
-
- private static boolean hasMultipleEnabledSubtypes(Context context,
- final boolean shouldIncludeAuxiliarySubtypes, List<InputMethodInfo> imiList) {
- final InputMethodManager imm = getInputMethodManager(context);
-
- // Number of the filtered IMEs
- int filteredImisCount = 0;
-
- for (InputMethodInfo imi : imiList) {
- // We can return true immediately after we find two or more filtered IMEs.
- if (filteredImisCount > 1) return true;
- final List<InputMethodSubtype> subtypes =
- imm.getEnabledInputMethodSubtypeList(imi, true);
- // IMEs that have no subtypes should be counted.
- if (subtypes.isEmpty()) {
- ++filteredImisCount;
- continue;
- }
-
- int auxCount = 0;
- for (InputMethodSubtype subtype : subtypes) {
- if (subtype.isAuxiliary()) {
- ++auxCount;
- }
- }
- final int nonAuxCount = subtypes.size() - auxCount;
-
- // IMEs that have one or more non-auxiliary subtypes should be counted.
- // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
- // subtypes should be counted as well.
- if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
- ++filteredImisCount;
- continue;
- }
- }
-
- if (filteredImisCount > 1) {
- return true;
- }
- final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(null, true);
- int keyboardCount = 0;
- // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
- // both explicitly and implicitly enabled input method subtype.
- // (The current IME should be LatinIME.)
- for (InputMethodSubtype subtype : subtypes) {
- if (KEYBOARD_MODE.equals(subtype.getMode())) {
- ++keyboardCount;
- }
- }
- return keyboardCount > 1;
- }
-
- public static InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(
- Context context, String localeString, String keyboardLayoutSetName) {
- final InputMethodInfo imi = getInputMethodInfoOfThisIme(context);
- final int count = imi.getSubtypeCount();
- for (int i = 0; i < count; i++) {
- final InputMethodSubtype subtype = imi.getSubtypeAt(i);
- final String layoutName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
- if (localeString.equals(subtype.getLocale())
- && keyboardLayoutSetName.equals(layoutName)) {
- return subtype;
- }
- }
- return null;
- }
-
- public static void setAdditionalInputMethodSubtypes(Context context,
- InputMethodSubtype[] subtypes) {
- final InputMethodManager imm = getInputMethodManager(context);
- final String imiId = getInputMethodIdOfThisIme(context);
- imm.setAdditionalInputMethodSubtypes(imiId, subtypes);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 422fc72d3..dd58db575 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -117,36 +117,56 @@ public final class InputAttributes {
if (inputClass == InputType.TYPE_CLASS_DATETIME)
Log.i(TAG, " TYPE_CLASS_DATETIME");
Log.i(TAG, "Variation:");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS))
- Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_ADDRESS");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT))
- Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_SUBJECT");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_FILTER))
- Log.i(TAG, " TYPE_TEXT_VARIATION_FILTER");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE))
- Log.i(TAG, " TYPE_TEXT_VARIATION_LONG_MESSAGE");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_NORMAL))
- Log.i(TAG, " TYPE_TEXT_VARIATION_NORMAL");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PASSWORD))
- Log.i(TAG, " TYPE_TEXT_VARIATION_PASSWORD");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PERSON_NAME))
- Log.i(TAG, " TYPE_TEXT_VARIATION_PERSON_NAME");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PHONETIC))
- Log.i(TAG, " TYPE_TEXT_VARIATION_PHONETIC");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS))
- Log.i(TAG, " TYPE_TEXT_VARIATION_POSTAL_ADDRESS");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE))
- Log.i(TAG, " TYPE_TEXT_VARIATION_SHORT_MESSAGE");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_URI))
- Log.i(TAG, " TYPE_TEXT_VARIATION_URI");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD))
- Log.i(TAG, " TYPE_TEXT_VARIATION_VISIBLE_PASSWORD");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT))
- Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EDIT_TEXT");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS))
- Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS");
- if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD))
- Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_PASSWORD");
+ switch (InputType.TYPE_MASK_VARIATION & inputType) {
+ case InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_ADDRESS");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_SUBJECT");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_FILTER:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_FILTER");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_LONG_MESSAGE");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_NORMAL:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_NORMAL");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_PASSWORD:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_PASSWORD");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_PERSON_NAME:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_PERSON_NAME");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_PHONETIC:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_PHONETIC");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_POSTAL_ADDRESS");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_SHORT_MESSAGE");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_URI:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_URI");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_VISIBLE_PASSWORD");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EDIT_TEXT");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS");
+ break;
+ case InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD:
+ Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_PASSWORD");
+ break;
+ default:
+ Log.i(TAG, " Unknown variation");
+ break;
+ }
Log.i(TAG, "Flags:");
if (0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS))
Log.i(TAG, " TYPE_TEXT_FLAG_NO_SUGGESTIONS");
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index 6b48aabb3..81c833000 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -1,23 +1,28 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import android.util.Log;
+
// TODO: This class is not thread-safe.
public final class InputPointers {
+ private static final String TAG = InputPointers.class.getSimpleName();
private final int mDefaultCapacity;
private final ResizableIntArray mXCoordinates;
private final ResizableIntArray mYCoordinates;
@@ -39,7 +44,8 @@ public final class InputPointers {
mTimes.add(index, time);
}
- public void addPointer(int x, int y, int pointerId, int time) {
+ @UsedForTesting
+ void addPointer(int x, int y, int pointerId, int time) {
mXCoordinates.add(x);
mYCoordinates.add(y);
mPointerIds.add(pointerId);
@@ -66,7 +72,8 @@ public final class InputPointers {
* @param startPos the starting index of the pointers in {@code src}.
* @param length the number of pointers to be appended.
*/
- public void append(InputPointers src, int startPos, int length) {
+ @UsedForTesting
+ void append(InputPointers src, int startPos, int length) {
if (length == 0) {
return;
}
@@ -122,6 +129,11 @@ public final class InputPointers {
}
public int[] getTimes() {
+ if (LatinImeLogger.sDBG) {
+ if (!isValidTimeStamps()) {
+ throw new RuntimeException("Time stamps are invalid.");
+ }
+ }
return mTimes.getPrimitiveArray();
}
@@ -130,4 +142,18 @@ public final class InputPointers {
return "size=" + getPointerSize() + " id=" + mPointerIds + " time=" + mTimes
+ " x=" + mXCoordinates + " y=" + mYCoordinates;
}
+
+ private boolean isValidTimeStamps() {
+ final int[] times = mTimes.getPrimitiveArray();
+ for (int i = 1; i < getPointerSize(); ++i) {
+ if (times[i] < times[i - 1]) {
+ // dump
+ for (int j = 0; j < times.length; ++j) {
+ Log.d(TAG, "--- (" + j + ") " + times[j]);
+ }
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
index 9a4503bf4..ecb20144b 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -1,22 +1,23 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
import android.text.InputType;
+import android.view.inputmethod.EditorInfo;
public final class InputTypeUtils implements InputType {
private static final int WEB_TEXT_PASSWORD_INPUT_TYPE =
@@ -35,6 +36,7 @@ public final class InputTypeUtils implements InputType {
InputType.TYPE_TEXT_VARIATION_URI,
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD };
+ public static final int IME_ACTION_CUSTOM_LABEL = EditorInfo.IME_MASK_ACTION + 1;
private InputTypeUtils() {
// This utility class is not publicly instantiable.
@@ -102,4 +104,15 @@ public final class InputTypeUtils implements InputType {
}
return true;
}
+
+ public static int getImeOptionsActionIdFromEditorInfo(final EditorInfo editorInfo) {
+ if ((editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
+ return EditorInfo.IME_ACTION_NONE;
+ } else if (editorInfo.actionLabel != null) {
+ return IME_ACTION_CUSTOM_LABEL;
+ } else {
+ // Note: this is different from editorInfo.actionId, hence "ImeOptionsActionId"
+ return editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java
index d7595bfbe..5359c8185 100644
--- a/java/src/com/android/inputmethod/latin/InputView.java
+++ b/java/src/com/android/inputmethod/latin/InputView.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
diff --git a/java/src/com/android/inputmethod/latin/IntentUtils.java b/java/src/com/android/inputmethod/latin/IntentUtils.java
new file mode 100644
index 000000000..d175af504
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/IntentUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Intent;
+import android.text.TextUtils;
+
+public final class IntentUtils {
+ private static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
+ // TODO: Can these be constants instead of literal String constants?
+ private static final String INPUT_METHOD_SUBTYPE_SETTINGS =
+ "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
+
+ private IntentUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static Intent getInputLanguageSelectionIntent(final String inputMethodId,
+ final int flagsForSubtypeSettings) {
+ // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
+ final String action = INPUT_METHOD_SUBTYPE_SETTINGS;
+ final Intent intent = new Intent(action);
+ if (!TextUtils.isEmpty(inputMethodId)) {
+ intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId);
+ }
+ if (flagsForSubtypeSettings > 0) {
+ intent.setFlags(flagsForSubtypeSettings);
+ }
+ return intent;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 94cdc9b85..826dc11e7 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -44,19 +44,22 @@ public final class LastComposedWord {
public final String mTypedWord;
public final String mCommittedWord;
public final String mSeparatorString;
- public final CharSequence mPrevWord;
- public final InputPointers mInputPointers = new InputPointers(BinaryDictionary.MAX_WORD_LENGTH);
+ public final String mPrevWord;
+ public final int mCapitalizedMode;
+ public final InputPointers mInputPointers =
+ new InputPointers(Constants.Dictionary.MAX_WORD_LENGTH);
private boolean mActive;
public static final LastComposedWord NOT_A_COMPOSED_WORD =
- new LastComposedWord(null, null, "", "", NOT_A_SEPARATOR, null);
+ new LastComposedWord(null, null, "", "", NOT_A_SEPARATOR, null,
+ WordComposer.CAPS_MODE_OFF);
// Warning: this is using the passed objects as is and fully expects them to be
// immutable. Do not fiddle with their contents after you passed them to this constructor.
public LastComposedWord(final int[] primaryKeyCodes, final InputPointers inputPointers,
- final String typedWord, final String committedWord,
- final String separatorString, final CharSequence prevWord) {
+ final String typedWord, final String committedWord, final String separatorString,
+ final String prevWord, final int capitalizedMode) {
mPrimaryKeyCodes = primaryKeyCodes;
if (inputPointers != null) {
mInputPointers.copy(inputPointers);
@@ -66,6 +69,7 @@ public final class LastComposedWord {
mSeparatorString = separatorString;
mActive = true;
mPrevWord = prevWord;
+ mCapitalizedMode = capitalizedMode;
}
public void deactivate() {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index e2a76a456..bb7e2d1c2 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -35,6 +35,7 @@ import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManager;
import android.net.ConnectivityManager;
+import android.os.Build.VERSION_CODES;
import android.os.Debug;
import android.os.Handler;
import android.os.HandlerThread;
@@ -60,18 +61,16 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
-import com.android.inputmethod.compat.CompatUtils;
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
import com.android.inputmethod.compat.SuggestionSpanUtils;
+import com.android.inputmethod.event.EventInterpreter;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
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.LocaleUtils.RunInLocale;
import com.android.inputmethod.latin.Utils.Stats;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.suggestions.SuggestionStripView;
@@ -81,6 +80,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Locale;
+import java.util.TreeSet;
/**
* Input method implementation for Qwerty'ish keyboard.
@@ -126,22 +126,25 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// Current space state of the input method. This can be any of the above constants.
private int mSpaceState;
- private SettingsValues mCurrentSettings;
+ private final Settings mSettings;
private View mExtractArea;
private View mKeyPreviewBackingView;
private View mSuggestionsContainer;
private SuggestionStripView mSuggestionStripView;
- /* package for tests */ Suggest mSuggest;
+ // Never null
+ private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+ @UsedForTesting Suggest mSuggest;
private CompletionInfo[] mApplicationSpecifiedCompletions;
private ApplicationInfo mTargetApplicationInfo;
- private InputMethodManagerCompatWrapper mImm;
- private Resources mResources;
- private SharedPreferences mPrefs;
- /* package for tests */ final KeyboardSwitcher mKeyboardSwitcher;
+ private RichInputMethodManager mRichImm;
+ @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
private final SubtypeSwitcher mSubtypeSwitcher;
private final SubtypeState mSubtypeState = new SubtypeState();
+ // At start, create a default event interpreter that does nothing by passing it no decoder spec.
+ // The event interpreter should never be null.
+ private EventInterpreter mEventInterpreter = new EventInterpreter(this);
private boolean mIsMainDictionaryAvailable;
private UserBinaryDictionary mUserDictionary;
@@ -164,18 +167,19 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private boolean mExpectingUpdateSelection;
private int mDeleteCount;
private long mLastKeyTime;
-
- private AudioAndHapticFeedbackManager mFeedbackManager;
+ private TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
// Member variables for remembering the current device orientation.
private int mDisplayOrientation;
// Object for reacting to adding/removing a dictionary pack.
+ // TODO: The experimental version is not supported by the Dictionary Pack Service yet.
private BroadcastReceiver mDictionaryPackInstallReceiver =
- new DictionaryPackInstallBroadcastReceiver(this);
+ ProductionFlag.IS_EXPERIMENTAL
+ ? null : new DictionaryPackInstallBroadcastReceiver(this);
// Keeps track of most recently inserted text (multi-character key) for reverting
- private CharSequence mEnteredText;
+ private String mEnteredText;
private boolean mIsAutoCorrectionIndicatorOn;
@@ -195,8 +199,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private int mDelayUpdateSuggestions;
private int mDelayUpdateShiftState;
- private long mDoubleSpacesTurnIntoPeriodTimeout;
- private long mDoubleSpaceTimerStart;
+ private long mDoubleSpacePeriodTimeout;
+ private long mDoubleSpacePeriodTimerStart;
public UIHandler(final LatinIME outerInstance) {
super(outerInstance);
@@ -208,8 +212,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
res.getInteger(R.integer.config_delay_update_suggestions);
mDelayUpdateShiftState =
res.getInteger(R.integer.config_delay_update_shift_state);
- mDoubleSpacesTurnIntoPeriodTimeout = res.getInteger(
- R.integer.config_double_spaces_turn_into_period_timeout);
+ mDoubleSpacePeriodTimeout =
+ res.getInteger(R.integer.config_double_space_period_timeout);
}
@Override
@@ -260,17 +264,17 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
.sendToTarget();
}
- public void startDoubleSpacesTimer() {
- mDoubleSpaceTimerStart = SystemClock.uptimeMillis();
+ public void startDoubleSpacePeriodTimer() {
+ mDoubleSpacePeriodTimerStart = SystemClock.uptimeMillis();
}
- public void cancelDoubleSpacesTimer() {
- mDoubleSpaceTimerStart = 0;
+ public void cancelDoubleSpacePeriodTimer() {
+ mDoubleSpacePeriodTimerStart = 0;
}
- public boolean isAcceptingDoubleSpaces() {
- return SystemClock.uptimeMillis() - mDoubleSpaceTimerStart
- < mDoubleSpacesTurnIntoPeriodTimeout;
+ public boolean isAcceptingDoubleSpacePeriod() {
+ return SystemClock.uptimeMillis() - mDoubleSpacePeriodTimerStart
+ < mDoubleSpacePeriodTimeout;
}
// Working variables for the following methods.
@@ -375,9 +379,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mCurrentSubtypeUsed = true;
}
- public void switchSubtype(final IBinder token, final InputMethodManagerCompatWrapper imm,
- final Context context) {
- final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype();
+ public void switchSubtype(final IBinder token, final RichInputMethodManager richImm) {
+ final InputMethodSubtype currentSubtype = richImm.getInputMethodManager()
+ .getCurrentInputMethodSubtype();
final InputMethodSubtype lastActiveSubtype = mLastActiveSubtype;
final boolean currentSubtypeUsed = mCurrentSubtypeUsed;
if (currentSubtypeUsed) {
@@ -385,18 +389,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mCurrentSubtypeUsed = false;
}
if (currentSubtypeUsed
- && ImfUtils.checkIfSubtypeBelongsToThisImeAndEnabled(context, lastActiveSubtype)
+ && richImm.checkIfSubtypeBelongsToThisImeAndEnabled(lastActiveSubtype)
&& !currentSubtype.equals(lastActiveSubtype)) {
- final String id = ImfUtils.getInputMethodIdOfThisIme(context);
- imm.setInputMethodAndSubtype(token, id, lastActiveSubtype);
+ richImm.setInputMethodAndSubtype(token, lastActiveSubtype);
return;
}
- imm.switchToNextInputMethod(token, true /* onlyCurrentIme */);
+ richImm.switchToNextInputMethod(token, true /* onlyCurrentIme */);
}
}
public LatinIME() {
super();
+ mSettings = Settings.getInstance();
mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mKeyboardSwitcher = KeyboardSwitcher.getInstance();
mIsHardwareAcceleratedDrawingEnabled =
@@ -406,33 +410,27 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
@Override
public void onCreate() {
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- mPrefs = prefs;
- LatinImeLogger.init(this, prefs);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.getInstance().init(this, prefs);
- }
- InputMethodManagerCompatWrapper.init(this);
+ Settings.init(this);
+ LatinImeLogger.init(this);
+ RichInputMethodManager.init(this);
+ mRichImm = RichInputMethodManager.getInstance();
SubtypeSwitcher.init(this);
- KeyboardSwitcher.init(this, prefs);
+ KeyboardSwitcher.init(this);
+ AudioAndHapticFeedbackManager.init(this);
AccessibilityUtils.init(this);
super.onCreate();
- mImm = InputMethodManagerCompatWrapper.getInstance();
mHandler.onCreate();
DEBUG = LatinImeLogger.sDBG;
- final Resources res = getResources();
- mResources = res;
-
loadSettings();
-
- ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes());
-
initSuggest();
- mDisplayOrientation = res.getConfiguration().orientation;
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.getInstance().init(this, mKeyboardSwitcher);
+ }
+ mDisplayOrientation = getResources().getConfiguration().orientation;
// Register to receive ringer mode change and network state change.
// Also receive installation and removal of a dictionary pack.
@@ -441,34 +439,28 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
registerReceiver(mReceiver, filter);
- final IntentFilter packageFilter = new IntentFilter();
- packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- packageFilter.addDataScheme(SCHEME_PACKAGE);
- registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
+ // TODO: The experimental version is not supported by the Dictionary Pack Service yet.
+ if (!ProductionFlag.IS_EXPERIMENTAL) {
+ final IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addDataScheme(SCHEME_PACKAGE);
+ registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
- final IntentFilter newDictFilter = new IntentFilter();
- newDictFilter.addAction(
- DictionaryPackInstallBroadcastReceiver.NEW_DICTIONARY_INTENT_ACTION);
- registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
+ final IntentFilter newDictFilter = new IntentFilter();
+ newDictFilter.addAction(
+ DictionaryPackInstallBroadcastReceiver.NEW_DICTIONARY_INTENT_ACTION);
+ registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
+ }
}
// Has to be package-visible for unit tests
- /* package for test */
+ @UsedForTesting
void loadSettings() {
- // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
- // is not guaranteed. It may even be called at the same time on a different thread.
- if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale();
final InputAttributes inputAttributes =
new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode());
- final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
- @Override
- protected SettingsValues job(Resources res) {
- return new SettingsValues(mPrefs, inputAttributes, LatinIME.this);
- }
- };
- mCurrentSettings = job.runInLocale(mResources, mSubtypeSwitcher.getCurrentSubtypeLocale());
- mFeedbackManager = new AudioAndHapticFeedbackManager(this, mCurrentSettings);
+ mSettings.loadSettings(locale, inputAttributes);
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
}
@@ -495,8 +487,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
mSuggest = new Suggest(this /* Context */, subtypeLocale,
this /* SuggestInitializationListener */);
- if (mCurrentSettings.mCorrectionEnabled) {
- mSuggest.setAutoCorrectionThreshold(mCurrentSettings.mAutoCorrectionThreshold);
+ if (mSettings.getCurrent().mCorrectionEnabled) {
+ mSuggest.setAutoCorrectionThreshold(mSettings.getCurrent().mAutoCorrectionThreshold);
}
mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
@@ -510,10 +502,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
resetContactsDictionary(oldContactsDictionary);
- // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
- // is not guaranteed. It may even be called at the same time on a different thread.
- if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- mUserHistoryDictionary = UserHistoryDictionary.getInstance(this, localeStr, mPrefs);
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mUserHistoryDictionary = UserHistoryDictionary.getInstance(this, localeStr, prefs);
mSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
}
@@ -526,7 +516,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
* @param oldContactsDictionary an optional dictionary to use, or null
*/
private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary) {
- final boolean shouldSetDictionary = (null != mSuggest && mCurrentSettings.mUseContactsDict);
+ final boolean shouldSetDictionary =
+ (null != mSuggest && mSettings.getCurrent().mUseContactsDict);
final ContactsBinaryDictionary dictionaryToUse;
if (!shouldSetDictionary) {
@@ -570,8 +561,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mSuggest.close();
mSuggest = null;
}
+ mSettings.onDestroy();
unregisterReceiver(mReceiver);
- unregisterReceiver(mDictionaryPackInstallReceiver);
+ // TODO: The experimental version is not supported by the Dictionary Pack Service yet.
+ if (!ProductionFlag.IS_EXPERIMENTAL) {
+ unregisterReceiver(mDictionaryPackInstallReceiver);
+ }
LatinImeLogger.commit();
LatinImeLogger.onDestroy();
super.onDestroy();
@@ -579,10 +574,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
@Override
public void onConfigurationChanged(final Configuration conf) {
- // System locale has been changed. Needs to reload keyboard.
- if (mSubtypeSwitcher.onConfigurationChanged(conf, this)) {
- loadKeyboard();
- }
// If orientation changed while predicting, commit the change
if (mDisplayOrientation != conf.orientation) {
mDisplayOrientation = conf.orientation;
@@ -648,7 +639,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) {
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
- mSubtypeSwitcher.updateSubtype(subtype);
+ mSubtypeSwitcher.onSubtypeChanged(subtype);
loadKeyboard();
}
@@ -661,6 +652,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
super.onStartInputView(editorInfo, restarting);
final KeyboardSwitcher switcher = mKeyboardSwitcher;
final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
+ final SettingsValues currentSettings = mSettings.getCurrent();
if (editorInfo == null) {
Log.e(TAG, "Null EditorInfo in onStartInputView()");
@@ -681,7 +673,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
+ ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0));
}
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, mPrefs);
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, prefs);
}
if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
Log.w(TAG, "Deprecated private IME option specified: "
@@ -713,18 +706,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
}
- final boolean inputTypeChanged = !mCurrentSettings.isSameInputType(editorInfo);
+ final boolean inputTypeChanged = !currentSettings.isSameInputType(editorInfo);
final boolean isDifferentTextField = !restarting || inputTypeChanged;
if (isDifferentTextField) {
- final boolean currentSubtypeEnabled = mSubtypeSwitcher
- .updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
- if (!currentSubtypeEnabled) {
- // Current subtype is disabled. Needs to update subtype and keyboard.
- final InputMethodSubtype newSubtype = ImfUtils.getCurrentInputMethodSubtype(
- this, mSubtypeSwitcher.getNoLanguageSubtype());
- mSubtypeSwitcher.updateSubtype(newSubtype);
- loadKeyboard();
- }
+ mSubtypeSwitcher.updateParametersOnStartInputView();
}
// The EditorInfo might have a flag that affects fullscreen mode.
@@ -738,12 +723,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
resetComposingState(true /* alsoResetLastComposedWord */);
mDeleteCount = 0;
mSpaceState = SPACE_STATE_NONE;
+ mCurrentlyPressedHardwareKeys.clear();
if (mSuggestionStripView != null) {
// This will set the punctuation suggestions if next word suggestion is off;
// otherwise it will clear the suggestion strip.
setPunctuationSuggestions();
}
+ mSuggestedWords = SuggestedWords.EMPTY;
mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart);
@@ -751,11 +738,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mainKeyboardView.closing();
loadSettings();
- if (mSuggest != null && mCurrentSettings.mCorrectionEnabled) {
- mSuggest.setAutoCorrectionThreshold(mCurrentSettings.mAutoCorrectionThreshold);
+ if (mSuggest != null && currentSettings.mCorrectionEnabled) {
+ mSuggest.setAutoCorrectionThreshold(currentSettings.mAutoCorrectionThreshold);
}
- switcher.loadKeyboard(editorInfo, mCurrentSettings);
+ switcher.loadKeyboard(editorInfo, currentSettings);
} else if (restarting) {
// TODO: Come up with a more comprehensive way to reset the keyboard layout when
// a keyboard layout set doesn't get reloaded in this method.
@@ -773,21 +760,25 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mLastSelectionEnd = editorInfo.initialSelEnd;
mHandler.cancelUpdateSuggestionStrip();
- mHandler.cancelDoubleSpacesTimer();
+ mHandler.cancelDoubleSpacePeriodTimer();
mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable);
- mainKeyboardView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn,
- mCurrentSettings.mKeyPreviewPopupDismissDelay);
- mainKeyboardView.setGestureHandlingEnabledByUser(mCurrentSettings.mGestureInputEnabled);
- mainKeyboardView.setGesturePreviewMode(mCurrentSettings.mGesturePreviewTrailEnabled,
- mCurrentSettings.mGestureFloatingPreviewTextEnabled);
+ mainKeyboardView.setKeyPreviewPopupEnabled(currentSettings.mKeyPreviewPopupOn,
+ currentSettings.mKeyPreviewPopupDismissDelay);
+ mainKeyboardView.setSlidingKeyInputPreviewEnabled(
+ currentSettings.mSlidingKeyInputPreviewEnabled);
+ mainKeyboardView.setGestureHandlingEnabledByUser(
+ currentSettings.mGestureInputEnabled);
+ mainKeyboardView.setGesturePreviewMode(currentSettings.mGesturePreviewTrailEnabled,
+ currentSettings.mGestureFloatingPreviewTextEnabled);
// If we have a user dictionary addition in progress, we should check now if we should
// replace the previously committed string with the word that has actually been added
// to the user dictionary.
if (null != mPositionalInfoForUserDictPendingAddition
&& mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
- mConnection, editorInfo, mLastSelectionEnd)) {
+ mConnection, editorInfo, mLastSelectionEnd,
+ mSubtypeSwitcher.getCurrentSubtypeLocale())) {
mPositionalInfoForUserDictPendingAddition = null;
}
// If tryReplaceWithActualWord returns false, we don't know what word was
@@ -821,10 +812,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
super.onFinishInput();
LatinImeLogger.commit();
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.getInstance().latinIME_onFinishInputInternal();
- }
-
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
mainKeyboardView.closing();
@@ -840,6 +827,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
// Remove pending messages related to update suggestions
mHandler.cancelUpdateSuggestionStrip();
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.getInstance().latinIME_onFinishInputViewInternal();
+ }
}
@Override
@@ -938,7 +929,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
*/
@Override
public void onExtractedTextClicked() {
- if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) return;
+ if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) return;
super.onExtractedTextClicked();
}
@@ -954,7 +945,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
*/
@Override
public void onExtractedCursorMovement(final int dx, final int dy) {
- if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) return;
+ if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) return;
super.onExtractedCursorMovement(dx, dy);
}
@@ -964,6 +955,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
LatinImeLogger.commit();
mKeyboardSwitcher.onHideWindow();
+ if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
+ AccessibleKeyboardViewProxy.getInstance().onHideWindow();
+ }
+
if (TRACE) Debug.stopMethodTracing();
if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
mOptionsDialog.dismiss();
@@ -982,7 +977,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
}
}
- if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return;
+ if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) return;
mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
if (applicationSpecifiedCompletions == null) {
clearSuggestionStrip();
@@ -1004,11 +999,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
false /* isPrediction */);
// When in fullscreen mode, show completions generated by the application
final boolean isAutoCorrection = false;
- setSuggestionStrip(suggestedWords, isAutoCorrection);
+ setSuggestedWords(suggestedWords, isAutoCorrection);
setAutoCorrectionIndicator(isAutoCorrection);
- // TODO: is this the right thing to do? What should we auto-correct to in
- // this case? This says to keep whatever the user typed.
- mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
setSuggestionStripShown(true);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
@@ -1050,7 +1042,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
final int keyboardHeight = mainKeyboardView.getHeight();
final int suggestionsHeight = mSuggestionsContainer.getHeight();
- final int displayHeight = mResources.getDisplayMetrics().heightPixels;
+ final int displayHeight = getResources().getDisplayMetrics().heightPixels;
final Rect rect = new Rect();
mKeyPreviewBackingView.getWindowVisibleDisplayFrame(rect);
final int notificationBarHeight = rect.top;
@@ -1080,12 +1072,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final int suggestionsHeight = (mSuggestionsContainer.getVisibility() == View.GONE) ? 0
: mSuggestionsContainer.getHeight();
final int extraHeight = extractHeight + backingHeight + suggestionsHeight;
- int touchY = extraHeight;
+ int visibleTopY = extraHeight;
// Need to set touchable region only if input view is being shown
if (mainKeyboardView.isShown()) {
if (mSuggestionsContainer.getVisibility() == View.VISIBLE) {
- touchY -= suggestionsHeight;
+ visibleTopY -= suggestionsHeight;
}
+ final int touchY = mainKeyboardView.isShowingMoreKeysPanel() ? 0 : visibleTopY;
final int touchWidth = mainKeyboardView.getWidth();
final int touchHeight = mainKeyboardView.getHeight() + extraHeight
// Extend touchable region below the keyboard.
@@ -1093,15 +1086,15 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
outInsets.touchableRegion.set(0, touchY, touchWidth, touchHeight);
}
- outInsets.contentTopInsets = touchY;
- outInsets.visibleTopInsets = touchY;
+ outInsets.contentTopInsets = visibleTopY;
+ outInsets.visibleTopInsets = visibleTopY;
}
@Override
public boolean onEvaluateFullscreenMode() {
// Reread resource value here, because this method is called by framework anytime as needed.
final boolean isFullscreenModeAllowed =
- mCurrentSettings.isFullscreenModeAllowed(getResources());
+ Settings.readUseFullscreenMode(getResources());
if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) {
// TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI
// implies NO_FULLSCREEN. However, the framework mistakenly does. i.e. NO_EXTRACT_UI
@@ -1128,10 +1121,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// the composing word, reset the last composed word, tell the inputconnection about it.
private void resetEntireInputState(final int newCursorPosition) {
resetComposingState(true /* alsoResetLastComposedWord */);
- if (mCurrentSettings.mBigramPredictionEnabled) {
+ if (mSettings.getCurrent().mBigramPredictionEnabled) {
clearSuggestionStrip();
} else {
- setSuggestionStrip(mCurrentSettings.mSuggestPuncList, false);
+ setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false);
}
mConnection.resetCachesUponCursorMove(newCursorPosition);
}
@@ -1144,17 +1137,20 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private void commitTyped(final String separatorString) {
if (!mWordComposer.isComposingWord()) return;
- final CharSequence typedWord = mWordComposer.getTypedWord();
+ final String typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD,
separatorString);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
+ }
}
}
// Called from the KeyboardSwitcher which needs to know auto caps state to display
// the right layout.
public int getCurrentAutoCapsState() {
- if (!mCurrentSettings.mAutoCap) return Constants.TextUtils.CAP_MODE_OFF;
+ if (!mSettings.getCurrent().mAutoCap) return Constants.TextUtils.CAP_MODE_OFF;
final EditorInfo ei = getCurrentInputEditorInfo();
if (ei == null) return Constants.TextUtils.CAP_MODE_OFF;
@@ -1178,46 +1174,53 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
private void swapSwapperAndSpace() {
- CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
+ final CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
// It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
if (lastTwo != null && lastTwo.length() == 2
- && lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
+ && lastTwo.charAt(0) == Constants.CODE_SPACE) {
mConnection.deleteSurroundingText(2, 0);
- mConnection.commitText(lastTwo.charAt(1) + " ", 1);
+ final String text = lastTwo.charAt(1) + " ";
+ mConnection.commitText(text, 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_swapSwapperAndSpace();
+ ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text);
}
mKeyboardSwitcher.updateShiftState();
}
}
- private boolean maybeDoubleSpace() {
- if (!mCurrentSettings.mCorrectionEnabled) return false;
- if (!mHandler.isAcceptingDoubleSpaces()) return false;
+ private boolean maybeDoubleSpacePeriod() {
+ if (!mSettings.getCurrent().mCorrectionEnabled) return false;
+ if (!mSettings.getCurrent().mUseDoubleSpacePeriod) return false;
+ if (!mHandler.isAcceptingDoubleSpacePeriod()) return false;
final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0);
if (lastThree != null && lastThree.length() == 3
- && canBeFollowedByPeriod(lastThree.charAt(0))
- && lastThree.charAt(1) == Keyboard.CODE_SPACE
- && lastThree.charAt(2) == Keyboard.CODE_SPACE) {
- mHandler.cancelDoubleSpacesTimer();
+ && canBeFollowedByDoubleSpacePeriod(lastThree.charAt(0))
+ && lastThree.charAt(1) == Constants.CODE_SPACE
+ && lastThree.charAt(2) == Constants.CODE_SPACE) {
+ mHandler.cancelDoubleSpacePeriodTimer();
mConnection.deleteSurroundingText(2, 0);
- mConnection.commitText(". ", 1);
+ final String textToInsert = ". ";
+ mConnection.commitText(textToInsert, 1);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
+ false /* isBatchMode */);
+ }
mKeyboardSwitcher.updateShiftState();
return true;
}
return false;
}
- private static boolean canBeFollowedByPeriod(final int codePoint) {
+ private static boolean canBeFollowedByDoubleSpacePeriod(final int codePoint) {
// TODO: Check again whether there really ain't a better way to check this.
// TODO: This should probably be language-dependant...
return Character.isLetterOrDigit(codePoint)
- || codePoint == Keyboard.CODE_SINGLE_QUOTE
- || codePoint == Keyboard.CODE_DOUBLE_QUOTE
- || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
- || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
- || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
- || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
+ || codePoint == Constants.CODE_SINGLE_QUOTE
+ || codePoint == Constants.CODE_DOUBLE_QUOTE
+ || codePoint == Constants.CODE_CLOSING_PARENTHESIS
+ || codePoint == Constants.CODE_CLOSING_SQUARE_BRACKET
+ || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET
+ || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET;
}
// Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is
@@ -1229,10 +1232,17 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mPositionalInfoForUserDictPendingAddition = null;
return;
}
+ final String wordToEdit;
+ if (CapsModeUtils.isAutoCapsMode(mLastComposedWord.mCapitalizedMode)) {
+ wordToEdit = word.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
+ } else {
+ wordToEdit = word;
+ }
mPositionalInfoForUserDictPendingAddition =
new PositionalInfoForUserDictPendingAddition(
- word, mLastSelectionEnd, getCurrentInputEditorInfo());
- mUserDictionary.addWordToUserDictionary(word, 128);
+ wordToEdit, mLastSelectionEnd, getCurrentInputEditorInfo(),
+ mLastComposedWord.mCapitalizedMode);
+ mUserDictionary.addWordToUserDictionary(wordToEdit);
}
public void onWordAddedToUserDictionary(final String newSpelling) {
@@ -1245,7 +1255,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
mPositionalInfoForUserDictPendingAddition.setActualWordBeingAdded(newSpelling);
if (mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
- mConnection, getCurrentInputEditorInfo(), mLastSelectionEnd)) {
+ mConnection, getCurrentInputEditorInfo(), mLastSelectionEnd,
+ mSubtypeSwitcher.getCurrentSubtypeLocale())) {
mPositionalInfoForUserDictPendingAddition = null;
}
}
@@ -1267,9 +1278,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
if (isShowingOptionDialog()) return false;
switch (requestCode) {
case CODE_SHOW_INPUT_METHOD_PICKER:
- if (ImfUtils.hasMultipleEnabledIMEsOrSubtypes(
- this, true /* include aux subtypes */)) {
- mImm.showInputMethodPicker();
+ if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
+ mRichImm.getInputMethodManager().showInputMethodPicker();
return true;
}
return false;
@@ -1281,10 +1291,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
return mOptionsDialog != null && mOptionsDialog.isShowing();
}
- private static int getActionId(final Keyboard keyboard) {
- return keyboard != null ? keyboard.mId.imeActionId() : EditorInfo.IME_ACTION_NONE;
- }
-
private void performEditorAction(final int actionId) {
mConnection.performEditorAction(actionId);
}
@@ -1292,11 +1298,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// TODO: Revise the language switch key behavior to make it much smarter and more reasonable.
private void handleLanguageSwitchKey() {
final IBinder token = getWindow().getWindow().getAttributes().token;
- if (mCurrentSettings.mIncludesOtherImesInLanguageSwitchList) {
- mImm.switchToNextInputMethod(token, false /* onlyCurrentIme */);
+ if (mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList) {
+ mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */);
return;
}
- mSubtypeState.switchSubtype(token, mImm, this);
+ mSubtypeState.switchSubtype(token, mRichImm);
}
private void sendDownUpKeyEventForBackwardCompatibility(final int code) {
@@ -1310,20 +1316,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
private void sendKeyCodePoint(final int code) {
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_sendKeyCodePoint(code);
+ }
// TODO: Remove this special handling of digit letters.
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (code >= '0' && code <= '9') {
sendDownUpKeyEventForBackwardCompatibility(code - '0' + KeyEvent.KEYCODE_0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_sendKeyCodePoint(code);
- }
return;
}
- // 16 is android.os.Build.VERSION_CODES.JELLY_BEAN but we can't write it because
- // we want to be able to compile against the Ice Cream Sandwich SDK.
- if (Keyboard.CODE_ENTER == code && mTargetApplicationInfo != null
- && mTargetApplicationInfo.targetSdkVersion < 16) {
+ if (Constants.CODE_ENTER == code && mTargetApplicationInfo != null
+ && mTargetApplicationInfo.targetSdkVersion < VERSION_CODES.JELLY_BEAN) {
// Backward compatibility mode. Before Jelly bean, the keyboard would simulate
// a hardware keyboard event on pressing enter or delete. This is bad for many
// reasons (there are race conditions with commits) but some applications are
@@ -1338,8 +1342,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// Implementation of {@link KeyboardActionListener}.
@Override
public void onCodeInput(final int primaryCode, final int x, final int y) {
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
+ }
final long when = SystemClock.uptimeMillis();
- if (primaryCode != Keyboard.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
+ if (primaryCode != Constants.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
mDeleteCount = 0;
}
mLastKeyTime = when;
@@ -1353,115 +1360,147 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final int spaceState = mSpaceState;
if (!mWordComposer.isComposingWord()) mIsAutoCorrectionIndicatorOn = false;
- // TODO: Consolidate the double space timer, mLastKeyTime, and the space state.
- if (primaryCode != Keyboard.CODE_SPACE) {
- mHandler.cancelDoubleSpacesTimer();
+ // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state.
+ if (primaryCode != Constants.CODE_SPACE) {
+ mHandler.cancelDoubleSpacePeriodTimer();
}
boolean didAutoCorrect = false;
switch (primaryCode) {
- case Keyboard.CODE_DELETE:
+ case Constants.CODE_DELETE:
mSpaceState = SPACE_STATE_NONE;
handleBackspace(spaceState);
mDeleteCount++;
mExpectingUpdateSelection = true;
LatinImeLogger.logOnDelete(x, y);
break;
- case Keyboard.CODE_SHIFT:
- case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
+ case Constants.CODE_SHIFT:
+ case Constants.CODE_SWITCH_ALPHA_SYMBOL:
// Shift and symbol key is handled in onPressKey() and onReleaseKey().
break;
- case Keyboard.CODE_SETTINGS:
+ case Constants.CODE_SETTINGS:
onSettingsKeyPressed();
break;
- case Keyboard.CODE_SHORTCUT:
+ case Constants.CODE_SHORTCUT:
mSubtypeSwitcher.switchToShortcutIME(this);
break;
- case Keyboard.CODE_ACTION_ENTER:
- performEditorAction(getActionId(switcher.getKeyboard()));
- break;
- case Keyboard.CODE_ACTION_NEXT:
+ case Constants.CODE_ACTION_NEXT:
performEditorAction(EditorInfo.IME_ACTION_NEXT);
break;
- case Keyboard.CODE_ACTION_PREVIOUS:
+ case Constants.CODE_ACTION_PREVIOUS:
performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
break;
- case Keyboard.CODE_LANGUAGE_SWITCH:
+ case Constants.CODE_LANGUAGE_SWITCH:
handleLanguageSwitchKey();
break;
- case Keyboard.CODE_RESEARCH:
+ case Constants.CODE_RESEARCH:
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.getInstance().onResearchKeySelected(this);
}
break;
- default:
- mSpaceState = SPACE_STATE_NONE;
- if (mCurrentSettings.isWordSeparator(primaryCode)) {
- didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
+ case Constants.CODE_ENTER:
+ final EditorInfo editorInfo = getCurrentInputEditorInfo();
+ final int imeOptionsActionId =
+ InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo);
+ if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) {
+ // Either we have an actionLabel and we should performEditorAction with actionId
+ // regardless of its value.
+ performEditorAction(editorInfo.actionId);
+ } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) {
+ // We didn't have an actionLabel, but we had another action to execute.
+ // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast,
+ // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it
+ // means there should be an action and the app didn't bother to set a specific
+ // code for it - presumably it only handles one. It does not have to be treated
+ // in any specific way: anything that is not IME_ACTION_NONE should be sent to
+ // performEditorAction.
+ performEditorAction(imeOptionsActionId);
} else {
- if (SPACE_STATE_PHANTOM == spaceState) {
- if (ProductionFlag.IS_INTERNAL) {
- if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
- Stats.onAutoCorrection(
- "", mWordComposer.getTypedWord(), " ", mWordComposer);
- }
- }
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
- }
- final int keyX, keyY;
- final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
- if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) {
- keyX = x;
- keyY = y;
- } else {
- keyX = Constants.NOT_A_COORDINATE;
- keyY = Constants.NOT_A_COORDINATE;
- }
- handleCharacter(primaryCode, keyX, keyY, spaceState);
+ // No action label, and the action from imeOptions is NONE: this is a regular
+ // enter key that should input a carriage return.
+ didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
}
- mExpectingUpdateSelection = true;
+ break;
+ case Constants.CODE_SHIFT_ENTER:
+ didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
+ break;
+ default:
+ didAutoCorrect = handleNonSpecialCharacter(primaryCode, x, y, spaceState);
break;
}
switcher.onCodeInput(primaryCode);
// Reset after any single keystroke, except shift and symbol-shift
- if (!didAutoCorrect && primaryCode != Keyboard.CODE_SHIFT
- && primaryCode != Keyboard.CODE_SWITCH_ALPHA_SYMBOL)
+ if (!didAutoCorrect && primaryCode != Constants.CODE_SHIFT
+ && primaryCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
mLastComposedWord.deactivate();
- if (Keyboard.CODE_DELETE != primaryCode) {
+ if (Constants.CODE_DELETE != primaryCode) {
mEnteredText = null;
}
mConnection.endBatchEdit();
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
+ }
+
+ private boolean handleNonSpecialCharacter(final int primaryCode, final int x, final int y,
+ final int spaceState) {
+ mSpaceState = SPACE_STATE_NONE;
+ final boolean didAutoCorrect;
+ if (mSettings.getCurrent().isWordSeparator(primaryCode)) {
+ didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
+ } else {
+ didAutoCorrect = false;
+ if (SPACE_STATE_PHANTOM == spaceState) {
+ if (ProductionFlag.IS_INTERNAL) {
+ if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
+ Stats.onAutoCorrection(
+ "", mWordComposer.getTypedWord(), " ", mWordComposer);
+ }
+ }
+ commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ }
+ final int keyX, keyY;
+ final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
+ if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) {
+ keyX = x;
+ keyY = y;
+ } else {
+ keyX = Constants.NOT_A_COORDINATE;
+ keyY = Constants.NOT_A_COORDINATE;
+ }
+ handleCharacter(primaryCode, keyX, keyY, spaceState);
}
+ mExpectingUpdateSelection = true;
+ return didAutoCorrect;
}
// Called from PointerTracker through the KeyboardActionListener interface
@Override
- public void onTextInput(final CharSequence rawText) {
+ public void onTextInput(final String rawText) {
mConnection.beginBatchEdit();
if (mWordComposer.isComposingWord()) {
- commitCurrentAutoCorrection(rawText.toString());
+ commitCurrentAutoCorrection(rawText);
} else {
resetComposingState(true /* alsoResetLastComposedWord */);
}
mHandler.postUpdateSuggestionStrip();
- final CharSequence text = specificTldProcessingOnTextInput(rawText);
+ final String text = specificTldProcessingOnTextInput(rawText);
if (SPACE_STATE_PHANTOM == mSpaceState) {
promotePhantomSpace();
}
mConnection.commitText(text, 1);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onTextInput(text, false /* isBatchMode */);
+ }
mConnection.endBatchEdit();
// Space state must be updated before calling updateShiftState
mSpaceState = SPACE_STATE_NONE;
mKeyboardSwitcher.updateShiftState();
- mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
+ mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT);
mEnteredText = text;
}
@Override
public void onStartBatchInput() {
- BatchInputUpdater.getInstance().onStartBatchInput();
+ BatchInputUpdater.getInstance().onStartBatchInput(this);
+ mHandler.cancelUpdateSuggestionStrip();
mConnection.beginBatchEdit();
if (mWordComposer.isComposingWord()) {
if (ProductionFlag.IS_INTERNAL) {
@@ -1469,7 +1508,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
Stats.onAutoCorrection("", mWordComposer.getTypedWord(), " ", mWordComposer);
}
}
- if (mWordComposer.size() <= 1) {
+ final int wordComposerSize = mWordComposer.size();
+ // Since isComposingWord() is true, the size is at least 1.
+ final int lastChar = mWordComposer.getCodeAt(wordComposerSize - 1);
+ if (wordComposerSize <= 1) {
// We auto-correct the previous (typed, not gestured) string iff it's one character
// long. The reason for this is, even in the middle of gesture typing, you'll still
// tap one-letter words and you want them auto-corrected (typically, "i" in English
@@ -1483,16 +1525,17 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
mExpectingUpdateSelection = true;
// The following is necessary for the case where the user typed something but didn't
- // manual pick it and didn't input any separator.
- mSpaceState = SPACE_STATE_PHANTOM;
+ // manual pick it and didn't input any separator: we want to put a space between what
+ // has been entered and the coming gesture input result, so we go into phantom space
+ // state, which will be promoted to a space when the gesture result is committed. But if
+ // the current input ends in a word connector on the other hand, then we want to have
+ // the next input stick to the current input so we don't switch to phantom space state.
+ if (!mSettings.getCurrent().isWordConnector(lastChar)) {
+ mSpaceState = SPACE_STATE_PHANTOM;
+ }
} else {
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
- // TODO: reverse this logic. We should have the means to determine whether a character
- // should usually be followed by a space, and it should be more readable.
- if (Constants.NOT_A_CODE != codePointBeforeCursor
- && !Character.isWhitespace(codePointBeforeCursor)
- && !mCurrentSettings.isPhantomSpacePromotingSymbol(codePointBeforeCursor)
- && !mCurrentSettings.isWeakSpaceStripper(codePointBeforeCursor)) {
+ if (mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
mSpaceState = SPACE_STATE_PHANTOM;
}
}
@@ -1503,7 +1546,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private static final class BatchInputUpdater implements Handler.Callback {
private final Handler mHandler;
private LatinIME mLatinIme;
- private boolean mInBatchInput; // synchornized using "this".
+ private boolean mInBatchInput; // synchronized using "this".
private BatchInputUpdater() {
final HandlerThread handlerThread = new HandlerThread(
@@ -1527,33 +1570,32 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
public boolean handleMessage(final Message msg) {
switch (msg.what) {
case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
- updateBatchInput((InputPointers)msg.obj, mLatinIme);
+ updateBatchInput((InputPointers)msg.obj);
break;
}
return true;
}
// Run in the UI thread.
- public synchronized void onStartBatchInput() {
+ public synchronized void onStartBatchInput(final LatinIME latinIme) {
+ mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
+ mLatinIme = latinIme;
mInBatchInput = true;
}
// Run in the Handler thread.
- private synchronized void updateBatchInput(final InputPointers batchPointers,
- final LatinIME latinIme) {
+ private synchronized void updateBatchInput(final InputPointers batchPointers) {
if (!mInBatchInput) {
- // Batch input has ended while the message was being delivered.
+ // Batch input has ended or canceled while the message was being delivered.
return;
}
- final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(
- batchPointers, latinIme);
- latinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+ final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
+ mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
suggestedWords, false /* dismissGestureFloatingPreviewText */);
}
// Run in the UI thread.
- public void onUpdateBatchInput(final InputPointers batchPointers, final LatinIME latinIme) {
- mLatinIme = latinIme;
+ public void onUpdateBatchInput(final InputPointers batchPointers) {
if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) {
return;
}
@@ -1562,33 +1604,42 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
.sendToTarget();
}
+ public synchronized void onCancelBatchInput() {
+ mInBatchInput = false;
+ mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+ SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
+ }
+
// Run in the UI thread.
- public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers,
- final LatinIME latinIme) {
+ public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers) {
mInBatchInput = false;
- final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(
- batchPointers, latinIme);
- latinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+ final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
+ mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
suggestedWords, true /* dismissGestureFloatingPreviewText */);
return suggestedWords;
}
// {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to
// be synchronized.
- private static SuggestedWords getSuggestedWordsGestureLocked(
- final InputPointers batchPointers, final LatinIME latinIme) {
- latinIme.mWordComposer.setBatchInputPointers(batchPointers);
- return latinIme.getSuggestedWords(Suggest.SESSION_GESTURE);
+ private SuggestedWords getSuggestedWordsGestureLocked(final InputPointers batchPointers) {
+ mLatinIme.mWordComposer.setBatchInputPointers(batchPointers);
+ final SuggestedWords suggestedWords =
+ mLatinIme.getSuggestedWords(Suggest.SESSION_GESTURE);
+ final int suggestionCount = suggestedWords.size();
+ if (suggestionCount <= 1) {
+ final String mostProbableSuggestion = (suggestionCount == 0) ? null
+ : suggestedWords.getWord(0);
+ return mLatinIme.getOlderSuggestions(mostProbableSuggestion);
+ }
+ return suggestedWords;
}
}
private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
final boolean dismissGestureFloatingPreviewText) {
- final String batchInputText = (suggestedWords.size() > 0)
- ? suggestedWords.getWord(0) : null;
- final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
- mainKeyboardView.showGestureFloatingPreviewText(batchInputText);
showSuggestionStrip(suggestedWords, null);
+ final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+ mainKeyboardView.showGestureFloatingPreviewText(suggestedWords);
if (dismissGestureFloatingPreviewText) {
mainKeyboardView.dismissGestureFloatingPreviewText();
}
@@ -1596,15 +1647,15 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
@Override
public void onUpdateBatchInput(final InputPointers batchPointers) {
- BatchInputUpdater.getInstance().onUpdateBatchInput(batchPointers, this);
+ BatchInputUpdater.getInstance().onUpdateBatchInput(batchPointers);
}
@Override
public void onEndBatchInput(final InputPointers batchPointers) {
final SuggestedWords suggestedWords = BatchInputUpdater.getInstance().onEndBatchInput(
- batchPointers, this);
- final String batchInputText = (suggestedWords.size() > 0)
- ? suggestedWords.getWord(0) : null;
+ batchPointers);
+ final String batchInputText = suggestedWords.isEmpty()
+ ? null : suggestedWords.getWord(0);
if (TextUtils.isEmpty(batchInputText)) {
return;
}
@@ -1616,13 +1667,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mConnection.setComposingText(batchInputText, 1);
mExpectingUpdateSelection = true;
mConnection.endBatchEdit();
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords);
+ }
// Space state must be updated before calling updateShiftState
mSpaceState = SPACE_STATE_PHANTOM;
mKeyboardSwitcher.updateShiftState();
}
- private CharSequence specificTldProcessingOnTextInput(final CharSequence text) {
- if (text.length() <= 1 || text.charAt(0) != Keyboard.CODE_PERIOD
+ private String specificTldProcessingOnTextInput(final String text) {
+ if (text.length() <= 1 || text.charAt(0) != Constants.CODE_PERIOD
|| !Character.isLetter(text.charAt(1))) {
// Not a tld: do nothing.
return text;
@@ -1633,8 +1687,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// TODO: use getCodePointBeforeCursor instead to improve performance and simplify the code
final CharSequence lastOne = mConnection.getTextBeforeCursor(1, 0);
if (lastOne != null && lastOne.length() == 1
- && lastOne.charAt(0) == Keyboard.CODE_PERIOD) {
- return text.subSequence(1, text.length());
+ && lastOne.charAt(0) == Constants.CODE_PERIOD) {
+ return text.substring(1);
} else {
return text;
}
@@ -1647,6 +1701,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mKeyboardSwitcher.onCancelInput();
}
+ @Override
+ public void onCancelBatchInput() {
+ BatchInputUpdater.getInstance().onCancelBatchInput();
+ }
+
private void handleBackspace(final int spaceState) {
// In many cases, we may have to put the keyboard in auto-shift state again. However
// we want to wait a few milliseconds before doing it to avoid the keyboard flashing
@@ -1656,8 +1715,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
if (mWordComposer.isComposingWord()) {
final int length = mWordComposer.size();
if (length > 0) {
- // Immediately after a batch input.
- if (SPACE_STATE_PHANTOM == spaceState) {
+ if (mWordComposer.isBatchMode()) {
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ final String word = mWordComposer.getTypedWord();
+ ResearchLogger.latinIME_handleBackspace_batch(word, 1);
+ ResearchLogger.getInstance().uncommitCurrentLogUnit(
+ word, false /* dumpCurrentLogUnit */);
+ }
mWordComposer.reset();
} else {
mWordComposer.deleteLast();
@@ -1688,8 +1752,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
return;
}
if (SPACE_STATE_DOUBLE == spaceState) {
- mHandler.cancelDoubleSpacesTimer();
- if (mConnection.revertDoubleSpace()) {
+ mHandler.cancelDoubleSpacePeriodTimer();
+ if (mConnection.revertDoubleSpacePeriod()) {
// No need to reset mSpaceState, it has already be done (that's why we
// receive it as a parameter)
return;
@@ -1705,19 +1769,25 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// We should backspace one char and restart suggestion if at the end of a word.
if (mLastSelectionStart != mLastSelectionEnd) {
// If there is a selection, remove it.
- final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
+ final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
- mConnection.deleteSurroundingText(lengthToDelete, 0);
+ // Reset mLastSelectionEnd to mLastSelectionStart. This is what is supposed to
+ // happen, and if it's wrong, the next call to onUpdateSelection will correct it,
+ // but we want to set it right away to avoid it being used with the wrong values
+ // later (typically, in a subsequent press on backspace).
+ mLastSelectionEnd = mLastSelectionStart;
+ mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_handleBackspace(numCharsDeleted);
+ }
} else {
// There is no selection, just delete one character.
if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
// This should never happen.
Log.e(TAG, "Backspace when we don't know the selection position");
}
- // 16 is android.os.Build.VERSION_CODES.JELLY_BEAN but we can't write it because
- // we want to be able to compile against the Ice Cream Sandwich SDK.
if (mTargetApplicationInfo != null
- && mTargetApplicationInfo.targetSdkVersion < 16) {
+ && mTargetApplicationInfo.targetSdkVersion < VERSION_CODES.JELLY_BEAN) {
// Backward compatibility mode. Before Jelly bean, the keyboard would simulate
// a hardware keyboard event on pressing enter or delete. This is bad for many
// reasons (there are race conditions with commits) but some applications are
@@ -1726,35 +1796,38 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
} else {
mConnection.deleteSurroundingText(1, 0);
}
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_handleBackspace(1);
+ }
if (mDeleteCount > DELETE_ACCELERATE_AT) {
mConnection.deleteSurroundingText(1, 0);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_handleBackspace(1);
+ }
}
}
- if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
+ if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
restartSuggestionsOnWordBeforeCursorIfAtEndOfWord();
}
}
}
+ /*
+ * Strip a trailing space if necessary and returns whether it's a swap weak space situation.
+ */
private boolean maybeStripSpace(final int code,
final int spaceState, final boolean isFromSuggestionStrip) {
- if (Keyboard.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
+ if (Constants.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
mConnection.removeTrailingSpace();
return false;
- } else if ((SPACE_STATE_WEAK == spaceState
- || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
+ }
+ if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
&& isFromSuggestionStrip) {
- if (mCurrentSettings.isWeakSpaceSwapper(code)) {
- return true;
- } else {
- if (mCurrentSettings.isWeakSpaceStripper(code)) {
- mConnection.removeTrailingSpace();
- }
- return false;
- }
- } else {
- return false;
+ if (mSettings.getCurrent().isUsuallyPrecededBySpace(code)) return false;
+ if (mSettings.getCurrent().isUsuallyFollowedBySpace(code)) return true;
+ mConnection.removeTrailingSpace();
}
+ return false;
}
private void handleCharacter(final int primaryCode, final int x,
@@ -1762,7 +1835,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
boolean isComposingWord = mWordComposer.isComposingWord();
if (SPACE_STATE_PHANTOM == spaceState &&
- !mCurrentSettings.isSymbolExcludedFromWordSeparators(primaryCode)) {
+ !mSettings.getCurrent().isWordConnector(primaryCode)) {
if (isComposingWord) {
// Sanity check
throw new RuntimeException("Should not be composing here");
@@ -1774,14 +1847,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
// thread here.
if (!isComposingWord && (isAlphabet(primaryCode)
- || mCurrentSettings.isSymbolExcludedFromWordSeparators(primaryCode))
- && mCurrentSettings.isSuggestionsRequested(mDisplayOrientation) &&
- !mConnection.isCursorTouchingWord(mCurrentSettings)) {
+ || mSettings.getCurrent().isWordConnector(primaryCode))
+ && mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation) &&
+ !mConnection.isCursorTouchingWord(mSettings.getCurrent())) {
// Reset entirely the composing state anyway, then start composing a new word unless
// the character is a single quote. The idea here is, single quote is not a
// separator and it should be treated as a normal character, except in the first
// position where it should not start composing a word.
- isComposingWord = (Keyboard.CODE_SINGLE_QUOTE != primaryCode);
+ isComposingWord = (Constants.CODE_SINGLE_QUOTE != primaryCode);
// Here we don't need to reset the last composed word. It will be reset
// when we commit this one, if we ever do; if on the other hand we backspace
// it entirely and resume suggestions on the previous word, we'd like to still
@@ -1790,15 +1863,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
if (isComposingWord) {
final int keyX, keyY;
- if (KeyboardActionListener.Adapter.isInvalidCoordinate(x)
- || KeyboardActionListener.Adapter.isInvalidCoordinate(y)) {
- keyX = x;
- keyY = y;
- } else {
+ if (Constants.isValidCoordinate(x) && Constants.isValidCoordinate(y)) {
final KeyDetector keyDetector =
mKeyboardSwitcher.getMainKeyboardView().getKeyDetector();
keyX = keyDetector.getTouchX(x);
keyY = keyDetector.getTouchY(y);
+ } else {
+ keyX = x;
+ keyY = y;
}
mWordComposer.add(primaryCode, keyX, keyY);
// If it's the first letter, make note of auto-caps state
@@ -1828,10 +1900,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// Returns true if we did an autocorrection, false otherwise.
private boolean handleSeparator(final int primaryCode, final int x, final int y,
final int spaceState) {
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.recordTimeForLogUnitSplit();
+ ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
+ }
boolean didAutoCorrect = false;
// Handle separator
if (mWordComposer.isComposingWord()) {
- if (mCurrentSettings.mCorrectionEnabled) {
+ if (mSettings.getCurrent().mCorrectionEnabled) {
// TODO: maybe cache Strings in an <String> sparse array or something
commitCurrentAutoCorrection(new String(new int[]{primaryCode}, 0, 1));
didAutoCorrect = true;
@@ -1844,31 +1920,28 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
Constants.SUGGESTION_STRIP_COORDINATE == x);
if (SPACE_STATE_PHANTOM == spaceState &&
- mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
+ mSettings.getCurrent().isUsuallyPrecededBySpace(primaryCode)) {
promotePhantomSpace();
}
sendKeyCodePoint(primaryCode);
- if (Keyboard.CODE_SPACE == primaryCode) {
- if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
- if (maybeDoubleSpace()) {
+ if (Constants.CODE_SPACE == primaryCode) {
+ if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
+ if (maybeDoubleSpacePeriod()) {
mSpaceState = SPACE_STATE_DOUBLE;
} else if (!isShowingPunctuationList()) {
mSpaceState = SPACE_STATE_WEAK;
}
}
- mHandler.startDoubleSpacesTimer();
- if (!mConnection.isCursorTouchingWord(mCurrentSettings)) {
- mHandler.postUpdateSuggestionStrip();
- }
+ mHandler.startDoubleSpacePeriodTimer();
+ mHandler.postUpdateSuggestionStrip();
} else {
if (swapWeakSpace) {
swapSwapperAndSpace();
mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
} else if (SPACE_STATE_PHANTOM == spaceState
- && !mCurrentSettings.isWeakSpaceStripper(primaryCode)
- && !mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
+ && mSettings.getCurrent().isUsuallyFollowedBySpace(primaryCode)) {
// If we are in phantom space state, and the user presses a separator, we want to
// stay in phantom space state so that the next keypress has a chance to add the
// space. For example, if I type "Good dat", pick "day" from the suggestion strip
@@ -1894,13 +1967,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
return didAutoCorrect;
}
- private CharSequence getTextWithUnderline(final CharSequence text) {
+ private CharSequence getTextWithUnderline(final String text) {
return mIsAutoCorrectionIndicatorOn
? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text)
: text;
}
private void handleClose() {
+ // TODO: Verify that words are logged properly when IME is closed.
commitTyped(LastComposedWord.NOT_A_SEPARATOR);
requestHideSelf(0);
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
@@ -1911,10 +1985,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// TODO: make this private
// Outside LatinIME, only used by the test suite.
- /* package for tests */
+ @UsedForTesting
boolean isShowingPunctuationList() {
- if (mSuggestionStripView == null) return false;
- return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions();
+ if (mSuggestedWords == null) return false;
+ return mSettings.getCurrent().mSuggestPuncList == mSuggestedWords;
}
private boolean isSuggestionsStripVisible() {
@@ -1922,19 +1996,20 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
return false;
if (mSuggestionStripView.isShowingAddToDictionaryHint())
return true;
- if (!mCurrentSettings.isSuggestionStripVisibleInOrientation(mDisplayOrientation))
+ if (!mSettings.getCurrent().isSuggestionStripVisibleInOrientation(mDisplayOrientation))
return false;
- if (mCurrentSettings.isApplicationSpecifiedCompletionsOn())
+ if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn())
return true;
- return mCurrentSettings.isSuggestionsRequested(mDisplayOrientation);
+ return mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation);
}
private void clearSuggestionStrip() {
- setSuggestionStrip(SuggestedWords.EMPTY, false);
+ setSuggestedWords(SuggestedWords.EMPTY, false);
setAutoCorrectionIndicator(false);
}
- private void setSuggestionStrip(final SuggestedWords words, final boolean isAutoCorrection) {
+ private void setSuggestedWords(final SuggestedWords words, final boolean isAutoCorrection) {
+ mSuggestedWords = words;
if (mSuggestionStripView != null) {
mSuggestionStripView.setSuggestions(words);
mKeyboardSwitcher.onAutoCorrectionStateChanged(isAutoCorrection);
@@ -1960,16 +2035,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mHandler.cancelUpdateSuggestionStrip();
// Check if we have a suggestion engine attached.
- if (mSuggest == null || !mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
+ if (mSuggest == null
+ || !mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
if (mWordComposer.isComposingWord()) {
Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
+ "requested!");
- mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
}
return;
}
- if (!mWordComposer.isComposingWord() && !mCurrentSettings.mBigramPredictionEnabled) {
+ if (!mWordComposer.isComposingWord() && !mSettings.getCurrent().mBigramPredictionEnabled) {
setPunctuationSuggestions();
return;
}
@@ -1981,7 +2056,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private SuggestedWords getSuggestedWords(final int sessionId) {
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
- if (keyboard == null) {
+ if (keyboard == null || mSuggest == null) {
return SuggestedWords.EMPTY;
}
final String typedWord = mWordComposer.getTypedWord();
@@ -1989,16 +2064,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we
// should just skip whitespace if any, so 1.
// TODO: this is slow (2-way IPC) - we should probably cache this instead.
- final CharSequence prevWord =
- mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators,
+ final String prevWord =
+ mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators,
mWordComposer.isComposingWord() ? 2 : 1);
final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer,
- prevWord, keyboard.getProximityInfo(), mCurrentSettings.mCorrectionEnabled,
+ prevWord, keyboard.getProximityInfo(), mSettings.getCurrent().mCorrectionEnabled,
sessionId);
return maybeRetrieveOlderSuggestions(typedWord, suggestedWords);
}
- private SuggestedWords maybeRetrieveOlderSuggestions(final CharSequence typedWord,
+ private SuggestedWords maybeRetrieveOlderSuggestions(final String typedWord,
final SuggestedWords suggestedWords) {
// TODO: consolidate this into getSuggestedWords
// We update the suggestion strip only when we have some suggestions to show, i.e. when
@@ -2008,45 +2083,47 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// old suggestions. Also, if we are showing the "add to dictionary" hint, we need to
// revert to suggestions - although it is unclear how we can come here if it's displayed.
if (suggestedWords.size() > 1 || typedWord.length() <= 1
- || !suggestedWords.mTypedWordValid
+ || suggestedWords.mTypedWordValid
|| mSuggestionStripView.isShowingAddToDictionaryHint()) {
return suggestedWords;
} else {
- SuggestedWords previousSuggestions = mSuggestionStripView.getSuggestions();
- if (previousSuggestions == mCurrentSettings.mSuggestPuncList) {
- previousSuggestions = SuggestedWords.EMPTY;
- }
- final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
- SuggestedWords.getTypedWordAndPreviousSuggestions(
- typedWord, previousSuggestions);
- return new SuggestedWords(typedWordAndPreviousSuggestions,
- false /* typedWordValid */,
- false /* hasAutoCorrectionCandidate */,
- false /* isPunctuationSuggestions */,
- true /* isObsoleteSuggestions */,
- false /* isPrediction */);
+ return getOlderSuggestions(typedWord);
}
}
- private void showSuggestionStrip(final SuggestedWords suggestedWords,
- final CharSequence typedWord) {
- if (null == suggestedWords || suggestedWords.size() <= 0) {
+ private SuggestedWords getOlderSuggestions(final String typedWord) {
+ SuggestedWords previousSuggestedWords = mSuggestedWords;
+ if (previousSuggestedWords == mSettings.getCurrent().mSuggestPuncList) {
+ previousSuggestedWords = SuggestedWords.EMPTY;
+ }
+ if (typedWord == null) {
+ return previousSuggestedWords;
+ }
+ final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
+ SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord,
+ previousSuggestedWords);
+ return new SuggestedWords(typedWordAndPreviousSuggestions,
+ false /* typedWordValid */,
+ false /* hasAutoCorrectionCandidate */,
+ false /* isPunctuationSuggestions */,
+ true /* isObsoleteSuggestions */,
+ false /* isPrediction */);
+ }
+
+ private void showSuggestionStrip(final SuggestedWords suggestedWords, final String typedWord) {
+ if (suggestedWords.isEmpty()) {
clearSuggestionStrip();
return;
}
- final CharSequence autoCorrection;
- if (suggestedWords.size() > 0) {
- if (suggestedWords.mWillAutoCorrect) {
- autoCorrection = suggestedWords.getWord(1);
- } else {
- autoCorrection = typedWord;
- }
+ final String autoCorrection;
+ if (suggestedWords.mWillAutoCorrect) {
+ autoCorrection = suggestedWords.getWord(1);
} else {
- autoCorrection = null;
+ autoCorrection = typedWord;
}
mWordComposer.setAutoCorrection(autoCorrection);
final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
- setSuggestionStrip(suggestedWords, isAutoCorrection);
+ setSuggestedWords(suggestedWords, isAutoCorrection);
setAutoCorrectionIndicator(isAutoCorrection);
setSuggestionStripShown(isSuggestionsStripVisible());
}
@@ -2056,9 +2133,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
if (mHandler.hasPendingUpdateSuggestions()) {
updateSuggestionStrip();
}
- final CharSequence typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
+ final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
final String typedWord = mWordComposer.getTypedWord();
- final CharSequence autoCorrection = (typedAutoCorrection != null)
+ final String autoCorrection = (typedAutoCorrection != null)
? typedAutoCorrection : typedWord;
if (autoCorrection != null) {
if (TextUtils.isEmpty(typedWord)) {
@@ -2066,8 +2143,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
+ "is empty? Impossible! I must commit suicide.");
}
if (ProductionFlag.IS_INTERNAL) {
- Stats.onAutoCorrection(
- typedWord, autoCorrection.toString(), separatorString, mWordComposer);
+ Stats.onAutoCorrection(typedWord, autoCorrection, separatorString, mWordComposer);
+ }
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ final SuggestedWords suggestedWords = mSuggestedWords;
+ ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
+ separatorString, mWordComposer.isBatchMode(), suggestedWords);
}
mExpectingUpdateSelection = true;
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
@@ -2089,19 +2170,20 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
// interface
@Override
- public void pickSuggestionManually(final int index, final CharSequence suggestion) {
- final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
+ public void pickSuggestionManually(final int index, final String suggestion) {
+ final SuggestedWords suggestedWords = mSuggestedWords;
// If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
if (suggestion.length() == 1 && isShowingPunctuationList()) {
// Word separators are suggested before the user inputs something.
// So, LatinImeLogger logs "" as a user's input.
- LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
+ LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
final int primaryCode = suggestion.charAt(0);
onCodeInput(primaryCode,
Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_punctuationSuggestion(index, suggestion);
+ ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
+ false /* isBatchMode */, suggestedWords.mIsPrediction);
}
return;
}
@@ -2111,16 +2193,17 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// In the batch input mode, a manually picked suggested word should just replace
// the current batch input text and there is no need for a phantom space.
&& !mWordComposer.isBatchMode()) {
- int firstChar = Character.codePointAt(suggestion, 0);
- if ((!mCurrentSettings.isWeakSpaceStripper(firstChar))
- && (!mCurrentSettings.isWeakSpaceSwapper(firstChar))) {
+ final int firstChar = Character.codePointAt(suggestion, 0);
+ if (!mSettings.getCurrent().isWordSeparator(firstChar)
+ || mSettings.getCurrent().isUsuallyPrecededBySpace(firstChar)) {
promotePhantomSpace();
}
}
- if (mCurrentSettings.isApplicationSpecifiedCompletionsOn()
+ if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()
&& mApplicationSpecifiedCompletions != null
&& index >= 0 && index < mApplicationSpecifiedCompletions.length) {
+ mSuggestedWords = SuggestedWords.EMPTY;
if (mSuggestionStripView != null) {
mSuggestionStripView.clear();
}
@@ -2134,14 +2217,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// We need to log before we commit, because the word composer will store away the user
// typed word.
- final String replacedWord = mWordComposer.getTypedWord().toString();
- LatinImeLogger.logOnManualSuggestion(replacedWord,
- suggestion.toString(), index, suggestedWords);
+ final String replacedWord = mWordComposer.getTypedWord();
+ LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
mExpectingUpdateSelection = true;
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
LastComposedWord.NOT_A_SEPARATOR);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion);
+ ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
+ mWordComposer.isBatchMode());
}
mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
@@ -2159,12 +2242,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
&& !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
if (ProductionFlag.IS_INTERNAL) {
- Stats.onSeparator((char)Keyboard.CODE_SPACE,
+ Stats.onSeparator((char)Constants.CODE_SPACE,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
mSuggestionStripView.showAddToDictionaryHint(
- suggestion, mCurrentSettings.mHintToSaveText);
+ suggestion, mSettings.getCurrent().mHintToSaveText);
} else {
// If we're not showing the "Touch again to save", then update the suggestion strip.
mHandler.postUpdateSuggestionStrip();
@@ -2174,58 +2257,56 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
/**
* Commits the chosen word to the text field and saves it for later retrieval.
*/
- private void commitChosenWord(final CharSequence chosenWord, final int commitType,
+ private void commitChosenWord(final String chosenWord, final int commitType,
final String separatorString) {
- final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
+ final SuggestedWords suggestedWords = mSuggestedWords;
mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
// Add the word to the user history dictionary
- final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
+ final String prevWord = addToUserHistoryDictionary(chosenWord);
// TODO: figure out here if this is an auto-correct or if the best word is actually
// what user typed. Note: currently this is done much later in
// LastComposedWord#didCommitTypedWord by string equality of the remembered
// strings.
- mLastComposedWord = mWordComposer.commitWord(commitType, chosenWord.toString(),
- separatorString, prevWord);
+ mLastComposedWord = mWordComposer.commitWord(commitType, chosenWord, separatorString,
+ prevWord);
}
private void setPunctuationSuggestions() {
- if (mCurrentSettings.mBigramPredictionEnabled) {
+ if (mSettings.getCurrent().mBigramPredictionEnabled) {
clearSuggestionStrip();
} else {
- setSuggestionStrip(mCurrentSettings.mSuggestPuncList, false);
+ setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false);
}
setAutoCorrectionIndicator(false);
setSuggestionStripShown(isSuggestionsStripVisible());
}
- private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) {
+ private String addToUserHistoryDictionary(final String suggestion) {
if (TextUtils.isEmpty(suggestion)) return null;
if (mSuggest == null) return null;
// If correction is not enabled, we don't add words to the user history dictionary.
// That's to avoid unintended additions in some sensitive fields, or fields that
// expect to receive non-words.
- if (!mCurrentSettings.mCorrectionEnabled) return null;
+ if (!mSettings.getCurrent().mCorrectionEnabled) return null;
final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
if (userHistoryDictionary != null) {
- final CharSequence prevWord
- = mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2);
+ final String prevWord
+ = mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators, 2);
final String secondWord;
if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
- secondWord = suggestion.toString().toLowerCase(
- mSubtypeSwitcher.getCurrentSubtypeLocale());
+ secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
} else {
- secondWord = suggestion.toString();
+ secondWord = suggestion;
}
// We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
// We don't add words with 0-frequency (assuming they would be profanity etc.).
final int maxFreq = AutoCorrection.getMaxFrequency(
mSuggest.getUnigramDictionaries(), suggestion);
if (maxFreq == 0) return null;
- userHistoryDictionary.addToUserHistory(null == prevWord ? null : prevWord.toString(),
- secondWord, maxFreq > 0);
+ userHistoryDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0);
return prevWord;
}
return null;
@@ -2236,9 +2317,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
* word, else do nothing.
*/
private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() {
- final CharSequence word = mConnection.getWordBeforeCursorIfAtEndOfWord(mCurrentSettings);
+ final CharSequence word =
+ mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent());
if (null != word) {
restartSuggestionsOnWordBeforeCursor(word);
+ // TODO: Handle the case where the user manually moves the cursor and then backs up over
+ // a separator. In that case, the current log unit should not be uncommitted.
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.getInstance().uncommitCurrentLogUnit(word.toString(),
+ true /* dumpCurrentLogUnit */);
+ }
}
}
@@ -2251,9 +2339,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
private void revertCommit() {
- final CharSequence previousWord = mLastComposedWord.mPrevWord;
+ final String previousWord = mLastComposedWord.mPrevWord;
final String originallyTypedWord = mLastComposedWord.mTypedWord;
- final CharSequence committedWord = mLastComposedWord.mCommittedWord;
+ final String committedWord = mLastComposedWord.mCommittedWord;
final int cancelLength = committedWord.length();
final int separatorLength = LastComposedWord.getSeparatorLength(
mLastComposedWord.mSeparatorString);
@@ -2263,9 +2351,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
if (mWordComposer.isComposingWord()) {
throw new RuntimeException("revertCommit, but we are composing a word");
}
- final String wordBeforeCursor =
+ final CharSequence wordBeforeCursor =
mConnection.getTextBeforeCursor(deleteLength, 0)
- .subSequence(0, cancelLength).toString();
+ .subSequence(0, cancelLength);
if (!TextUtils.equals(committedWord, wordBeforeCursor)) {
throw new RuntimeException("revertCommit check failed: we thought we were "
+ "reverting \"" + committedWord
@@ -2274,8 +2362,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
mConnection.deleteSurroundingText(deleteLength, 0);
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
- mUserHistoryDictionary.cancelAddingUserHistory(
- previousWord.toString(), committedWord.toString());
+ mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
}
mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
if (ProductionFlag.IS_INTERNAL) {
@@ -2283,7 +2370,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_revertCommit(originallyTypedWord);
+ ResearchLogger.latinIME_revertCommit(committedWord, originallyTypedWord,
+ mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString);
+ ResearchLogger.getInstance().uncommitCurrentLogUnit(committedWord,
+ true /* dumpCurrentLogUnit */);
}
// Don't restart suggestion yet. We'll restart if the user deletes the
// separator.
@@ -2294,19 +2384,22 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// This essentially inserts a space, and that's it.
public void promotePhantomSpace() {
- if (mCurrentSettings.shouldInsertSpacesAutomatically()) {
- sendKeyCodePoint(Keyboard.CODE_SPACE);
+ if (mSettings.getCurrent().shouldInsertSpacesAutomatically()) {
+ sendKeyCodePoint(Constants.CODE_SPACE);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_promotePhantomSpace();
+ }
}
}
// Used by the RingCharBuffer
public boolean isWordSeparator(final int code) {
- return mCurrentSettings.isWordSeparator(code);
+ return mSettings.getCurrent().isWordSeparator(code);
}
// TODO: Make this private
// Outside LatinIME, only used by the {@link InputTestsBase} test suite.
- /* package for test */
+ @UsedForTesting
void loadKeyboard() {
// When the device locale is changed in SetupWizard etc., this method may get called via
// onConfigurationChanged before SoftInputWindow is shown.
@@ -2314,7 +2407,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
loadSettings();
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
// Reload keyboard because the current language has been changed.
- mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mCurrentSettings);
+ mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent());
}
// Since we just changed languages, we should re-evaluate suggestions with whatever word
// we are currently composing. If we are not composing anything, we may want to display
@@ -2322,13 +2415,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mHandler.postUpdateSuggestionStrip();
}
- // TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to
- // {@link KeyboardSwitcher}. Called from KeyboardSwitcher
- public void hapticAndAudioFeedback(final int primaryCode) {
- mFeedbackManager.hapticAndAudioFeedback(
- primaryCode, mKeyboardSwitcher.getMainKeyboardView());
- }
-
// Callback called by PointerTracker through the KeyboardActionListener. This is called when a
// key is depressed; release matching call is onReleaseKey below.
@Override
@@ -2345,16 +2431,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// If accessibility is on, ensure the user receives keyboard state updates.
if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
switch (primaryCode) {
- case Keyboard.CODE_SHIFT:
+ case Constants.CODE_SHIFT:
AccessibleKeyboardViewProxy.getInstance().notifyShiftState();
break;
- case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
+ case Constants.CODE_SWITCH_ALPHA_SYMBOL:
AccessibleKeyboardViewProxy.getInstance().notifySymbolsState();
break;
}
}
- if (Keyboard.CODE_DELETE == primaryCode) {
+ if (Constants.CODE_DELETE == primaryCode) {
// This is a stopgap solution to avoid leaving a high surrogate alone in a text view.
// In the future, we need to deprecate deteleSurroundingText() and have a surrogate
// pair-friendly way of deleting characters in InputConnection.
@@ -2366,6 +2452,35 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
}
+ // Hooks for hardware keyboard
+ @Override
+ public boolean onKeyDown(final int keyCode, final KeyEvent event) {
+ if (!ProductionFlag.IS_HARDWARE_KEYBOARD_SUPPORTED) return super.onKeyDown(keyCode, event);
+ // onHardwareKeyEvent, like onKeyDown returns true if it handled the event, false if
+ // it doesn't know what to do with it and leave it to the application. For example,
+ // hardware key events for adjusting the screen's brightness are passed as is.
+ if (mEventInterpreter.onHardwareKeyEvent(event)) {
+ final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode();
+ mCurrentlyPressedHardwareKeys.add(keyIdentifier);
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(final int keyCode, final KeyEvent event) {
+ final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode();
+ if (mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) {
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ // onKeyDown and onKeyUp are the main events we are interested in. There are two more events
+ // related to handling of hardware key events that we may want to implement in the future:
+ // boolean onKeyLongPress(final int keyCode, final KeyEvent event);
+ // boolean onKeyMultiple(final int keyCode, final int count, final KeyEvent event);
+
// receive ringer mode change and network state change.
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -2374,7 +2489,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
mSubtypeSwitcher.onNetworkStateChanged(intent);
} else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
- mFeedbackManager.onRingerModeChanged();
+ mKeyboardSwitcher.onRingerModeChanged();
}
}
};
@@ -2400,7 +2515,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private void launchSubActivity(final Class<? extends Activity> activityClass) {
Intent intent = new Intent();
intent.setClass(LatinIME.this, activityClass);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
@@ -2411,15 +2528,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
getString(R.string.language_selection_title),
getString(R.string.english_ime_settings),
};
- final Context context = this;
final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface di, int position) {
di.dismiss();
switch (position) {
case 0:
- Intent intent = CompatUtils.getInputLanguageSelectionIntent(
- ImfUtils.getInputMethodIdOfThisIme(context),
+ final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
+ mRichImm.getInputMethodIdOfThisIme(),
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -2457,13 +2573,19 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
dialog.show();
}
+ // TODO: can this be removed somehow without breaking the tests?
+ @UsedForTesting
+ /* package for test */ String getFirstSuggestedWord() {
+ return mSuggestedWords.size() > 0 ? mSuggestedWords.getWord(0) : null;
+ }
+
public void debugDumpStateAndCrashWithException(final String context) {
final StringBuilder s = new StringBuilder();
s.append("Target application : ").append(mTargetApplicationInfo.name)
.append("\nPackage : ").append(mTargetApplicationInfo.packageName)
.append("\nTarget app sdk version : ")
.append(mTargetApplicationInfo.targetSdkVersion)
- .append("\nAttributes : ").append(mCurrentSettings.getInputAttributesDebugString())
+ .append("\nAttributes : ").append(mSettings.getCurrent().mInputAttributes)
.append("\nContext : ").append(context);
throw new RuntimeException(s.toString());
}
@@ -2477,13 +2599,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
p.println(" Keyboard mode = " + keyboardMode);
+ final SettingsValues settingsValues = mSettings.getCurrent();
p.println(" mIsSuggestionsSuggestionsRequested = "
- + mCurrentSettings.isSuggestionsRequested(mDisplayOrientation));
- p.println(" mCorrectionEnabled=" + mCurrentSettings.mCorrectionEnabled);
+ + settingsValues.isSuggestionsRequested(mDisplayOrientation));
+ p.println(" mCorrectionEnabled=" + settingsValues.mCorrectionEnabled);
p.println(" isComposingWord=" + mWordComposer.isComposingWord());
- p.println(" mSoundOn=" + mCurrentSettings.mSoundOn);
- p.println(" mVibrateOn=" + mCurrentSettings.mVibrateOn);
- p.println(" mKeyPreviewPopupOn=" + mCurrentSettings.mKeyPreviewPopupOn);
- p.println(" inputAttributes=" + mCurrentSettings.getInputAttributesDebugString());
+ p.println(" mSoundOn=" + settingsValues.mSoundOn);
+ p.println(" mVibrateOn=" + settingsValues.mVibrateOn);
+ p.println(" mKeyPreviewPopupOn=" + settingsValues.mKeyPreviewPopupOn);
+ p.println(" inputAttributes=" + settingsValues.mInputAttributes);
}
}
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index 394a9c7aa..e4e8b94b2 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -31,7 +31,7 @@ public final class LatinImeLogger implements SharedPreferences.OnSharedPreferenc
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
}
- public static void init(LatinIME context, SharedPreferences prefs) {
+ public static void init(LatinIME context) {
}
public static void commit() {
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java
index feb1b2d0e..5fde8158a 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -180,14 +180,15 @@ public final class LocaleUtils {
synchronized (sLockForRunInLocale) {
final Configuration conf = res.getConfiguration();
final Locale oldLocale = conf.locale;
+ final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale));
try {
- if (newLocale != null && !newLocale.equals(oldLocale)) {
+ if (needsChange) {
conf.locale = newLocale;
res.updateConfiguration(conf, null);
}
return job(res);
} finally {
- if (newLocale != null && !newLocale.equals(oldLocale)) {
+ if (needsChange) {
conf.locale = oldLocale;
res.updateConfiguration(conf, null);
}
diff --git a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
index 8a2d22256..a8800007a 100644
--- a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
+++ b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
@@ -1,23 +1,25 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
import android.view.inputmethod.EditorInfo;
+import java.util.Locale;
+
/**
* Holder class for data about a word already committed but that may still be edited.
*
@@ -29,17 +31,19 @@ import android.view.inputmethod.EditorInfo;
* the IME needs to take a note of what it has to replace and where it is.
* This class encapsulates this data.
*/
-public class PositionalInfoForUserDictPendingAddition {
+public final class PositionalInfoForUserDictPendingAddition {
final private String mOriginalWord;
final private int mCursorPos; // Position of the cursor after the word
final private EditorInfo mEditorInfo; // On what binding this has been added
+ final private int mCapitalizedMode;
private String mActualWordBeingAdded;
public PositionalInfoForUserDictPendingAddition(final String word, final int cursorPos,
- final EditorInfo editorInfo) {
+ final EditorInfo editorInfo, final int capitalizedMode) {
mOriginalWord = word;
mCursorPos = cursorPos;
mEditorInfo = editorInfo;
+ mCapitalizedMode = capitalizedMode;
}
public void setActualWordBeingAdded(final String actualWordBeingAdded) {
@@ -68,10 +72,11 @@ public class PositionalInfoForUserDictPendingAddition {
* @param connection The RichInputConnection through which to contact the editor.
* @param editorInfo Information pertaining to the editor we are currently in.
* @param currentCursorPosition The current cursor position, for checking purposes.
+ * @param locale The locale for changing case, if necessary
* @return true if the edit has been successfully made, false if we need to try again later
*/
public boolean tryReplaceWithActualWord(final RichInputConnection connection,
- final EditorInfo editorInfo, final int currentCursorPosition) {
+ final EditorInfo editorInfo, final int currentCursorPosition, final Locale locale) {
// If we still don't know the actual word being added, we need to try again later.
if (null == mActualWordBeingAdded) return false;
// The entered text and the registered text were the same anyway : we can
@@ -90,9 +95,12 @@ public class PositionalInfoForUserDictPendingAddition {
// so that it won't be tried again
if (currentCursorPosition != mCursorPos) return true;
// We have made all the checks : do the replacement and report success
+ // If this was auto-capitalized, we need to restore the case before committing
+ final String wordWithCaseFixed = CapsModeUtils.applyAutoCapsMode(mActualWordBeingAdded,
+ mCapitalizedMode, locale);
connection.setComposingRegion(currentCursorPosition - mOriginalWord.length(),
currentCursorPosition);
- connection.commitText(mActualWordBeingAdded, mActualWordBeingAdded.length());
+ connection.commitText(wordWithCaseFixed, wordWithCaseFixed.length());
return true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
index 9a46f160b..691f0602a 100644
--- a/java/src/com/android/inputmethod/latin/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java
index 5021ad384..b74b979b5 100644
--- a/java/src/com/android/inputmethod/latin/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java
@@ -19,11 +19,15 @@ package com.android.inputmethod.latin;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
+import android.util.Log;
import android.util.TypedValue;
import java.util.HashMap;
public final class ResourceUtils {
+ private static final String TAG = ResourceUtils.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
public static final float UNDEFINED_RATIO = -1.0f;
public static final int UNDEFINED_DIMENSION = -1;
@@ -31,24 +35,44 @@ public final class ResourceUtils {
// This utility class is not publicly instantiable.
}
+ private static final String DEFAULT_PREFIX = "DEFAULT,";
private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
private static final HashMap<String, String> sDeviceOverrideValueMap =
CollectionUtils.newHashMap();
- public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
+ public static String getDeviceOverrideValue(final Resources res, final int overrideResId) {
final int orientation = res.getConfiguration().orientation;
final String key = overrideResId + "-" + orientation;
- if (!sDeviceOverrideValueMap.containsKey(key)) {
- String overrideValue = defValue;
- for (final String element : res.getStringArray(overrideResId)) {
- if (element.startsWith(HARDWARE_PREFIX)) {
- overrideValue = element.substring(HARDWARE_PREFIX.length());
- break;
- }
+ if (sDeviceOverrideValueMap.containsKey(key)) {
+ return sDeviceOverrideValueMap.get(key);
+ }
+
+ final String[] overrideArray = res.getStringArray(overrideResId);
+ final String overrideValue = StringUtils.findPrefixedString(HARDWARE_PREFIX, overrideArray);
+ // The overrideValue might be an empty string.
+ if (overrideValue != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Find override value:"
+ + " resource="+ res.getResourceEntryName(overrideResId)
+ + " Build.HARDWARE=" + Build.HARDWARE + " override=" + overrideValue);
}
sDeviceOverrideValueMap.put(key, overrideValue);
+ return overrideValue;
+ }
+
+ final String defaultValue = StringUtils.findPrefixedString(DEFAULT_PREFIX, overrideArray);
+ // The defaultValue might be an empty string.
+ if (defaultValue == null) {
+ Log.w(TAG, "Couldn't find override value nor default value:"
+ + " resource="+ res.getResourceEntryName(overrideResId)
+ + " Build.HARDWARE=" + Build.HARDWARE);
+ } else if (DEBUG) {
+ Log.d(TAG, "Found default value:"
+ + " resource="+ res.getResourceEntryName(overrideResId)
+ + " Build.HARDWARE=" + Build.HARDWARE + " default=" + defaultValue);
}
- return sDeviceOverrideValueMap.get(key);
+ sDeviceOverrideValueMap.put(key, defaultValue);
+ return defaultValue;
}
public static boolean isValidFraction(final float fraction) {
@@ -85,8 +109,8 @@ public final class ResourceUtils {
return a.getDimensionPixelSize(index, ResourceUtils.UNDEFINED_DIMENSION);
}
- public static float getDimensionOrFraction(TypedArray a, int index, int base,
- float defValue) {
+ public static float getDimensionOrFraction(final TypedArray a, final int index, final int base,
+ final float defValue) {
final TypedValue value = a.peekValue(index);
if (value == null) {
return defValue;
@@ -99,7 +123,7 @@ public final class ResourceUtils {
return defValue;
}
- public static int getEnumValue(TypedArray a, int index, int defValue) {
+ public static int getEnumValue(final TypedArray a, final int index, final int defValue) {
final TypedValue value = a.peekValue(index);
if (value == null) {
return defValue;
@@ -110,19 +134,19 @@ public final class ResourceUtils {
return defValue;
}
- public static boolean isFractionValue(TypedValue v) {
+ public static boolean isFractionValue(final TypedValue v) {
return v.type == TypedValue.TYPE_FRACTION;
}
- public static boolean isDimensionValue(TypedValue v) {
+ public static boolean isDimensionValue(final TypedValue v) {
return v.type == TypedValue.TYPE_DIMENSION;
}
- public static boolean isIntegerValue(TypedValue v) {
+ public static boolean isIntegerValue(final TypedValue v) {
return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
}
- public static boolean isStringValue(TypedValue v) {
+ public static boolean isStringValue(final TypedValue v) {
return v.type == TypedValue.TYPE_STRING;
}
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 3b732278b..7300dbd23 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -26,7 +26,6 @@ import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
-import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.ResearchLogger;
@@ -47,7 +46,7 @@ public final class RichInputConnection {
private static final boolean DEBUG_PREVIOUS_TEXT = false;
private static final boolean DEBUG_BATCH_NESTING = false;
// Provision for a long word pair and a separator
- private static final int LOOKBACK_CHARACTER_NUM = BinaryDictionary.MAX_WORD_LENGTH * 2 + 1;
+ private static final int LOOKBACK_CHARACTER_NUM = Constants.Dictionary.MAX_WORD_LENGTH * 2 + 1;
private static final Pattern spaceRegex = Pattern.compile("\\s+");
private static final int INVALID_CURSOR_POSITION = -1;
@@ -66,12 +65,6 @@ public final class RichInputConnection {
* This contains the currently composing text, as LatinIME thinks the TextView is seeing it.
*/
private StringBuilder mComposingText = new StringBuilder();
- /**
- * This is a one-character string containing the character after the cursor. Since LatinIME
- * never touches it directly, it's never modified by any means other than re-reading from the
- * TextView when the cursor position is changed by the user.
- */
- private CharSequence mCharAfterTheCursor = "";
// 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.
private static final int DEFAULT_TEXT_CACHE_SIZE = 100;
@@ -147,7 +140,6 @@ public final class RichInputConnection {
mCommittedTextBeforeComposingText.setLength(0);
final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
if (null != textBeforeCursor) mCommittedTextBeforeComposingText.append(textBeforeCursor);
- mCharAfterTheCursor = getTextAfterCursor(1, 0);
if (null != mIC) {
mIC.finishComposingText();
if (ProductionFlag.IS_EXPERIMENTAL) {
@@ -186,9 +178,6 @@ public final class RichInputConnection {
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitText(text, i);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.richInputConnection_commitText(text, i);
- }
}
}
@@ -232,7 +221,7 @@ public final class RichInputConnection {
}
// This never calls InputConnection#getCapsMode - in fact, it's a static method that
// never blocks or initiates IPC.
- return StringUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType, locale,
+ return CapsModeUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType, locale,
hasSpaceBefore);
}
@@ -295,40 +284,40 @@ public final class RichInputConnection {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
- // This method is only called for enter or backspace when speaking to old
- // applications (target SDK <= 15), or for digits.
+ // This method is only called for enter or backspace when speaking to old applications
+ // (target SDK <= 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)), or for digits.
// When talking to new applications we never use this method because it's inherently
// racy and has unpredictable results, but for backward compatibility we continue
// sending the key events for only Enter and Backspace because some applications
// mistakenly catch them to do some stuff.
switch (keyEvent.getKeyCode()) {
- case KeyEvent.KEYCODE_ENTER:
- mCommittedTextBeforeComposingText.append("\n");
- mCurrentCursorPosition += 1;
- break;
- case KeyEvent.KEYCODE_DEL:
- if (0 == mComposingText.length()) {
- if (mCommittedTextBeforeComposingText.length() > 0) {
- mCommittedTextBeforeComposingText.delete(
- mCommittedTextBeforeComposingText.length() - 1,
- mCommittedTextBeforeComposingText.length());
- }
- } else {
- mComposingText.delete(mComposingText.length() - 1, mComposingText.length());
- }
- if (mCurrentCursorPosition > 0) mCurrentCursorPosition -= 1;
- break;
- case KeyEvent.KEYCODE_UNKNOWN:
- if (null != keyEvent.getCharacters()) {
- mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
- mCurrentCursorPosition += keyEvent.getCharacters().length();
+ case KeyEvent.KEYCODE_ENTER:
+ mCommittedTextBeforeComposingText.append("\n");
+ mCurrentCursorPosition += 1;
+ break;
+ case KeyEvent.KEYCODE_DEL:
+ if (0 == mComposingText.length()) {
+ if (mCommittedTextBeforeComposingText.length() > 0) {
+ mCommittedTextBeforeComposingText.delete(
+ mCommittedTextBeforeComposingText.length() - 1,
+ mCommittedTextBeforeComposingText.length());
}
- break;
- default:
- final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1);
- mCommittedTextBeforeComposingText.append(text);
- mCurrentCursorPosition += text.length();
- break;
+ } else {
+ mComposingText.delete(mComposingText.length() - 1, mComposingText.length());
+ }
+ if (mCurrentCursorPosition > 0) mCurrentCursorPosition -= 1;
+ break;
+ case KeyEvent.KEYCODE_UNKNOWN:
+ if (null != keyEvent.getCharacters()) {
+ mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
+ mCurrentCursorPosition += keyEvent.getCharacters().length();
+ }
+ break;
+ default:
+ final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1);
+ mCommittedTextBeforeComposingText.append(text);
+ mCurrentCursorPosition += text.length();
+ break;
}
}
if (null != mIC) {
@@ -394,9 +383,6 @@ public final class RichInputConnection {
// TextView flash the text for a second based on indices contained in the argument.
if (null != mIC) {
mIC.commitCorrection(correctionInfo);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.richInputConnection_commitCorrection(correctionInfo);
- }
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -417,7 +403,8 @@ public final class RichInputConnection {
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
- public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
+ @SuppressWarnings("unused")
+ public String getNthPreviousWord(final String sentenceSeperators, final int n) {
mIC = mParent.getCurrentInputConnection();
if (null == mIC) return null;
final CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
@@ -485,19 +472,22 @@ public final class RichInputConnection {
// (n = 2) "abc|" -> null
// (n = 2) "abc |" -> null
// (n = 2) "abc. def|" -> null
- public static CharSequence getNthPreviousWord(final CharSequence prev,
+ public static String getNthPreviousWord(final CharSequence prev,
final String sentenceSeperators, final int n) {
if (prev == null) return null;
- String[] w = spaceRegex.split(prev);
+ final String[] w = spaceRegex.split(prev);
// If we can't find n words, or we found an empty word, return null.
- if (w.length < n || w[w.length - n].length() <= 0) return null;
+ if (w.length < n) return null;
+ final String nthPrevWord = w[w.length - n];
+ final int length = nthPrevWord.length();
+ if (length <= 0) return null;
// If ends in a separator, return null
- char lastChar = w[w.length - n].charAt(w[w.length - n].length() - 1);
+ final char lastChar = nthPrevWord.charAt(length - 1);
if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
- return w[w.length - n];
+ return nthPrevWord;
}
/**
@@ -530,72 +520,68 @@ public final class RichInputConnection {
* be included in the returned range
* @return a range containing the text surrounding the cursor
*/
- public Range getWordRangeAtCursor(String sep, int additionalPrecedingWordsCount) {
+ public Range getWordRangeAtCursor(final String sep, final int additionalPrecedingWordsCount) {
mIC = mParent.getCurrentInputConnection();
if (mIC == null || sep == null) {
return null;
}
- CharSequence before = mIC.getTextBeforeCursor(1000, 0);
- CharSequence after = mIC.getTextAfterCursor(1000, 0);
+ final CharSequence before = mIC.getTextBeforeCursor(1000, 0);
+ final CharSequence after = mIC.getTextAfterCursor(1000, 0);
if (before == null || after == null) {
return null;
}
// Going backward, alternate skipping non-separators and separators until enough words
// have been read.
- int start = before.length();
+ int count = additionalPrecedingWordsCount;
+ int startIndexInBefore = before.length();
boolean isStoppingAtWhitespace = true; // toggles to indicate what to stop at
while (true) { // see comments below for why this is guaranteed to halt
- while (start > 0) {
- final int codePoint = Character.codePointBefore(before, start);
+ while (startIndexInBefore > 0) {
+ final int codePoint = Character.codePointBefore(before, startIndexInBefore);
if (isStoppingAtWhitespace == isSeparator(codePoint, sep)) {
break; // inner loop
}
- --start;
+ --startIndexInBefore;
if (Character.isSupplementaryCodePoint(codePoint)) {
- --start;
+ --startIndexInBefore;
}
}
// isStoppingAtWhitespace is true every other time through the loop,
// so additionalPrecedingWordsCount is guaranteed to become < 0, which
// guarantees outer loop termination
- if (isStoppingAtWhitespace && (--additionalPrecedingWordsCount < 0)) {
+ if (isStoppingAtWhitespace && (--count < 0)) {
break; // outer loop
}
isStoppingAtWhitespace = !isStoppingAtWhitespace;
}
// Find last word separator after the cursor
- int end = -1;
- while (++end < after.length()) {
- final int codePoint = Character.codePointAt(after, end);
+ int endIndexInAfter = -1;
+ while (++endIndexInAfter < after.length()) {
+ final int codePoint = Character.codePointAt(after, endIndexInAfter);
if (isSeparator(codePoint, sep)) {
break;
}
if (Character.isSupplementaryCodePoint(codePoint)) {
- ++end;
+ ++endIndexInAfter;
}
}
- int cursor = getCursorPosition();
- if (start >= 0 && cursor + end <= after.length() + before.length()) {
- String word = before.toString().substring(start, before.length())
- + after.toString().substring(0, end);
- return new Range(before.length() - start, end, word);
- }
-
- return null;
+ final String word = before.toString().substring(startIndexInBefore, before.length())
+ + after.toString().substring(0, endIndexInAfter);
+ return new Range(before.length() - startIndexInBefore, endIndexInAfter, word);
}
public boolean isCursorTouchingWord(final SettingsValues settingsValues) {
- CharSequence before = getTextBeforeCursor(1, 0);
- CharSequence after = getTextAfterCursor(1, 0);
+ final CharSequence before = getTextBeforeCursor(1, 0);
+ final CharSequence after = getTextAfterCursor(1, 0);
if (!TextUtils.isEmpty(before) && !settingsValues.isWordSeparator(before.charAt(0))
- && !settingsValues.isSymbolExcludedFromWordSeparators(before.charAt(0))) {
+ && !settingsValues.isWordConnector(before.charAt(0))) {
return true;
}
if (!TextUtils.isEmpty(after) && !settingsValues.isWordSeparator(after.charAt(0))
- && !settingsValues.isSymbolExcludedFromWordSeparators(after.charAt(0))) {
+ && !settingsValues.isWordConnector(after.charAt(0))) {
return true;
}
return false;
@@ -605,7 +591,7 @@ public final class RichInputConnection {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
final CharSequence lastOne = getTextBeforeCursor(1, 0);
if (lastOne != null && lastOne.length() == 1
- && lastOne.charAt(0) == Keyboard.CODE_SPACE) {
+ && lastOne.charAt(0) == Constants.CODE_SPACE) {
deleteSurroundingText(1, 0);
}
}
@@ -631,7 +617,7 @@ public final class RichInputConnection {
CharSequence word = getWordAtCursor(settings.mWordSeparators);
// We don't suggest on leading single quotes, so we have to remove them from the word if
// it starts with single quotes.
- while (!TextUtils.isEmpty(word) && Keyboard.CODE_SINGLE_QUOTE == word.charAt(0)) {
+ while (!TextUtils.isEmpty(word) && Constants.CODE_SINGLE_QUOTE == word.charAt(0)) {
word = word.subSequence(1, word.length());
}
if (TextUtils.isEmpty(word)) return null;
@@ -647,31 +633,33 @@ public final class RichInputConnection {
final char firstChar = word.charAt(0); // we just tested that word is not empty
if (word.length() == 1 && !Character.isLetter(firstChar)) return null;
- // We only suggest on words that start with a letter or a symbol that is excluded from
- // word separators (see #handleCharacterWhileInBatchEdit).
- if (!(Character.isLetter(firstChar)
- || settings.isSymbolExcludedFromWordSeparators(firstChar))) {
- return null;
- }
+ // We don't restart suggestion if the first character is not a letter, because we don't
+ // start composing when the first character is not a letter.
+ if (!Character.isLetter(firstChar)) return null;
return word;
}
- public boolean revertDoubleSpace() {
+ public boolean revertDoubleSpacePeriod() {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
// Here we test whether we indeed have a period and a space before us. This should not
// be needed, but it's there just in case something went wrong.
final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0);
- if (!". ".equals(textBeforeCursor)) {
+ final String periodSpace = ". ";
+ if (!periodSpace.equals(textBeforeCursor)) {
// Theoretically we should not be coming here if there isn't ". " before the
// cursor, but the application may be changing the text while we are typing, so
// anything goes. We should not crash.
Log.d(TAG, "Tried to revert double-space combo but we didn't find "
- + "\". \" just before the cursor.");
+ + "\"" + periodSpace + "\" just before the cursor.");
return false;
}
deleteSurroundingText(2, 0);
- commitText(" ", 1);
+ final String doubleSpace = " ";
+ commitText(doubleSpace, 1);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_revertDoubleSpacePeriod();
+ }
return true;
}
@@ -683,7 +671,7 @@ public final class RichInputConnection {
// NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to
// enter surrogate pairs this code will have been removed.
if (TextUtils.isEmpty(textBeforeCursor)
- || (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))) {
+ || (Constants.CODE_SPACE != textBeforeCursor.charAt(1))) {
// We may only come here if the application is changing the text while we are typing.
// This is quite a broken case, but not logically impossible, so we shouldn't crash,
// but some debugging log may be in order.
@@ -692,7 +680,11 @@ public final class RichInputConnection {
return false;
}
deleteSurroundingText(2, 0);
- commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
+ final String text = " " + textBeforeCursor.subSequence(0, 1);
+ commitText(text, 1);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_revertSwapPunctuation();
+ }
return true;
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
new file mode 100644
index 000000000..e39aae958
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Enrichment class for InputMethodManager to simplify interaction and add functionality.
+ */
+public final class RichInputMethodManager {
+ private static final String TAG = RichInputMethodManager.class.getSimpleName();
+
+ private RichInputMethodManager() {
+ // This utility class is not publicly instantiable.
+ }
+
+ private static final RichInputMethodManager sInstance = new RichInputMethodManager();
+
+ private InputMethodManagerCompatWrapper mImmWrapper;
+ private InputMethodInfo mInputMethodInfoOfThisIme;
+
+ public static RichInputMethodManager getInstance() {
+ sInstance.checkInitialized();
+ return sInstance;
+ }
+
+ public static void init(final Context context) {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ sInstance.initInternal(context, prefs);
+ }
+
+ private boolean isInitialized() {
+ return mImmWrapper != null;
+ }
+
+ private void checkInitialized() {
+ if (!isInitialized()) {
+ throw new RuntimeException(TAG + " is used before initialization");
+ }
+ }
+
+ private void initInternal(final Context context, final SharedPreferences prefs) {
+ if (isInitialized()) {
+ return;
+ }
+ mImmWrapper = new InputMethodManagerCompatWrapper(context);
+ mInputMethodInfoOfThisIme = getInputMethodInfoOfThisIme(context);
+
+ // Initialize additional subtypes.
+ SubtypeLocale.init(context);
+ final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(
+ prefs, context.getResources());
+ final InputMethodSubtype[] additionalSubtypes =
+ AdditionalSubtype.createAdditionalSubtypesArray(prefAdditionalSubtypes);
+ setAdditionalInputMethodSubtypes(additionalSubtypes);
+ }
+
+ public InputMethodManager getInputMethodManager() {
+ checkInitialized();
+ return mImmWrapper.mImm;
+ }
+
+ private InputMethodInfo getInputMethodInfoOfThisIme(final Context context) {
+ final String packageName = context.getPackageName();
+ for (final InputMethodInfo imi : mImmWrapper.mImm.getInputMethodList()) {
+ if (imi.getPackageName().equals(packageName)) {
+ return imi;
+ }
+ }
+ throw new RuntimeException("Input method id for " + packageName + " not found.");
+ }
+
+ public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
+ final boolean result = mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme);
+ if (!result) {
+ mImmWrapper.mImm.switchToLastInputMethod(token);
+ return false;
+ }
+ return true;
+ }
+
+ public InputMethodInfo getInputMethodInfoOfThisIme() {
+ return mInputMethodInfoOfThisIme;
+ }
+
+ public String getInputMethodIdOfThisIme() {
+ return mInputMethodInfoOfThisIme.getId();
+ }
+
+ public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) {
+ return checkIfSubtypeBelongsToImeAndEnabled(mInputMethodInfoOfThisIme, subtype);
+ }
+
+ public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
+ final InputMethodSubtype subtype) {
+ final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype);
+ final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(
+ subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
+ mInputMethodInfoOfThisIme, false /* allowsImplicitlySelectedSubtypes */));
+ return subtypeEnabled && !subtypeExplicitlyEnabled;
+ }
+
+ public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi,
+ final InputMethodSubtype subtype) {
+ return checkIfSubtypeBelongsToList(
+ subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
+ imi, true /* allowsImplicitlySelectedSubtypes */));
+ }
+
+ private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype,
+ final List<InputMethodSubtype> subtypes) {
+ for (final InputMethodSubtype ims : subtypes) {
+ if (ims.equals(subtype)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype) {
+ final InputMethodInfo myImi = mInputMethodInfoOfThisIme;
+ final int count = myImi.getSubtypeCount();
+ for (int i = 0; i < count; i++) {
+ final InputMethodSubtype ims = myImi.getSubtypeAt(i);
+ if (ims.equals(subtype)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public InputMethodSubtype getCurrentInputMethodSubtype(
+ final InputMethodSubtype defaultSubtype) {
+ final InputMethodSubtype currentSubtype = mImmWrapper.mImm.getCurrentInputMethodSubtype();
+ return (currentSubtype != null) ? currentSubtype : defaultSubtype;
+ }
+
+ public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) {
+ final List<InputMethodInfo> enabledImis = mImmWrapper.mImm.getEnabledInputMethodList();
+ return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis);
+ }
+
+ public boolean hasMultipleEnabledSubtypesInThisIme(
+ final boolean shouldIncludeAuxiliarySubtypes) {
+ final List<InputMethodInfo> imiList = Collections.singletonList(mInputMethodInfoOfThisIme);
+ return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
+ }
+
+ private boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes,
+ final List<InputMethodInfo> imiList) {
+ // Number of the filtered IMEs
+ int filteredImisCount = 0;
+
+ for (InputMethodInfo imi : imiList) {
+ // We can return true immediately after we find two or more filtered IMEs.
+ if (filteredImisCount > 1) return true;
+ final List<InputMethodSubtype> subtypes =
+ mImmWrapper.mImm.getEnabledInputMethodSubtypeList(imi, true);
+ // IMEs that have no subtypes should be counted.
+ if (subtypes.isEmpty()) {
+ ++filteredImisCount;
+ continue;
+ }
+
+ int auxCount = 0;
+ for (InputMethodSubtype subtype : subtypes) {
+ if (subtype.isAuxiliary()) {
+ ++auxCount;
+ }
+ }
+ final int nonAuxCount = subtypes.size() - auxCount;
+
+ // IMEs that have one or more non-auxiliary subtypes should be counted.
+ // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+ // subtypes should be counted as well.
+ if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+ ++filteredImisCount;
+ continue;
+ }
+ }
+
+ if (filteredImisCount > 1) {
+ return true;
+ }
+ final List<InputMethodSubtype> subtypes =
+ mImmWrapper.mImm.getEnabledInputMethodSubtypeList(null, true);
+ int keyboardCount = 0;
+ // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
+ // both explicitly and implicitly enabled input method subtype.
+ // (The current IME should be LatinIME.)
+ for (InputMethodSubtype subtype : subtypes) {
+ if (KEYBOARD_MODE.equals(subtype.getMode())) {
+ ++keyboardCount;
+ }
+ }
+ return keyboardCount > 1;
+ }
+
+ public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString,
+ final String keyboardLayoutSetName) {
+ final InputMethodInfo myImi = mInputMethodInfoOfThisIme;
+ final int count = myImi.getSubtypeCount();
+ for (int i = 0; i < count; i++) {
+ final InputMethodSubtype subtype = myImi.getSubtypeAt(i);
+ final String layoutName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
+ if (localeString.equals(subtype.getLocale())
+ && keyboardLayoutSetName.equals(layoutName)) {
+ return subtype;
+ }
+ }
+ return null;
+ }
+
+ public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) {
+ mImmWrapper.mImm.setInputMethodAndSubtype(
+ token, mInputMethodInfoOfThisIme.getId(), subtype);
+ }
+
+ public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) {
+ mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
+ mInputMethodInfoOfThisIme.getId(), subtypes);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
new file mode 100644
index 000000000..9819a02ef
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public final class SeekBarDialogPreference extends DialogPreference
+ implements SeekBar.OnSeekBarChangeListener {
+ public interface ValueProxy {
+ public int readValue(final String key);
+ public int readDefaultValue(final String key);
+ public void writeValue(final int value, final String key);
+ public void feedbackValue(final int value);
+ }
+
+ private final int mValueFormatResId;
+ private final int mMaxValue;
+ private final int mMinValue;
+ private final int mStepValue;
+
+ private TextView mValueView;
+ private SeekBar mSeekBar;
+
+ private ValueProxy mValueProxy;
+
+ public SeekBarDialogPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.SeekBarDialogPreference, 0, 0);
+ mValueFormatResId = a.getResourceId(R.styleable.SeekBarDialogPreference_valueFormatText, 0);
+ mMaxValue = a.getInt(R.styleable.SeekBarDialogPreference_maxValue, 0);
+ mMinValue = a.getInt(R.styleable.SeekBarDialogPreference_minValue, 0);
+ mStepValue = a.getInt(R.styleable.SeekBarDialogPreference_stepValue, 0);
+ a.recycle();
+ setDialogLayoutResource(R.layout.seek_bar_dialog);
+ }
+
+ public void setInterface(final ValueProxy proxy) {
+ mValueProxy = proxy;
+ setSummary(getValueText(proxy.readValue(getKey())));
+ }
+
+ private String getValueText(final int value) {
+ if (mValueFormatResId == 0) {
+ return Integer.toString(value);
+ } else {
+ return getContext().getString(mValueFormatResId, value);
+ }
+ }
+
+ @Override
+ protected View onCreateDialogView() {
+ final View view = super.onCreateDialogView();
+ mSeekBar = (SeekBar)view.findViewById(R.id.seek_bar_dialog_bar);
+ mSeekBar.setMax(mMaxValue - mMinValue);
+ mSeekBar.setOnSeekBarChangeListener(this);
+ mValueView = (TextView)view.findViewById(R.id.seek_bar_dialog_value);
+ return view;
+ }
+
+ private int getProgressFromValue(final int value) {
+ return value - mMinValue;
+ }
+
+ private int getValueFromProgress(final int progress) {
+ return progress + mMinValue;
+ }
+
+ private int clipValue(final int value) {
+ final int clippedValue = Math.min(mMaxValue, Math.max(mMinValue, value));
+ if (mStepValue <= 1) {
+ return clippedValue;
+ }
+ return clippedValue - (clippedValue % mStepValue);
+ }
+
+ private int getClippedValueFromProgress(final int progress) {
+ return clipValue(getValueFromProgress(progress));
+ }
+
+ private void setValue(final int value, final boolean fromUser) {
+ mValueView.setText(getValueText(value));
+ if (!fromUser) {
+ mSeekBar.setProgress(getProgressFromValue(value));
+ }
+ }
+
+ @Override
+ protected void onBindDialogView(final View view) {
+ setValue(clipValue(mValueProxy.readValue(getKey())), false /* fromUser */);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) {
+ builder.setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .setNeutralButton(R.string.button_default, this);
+ }
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ super.onClick(dialog, which);
+ if (which == DialogInterface.BUTTON_NEUTRAL) {
+ setValue(clipValue(mValueProxy.readDefaultValue(getKey())), false /* fromUser */);
+ }
+ if (which != DialogInterface.BUTTON_NEGATIVE) {
+ setSummary(mValueView.getText());
+ mValueProxy.writeValue(getClippedValueFromProgress(mSeekBar.getProgress()), getKey());
+ }
+ }
+
+ @Override
+ public void onProgressChanged(final SeekBar seekBar, final int progress,
+ final boolean fromUser) {
+ setValue(getClippedValueFromProgress(progress), fromUser);
+ }
+
+ @Override
+ public void onStartTrackingTouch(final SeekBar seekBar) {}
+
+ @Override
+ public void onStopTrackingTouch(final SeekBar seekBar) {
+ mValueProxy.feedbackValue(getClippedValueFromProgress(seekBar.getProgress()));
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 348928df8..435074bdb 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -1,49 +1,33 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
-import android.app.AlertDialog;
-import android.app.backup.BackupManager;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
-import android.media.AudioManager;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceGroup;
-import android.preference.PreferenceScreen;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.inputmethod.InputMethodSubtype;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
-import android.widget.TextView;
-
-import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethodcommon.InputMethodSettingsFragment;
-
-public final class Settings extends InputMethodSettingsFragment
- implements SharedPreferences.OnSharedPreferenceChangeListener {
+import android.preference.PreferenceManager;
+import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
// In the same order as xml/prefs.xml
public static final String PREF_GENERAL_SETTINGS = "general_settings";
public static final String PREF_AUTO_CAP = "auto_cap";
@@ -60,6 +44,8 @@ public final class Settings extends InputMethodSettingsFragment
"last_user_dictionary_write_time";
public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
+ public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD =
+ "pref_key_use_double_space_period";
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 =
@@ -70,6 +56,8 @@ public final class Settings extends InputMethodSettingsFragment
public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction";
public static final String PREF_GESTURE_SETTINGS = "gesture_typing_settings";
public static final String PREF_GESTURE_INPUT = "gesture_input";
+ 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";
public static final String PREF_VIBRATION_DURATION_SETTINGS =
"pref_vibration_duration_settings";
public static final String PREF_KEYPRESS_SOUND_VOLUME =
@@ -77,391 +65,213 @@ public final class Settings extends InputMethodSettingsFragment
public static final String PREF_GESTURE_PREVIEW_TRAIL = "pref_gesture_preview_trail";
public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT =
"pref_gesture_floating_preview_text";
+ public static final String PREF_SHOW_SETUP_WIZARD_ICON = "pref_show_setup_wizard_icon";
public static final String PREF_INPUT_LANGUAGE = "input_language";
public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
public static final String PREF_DEBUG_SETTINGS = "debug_settings";
- private PreferenceScreen mKeypressVibrationDurationSettingsPref;
- private PreferenceScreen mKeypressSoundVolumeSettingsPref;
- private ListPreference mVoicePreference;
- private ListPreference mShowCorrectionSuggestionsPreference;
- private ListPreference mAutoCorrectionThresholdPreference;
- private ListPreference mKeyPreviewPopupDismissDelay;
- // Use bigrams to predict the next word when there is no input for it yet
- private CheckBoxPreference mBigramPrediction;
- private Preference mDebugSettingsPreference;
-
- private TextView mKeypressVibrationDurationSettingsTextView;
- private TextView mKeypressSoundVolumeSettingsTextView;
-
- private static void setPreferenceEnabled(final Preference preference, final boolean enabled) {
- if (preference != null) {
- preference.setEnabled(enabled);
- }
+ // This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead.
+ // This is being used only for the backward compatibility.
+ private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
+ "pref_suppress_language_switch_key";
+
+ private Resources mRes;
+ private SharedPreferences mPrefs;
+ private Locale mCurrentLocale;
+ private SettingsValues mSettingsValues;
+
+ private static final Settings sInstance = new Settings();
+
+ public static Settings getInstance() {
+ return sInstance;
}
- private void ensureConsistencyOfAutoCorrectionSettings() {
- final String autoCorrectionOff = getResources().getString(
- R.string.auto_correction_threshold_mode_index_off);
- final String currentSetting = mAutoCorrectionThresholdPreference.getValue();
- setPreferenceEnabled(mBigramPrediction, !currentSetting.equals(autoCorrectionOff));
+ public static void init(final Context context) {
+ sInstance.onCreate(context);
}
- @Override
- public void onCreate(final Bundle icicle) {
- super.onCreate(icicle);
- setInputMethodSettingsCategoryTitle(R.string.language_selection_title);
- setSubtypeEnablerTitle(R.string.select_language);
- addPreferencesFromResource(R.xml.prefs);
-
- final Resources res = getResources();
- final Context context = getActivity();
-
- // When we are called from the Settings application but we are not already running, the
- // {@link SubtypeLocale} class may not have been initialized. It is safe to call
- // {@link SubtypeLocale#init(Context)} multiple times.
- SubtypeLocale.init(context);
- mVoicePreference = (ListPreference) findPreference(PREF_VOICE_MODE);
- mShowCorrectionSuggestionsPreference =
- (ListPreference) findPreference(PREF_SHOW_SUGGESTIONS_SETTING);
- SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
- prefs.registerOnSharedPreferenceChangeListener(this);
-
- mAutoCorrectionThresholdPreference =
- (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
- mBigramPrediction = (CheckBoxPreference) findPreference(PREF_BIGRAM_PREDICTIONS);
- ensureConsistencyOfAutoCorrectionSettings();
-
- final PreferenceGroup generalSettings =
- (PreferenceGroup) findPreference(PREF_GENERAL_SETTINGS);
- final PreferenceGroup textCorrectionGroup =
- (PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS);
- final PreferenceGroup gestureTypingSettings =
- (PreferenceGroup) findPreference(PREF_GESTURE_SETTINGS);
- final PreferenceGroup miscSettings =
- (PreferenceGroup) findPreference(PREF_MISC_SETTINGS);
-
- mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS);
- if (mDebugSettingsPreference != null) {
- if (ProductionFlag.IS_INTERNAL) {
- final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
- debugSettingsIntent.setClassName(
- context.getPackageName(), DebugSettingsActivity.class.getName());
- mDebugSettingsPreference.setIntent(debugSettingsIntent);
- } else {
- miscSettings.removePreference(mDebugSettingsPreference);
- }
- }
+ private Settings() {
+ // Intentional empty constructor for singleton.
+ }
- final boolean showVoiceKeyOption = res.getBoolean(
- R.bool.config_enable_show_voice_key_option);
- if (!showVoiceKeyOption) {
- generalSettings.removePreference(mVoicePreference);
- }
+ private void onCreate(final Context context) {
+ mRes = context.getResources();
+ mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
+ }
- final PreferenceGroup advancedSettings =
- (PreferenceGroup) findPreference(PREF_ADVANCED_SETTINGS);
- if (!VibratorUtils.getInstance(context).hasVibrator()) {
- generalSettings.removePreference(findPreference(PREF_VIBRATE_ON));
- if (null != advancedSettings) { // Theoretically advancedSettings cannot be null
- advancedSettings.removePreference(findPreference(PREF_VIBRATION_DURATION_SETTINGS));
- }
- }
+ public void onDestroy() {
+ mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ }
- final boolean showKeyPreviewPopupOption = res.getBoolean(
- R.bool.config_enable_show_popup_on_keypress_option);
- mKeyPreviewPopupDismissDelay =
- (ListPreference) findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
- if (!showKeyPreviewPopupOption) {
- generalSettings.removePreference(findPreference(PREF_POPUP_ON));
- if (null != advancedSettings) { // Theoretically advancedSettings cannot be null
- advancedSettings.removePreference(mKeyPreviewPopupDismissDelay);
- }
- } else {
- final String[] entries = new String[] {
- res.getString(R.string.key_preview_popup_dismiss_no_delay),
- res.getString(R.string.key_preview_popup_dismiss_default_delay),
- };
- final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
- R.integer.config_key_preview_linger_timeout));
- mKeyPreviewPopupDismissDelay.setEntries(entries);
- mKeyPreviewPopupDismissDelay.setEntryValues(
- new String[] { "0", popupDismissDelayDefaultValue });
- if (null == mKeyPreviewPopupDismissDelay.getValue()) {
- mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
- }
- setPreferenceEnabled(mKeyPreviewPopupDismissDelay,
- SettingsValues.isKeyPreviewPopupEnabled(prefs, res));
- }
+ @Override
+ public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
+ loadSettings(mCurrentLocale, mSettingsValues.mInputAttributes);
+ }
- setPreferenceEnabled(findPreference(PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST),
- SettingsValues.showsLanguageSwitchKey(prefs));
+ public void loadSettings(final Locale locale, final InputAttributes inputAttributes) {
+ mCurrentLocale = locale;
+ final SharedPreferences prefs = mPrefs;
+ final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
+ @Override
+ protected SettingsValues job(final Resources res) {
+ return new SettingsValues(prefs, res, inputAttributes);
+ }
+ };
+ mSettingsValues = job.runInLocale(mRes, locale);
+ }
- final PreferenceScreen dictionaryLink =
- (PreferenceScreen) findPreference(PREF_CONFIGURE_DICTIONARIES_KEY);
- final Intent intent = dictionaryLink.getIntent();
+ // TODO: Remove this method and add proxy method to SettingsValues.
+ public SettingsValues getCurrent() {
+ return mSettingsValues;
+ }
- final int number = context.getPackageManager().queryIntentActivities(intent, 0).size();
- if (0 >= number) {
- textCorrectionGroup.removePreference(dictionaryLink);
- }
+ // Accessed from the settings interface, hence public
+ public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
+ final Resources res) {
+ return prefs.getBoolean(Settings.PREF_SOUND_ON,
+ res.getBoolean(R.bool.config_default_sound_enabled));
+ }
- final boolean gestureInputEnabledByBuildConfig = res.getBoolean(
- R.bool.config_gesture_input_enabled_by_build_config);
- if (!gestureInputEnabledByBuildConfig) {
- getPreferenceScreen().removePreference(gestureTypingSettings);
- }
+ public static boolean readVibrationEnabled(final SharedPreferences prefs,
+ final Resources res) {
+ final boolean hasVibrator = AudioAndHapticFeedbackManager.getInstance().hasVibrator();
+ return hasVibrator && prefs.getBoolean(PREF_VIBRATE_ON,
+ res.getBoolean(R.bool.config_default_vibration_enabled));
+ }
- mKeypressVibrationDurationSettingsPref =
- (PreferenceScreen) findPreference(PREF_VIBRATION_DURATION_SETTINGS);
- if (mKeypressVibrationDurationSettingsPref != null) {
- mKeypressVibrationDurationSettingsPref.setOnPreferenceClickListener(
- new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference arg0) {
- showKeypressVibrationDurationSettingsDialog();
- return true;
- }
- });
- updateKeypressVibrationDurationSettingsSummary(prefs, res);
- }
+ public static boolean readAutoCorrectEnabled(final String currentAutoCorrectionSetting,
+ final Resources res) {
+ final String autoCorrectionOff = res.getString(
+ R.string.auto_correction_threshold_mode_index_off);
+ return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
+ }
- mKeypressSoundVolumeSettingsPref =
- (PreferenceScreen) findPreference(PREF_KEYPRESS_SOUND_VOLUME);
- if (mKeypressSoundVolumeSettingsPref != null) {
- mKeypressSoundVolumeSettingsPref.setOnPreferenceClickListener(
- new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference arg0) {
- showKeypressSoundVolumeSettingDialog();
- return true;
- }
- });
- updateKeypressSoundVolumeSummary(prefs, res);
- }
- refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, res);
+ public static boolean readFromBuildConfigIfGestureInputEnabled(final Resources res) {
+ return res.getBoolean(R.bool.config_gesture_input_enabled_by_build_config);
}
- @Override
- public void onResume() {
- super.onResume();
- final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
- if (isShortcutImeEnabled) {
- updateVoiceModeSummary();
- } else {
- getPreferenceScreen().removePreference(mVoicePreference);
- }
- updateShowCorrectionSuggestionsSummary();
- updateKeyPreviewPopupDelaySummary();
- updateCustomInputStylesSummary();
+ public static boolean readGestureInputEnabled(final SharedPreferences prefs,
+ final Resources res) {
+ return readFromBuildConfigIfGestureInputEnabled(res)
+ && prefs.getBoolean(Settings.PREF_GESTURE_INPUT, true);
}
- @Override
- public void onDestroy() {
- getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
- this);
- super.onDestroy();
+ public static boolean readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(
+ final Resources res) {
+ return res.getBoolean(R.bool.config_enable_show_option_of_key_preview_popup);
}
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
- (new BackupManager(getActivity())).dataChanged();
- if (key.equals(PREF_POPUP_ON)) {
- setPreferenceEnabled(findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY),
- prefs.getBoolean(PREF_POPUP_ON, true));
- } else if (key.equals(PREF_SHOW_LANGUAGE_SWITCH_KEY)) {
- setPreferenceEnabled(findPreference(PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST),
- SettingsValues.showsLanguageSwitchKey(prefs));
- } else if (key.equals(PREF_GESTURE_INPUT)) {
- final boolean gestureInputEnabledByConfig = getResources().getBoolean(
- R.bool.config_gesture_input_enabled_by_build_config);
- if (gestureInputEnabledByConfig) {
- final boolean gestureInputEnabledByUser = prefs.getBoolean(
- PREF_GESTURE_INPUT, true);
- setPreferenceEnabled(findPreference(PREF_GESTURE_PREVIEW_TRAIL),
- gestureInputEnabledByUser);
- setPreferenceEnabled(findPreference(PREF_GESTURE_FLOATING_PREVIEW_TEXT),
- gestureInputEnabledByUser);
- }
+ public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs,
+ final Resources res) {
+ final boolean defaultKeyPreviewPopup = res.getBoolean(
+ R.bool.config_default_key_preview_popup);
+ if (!readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(res)) {
+ return defaultKeyPreviewPopup;
}
- ensureConsistencyOfAutoCorrectionSettings();
- updateVoiceModeSummary();
- updateShowCorrectionSuggestionsSummary();
- updateKeyPreviewPopupDelaySummary();
- refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
+ return prefs.getBoolean(PREF_POPUP_ON, defaultKeyPreviewPopup);
}
- private void updateShowCorrectionSuggestionsSummary() {
- mShowCorrectionSuggestionsPreference.setSummary(
- getResources().getStringArray(R.array.prefs_suggestion_visibilities)
- [mShowCorrectionSuggestionsPreference.findIndexOfValue(
- mShowCorrectionSuggestionsPreference.getValue())]);
+ public static int readKeyPreviewPopupDismissDelay(final SharedPreferences prefs,
+ final Resources res) {
+ return Integer.parseInt(prefs.getString(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
+ Integer.toString(res.getInteger(
+ R.integer.config_key_preview_linger_timeout))));
}
- private void updateCustomInputStylesSummary() {
- final PreferenceScreen customInputStyles =
- (PreferenceScreen)findPreference(PREF_CUSTOM_INPUT_STYLES);
- final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
- final Resources res = getResources();
- final String prefSubtype = SettingsValues.getPrefAdditionalSubtypes(prefs, res);
- final InputMethodSubtype[] subtypes =
- AdditionalSubtype.createAdditionalSubtypesArray(prefSubtype);
- final StringBuilder styles = new StringBuilder();
- for (final InputMethodSubtype subtype : subtypes) {
- if (styles.length() > 0) styles.append(", ");
- styles.append(SubtypeLocale.getSubtypeDisplayName(subtype, res));
+ public static boolean readShowsLanguageSwitchKey(final SharedPreferences prefs) {
+ if (prefs.contains(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY)) {
+ final boolean suppressLanguageSwitchKey = prefs.getBoolean(
+ PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false);
+ final SharedPreferences.Editor editor = prefs.edit();
+ editor.remove(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY);
+ editor.putBoolean(PREF_SHOW_LANGUAGE_SWITCH_KEY, !suppressLanguageSwitchKey);
+ editor.apply();
}
- customInputStyles.setSummary(styles);
+ return prefs.getBoolean(PREF_SHOW_LANGUAGE_SWITCH_KEY, true);
}
- private void updateKeyPreviewPopupDelaySummary() {
- final ListPreference lp = mKeyPreviewPopupDismissDelay;
- final CharSequence[] entries = lp.getEntries();
- if (entries == null || entries.length <= 0) return;
- lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]);
+ public static String readPrefAdditionalSubtypes(final SharedPreferences prefs,
+ final Resources res) {
+ final String predefinedPrefSubtypes = AdditionalSubtype.createPrefSubtypes(
+ res.getStringArray(R.array.predefined_subtypes));
+ return prefs.getString(PREF_CUSTOM_INPUT_STYLES, predefinedPrefSubtypes);
}
- private void updateVoiceModeSummary() {
- mVoicePreference.setSummary(
- getResources().getStringArray(R.array.voice_input_modes_summary)
- [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]);
+ public static void writePrefAdditionalSubtypes(final SharedPreferences prefs,
+ final String prefSubtypes) {
+ prefs.edit().putString(Settings.PREF_CUSTOM_INPUT_STYLES, prefSubtypes).apply();
}
- private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
- final SharedPreferences sp, final Resources res) {
- if (mKeypressVibrationDurationSettingsPref != null) {
- final boolean hasVibratorHardware = VibratorUtils.getInstance(getActivity())
- .hasVibrator();
- final boolean vibrateOnByUser = sp.getBoolean(Settings.PREF_VIBRATE_ON,
- res.getBoolean(R.bool.config_default_vibration_enabled));
- setPreferenceEnabled(mKeypressVibrationDurationSettingsPref,
- hasVibratorHardware && vibrateOnByUser);
- }
+ public static float readKeypressSoundVolume(final SharedPreferences prefs,
+ final Resources res) {
+ final float volume = prefs.getFloat(PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
+ return (volume >= 0) ? volume : readDefaultKeypressSoundVolume(res);
+ }
- if (mKeypressSoundVolumeSettingsPref != null) {
- final boolean soundOn = sp.getBoolean(Settings.PREF_SOUND_ON,
- res.getBoolean(R.bool.config_default_sound_enabled));
- setPreferenceEnabled(mKeypressSoundVolumeSettingsPref, soundOn);
- }
+ public static float readDefaultKeypressSoundVolume(final Resources res) {
+ return Float.parseFloat(
+ ResourceUtils.getDeviceOverrideValue(res, R.array.keypress_volumes));
}
- private void updateKeypressVibrationDurationSettingsSummary(
- final SharedPreferences sp, final Resources res) {
- if (mKeypressVibrationDurationSettingsPref != null) {
- mKeypressVibrationDurationSettingsPref.setSummary(
- SettingsValues.getCurrentVibrationDuration(sp, res)
- + res.getString(R.string.settings_ms));
- }
+ public static int readKeyLongpressTimeout(final SharedPreferences prefs,
+ final Resources res) {
+ final int ms = prefs.getInt(PREF_KEY_LONGPRESS_TIMEOUT, -1);
+ return (ms >= 0) ? ms : readDefaultKeyLongpressTimeout(res);
}
- private void showKeypressVibrationDurationSettingsDialog() {
- final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
- final Context context = getActivity();
- final Resources res = context.getResources();
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.prefs_keypress_vibration_duration_settings);
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- final int ms = Integer.valueOf(
- mKeypressVibrationDurationSettingsTextView.getText().toString());
- sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, ms).apply();
- updateKeypressVibrationDurationSettingsSummary(sp, res);
- }
- });
- builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- dialog.dismiss();
- }
- });
- final View v = LayoutInflater.from(context).inflate(
- R.layout.vibration_settings_dialog, null);
- final int currentMs = SettingsValues.getCurrentVibrationDuration(
- getPreferenceManager().getSharedPreferences(), getResources());
- mKeypressVibrationDurationSettingsTextView = (TextView)v.findViewById(R.id.vibration_value);
- final SeekBar sb = (SeekBar)v.findViewById(R.id.vibration_settings);
- sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {
- final int tempMs = arg1;
- mKeypressVibrationDurationSettingsTextView.setText(String.valueOf(tempMs));
- }
+ public static int readDefaultKeyLongpressTimeout(final Resources res) {
+ return res.getInteger(R.integer.config_default_longpress_key_timeout);
+ }
- @Override
- public void onStartTrackingTouch(SeekBar arg0) {
- }
+ public static int readKeypressVibrationDuration(final SharedPreferences prefs,
+ final Resources res) {
+ final int ms = prefs.getInt(PREF_VIBRATION_DURATION_SETTINGS, -1);
+ return (ms >= 0) ? ms : readDefaultKeypressVibrationDuration(res);
+ }
- @Override
- public void onStopTrackingTouch(SeekBar arg0) {
- final int tempMs = arg0.getProgress();
- VibratorUtils.getInstance(context).vibrate(tempMs);
- }
- });
- sb.setProgress(currentMs);
- mKeypressVibrationDurationSettingsTextView.setText(String.valueOf(currentMs));
- builder.setView(v);
- builder.create().show();
+ public static int readDefaultKeypressVibrationDuration(final Resources res) {
+ return Integer.parseInt(
+ ResourceUtils.getDeviceOverrideValue(res, R.array.keypress_vibration_durations));
+ }
+
+ public static boolean readUsabilityStudyMode(final SharedPreferences prefs) {
+ return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true);
}
- private void updateKeypressSoundVolumeSummary(final SharedPreferences sp, final Resources res) {
- if (mKeypressSoundVolumeSettingsPref != null) {
- mKeypressSoundVolumeSettingsPref.setSummary(String.valueOf(
- (int)(SettingsValues.getCurrentKeypressSoundVolume(sp, res) * 100)));
+ public static long readLastUserHistoryWriteTime(final SharedPreferences prefs,
+ final String locale) {
+ final String str = prefs.getString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
+ final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(str);
+ if (map.containsKey(locale)) {
+ return map.get(locale);
}
+ return 0;
}
- private void showKeypressSoundVolumeSettingDialog() {
- final Context context = getActivity();
- final AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
- final Resources res = context.getResources();
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.prefs_keypress_sound_volume_settings);
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- final float volume =
- ((float)Integer.valueOf(
- mKeypressSoundVolumeSettingsTextView.getText().toString())) / 100;
- sp.edit().putFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, volume).apply();
- updateKeypressSoundVolumeSummary(sp, res);
- }
- });
- builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- dialog.dismiss();
- }
- });
- final View v = LayoutInflater.from(context).inflate(
- R.layout.sound_effect_volume_dialog, null);
- final int currentVolumeInt =
- (int)(SettingsValues.getCurrentKeypressSoundVolume(sp, res) * 100);
- mKeypressSoundVolumeSettingsTextView =
- (TextView)v.findViewById(R.id.sound_effect_volume_value);
- final SeekBar sb = (SeekBar)v.findViewById(R.id.sound_effect_volume_bar);
- sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {
- final int tempVolume = arg1;
- mKeypressSoundVolumeSettingsTextView.setText(String.valueOf(tempVolume));
- }
+ public static void writeLastUserHistoryWriteTime(final SharedPreferences prefs,
+ final String locale) {
+ final String oldStr = prefs.getString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
+ final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(oldStr);
+ map.put(locale, System.currentTimeMillis());
+ final String newStr = LocaleUtils.localeAndTimeHashMapToStr(map);
+ prefs.edit().putString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
+ }
- @Override
- public void onStartTrackingTouch(SeekBar arg0) {
- }
+ public static boolean readUseFullscreenMode(final Resources res) {
+ return res.getBoolean(R.bool.config_use_fullscreen_mode);
+ }
- @Override
- public void onStopTrackingTouch(SeekBar arg0) {
- final float tempVolume = ((float)arg0.getProgress()) / 100;
- am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, tempVolume);
- }
- });
- sb.setProgress(currentVolumeInt);
- mKeypressSoundVolumeSettingsTextView.setText(String.valueOf(currentVolumeInt));
- builder.setView(v);
- builder.create().show();
+ public static boolean readShowSetupWizardIcon(final SharedPreferences prefs,
+ final Context context) {
+ if (!prefs.contains(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
+ final ApplicationInfo appInfo = context.getApplicationInfo();
+ final boolean isApplicationInSystemImage =
+ (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ // Default value
+ return !isApplicationInSystemImage;
+ }
+ return prefs.getBoolean(Settings.PREF_SHOW_SETUP_WIZARD_ICON, false);
}
}
diff --git a/java/src/com/android/inputmethod/latin/SettingsActivity.java b/java/src/com/android/inputmethod/latin/SettingsActivity.java
index 0d3c8ebb7..ed8cf6d8f 100644
--- a/java/src/com/android/inputmethod/latin/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/SettingsActivity.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -20,7 +20,7 @@ import android.content.Intent;
import android.preference.PreferenceActivity;
public final class SettingsActivity extends PreferenceActivity {
- private static final String DEFAULT_FRAGMENT = Settings.class.getName();
+ private static final String DEFAULT_FRAGMENT = SettingsFragment.class.getName();
@Override
public Intent getIntent() {
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
new file mode 100644
index 000000000..4c90e485a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.app.backup.BackupManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager;
+import com.android.inputmethodcommon.InputMethodSettingsFragment;
+
+public final class SettingsFragment extends InputMethodSettingsFragment
+ implements SharedPreferences.OnSharedPreferenceChangeListener {
+ private ListPreference mVoicePreference;
+ private ListPreference mShowCorrectionSuggestionsPreference;
+ private ListPreference mAutoCorrectionThresholdPreference;
+ private ListPreference mKeyPreviewPopupDismissDelay;
+ // Use bigrams to predict the next word when there is no input for it yet
+ private CheckBoxPreference mBigramPrediction;
+
+ private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) {
+ final Preference preference = findPreference(preferenceKey);
+ if (preference != null) {
+ preference.setEnabled(enabled);
+ }
+ }
+
+ private static void removePreference(final String preferenceKey, final PreferenceGroup parent) {
+ if (parent == null) {
+ return;
+ }
+ final Preference preference = parent.findPreference(preferenceKey);
+ if (preference != null) {
+ parent.removePreference(preference);
+ }
+ }
+
+ @Override
+ public void onCreate(final Bundle icicle) {
+ super.onCreate(icicle);
+ setInputMethodSettingsCategoryTitle(R.string.language_selection_title);
+ setSubtypeEnablerTitle(R.string.select_language);
+ addPreferencesFromResource(R.xml.prefs);
+
+ final Resources res = getResources();
+ final Context context = getActivity();
+
+ // When we are called from the Settings application but we are not already running, the
+ // {@link SubtypeLocale} class may not have been initialized. It is safe to call
+ // {@link SubtypeLocale#init(Context)} multiple times.
+ SubtypeLocale.init(context);
+ mVoicePreference = (ListPreference) findPreference(Settings.PREF_VOICE_MODE);
+ mShowCorrectionSuggestionsPreference =
+ (ListPreference) findPreference(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
+ final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+ prefs.registerOnSharedPreferenceChangeListener(this);
+
+ mAutoCorrectionThresholdPreference =
+ (ListPreference) findPreference(Settings.PREF_AUTO_CORRECTION_THRESHOLD);
+ mBigramPrediction = (CheckBoxPreference) findPreference(Settings.PREF_BIGRAM_PREDICTIONS);
+ ensureConsistencyOfAutoCorrectionSettings();
+
+ final PreferenceGroup generalSettings =
+ (PreferenceGroup) findPreference(Settings.PREF_GENERAL_SETTINGS);
+ final PreferenceGroup miscSettings =
+ (PreferenceGroup) findPreference(Settings.PREF_MISC_SETTINGS);
+
+ final Preference debugSettings = findPreference(Settings.PREF_DEBUG_SETTINGS);
+ if (debugSettings != null) {
+ if (ProductionFlag.IS_INTERNAL) {
+ final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
+ debugSettingsIntent.setClassName(
+ context.getPackageName(), DebugSettingsActivity.class.getName());
+ debugSettings.setIntent(debugSettingsIntent);
+ } else {
+ miscSettings.removePreference(debugSettings);
+ }
+ }
+
+ final boolean showVoiceKeyOption = res.getBoolean(
+ R.bool.config_enable_show_voice_key_option);
+ if (!showVoiceKeyOption) {
+ generalSettings.removePreference(mVoicePreference);
+ }
+
+ final PreferenceGroup advancedSettings =
+ (PreferenceGroup) findPreference(Settings.PREF_ADVANCED_SETTINGS);
+ if (!AudioAndHapticFeedbackManager.getInstance().hasVibrator()) {
+ removePreference(Settings.PREF_VIBRATE_ON, generalSettings);
+ removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedSettings);
+ }
+
+ mKeyPreviewPopupDismissDelay =
+ (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+ if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(res)) {
+ removePreference(Settings.PREF_POPUP_ON, generalSettings);
+ removePreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, advancedSettings);
+ } else {
+ final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
+ R.integer.config_key_preview_linger_timeout));
+ mKeyPreviewPopupDismissDelay.setEntries(new String[] {
+ res.getString(R.string.key_preview_popup_dismiss_no_delay),
+ res.getString(R.string.key_preview_popup_dismiss_default_delay),
+ });
+ mKeyPreviewPopupDismissDelay.setEntryValues(new String[] {
+ "0",
+ popupDismissDelayDefaultValue
+ });
+ if (null == mKeyPreviewPopupDismissDelay.getValue()) {
+ mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
+ }
+ mKeyPreviewPopupDismissDelay.setEnabled(
+ Settings.readKeyPreviewPopupEnabled(prefs, res));
+ }
+
+ 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 =
+ (PreferenceScreen) findPreference(Settings.PREF_CONFIGURE_DICTIONARIES_KEY);
+ final Intent intent = dictionaryLink.getIntent();
+ final int number = context.getPackageManager().queryIntentActivities(intent, 0).size();
+ // TODO: The experimental version is not supported by the Dictionary Pack Service yet
+ if (ProductionFlag.IS_EXPERIMENTAL || 0 >= number) {
+ textCorrectionGroup.removePreference(dictionaryLink);
+ }
+
+ if (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) {
+ removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen());
+ }
+
+ final CheckBoxPreference showSetupWizardIcon =
+ (CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
+ showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, context));
+
+ setupKeyLongpressTimeoutSettings(prefs, res);
+ setupKeypressVibrationDurationSettings(prefs, res);
+ setupKeypressSoundVolumeSettings(prefs, res);
+ refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, res);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
+ if (isShortcutImeEnabled) {
+ updateVoiceModeSummary();
+ } else {
+ getPreferenceScreen().removePreference(mVoicePreference);
+ }
+ updateShowCorrectionSuggestionsSummary();
+ updateKeyPreviewPopupDelaySummary();
+ updateCustomInputStylesSummary();
+ }
+
+ @Override
+ public void onDestroy() {
+ getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
+ this);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
+ (new BackupManager(getActivity())).dataChanged();
+ final Resources res = getResources();
+ 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_GESTURE_INPUT)) {
+ final boolean gestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
+ setPreferenceEnabled(Settings.PREF_GESTURE_PREVIEW_TRAIL, gestureInputEnabled);
+ setPreferenceEnabled(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, gestureInputEnabled);
+ } else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
+ LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
+ }
+ ensureConsistencyOfAutoCorrectionSettings();
+ updateVoiceModeSummary();
+ updateShowCorrectionSuggestionsSummary();
+ updateKeyPreviewPopupDelaySummary();
+ refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
+ }
+
+ private void ensureConsistencyOfAutoCorrectionSettings() {
+ final String autoCorrectionOff = getResources().getString(
+ R.string.auto_correction_threshold_mode_index_off);
+ final String currentSetting = mAutoCorrectionThresholdPreference.getValue();
+ mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
+ }
+
+ private void updateShowCorrectionSuggestionsSummary() {
+ mShowCorrectionSuggestionsPreference.setSummary(
+ getResources().getStringArray(R.array.prefs_suggestion_visibilities)
+ [mShowCorrectionSuggestionsPreference.findIndexOfValue(
+ mShowCorrectionSuggestionsPreference.getValue())]);
+ }
+
+ private void updateCustomInputStylesSummary() {
+ final PreferenceScreen customInputStyles =
+ (PreferenceScreen)findPreference(Settings.PREF_CUSTOM_INPUT_STYLES);
+ final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+ final Resources res = getResources();
+ final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res);
+ final InputMethodSubtype[] subtypes =
+ AdditionalSubtype.createAdditionalSubtypesArray(prefSubtype);
+ final StringBuilder styles = new StringBuilder();
+ for (final InputMethodSubtype subtype : subtypes) {
+ if (styles.length() > 0) styles.append(", ");
+ styles.append(SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype));
+ }
+ customInputStyles.setSummary(styles);
+ }
+
+ private void updateKeyPreviewPopupDelaySummary() {
+ final ListPreference lp = mKeyPreviewPopupDismissDelay;
+ final CharSequence[] entries = lp.getEntries();
+ if (entries == null || entries.length <= 0) return;
+ lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]);
+ }
+
+ private void updateVoiceModeSummary() {
+ mVoicePreference.setSummary(
+ getResources().getStringArray(R.array.voice_input_modes_summary)
+ [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]);
+ }
+
+ private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
+ final SharedPreferences sp, final Resources res) {
+ setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS,
+ Settings.readVibrationEnabled(sp, res));
+ setPreferenceEnabled(Settings.PREF_KEYPRESS_SOUND_VOLUME,
+ Settings.readKeypressSoundEnabled(sp, res));
+ }
+
+ private void setupKeypressVibrationDurationSettings(final SharedPreferences sp,
+ final Resources res) {
+ final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
+ Settings.PREF_VIBRATION_DURATION_SETTINGS);
+ if (pref == null) {
+ return;
+ }
+ pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
+ @Override
+ public void writeValue(final int value, final String key) {
+ sp.edit().putInt(key, value).apply();
+ }
+
+ @Override
+ public int readValue(final String key) {
+ return Settings.readKeypressVibrationDuration(sp, res);
+ }
+
+ @Override
+ public int readDefaultValue(final String key) {
+ return Settings.readDefaultKeypressVibrationDuration(res);
+ }
+
+ @Override
+ public void feedbackValue(final int value) {
+ AudioAndHapticFeedbackManager.getInstance().vibrate(value);
+ }
+ });
+ }
+
+ private void setupKeyLongpressTimeoutSettings(final SharedPreferences sp,
+ final Resources res) {
+ final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
+ Settings.PREF_KEY_LONGPRESS_TIMEOUT);
+ if (pref == null) {
+ return;
+ }
+ pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
+ @Override
+ public void writeValue(final int value, final String key) {
+ sp.edit().putInt(key, value).apply();
+ }
+
+ @Override
+ public int readValue(final String key) {
+ return Settings.readKeyLongpressTimeout(sp, res);
+ }
+
+ @Override
+ public int readDefaultValue(final String key) {
+ return Settings.readDefaultKeyLongpressTimeout(res);
+ }
+
+ @Override
+ public void feedbackValue(final int value) {}
+ });
+ }
+
+ private void setupKeypressSoundVolumeSettings(final SharedPreferences sp, final Resources res) {
+ final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
+ Settings.PREF_KEYPRESS_SOUND_VOLUME);
+ if (pref == null) {
+ return;
+ }
+ final AudioManager am = (AudioManager)getActivity().getSystemService(Context.AUDIO_SERVICE);
+ pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
+ private static final float PERCENTAGE_FLOAT = 100.0f;
+
+ private float getValueFromPercentage(final int percentage) {
+ return percentage / PERCENTAGE_FLOAT;
+ }
+
+ private int getPercentageFromValue(final float floatValue) {
+ return (int)(floatValue * PERCENTAGE_FLOAT);
+ }
+
+ @Override
+ public void writeValue(final int value, final String key) {
+ sp.edit().putFloat(key, getValueFromPercentage(value)).apply();
+ }
+
+ @Override
+ public int readValue(final String key) {
+ return getPercentageFromValue(Settings.readKeypressSoundVolume(sp, res));
+ }
+
+ @Override
+ public int readDefaultValue(final String key) {
+ return getPercentageFromValue(Settings.readDefaultKeypressSoundVolume(res));
+ }
+
+ @Override
+ public void feedbackValue(final int value) {
+ am.playSoundEffect(
+ AudioManager.FX_KEYPRESS_STANDARD, getValueFromPercentage(value));
+ }
+ });
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 2f49fe92e..d05868029 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -1,35 +1,32 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
-import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.keyboard.internal.KeySpecParser;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
/**
* When you call the constructor of this class, you may want to change the current system locale by
@@ -38,26 +35,12 @@ import java.util.HashMap;
public final class SettingsValues {
private static final String TAG = SettingsValues.class.getSimpleName();
- private static final int SUGGESTION_VISIBILITY_SHOW_VALUE
- = R.string.prefs_suggestion_visibility_show_value;
- private static final int SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE
- = R.string.prefs_suggestion_visibility_show_only_portrait_value;
- private static final int SUGGESTION_VISIBILITY_HIDE_VALUE
- = R.string.prefs_suggestion_visibility_hide_value;
-
- private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] {
- SUGGESTION_VISIBILITY_SHOW_VALUE,
- SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE,
- SUGGESTION_VISIBILITY_HIDE_VALUE
- };
-
// From resources:
public final int mDelayUpdateOldSuggestions;
- public final String mWeakSpaceStrippers;
- public final String mWeakSpaceSwappers;
- private final String mPhantomSpacePromotingSymbols;
+ public final int[] mSymbolsPrecededBySpace;
+ public final int[] mSymbolsFollowedBySpace;
+ public final int[] mWordConnectors;
public final SuggestedWords mSuggestPuncList;
- private final String mSymbolsExcludedFromWordSeparators;
public final String mWordSeparators;
public final CharSequence mHintToSaveText;
@@ -67,32 +50,24 @@ public final class SettingsValues {
public final boolean mSoundOn;
public final boolean mKeyPreviewPopupOn;
private final String mVoiceMode;
- private final String mAutoCorrectionThresholdRawValue;
- public final String mShowSuggestionsSetting;
- @SuppressWarnings("unused") // TODO: Use this
- private final boolean mUsabilityStudyMode;
public final boolean mIncludesOtherImesInLanguageSwitchList;
public final boolean mShowsLanguageSwitchKey;
- @SuppressWarnings("unused") // TODO: Use this
- private final String mKeyPreviewPopupDismissDelayRawValue;
public final boolean mUseContactsDict;
+ public final boolean mUseDoubleSpacePeriod;
// Use bigrams to predict the next word when there is no input for it yet
public final boolean mBigramPredictionEnabled;
- @SuppressWarnings("unused") // TODO: Use this
- private final int mVibrationDurationSettingsRawValue;
- @SuppressWarnings("unused") // TODO: Use this
- private final float mKeypressSoundVolumeRawValue;
- private final InputMethodSubtype[] mAdditionalSubtypes;
public final boolean mGestureInputEnabled;
public final boolean mGesturePreviewTrailEnabled;
public final boolean mGestureFloatingPreviewTextEnabled;
+ public final boolean mSlidingKeyInputPreviewEnabled;
+ public final int mKeyLongpressTimeout;
// From the input box
- private final InputAttributes mInputAttributes;
+ public final InputAttributes mInputAttributes;
// Deduced settings
public final int mKeypressVibrationDuration;
- public final float mFxVolume;
+ public final float mKeypressSoundVolume;
public final int mKeyPreviewPopupDismissDelay;
private final boolean mAutoCorrectEnabled;
public final float mAutoCorrectionThreshold;
@@ -101,32 +76,24 @@ public final class SettingsValues {
private final boolean mVoiceKeyEnabled;
private final boolean mVoiceKeyOnMain;
- public SettingsValues(final SharedPreferences prefs, final InputAttributes inputAttributes,
- final Context context) {
- final Resources res = context.getResources();
-
+ public SettingsValues(final SharedPreferences prefs, final Resources res,
+ final InputAttributes inputAttributes) {
// Get the resources
mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions);
- mWeakSpaceStrippers = res.getString(R.string.weak_space_stripping_symbols);
- mWeakSpaceSwappers = res.getString(R.string.weak_space_swapping_symbols);
- mPhantomSpacePromotingSymbols = res.getString(R.string.phantom_space_promoting_symbols);
- if (LatinImeLogger.sDBG) {
- final int length = mWeakSpaceStrippers.length();
- for (int i = 0; i < length; i = mWeakSpaceStrippers.offsetByCodePoints(i, 1)) {
- if (isWeakSpaceSwapper(mWeakSpaceStrippers.codePointAt(i))) {
- throw new RuntimeException("Char code " + mWeakSpaceStrippers.codePointAt(i)
- + " is both a weak space swapper and stripper.");
- }
- }
- }
- final String[] suggestPuncsSpec = KeySpecParser.parseCsvString(
- res.getString(R.string.suggested_punctuations), null);
+ mSymbolsPrecededBySpace =
+ StringUtils.toCodePointArray(res.getString(R.string.symbols_preceded_by_space));
+ Arrays.sort(mSymbolsPrecededBySpace);
+ mSymbolsFollowedBySpace =
+ StringUtils.toCodePointArray(res.getString(R.string.symbols_followed_by_space));
+ Arrays.sort(mSymbolsFollowedBySpace);
+ mWordConnectors =
+ StringUtils.toCodePointArray(res.getString(R.string.symbols_word_connectors));
+ Arrays.sort(mWordConnectors);
+ final String[] suggestPuncsSpec = StringUtils.parseCsvString(res.getString(
+ R.string.suggested_punctuations));
mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
- mSymbolsExcludedFromWordSeparators =
- res.getString(R.string.symbols_excluded_from_word_separators);
- mWordSeparators = createWordSeparators(mWeakSpaceStrippers, mWeakSpaceSwappers,
- mSymbolsExcludedFromWordSeparators, res);
- mHintToSaveText = context.getText(R.string.hint_add_to_dictionary);
+ mWordSeparators = res.getString(R.string.symbols_word_separators);
+ mHintToSaveText = res.getText(R.string.hint_add_to_dictionary);
// Store the input attributes
if (null == inputAttributes) {
@@ -137,97 +104,43 @@ public final class SettingsValues {
// Get the settings preferences
mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
- mVibrateOn = isVibrateOn(context, prefs, res);
- mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
- res.getBoolean(R.bool.config_default_sound_enabled));
- mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res);
+ mVibrateOn = Settings.readVibrationEnabled(prefs, res);
+ mSoundOn = Settings.readKeypressSoundEnabled(prefs, res);
+ mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res);
+ mSlidingKeyInputPreviewEnabled = prefs.getBoolean(
+ Settings.PREF_SLIDING_KEY_INPUT_PREVIEW, true);
final String voiceModeMain = res.getString(R.string.voice_mode_main);
final String voiceModeOff = res.getString(R.string.voice_mode_off);
mVoiceMode = prefs.getString(Settings.PREF_VOICE_MODE, voiceModeMain);
- mAutoCorrectionThresholdRawValue = prefs.getString(Settings.PREF_AUTO_CORRECTION_THRESHOLD,
+ final String autoCorrectionThresholdRawValue = prefs.getString(
+ Settings.PREF_AUTO_CORRECTION_THRESHOLD,
res.getString(R.string.auto_correction_threshold_mode_index_modest));
- mShowSuggestionsSetting = prefs.getString(Settings.PREF_SHOW_SUGGESTIONS_SETTING,
- res.getString(R.string.prefs_suggestion_visibility_default_value));
- mUsabilityStudyMode = getUsabilityStudyMode(prefs);
mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean(
Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false);
- mShowsLanguageSwitchKey = showsLanguageSwitchKey(prefs);
- mKeyPreviewPopupDismissDelayRawValue = prefs.getString(
- Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
- Integer.toString(res.getInteger(R.integer.config_key_preview_linger_timeout)));
+ mShowsLanguageSwitchKey = Settings.readShowsLanguageSwitchKey(prefs);
mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
- mAutoCorrectEnabled = isAutoCorrectEnabled(res, mAutoCorrectionThresholdRawValue);
- mBigramPredictionEnabled = isBigramPredictionEnabled(prefs, res);
- mVibrationDurationSettingsRawValue =
- prefs.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1);
- mKeypressSoundVolumeRawValue = prefs.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
+ mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true);
+ mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(autoCorrectionThresholdRawValue, res);
+ mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res);
// Compute other readable settings
- mKeypressVibrationDuration = getCurrentVibrationDuration(prefs, res);
- mFxVolume = getCurrentKeypressSoundVolume(prefs, res);
- mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res);
- mAutoCorrectionThreshold = getAutoCorrectionThreshold(res,
- mAutoCorrectionThresholdRawValue);
+ mKeyLongpressTimeout = Settings.readKeyLongpressTimeout(prefs, res);
+ mKeypressVibrationDuration = Settings.readKeypressVibrationDuration(prefs, res);
+ mKeypressSoundVolume = Settings.readKeypressSoundVolume(prefs, res);
+ mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res);
+ mAutoCorrectionThreshold = readAutoCorrectionThreshold(res,
+ autoCorrectionThresholdRawValue);
mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff);
mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
- mAdditionalSubtypes = AdditionalSubtype.createAdditionalSubtypesArray(
- getPrefAdditionalSubtypes(prefs, res));
- final boolean gestureInputEnabledByBuildConfig = res.getBoolean(
- R.bool.config_gesture_input_enabled_by_build_config);
- mGestureInputEnabled = gestureInputEnabledByBuildConfig
- && prefs.getBoolean(Settings.PREF_GESTURE_INPUT, true);
+ mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
mGesturePreviewTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
- mSuggestionVisibility = createSuggestionVisibility(res);
- }
-
- // Helper functions to create member values.
- private static SuggestedWords createSuggestPuncList(final String[] puncs) {
- final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList();
- if (puncs != null) {
- for (final String puncSpec : puncs) {
- puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
- SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_HARDCODED,
- Dictionary.TYPE_HARDCODED));
- }
- }
- return new SuggestedWords(puncList,
- false /* typedWordValid */,
- false /* hasAutoCorrectionCandidate */,
- true /* isPunctuationSuggestions */,
- false /* isObsoleteSuggestions */,
- false /* isPrediction */);
- }
-
- private static String createWordSeparators(final String weakSpaceStrippers,
- final String weakSpaceSwappers, final String symbolsExcludedFromWordSeparators,
- final Resources res) {
- String wordSeparators = weakSpaceStrippers + weakSpaceSwappers
- + res.getString(R.string.phantom_space_promoting_symbols);
- for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) {
- wordSeparators = wordSeparators.replace(
- symbolsExcludedFromWordSeparators.substring(i, i + 1), "");
- }
- return wordSeparators;
- }
-
- private int createSuggestionVisibility(final Resources res) {
- final String suggestionVisiblityStr = mShowSuggestionsSetting;
- for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
- if (suggestionVisiblityStr.equals(res.getString(visibility))) {
- return visibility;
- }
- }
- throw new RuntimeException("Bug: visibility string is not configured correctly");
- }
-
- private static boolean isVibrateOn(final Context context, final SharedPreferences prefs,
- final Resources res) {
- final boolean hasVibrator = VibratorUtils.getInstance(context).hasVibrator();
- return hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON,
- res.getBoolean(R.bool.config_default_vibration_enabled));
+ final String showSuggestionsSetting = prefs.getString(
+ Settings.PREF_SHOW_SUGGESTIONS_SETTING,
+ res.getString(R.string.prefs_suggestion_visibility_default_value));
+ mSuggestionVisibility = createSuggestionVisibility(res, showSuggestionsSetting);
}
public boolean isApplicationSpecifiedCompletionsOn() {
@@ -250,87 +163,22 @@ public final class SettingsValues {
return mWordSeparators.contains(String.valueOf((char)code));
}
- public boolean isSymbolExcludedFromWordSeparators(final int code) {
- return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code));
+ public boolean isWordConnector(final int code) {
+ return Arrays.binarySearch(mWordConnectors, code) >= 0;
}
- // TODO: use "Phantom" instead of "Weak" in this method name
- public boolean isWeakSpaceStripper(final int code) {
- // TODO: this does not work if the code does not fit in a char
- return mWeakSpaceStrippers.contains(String.valueOf((char)code));
+ public boolean isUsuallyPrecededBySpace(final int code) {
+ return Arrays.binarySearch(mSymbolsPrecededBySpace, code) >= 0;
}
- // TODO: use "Phantom" instead of "Weak" in this method name
- public boolean isWeakSpaceSwapper(final int code) {
- // TODO: this does not work if the code does not fit in a char
- return mWeakSpaceSwappers.contains(String.valueOf((char)code));
- }
-
- public boolean isPhantomSpacePromotingSymbol(final int code) {
- // TODO: this does not work if the code does not fit in a char
- return mPhantomSpacePromotingSymbols.contains(String.valueOf((char)code));
+ public boolean isUsuallyFollowedBySpace(final int code) {
+ return Arrays.binarySearch(mSymbolsFollowedBySpace, code) >= 0;
}
public boolean shouldInsertSpacesAutomatically() {
return mInputAttributes.mShouldInsertSpacesAutomatically;
}
- private static boolean isAutoCorrectEnabled(final Resources res,
- final String currentAutoCorrectionSetting) {
- final String autoCorrectionOff = res.getString(
- R.string.auto_correction_threshold_mode_index_off);
- return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
- }
-
- // Public to access from KeyboardSwitcher. Should it have access to some
- // process-global instance instead?
- public static boolean isKeyPreviewPopupEnabled(final SharedPreferences prefs,
- final Resources res) {
- final boolean showPopupOption = res.getBoolean(
- R.bool.config_enable_show_popup_on_keypress_option);
- if (!showPopupOption) return res.getBoolean(R.bool.config_default_popup_preview);
- return prefs.getBoolean(Settings.PREF_POPUP_ON,
- res.getBoolean(R.bool.config_default_popup_preview));
- }
-
- // Likewise
- public static int getKeyPreviewPopupDismissDelay(final SharedPreferences prefs,
- final Resources res) {
- // TODO: use mKeyPreviewPopupDismissDelayRawValue instead of reading it again here.
- return Integer.parseInt(prefs.getString(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
- Integer.toString(res.getInteger(
- R.integer.config_key_preview_linger_timeout))));
- }
-
- private static boolean isBigramPredictionEnabled(final SharedPreferences prefs,
- final Resources res) {
- return prefs.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, res.getBoolean(
- R.bool.config_default_next_word_prediction));
- }
-
- private static float getAutoCorrectionThreshold(final Resources res,
- final String currentAutoCorrectionSetting) {
- final String[] autoCorrectionThresholdValues = res.getStringArray(
- R.array.auto_correction_threshold_values);
- // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off.
- float autoCorrectionThreshold = Float.MAX_VALUE;
- try {
- final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
- if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
- autoCorrectionThreshold = Float.parseFloat(
- autoCorrectionThresholdValues[arrayIndex]);
- }
- } catch (NumberFormatException e) {
- // Whenever the threshold settings are correct, never come here.
- autoCorrectionThreshold = Float.MAX_VALUE;
- Log.w(TAG, "Cannot load auto correction threshold setting."
- + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
- + ", autoCorrectionThresholdValues: "
- + Arrays.toString(autoCorrectionThresholdValues));
- }
- return autoCorrectionThreshold;
- }
-
public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) {
final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
@@ -342,108 +190,89 @@ public final class SettingsValues {
return mVoiceKeyOnMain;
}
- // This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead.
- // This is being used only for the backward compatibility.
- private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
- "pref_suppress_language_switch_key";
-
- public static boolean showsLanguageSwitchKey(final SharedPreferences prefs) {
- if (prefs.contains(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY)) {
- final boolean suppressLanguageSwitchKey = prefs.getBoolean(
- PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false);
- final SharedPreferences.Editor editor = prefs.edit();
- editor.remove(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY);
- editor.putBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, !suppressLanguageSwitchKey);
- editor.apply();
- }
- return prefs.getBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, true);
- }
-
- public boolean isLanguageSwitchKeyEnabled(final Context context) {
+ public boolean isLanguageSwitchKeyEnabled() {
if (!mShowsLanguageSwitchKey) {
return false;
}
+ final RichInputMethodManager imm = RichInputMethodManager.getInstance();
if (mIncludesOtherImesInLanguageSwitchList) {
- return ImfUtils.hasMultipleEnabledIMEsOrSubtypes(
- context, /* include aux subtypes */false);
+ return imm.hasMultipleEnabledIMEsOrSubtypes(false /* include aux subtypes */);
} else {
- return ImfUtils.hasMultipleEnabledSubtypesInThisIme(
- context, /* include aux subtypes */false);
+ return imm.hasMultipleEnabledSubtypesInThisIme(false /* include aux subtypes */);
}
}
- public static boolean isFullscreenModeAllowed(final Resources res) {
- return res.getBoolean(R.bool.config_use_fullscreen_mode);
+ public boolean isSameInputType(final EditorInfo editorInfo) {
+ return mInputAttributes.isSameInputType(editorInfo);
}
- public InputMethodSubtype[] getAdditionalSubtypes() {
- return mAdditionalSubtypes;
+ // Helper functions to create member values.
+ private static SuggestedWords createSuggestPuncList(final String[] puncs) {
+ final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList();
+ if (puncs != null) {
+ for (final String puncSpec : puncs) {
+ // TODO: Stop using KeySpceParser.getLabel().
+ puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
+ SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_HARDCODED,
+ Dictionary.TYPE_HARDCODED));
+ }
+ }
+ return new SuggestedWords(puncList,
+ false /* typedWordValid */,
+ false /* hasAutoCorrectionCandidate */,
+ true /* isPunctuationSuggestions */,
+ false /* isObsoleteSuggestions */,
+ false /* isPrediction */);
}
- public static String getPrefAdditionalSubtypes(final SharedPreferences prefs,
- final Resources res) {
- final String predefinedPrefSubtypes = AdditionalSubtype.createPrefSubtypes(
- res.getStringArray(R.array.predefined_subtypes));
- return prefs.getString(Settings.PREF_CUSTOM_INPUT_STYLES, predefinedPrefSubtypes);
- }
+ private static final int SUGGESTION_VISIBILITY_SHOW_VALUE =
+ R.string.prefs_suggestion_visibility_show_value;
+ private static final int SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE =
+ R.string.prefs_suggestion_visibility_show_only_portrait_value;
+ private static final int SUGGESTION_VISIBILITY_HIDE_VALUE =
+ R.string.prefs_suggestion_visibility_hide_value;
+ private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] {
+ SUGGESTION_VISIBILITY_SHOW_VALUE,
+ SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE,
+ SUGGESTION_VISIBILITY_HIDE_VALUE
+ };
- // Accessed from the settings interface, hence public
- public static float getCurrentKeypressSoundVolume(final SharedPreferences prefs,
- final Resources res) {
- // TODO: use mVibrationDurationSettingsRawValue instead of reading it again here
- final float volume = prefs.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
- if (volume >= 0) {
- return volume;
+ private static int createSuggestionVisibility(final Resources res,
+ final String suggestionVisiblityStr) {
+ for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
+ if (suggestionVisiblityStr.equals(res.getString(visibility))) {
+ return visibility;
+ }
}
-
- return Float.parseFloat(ResourceUtils.getDeviceOverrideValue(
- res, R.array.keypress_volumes, "-1.0f"));
+ throw new RuntimeException("Bug: visibility string is not configured correctly");
}
- // Likewise
- public static int getCurrentVibrationDuration(final SharedPreferences prefs,
+ private static boolean readBigramPredictionEnabled(final SharedPreferences prefs,
final Resources res) {
- // TODO: use mKeypressVibrationDuration instead of reading it again here
- final int ms = prefs.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1);
- if (ms >= 0) {
- return ms;
- }
-
- return Integer.parseInt(ResourceUtils.getDeviceOverrideValue(
- res, R.array.keypress_vibration_durations, "-1"));
- }
-
- // Likewise
- public static boolean getUsabilityStudyMode(final SharedPreferences prefs) {
- // TODO: use mUsabilityStudyMode instead of reading it again here
- return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true);
+ return prefs.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, res.getBoolean(
+ R.bool.config_default_next_word_prediction));
}
- public static long getLastUserHistoryWriteTime(final SharedPreferences prefs,
- final String locale) {
- final String str = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(str);
- if (map.containsKey(locale)) {
- return map.get(locale);
+ private static float readAutoCorrectionThreshold(final Resources res,
+ final String currentAutoCorrectionSetting) {
+ final String[] autoCorrectionThresholdValues = res.getStringArray(
+ R.array.auto_correction_threshold_values);
+ // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off.
+ float autoCorrectionThreshold = Float.MAX_VALUE;
+ try {
+ final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
+ if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
+ autoCorrectionThreshold = Float.parseFloat(
+ autoCorrectionThresholdValues[arrayIndex]);
+ }
+ } catch (NumberFormatException e) {
+ // Whenever the threshold settings are correct, never come here.
+ autoCorrectionThreshold = Float.MAX_VALUE;
+ Log.w(TAG, "Cannot load auto correction threshold setting."
+ + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
+ + ", autoCorrectionThresholdValues: "
+ + Arrays.toString(autoCorrectionThresholdValues));
}
- return 0;
- }
-
- public static void setLastUserHistoryWriteTime(final SharedPreferences prefs,
- final String locale) {
- final String oldStr = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(oldStr);
- map.put(locale, System.currentTimeMillis());
- final String newStr = LocaleUtils.localeAndTimeHashMapToStr(map);
- prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
- }
-
- public boolean isSameInputType(final EditorInfo editorInfo) {
- return mInputAttributes.isSameInputType(editorInfo);
- }
-
- // For debug.
- public String getInputAttributesDebugString() {
- return mInputAttributes.toString();
+ return autoCorrectionThreshold;
}
}
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index df7709892..90c3fcdd2 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -18,8 +18,6 @@ package com.android.inputmethod.latin;
import android.text.TextUtils;
-import com.android.inputmethod.keyboard.Keyboard; // For character constants
-
import java.util.ArrayList;
import java.util.Locale;
@@ -28,30 +26,30 @@ public final class StringUtils {
// This utility class is not publicly instantiable.
}
- public static int codePointCount(String text) {
+ public static int codePointCount(final String text) {
if (TextUtils.isEmpty(text)) return 0;
return text.codePointCount(0, text.length());
}
- public static boolean containsInArray(String key, String[] array) {
+ public static boolean containsInArray(final String key, final String[] array) {
for (final String element : array) {
if (key.equals(element)) return true;
}
return false;
}
- public static boolean containsInCsv(String key, String csv) {
+ public static boolean containsInCsv(final String key, final String csv) {
if (TextUtils.isEmpty(csv)) return false;
return containsInArray(key, csv.split(","));
}
- public static String appendToCsvIfNotExists(String key, String csv) {
+ public static String appendToCsvIfNotExists(final String key, final String csv) {
if (TextUtils.isEmpty(csv)) return key;
if (containsInCsv(key, csv)) return csv;
return csv + "," + key;
}
- public static String removeFromCsvIfExists(String key, String csv) {
+ public static String removeFromCsvIfExists(final String key, final String csv) {
if (TextUtils.isEmpty(csv)) return "";
final String[] elements = csv.split(",");
if (!containsInArray(key, elements)) return csv;
@@ -63,65 +61,20 @@ public final class StringUtils {
}
/**
- * Returns true if a and b are equal ignoring the case of the character.
- * @param a first character to check
- * @param b second character to check
- * @return {@code true} if a and b are equal, {@code false} otherwise.
- */
- public static boolean equalsIgnoreCase(char a, char b) {
- // Some language, such as Turkish, need testing both cases.
- return a == b
- || Character.toLowerCase(a) == Character.toLowerCase(b)
- || Character.toUpperCase(a) == Character.toUpperCase(b);
- }
-
- /**
- * Returns true if a and b are equal ignoring the case of the characters, including if they are
- * both null.
- * @param a first CharSequence to check
- * @param b second CharSequence to check
- * @return {@code true} if a and b are equal, {@code false} otherwise.
- */
- public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
- if (a == b)
- return true; // including both a and b are null.
- if (a == null || b == null)
- return false;
- final int length = a.length();
- if (length != b.length())
- return false;
- for (int i = 0; i < length; i++) {
- if (!equalsIgnoreCase(a.charAt(i), b.charAt(i)))
- return false;
- }
- return true;
- }
-
- /**
- * Returns true if a and b are equal ignoring the case of the characters, including if a is null
- * and b is zero length.
- * @param a CharSequence to check
- * @param b character array to check
- * @param offset start offset of array b
- * @param length length of characters in array b
- * @return {@code true} if a and b are equal, {@code false} otherwise.
- * @throws IndexOutOfBoundsException
- * if {@code offset < 0 || length < 0 || offset + length > data.length}.
- * @throws NullPointerException if {@code b == null}.
+ * Find a string that start with specified prefix from an array.
+ *
+ * @param prefix a prefix string to find.
+ * @param array an string array to be searched.
+ * @return the rest part of the string that starts with the prefix.
+ * Returns null if it couldn't be found.
*/
- public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) {
- if (offset < 0 || length < 0 || length > b.length - offset)
- throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset
- + " length=" + length);
- if (a == null)
- return length == 0; // including a is null and b is zero length.
- if (a.length() != length)
- return false;
- for (int i = 0; i < length; i++) {
- if (!equalsIgnoreCase(a.charAt(i), b[offset + i]))
- return false;
+ public static String findPrefixedString(final String prefix, final String[] array) {
+ for (final String element : array) {
+ if (element.startsWith(prefix)) {
+ return element.substring(prefix.length());
+ }
}
- return true;
+ return null;
}
/**
@@ -130,15 +83,15 @@ public final class StringUtils {
* This method will always keep the first occurrence of all strings at their position
* in the array, removing the subsequent ones.
*/
- public static void removeDupes(final ArrayList<CharSequence> suggestions) {
+ public static void removeDupes(final ArrayList<String> suggestions) {
if (suggestions.size() < 2) return;
int i = 1;
// Don't cache suggestions.size(), since we may be removing items
while (i < suggestions.size()) {
- final CharSequence cur = suggestions.get(i);
+ final String cur = suggestions.get(i);
// Compare each suggestion with each previous suggestion
for (int j = 0; j < i; j++) {
- CharSequence previous = suggestions.get(j);
+ final String previous = suggestions.get(j);
if (TextUtils.equals(cur, previous)) {
suggestions.remove(i);
i--;
@@ -149,7 +102,7 @@ public final class StringUtils {
}
}
- public static String toTitleCase(String s, Locale locale) {
+ public static String toTitleCase(final String s, final Locale locale) {
if (s.length() <= 1) {
// TODO: is this really correct? Shouldn't this be s.toUpperCase()?
return s;
@@ -165,228 +118,57 @@ public final class StringUtils {
return s.toUpperCase(locale).charAt(0) + s.substring(1);
}
+ private static final int[] EMPTY_CODEPOINTS = {};
+
public static int[] toCodePointArray(final String string) {
- final char[] characters = string.toCharArray();
- final int length = characters.length;
- final int[] codePoints = new int[Character.codePointCount(characters, 0, length)];
+ final int length = string.length();
if (length <= 0) {
- return new int[0];
+ return EMPTY_CODEPOINTS;
}
- int codePoint = Character.codePointAt(characters, 0);
- int dsti = 0;
- for (int srci = Character.charCount(codePoint);
- srci < length; srci += Character.charCount(codePoint), ++dsti) {
- codePoints[dsti] = codePoint;
- codePoint = Character.codePointAt(characters, srci);
+ final int[] codePoints = new int[string.codePointCount(0, length)];
+ int destIndex = 0;
+ for (int index = 0; index < length; index = string.offsetByCodePoints(index, 1)) {
+ codePoints[destIndex] = string.codePointAt(index);
+ destIndex++;
}
- codePoints[dsti] = codePoint;
return codePoints;
}
- /**
- * 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
- * to match those in {@link InputType}.
- *
- * This code is a straight copy of TextUtils.getCapsMode (modulo namespace and formatting
- * issues). This will change in the future as we simplify the code for our use and fix bugs.
- *
- * @param cs The text that should be checked for caps modes.
- * @param reqModes The modes to be checked: may be any combination of
- * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
- * {@link TextUtils#CAP_MODE_SENTENCES}.
- * @param locale The locale to consider for capitalization rules
- * @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs
- *
- * @return Returns the actual capitalization modes that can be in effect
- * at the current position, which is any combination of
- * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
- * {@link TextUtils#CAP_MODE_SENTENCES}.
- */
- public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale,
- final boolean hasSpaceBefore) {
- // Quick description of what we want to do:
- // CAP_MODE_CHARACTERS is always on.
- // CAP_MODE_WORDS is on if there is some whitespace before the cursor.
- // CAP_MODE_SENTENCES is on if there is some whitespace before the cursor, and the end
- // of a sentence just before that.
- // We ignore opening parentheses and the like just before the cursor for purposes of
- // finding whitespace for WORDS and SENTENCES modes.
- // The end of a sentence ends with a period, question mark or exclamation mark. If it's
- // a period, it also needs not to be an abbreviation, which means it also needs to either
- // be immediately preceded by punctuation, or by a string of only letters with single
- // periods interleaved.
-
- // Step 1 : check for cap MODE_CHARACTERS. If it's looked for, it's always on.
- if ((reqModes & (TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES)) == 0) {
- // Here we are not looking for MODE_WORDS or MODE_SENTENCES, so since we already
- // evaluated MODE_CHARACTERS, we can return.
- return TextUtils.CAP_MODE_CHARACTERS & reqModes;
- }
-
- // Step 2 : Skip (ignore at the end of input) any opening punctuation. This includes
- // opening parentheses, brackets, opening quotes, everything that *opens* a span of
- // text in the linguistic sense. In RTL languages, this is still an opening sign, although
- // it may look like a right parenthesis for example. We also include double quote and
- // single quote since they aren't start punctuation in the unicode sense, but should still
- // be skipped for English. TODO: does this depend on the language?
- int i;
- if (hasSpaceBefore) {
- i = cs.length() + 1;
- } else {
- for (i = cs.length(); i > 0; i--) {
- final char c = cs.charAt(i - 1);
- if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE
- && Character.getType(c) != Character.START_PUNCTUATION) {
- break;
- }
- }
- }
-
- // We are now on the character that precedes any starting punctuation, so in the most
- // frequent case this will be whitespace or a letter, although it may occasionally be a
- // start of line, or some symbol.
-
- // Step 3 : Search for the start of a paragraph. From the starting point computed in step 2,
- // we go back over any space or tab char sitting there. We find the start of a paragraph
- // if the first char that's not a space or tab is a start of line (as in \n, start of text,
- // or some other similar characters).
- int j = i;
- char prevChar = Keyboard.CODE_SPACE;
- if (hasSpaceBefore) --j;
- while (j > 0) {
- prevChar = cs.charAt(j - 1);
- if (!Character.isSpaceChar(prevChar) && prevChar != Keyboard.CODE_TAB) break;
- j--;
- }
- if (j <= 0 || Character.isWhitespace(prevChar)) {
- // There are only spacing chars between the start of the paragraph and the cursor,
- // defined as a isWhitespace() char that is neither a isSpaceChar() nor a tab. Both
- // MODE_WORDS and MODE_SENTENCES should be active.
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
- | TextUtils.CAP_MODE_SENTENCES) & reqModes;
- }
- if (i == j) {
- // If we don't have whitespace before index i, it means neither MODE_WORDS
- // nor mode sentences should be on so we can return right away.
- return TextUtils.CAP_MODE_CHARACTERS & reqModes;
- }
- if ((reqModes & TextUtils.CAP_MODE_SENTENCES) == 0) {
- // Here we know we have whitespace before the cursor (if not, we returned in the above
- // if i == j clause), so we need MODE_WORDS to be on. And we don't need to evaluate
- // MODE_SENTENCES so we can return right away.
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
- }
- // Please note that because of the reqModes & CAP_MODE_SENTENCES test a few lines above,
- // we know that MODE_SENTENCES is being requested.
-
- // Step 4 : Search for MODE_SENTENCES.
- // English is a special case in that "American typography" rules, which are the most common
- // in English, state that a sentence terminator immediately following a quotation mark
- // should be swapped with it and de-duplicated (included in the quotation mark),
- // e.g. <<Did he say, "let's go home?">>
- // No other language has such a rule as far as I know, instead putting inside the quotation
- // mark as the exact thing quoted and handling the surrounding punctuation independently,
- // e.g. <<Did he say, "let's go home"?>>
- // Hence, specifically for English, we treat this special case here.
- if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) {
- for (; j > 0; j--) {
- // Here we look to go over any closing punctuation. This is because in dominant
- // variants of English, the final period is placed within double quotes and maybe
- // other closing punctuation signs. This is generally not true in other languages.
- final char c = cs.charAt(j - 1);
- if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE
- && Character.getType(c) != Character.END_PUNCTUATION) {
- break;
+ public static String[] parseCsvString(final String text) {
+ final int size = text.length();
+ if (size == 0) {
+ return null;
+ }
+ if (codePointCount(text) == 1) {
+ return text.codePointAt(0) == Constants.CSV_SEPARATOR ? null : new String[] { text };
+ }
+
+ ArrayList<String> list = null;
+ int start = 0;
+ for (int pos = 0; pos < size; pos++) {
+ final char c = text.charAt(pos);
+ if (c == Constants.CSV_SEPARATOR) {
+ // Skip empty entry.
+ if (pos - start > 0) {
+ if (list == null) {
+ list = CollectionUtils.newArrayList();
+ }
+ list.add(text.substring(start, pos));
}
+ // Skip comma
+ start = pos + 1;
+ } else if (c == Constants.CSV_ESCAPE) {
+ // Skip escape character and escaped character.
+ pos++;
}
}
-
- if (j <= 0) return TextUtils.CAP_MODE_CHARACTERS & reqModes;
- char c = cs.charAt(--j);
-
- // We found the next interesting chunk of text ; next we need to determine if it's the
- // end of a sentence. If we have a question mark or an exclamation mark, it's the end of
- // a sentence. If it's neither, the only remaining case is the period so we get the opposite
- // case out of the way.
- if (c == Keyboard.CODE_QUESTION_MARK || c == Keyboard.CODE_EXCLAMATION_MARK) {
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
- }
- if (c != Keyboard.CODE_PERIOD || j <= 0) {
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ final String remain = (size - start > 0) ? text.substring(start) : null;
+ if (list == null) {
+ return remain != null ? new String[] { remain } : null;
}
-
- // 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,}
- // To find out, we will have a simple state machine with the following states :
- // START, WORD, PERIOD, ABBREVIATION
- // On START : (just before the first period)
- // letter => WORD
- // 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)
- // letter => WORD
- // period => PERIOD
- // otherwise => end with caps (it was a word with a full stop at the end)
- // On PERIOD : (period within a potential abbreviation)
- // letter => LETTER
- // otherwise => end with caps (it was not an abbreviation)
- // On LETTER : (letter within a potential abbreviation)
- // letter => LETTER
- // period => PERIOD
- // otherwise => end with no caps (it was an abbreviation)
- // "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This
- // should capitalize.
-
- final int START = 0;
- final int WORD = 1;
- final int PERIOD = 2;
- final int LETTER = 3;
- 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;
- int state = START;
- while (j > 0) {
- c = cs.charAt(--j);
- switch (state) {
- case START:
- if (Character.isLetter(c)) {
- state = WORD;
- } else if (Character.isWhitespace(c)) {
- return noCaps;
- } else {
- return caps;
- }
- break;
- case WORD:
- if (Character.isLetter(c)) {
- state = WORD;
- } else if (c == Keyboard.CODE_PERIOD) {
- state = PERIOD;
- } else {
- return caps;
- }
- break;
- case PERIOD:
- if (Character.isLetter(c)) {
- state = LETTER;
- } else {
- return caps;
- }
- break;
- case LETTER:
- if (Character.isLetter(c)) {
- state = LETTER;
- } else if (c == Keyboard.CODE_PERIOD) {
- state = PERIOD;
- } else {
- return noCaps;
- }
- }
+ if (remain != null) {
+ list.add(remain);
}
- // Here we arrived at the start of the line. This should behave exactly like whitespace.
- return (START == state || LETTER == state) ? noCaps : caps;
+ return list.toArray(new String[list.size()]);
}
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 579f96bb4..9cbfe6698 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -42,6 +42,7 @@ public final class SubtypeLocale {
public static final int UNKNOWN_KEYBOARD_LAYOUT = R.string.subtype_generic;
private static boolean sInitialized = false;
+ private static Resources sResources;
private static String[] sPredefinedKeyboardLayoutSet;
// Keyboard layout to its display name map.
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
@@ -50,20 +51,22 @@ public final class SubtypeLocale {
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
CollectionUtils.newHashMap();
// Exceptional locale to subtype name resource id map.
+ private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap =
+ CollectionUtils.newHashMap();
+ // Exceptional locale to subtype name with layout resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
CollectionUtils.newHashMap();
+ private static final String SUBTYPE_NAME_RESOURCE_PREFIX =
+ "string/subtype_";
private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
"string/subtype_generic_";
private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX =
"string/subtype_with_layout_";
private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX =
"string/subtype_no_language_";
- // Exceptional locales to display name map.
- private static final HashMap<String, String> sExceptionalDisplayNamesMap =
- CollectionUtils.newHashMap();
// 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 =
+ private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
CollectionUtils.newHashMap();
private SubtypeLocale() {
@@ -71,10 +74,11 @@ public final class SubtypeLocale {
}
// Note that this initialization method can be called multiple times.
- public static synchronized void init(Context context) {
+ public static synchronized void init(final Context context) {
if (sInitialized) return;
final Resources res = context.getResources();
+ sResources = res;
final String[] predefinedLayoutSet = res.getStringArray(R.array.predefined_layouts);
sPredefinedKeyboardLayoutSet = predefinedLayoutSet;
@@ -96,14 +100,16 @@ public final class SubtypeLocale {
final String[] exceptionalLocales = res.getStringArray(
R.array.subtype_locale_exception_keys);
- final String[] exceptionalDisplayNames = res.getStringArray(
- R.array.subtype_locale_exception_values);
for (int i = 0; i < exceptionalLocales.length; i++) {
final String localeString = exceptionalLocales[i];
- sExceptionalDisplayNamesMap.put(localeString, exceptionalDisplayNames[i]);
- final String resourceName = SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + localeString;
+ final String resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + localeString;
final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME);
- sExceptionalLocaleToWithLayoutNameIdsMap.put(localeString, resId);
+ sExceptionalLocaleToNameIdsMap.put(localeString, resId);
+ final String resourceNameWithLayout =
+ SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + localeString;
+ final int resIdWithLayout = res.getIdentifier(
+ resourceNameWithLayout, null, RESOURCE_PACKAGE_NAME);
+ sExceptionalLocaleToWithLayoutNameIdsMap.put(localeString, resIdWithLayout);
}
final String[] keyboardLayoutSetMap = res.getStringArray(
@@ -121,64 +127,115 @@ public final class SubtypeLocale {
return sPredefinedKeyboardLayoutSet;
}
- public static boolean isExceptionalLocale(String localeString) {
- return sExceptionalLocaleToWithLayoutNameIdsMap.containsKey(localeString);
+ public static boolean isExceptionalLocale(final String localeString) {
+ return sExceptionalLocaleToNameIdsMap.containsKey(localeString);
}
- private static final String getNoLanguageLayoutKey(String keyboardLayoutName) {
+ private static final String getNoLanguageLayoutKey(final String keyboardLayoutName) {
return NO_LANGUAGE + "_" + keyboardLayoutName;
}
- public static int getSubtypeNameId(String localeString, String keyboardLayoutName) {
- if (Build.VERSION.SDK_INT >= /* JELLY_BEAN */ 15 && isExceptionalLocale(localeString)) {
+ public static int getSubtypeNameId(final String localeString, final String keyboardLayoutName) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
+ && isExceptionalLocale(localeString)) {
return sExceptionalLocaleToWithLayoutNameIdsMap.get(localeString);
}
- final String key = localeString.equals(NO_LANGUAGE)
+ final String key = NO_LANGUAGE.equals(localeString)
? getNoLanguageLayoutKey(keyboardLayoutName)
: keyboardLayoutName;
final Integer nameId = sKeyboardLayoutToNameIdsMap.get(key);
return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId;
}
- public static String getSubtypeLocaleDisplayName(String localeString) {
- final String exceptionalValue = sExceptionalDisplayNamesMap.get(localeString);
- if (exceptionalValue != null) {
- return exceptionalValue;
+ private static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) {
+ if (NO_LANGUAGE.equals(localeString)) {
+ return sResources.getConfiguration().locale;
}
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
- return StringUtils.toTitleCase(locale.getDisplayName(locale), locale);
+ return LocaleUtils.constructLocaleFromString(localeString);
+ }
+
+ public static String getSubtypeLocaleDisplayNameInSystemLocale(final String localeString) {
+ final Locale displayLocale = sResources.getConfiguration().locale;
+ return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
+ }
+
+ public static String getSubtypeLocaleDisplayName(final String localeString) {
+ final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
+ return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
+ }
+
+ private static String getSubtypeLocaleDisplayNameInternal(final String localeString,
+ final Locale displayLocale) {
+ final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
+ final String displayName;
+ if (exceptionalNameResId != null) {
+ final RunInLocale<String> getExceptionalName = new RunInLocale<String>() {
+ @Override
+ protected String job(final Resources res) {
+ return res.getString(exceptionalNameResId);
+ }
+ };
+ displayName = getExceptionalName.runInLocale(sResources, displayLocale);
+ } else if (NO_LANGUAGE.equals(localeString)) {
+ // No language subtype should be displayed in system locale.
+ return sResources.getString(R.string.subtype_no_language);
+ } else {
+ final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+ displayName = locale.getDisplayName(displayLocale);
+ }
+ return StringUtils.toTitleCase(displayName, displayLocale);
}
// InputMethodSubtype's display name in its locale.
// isAdditionalSubtype (T=true, F=false)
- // locale layout | display name
- // ------ ------ - ----------------------
- // en_US qwerty F English (US) exception
- // en_GB qwerty F English (UK) exception
- // fr azerty F Français
- // fr_CA qwerty F Français (Canada)
- // de qwertz F Deutsch
- // zz qwerty F No language (QWERTY) in system locale
- // fr qwertz T Français (QWERTZ)
- // de qwerty T Deutsch (QWERTY)
- // en_US azerty T English (US) (AZERTY)
- // zz azerty T No language (AZERTY) in system locale
-
- public static String getSubtypeDisplayName(final InputMethodSubtype subtype, Resources res) {
- final String replacementString = (Build.VERSION.SDK_INT >= /* JELLY_BEAN */ 15
- && subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME))
- ? subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)
- : getSubtypeLocaleDisplayName(subtype.getLocale());
+ // locale layout | display name
+ // ------ ------- - ----------------------
+ // en_US qwerty F English (US) exception
+ // en_GB qwerty F English (UK) exception
+ // es_US spanish F Español (EE.UU.) exception
+ // fr azerty F Français
+ // fr_CA qwerty F Français (Canada)
+ // de qwertz F Deutsch
+ // zz qwerty F No language (QWERTY) in system locale
+ // fr qwertz T Français (QWERTZ)
+ // de qwerty T Deutsch (QWERTY)
+ // en_US azerty T English (US) (AZERTY) exception
+ // zz azerty T No language (AZERTY) in system locale
+
+ private static String getReplacementString(final InputMethodSubtype subtype,
+ final Locale displayLocale) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
+ && subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
+ return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME);
+ } else {
+ return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale);
+ }
+ }
+
+ public static String getSubtypeDisplayNameInSystemLocale(final InputMethodSubtype subtype) {
+ final Locale displayLocale = sResources.getConfiguration().locale;
+ return getSubtypeDisplayNameInternal(subtype, displayLocale);
+ }
+
+ public static String getSubtypeDisplayName(final InputMethodSubtype subtype) {
+ final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(subtype.getLocale());
+ return getSubtypeDisplayNameInternal(subtype, displayLocale);
+ }
+
+ private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype,
+ final Locale displayLocale) {
+ final String replacementString = getReplacementString(subtype, displayLocale);
final int nameResId = subtype.getNameResId();
final RunInLocale<String> getSubtypeName = new RunInLocale<String>() {
@Override
- protected String job(Resources res) {
+ protected String job(final Resources res) {
try {
return res.getString(nameResId, replacementString);
} catch (Resources.NotFoundException e) {
// TODO: Remove this catch when InputMethodManager.getCurrentInputMethodSubtype
// is fixed.
Log.w(TAG, "Unknown subtype: mode=" + subtype.getMode()
+ + " nameResId=" + subtype.getNameResId()
+ " locale=" + subtype.getLocale()
+ " extra=" + subtype.getExtraValue()
+ "\n" + Utils.getStackTrace());
@@ -186,31 +243,30 @@ public final class SubtypeLocale {
}
}
};
- final Locale locale = isNoLanguage(subtype)
- ? res.getConfiguration().locale : getSubtypeLocale(subtype);
- return getSubtypeName.runInLocale(res, locale);
+ return StringUtils.toTitleCase(
+ getSubtypeName.runInLocale(sResources, displayLocale), displayLocale);
}
- public static boolean isNoLanguage(InputMethodSubtype subtype) {
+ public static boolean isNoLanguage(final InputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
- return localeString.equals(NO_LANGUAGE);
+ return NO_LANGUAGE.equals(localeString);
}
- public static Locale getSubtypeLocale(InputMethodSubtype subtype) {
+ public static Locale getSubtypeLocale(final InputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
return LocaleUtils.constructLocaleFromString(localeString);
}
- public static String getKeyboardLayoutSetDisplayName(InputMethodSubtype subtype) {
+ public static String getKeyboardLayoutSetDisplayName(final InputMethodSubtype subtype) {
final String layoutName = getKeyboardLayoutSetName(subtype);
return getKeyboardLayoutSetDisplayName(layoutName);
}
- public static String getKeyboardLayoutSetDisplayName(String layoutName) {
+ public static String getKeyboardLayoutSetDisplayName(final String layoutName) {
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
}
- public static String getKeyboardLayoutSetName(InputMethodSubtype subtype) {
+ public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) {
String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET);
if (keyboardLayoutSet == null) {
// This subtype doesn't have a keyboardLayoutSet extra value, so lookup its keyboard
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 8e51a372b..2f9e34ff1 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -20,7 +20,6 @@ import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.REQ_NET
import android.content.Context;
import android.content.Intent;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.net.ConnectivityManager;
@@ -32,6 +31,7 @@ import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import java.util.List;
@@ -43,7 +43,7 @@ public final class SubtypeSwitcher {
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
- private /* final */ InputMethodManager mImm;
+ private /* final */ RichInputMethodManager mRichImm;
private /* final */ Resources mResources;
private /* final */ ConnectivityManager mConnectivityManager;
@@ -53,9 +53,6 @@ public final class SubtypeSwitcher {
private InputMethodInfo mShortcutInputMethodInfo;
private InputMethodSubtype mShortcutSubtype;
private InputMethodSubtype mNoLanguageSubtype;
- // Note: This variable is always non-null after {@link #initialize(LatinIME)}.
- private InputMethodSubtype mCurrentSubtype;
- private Locale mCurrentSystemLocale;
/*-----------------------------------------------------------*/
private boolean mIsNetworkConnected;
@@ -84,7 +81,6 @@ public final class SubtypeSwitcher {
public static void init(final Context context) {
SubtypeLocale.init(context);
sInstance.initialize(context);
- sInstance.updateAllParameters(context);
}
private SubtypeSwitcher() {
@@ -93,63 +89,31 @@ public final class SubtypeSwitcher {
private void initialize(final Context service) {
mResources = service.getResources();
- mImm = ImfUtils.getInputMethodManager(service);
+ mRichImm = RichInputMethodManager.getInstance();
mConnectivityManager = (ConnectivityManager) service.getSystemService(
Context.CONNECTIVITY_SERVICE);
- mCurrentSystemLocale = mResources.getConfiguration().locale;
- mNoLanguageSubtype = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet(
- service, SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY);
- mCurrentSubtype = ImfUtils.getCurrentInputMethodSubtype(service, mNoLanguageSubtype);
+ mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+ SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY);
if (mNoLanguageSubtype == null) {
throw new RuntimeException("Can't find no lanugage with QWERTY subtype");
}
final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
mIsNetworkConnected = (info != null && info.isConnected());
- }
- // Update all parameters stored in SubtypeSwitcher.
- // Only configuration changed event is allowed to call this because this is heavy.
- private void updateAllParameters(final Context context) {
- mCurrentSystemLocale = mResources.getConfiguration().locale;
- updateSubtype(ImfUtils.getCurrentInputMethodSubtype(context, mNoLanguageSubtype));
- updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
+ onSubtypeChanged(getCurrentSubtype());
+ updateParametersOnStartInputView();
}
/**
- * Update parameters which are changed outside LatinIME. This parameters affect UI so they
- * should be updated every time onStartInputView.
- *
- * @return true if the current subtype is enabled.
+ * Update parameters which are changed outside LatinIME. This parameters affect UI so that they
+ * should be updated every time onStartInputView is called.
*/
- public boolean updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled() {
- final boolean currentSubtypeEnabled =
- updateEnabledSubtypesAndReturnIfEnabled(mCurrentSubtype);
- updateShortcutIME();
- return currentSubtypeEnabled;
- }
-
- /**
- * Update enabled subtypes from the framework.
- *
- * @param subtype the subtype to be checked
- * @return true if the {@code subtype} is enabled.
- */
- private boolean updateEnabledSubtypesAndReturnIfEnabled(final InputMethodSubtype subtype) {
+ public void updateParametersOnStartInputView() {
final List<InputMethodSubtype> enabledSubtypesOfThisIme =
- mImm.getEnabledInputMethodSubtypeList(null, true);
+ mRichImm.getInputMethodManager().getEnabledInputMethodSubtypeList(null, true);
mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
-
- for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) {
- if (ims.equals(subtype)) {
- return true;
- }
- }
- if (DBG) {
- Log.w(TAG, "Subtype: " + subtype.getLocale() + "/" + subtype.getExtraValue()
- + " was disabled");
- }
- return false;
+ updateShortcutIME();
}
private void updateShortcutIME() {
@@ -162,7 +126,7 @@ public final class SubtypeSwitcher {
}
// TODO: Update an icon for shortcut IME
final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
- mImm.getShortcutInputMethodsAndSubtypes();
+ mRichImm.getInputMethodManager().getShortcutInputMethodsAndSubtypes();
mShortcutInputMethodInfo = null;
mShortcutSubtype = null;
for (final InputMethodInfo imi : shortcuts.keySet()) {
@@ -185,20 +149,20 @@ public final class SubtypeSwitcher {
}
// Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
- public void updateSubtype(InputMethodSubtype newSubtype) {
+ public void onSubtypeChanged(final InputMethodSubtype newSubtype) {
if (DBG) {
- Log.w(TAG, "onCurrentInputMethodSubtypeChanged: to: "
- + newSubtype.getLocale() + "/" + newSubtype.getExtraValue() + ", from: "
- + mCurrentSubtype.getLocale() + "/" + mCurrentSubtype.getExtraValue());
+ Log.w(TAG, "onSubtypeChanged: " + SubtypeLocale.getSubtypeDisplayName(newSubtype));
}
final Locale newLocale = SubtypeLocale.getSubtypeLocale(newSubtype);
+ final Locale systemLocale = mResources.getConfiguration().locale;
+ final boolean sameLocale = systemLocale.equals(newLocale);
+ final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage());
+ final boolean implicitlyEnabled =
+ mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype);
mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage(
- mCurrentSystemLocale.equals(newLocale));
+ sameLocale || (sameLanguage && implicitlyEnabled));
- if (newSubtype.equals(mCurrentSubtype)) return;
-
- mCurrentSubtype = newSubtype;
updateShortcutIME();
}
@@ -221,7 +185,7 @@ public final class SubtypeSwitcher {
if (token == null) {
return;
}
- final InputMethodManager imm = mImm;
+ final InputMethodManager imm = mRichImm.getInputMethodManager();
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
@@ -238,14 +202,8 @@ public final class SubtypeSwitcher {
if (mShortcutSubtype == null) {
return true;
}
- final boolean allowsImplicitlySelectedSubtypes = true;
- for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList(
- mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
- if (enabledSubtype.equals(mShortcutSubtype)) {
- return true;
- }
- }
- return false;
+ return mRichImm.checkIfSubtypeBelongsToImeAndEnabled(
+ mShortcutInputMethodInfo, mShortcutSubtype);
}
public boolean isShortcutImeReady() {
@@ -281,22 +239,19 @@ public final class SubtypeSwitcher {
return mNeedsToDisplayLanguage.getValue();
}
- public Locale getCurrentSubtypeLocale() {
- return SubtypeLocale.getSubtypeLocale(mCurrentSubtype);
+ private static Locale sForcedLocaleForTesting = null;
+ @UsedForTesting
+ void forceLocale(final Locale locale) {
+ sForcedLocaleForTesting = locale;
}
- public boolean onConfigurationChanged(final Configuration conf, final Context context) {
- final Locale systemLocale = conf.locale;
- final boolean systemLocaleChanged = !systemLocale.equals(mCurrentSystemLocale);
- // If system configuration was changed, update all parameters.
- if (systemLocaleChanged) {
- updateAllParameters(context);
- }
- return systemLocaleChanged;
+ public Locale getCurrentSubtypeLocale() {
+ if (null != sForcedLocaleForTesting) return sForcedLocaleForTesting;
+ return SubtypeLocale.getSubtypeLocale(getCurrentSubtype());
}
public InputMethodSubtype getCurrentSubtype() {
- return mCurrentSubtype;
+ return mRichImm.getCurrentInputMethodSubtype(mNoLanguageSubtype);
}
public InputMethodSubtype getNoLanguageSubtype() {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index f0e3b4ebd..975664dca 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -19,7 +19,7 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.text.TextUtils;
-import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -38,7 +38,7 @@ public final class Suggest {
public static final String TAG = Suggest.class.getSimpleName();
// Session id for
- // {@link #getSuggestedWords(WordComposer,CharSequence,ProximityInfo,boolean,int)}.
+ // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}.
public static final int SESSION_TYPING = 0;
public static final int SESSION_GESTURE = 1;
@@ -57,6 +57,8 @@ public final class Suggest {
private ContactsBinaryDictionary mContactsDict;
private final ConcurrentHashMap<String, Dictionary> mDictionaries =
CollectionUtils.newConcurrentHashMap();
+ @UsedForTesting
+ private boolean mIsCurrentlyWaitingForMainDictionary = false;
public static final int MAX_SUGGESTIONS = 18;
@@ -71,9 +73,9 @@ public final class Suggest {
mLocale = locale;
}
- /* package for test */ Suggest(final Context context, final File dictionary,
- final long startOffset, final long length, final Locale locale) {
- final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
+ @UsedForTesting
+ Suggest(final File dictionary, final long startOffset, final long length, final Locale locale) {
+ final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionary,
startOffset, length /* useFullEditDistance */, false, locale);
mLocale = locale;
mMainDictionary = mainDict;
@@ -98,6 +100,7 @@ public final class Suggest {
public void resetMainDict(final Context context, final Locale locale,
final SuggestInitializationListener listener) {
+ mIsCurrentlyWaitingForMainDictionary = true;
mMainDictionary = null;
if (listener != null) {
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
@@ -112,6 +115,7 @@ public final class Suggest {
if (listener != null) {
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
}
+ mIsCurrentlyWaitingForMainDictionary = false;
}
}.start();
}
@@ -122,6 +126,11 @@ public final class Suggest {
return null != mMainDictionary && mMainDictionary.isInitialized();
}
+ @UsedForTesting
+ public boolean isCurrentlyWaitingForMainDictionary() {
+ return mIsCurrentlyWaitingForMainDictionary;
+ }
+
public Dictionary getMainDictionary() {
return mMainDictionary;
}
@@ -138,7 +147,7 @@ public final class Suggest {
* Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
* before the main dictionary, if set. This refers to the system-managed user dictionary.
*/
- public void setUserDictionary(UserBinaryDictionary userDictionary) {
+ public void setUserDictionary(final UserBinaryDictionary userDictionary) {
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER, userDictionary);
}
@@ -147,12 +156,12 @@ public final class Suggest {
* the contacts dictionary by passing null to this method. In this case no contacts dictionary
* won't be used.
*/
- public void setContactsDictionary(ContactsBinaryDictionary contactsDictionary) {
+ public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
mContactsDict = contactsDictionary;
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary);
}
- public void setUserHistoryDictionary(UserHistoryDictionary userHistoryDictionary) {
+ public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
}
@@ -160,9 +169,9 @@ public final class Suggest {
mAutoCorrectionThreshold = threshold;
}
- public SuggestedWords getSuggestedWords(
- final WordComposer wordComposer, CharSequence prevWordForBigram,
- final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, int sessionId) {
+ public SuggestedWords getSuggestedWords(final WordComposer wordComposer,
+ final String prevWordForBigram, final ProximityInfo proximityInfo,
+ final boolean isCorrectionEnabled, final int sessionId) {
LatinImeLogger.onStartSuggestion(prevWordForBigram);
if (wordComposer.isBatchMode()) {
return getSuggestedWordsForBatchInput(
@@ -174,9 +183,9 @@ public final class Suggest {
}
// Retrieves suggestions for the typing input.
- private SuggestedWords getSuggestedWordsForTypingInput(
- final WordComposer wordComposer, CharSequence prevWordForBigram,
- final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
+ private SuggestedWords getSuggestedWordsForTypingInput(final WordComposer wordComposer,
+ final String prevWordForBigram, final ProximityInfo proximityInfo,
+ final boolean isCorrectionEnabled) {
final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
MAX_SUGGESTIONS);
@@ -203,7 +212,7 @@ public final class Suggest {
wordComposerForLookup, prevWordForBigram, proximityInfo));
}
- final CharSequence whitelistedWord;
+ final String whitelistedWord;
if (suggestionsSet.isEmpty()) {
whitelistedWord = null;
} else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
@@ -287,9 +296,9 @@ public final class Suggest {
}
// Retrieves suggestions for the batch input.
- private SuggestedWords getSuggestedWordsForBatchInput(
- final WordComposer wordComposer, CharSequence prevWordForBigram,
- final ProximityInfo proximityInfo, int sessionId) {
+ private SuggestedWords getSuggestedWordsForBatchInput(final WordComposer wordComposer,
+ final String prevWordForBigram, final ProximityInfo proximityInfo,
+ final int sessionId) {
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
MAX_SUGGESTIONS);
@@ -307,7 +316,7 @@ public final class Suggest {
}
for (SuggestedWordInfo wordInfo : suggestionsSet) {
- LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict);
+ LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict);
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
@@ -372,7 +381,7 @@ public final class Suggest {
if (o1.mScore < o2.mScore) return 1;
if (o1.mCodePointCount < o2.mCodePointCount) return -1;
if (o1.mCodePointCount > o2.mCodePointCount) return 1;
- return o1.mWord.toString().compareTo(o2.mWord.toString());
+ return o1.mWord.compareTo(o2.mWord);
}
}
private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator =
@@ -383,16 +392,17 @@ public final class Suggest {
final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
if (isAllUpperCase) {
- sb.append(wordInfo.mWord.toString().toUpperCase(locale));
+ sb.append(wordInfo.mWord.toUpperCase(locale));
} else if (isFirstCharCapitalized) {
- sb.append(StringUtils.toTitleCase(wordInfo.mWord.toString(), locale));
+ sb.append(StringUtils.toTitleCase(wordInfo.mWord, locale));
} else {
sb.append(wordInfo.mWord);
}
for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
- sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
+ sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE);
}
- return new SuggestedWordInfo(sb, wordInfo.mScore, wordInfo.mKind, wordInfo.mSourceDict);
+ return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKind,
+ wordInfo.mSourceDict);
}
public void close() {
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 52e292a86..3d6fe2d22 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -53,6 +53,10 @@ public final class SuggestedWords {
mIsPrediction = isPrediction;
}
+ public boolean isEmpty() {
+ return mSuggestedWordInfoList.isEmpty();
+ }
+
public int size() {
return mSuggestedWordInfoList.size();
}
@@ -61,10 +65,6 @@ public final class SuggestedWords {
return mSuggestedWordInfoList.get(pos).mWord;
}
- public SuggestedWordInfo getWordInfo(int pos) {
- return mSuggestedWordInfoList.get(pos);
- }
-
public SuggestedWordInfo getInfo(int pos) {
return mSuggestedWordInfoList.get(pos);
}
@@ -86,11 +86,14 @@ public final class SuggestedWords {
public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
final CompletionInfo[] infos) {
final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList();
- for (CompletionInfo info : infos) {
- if (null != info && info.getText() != null) {
- result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE,
- SuggestedWordInfo.KIND_APP_DEFINED, Dictionary.TYPE_APPLICATION_DEFINED));
- }
+ for (final CompletionInfo info : infos) {
+ if (info == null) continue;
+ final CharSequence text = info.getText();
+ if (null == text) continue;
+ final SuggestedWordInfo suggestedWordInfo = new SuggestedWordInfo(text.toString(),
+ SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_APP_DEFINED,
+ Dictionary.TYPE_APPLICATION_DEFINED);
+ result.add(suggestedWordInfo);
}
return result;
}
@@ -98,7 +101,7 @@ public final class SuggestedWords {
// Should get rid of the first one (what the user typed previously) from suggestions
// and replace it with what the user currently typed.
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
- final CharSequence typedWord, final SuggestedWords previousSuggestions) {
+ final String typedWord, final SuggestedWords previousSuggestions) {
final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList();
final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
@@ -106,8 +109,8 @@ public final class SuggestedWords {
alreadySeen.add(typedWord.toString());
final int previousSize = previousSuggestions.size();
for (int pos = 1; pos < previousSize; pos++) {
- final SuggestedWordInfo prevWordInfo = previousSuggestions.getWordInfo(pos);
- final String prevWord = prevWordInfo.mWord.toString();
+ final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(pos);
+ final String prevWord = prevWordInfo.mWord;
// Filter out duplicate suggestion.
if (!alreadySeen.contains(prevWord)) {
suggestionsList.add(prevWordInfo);
@@ -135,9 +138,9 @@ public final class SuggestedWords {
public final String mSourceDict;
private String mDebugString = "";
- public SuggestedWordInfo(final CharSequence word, final int score, final int kind,
+ public SuggestedWordInfo(final String word, final int score, final int kind,
final String sourceDict) {
- mWord = word.toString();
+ mWord = word;
mScore = score;
mKind = kind;
mSourceDict = sourceDict;
@@ -145,7 +148,7 @@ public final class SuggestedWords {
}
- public void setDebugString(String str) {
+ public void setDebugString(final String str) {
if (null == str) throw new NullPointerException("Debug info is null");
mDebugString = str;
}
@@ -167,7 +170,7 @@ public final class SuggestedWords {
if (TextUtils.isEmpty(mDebugString)) {
return mWord;
} else {
- return mWord + " (" + mDebugString.toString() + ")";
+ return mWord + " (" + mDebugString + ")";
}
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
index d188fc5ef..ed6fc03f9 100644
--- a/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
+++ b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
@@ -16,11 +16,10 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.compat.SuggestionSpanUtils;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.text.style.SuggestionSpan;
import android.util.Log;
public final class SuggestionSpanPickedNotificationReceiver extends BroadcastReceiver {
@@ -30,12 +29,12 @@ public final class SuggestionSpanPickedNotificationReceiver extends BroadcastRec
@Override
public void onReceive(Context context, Intent intent) {
- if (SuggestionSpanUtils.ACTION_SUGGESTION_PICKED.equals(intent.getAction())) {
+ if (SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(intent.getAction())) {
if (DBG) {
final String before = intent.getStringExtra(
- SuggestionSpanUtils.SUGGESTION_SPAN_PICKED_BEFORE);
+ SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE);
final String after = intent.getStringExtra(
- SuggestionSpanUtils.SUGGESTION_SPAN_PICKED_AFTER);
+ SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER);
Log.d(TAG, "Received notification picked: " + before + "," + after);
}
}
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
index 8f21b7b4a..ec4dc1436 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -33,13 +33,13 @@ public final class SynchronouslyLoadedContactsBinaryDictionary extends ContactsB
@Override
public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
- final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
+ final String prevWordForBigrams, final ProximityInfo proximityInfo) {
syncReloadDictionaryIfRequired();
return super.getSuggestions(codes, prevWordForBigrams, proximityInfo);
}
@Override
- public synchronized boolean isValidWord(CharSequence word) {
+ public synchronized boolean isValidWord(final String word) {
syncReloadDictionaryIfRequired();
return isValidWordInner(word);
}
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
index 612f54d73..4bdaf2039 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -36,13 +36,13 @@ public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDic
@Override
public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
- final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
+ final String prevWordForBigrams, final ProximityInfo proximityInfo) {
syncReloadDictionaryIfRequired();
return super.getSuggestions(codes, prevWordForBigrams, proximityInfo);
}
@Override
- public synchronized boolean isValidWord(CharSequence word) {
+ public synchronized boolean isValidWord(final String word) {
syncReloadDictionaryIfRequired();
return isValidWordInner(word);
}
diff --git a/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java b/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
index 743a8c60f..1ea4ac346 100644
--- a/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
+++ b/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 8570746b1..0d5bde623 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -24,27 +24,28 @@ import android.content.Intent;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Build;
import android.provider.UserDictionary.Words;
import android.text.TextUtils;
import java.util.Arrays;
/**
- * An expandable dictionary that stores the words in the user unigram dictionary.
- *
- * Largely a copy of UserDictionary, will replace that class in the future.
+ * An expandable dictionary that stores the words in the user dictionary provider into a binary
+ * dictionary file to use it from native code.
*/
public class UserBinaryDictionary extends ExpandableBinaryDictionary {
// The user dictionary provider uses an empty string to mean "all languages".
private static final String USER_DICTIONARY_ALL_LANGUAGES = "";
+ private static final int HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY = 250;
+ private static final int LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY = 160;
// TODO: use Words.SHORTCUT when we target JellyBean or above
final static String SHORTCUT = "shortcut";
private static final String[] PROJECTION_QUERY;
static {
- // 16 is JellyBean, but we want this to compile against ICS.
- if (android.os.Build.VERSION.SDK_INT >= 16) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
PROJECTION_QUERY = new String[] {
Words.WORD,
SHORTCUT,
@@ -90,13 +91,14 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
mObserver = new ContentObserver(null) {
@Override
public void onChange(final boolean self) {
- // This hook is deprecated as of API level 16, but should still be supported for
- // cases where the IME is running on an older version of the platform.
+ // This hook is deprecated as of API level 16 (Build.VERSION_CODES.JELLY_BEAN),
+ // but should still be supported for cases where the IME is running on an older
+ // version of the platform.
onChange(self, null);
}
- // The following hook is only available as of API level 16, and as such it will only
- // work on JellyBean+ devices. On older versions of the platform, the hook
- // above will be called instead.
+ // The following hook is only available as of API level 16
+ // (Build.VERSION_CODES.JELLY_BEAN), and as such it will only work on JellyBean+
+ // devices. On older versions of the platform, the hook above will be called instead.
@Override
public void onChange(final boolean self, final Uri uri) {
setRequiresReload(true);
@@ -214,17 +216,13 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
*
* @param word the word to add. If the word is capitalized, then the dictionary will
* recognize it as a capitalized word when searched.
- * @param frequency the frequency of occurrence of the word. A frequency of 255 is considered
- * the highest.
- * @TODO use a higher or float range for frequency
*/
- public synchronized void addWordToUserDictionary(final String word, final int frequency) {
+ public synchronized void addWordToUserDictionary(final String word) {
// TODO: do something for the UI. With the following, any sufficiently long word will
// look like it will go to the user dictionary but it won't.
// Safeguard against adding long words. Can cause stack overflow.
if (word.length() >= MAX_WORD_LENGTH) return;
- // TODO: Add an argument to the intent to specify the frequency.
Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT);
intent.putExtra(Words.WORD, word);
intent.putExtra(Words.LOCALE, mLocale);
@@ -232,9 +230,21 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
mContext.startActivity(intent);
}
- private void addWords(Cursor cursor) {
- // 16 is JellyBean, but we want this to compile against ICS.
- final boolean hasShortcutColumn = android.os.Build.VERSION.SDK_INT >= 16;
+ private int scaleFrequencyFromDefaultToLatinIme(final int defaultFrequency) {
+ // The default frequency for the user dictionary is 250 for historical reasons.
+ // Latin IME considers a good value for the default user dictionary frequency
+ // is about 160 considering the scale we use. So we are scaling down the values.
+ if (defaultFrequency > Integer.MAX_VALUE / LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY) {
+ return (defaultFrequency / HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY)
+ * LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY;
+ } else {
+ return (defaultFrequency * LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY)
+ / HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY;
+ }
+ }
+
+ private void addWords(final Cursor cursor) {
+ final boolean hasShortcutColumn = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
clearFusionDictionary();
if (cursor == null) return;
if (cursor.moveToFirst()) {
@@ -245,12 +255,13 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
final String word = cursor.getString(indexWord);
final String shortcut = hasShortcutColumn ? cursor.getString(indexShortcut) : null;
final int frequency = cursor.getInt(indexFrequency);
+ final int adjustedFrequency = scaleFrequencyFromDefaultToLatinIme(frequency);
// Safeguard against adding really long words.
if (word.length() < MAX_WORD_LENGTH) {
- super.addWord(word, null, frequency);
+ super.addWord(word, null, adjustedFrequency);
}
if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) {
- super.addWord(shortcut, word, frequency);
+ super.addWord(shortcut, word, adjustedFrequency);
}
cursor.moveToNext();
}
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
index e39011145..62f2a9750 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
@@ -1,23 +1,24 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
import android.util.Log;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
@@ -47,6 +48,7 @@ public final class UserHistoryDictIOUtils {
public void setBigram(final String word1, final String word2, final int frequency);
}
+ @UsedForTesting
public interface BigramDictionaryInterface {
public int getFrequency(final String word1, final String word2);
}
@@ -62,7 +64,7 @@ public final class UserHistoryDictIOUtils {
@Override
public int readUnsignedByte() {
- return ((int)mBuffer[mPosition++]) & 0xFF;
+ return mBuffer[mPosition++] & 0xFF;
}
@Override
@@ -120,16 +122,17 @@ public final class UserHistoryDictIOUtils {
BinaryDictInputOutput.writeDictionaryBinary(destination, fusionDict, formatOptions);
Log.d(TAG, "end writing");
} catch (IOException e) {
- Log.e(TAG, "IO exception while writing file: " + e);
+ Log.e(TAG, "IO exception while writing file", e);
} catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported fomat: " + e);
+ Log.e(TAG, "Unsupported format", e);
}
}
/**
* Constructs a new FusionDictionary from BigramDictionaryInterface.
*/
- /* packages for test */ static FusionDictionary constructFusionDictionary(
+ @UsedForTesting
+ static FusionDictionary constructFusionDictionary(
final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) {
final FusionDictionary fusionDict = new FusionDictionary(new Node(),
new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
@@ -181,11 +184,11 @@ public final class UserHistoryDictIOUtils {
BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies,
bigrams);
} catch (IOException e) {
- Log.e(TAG, "IO exception while reading file: " + e);
+ Log.e(TAG, "IO exception while reading file", e);
} catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported format: " + e);
+ Log.e(TAG, "Unsupported format", e);
} catch (ArrayIndexOutOfBoundsException e) {
- Log.e(TAG, "ArrayIndexOutOfBoundsException while reading file: " + e);
+ Log.e(TAG, "ArrayIndexOutOfBoundsException while reading file", e);
}
addWordsFromWordMap(unigrams, frequencies, bigrams, dict);
}
@@ -193,7 +196,8 @@ public final class UserHistoryDictIOUtils {
/**
* Adds all unigrams and bigrams in maps to OnAddWordListener.
*/
- /* package for test */ static void addWordsFromWordMap(final Map<Integer, String> unigrams,
+ @UsedForTesting
+ static void addWordsFromWordMap(final Map<Integer, String> unigrams,
final Map<Integer, Integer> frequencies,
final Map<Integer, ArrayList<PendingAttribute>> bigrams, final OnAddWordListener to) {
for (Map.Entry<Integer, String> entry : unigrams.entrySet()) {
@@ -211,4 +215,4 @@ public final class UserHistoryDictIOUtils {
}
}
-} \ No newline at end of file
+}
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 3615fa1fb..528028328 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
@@ -21,6 +21,7 @@ import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.util.Log;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.UserHistoryDictIOUtils.BigramDictionaryInterface;
@@ -75,7 +76,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
private final SharedPreferences mPrefs;
// Should always be false except when we use this class for test
- /* package for test */ boolean isTest = false;
+ @UsedForTesting boolean isTest = false;
private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
sLangDictCache = CollectionUtils.newConcurrentHashMap();
@@ -122,7 +123,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
@Override
protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo) {
+ final String prevWord, final ProximityInfo proximityInfo) {
// Inhibit suggestions (not predictions) for user history for now. Removing this method
// is enough to use it through the standard ExpandableDictionary way.
return null;
@@ -132,7 +133,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
* Return whether the passed charsequence is in the dictionary.
*/
@Override
- public synchronized boolean isValidWord(final CharSequence word) {
+ public synchronized boolean isValidWord(final String word) {
// TODO: figure out what is the correct thing to do here.
return false;
}
@@ -145,9 +146,9 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
* context, as in beginning of a sentence for example.
* The second word may not be null (a NullPointerException would be thrown).
*/
- public int addToUserHistory(final String word1, String word2, boolean isValid) {
- if (word2.length() >= BinaryDictionary.MAX_WORD_LENGTH ||
- (word1 != null && word1.length() >= BinaryDictionary.MAX_WORD_LENGTH)) {
+ public int addToUserHistory(final String word1, final String word2, final boolean isValid) {
+ if (word2.length() >= Constants.Dictionary.MAX_WORD_LENGTH ||
+ (word1 != null && word1.length() >= Constants.Dictionary.MAX_WORD_LENGTH)) {
return -1;
}
if (mBigramListLock.tryLock()) {
@@ -175,7 +176,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
return -1;
}
- public boolean cancelAddingUserHistory(String word1, String word2) {
+ public boolean cancelAddingUserHistory(final String word1, final String word2) {
if (mBigramListLock.tryLock()) {
try {
if (mBigramList.removeBigram(word1, word2)) {
@@ -218,7 +219,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
} catch (InterruptedException e) {
}
}
- final long last = SettingsValues.getLastUserHistoryWriteTime(mPrefs, mLocale);
+ final long last = Settings.readLastUserHistoryWriteTime(mPrefs, mLocale);
final boolean initializing = last == 0;
final long now = System.currentTimeMillis();
profTotal = 0;
@@ -226,7 +227,8 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
final ExpandableDictionary dictionary = this;
final OnAddWordListener listener = new OnAddWordListener() {
@Override
- public void setUnigram(String word, String shortcutTarget, int frequency) {
+ public void setUnigram(final String word, final String shortcutTarget,
+ final int frequency) {
profTotal++;
if (DBG_SAVE_RESTORE) {
Log.d(TAG, "load unigram: " + word + "," + frequency);
@@ -236,9 +238,9 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
}
@Override
- public void setBigram(String word1, String word2, int frequency) {
- if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH
- && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) {
+ public void setBigram(final String word1, final String word2, final int frequency) {
+ if (word1.length() < Constants.Dictionary.MAX_WORD_LENGTH
+ && word2.length() < Constants.Dictionary.MAX_WORD_LENGTH) {
profTotal++;
if (DBG_SAVE_RESTORE) {
Log.d(TAG, "load bigram: " + word1 + "," + word2 + "," + frequency);
@@ -250,7 +252,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
mBigramList.addBigram(word1, word2, (byte)frequency);
}
};
-
+
// Load the dictionary from binary file
FileInputStream inStream = null;
try {
@@ -261,9 +263,10 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
UserHistoryDictIOUtils.readDictionaryBinary(
new UserHistoryDictIOUtils.ByteArrayWrapper(buffer), listener);
} catch (FileNotFoundException e) {
- Log.e(TAG, "when loading: file not found" + e);
+ // This is an expected condition: we don't have a user history dictionary for this
+ // language yet. It will be created sometime later.
} catch (IOException e) {
- Log.e(TAG, "IOException when open bytebuffer: " + e);
+ Log.e(TAG, "IOException on opening a bytebuffer", e);
} finally {
if (inStream != null) {
try {
@@ -292,8 +295,9 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
private final SharedPreferences mPrefs;
private final Context mContext;
- public UpdateBinaryTask(UserHistoryDictionaryBigramList pendingWrites, String locale,
- UserHistoryDictionary dict, SharedPreferences prefs, Context context) {
+ public UpdateBinaryTask(final UserHistoryDictionaryBigramList pendingWrites,
+ final String locale, final UserHistoryDictionary dict,
+ final SharedPreferences prefs, final Context context) {
mBigramList = pendingWrites;
mLocale = locale;
mUserHistoryDictionary = dict;
@@ -303,7 +307,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
}
@Override
- protected Void doInBackground(Void... v) {
+ protected Void doInBackground(final Void... v) {
if (mUserHistoryDictionary.isTest) {
// If isTest == true, wait until the lock is released.
mUserHistoryDictionary.mBigramListLock.lock();
@@ -325,7 +329,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
Thread.sleep(15000);
Log.w(TAG, "End stress in closing");
} catch (InterruptedException e) {
- Log.e(TAG, "In stress test: " + e);
+ Log.e(TAG, "In stress test", e);
}
}
@@ -340,7 +344,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
out.flush();
out.close();
} catch (IOException e) {
- Log.e(TAG, "IO Exception while writing file: " + e);
+ Log.e(TAG, "IO Exception while writing file", e);
} finally {
if (out != null) {
try {
@@ -352,7 +356,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
}
// Save the timestamp after we finish writing the binary dictionary.
- SettingsValues.setLastUserHistoryWriteTime(mPrefs, mLocale);
+ Settings.writeLastUserHistoryWriteTime(mPrefs, mLocale);
if (PROFILE_SAVE_RESTORE) {
final long diff = System.currentTimeMillis() - now;
Log.w(TAG, "PROF: Write User HistoryDictionary: " + mLocale + ", " + diff + "ms.");
@@ -360,7 +364,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
}
@Override
- public int getFrequency(String word1, String word2) {
+ public int getFrequency(final String word1, final String word2) {
final int freq;
if (word1 == null) { // unigram
freq = FREQUENCY_FOR_TYPED;
@@ -373,10 +377,10 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
final byte fc = fcp.getFc();
final boolean isValid = fcp.isValid();
if (prevFc > 0 && prevFc == fc) {
- freq = ((int)fc) & 0xFF;
+ freq = fc & 0xFF;
} else if (UserHistoryForgettingCurveUtils.
needsToSave(fc, isValid, mAddLevel0Bigrams)) {
- freq = ((int)fc) & 0xFF;
+ freq = fc & 0xFF;
} else {
// Delete this entry
freq = -1;
@@ -390,6 +394,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary {
}
}
+ @UsedForTesting
void forceAddWordForTest(final String word1, final String word2, final boolean isValid) {
mBigramListLock.lock();
try {
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
index df44948f9..316f09603 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
@@ -18,6 +18,8 @@ package com.android.inputmethod.latin;
import android.util.Log;
+import com.android.inputmethod.annotations.UsedForTesting;
+
import java.util.HashMap;
import java.util.Set;
@@ -26,6 +28,7 @@ import java.util.Set;
* All bigrams including stale ones in SQL DB should be stored in this class to avoid adding stale
* bigrams when we write to the SQL DB.
*/
+@UsedForTesting
public final class UserHistoryDictionaryBigramList {
public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0;
private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName();
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 876bc8e79..acfcd5354 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -41,6 +41,7 @@ import java.io.PrintWriter;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.Locale;
public final class Utils {
private Utils() {
@@ -60,7 +61,7 @@ public final class Utils {
}
}
- /* package */ static class RingCharBuffer {
+ /* package */ static final class RingCharBuffer {
private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
private static final int INVALID_COORDINATE = -2;
@@ -193,7 +194,7 @@ public final class Utils {
private UsabilityStudyLogUtils() {
mDate = new Date();
- mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ");
+ mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US);
HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
Process.THREAD_PRIORITY_BACKGROUND);
@@ -202,7 +203,7 @@ public final class Utils {
}
// Initialization-on-demand holder
- private static class OnDemandInitializationHolder {
+ private static final class OnDemandInitializationHolder {
public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
}
@@ -255,7 +256,7 @@ public final class Utils {
final long currentTime = System.currentTimeMillis();
mDate.setTime(currentTime);
- final String printString = String.format("%s\t%d\t%s\n",
+ 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);
@@ -297,7 +298,7 @@ public final class Utils {
final Date date = new Date();
date.setTime(System.currentTimeMillis());
final String currentDateTimeString =
- new SimpleDateFormat("yyyyMMdd-HHmmssZ").format(date);
+ new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date);
if (mFile == null) {
Log.w(USABILITY_TAG, "No internal log file found.");
return;
@@ -313,11 +314,15 @@ public final class Utils {
+ "/research-" + currentDateTimeString + ".log";
final File destFile = new File(destPath);
try {
- final FileChannel src = (new FileInputStream(mFile)).getChannel();
- final FileChannel dest = (new FileOutputStream(destFile)).getChannel();
+ final FileInputStream srcStream = new FileInputStream(mFile);
+ final FileOutputStream destStream = new FileOutputStream(destFile);
+ final FileChannel src = srcStream.getChannel();
+ final FileChannel dest = destStream.getChannel();
src.transferTo(0, src.size(), dest);
src.close();
+ srcStream.close();
dest.close();
+ destStream.close();
} catch (FileNotFoundException e1) {
Log.w(USABILITY_TAG, e1);
return;
diff --git a/java/src/com/android/inputmethod/latin/VibratorUtils.java b/java/src/com/android/inputmethod/latin/VibratorUtils.java
deleted file mode 100644
index b6696cec0..000000000
--- a/java/src/com/android/inputmethod/latin/VibratorUtils.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-import android.os.Vibrator;
-
-public final class VibratorUtils {
- private static final VibratorUtils sInstance = new VibratorUtils();
- private Vibrator mVibrator;
-
- private VibratorUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static VibratorUtils getInstance(Context context) {
- if (sInstance.mVibrator == null) {
- sInstance.mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- }
- return sInstance;
- }
-
- public boolean hasVibrator() {
- if (mVibrator == null) {
- return false;
- }
- return mVibrator.hasVibrator();
- }
-
- public void vibrate(long milliseconds) {
- if (mVibrator == null) {
- return;
- }
- mVibrator.vibrate(milliseconds);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index da0071adc..f7cb4346a 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -1,21 +1,22 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
@@ -25,7 +26,7 @@ import java.util.Arrays;
* A place to store the currently composing word with information such as adjacent key codes as well
*/
public final class WordComposer {
- private static final int N = BinaryDictionary.MAX_WORD_LENGTH;
+ private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
public static final int CAPS_MODE_OFF = 0;
// 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits
@@ -36,9 +37,9 @@ public final class WordComposer {
public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
private int[] mPrimaryKeyCodes;
- private final InputPointers mInputPointers = new InputPointers(N);
+ private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
private final StringBuilder mTypedWord;
- private CharSequence mAutoCorrection;
+ private String mAutoCorrection;
private boolean mIsResumed;
private boolean mIsBatchMode;
@@ -55,8 +56,8 @@ public final class WordComposer {
private boolean mIsFirstCharCapitalized;
public WordComposer() {
- mPrimaryKeyCodes = new int[N];
- mTypedWord = new StringBuilder(N);
+ mPrimaryKeyCodes = new int[MAX_WORD_LENGTH];
+ mTypedWord = new StringBuilder(MAX_WORD_LENGTH);
mAutoCorrection = null;
mTrailingSingleQuotesCount = 0;
mIsResumed = false;
@@ -64,7 +65,7 @@ public final class WordComposer {
refreshSize();
}
- public WordComposer(WordComposer source) {
+ public WordComposer(final WordComposer source) {
mPrimaryKeyCodes = Arrays.copyOf(source.mPrimaryKeyCodes, source.mPrimaryKeyCodes.length);
mTypedWord = new StringBuilder(source.mTypedWord);
mInputPointers.copy(source.mInputPointers);
@@ -111,7 +112,7 @@ public final class WordComposer {
// TODO: make sure that the index should not exceed MAX_WORD_LENGTH
public int getCodeAt(int index) {
- if (index >= BinaryDictionary.MAX_WORD_LENGTH) {
+ if (index >= MAX_WORD_LENGTH) {
return -1;
}
return mPrimaryKeyCodes[index];
@@ -121,7 +122,8 @@ public final class WordComposer {
return mInputPointers;
}
- private static boolean isFirstCharCapitalized(int index, int codePoint, boolean previous) {
+ 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);
}
@@ -129,12 +131,12 @@ public final class WordComposer {
/**
* Add a new keystroke, with the pressed key's code point with the touch point coordinates.
*/
- public void add(int primaryCode, int keyX, int keyY) {
+ public void add(final int primaryCode, final int keyX, final int keyY) {
final int newIndex = size();
mTypedWord.appendCodePoint(primaryCode);
refreshSize();
- if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) {
- mPrimaryKeyCodes[newIndex] = primaryCode >= Keyboard.CODE_SPACE
+ if (newIndex < MAX_WORD_LENGTH) {
+ mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE
? Character.toLowerCase(primaryCode) : primaryCode;
// In the batch input mode, the {@code mInputPointers} holds batch input points and
// shouldn't be overridden by the "typed key" coordinates
@@ -148,7 +150,7 @@ public final class WordComposer {
newIndex, primaryCode, mIsFirstCharCapitalized);
if (Character.isUpperCase(primaryCode)) mCapsCount++;
if (Character.isDigit(primaryCode)) mDigitsCount++;
- if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) {
+ if (Constants.CODE_SINGLE_QUOTE == primaryCode) {
++mTrailingSingleQuotesCount;
} else {
mTrailingSingleQuotesCount = 0;
@@ -156,12 +158,12 @@ public final class WordComposer {
mAutoCorrection = null;
}
- public void setBatchInputPointers(InputPointers batchPointers) {
+ public void setBatchInputPointers(final InputPointers batchPointers) {
mInputPointers.set(batchPointers);
mIsBatchMode = true;
}
- public void setBatchInputWord(CharSequence word) {
+ public void setBatchInputWord(final String word) {
reset();
mIsBatchMode = true;
final int length = word.length();
@@ -176,7 +178,8 @@ public final class WordComposer {
/**
* Internal method to retrieve reasonable proximity info for a character.
*/
- private void addKeyInfo(final int codePoint, final Keyboard keyboard) {
+ @UsedForTesting
+ public void addKeyInfo(final int codePoint, final Keyboard keyboard) {
final int x, y;
final Key key;
if (keyboard != null && (key = keyboard.getKey(codePoint)) != null) {
@@ -235,7 +238,7 @@ public final class WordComposer {
int i = mTypedWord.length();
while (i > 0) {
i = mTypedWord.offsetByCodePoints(i, -1);
- if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
+ if (Constants.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
++mTrailingSingleQuotesCount;
}
}
@@ -321,14 +324,14 @@ public final class WordComposer {
/**
* Sets the auto-correction for this word.
*/
- public void setAutoCorrection(final CharSequence correction) {
+ public void setAutoCorrection(final String correction) {
mAutoCorrection = correction;
}
/**
* @return the auto-correction for this word, or null if none.
*/
- public CharSequence getAutoCorrectionOrNull() {
+ public String getAutoCorrectionOrNull() {
return mAutoCorrection;
}
@@ -341,15 +344,15 @@ public final class WordComposer {
// `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
public LastComposedWord commitWord(final int type, final String committedWord,
- final String separatorString, final CharSequence prevWord) {
+ final String separatorString, final String prevWord) {
// Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK
// or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
// the last composed word to ensure this does not happen.
final int[] primaryKeyCodes = mPrimaryKeyCodes;
- mPrimaryKeyCodes = new int[N];
+ mPrimaryKeyCodes = new int[MAX_WORD_LENGTH];
final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes,
mInputPointers, mTypedWord.toString(), committedWord, separatorString,
- prevWord);
+ prevWord, mCapitalizedMode);
mInputPointers.reset();
if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
&& type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) {
@@ -359,8 +362,10 @@ public final class WordComposer {
mDigitsCount = 0;
mIsBatchMode = false;
mTypedWord.setLength(0);
+ mCodePointSize = 0;
mTrailingSingleQuotesCount = 0;
mIsFirstCharCapitalized = false;
+ mCapitalizedMode = CAPS_MODE_OFF;
refreshSize();
mAutoCorrection = null;
mIsResumed = false;
@@ -373,6 +378,7 @@ public final class WordComposer {
mTypedWord.setLength(0);
mTypedWord.append(lastComposedWord.mTypedWord);
refreshSize();
+ mCapitalizedMode = lastComposedWord.mCapitalizedMode;
mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
mIsResumed = true;
}
diff --git a/java/src/com/android/inputmethod/latin/WordListInfo.java b/java/src/com/android/inputmethod/latin/WordListInfo.java
index 095320e25..5ac806a0c 100644
--- a/java/src/com/android/inputmethod/latin/WordListInfo.java
+++ b/java/src/com/android/inputmethod/latin/WordListInfo.java
@@ -1,17 +1,17 @@
-/**
+/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
index 75dc68f1d..f01d4c5e6 100644
--- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java
+++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin;
diff --git a/java/src/com/android/inputmethod/latin/define/JniLibName.java b/java/src/com/android/inputmethod/latin/define/JniLibName.java
index e23e1a968..abfc36d39 100644
--- a/java/src/com/android/inputmethod/latin/define/JniLibName.java
+++ b/java/src/com/android/inputmethod/latin/define/JniLibName.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.define;
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index 52c066a44..fe9be16c6 100644
--- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.define;
@@ -23,4 +23,11 @@ public final class ProductionFlag {
public static final boolean IS_EXPERIMENTAL = false;
public static final boolean IS_INTERNAL = false;
+
+ // When false, IS_EXPERIMENTAL_DEBUG suggests that all guarded class-private DEBUG flags should
+ // be false, and any privacy controls should be enforced. IS_EXPERIMENTAL_DEBUG should be false
+ // for any released build.
+ public static final boolean IS_EXPERIMENTAL_DEBUG = false;
+
+ public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = true;
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 7b0231a6b..c87a9254d 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -1,34 +1,51 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.makedict;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.CharEncoding;
import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
public final class BinaryDictIOUtils {
private static final boolean DBG = false;
+ private static final int MSB24 = 0x800000;
+ private static final int SINT24_MAX = 0x7FFFFF;
+ private static final int MAX_JUMPS = 10000;
+
+ private BinaryDictIOUtils() {
+ // This utility class is not publicly instantiable.
+ }
private static final class Position {
public static final int NOT_READ_GROUPCOUNT = -1;
@@ -90,7 +107,9 @@ public final class BinaryDictIOUtils {
final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(info.mFlags,
formatOptions);
- if (!isMovedGroup
+ final boolean isDeletedGroup = BinaryDictInputOutput.isDeletedGroup(info.mFlags,
+ formatOptions);
+ if (!isMovedGroup && !isDeletedGroup
&& info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) {// found word
words.put(info.mOriginalAddress, new String(pushedChars, 0, index));
frequencies.put(info.mOriginalAddress, info.mFrequency);
@@ -153,6 +172,7 @@ public final class BinaryDictIOUtils {
* @throws IOException
* @throws UnsupportedFormatException
*/
+ @UsedForTesting
public static int getTerminalPosition(final FusionDictionaryBufferInterface buffer,
final String word) throws IOException, UnsupportedFormatException {
if (word == null) return FormatSpec.NOT_VALID_WORD;
@@ -165,19 +185,19 @@ public final class BinaryDictIOUtils {
if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD;
do {
- int groupOffset = buffer.position() - header.mHeaderSize;
final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer);
- groupOffset += BinaryDictInputOutput.getGroupCountSize(charGroupCount);
-
boolean foundNextCharGroup = false;
for (int i = 0; i < charGroupCount; ++i) {
final int charGroupPos = buffer.position();
final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer,
buffer.position(), header.mFormatOptions);
- if (BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags,
- header.mFormatOptions)) {
- continue;
- }
+ final boolean isMovedGroup =
+ BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags,
+ header.mFormatOptions);
+ final boolean isDeletedGroup =
+ BinaryDictInputOutput.isDeletedGroup(currentInfo.mFlags,
+ header.mFormatOptions);
+ if (isMovedGroup) continue;
boolean same = true;
for (int p = 0, j = word.offsetByCodePoints(0, wordPos);
p < currentInfo.mCharacters.length;
@@ -192,7 +212,8 @@ public final class BinaryDictIOUtils {
if (same) {
// found the group matches the word.
if (wordPos + currentInfo.mCharacters.length == wordLen) {
- if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL) {
+ if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL
+ || isDeletedGroup) {
return FormatSpec.NOT_VALID_WORD;
} else {
return charGroupPos;
@@ -206,7 +227,6 @@ public final class BinaryDictIOUtils {
buffer.position(currentInfo.mChildrenAddress);
break;
}
- groupOffset = currentInfo.mEndAddress;
}
// If we found the next char group, it is under the file pointer.
@@ -228,6 +248,10 @@ public final class BinaryDictIOUtils {
return FormatSpec.NOT_VALID_WORD;
}
+ private static int markAsDeleted(final int flags) {
+ return (flags & (~FormatSpec.MASK_GROUP_ADDRESS_TYPE)) | FormatSpec.FLAG_IS_DELETED;
+ }
+
/**
* Delete the word from the binary file.
*
@@ -236,6 +260,7 @@ public final class BinaryDictIOUtils {
* @throws IOException
* @throws UnsupportedFormatException
*/
+ @UsedForTesting
public static void deleteWord(final FusionDictionaryBufferInterface buffer,
final String word) throws IOException, UnsupportedFormatException {
buffer.position(0);
@@ -245,21 +270,58 @@ public final class BinaryDictIOUtils {
buffer.position(wordPosition);
final int flags = buffer.readUnsignedByte();
- final int newFlags = flags ^ FormatSpec.FLAG_IS_TERMINAL;
buffer.position(wordPosition);
- buffer.put((byte)newFlags);
+ buffer.put((byte)markAsDeleted(flags));
}
- private static void putSInt24(final FusionDictionaryBufferInterface buffer,
+ /**
+ * @return the size written, in bytes. Always 3 bytes.
+ */
+ private static int writeSInt24ToBuffer(final FusionDictionaryBufferInterface buffer,
final int value) {
final int absValue = Math.abs(value);
buffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
buffer.put((byte)((absValue >> 8) & 0xFF));
buffer.put((byte)(absValue & 0xFF));
+ return 3;
+ }
+
+ /**
+ * @return the size written, in bytes. Always 3 bytes.
+ */
+ private static int writeSInt24ToStream(final OutputStream destination, final int value)
+ throws IOException {
+ final int absValue = Math.abs(value);
+ destination.write((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
+ destination.write((byte)((absValue >> 8) & 0xFF));
+ destination.write((byte)(absValue & 0xFF));
+ return 3;
+ }
+
+ /**
+ * @return the size written, in bytes. 1, 2, or 3 bytes.
+ */
+ private static int writeVariableAddress(final OutputStream destination, final int value)
+ throws IOException {
+ switch (BinaryDictInputOutput.getByteSize(value)) {
+ case 1:
+ destination.write((byte)value);
+ break;
+ case 2:
+ destination.write((byte)(0xFF & (value >> 8)));
+ destination.write((byte)(0xFF & value));
+ break;
+ case 3:
+ destination.write((byte)(0xFF & (value >> 16)));
+ destination.write((byte)(0xFF & (value >> 8)));
+ destination.write((byte)(0xFF & value));
+ break;
+ }
+ return BinaryDictInputOutput.getByteSize(value);
}
/**
- * Update a parent address in a CharGroup that is addressed by groupOriginAddress.
+ * Update a parent address in a CharGroup that is referred to by groupOriginAddress.
*
* @param buffer the buffer to write.
* @param groupOriginAddress the address of the group.
@@ -275,8 +337,686 @@ public final class BinaryDictIOUtils {
throw new RuntimeException("this file format does not support parent addresses");
}
final int flags = buffer.readUnsignedByte();
+ if (BinaryDictInputOutput.isMovedGroup(flags, formatOptions)) {
+ // if the group is moved, the parent address is stored in the destination group.
+ // We are guaranteed to process the destination group later, so there is no need to
+ // update anything here.
+ buffer.position(originalPosition);
+ return;
+ }
+ if (DBG) {
+ MakedictLog.d("update parent address flags=" + flags + ", " + groupOriginAddress);
+ }
final int parentOffset = newParentAddress - groupOriginAddress;
- putSInt24(buffer, parentOffset);
+ writeSInt24ToBuffer(buffer, parentOffset);
buffer.position(originalPosition);
}
+
+ private static void skipCharGroup(final FusionDictionaryBufferInterface buffer,
+ final FormatOptions formatOptions) {
+ final int flags = buffer.readUnsignedByte();
+ BinaryDictInputOutput.readParentAddress(buffer, formatOptions);
+ skipString(buffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
+ BinaryDictInputOutput.readChildrenAddress(buffer, flags, formatOptions);
+ if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) buffer.readUnsignedByte();
+ if ((flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) != 0) {
+ final int shortcutsSize = buffer.readUnsignedShort();
+ buffer.position(buffer.position() + shortcutsSize
+ - FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE);
+ }
+ if ((flags & FormatSpec.FLAG_HAS_BIGRAMS) != 0) {
+ int bigramCount = 0;
+ while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
+ final int bigramFlags = buffer.readUnsignedByte();
+ switch (bigramFlags & FormatSpec.MASK_ATTRIBUTE_ADDRESS_TYPE) {
+ case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
+ buffer.readUnsignedByte();
+ break;
+ case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
+ buffer.readUnsignedShort();
+ break;
+ case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
+ buffer.readUnsignedInt24();
+ break;
+ }
+ if ((bigramFlags & FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT) == 0) break;
+ }
+ if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
+ throw new RuntimeException("Too many bigrams in a group.");
+ }
+ }
+ }
+
+ /**
+ * Update parent addresses in a Node that is referred to by nodeOriginAddress.
+ *
+ * @param buffer the buffer to be modified.
+ * @param nodeOriginAddress the address of a modified Node.
+ * @param newParentAddress the address to be written.
+ * @param formatOptions file format options.
+ */
+ public static void updateParentAddresses(final FusionDictionaryBufferInterface buffer,
+ final int nodeOriginAddress, final int newParentAddress,
+ final FormatOptions formatOptions) {
+ final int originalPosition = buffer.position();
+ buffer.position(nodeOriginAddress);
+ do {
+ final int count = BinaryDictInputOutput.readCharGroupCount(buffer);
+ for (int i = 0; i < count; ++i) {
+ updateParentAddress(buffer, buffer.position(), newParentAddress, formatOptions);
+ skipCharGroup(buffer, formatOptions);
+ }
+ final int forwardLinkAddress = buffer.readUnsignedInt24();
+ buffer.position(forwardLinkAddress);
+ } while (formatOptions.mSupportsDynamicUpdate
+ && buffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
+ buffer.position(originalPosition);
+ }
+
+ private static void skipString(final FusionDictionaryBufferInterface buffer,
+ final boolean hasMultipleChars) {
+ if (hasMultipleChars) {
+ int character = CharEncoding.readChar(buffer);
+ while (character != FormatSpec.INVALID_CHARACTER) {
+ character = CharEncoding.readChar(buffer);
+ }
+ } else {
+ CharEncoding.readChar(buffer);
+ }
+ }
+
+ /**
+ * Write a string to a stream.
+ *
+ * @param destination the stream to write.
+ * @param word the string to be written.
+ * @return the size written, in bytes.
+ * @throws IOException
+ */
+ private static int writeString(final OutputStream destination, final String word)
+ throws IOException {
+ int size = 0;
+ final int length = word.length();
+ for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+ final int codePoint = word.codePointAt(i);
+ if (CharEncoding.getCharSize(codePoint) == 1) {
+ destination.write((byte)codePoint);
+ size++;
+ } else {
+ destination.write((byte)(0xFF & (codePoint >> 16)));
+ destination.write((byte)(0xFF & (codePoint >> 8)));
+ destination.write((byte)(0xFF & codePoint));
+ size += 3;
+ }
+ }
+ destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR);
+ size += FormatSpec.GROUP_TERMINATOR_SIZE;
+ return size;
+ }
+
+ /**
+ * Update a children address in a CharGroup that is addressed by groupOriginAddress.
+ *
+ * @param buffer the buffer to write.
+ * @param groupOriginAddress the address of the group.
+ * @param newChildrenAddress the absolute address of the child.
+ * @param formatOptions file format options.
+ */
+ public static void updateChildrenAddress(final FusionDictionaryBufferInterface buffer,
+ final int groupOriginAddress, final int newChildrenAddress,
+ final FormatOptions formatOptions) {
+ final int originalPosition = buffer.position();
+ buffer.position(groupOriginAddress);
+ final int flags = buffer.readUnsignedByte();
+ final int parentAddress = BinaryDictInputOutput.readParentAddress(buffer, formatOptions);
+ skipString(buffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
+ if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) buffer.readUnsignedByte();
+ final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS
+ ? FormatSpec.NO_CHILDREN_ADDRESS : newChildrenAddress - buffer.position();
+ writeSInt24ToBuffer(buffer, childrenOffset);
+ buffer.position(originalPosition);
+ }
+
+ /**
+ * Write a char group to an output stream.
+ * A char group is an in-memory representation of a node in trie.
+ * A char group info is an on-disk representation of a node.
+ *
+ * @param destination the stream to write.
+ * @param info the char group info to be written.
+ * @return the size written, in bytes.
+ */
+ public static int writeCharGroup(final OutputStream destination, final CharGroupInfo info)
+ throws IOException {
+ int size = FormatSpec.GROUP_FLAGS_SIZE;
+ destination.write((byte)info.mFlags);
+ final int parentOffset = info.mParentAddress == FormatSpec.NO_PARENT_ADDRESS ?
+ FormatSpec.NO_PARENT_ADDRESS : info.mParentAddress - info.mOriginalAddress;
+ size += writeSInt24ToStream(destination, parentOffset);
+
+ for (int i = 0; i < info.mCharacters.length; ++i) {
+ if (CharEncoding.getCharSize(info.mCharacters[i]) == 1) {
+ destination.write((byte)info.mCharacters[i]);
+ size++;
+ } else {
+ size += writeSInt24ToStream(destination, info.mCharacters[i]);
+ }
+ }
+ if (info.mCharacters.length > 1) {
+ destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR);
+ size++;
+ }
+
+ if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
+ destination.write((byte)info.mFrequency);
+ size++;
+ }
+
+ if (DBG) {
+ MakedictLog.d("writeCharGroup origin=" + info.mOriginalAddress + ", size=" + size
+ + ", child=" + info.mChildrenAddress + ", characters ="
+ + new String(info.mCharacters, 0, info.mCharacters.length));
+ }
+ final int childrenOffset = info.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS ?
+ 0 : info.mChildrenAddress - (info.mOriginalAddress + size);
+ writeSInt24ToStream(destination, childrenOffset);
+ size += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
+
+ if (info.mShortcutTargets != null && info.mShortcutTargets.size() > 0) {
+ final int shortcutListSize =
+ BinaryDictInputOutput.getShortcutListSize(info.mShortcutTargets);
+ destination.write((byte)(shortcutListSize >> 8));
+ destination.write((byte)(shortcutListSize & 0xFF));
+ size += 2;
+ final Iterator<WeightedString> shortcutIterator = info.mShortcutTargets.iterator();
+ while (shortcutIterator.hasNext()) {
+ final WeightedString target = shortcutIterator.next();
+ destination.write((byte)BinaryDictInputOutput.makeShortcutFlags(
+ shortcutIterator.hasNext(), target.mFrequency));
+ size++;
+ size += writeString(destination, target.mWord);
+ }
+ }
+
+ if (info.mBigrams != null) {
+ // TODO: Consolidate this code with the code that computes the size of the bigram list
+ // in BinaryDictionaryInputOutput#computeActualNodeSize
+ for (int i = 0; i < info.mBigrams.size(); ++i) {
+
+ final int bigramFrequency = info.mBigrams.get(i).mFrequency;
+ int bigramFlags = (i < info.mBigrams.size() - 1)
+ ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0;
+ size++;
+ final int bigramOffset = info.mBigrams.get(i).mAddress - (info.mOriginalAddress
+ + size);
+ bigramFlags |= (bigramOffset < 0) ? FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE : 0;
+ switch (BinaryDictInputOutput.getByteSize(bigramOffset)) {
+ case 1:
+ bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
+ break;
+ case 2:
+ bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
+ break;
+ case 3:
+ bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+ break;
+ }
+ bigramFlags |= bigramFrequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY;
+ destination.write((byte)bigramFlags);
+ size += writeVariableAddress(destination, Math.abs(bigramOffset));
+ }
+ }
+ return size;
+ }
+
+ @SuppressWarnings("unused")
+ private static void updateForwardLink(final FusionDictionaryBufferInterface buffer,
+ final int nodeOriginAddress, final int newNodeAddress,
+ final FormatOptions formatOptions) {
+ buffer.position(nodeOriginAddress);
+ int jumpCount = 0;
+ while (jumpCount++ < MAX_JUMPS) {
+ final int count = BinaryDictInputOutput.readCharGroupCount(buffer);
+ for (int i = 0; i < count; ++i) skipCharGroup(buffer, formatOptions);
+ final int forwardLinkAddress = buffer.readUnsignedInt24();
+ if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+ buffer.position(buffer.position() - FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
+ writeSInt24ToBuffer(buffer, newNodeAddress);
+ return;
+ }
+ buffer.position(forwardLinkAddress);
+ }
+ if (DBG && jumpCount >= MAX_JUMPS) {
+ throw new RuntimeException("too many jumps, probably a bug.");
+ }
+ }
+
+ /**
+ * Helper method to move a char group to the tail of the file.
+ */
+ private static int moveCharGroup(final OutputStream destination,
+ final FusionDictionaryBufferInterface buffer, final CharGroupInfo info,
+ final int nodeOriginAddress, final int oldGroupAddress,
+ final FormatOptions formatOptions) throws IOException {
+ updateParentAddress(buffer, oldGroupAddress, buffer.limit() + 1, formatOptions);
+ buffer.position(oldGroupAddress);
+ final int currentFlags = buffer.readUnsignedByte();
+ buffer.position(oldGroupAddress);
+ buffer.put((byte)(FormatSpec.FLAG_IS_MOVED | (currentFlags
+ & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG))));
+ int size = FormatSpec.GROUP_FLAGS_SIZE;
+ updateForwardLink(buffer, nodeOriginAddress, buffer.limit(), formatOptions);
+ size += writeNode(destination, new CharGroupInfo[] { info });
+ return size;
+ }
+
+ /**
+ * Compute the size of the char group.
+ */
+ private static int computeGroupSize(final CharGroupInfo info,
+ final FormatOptions formatOptions) {
+ int size = FormatSpec.GROUP_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
+ + BinaryDictInputOutput.getGroupCharactersSize(info.mCharacters)
+ + BinaryDictInputOutput.getChildrenAddressSize(info.mFlags, formatOptions);
+ if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
+ size += FormatSpec.GROUP_FREQUENCY_SIZE;
+ }
+ if (info.mShortcutTargets != null && !info.mShortcutTargets.isEmpty()) {
+ size += BinaryDictInputOutput.getShortcutListSize(info.mShortcutTargets);
+ }
+ if (info.mBigrams != null) {
+ for (final PendingAttribute attr : info.mBigrams) {
+ size += FormatSpec.GROUP_FLAGS_SIZE;
+ size += BinaryDictInputOutput.getByteSize(attr.mAddress);
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Write a node to the stream.
+ *
+ * @param destination the stream to write.
+ * @param infos groups to be written.
+ * @return the size written, in bytes.
+ * @throws IOException
+ */
+ private static int writeNode(final OutputStream destination, final CharGroupInfo[] infos)
+ throws IOException {
+ int size = BinaryDictInputOutput.getGroupCountSize(infos.length);
+ switch (BinaryDictInputOutput.getGroupCountSize(infos.length)) {
+ case 1:
+ destination.write((byte)infos.length);
+ break;
+ case 2:
+ destination.write((byte)(infos.length >> 8));
+ destination.write((byte)(infos.length & 0xFF));
+ break;
+ default:
+ throw new RuntimeException("Invalid group count size.");
+ }
+ for (final CharGroupInfo info : infos) size += writeCharGroup(destination, info);
+ writeSInt24ToStream(destination, FormatSpec.NO_FORWARD_LINK_ADDRESS);
+ return size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+ }
+
+ /**
+ * Move a group that is referred to by oldGroupOrigin to the tail of the file.
+ * And set the children address to the byte after the group.
+ *
+ * @param nodeOrigin the address of the tail of the file.
+ * @param characters
+ * @param length
+ * @param flags
+ * @param frequency
+ * @param parentAddress
+ * @param shortcutTargets
+ * @param bigrams
+ * @param destination the stream representing the tail of the file.
+ * @param buffer the buffer representing the (constant-size) body of the file.
+ * @param oldNodeOrigin
+ * @param oldGroupOrigin
+ * @param formatOptions
+ * @return the size written, in bytes.
+ * @throws IOException
+ */
+ private static int moveGroup(final int nodeOrigin, final int[] characters, final int length,
+ final int flags, final int frequency, final int parentAddress,
+ final ArrayList<WeightedString> shortcutTargets,
+ final ArrayList<PendingAttribute> bigrams, final OutputStream destination,
+ final FusionDictionaryBufferInterface buffer, final int oldNodeOrigin,
+ final int oldGroupOrigin, final FormatOptions formatOptions) throws IOException {
+ int size = 0;
+ final int newGroupOrigin = nodeOrigin + 1;
+ final int[] writtenCharacters = Arrays.copyOfRange(characters, 0, length);
+ final CharGroupInfo tmpInfo = new CharGroupInfo(newGroupOrigin, -1 /* endAddress */,
+ flags, writtenCharacters, frequency, parentAddress, FormatSpec.NO_CHILDREN_ADDRESS,
+ shortcutTargets, bigrams);
+ size = computeGroupSize(tmpInfo, formatOptions);
+ final CharGroupInfo newInfo = new CharGroupInfo(newGroupOrigin, newGroupOrigin + size,
+ flags, writtenCharacters, frequency, parentAddress,
+ nodeOrigin + 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE, shortcutTargets,
+ bigrams);
+ moveCharGroup(destination, buffer, newInfo, oldNodeOrigin, oldGroupOrigin, formatOptions);
+ return 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+ }
+
+ /**
+ * Insert a word into a binary dictionary.
+ *
+ * @param buffer
+ * @param destination
+ * @param word
+ * @param frequency
+ * @param bigramStrings
+ * @param shortcuts
+ * @throws IOException
+ * @throws UnsupportedFormatException
+ */
+ // TODO: Support batch insertion.
+ // TODO: Remove @UsedForTesting once UserHistoryDictionary is implemented by BinaryDictionary.
+ @UsedForTesting
+ public static void insertWord(final FusionDictionaryBufferInterface buffer,
+ final OutputStream destination, final String word, final int frequency,
+ final ArrayList<WeightedString> bigramStrings,
+ final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
+ final boolean isBlackListEntry)
+ throws IOException, UnsupportedFormatException {
+ final ArrayList<PendingAttribute> bigrams = new ArrayList<PendingAttribute>();
+ if (bigramStrings != null) {
+ for (final WeightedString bigram : bigramStrings) {
+ int position = getTerminalPosition(buffer, bigram.mWord);
+ if (position == FormatSpec.NOT_VALID_WORD) {
+ // TODO: figure out what is the correct thing to do here.
+ } else {
+ bigrams.add(new PendingAttribute(bigram.mFrequency, position));
+ }
+ }
+ }
+
+ final boolean isTerminal = true;
+ final boolean hasBigrams = !bigrams.isEmpty();
+ final boolean hasShortcuts = shortcuts != null && !shortcuts.isEmpty();
+
+ // find the insert position of the word.
+ if (buffer.position() != 0) buffer.position(0);
+ final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
+
+ int wordPos = 0, address = buffer.position(), nodeOriginAddress = buffer.position();
+ final int[] codePoints = FusionDictionary.getCodePoints(word);
+ final int wordLen = codePoints.length;
+
+ for (int depth = 0; depth < Constants.Dictionary.MAX_WORD_LENGTH; ++depth) {
+ if (wordPos >= wordLen) break;
+ nodeOriginAddress = buffer.position();
+ int nodeParentAddress = -1;
+ final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer);
+ boolean foundNextGroup = false;
+
+ for (int i = 0; i < charGroupCount; ++i) {
+ address = buffer.position();
+ final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer,
+ buffer.position(), header.mFormatOptions);
+ final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags,
+ header.mFormatOptions);
+ if (isMovedGroup) continue;
+ nodeParentAddress = (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS)
+ ? FormatSpec.NO_PARENT_ADDRESS : currentInfo.mParentAddress + address;
+ boolean matched = true;
+ for (int p = 0; p < currentInfo.mCharacters.length; ++p) {
+ if (wordPos + p >= wordLen) {
+ /*
+ * splitting
+ * before
+ * abcd - ef
+ *
+ * insert "abc"
+ *
+ * after
+ * abc - d - ef
+ */
+ final int newNodeAddress = buffer.limit();
+ final int flags = BinaryDictInputOutput.makeCharGroupFlags(p > 1,
+ isTerminal, 0, hasShortcuts, hasBigrams, false /* isNotAWord */,
+ false /* isBlackListEntry */, header.mFormatOptions);
+ int written = moveGroup(newNodeAddress, currentInfo.mCharacters, p, flags,
+ frequency, nodeParentAddress, shortcuts, bigrams, destination,
+ buffer, nodeOriginAddress, address, header.mFormatOptions);
+
+ final int[] characters2 = Arrays.copyOfRange(currentInfo.mCharacters, p,
+ currentInfo.mCharacters.length);
+ if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
+ updateParentAddresses(buffer, currentInfo.mChildrenAddress,
+ newNodeAddress + written + 1, header.mFormatOptions);
+ }
+ final CharGroupInfo newInfo2 = new CharGroupInfo(
+ newNodeAddress + written + 1, -1 /* endAddress */,
+ currentInfo.mFlags, characters2, currentInfo.mFrequency,
+ newNodeAddress + 1, currentInfo.mChildrenAddress,
+ currentInfo.mShortcutTargets, currentInfo.mBigrams);
+ writeNode(destination, new CharGroupInfo[] { newInfo2 });
+ return;
+ } else if (codePoints[wordPos + p] != currentInfo.mCharacters[p]) {
+ if (p > 0) {
+ /*
+ * splitting
+ * before
+ * ab - cd
+ *
+ * insert "ac"
+ *
+ * after
+ * a - b - cd
+ * |
+ * - c
+ */
+
+ final int newNodeAddress = buffer.limit();
+ final int childrenAddress = currentInfo.mChildrenAddress;
+
+ // move prefix
+ final int prefixFlags = BinaryDictInputOutput.makeCharGroupFlags(p > 1,
+ false /* isTerminal */, 0 /* childrenAddressSize*/,
+ false /* hasShortcut */, false /* hasBigrams */,
+ false /* isNotAWord */, false /* isBlackListEntry */,
+ header.mFormatOptions);
+ int written = moveGroup(newNodeAddress, currentInfo.mCharacters, p,
+ prefixFlags, -1 /* frequency */, nodeParentAddress, null, null,
+ destination, buffer, nodeOriginAddress, address,
+ header.mFormatOptions);
+
+ final int[] suffixCharacters = Arrays.copyOfRange(
+ currentInfo.mCharacters, p, currentInfo.mCharacters.length);
+ if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
+ updateParentAddresses(buffer, currentInfo.mChildrenAddress,
+ newNodeAddress + written + 1, header.mFormatOptions);
+ }
+ final int suffixFlags = BinaryDictInputOutput.makeCharGroupFlags(
+ suffixCharacters.length > 1,
+ (currentInfo.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0,
+ 0 /* childrenAddressSize */,
+ (currentInfo.mFlags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)
+ != 0,
+ (currentInfo.mFlags & FormatSpec.FLAG_HAS_BIGRAMS) != 0,
+ isNotAWord, isBlackListEntry, header.mFormatOptions);
+ final CharGroupInfo suffixInfo = new CharGroupInfo(
+ newNodeAddress + written + 1, -1 /* endAddress */, suffixFlags,
+ suffixCharacters, currentInfo.mFrequency, newNodeAddress + 1,
+ currentInfo.mChildrenAddress, currentInfo.mShortcutTargets,
+ currentInfo.mBigrams);
+ written += computeGroupSize(suffixInfo, header.mFormatOptions) + 1;
+
+ final int[] newCharacters = Arrays.copyOfRange(codePoints, wordPos + p,
+ codePoints.length);
+ final int flags = BinaryDictInputOutput.makeCharGroupFlags(
+ newCharacters.length > 1, isTerminal,
+ 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+ isNotAWord, isBlackListEntry, header.mFormatOptions);
+ final CharGroupInfo newInfo = new CharGroupInfo(
+ newNodeAddress + written, -1 /* endAddress */, flags,
+ newCharacters, frequency, newNodeAddress + 1,
+ FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
+ writeNode(destination, new CharGroupInfo[] { suffixInfo, newInfo });
+ return;
+ }
+ matched = false;
+ break;
+ }
+ }
+
+ if (matched) {
+ if (wordPos + currentInfo.mCharacters.length == wordLen) {
+ // the word exists in the dictionary.
+ // only update group.
+ final int newNodeAddress = buffer.limit();
+ final boolean hasMultipleChars = currentInfo.mCharacters.length > 1;
+ final int flags = BinaryDictInputOutput.makeCharGroupFlags(hasMultipleChars,
+ isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+ isNotAWord, isBlackListEntry, header.mFormatOptions);
+ final CharGroupInfo newInfo = new CharGroupInfo(newNodeAddress + 1,
+ -1 /* endAddress */, flags, currentInfo.mCharacters, frequency,
+ nodeParentAddress, currentInfo.mChildrenAddress, shortcuts,
+ bigrams);
+ moveCharGroup(destination, buffer, newInfo, nodeOriginAddress, address,
+ header.mFormatOptions);
+ return;
+ }
+ wordPos += currentInfo.mCharacters.length;
+ if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) {
+ /*
+ * found the prefix of the word.
+ * make new node and link to the node from this group.
+ *
+ * before
+ * ab - cd
+ *
+ * insert "abcde"
+ *
+ * after
+ * ab - cd - e
+ */
+ final int newNodeAddress = buffer.limit();
+ updateChildrenAddress(buffer, address, newNodeAddress,
+ header.mFormatOptions);
+ final int newGroupAddress = newNodeAddress + 1;
+ final boolean hasMultipleChars = (wordLen - wordPos) > 1;
+ final int flags = BinaryDictInputOutput.makeCharGroupFlags(hasMultipleChars,
+ isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+ isNotAWord, isBlackListEntry, header.mFormatOptions);
+ final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
+ final CharGroupInfo newInfo = new CharGroupInfo(newGroupAddress, -1, flags,
+ characters, frequency, address, FormatSpec.NO_CHILDREN_ADDRESS,
+ shortcuts, bigrams);
+ writeNode(destination, new CharGroupInfo[] { newInfo });
+ return;
+ }
+ buffer.position(currentInfo.mChildrenAddress);
+ foundNextGroup = true;
+ break;
+ }
+ }
+
+ if (foundNextGroup) continue;
+
+ // reached the end of the array.
+ final int linkAddressPosition = buffer.position();
+ int nextLink = buffer.readUnsignedInt24();
+ if ((nextLink & MSB24) != 0) {
+ nextLink = -(nextLink & SINT24_MAX);
+ }
+ if (nextLink == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+ /*
+ * expand this node.
+ *
+ * before
+ * ab - cd
+ *
+ * insert "abef"
+ *
+ * after
+ * ab - cd
+ * |
+ * - ef
+ */
+
+ // change the forward link address.
+ final int newNodeAddress = buffer.limit();
+ buffer.position(linkAddressPosition);
+ writeSInt24ToBuffer(buffer, newNodeAddress);
+
+ final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
+ final int flags = BinaryDictInputOutput.makeCharGroupFlags(characters.length > 1,
+ isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+ isNotAWord, isBlackListEntry, header.mFormatOptions);
+ final CharGroupInfo newInfo = new CharGroupInfo(newNodeAddress + 1,
+ -1 /* endAddress */, flags, characters, frequency, nodeParentAddress,
+ FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
+ writeNode(destination, new CharGroupInfo[]{ newInfo });
+ return;
+ } else {
+ depth--;
+ buffer.position(nextLink);
+ }
+ }
+ }
+
+ /**
+ * Find a word from the buffer.
+ *
+ * @param buffer the buffer representing the body of the dictionary file.
+ * @param word the word searched
+ * @return the found group
+ * @throws IOException
+ * @throws UnsupportedFormatException
+ */
+ @UsedForTesting
+ public static CharGroupInfo findWordFromBuffer(final FusionDictionaryBufferInterface buffer,
+ final String word) throws IOException, UnsupportedFormatException {
+ int position = getTerminalPosition(buffer, word);
+ if (position != FormatSpec.NOT_VALID_WORD) {
+ buffer.position(0);
+ final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
+ buffer.position(position);
+ return BinaryDictInputOutput.readCharGroup(buffer, position, header.mFormatOptions);
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method to read the header of a binary file.
+ *
+ * This is quite resource intensive - don't call when performance is critical.
+ *
+ * @param file The file to read.
+ * @param offset The offset in the file where to start reading the data.
+ * @param length The length of the data file.
+ */
+ private static final int HEADER_READING_BUFFER_SIZE = 16384;
+ public static FileHeader getDictionaryFileHeader(
+ final File file, final long offset, final long length)
+ throws FileNotFoundException, IOException, UnsupportedFormatException {
+ final byte[] buffer = new byte[HEADER_READING_BUFFER_SIZE];
+ final FileInputStream inStream = new FileInputStream(file);
+ try {
+ inStream.read(buffer);
+ final BinaryDictInputOutput.ByteBufferWrapper wrapper =
+ new BinaryDictInputOutput.ByteBufferWrapper(inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, offset, length));
+ return BinaryDictInputOutput.readHeader(wrapper);
+ } finally {
+ inStream.close();
+ }
+ }
+
+ public static FileHeader getDictionaryFileHeaderOrNull(final File file, final long offset,
+ final long length) {
+ try {
+ final FileHeader header = getDictionaryFileHeader(file, offset, length);
+ return header;
+ } catch (UnsupportedFormatException e) {
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index b431a4da9..58ec1e83f 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -1,21 +1,22 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.makedict;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
@@ -55,6 +56,7 @@ public final class BinaryDictInputOutput {
private static final int MAX_PASSES = 24;
private static final int MAX_JUMPS = 12;
+ @UsedForTesting
public interface FusionDictionaryBufferInterface {
public int readUnsignedByte();
public int readUnsignedShort();
@@ -76,12 +78,12 @@ public final class BinaryDictInputOutput {
@Override
public int readUnsignedByte() {
- return ((int)mBuffer.get()) & 0xFF;
+ return mBuffer.get() & 0xFF;
}
@Override
public int readUnsignedShort() {
- return ((int)mBuffer.getShort()) & 0xFFFF;
+ return mBuffer.getShort() & 0xFFFF;
}
@Override
@@ -124,8 +126,7 @@ public final class BinaryDictInputOutput {
/**
* A class grouping utility function for our specific character encoding.
*/
- private static final class CharEncoding {
-
+ static final class CharEncoding {
private static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
private static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF;
@@ -154,7 +155,7 @@ public final class BinaryDictInputOutput {
* @param character the character code.
* @return the size in binary encoded-form, either 1 or 3 bytes.
*/
- private static int getCharSize(final int character) {
+ static int getCharSize(final int character) {
// See char encoding in FusionDictionary.java
if (fitsOnOneByte(character)) return 1;
if (FormatSpec.INVALID_CHARACTER == character) return 1;
@@ -263,7 +264,7 @@ public final class BinaryDictInputOutput {
* @param buffer the buffer, positioned over an encoded character.
* @return the character code.
*/
- private static int readChar(final FusionDictionaryBufferInterface buffer) {
+ static int readChar(final FusionDictionaryBufferInterface buffer) {
int character = buffer.readUnsignedByte();
if (!fitsOnOneByte(character)) {
if (FormatSpec.GROUP_CHARACTERS_TERMINATOR == character) {
@@ -277,6 +278,21 @@ public final class BinaryDictInputOutput {
}
/**
+ * Compute the binary size of the character array.
+ *
+ * If only one character, this is the size of this character. If many, it's the sum of their
+ * sizes + 1 byte for the terminator.
+ *
+ * @param characters the character array
+ * @return the size of the char array, including the terminator if any
+ */
+ static int getGroupCharactersSize(final int[] characters) {
+ int size = CharEncoding.getCharArraySize(characters);
+ if (characters.length > 1) size += FormatSpec.GROUP_TERMINATOR_SIZE;
+ return size;
+ }
+
+ /**
* Compute the binary size of the character array in a group
*
* If only one character, this is the size of this character. If many, it's the sum of their
@@ -286,9 +302,7 @@ public final class BinaryDictInputOutput {
* @return the size of the char array, including the terminator if any
*/
private static int getGroupCharactersSize(final CharGroup group) {
- int size = CharEncoding.getCharArraySize(group.mChars);
- if (group.hasSeveralChars()) size += FormatSpec.GROUP_TERMINATOR_SIZE;
- return size;
+ return getGroupCharactersSize(group.mChars);
}
/**
@@ -338,7 +352,7 @@ public final class BinaryDictInputOutput {
* This is known in advance and does not change according to position in the file
* like address lists do.
*/
- private static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) {
+ static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) {
if (null == shortcutList) return 0;
int size = FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE;
for (final WeightedString shortcut : shortcutList) {
@@ -399,7 +413,16 @@ public final class BinaryDictInputOutput {
* Helper method to check whether the group is moved.
*/
public static boolean isMovedGroup(final int flags, final FormatOptions options) {
- return options.mSupportsDynamicUpdate && ((flags & FormatSpec.FLAG_IS_MOVED) == 1);
+ return options.mSupportsDynamicUpdate
+ && ((flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) == FormatSpec.FLAG_IS_MOVED);
+ }
+
+ /**
+ * Helper method to check whether the group is deleted.
+ */
+ public static boolean isDeletedGroup(final int flags, final FormatOptions formatOptions) {
+ return formatOptions.mSupportsDynamicUpdate
+ && ((flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) == FormatSpec.FLAG_IS_DELETED);
}
/**
@@ -439,7 +462,7 @@ public final class BinaryDictInputOutput {
* @param address the address
* @return the byte size.
*/
- private static int getByteSize(final int address) {
+ static int getByteSize(final int address) {
assert(address <= UINT24_MAX);
if (!hasChildrenAddress(address)) {
return 0;
@@ -452,11 +475,8 @@ public final class BinaryDictInputOutput {
}
}
- private static final int SINT8_MAX = 0x7F;
- private static final int SINT16_MAX = 0x7FFF;
private static final int SINT24_MAX = 0x7FFFFF;
private static final int MSB8 = 0x80;
- private static final int MSB16 = 0x8000;
private static final int MSB24 = 0x800000;
// End utility methods.
@@ -721,53 +741,60 @@ public final class BinaryDictInputOutput {
return 3;
}
- private static byte makeCharGroupFlags(final CharGroup group, final int groupAddress,
- final int childrenOffset, final FormatOptions formatOptions) {
+ /**
+ * Makes the flag value for a char group.
+ *
+ * @param hasMultipleChars whether the group has multiple chars.
+ * @param isTerminal whether the group is terminal.
+ * @param childrenAddressSize the size of a children address.
+ * @param hasShortcuts whether the group has shortcuts.
+ * @param hasBigrams whether the group has bigrams.
+ * @param isNotAWord whether the group is not a word.
+ * @param isBlackListEntry whether the group is a blacklist entry.
+ * @param formatOptions file format options.
+ * @return the flags
+ */
+ static int makeCharGroupFlags(final boolean hasMultipleChars, final boolean isTerminal,
+ final int childrenAddressSize, final boolean hasShortcuts, final boolean hasBigrams,
+ final boolean isNotAWord, final boolean isBlackListEntry,
+ final FormatOptions formatOptions) {
byte flags = 0;
- if (group.mChars.length > 1) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS;
- if (group.mFrequency >= 0) {
- flags |= FormatSpec.FLAG_IS_TERMINAL;
- }
- if (null != group.mChildren) {
- final int byteSize = formatOptions.mSupportsDynamicUpdate
- ? FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE : getByteSize(childrenOffset);
- switch (byteSize) {
- case 1:
- flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE;
- break;
- case 2:
- flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES;
- break;
- case 3:
- flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES;
- break;
- default:
- throw new RuntimeException("Node with a strange address");
- }
- } else if (formatOptions.mSupportsDynamicUpdate) {
- flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES;
- }
- if (null != group.mShortcutTargets) {
- if (DBG && 0 == group.mShortcutTargets.size()) {
- throw new RuntimeException("0-sized shortcut list must be null");
- }
- flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS;
- }
- if (null != group.mBigrams) {
- if (DBG && 0 == group.mBigrams.size()) {
- throw new RuntimeException("0-sized bigram list must be null");
+ if (hasMultipleChars) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS;
+ if (isTerminal) flags |= FormatSpec.FLAG_IS_TERMINAL;
+ if (formatOptions.mSupportsDynamicUpdate) {
+ flags |= FormatSpec.FLAG_IS_NOT_MOVED;
+ } else if (true) {
+ switch (childrenAddressSize) {
+ case 1:
+ flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE;
+ break;
+ case 2:
+ flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES;
+ break;
+ case 3:
+ flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES;
+ break;
+ case 0:
+ flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS;
+ break;
+ default:
+ throw new RuntimeException("Node with a strange address");
}
- flags |= FormatSpec.FLAG_HAS_BIGRAMS;
- }
- if (group.mIsNotAWord) {
- flags |= FormatSpec.FLAG_IS_NOT_A_WORD;
- }
- if (group.mIsBlacklistEntry) {
- flags |= FormatSpec.FLAG_IS_BLACKLISTED;
}
+ if (hasShortcuts) flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS;
+ if (hasBigrams) flags |= FormatSpec.FLAG_HAS_BIGRAMS;
+ if (isNotAWord) flags |= FormatSpec.FLAG_IS_NOT_A_WORD;
+ if (isBlackListEntry) flags |= FormatSpec.FLAG_IS_BLACKLISTED;
return flags;
}
+ private static byte makeCharGroupFlags(final CharGroup group, final int groupAddress,
+ final int childrenOffset, final FormatOptions formatOptions) {
+ return (byte) makeCharGroupFlags(group.mChars.length > 1, group.mFrequency >= 0,
+ getByteSize(childrenOffset), group.mShortcutTargets != null, group.mBigrams != null,
+ group.mIsNotAWord, group.mIsBlacklistEntry, formatOptions);
+ }
+
/**
* Makes the flag value for a bigram.
*
@@ -859,7 +886,7 @@ public final class BinaryDictInputOutput {
* @param frequency the frequency of the attribute, 0..15
* @return the flags
*/
- private static final int makeShortcutFlags(final boolean more, final int frequency) {
+ static final int makeShortcutFlags(final boolean more, final int frequency) {
return (more ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0)
+ (frequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY);
}
@@ -895,8 +922,10 @@ public final class BinaryDictInputOutput {
* @param formatOptions file format options.
* @return the address of the END of the node.
*/
+ @SuppressWarnings("unused")
private static int writePlacedNode(final FusionDictionary dict, byte[] buffer,
final Node node, final FormatOptions formatOptions) {
+ // TODO: Make the code in common with BinaryDictIOUtils#writeCharGroup
int index = node.mCachedAddress;
final int groupCount = node.mData.size();
@@ -1178,7 +1207,7 @@ public final class BinaryDictInputOutput {
// Input methods: Read a binary dictionary to memory.
// readDictionaryBinary is the public entry point for them.
- private static int getChildrenAddressSize(final int optionFlags,
+ static int getChildrenAddressSize(final int optionFlags,
final FormatOptions formatOptions) {
if (formatOptions.mSupportsDynamicUpdate) return FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
switch (optionFlags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) {
@@ -1194,7 +1223,7 @@ public final class BinaryDictInputOutput {
}
}
- private static int readChildrenAddress(final FusionDictionaryBufferInterface buffer,
+ static int readChildrenAddress(final FusionDictionaryBufferInterface buffer,
final int optionFlags, final FormatOptions options) {
if (options.mSupportsDynamicUpdate) {
final int address = buffer.readUnsignedInt24();
@@ -1219,7 +1248,7 @@ public final class BinaryDictInputOutput {
}
}
- private static int readParentAddress(final FusionDictionaryBufferInterface buffer,
+ static int readParentAddress(final FusionDictionaryBufferInterface buffer,
final FormatOptions formatOptions) {
if (supportsDynamicUpdate(formatOptions)) {
final int parentAddress = buffer.readUnsignedInt24();
@@ -1290,7 +1319,8 @@ public final class BinaryDictInputOutput {
ArrayList<PendingAttribute> bigrams = null;
if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
bigrams = new ArrayList<PendingAttribute>();
- while (true) {
+ int bigramCount = 0;
+ while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
final int bigramFlags = buffer.readUnsignedByte();
++addressPointer;
final int sign = 0 == (bigramFlags & FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE)
@@ -1318,6 +1348,9 @@ public final class BinaryDictInputOutput {
bigramAddress));
if (0 == (bigramFlags & FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT)) break;
}
+ if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
+ MakedictLog.d("too many bigrams in a group.");
+ }
}
return new CharGroupInfo(originalGroupAddress, addressPointer, flags, characters, frequency,
parentAddress, childrenAddress, shortcutTargets, bigrams);
@@ -1340,7 +1373,8 @@ public final class BinaryDictInputOutput {
// of this method. Since it performs direct, unbuffered random access to the file and
// may be called hundreds of thousands of times, the resulting performance is not
// reasonable without some kind of cache. Thus:
- private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>();
+ private static TreeMap<Integer, WeightedString> wordCache =
+ new TreeMap<Integer, WeightedString>();
/**
* Finds, as a string, the word at the address passed as an argument.
*
@@ -1348,15 +1382,15 @@ public final class BinaryDictInputOutput {
* @param headerSize the size of the header.
* @param address the address to seek.
* @param formatOptions file format options.
- * @return the word, as a string.
+ * @return the word with its frequency, as a weighted string.
*/
- /* packages for tests */ static String getWordAtAddress(
+ /* package for tests */ static WeightedString getWordAtAddress(
final FusionDictionaryBufferInterface buffer, final int headerSize, final int address,
final FormatOptions formatOptions) {
- final String cachedString = wordCache.get(address);
+ final WeightedString cachedString = wordCache.get(address);
if (null != cachedString) return cachedString;
- final String result;
+ final WeightedString result;
final int originalPointer = buffer.position();
buffer.position(address);
@@ -1372,14 +1406,16 @@ public final class BinaryDictInputOutput {
return result;
}
+ // TODO: static!? This will behave erratically when used in multi-threaded code.
+ // We need to fix this
private static int[] sGetWordBuffer = new int[FormatSpec.MAX_WORD_LENGTH];
- private static String getWordAtAddressWithParentAddress(
+ @SuppressWarnings("unused")
+ private static WeightedString getWordAtAddressWithParentAddress(
final FusionDictionaryBufferInterface buffer, final int headerSize, final int address,
final FormatOptions options) {
- final StringBuilder builder = new StringBuilder();
-
int currentAddress = address;
int index = FormatSpec.MAX_WORD_LENGTH - 1;
+ int frequency = Integer.MIN_VALUE;
// the length of the path from the root to the leaf is limited by MAX_WORD_LENGTH
for (int count = 0; count < FormatSpec.MAX_WORD_LENGTH; ++count) {
CharGroupInfo currentInfo;
@@ -1394,6 +1430,7 @@ public final class BinaryDictInputOutput {
MakedictLog.d("Too many jumps - probably a bug");
}
} while (isMovedGroup(currentInfo.mFlags, options));
+ if (Integer.MIN_VALUE == frequency) frequency = currentInfo.mFrequency;
for (int i = 0; i < currentInfo.mCharacters.length; ++i) {
sGetWordBuffer[index--] =
currentInfo.mCharacters[currentInfo.mCharacters.length - i - 1];
@@ -1402,17 +1439,19 @@ public final class BinaryDictInputOutput {
currentAddress = currentInfo.mParentAddress + currentInfo.mOriginalAddress;
}
- return new String(sGetWordBuffer, index + 1, FormatSpec.MAX_WORD_LENGTH - index - 1);
+ return new WeightedString(
+ new String(sGetWordBuffer, index + 1, FormatSpec.MAX_WORD_LENGTH - index - 1),
+ frequency);
}
- private static String getWordAtAddressWithoutParentAddress(
+ private static WeightedString getWordAtAddressWithoutParentAddress(
final FusionDictionaryBufferInterface buffer, final int headerSize, final int address,
final FormatOptions options) {
buffer.position(headerSize);
final int count = readCharGroupCount(buffer);
int groupOffset = getGroupCountSize(count);
final StringBuilder builder = new StringBuilder();
- String result = null;
+ WeightedString result = null;
CharGroupInfo last = null;
for (int i = count - 1; i >= 0; --i) {
@@ -1420,7 +1459,7 @@ public final class BinaryDictInputOutput {
groupOffset = info.mEndAddress;
if (info.mOriginalAddress == address) {
builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
- result = builder.toString();
+ result = new WeightedString(builder.toString(), info.mFrequency);
break; // and return
}
if (hasChildrenAddress(info.mChildrenAddress)) {
@@ -1481,9 +1520,11 @@ public final class BinaryDictInputOutput {
if (null != info.mBigrams) {
bigrams = new ArrayList<WeightedString>();
for (PendingAttribute bigram : info.mBigrams) {
- final String word = getWordAtAddress(
+ final WeightedString word = getWordAtAddress(
buffer, headerSize, bigram.mAddress, options);
- bigrams.add(new WeightedString(word, bigram.mFrequency));
+ final int reconstructedFrequency =
+ reconstructBigramFrequency(word.mFrequency, bigram.mFrequency);
+ bigrams.add(new WeightedString(word.mWord, reconstructedFrequency));
}
}
if (hasChildrenAddress(info.mChildrenAddress)) {
@@ -1618,6 +1659,7 @@ public final class BinaryDictInputOutput {
* @param dict an optional dictionary to add words to, or null.
* @return the created (or merged) dictionary.
*/
+ @UsedForTesting
public static FusionDictionary readDictionaryBinary(
final FusionDictionaryBufferInterface buffer, final FusionDictionary dict)
throws IOException, UnsupportedFormatException {
@@ -1655,17 +1697,24 @@ public final class BinaryDictInputOutput {
}
/**
+ * Helper method to pass a file name instead of a File object to isBinaryDictionary.
+ */
+ public static boolean isBinaryDictionary(final String filename) {
+ final File file = new File(filename);
+ return isBinaryDictionary(file);
+ }
+
+ /**
* Basic test to find out whether the file is a binary dictionary or not.
*
* Concretely this only tests the magic number.
*
- * @param filename The name of the file to test.
+ * @param file The file to test.
* @return true if it's a binary dictionary, false otherwise
*/
- public static boolean isBinaryDictionary(final String filename) {
+ public static boolean isBinaryDictionary(final File file) {
FileInputStream inStream = null;
try {
- final File file = new File(filename);
inStream = new FileInputStream(file);
final ByteBuffer buffer = inStream.getChannel().map(
FileChannel.MapMode.READ_ONLY, 0, file.length());
@@ -1700,8 +1749,7 @@ public final class BinaryDictInputOutput {
final int bigramFrequency) {
final float stepSize = (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency)
/ (1.5f + FormatSpec.MAX_BIGRAM_FREQUENCY);
- final float resultFreqFloat = (float)unigramFrequency
- + stepSize * (bigramFrequency + 1.0f);
+ final float resultFreqFloat = unigramFrequency + stepSize * (bigramFrequency + 1.0f);
return (int)resultFreqFloat;
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java b/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
index 8e64082e6..b3617443e 100644
--- a/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
+++ b/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index b3fbb9fb5..e1e5e5500 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.makedict;
@@ -59,9 +59,11 @@ public final class FormatSpec {
* l | 10 = 2 bytes : FLAG_GROUP_ADDRESS_TYPE_TWOBYTES
* a | 11 = 3 bytes : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES
* g | ELSE
- * s | is moved ? 2 bits, 11 = no
- * | 01 = yes
+ * s | is moved ? 2 bits, 11 = no : FLAG_IS_NOT_MOVED
+ * | This must be the same as FLAG_GROUP_ADDRESS_TYPE_THREEBYTES
+ * | 01 = yes : FLAG_IS_MOVED
* | the new address is stored in the same place as the parent address
+ * | is deleted? 10 = yes : FLAG_IS_DELETED
* | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS
* | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL
* | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS
@@ -170,6 +172,7 @@ public final class FormatSpec {
static final int PARENT_ADDRESS_SIZE = 3;
static final int FORWARD_LINK_ADDRESS_SIZE = 3;
+ // These flags are used only in the static dictionary.
static final int MASK_GROUP_ADDRESS_TYPE = 0xC0;
static final int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
static final int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
@@ -183,7 +186,13 @@ public final class FormatSpec {
static final int FLAG_HAS_BIGRAMS = 0x04;
static final int FLAG_IS_NOT_A_WORD = 0x02;
static final int FLAG_IS_BLACKLISTED = 0x01;
- static final int FLAG_IS_MOVED = 0x40;
+
+ // These flags are used only in the dynamic dictionary.
+ static final int MASK_MOVE_AND_DELETE_FLAG = 0xC0;
+ static final int FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE = 0x40;
+ static final int FLAG_IS_MOVED = 0x00 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
+ static final int FLAG_IS_NOT_MOVED = 0x80 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
+ static final int FLAG_IS_DELETED = 0x80;
static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
static final int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
@@ -210,10 +219,13 @@ public final class FormatSpec {
static final int MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT = 0x7F; // 127
static final int MAX_CHARGROUPS_IN_A_NODE = 0x7FFF; // 32767
+ static final int MAX_BIGRAMS_IN_A_GROUP = 10000;
static final int MAX_TERMINAL_FREQUENCY = 255;
static final int MAX_BIGRAM_FREQUENCY = 15;
+ public static final int SHORTCUT_WHITELIST_FREQUENCY = 15;
+
// This option needs to be the same numeric value as the one in binary_format.h.
static final int NOT_VALID_WORD = -99;
static final int SIGNED_CHILDREN_ADDRESS_SIZE = 3;
@@ -240,16 +252,42 @@ public final class FormatSpec {
/**
* Class representing file header.
*/
- static final class FileHeader {
+ public static final class FileHeader {
public final int mHeaderSize;
public final DictionaryOptions mDictionaryOptions;
public final FormatOptions mFormatOptions;
+ private static final String DICTIONARY_VERSION_ATTRIBUTE = "version";
+ private static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale";
+ private static final String DICTIONARY_ID_ATTRIBUTE = "dictionary";
+ private static final String DICTIONARY_DESCRIPTION_ATTRIBUTE = "description";
public FileHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
final FormatOptions formatOptions) {
mHeaderSize = headerSize;
mDictionaryOptions = dictionaryOptions;
mFormatOptions = formatOptions;
}
+
+ // Helper method to get the locale as a String
+ public String getLocaleString() {
+ return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_LOCALE_ATTRIBUTE);
+ }
+
+ // Helper method to get the version String
+ public String getVersion() {
+ return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_VERSION_ATTRIBUTE);
+ }
+
+ // Helper method to get the dictionary ID as a String
+ public String getId() {
+ return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_ID_ATTRIBUTE);
+ }
+
+ // Helper method to get the description
+ public String getDescription() {
+ // TODO: Right now each dictionary file comes with a description in its own language.
+ // It will display as is no matter the device's locale. It should be internationalized.
+ return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_DESCRIPTION_ATTRIBUTE);
+ }
}
private FormatSpec() {
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 3193ef457..5c805598a 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -1,26 +1,28 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.makedict;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
@@ -28,6 +30,7 @@ import java.util.LinkedList;
/**
* A dictionary that can fusion heads and tails of words for more compression.
*/
+@UsedForTesting
public final class FusionDictionary implements Iterable<Word> {
private static final boolean DBG = MakedictLog.DBG;
@@ -141,6 +144,33 @@ public final class FusionDictionary implements Iterable<Word> {
return NOT_A_TERMINAL != mFrequency;
}
+ public int getFrequency() {
+ return mFrequency;
+ }
+
+ public boolean getIsNotAWord() {
+ return mIsNotAWord;
+ }
+
+ public boolean getIsBlacklistEntry() {
+ return mIsBlacklistEntry;
+ }
+
+ public ArrayList<WeightedString> getShortcutTargets() {
+ // We don't want write permission to escape outside the package, so we return a copy
+ if (null == mShortcutTargets) return null;
+ final ArrayList<WeightedString> copyOfShortcutTargets =
+ new ArrayList<WeightedString>(mShortcutTargets);
+ return copyOfShortcutTargets;
+ }
+
+ public ArrayList<WeightedString> getBigrams() {
+ // We don't want write permission to escape outside the package, so we return a copy
+ if (null == mBigrams) return null;
+ final ArrayList<WeightedString> copyOfBigrams = new ArrayList<WeightedString>(mBigrams);
+ return copyOfBigrams;
+ }
+
public boolean hasSeveralChars() {
assert(mChars.length > 0);
return 1 < mChars.length;
@@ -249,8 +279,6 @@ public final class FusionDictionary implements Iterable<Word> {
/**
* Options global to the dictionary.
- *
- * There are no options at the moment, so this class is empty.
*/
public static final class DictionaryOptions {
public final boolean mGermanUmlautProcessing;
@@ -262,6 +290,43 @@ public final class FusionDictionary implements Iterable<Word> {
mGermanUmlautProcessing = germanUmlautProcessing;
mFrenchLigatureProcessing = frenchLigatureProcessing;
}
+ @Override
+ public String toString() { // Convenience method
+ return toString(0, false);
+ }
+ public String toString(final int indentCount, final boolean plumbing) {
+ final StringBuilder indent = new StringBuilder();
+ if (plumbing) {
+ indent.append("H:");
+ } else {
+ for (int i = 0; i < indentCount; ++i) {
+ indent.append(" ");
+ }
+ }
+ final StringBuilder s = new StringBuilder();
+ for (final String optionKey : mAttributes.keySet()) {
+ s.append(indent);
+ s.append(optionKey);
+ s.append(" = ");
+ if ("date".equals(optionKey) && !plumbing) {
+ // Date needs a number of milliseconds, but the dictionary contains seconds
+ s.append(new Date(
+ 1000 * Long.parseLong(mAttributes.get(optionKey))).toString());
+ } else {
+ s.append(mAttributes.get(optionKey));
+ }
+ s.append("\n");
+ }
+ if (mGermanUmlautProcessing) {
+ s.append(indent);
+ s.append("Needs German umlaut processing\n");
+ }
+ if (mFrenchLigatureProcessing) {
+ s.append(indent);
+ s.append("Needs French ligature processing\n");
+ }
+ return s.toString();
+ }
}
public final DictionaryOptions mOptions;
@@ -279,7 +344,7 @@ public final class FusionDictionary implements Iterable<Word> {
/**
* Helper method to convert a String to an int array.
*/
- static private int[] getCodePoints(final String word) {
+ static int[] getCodePoints(final String word) {
// TODO: this is a copy-paste of the contents of StringUtils.toCodePointArray,
// which is not visible from the makedict package. Factor this code.
final char[] characters = word.toCharArray();
@@ -358,6 +423,10 @@ public final class FusionDictionary implements Iterable<Word> {
if (charGroup2 == null) {
add(getCodePoints(word2), 0, null, false /* isNotAWord */,
false /* isBlacklistEntry */);
+ // The chargroup for the first word may have moved by the above insertion,
+ // if word1 and word2 share a common stem that happens not to have been
+ // a cutting point until now. In this case, we need to refresh charGroup.
+ charGroup = findWordInTree(mRoot, word1);
}
charGroup.addBigram(word2, frequency);
} else {
@@ -417,7 +486,7 @@ public final class FusionDictionary implements Iterable<Word> {
if (differentCharIndex == currentGroup.mChars.length) {
if (charIndex + differentCharIndex >= word.length) {
// The new word is a prefix of an existing word, but the node on which it
- // should end already exists as is. Since the old CharNode was not a terminal,
+ // should end already exists as is. Since the old CharNode was not a terminal,
// make it one by filling in its frequency and other attributes
currentGroup.update(frequency, shortcutTargets, null, isNotAWord,
isBlacklistEntry);
@@ -459,7 +528,7 @@ public final class FusionDictionary implements Iterable<Word> {
} else {
newParent = new CharGroup(
Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
- null /* shortcutTargets */, null /* bigrams */, -1,
+ null /* shortcutTargets */, null /* bigrams */, -1,
false /* isNotAWord */, false /* isBlacklistEntry */, newChildren);
final CharGroup newWord = new CharGroup(Arrays.copyOfRange(word,
charIndex + differentCharIndex, word.length),
@@ -550,6 +619,7 @@ public final class FusionDictionary implements Iterable<Word> {
/**
* Helper method to find a word in a given branch.
*/
+ @SuppressWarnings("unused")
public static CharGroup findWordInTree(Node node, final String s) {
int index = 0;
final StringBuilder checker = DBG ? new StringBuilder() : null;
diff --git a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
index 6c6b00b6a..cf07209d9 100644
--- a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
+++ b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java b/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
index 5bb24da74..70e24cc98 100644
--- a/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
+++ b/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java b/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
index dbb2ea870..4f3861526 100644
--- a/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
+++ b/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/makedict/Word.java b/java/src/com/android/inputmethod/latin/makedict/Word.java
index 4c4f18f1a..0eabb7bf3 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Word.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Word.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
new file mode 100644
index 000000000..1b893a65d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.setup;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import com.android.inputmethod.compat.IntentCompatUtils;
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.Settings;
+
+/**
+ * This class detects the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME
+ * package has been replaced by a newer version of the same package. This class also detects
+ * {@link Intent#ACTION_BOOT_COMPLETED} and {@link Intent#ACTION_USER_INITIALIZE} broadcast intent.
+ *
+ * If this IME has already been installed in the system image and a new version of this IME has
+ * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver and it
+ * will hide the setup wizard's icon.
+ *
+ * If this IME has already been installed in the data partition and a new version of this IME has
+ * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver but it
+ * will not hide the setup wizard's icon, and the icon will appear on the launcher.
+ *
+ * If this IME hasn't been installed yet and has been newly installed, no
+ * {@link Intent#ACTION_MY_PACKAGE_REPLACED} will be sent and the setup wizard's icon will appear
+ * on the launcher.
+ *
+ * When the device has been booted, {@link Intent#ACTION_BOOT_COMPLETED} is received by this
+ * receiver and it checks whether the setup wizard's icon should be appeared or not on the launcher
+ * depending on which partition this IME is installed.
+ *
+ * When a multiuser account has been created, {@link Intent#ACTION_USER_INITIALIZE} is received
+ * by this receiver and it checks the whether the setup wizard's icon should be appeared or not on
+ * the launcher depending on which partition this IME is installed.
+ */
+public final class LauncherIconVisibilityManager extends BroadcastReceiver {
+ private static final String TAG = LauncherIconVisibilityManager.class.getSimpleName();
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (shouldHandleThisIntent(intent, context)) {
+ updateSetupWizardIconVisibility(context);
+ }
+
+ // The process that hosts this broadcast receiver is invoked and remains alive even after
+ // 1) the package has been re-installed, 2) the device has been booted,
+ // 3) a multiuser has been created.
+ // There is no good reason to keep the process alive if this IME isn't a current IME.
+ RichInputMethodManager.init(context);
+ if (!SetupActivity.isThisImeCurrent(context)) {
+ final int myPid = Process.myPid();
+ Log.i(TAG, "Killing my process: pid=" + myPid);
+ Process.killProcess(myPid);
+ }
+ }
+
+ private static boolean shouldHandleThisIntent(final Intent intent, final Context context) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(action)) {
+ Log.i(TAG, "Package has been replaced: " + context.getPackageName());
+ return true;
+ } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ Log.i(TAG, "Boot has been completed");
+ return true;
+ } else if (IntentCompatUtils.has_ACTION_USER_INITIALIZE(intent)) {
+ Log.i(TAG, "User initialize");
+ return true;
+ }
+ return false;
+ }
+
+ public static void updateSetupWizardIconVisibility(final Context context) {
+ final ComponentName setupWizardActivity = new ComponentName(context, SetupActivity.class);
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ final boolean stateHasSet;
+ if (Settings.readShowSetupWizardIcon(prefs, context)) {
+ stateHasSet = setActivityState(context, setupWizardActivity,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ Log.i(TAG, (stateHasSet ? "Enable activity: " : "Activity has already been enabled: ")
+ + setupWizardActivity);
+ } else {
+ stateHasSet = setActivityState(context, setupWizardActivity,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ Log.i(TAG, (stateHasSet ? "Disable activity: " : "Activity has already been disabled: ")
+ + setupWizardActivity);
+ }
+ }
+
+ private static boolean setActivityState(final Context context,
+ final ComponentName activityComponent, final int activityState) {
+ final PackageManager pm = context.getPackageManager();
+ final int activityComponentState = pm.getComponentEnabledSetting(activityComponent);
+ if (activityComponentState == activityState) {
+ return false;
+ }
+ pm.setComponentEnabledSetting(
+ activityComponent, activityState, PackageManager.DONT_KILL_APP);
+ return true;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
new file mode 100644
index 000000000..e009fbc39
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.setup;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Message;
+import android.provider.Settings;
+import android.view.View;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
+
+import com.android.inputmethod.compat.TextViewCompatUtils;
+import com.android.inputmethod.compat.ViewCompatUtils;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.SettingsActivity;
+import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+
+import java.util.HashMap;
+
+public final class SetupActivity extends Activity {
+ private SetupStepIndicatorView mStepIndicatorView;
+ private final SetupStepGroup mSetupSteps = new SetupStepGroup();
+ private static final String STATE_STEP = "step";
+ private int mStepNumber;
+ private static final int STEP_1 = 1;
+ private static final int STEP_2 = 2;
+ private static final int STEP_3 = 3;
+
+ private final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this);
+
+ static final class SettingsPoolingHandler extends StaticInnerHandlerWrapper<SetupActivity> {
+ private static final int MSG_POLLING_IME_SETTINGS = 0;
+ private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
+
+ public SettingsPoolingHandler(final SetupActivity outerInstance) {
+ super(outerInstance);
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ final SetupActivity setupActivity = getOuterInstance();
+ switch (msg.what) {
+ case MSG_POLLING_IME_SETTINGS:
+ if (SetupActivity.isThisImeEnabled(setupActivity)) {
+ setupActivity.invokeSetupWizardOfThisIme();
+ return;
+ }
+ startPollingImeSettings();
+ break;
+ }
+ }
+
+ public void startPollingImeSettings() {
+ sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS),
+ IME_SETTINGS_POLLING_INTERVAL);
+ }
+
+ public void cancelPollingImeSettings() {
+ removeMessages(MSG_POLLING_IME_SETTINGS);
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ setTheme(android.R.style.Theme_DeviceDefault_Light_NoActionBar);
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.setup_wizard);
+
+ RichInputMethodManager.init(this);
+
+ if (savedInstanceState == null) {
+ mStepNumber = determineSetupStepNumber();
+ } else {
+ mStepNumber = savedInstanceState.getInt(STATE_STEP);
+ }
+
+ if (mStepNumber == STEP_3) {
+ // This IME already has been enabled and set as current IME.
+ // TODO: Implement tutorial.
+ invokeSettingsOfThisIme();
+ finish();
+ return;
+ }
+
+ // TODO: Use sans-serif-thin font family depending on the system locale white list and
+ // the SDK version.
+ final TextView titleView = (TextView)findViewById(R.id.setup_title);
+ titleView.setText(getString(R.string.setup_title, getString(R.string.english_ime_name)));
+
+ mStepIndicatorView = (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator);
+
+ final SetupStep step1 = new SetupStep(findViewById(R.id.setup_step1),
+ R.string.setup_step1_title, R.string.setup_step1_instruction,
+ R.drawable.ic_settings_language, R.string.language_settings);
+ step1.setAction(new Runnable() {
+ @Override
+ public void run() {
+ invokeLanguageAndInputSettings();
+ mHandler.startPollingImeSettings();
+ }
+ });
+ mSetupSteps.addStep(STEP_1, step1);
+
+ final SetupStep step2 = new SetupStep(findViewById(R.id.setup_step2),
+ R.string.setup_step2_title, R.string.setup_step2_instruction,
+ 0 /* actionIcon */, R.string.select_input_method);
+ step2.setAction(new Runnable() {
+ @Override
+ public void run() {
+ // Invoke input method picker.
+ RichInputMethodManager.getInstance().getInputMethodManager()
+ .showInputMethodPicker();
+ }
+ });
+ mSetupSteps.addStep(STEP_2, step2);
+
+ final SetupStep step3 = new SetupStep(findViewById(R.id.setup_step3),
+ R.string.setup_step3_title, 0 /* instruction */,
+ R.drawable.sym_keyboard_language_switch, R.string.setup_step3_instruction);
+ step3.setAction(new Runnable() {
+ @Override
+ public void run() {
+ invokeSubtypeEnablerOfThisIme();
+ }
+ });
+ mSetupSteps.addStep(STEP_3, step3);
+ }
+
+ private void invokeSetupWizardOfThisIme() {
+ final Intent intent = new Intent();
+ intent.setClass(this, SetupActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ }
+
+ private void invokeSettingsOfThisIme() {
+ final Intent intent = new Intent();
+ intent.setClass(this, SettingsActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ }
+
+ private void invokeLanguageAndInputSettings() {
+ final Intent intent = new Intent();
+ intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ startActivity(intent);
+ }
+
+ private void invokeSubtypeEnablerOfThisIme() {
+ final InputMethodInfo imi =
+ RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+ final Intent intent = new Intent();
+ intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId());
+ startActivity(intent);
+ }
+
+ /**
+ * Check if the IME specified by the context is enabled.
+ * Note that {@link RichInputMethodManager} must have been initialized before calling this
+ * method.
+ *
+ * @param context package context of the IME to be checked.
+ * @return true if this IME is enabled.
+ */
+ public static boolean isThisImeEnabled(final Context context) {
+ final String packageName = context.getPackageName();
+ final InputMethodManager imm = RichInputMethodManager.getInstance().getInputMethodManager();
+ for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
+ if (packageName.equals(imi.getPackageName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if the IME specified by the context is the current IME.
+ * Note that {@link RichInputMethodManager} must have been initialized before calling this
+ * method.
+ *
+ * @param context package context of the IME to be checked.
+ * @return true if this IME is the current IME.
+ */
+ public static boolean isThisImeCurrent(final Context context) {
+ final InputMethodInfo myImi =
+ RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+ final String currentImeId = Settings.Secure.getString(
+ context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ return myImi.getId().equals(currentImeId);
+ }
+
+ private int determineSetupStepNumber() {
+ mHandler.cancelPollingImeSettings();
+ if (!isThisImeEnabled(this)) {
+ return STEP_1;
+ }
+ if (!isThisImeCurrent(this)) {
+ return STEP_2;
+ }
+ return STEP_3;
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(STATE_STEP, mStepNumber);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mStepNumber = savedInstanceState.getInt(STATE_STEP);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mStepNumber = determineSetupStepNumber();
+ }
+
+ @Override
+ protected void onRestart() {
+ super.onRestart();
+ mStepNumber = determineSetupStepNumber();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ updateSetupStepView();
+ }
+
+ @Override
+ public void onWindowFocusChanged(final boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (!hasFocus) {
+ return;
+ }
+ mStepNumber = determineSetupStepNumber();
+ updateSetupStepView();
+ }
+
+ private void updateSetupStepView() {
+ final int layoutDirection = ViewCompatUtils.getLayoutDirection(mStepIndicatorView);
+ mStepIndicatorView.setIndicatorPosition(
+ getIndicatorPosition(mStepNumber, mSetupSteps.getTotalStep(), layoutDirection));
+ mSetupSteps.enableStep(mStepNumber);
+ }
+
+ private static float getIndicatorPosition(final int step, final int totalStep,
+ final int layoutDirection) {
+ final float pos = ((step - STEP_1) * 2 + 1) / (float)(totalStep * 2);
+ return (layoutDirection == ViewCompatUtils.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos;
+ }
+
+ static final class SetupStep implements View.OnClickListener {
+ private final View mRootView;
+ private final TextView mActionLabel;
+ private Runnable mAction;
+
+ public SetupStep(final View rootView, final int title, final int instruction,
+ final int actionIcon, final int actionLabel) {
+ mRootView = rootView;
+ final Resources res = rootView.getResources();
+ final String applicationName = res.getString(R.string.english_ime_name);
+
+ final TextView titleView = (TextView)rootView.findViewById(R.id.setup_step_title);
+ titleView.setText(res.getString(title, applicationName));
+
+ final TextView instructionView = (TextView)rootView.findViewById(
+ R.id.setup_step_instruction);
+ if (instruction == 0) {
+ instructionView.setVisibility(View.GONE);
+ } else {
+ instructionView.setText(res.getString(instruction, applicationName));
+ }
+
+ mActionLabel = (TextView)rootView.findViewById(R.id.setup_step_action_label);
+ mActionLabel.setText(res.getString(actionLabel));
+ if (actionIcon == 0) {
+ final int paddingEnd = ViewCompatUtils.getPaddingEnd(mActionLabel);
+ ViewCompatUtils.setPaddingRelative(mActionLabel, paddingEnd, 0, paddingEnd, 0);
+ } else {
+ final int overrideColor = res.getColor(R.color.setup_text_action);
+ final Drawable icon = res.getDrawable(actionIcon);
+ icon.setColorFilter(overrideColor, PorterDuff.Mode.MULTIPLY);
+ icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ TextViewCompatUtils.setCompoundDrawablesRelative(
+ mActionLabel, icon, null, null, null);
+ }
+ }
+
+ public void setEnabled(final boolean enabled) {
+ mRootView.setVisibility(enabled ? View.VISIBLE : View.GONE);
+ }
+
+ public void setAction(final Runnable action) {
+ mActionLabel.setOnClickListener(this);
+ mAction = action;
+ }
+
+ @Override
+ public void onClick(final View v) {
+ if (mAction != null) {
+ mAction.run();
+ }
+ }
+ }
+
+ static final class SetupStepGroup {
+ private final HashMap<Integer, SetupStep> mGroup = CollectionUtils.newHashMap();
+
+ public void addStep(final int stepNo, final SetupStep step) {
+ mGroup.put(stepNo, step);
+ }
+
+ public void enableStep(final int enableStepNo) {
+ for (final Integer stepNo : mGroup.keySet()) {
+ final SetupStep step = mGroup.get(stepNo);
+ step.setEnabled(stepNo == enableStepNo);
+ }
+ }
+
+ public int getTotalStep() {
+ return mGroup.size();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
new file mode 100644
index 000000000..077a21793
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.setup;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.inputmethod.latin.R;
+
+public final class SetupStepIndicatorView extends View {
+ private final Path mIndicatorPath = new Path();
+ private final Paint mIndicatorPaint = new Paint();
+ private float mXRatio;
+
+ public SetupStepIndicatorView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mIndicatorPaint.setColor(getResources().getColor(R.color.setup_step_background));
+ mIndicatorPaint.setStyle(Paint.Style.FILL);
+ }
+
+ public void setIndicatorPosition(final float xRatio) {
+ mXRatio = xRatio;
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(final Canvas canvas) {
+ super.onDraw(canvas);
+ final int xPos = (int)(getWidth() * mXRatio);
+ final int height = getHeight();
+ mIndicatorPath.rewind();
+ mIndicatorPath.moveTo(xPos, 0);
+ mIndicatorPath.lineTo(xPos + height, height);
+ mIndicatorPath.lineTo(xPos - height, height);
+ mIndicatorPath.close();
+ canvas.drawPath(mIndicatorPath, mIndicatorPaint);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 5a11ae534..38a26486d 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.spellcheck;
@@ -81,6 +81,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
public static final int SCRIPT_LATIN = 0;
public static final int SCRIPT_CYRILLIC = 1;
+ public static final int SCRIPT_GREEK = 2;
public static final String SINGLE_QUOTE = "\u0027";
public static final String APOSTROPHE = "\u2019";
private static final TreeMap<String, Integer> mLanguageToScript;
@@ -94,18 +95,23 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
// IMPORTANT: this only contains languages - do not write countries in there.
// Only the language is searched from the map.
mLanguageToScript = CollectionUtils.newTreeMap();
- mLanguageToScript.put("en", SCRIPT_LATIN);
- mLanguageToScript.put("fr", SCRIPT_LATIN);
- mLanguageToScript.put("de", SCRIPT_LATIN);
- mLanguageToScript.put("nl", SCRIPT_LATIN);
mLanguageToScript.put("cs", SCRIPT_LATIN);
+ mLanguageToScript.put("da", SCRIPT_LATIN);
+ mLanguageToScript.put("de", SCRIPT_LATIN);
+ mLanguageToScript.put("el", SCRIPT_GREEK);
+ mLanguageToScript.put("en", SCRIPT_LATIN);
mLanguageToScript.put("es", SCRIPT_LATIN);
- mLanguageToScript.put("it", SCRIPT_LATIN);
+ mLanguageToScript.put("fi", SCRIPT_LATIN);
+ mLanguageToScript.put("fr", SCRIPT_LATIN);
mLanguageToScript.put("hr", SCRIPT_LATIN);
+ mLanguageToScript.put("it", SCRIPT_LATIN);
+ mLanguageToScript.put("lt", SCRIPT_LATIN);
+ mLanguageToScript.put("lv", SCRIPT_LATIN);
+ mLanguageToScript.put("nb", SCRIPT_LATIN);
+ mLanguageToScript.put("nl", SCRIPT_LATIN);
mLanguageToScript.put("pt", SCRIPT_LATIN);
+ mLanguageToScript.put("sl", SCRIPT_LATIN);
mLanguageToScript.put("ru", SCRIPT_CYRILLIC);
- // TODO: Make a persian proximity, and activate the Farsi subtype.
- // mLanguageToScript.put("fa", SCRIPT_PERSIAN);
}
@Override public void onCreate() {
@@ -212,7 +218,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
}
}
- private final ArrayList<CharSequence> mSuggestions;
+ private final ArrayList<String> mSuggestions;
private final int[] mScores;
private final String mOriginalText;
private final float mSuggestionThreshold;
@@ -294,6 +300,8 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
final String[] gatheredSuggestions;
final boolean hasRecommendedSuggestions;
if (0 == mLength) {
+ // TODO: the comment below describes what is intended, but in the practice
+ // mBestSuggestion is only ever set to null so it doesn't work. Fix this.
// Either we found no suggestions, or we found some BUT the max length was 0.
// If we found some mBestSuggestion will not be null. If it is null, then
// we found none, regardless of the max length.
@@ -335,7 +343,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
gatheredSuggestions = mSuggestions.toArray(EMPTY_STRING_ARRAY);
final int bestScore = mScores[mLength - 1];
- final CharSequence bestSuggestion = mSuggestions.get(0);
+ final String bestSuggestion = mSuggestions.get(0);
final float normalizedScore =
BinaryDictionary.calcNormalizedScore(
mOriginalText, bestSuggestion.toString(), bestScore);
@@ -438,15 +446,23 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE;
final int len = text.length();
int capsCount = 1;
+ int letterCount = 1;
for (int i = 1; i < len; i = text.offsetByCodePoints(i, 1)) {
- if (1 != capsCount && i != capsCount) break;
- if (Character.isUpperCase(text.codePointAt(i))) ++capsCount;
+ if (1 != capsCount && letterCount != capsCount) break;
+ final int codePoint = text.codePointAt(i);
+ if (Character.isUpperCase(codePoint)) {
+ ++capsCount;
+ ++letterCount;
+ } else if (Character.isLetter(codePoint)) {
+ // We need to discount non-letters since they may not be upper-case, but may
+ // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME")
+ ++letterCount;
+ }
}
- // We know the first char is upper case. So we want to test if either everything
- // else is lower case, or if everything else is upper case. If the string is
- // exactly one char long, then we will arrive here with capsCount 1, and this is
- // correct, too.
+ // We know the first char is upper case. So we want to test if either every letter other
+ // than the first is lower case, or if they are all upper case. If the string is exactly
+ // one char long, then we will arrive here with letterCount 1, and this is correct, too.
if (1 == capsCount) return CAPITALIZE_FIRST;
- return (len == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
+ return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
}
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 668e7a641..63f46b79e 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java
index 8eb1eb68e..e0418d404 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 53ed4d3c3..cd3f9e442 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -1,17 +1,17 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.spellcheck;
@@ -28,9 +28,11 @@ import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.LocaleUtils;
-import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer;
import java.util.ArrayList;
@@ -141,8 +143,17 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
// All Cyrillic characters are in the 400~52F block. There are some in the upper
// Unicode range, but they are archaic characters that are not used in modern
- // russian and are not used by our dictionary.
+ // Russian and are not used by our dictionary.
return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint);
+ case AndroidSpellCheckerService.SCRIPT_GREEK:
+ // Greek letters are either in the 370~3FF range (Greek & Coptic), or in the
+ // 1F00~1FFF range (Greek extended). Our dictionary contains both sort of characters.
+ // Our dictionary also contains a few words with 0xF2; it would be best to check
+ // if that's correct, but a Google search does return results for these words so
+ // they are probably okay.
+ return (codePoint >= 0x370 && codePoint <= 0x3FF)
+ || (codePoint >= 0x1F00 && codePoint <= 0x1FFF)
+ || codePoint == 0xF2;
default:
// Should never come here
throw new RuntimeException("Impossible value of script: " + script);
@@ -188,6 +199,35 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
return (letterCount * 4 < length * 3);
}
+ /**
+ * Helper method to test valid capitalizations of a word.
+ *
+ * If the "text" is lower-case, we test only the exact string.
+ * If the "Text" is capitalized, we test the exact string "Text" and the lower-cased
+ * version of it "text".
+ * If the "TEXT" is fully upper case, we test the exact string "TEXT", the lower-cased
+ * version of it "text" and the capitalized version of it "Text".
+ */
+ private boolean isInDictForAnyCapitalization(final Dictionary dict, final String text,
+ final int capitalizeType) {
+ // If the word is in there as is, then it's in the dictionary. If not, we'll test lower
+ // case versions, but only if the word is not already all-lower case or mixed case.
+ if (dict.isValidWord(text)) return true;
+ if (AndroidSpellCheckerService.CAPITALIZE_NONE == capitalizeType) return false;
+
+ // If we come here, we have a capitalized word (either First- or All-).
+ // Downcase the word and look it up again. If the word is only capitalized, we
+ // tested all possibilities, so if it's still negative we can return false.
+ final String lowerCaseText = text.toLowerCase(mLocale);
+ if (dict.isValidWord(lowerCaseText)) return true;
+ if (AndroidSpellCheckerService.CAPITALIZE_FIRST == capitalizeType) return false;
+
+ // If the lower case version is not in the dictionary, it's still possible
+ // that we have an all-caps version of a word that needs to be capitalized
+ // according to the dictionary. E.g. "GERMANS" only exists in the dictionary as "Germans".
+ return dict.isValidWord(StringUtils.toTitleCase(lowerCaseText, mLocale));
+ }
+
// Note : this must be reentrant
/**
* Gets a list of suggestions for a specific string. This returns a list of possible
@@ -268,17 +308,11 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
dictInfo.mDictionary.getSuggestions(composer, prevWord,
dictInfo.mProximityInfo);
for (final SuggestedWordInfo suggestion : suggestions) {
- final String suggestionStr = suggestion.mWord.toString();
+ final String suggestionStr = suggestion.mWord;
suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0,
suggestionStr.length(), suggestion.mScore);
}
- isInDict = dictInfo.mDictionary.isValidWord(text);
- if (!isInDict && AndroidSpellCheckerService.CAPITALIZE_NONE != capitalizeType) {
- // We want to test the word again if it's all caps or first caps only.
- // If it's fully down, we already tested it, if it's mixed case, we don't
- // want to test a lowercase version of it.
- isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale));
- }
+ isInDict = isInDictForAnyCapitalization(dictInfo.mDictionary, text, capitalizeType);
} finally {
if (null != dictInfo) {
if (!mDictionaryPool.offer(dictInfo)) {
@@ -318,7 +352,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
if (DBG) {
throw e;
} else {
- Log.e(TAG, "Exception while spellcheking: " + e);
+ Log.e(TAG, "Exception while spellcheking", e);
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
}
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
index 9d7c61a33..017a4f555 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index 1fb2bbb6a..81dd92d9e 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.spellcheck;
@@ -51,11 +51,11 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity>
new Dictionary(Dictionary.TYPE_MAIN) {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo) {
+ final String prevWord, final ProximityInfo proximityInfo) {
return noSuggestions;
}
@Override
- public boolean isValidWord(CharSequence word) {
+ public boolean isValidWord(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/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 11bb97031..49dca21e6 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -1,21 +1,22 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.spellcheck;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
@@ -23,7 +24,7 @@ import com.android.inputmethod.latin.Constants;
import java.util.TreeMap;
public final class SpellCheckerProximityInfo {
- /* public for test */
+ @UsedForTesting
final public static int NUL = Constants.NOT_A_CODE;
// This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
@@ -72,6 +73,12 @@ public final class SpellCheckerProximityInfo {
// to spell check has been entered with one of the keyboards above. Also, specifically
// to English, many spelling errors consist of the last vowel of the word being wrong
// because in English vowels tend to merge with each other in pronunciation.
+ /*
+ The Qwerty layout this represents looks like the following:
+ q w e r t y u i o p
+ a s d f g h j k l
+ z x c v b n m
+ */
final static int[] PROXIMITY = {
// Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
// and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
@@ -126,10 +133,13 @@ public final class SpellCheckerProximityInfo {
final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// TODO: The following table is solely based on the keyboard layout. Consult with Russian
// speakers on commonly misspelled words/letters.
- final static int[] PROXIMITY = {
- // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
- // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
- // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
+ /*
+ The Russian layout this represents looks like the following:
+ й ц у к е н г ш щ з х
+ ф ы в а п р о л д ж э
+ я ч с м и т ь б ю
+
+ This gives us the following table:
'й', 'ц', 'ф', 'ы', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ц', 'й', 'ф', 'ы', 'в', 'у', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'у', 'ц', 'ы', 'в', 'а', 'к', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
@@ -142,7 +152,6 @@ public final class SpellCheckerProximityInfo {
'з', 'щ', 'д', 'ж', 'э', 'х', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'х', 'з', 'ж', 'э', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- // Proximity for row 2. See comment above about size.
'ф', 'й', 'ц', 'ы', 'я', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ы', 'й', 'ц', 'у', 'ф', 'в', 'я', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'в', 'ц', 'у', 'к', 'ы', 'а', 'я', 'ч', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
@@ -155,7 +164,6 @@ public final class SpellCheckerProximityInfo {
'ж', 'щ', 'з', 'х', 'д', 'э', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'э', 'з', 'х', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- // Proximity for row 3. See comment above about size.
'я', 'ф', 'ы', 'в', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ч', 'ы', 'в', 'а', 'я', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'с', 'в', 'а', 'п', 'ч', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
@@ -165,6 +173,249 @@ public final class SpellCheckerProximityInfo {
'ь', 'о', 'л', 'д', 'т', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'б', 'л', 'д', 'ж', 'ь', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ю', 'д', 'ж', 'э', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ Using the following characters:
+ */
+ private static final int CY_SHORT_I = '\u0439'; // й
+ private static final int CY_TSE = '\u0446'; // ц
+ private static final int CY_U = '\u0443'; // у
+ private static final int CY_KA = '\u043A'; // к
+ private static final int CY_IE = '\u0435'; // е
+ private static final int CY_EN = '\u043D'; // н
+ private static final int CY_GHE = '\u0433'; // г
+ private static final int CY_SHA = '\u0448'; // ш
+ private static final int CY_SHCHA = '\u0449'; // щ
+ private static final int CY_ZE = '\u0437'; // з
+ private static final int CY_HA = '\u0445'; // х
+ private static final int CY_EF = '\u0444'; // ф
+ private static final int CY_YERU = '\u044B'; // ы
+ private static final int CY_VE = '\u0432'; // в
+ private static final int CY_A = '\u0430'; // а
+ private static final int CY_PE = '\u043F'; // п
+ private static final int CY_ER = '\u0440'; // р
+ private static final int CY_O = '\u043E'; // о
+ private static final int CY_EL = '\u043B'; // л
+ private static final int CY_DE = '\u0434'; // д
+ private static final int CY_ZHE = '\u0436'; // ж
+ private static final int CY_E = '\u044D'; // э
+ private static final int CY_YA = '\u044F'; // я
+ private static final int CY_CHE = '\u0447'; // ч
+ private static final int CY_ES = '\u0441'; // с
+ private static final int CY_EM = '\u043C'; // м
+ private static final int CY_I = '\u0438'; // и
+ private static final int CY_TE = '\u0442'; // т
+ private static final int CY_SOFT_SIGN = '\u044C'; // ь
+ private static final int CY_BE = '\u0431'; // б
+ private static final int CY_YU = '\u044E'; // ю
+ final static int[] PROXIMITY = {
+ // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
+ // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
+ // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
+ CY_SHORT_I, CY_TSE, CY_EF, CY_YERU, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_TSE, CY_SHORT_I, CY_EF, CY_YERU, CY_VE, CY_U, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_U, CY_TSE, CY_YERU, CY_VE, CY_A, CY_KA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_KA, CY_U, CY_VE, CY_A, CY_PE, CY_IE, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_IE, CY_KA, CY_A, CY_PE, CY_ER, CY_EN, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_EN, CY_IE, CY_PE, CY_ER, CY_O, CY_GHE, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_GHE, CY_EN, CY_ER, CY_O, CY_EL, CY_SHA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_SHA, CY_GHE, CY_O, CY_EL, CY_DE, CY_SHCHA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_SHCHA, CY_SHA, CY_EL, CY_DE, CY_ZHE, CY_ZE, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_ZE, CY_SHCHA, CY_DE, CY_ZHE, CY_E, CY_HA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_HA, CY_ZE, CY_ZHE, CY_E, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ // Proximity for row 2. See comment above about size.
+ CY_EF, CY_SHORT_I, CY_TSE, CY_YERU, CY_YA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_YERU, CY_SHORT_I, CY_TSE, CY_U, CY_EF, CY_VE, CY_YA, CY_CHE,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_VE, CY_TSE, CY_U, CY_KA, CY_YERU, CY_A, CY_YA, CY_CHE,
+ CY_ES, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_A, CY_U, CY_KA, CY_IE, CY_VE, CY_PE, CY_CHE, CY_ES,
+ CY_EM, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_PE, CY_KA, CY_IE, CY_EN, CY_A, CY_ER, CY_ES, CY_EM,
+ CY_I, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_ER, CY_IE, CY_EN, CY_GHE, CY_PE, CY_O, CY_EM, CY_I,
+ CY_TE, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_O, CY_EN, CY_GHE, CY_SHA, CY_ER, CY_EL, CY_I, CY_TE,
+ CY_SOFT_SIGN, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_EL, CY_GHE, CY_SHA, CY_SHCHA, CY_O, CY_DE, CY_TE, CY_SOFT_SIGN,
+ CY_BE, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_DE, CY_SHA, CY_SHCHA, CY_ZE, CY_EL, CY_ZHE, CY_SOFT_SIGN, CY_BE,
+ CY_YU, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_ZHE, CY_SHCHA, CY_ZE, CY_HA, CY_DE, CY_E, CY_BE, CY_YU,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_E, CY_ZE, CY_HA, CY_YU, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ // Proximity for row 3. See comment above about size.
+ CY_YA, CY_EF, CY_YERU, CY_VE, CY_CHE, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_CHE, CY_YERU, CY_VE, CY_A, CY_YA, CY_ES, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_ES, CY_VE, CY_A, CY_PE, CY_CHE, CY_EM, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_EM, CY_A, CY_PE, CY_ER, CY_ES, CY_I, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_I, CY_PE, CY_ER, CY_O, CY_EM, CY_TE, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_TE, CY_ER, CY_O, CY_EL, CY_I, CY_SOFT_SIGN, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_SOFT_SIGN, CY_O, CY_EL, CY_DE, CY_TE, CY_BE, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_BE, CY_EL, CY_DE, CY_ZHE, CY_SOFT_SIGN, CY_YU, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_YU, CY_DE, CY_ZHE, CY_E, CY_BE, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ };
+ static {
+ buildProximityIndices(PROXIMITY, INDICES);
+ }
+ static int getIndexOf(int characterCode) {
+ return computeIndex(characterCode, INDICES);
+ }
+ }
+
+ private static final class Greek {
+ final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
+ // TODO: The following table is solely based on the keyboard layout. Consult with Greek
+ // speakers on commonly misspelled words/letters.
+ /*
+ The Greek layout this represents looks like the following:
+ ; ς ε ρ τ υ θ ι ο π
+ α σ δ φ γ η ξ κ λ
+ ζ χ ψ ω β ν μ
+
+ This gives us the following table:
+ 'ς', 'ε', 'α', 'σ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ε', 'ς', 'ρ', 'σ', 'δ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ρ', 'ε', 'τ', 'δ', 'φ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'τ', 'ρ', 'υ', 'φ', 'γ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'υ', 'τ', 'θ', 'γ', 'η', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'θ', 'υ', 'ι', 'η', 'ξ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ι', 'θ', 'ο', 'ξ', 'κ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ο', 'ι', 'π', 'κ', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'π', 'ο', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ 'α', 'ς', 'σ', 'ζ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'σ', 'ς', 'ε', 'α', 'δ', 'ζ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'δ', 'ε', 'ρ', 'σ', 'φ', 'ζ', 'χ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'φ', 'ρ', 'τ', 'δ', 'γ', 'χ', 'ψ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'γ', 'τ', 'υ', 'φ', 'η', 'ψ', 'ω', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'η', 'υ', 'θ', 'γ', 'ξ', 'ω', 'β', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ξ', 'θ', 'ι', 'η', 'κ', 'β', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'κ', 'ι', 'ο', 'ξ', 'λ', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'λ', 'ο', 'π', 'κ', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ 'ζ', 'α', 'σ', 'δ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'χ', 'σ', 'δ', 'φ', 'ζ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ψ', 'δ', 'φ', 'γ', 'χ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ω', 'φ', 'γ', 'η', 'ψ', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'β', 'γ', 'η', 'ξ', 'ω', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ν', 'η', 'ξ', 'κ', 'β', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'μ', 'ξ', 'κ', 'λ', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ Using the following characters:
+ */
+ private static final int GR_FINAL_SIGMA = '\u03C2'; // ς
+ private static final int GR_EPSILON = '\u03B5'; // ε
+ private static final int GR_RHO = '\u03C1'; // ρ
+ private static final int GR_TAU = '\u03C4'; // τ
+ private static final int GR_UPSILON = '\u03C5'; // υ
+ private static final int GR_THETA = '\u03B8'; // θ
+ private static final int GR_IOTA = '\u03B9'; // ι
+ private static final int GR_OMICRON = '\u03BF'; // ο
+ private static final int GR_PI = '\u03C0'; // π
+ private static final int GR_ALPHA = '\u03B1'; // α
+ private static final int GR_SIGMA = '\u03C3'; // σ
+ private static final int GR_DELTA = '\u03B4'; // δ
+ private static final int GR_PHI = '\u03C6'; // φ
+ private static final int GR_GAMMA = '\u03B3'; // γ
+ private static final int GR_ETA = '\u03B7'; // η
+ private static final int GR_XI = '\u03BE'; // ξ
+ private static final int GR_KAPPA = '\u03BA'; // κ
+ private static final int GR_LAMDA = '\u03BB'; // λ
+ private static final int GR_ZETA = '\u03B6'; // ζ
+ private static final int GR_CHI = '\u03C7'; // χ
+ private static final int GR_PSI = '\u03C8'; // ψ
+ private static final int GR_OMEGA = '\u03C9'; // ω
+ private static final int GR_BETA = '\u03B2'; // β
+ private static final int GR_NU = '\u03BD'; // ν
+ private static final int GR_MU = '\u03BC'; // μ
+ final static int[] PROXIMITY = {
+ // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
+ // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
+ // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
+ GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_SIGMA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_EPSILON, GR_FINAL_SIGMA, GR_RHO, GR_SIGMA, GR_DELTA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_RHO, GR_EPSILON, GR_TAU, GR_DELTA, GR_PHI, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_TAU, GR_RHO, GR_UPSILON, GR_PHI, GR_GAMMA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_UPSILON, GR_TAU, GR_THETA, GR_GAMMA, GR_ETA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_THETA, GR_UPSILON, GR_IOTA, GR_ETA, GR_XI, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_IOTA, GR_THETA, GR_OMICRON, GR_XI, GR_KAPPA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_OMICRON, GR_IOTA, GR_PI, GR_KAPPA, GR_LAMDA, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_PI, GR_OMICRON, GR_LAMDA, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ GR_ALPHA, GR_FINAL_SIGMA, GR_SIGMA, GR_ZETA, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_SIGMA, GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_DELTA, GR_ZETA, GR_CHI, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_DELTA, GR_EPSILON, GR_RHO, GR_SIGMA, GR_PHI, GR_ZETA, GR_CHI, GR_PSI,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_PHI, GR_RHO, GR_TAU, GR_DELTA, GR_GAMMA, GR_CHI, GR_PSI, GR_OMEGA,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_GAMMA, GR_TAU, GR_UPSILON, GR_PHI, GR_ETA, GR_PSI, GR_OMEGA, GR_BETA,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_ETA, GR_UPSILON, GR_THETA, GR_GAMMA, GR_XI, GR_OMEGA, GR_BETA, GR_NU,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_XI, GR_THETA, GR_IOTA, GR_ETA, GR_KAPPA, GR_BETA, GR_NU, GR_MU,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_KAPPA, GR_IOTA, GR_OMICRON, GR_XI, GR_LAMDA, GR_NU, GR_MU, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_LAMDA, GR_OMICRON, GR_PI, GR_KAPPA, GR_MU, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ GR_ZETA, GR_ALPHA, GR_SIGMA, GR_DELTA, GR_CHI, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_CHI, GR_SIGMA, GR_DELTA, GR_PHI, GR_ZETA, GR_PSI, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_PSI, GR_DELTA, GR_PHI, GR_GAMMA, GR_CHI, GR_OMEGA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_OMEGA, GR_PHI, GR_GAMMA, GR_ETA, GR_PSI, GR_BETA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_BETA, GR_GAMMA, GR_ETA, GR_XI, GR_OMEGA, GR_NU, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_NU, GR_ETA, GR_XI, GR_KAPPA, GR_BETA, GR_MU, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_MU, GR_XI, GR_KAPPA, GR_LAMDA, GR_NU, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
@@ -182,6 +433,8 @@ public final class SpellCheckerProximityInfo {
return Latin.PROXIMITY;
case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
return Cyrillic.PROXIMITY;
+ case AndroidSpellCheckerService.SCRIPT_GREEK:
+ return Greek.PROXIMITY;
default:
throw new RuntimeException("Wrong script supplied: " + script);
}
@@ -193,6 +446,8 @@ public final class SpellCheckerProximityInfo {
return Latin.getIndexOf(codePoint);
case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
return Cyrillic.getIndexOf(codePoint);
+ case AndroidSpellCheckerService.SCRIPT_GREEK:
+ return Greek.getIndexOf(codePoint);
default:
throw new RuntimeException("Wrong script supplied: " + script);
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
index e63dff312..119ca4755 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
@@ -1,17 +1,17 @@
-/**
+/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
index ef5123d68..9606b0352 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -1,17 +1,17 @@
-/**
+/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 4e9fd1968..ed408bb3c 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.suggestions;
@@ -22,7 +22,6 @@ import android.graphics.drawable.Drawable;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
@@ -66,7 +65,7 @@ public final class MoreSuggestions extends Keyboard {
int pos = fromPos, rowStartPos = fromPos;
final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS);
while (pos < size) {
- final String word = suggestions.getWord(pos).toString();
+ final String word = suggestions.getWord(pos);
// TODO: Should take care of text x-scaling.
mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
final int numColumn = pos - rowStartPos + 1;
@@ -176,11 +175,11 @@ public final class MoreSuggestions extends Keyboard {
}
public Builder layout(final SuggestedWords suggestions, final int fromPos,
- final int maxWidth, final int minWidth, final int maxRow) {
- final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard();
+ final int maxWidth, final int minWidth, final int maxRow,
+ final Keyboard parentKeyboard) {
final int xmlId = R.xml.kbd_suggestions_pane_template;
- load(xmlId, keyboard.mId);
- mParams.mVerticalGap = mParams.mTopPadding = keyboard.mVerticalGap / 2;
+ load(xmlId, parentKeyboard.mId);
+ mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow,
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 03a2e73d1..438820d17 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -17,92 +17,29 @@
package com.android.inputmethod.latin.suggestions;
import android.content.Context;
-import android.content.res.Resources;
import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.PopupWindow;
-import com.android.inputmethod.keyboard.KeyDetector;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardActionListener;
-import com.android.inputmethod.keyboard.KeyboardView;
-import com.android.inputmethod.keyboard.MoreKeysDetector;
-import com.android.inputmethod.keyboard.MoreKeysPanel;
-import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
-import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler;
-import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.keyboard.MoreKeysKeyboardView;
import com.android.inputmethod.latin.R;
/**
* A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting
* key presses and touch movements.
*/
-public final class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
- private final int[] mCoordinates = new int[2];
-
- final KeyDetector mModalPanelKeyDetector;
- private final KeyDetector mSlidingPanelKeyDetector;
-
- private Controller mController;
- KeyboardActionListener mListener;
- private int mOriginX;
- private int mOriginY;
-
- static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy.Adapter();
-
- final KeyboardActionListener mSuggestionsPaneListener =
- new KeyboardActionListener.Adapter() {
- @Override
- public void onPressKey(int primaryCode) {
- mListener.onPressKey(primaryCode);
- }
-
- @Override
- public void onReleaseKey(int primaryCode, boolean withSliding) {
- mListener.onReleaseKey(primaryCode, withSliding);
- }
-
- @Override
- public void onCodeInput(int primaryCode, int x, int y) {
- final int index = primaryCode - MoreSuggestions.SUGGESTION_CODE_BASE;
- if (index >= 0 && index < SuggestionStripView.MAX_SUGGESTIONS) {
- mListener.onCustomRequest(index);
- }
- }
-
- @Override
- public void onCancelInput() {
- mListener.onCancelInput();
- }
- };
-
- public MoreSuggestionsView(Context context, AttributeSet attrs) {
+public final class MoreSuggestionsView extends MoreKeysKeyboardView {
+ public MoreSuggestionsView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.moreSuggestionsViewStyle);
}
- public MoreSuggestionsView(Context context, AttributeSet attrs, int defStyle) {
+ public MoreSuggestionsView(final Context context, final AttributeSet attrs,
+ final int defStyle) {
super(context, attrs, defStyle);
-
- final Resources res = context.getResources();
- mModalPanelKeyDetector = new KeyDetector(/* keyHysteresisDistance */ 0);
- mSlidingPanelKeyDetector = new MoreKeysDetector(
- res.getDimension(R.dimen.more_suggestions_slide_allowance));
- setKeyPreviewPopupEnabled(false, 0);
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final Keyboard keyboard = getKeyboard();
- if (keyboard != null) {
- final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
- final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
- setMeasuredDimension(width, height);
- } else {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
+ protected int getDefaultCoordX() {
+ final MoreSuggestions pane = (MoreSuggestions)getKeyboard();
+ return pane.mOccupiedWidth / 2;
}
public void updateKeyboardGeometry(final int keyHeight) {
@@ -110,116 +47,10 @@ public final class MoreSuggestionsView extends KeyboardView implements MoreKeysP
}
@Override
- public void setKeyboard(Keyboard keyboard) {
- super.setKeyboard(keyboard);
- mModalPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), -getPaddingTop());
- mSlidingPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
- -getPaddingTop() + mVerticalCorrection);
- }
-
- @Override
- public KeyDetector getKeyDetector() {
- return mSlidingPanelKeyDetector;
- }
-
- @Override
- public KeyboardActionListener getKeyboardActionListener() {
- return mSuggestionsPaneListener;
- }
-
- @Override
- public DrawingProxy getDrawingProxy() {
- return this;
- }
-
- @Override
- public TimerProxy getTimerProxy() {
- return EMPTY_TIMER_PROXY;
- }
-
- @Override
- public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
- // Suggestions pane needs no pop-up key preview displayed, so we pass always false with a
- // delay of 0. The delay does not matter actually since the popup is not shown anyway.
- super.setKeyPreviewPopupEnabled(false, 0);
- }
-
- @Override
- public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY,
- PopupWindow window, KeyboardActionListener listener) {
- mController = controller;
- mListener = listener;
- final View container = (View)getParent();
- final MoreSuggestions pane = (MoreSuggestions)getKeyboard();
- final int defaultCoordX = pane.mOccupiedWidth / 2;
- // The coordinates of panel's left-top corner in parentView's coordinate system.
- final int x = pointX - defaultCoordX - container.getPaddingLeft();
- final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom();
-
- window.setContentView(container);
- window.setWidth(container.getMeasuredWidth());
- window.setHeight(container.getMeasuredHeight());
- parentView.getLocationInWindow(mCoordinates);
- window.showAtLocation(parentView, Gravity.NO_GRAVITY,
- x + mCoordinates[0], y + mCoordinates[1]);
-
- mOriginX = x + container.getPaddingLeft();
- mOriginY = y + container.getPaddingTop();
- }
-
- private boolean mIsDismissing;
-
- @Override
- public boolean dismissMoreKeysPanel() {
- if (mIsDismissing || mController == null) return false;
- mIsDismissing = true;
- final boolean dismissed = mController.dismissMoreKeysPanel();
- mIsDismissing = false;
- return dismissed;
- }
-
- @Override
- public int translateX(int x) {
- return x - mOriginX;
- }
-
- @Override
- public int translateY(int y) {
- return y - mOriginY;
- }
-
- private final KeyEventHandler mModalPanelKeyEventHandler = new KeyEventHandler() {
- @Override
- public KeyDetector getKeyDetector() {
- return mModalPanelKeyDetector;
- }
-
- @Override
- public KeyboardActionListener getKeyboardActionListener() {
- return mSuggestionsPaneListener;
- }
-
- @Override
- public DrawingProxy getDrawingProxy() {
- return MoreSuggestionsView.this;
+ public void onCodeInput(final int code, final int x, final int y) {
+ final int index = code - MoreSuggestions.SUGGESTION_CODE_BASE;
+ if (index >= 0 && index < SuggestionStripView.MAX_SUGGESTIONS) {
+ mListener.onCustomRequest(index);
}
-
- @Override
- public TimerProxy getTimerProxy() {
- return EMPTY_TIMER_PROXY;
- }
- };
-
- @Override
- public boolean onTouchEvent(MotionEvent me) {
- final int action = me.getAction();
- final long eventTime = me.getEventTime();
- final int index = me.getActionIndex();
- final int id = me.getPointerId(index);
- final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
- final int x = (int)me.getX(index);
- final int y = (int)me.getY(index);
- tracker.processMotionEvent(action, x, y, eventTime, mModalPanelKeyEventHandler);
- return true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index a1c95ad8a..5a29eee4e 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.android.inputmethod.latin.suggestions;
@@ -27,9 +27,7 @@ import android.graphics.Paint.Align;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.os.Message;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
@@ -48,21 +46,21 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.LinearLayout;
-import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;
+import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
-import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.keyboard.MoreKeysPanel;
-import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.keyboard.ViewLayoutUtils;
import com.android.inputmethod.latin.AutoCorrection;
import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.ResourceUtils;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.Utils;
import com.android.inputmethod.latin.define.ProductionFlag;
@@ -74,7 +72,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
OnLongClickListener {
public interface Listener {
public void addWordToUserDictionary(String word);
- public void pickSuggestionManually(int index, CharSequence word);
+ public void pickSuggestionManually(int index, String word);
}
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
@@ -83,54 +81,22 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
static final boolean DBG = LatinImeLogger.sDBG;
private final ViewGroup mSuggestionsStrip;
- private KeyboardView mKeyboardView;
+ MainKeyboardView mMainKeyboardView;
private final View mMoreSuggestionsContainer;
private final MoreSuggestionsView mMoreSuggestionsView;
private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
- private final PopupWindow mMoreSuggestionsWindow;
private final ArrayList<TextView> mWords = CollectionUtils.newArrayList();
private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList();
private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
- private final PopupWindow mPreviewPopup;
- private final TextView mPreviewText;
-
- private Listener mListener;
- private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+ Listener mListener;
+ SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
private final SuggestionStripViewParams mParams;
private static final float MIN_TEXT_XSCALE = 0.70f;
- private final UiHandler mHandler = new UiHandler(this);
-
- private static final class UiHandler extends StaticInnerHandlerWrapper<SuggestionStripView> {
- private static final int MSG_HIDE_PREVIEW = 0;
-
- public UiHandler(SuggestionStripView outerInstance) {
- super(outerInstance);
- }
-
- @Override
- public void dispatchMessage(Message msg) {
- final SuggestionStripView suggestionStripView = getOuterInstance();
- switch (msg.what) {
- case MSG_HIDE_PREVIEW:
- suggestionStripView.hidePreview();
- break;
- }
- }
-
- public void cancelHidePreview() {
- removeMessages(MSG_HIDE_PREVIEW);
- }
-
- public void cancelAllMessages() {
- cancelHidePreview();
- }
- }
-
private static final class SuggestionStripViewParams {
private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
@@ -177,8 +143,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private final TextView mLeftwardsArrowView;
private final TextView mHintToSaveView;
- public SuggestionStripViewParams(Context context, AttributeSet attrs, int defStyle,
- ArrayList<TextView> words, ArrayList<View> dividers, ArrayList<TextView> infos) {
+ public SuggestionStripViewParams(final Context context, final AttributeSet attrs,
+ final int defStyle, final ArrayList<TextView> words, final ArrayList<View> dividers,
+ final ArrayList<TextView> infos) {
mWords = words;
mDividers = dividers;
mInfos = infos;
@@ -250,7 +217,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
}
- public int setMoreSuggestionsHeight(int remainingHeight) {
+ public int setMoreSuggestionsHeight(final int remainingHeight) {
final int currentHeight = getMoreSuggestionsHeight();
if (currentHeight <= remainingHeight) {
return currentHeight;
@@ -262,7 +229,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
return newHeight;
}
- private static Drawable getMoreSuggestionsHint(Resources res, float textSize, int color) {
+ private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize,
+ final int color) {
final Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setTextAlign(Align.CENTER);
@@ -279,8 +247,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
return new BitmapDrawable(res, buffer);
}
- private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
- final CharSequence word = suggestedWords.getWord(pos);
+ private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords,
+ final int pos) {
+ final String word = suggestedWords.getWord(pos);
final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect();
final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid;
if (!isAutoCorrect && !isTypedWordValid)
@@ -299,7 +268,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
return spannedWord;
}
- private int getWordPosition(int index, SuggestedWords suggestedWords) {
+ private int getWordPosition(final int index, final SuggestedWords suggestedWords) {
// TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
// suggestions.
final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0;
@@ -312,7 +281,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
}
- private int getSuggestionTextColor(int index, SuggestedWords suggestedWords, int pos) {
+ private int getSuggestionTextColor(final int index, final SuggestedWords suggestedWords,
+ final int pos) {
// TODO: Need to revisit this logic with bigram suggestions
final boolean isSuggested = (pos != 0);
@@ -331,7 +301,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
// is in slot 1.
if (index == mCenterSuggestionIndex
&& AutoCorrection.shouldBlockAutoCorrectionBySafetyNet(
- suggestedWords.getWord(1).toString(), suggestedWords.getWord(0))) {
+ suggestedWords.getWord(1), suggestedWords.getWord(0))) {
return 0xFFFF0000;
}
}
@@ -355,8 +325,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
params.gravity = Gravity.CENTER;
}
- public void layout(SuggestedWords suggestedWords, ViewGroup stripView, ViewGroup placer,
- int stripWidth) {
+ public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView,
+ final ViewGroup placer, final int stripWidth) {
if (suggestedWords.mIsPunctuationSuggestions) {
layoutPunctuationSuggestions(suggestedWords, stripView);
return;
@@ -402,7 +372,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
x += word.getMeasuredWidth();
if (DBG && pos < suggestedWords.size()) {
- final CharSequence debugInfo = Utils.getDebugInfo(suggestedWords, pos);
+ final String debugInfo = Utils.getDebugInfo(suggestedWords, pos);
if (debugInfo != null) {
final TextView info = mInfos.get(pos);
info.setText(debugInfo);
@@ -418,14 +388,14 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
}
- private int getSuggestionWidth(int index, int maxWidth) {
+ private int getSuggestionWidth(final int index, final int maxWidth) {
final int paddings = mPadding * mSuggestionsCountInStrip;
final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
final int availableWidth = maxWidth - paddings - dividers;
return (int)(availableWidth * getSuggestionWeight(index));
}
- private float getSuggestionWeight(int index) {
+ private float getSuggestionWeight(final int index) {
if (index == mCenterSuggestionIndex) {
return mCenterSuggestionWeight;
} else {
@@ -434,7 +404,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
}
- private void setupTexts(SuggestedWords suggestedWords, int countInStrip) {
+ private void setupTexts(final SuggestedWords suggestedWords, final int countInStrip) {
mTexts.clear();
final int count = Math.min(suggestedWords.size(), countInStrip);
for (int pos = 0; pos < count; pos++) {
@@ -447,8 +417,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
}
- private void layoutPunctuationSuggestions(SuggestedWords suggestedWords,
- ViewGroup stripView) {
+ private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords,
+ final ViewGroup stripView) {
final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
for (int index = 0; index < countInStrip; index++) {
if (index != 0) {
@@ -459,7 +429,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
final TextView word = mWords.get(index);
word.setEnabled(true);
word.setTextColor(mColorAutoCorrect);
- final CharSequence text = suggestedWords.getWord(index);
+ final String text = suggestedWords.getWord(index);
word.setText(text);
word.setTextScaleX(1.0f);
word.setCompoundDrawables(null, null, null, null);
@@ -469,8 +439,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mMoreSuggestionsAvailable = false;
}
- public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView,
- int stripWidth, CharSequence hintText, OnClickListener listener) {
+ public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView,
+ final int stripWidth, final CharSequence hintText, final OnClickListener listener) {
final int width = stripWidth - mDividerWidth - mPadding * 2;
final TextView wordView = mWordToSaveView;
@@ -511,11 +481,11 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
return (CharSequence)mWordToSaveView.getTag();
}
- public boolean isAddToDictionaryShowing(View v) {
+ public boolean isAddToDictionaryShowing(final View v) {
return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView;
}
- private static void setLayoutWeight(View v, float weight, int height) {
+ private static void setLayoutWeight(final View v, final float weight, final int height) {
final ViewGroup.LayoutParams lp = v.getLayoutParams();
if (lp instanceof LinearLayout.LayoutParams) {
final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
@@ -525,7 +495,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
}
- private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) {
+ private static float getTextScaleX(final CharSequence text, final int maxWidth,
+ final TextPaint paint) {
paint.setTextScaleX(1.0f);
final int width = getTextWidth(text, paint);
if (width <= maxWidth) {
@@ -534,8 +505,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
return maxWidth / (float)width;
}
- private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
- TextPaint paint) {
+ private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth,
+ final TextPaint paint) {
if (text == null) return null;
paint.setTextScaleX(1.0f);
final int width = getTextWidth(text, paint);
@@ -556,7 +527,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
return ellipsized;
}
- private static int getTextWidth(CharSequence text, TextPaint paint) {
+ private static int getTextWidth(final CharSequence text, final TextPaint paint) {
if (TextUtils.isEmpty(text)) return 0;
final Typeface savedTypeface = paint.getTypeface();
paint.setTypeface(getTextTypeface(text));
@@ -571,7 +542,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
return width;
}
- private static Typeface getTextTypeface(CharSequence text) {
+ private static Typeface getTextTypeface(final CharSequence text) {
if (!(text instanceof SpannableString))
return Typeface.DEFAULT;
@@ -593,23 +564,17 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
* @param context
* @param attrs
*/
- public SuggestionStripView(Context context, AttributeSet attrs) {
+ public SuggestionStripView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.suggestionStripViewStyle);
}
- public SuggestionStripView(Context context, AttributeSet attrs, int defStyle) {
+ public SuggestionStripView(final Context context, final AttributeSet attrs,
+ final int defStyle) {
super(context, attrs, defStyle);
final LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.suggestions_strip, this);
- mPreviewPopup = new PopupWindow(context);
- mPreviewText = (TextView) inflater.inflate(R.layout.suggestion_preview, null);
- mPreviewPopup.setWindowLayoutMode(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- mPreviewPopup.setContentView(mPreviewText);
- mPreviewPopup.setBackgroundDrawable(null);
-
mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip);
for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) {
final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null);
@@ -632,21 +597,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
.findViewById(R.id.more_suggestions_view);
mMoreSuggestionsBuilder = new MoreSuggestions.Builder(mMoreSuggestionsView);
- final PopupWindow moreWindow = new PopupWindow(context);
- moreWindow.setWindowLayoutMode(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- moreWindow.setBackgroundDrawable(new ColorDrawable(android.R.color.transparent));
- moreWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
- moreWindow.setFocusable(true);
- moreWindow.setOutsideTouchable(true);
- moreWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
- @Override
- public void onDismiss() {
- mKeyboardView.dimEntireKeyboard(false);
- }
- });
- mMoreSuggestionsWindow = moreWindow;
-
final Resources res = context.getResources();
mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset(
R.dimen.more_suggestions_modal_tolerance);
@@ -658,15 +608,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
* A connection back to the input method.
* @param listener
*/
- public void setListener(Listener listener, View inputView) {
+ public void setListener(final Listener listener, final View inputView) {
mListener = listener;
- mKeyboardView = (KeyboardView)inputView.findViewById(R.id.keyboard_view);
+ mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view);
}
- public void setSuggestions(SuggestedWords suggestedWords) {
- if (suggestedWords == null)
- return;
-
+ public void setSuggestions(final SuggestedWords suggestedWords) {
clear();
mSuggestedWords = suggestedWords;
mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth());
@@ -675,7 +622,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
}
- public int setMoreSuggestionsHeight(int remainingHeight) {
+ public int setMoreSuggestionsHeight(final int remainingHeight) {
return mParams.setMoreSuggestionsHeight(remainingHeight);
}
@@ -684,7 +631,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
&& mParams.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0));
}
- public void showAddToDictionaryHint(CharSequence word, CharSequence hintText) {
+ public void showAddToDictionaryHint(final String word, final CharSequence hintText) {
clear();
mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText, this);
}
@@ -697,10 +644,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
return false;
}
- public SuggestedWords getSuggestions() {
- return mSuggestedWords;
- }
-
public void clear() {
mSuggestionsStrip.removeAllViews();
removeAllViews();
@@ -708,16 +651,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
dismissMoreSuggestions();
}
- private void hidePreview() {
- mPreviewPopup.dismiss();
- }
-
private final KeyboardActionListener mMoreSuggestionsListener =
new KeyboardActionListener.Adapter() {
@Override
- public boolean onCustomRequest(int requestCode) {
+ public boolean onCustomRequest(final int requestCode) {
final int index = requestCode;
- final CharSequence word = mSuggestedWords.getWord(index);
+ final String word = mSuggestedWords.getWord(index);
mListener.pickSuggestionManually(index, word);
dismissMoreSuggestions();
return true;
@@ -732,55 +671,62 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private final MoreKeysPanel.Controller mMoreSuggestionsController =
new MoreKeysPanel.Controller() {
@Override
- public boolean dismissMoreKeysPanel() {
- return dismissMoreSuggestions();
+ public boolean onDismissMoreKeysPanel() {
+ return mMainKeyboardView.onDismissMoreKeysPanel();
}
- };
- private boolean dismissMoreSuggestions() {
- if (mMoreSuggestionsWindow.isShowing()) {
- mMoreSuggestionsWindow.dismiss();
- return true;
+ @Override
+ public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
+ mMainKeyboardView.onShowMoreKeysPanel(panel);
}
- return false;
+
+ @Override
+ public void onCancelMoreKeysPanel() {
+ dismissMoreSuggestions();
+ }
+ };
+
+ boolean dismissMoreSuggestions() {
+ return mMoreSuggestionsView.dismissMoreKeysPanel();
}
@Override
- public boolean onLongClick(View view) {
+ public boolean onLongClick(final View view) {
+ KeyboardSwitcher.getInstance().hapticAndAudioFeedback(Constants.NOT_A_CODE);
return showMoreSuggestions();
}
- private boolean showMoreSuggestions() {
+ boolean showMoreSuggestions() {
+ final Keyboard parentKeyboard = KeyboardSwitcher.getInstance().getKeyboard();
+ if (parentKeyboard == null) {
+ return false;
+ }
final SuggestionStripViewParams params = mParams;
- if (params.mMoreSuggestionsAvailable) {
- final int stripWidth = getWidth();
- final View container = mMoreSuggestionsContainer;
- final int maxWidth = stripWidth - container.getPaddingLeft()
- - container.getPaddingRight();
- final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
- builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth,
- (int)(maxWidth * params.mMinMoreSuggestionsWidth),
- params.getMaxMoreSuggestionsRow());
- mMoreSuggestionsView.setKeyboard(builder.build());
- container.measure(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-
- final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView;
- final int pointX = stripWidth / 2;
- final int pointY = -params.mMoreSuggestionsBottomGap;
- moreKeysPanel.showMoreKeysPanel(
- this, mMoreSuggestionsController, pointX, pointY,
- mMoreSuggestionsWindow, mMoreSuggestionsListener);
- mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
- mOriginX = mLastX;
- mOriginY = mLastY;
- mKeyboardView.dimEntireKeyboard(true);
- for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
- mWords.get(i).setPressed(false);
- }
- return true;
+ if (!params.mMoreSuggestionsAvailable) {
+ return false;
}
- return false;
+ final int stripWidth = getWidth();
+ final View container = mMoreSuggestionsContainer;
+ final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight();
+ final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
+ builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth,
+ (int)(maxWidth * params.mMinMoreSuggestionsWidth),
+ params.getMaxMoreSuggestionsRow(), parentKeyboard);
+ mMoreSuggestionsView.setKeyboard(builder.build());
+ container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView;
+ final int pointX = stripWidth / 2;
+ final int pointY = -params.mMoreSuggestionsBottomGap;
+ moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY,
+ mMoreSuggestionsListener);
+ mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
+ mOriginX = mLastX;
+ mOriginY = mLastY;
+ for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
+ mWords.get(i).setPressed(false);
+ }
+ return true;
}
// Working variables for onLongClick and dispatchTouchEvent.
@@ -807,8 +753,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
};
@Override
- public boolean dispatchTouchEvent(MotionEvent me) {
- if (!mMoreSuggestionsWindow.isShowing()
+ public boolean dispatchTouchEvent(final MotionEvent me) {
+ if (!mMoreSuggestionsView.isShowingInParent()
|| mMoreSuggestionsMode == MORE_SUGGESTIONS_IN_MODAL_MODE) {
mLastX = (int)me.getX();
mLastY = (int)me.getY();
@@ -823,7 +769,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
final long eventTime = me.getEventTime();
final int index = me.getActionIndex();
final int id = me.getPointerId(index);
- final PointerTracker tracker = PointerTracker.getPointerTracker(id, moreKeysPanel);
final int x = (int)me.getX(index);
final int y = (int)me.getY(index);
final int translatedX = moreKeysPanel.translateX(x);
@@ -835,7 +780,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
// Decided to be in the sliding input mode only when the touch point has been moved
// upward.
mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE;
- tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
// Decided to be in the modal input mode
mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE;
@@ -844,12 +788,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
// MORE_SUGGESTIONS_IN_SLIDING_MODE
- tracker.processMotionEvent(action, translatedX, translatedY, eventTime, moreKeysPanel);
+ mMoreSuggestionsView.processMotionEvent(action, translatedX, translatedY, id, eventTime);
return true;
}
@Override
- public void onClick(View view) {
+ public void onClick(final View view) {
if (mParams.isAddToDictionaryShowing(view)) {
mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString());
clear();
@@ -863,15 +807,13 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
if (index >= mSuggestedWords.size())
return;
- final CharSequence word = mSuggestedWords.getWord(index);
+ final String word = mSuggestedWords.getWord(index);
mListener.pickSuggestionManually(index, word);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mHandler.cancelAllMessages();
- hidePreview();
dismissMoreSuggestions();
}
}