diff options
Diffstat (limited to 'java/src/com/android')
21 files changed, 702 insertions, 265 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index 538e515bc..43c61443e 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -73,10 +73,12 @@ public final class KeyboardId { public final boolean mLanguageSwitchKeyEnabled; public final String mCustomActionLabel; public final boolean mHasShortcutKey; + public final boolean mIsSplitLayout; private final int mHashCode; - public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) { + public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params, + boolean isSplitLayout) { mSubtype = params.mSubtype; mLocale = SubtypeLocaleUtils.getSubtypeLocale(mSubtype); mWidth = params.mKeyboardWidth; @@ -89,6 +91,7 @@ public final class KeyboardId { mCustomActionLabel = (mEditorInfo.actionLabel != null) ? mEditorInfo.actionLabel.toString() : null; mHasShortcutKey = params.mVoiceInputKeyEnabled; + mIsSplitLayout = isSplitLayout; mHashCode = computeHashCode(this); } @@ -108,7 +111,8 @@ public final class KeyboardId { id.mCustomActionLabel, id.navigateNext(), id.navigatePrevious(), - id.mSubtype + id.mSubtype, + id.mIsSplitLayout }); } @@ -128,7 +132,8 @@ public final class KeyboardId { && TextUtils.equals(other.mCustomActionLabel, mCustomActionLabel) && other.navigateNext() == navigateNext() && other.navigatePrevious() == navigatePrevious() - && other.mSubtype.equals(mSubtype); + && other.mSubtype.equals(mSubtype) + && other.mIsSplitLayout == mIsSplitLayout; } private static boolean isAlphabetKeyboard(final int elementId) { @@ -175,7 +180,7 @@ public final class KeyboardId { @Override public String toString() { - return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s]", + return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s%s]", elementIdToName(mElementId), mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET), mWidth, mHeight, @@ -187,7 +192,8 @@ public final class KeyboardId { (passwordInput() ? " passwordInput" : ""), (mHasShortcutKey ? " hasShortcutKey" : ""), (mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""), - (isMultiLine() ? " isMultiLine" : "") + (isMultiLine() ? " isMultiLine" : ""), + (mIsSplitLayout ? " isSplitLayout" : "") ); } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index 3f4367313..1dbecdc81 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -96,6 +96,7 @@ public final class KeyboardLayoutSet { private static final class ElementParams { int mKeyboardXmlId; boolean mProximityCharsCorrectionEnabled; + boolean mSupportsSplitLayout; public ElementParams() {} } @@ -168,7 +169,10 @@ public final class KeyboardLayoutSet { // attribute in a keyboard_layout_set XML file. Also each keyboard layout XML resource is // specified as an elementKeyboard attribute in the file. // The KeyboardId is an internal key for a Keyboard object. - final KeyboardId id = new KeyboardId(keyboardLayoutSetElementId, mParams); + + // TODO: AND mSupportsSplitLayout with the user preference that forces a split. + final KeyboardId id = new KeyboardId(keyboardLayoutSetElementId, mParams, + elementParams.mSupportsSplitLayout); try { return getKeyboard(elementParams, id); } catch (final RuntimeException e) { @@ -376,6 +380,8 @@ public final class KeyboardLayoutSet { elementParams.mProximityCharsCorrectionEnabled = a.getBoolean( R.styleable.KeyboardLayoutSet_Element_enableProximityCharsCorrection, false); + elementParams.mSupportsSplitLayout = a.getBoolean( + R.styleable.KeyboardLayoutSet_Element_supportsSplitLayout, false); mParams.mKeyboardLayoutSetElementIdToParamsMap.put(elementName, elementParams); } finally { a.recycle(); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 28f2dcf89..d36c199e2 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -18,7 +18,6 @@ package com.android.inputmethod.keyboard; import android.content.Context; import android.content.SharedPreferences; -import android.content.res.Configuration; import android.content.res.Resources; import android.preference.PreferenceManager; import android.util.Log; @@ -257,13 +256,12 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } public void onToggleEmojiKeyboard() { - if (mKeyboardLayoutSet == null) { - return; - } - if (isShowingEmojiPalettes()) { - setAlphabetKeyboard(); - } else { + if (mKeyboardLayoutSet == null || !isShowingEmojiPalettes()) { + mLatinIME.startShowingInputView(); setEmojiKeyboard(); + } else { + mLatinIME.stopShowingInputView(); + setAlphabetKeyboard(); } } diff --git a/java/src/com/android/inputmethod/keyboard/TextDecorator.java b/java/src/com/android/inputmethod/keyboard/TextDecorator.java index cf58d6a09..f614b2257 100644 --- a/java/src/com/android/inputmethod/keyboard/TextDecorator.java +++ b/java/src/com/android/inputmethod/keyboard/TextDecorator.java @@ -24,7 +24,6 @@ import android.os.Message; import android.text.TextUtils; import android.util.Log; import android.view.View; -import android.view.inputmethod.CursorAnchorInfo; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper; @@ -154,13 +153,11 @@ public class TextDecorator { * {@code false} is the input method is finishing the full screen mode. */ public void notifyFullScreenMode(final boolean fullScreenMode) { - final boolean currentFullScreenMode = mIsFullScreenMode; - if (!currentFullScreenMode && fullScreenMode) { - // Currently full screen mode is not supported. - // TODO: Support full screen mode. - mUiOperator.hideUi(); - } + final boolean fullScreenModeChanged = (mIsFullScreenMode != fullScreenMode); mIsFullScreenMode = fullScreenMode; + if (fullScreenModeChanged) { + layoutLater(); + } } /** @@ -183,11 +180,6 @@ public class TextDecorator { * @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}. */ public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) { - if (mIsFullScreenMode) { - // TODO: Consider to call InputConnection#requestCursorAnchorInfo to disable the - // event callback to suppress unnecessary event callbacks. - return; - } mCursorAnchorInfoWrapper = info; // Do not use layoutLater() to minimize the latency. layoutImmediately(); @@ -240,11 +232,6 @@ public class TextDecorator { } private void layoutMain() { - if (mIsFullScreenMode) { - cancelLayoutInternalUnexpectedly("Full screen mode isn't yet supported."); - return; - } - if (mMode != MODE_COMMIT && mMode != MODE_ADD_TO_DICTIONARY) { if (mMode == MODE_NONE) { cancelLayoutInternalExpectedly("Not ready for layouting."); @@ -328,7 +315,7 @@ public class TextDecorator { return; } } else { - if (!TextUtils.isEmpty(composingText)) { + if (!mIsFullScreenMode && !TextUtils.isEmpty(composingText)) { // This is an unexpected case. // TODO: Document this. mUiOperator.hideUi(); diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java index 46428839f..eced45ea5 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -61,13 +61,13 @@ public class DictionaryFacilitator { // dictionary. private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140; - private DictionaryGroup mDictionaryGroup = new DictionaryGroup(); + private DictionaryGroup[] mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() }; private boolean mIsUserDictEnabled = false; - private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); + private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0); // To synchronize assigning mDictionaryGroup to ensure closing dictionaries. private final Object mLock = new Object(); private final DistracterFilter mDistracterFilter; - private final PersonalizationDictionaryFacilitator mPersonalizationDictionaryFacilitator; + private final PersonalizationHelperForDictionaryFacilitator mPersonalizationHelper; private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS = new String[] { @@ -175,26 +175,27 @@ public class DictionaryFacilitator { public DictionaryFacilitator() { mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER; - mPersonalizationDictionaryFacilitator = null; + mPersonalizationHelper = null; } public DictionaryFacilitator(final Context context) { mDistracterFilter = new DistracterFilterCheckingExactMatchesAndSuggestions(context); - mPersonalizationDictionaryFacilitator = - new PersonalizationDictionaryFacilitator(context, mDistracterFilter); + mPersonalizationHelper = + new PersonalizationHelperForDictionaryFacilitator(context, mDistracterFilter); } public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) { mDistracterFilter.updateEnabledSubtypes(enabledSubtypes); - mPersonalizationDictionaryFacilitator.updateEnabledSubtypes(enabledSubtypes); + mPersonalizationHelper.updateEnabledSubtypes(enabledSubtypes); } public void setIsMonolingualUser(final boolean isMonolingualUser) { - mPersonalizationDictionaryFacilitator.setIsMonolingualUser(isMonolingualUser); + mPersonalizationHelper.setIsMonolingualUser(isMonolingualUser); } + // TODO: remove this, replace with version returning multiple locales public Locale getLocale() { - return mDictionaryGroup.mLocale; + return mDictionaryGroups[0].mLocale; } private static ExpandableBinaryDictionary getSubDict(final String dictType, @@ -226,97 +227,151 @@ public class DictionaryFacilitator { usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */); } - public void resetDictionariesWithDictNamePrefix(final Context context, final Locale newLocale, + private DictionaryGroup findDictionaryGroupWithLocale(final DictionaryGroup[] dictionaryGroups, + final Locale locale) { + for (int i = 0; i < dictionaryGroups.length; ++i) { + if (locale.equals(dictionaryGroups[i].mLocale)) { + return dictionaryGroups[i]; + } + } + return null; + } + + private DictionaryGroup getDictionaryGroupForActiveLanguage() { + // TODO: implement this + return mDictionaryGroups[0]; + } + + public void resetDictionariesWithDictNamePrefix(final Context context, + final Locale newLocaleToUse, final boolean useContactsDict, final boolean usePersonalizedDicts, final boolean forceReloadMainDictionary, final DictionaryInitializationListener listener, final String dictNamePrefix) { - final boolean localeHasBeenChanged = !newLocale.equals(mDictionaryGroup.mLocale); - // We always try to have the main dictionary. Other dictionaries can be unused. - final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary; + final HashMap<Locale, ArrayList<String>> existingDictsToCleanup = new HashMap<>(); + // TODO: use several locales + final Locale[] newLocales = new Locale[] { newLocaleToUse }; // TODO: Make subDictTypesToUse configurable by resource or a static final list. final HashSet<String> subDictTypesToUse = new HashSet<>(); + subDictTypesToUse.add(Dictionary.TYPE_USER); if (useContactsDict) { subDictTypesToUse.add(Dictionary.TYPE_CONTACTS); } - subDictTypesToUse.add(Dictionary.TYPE_USER); if (usePersonalizedDicts) { subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY); subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION); subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL); } - final Dictionary newMainDict; - if (reloadMainDictionary) { - // The main dictionary will be asynchronously loaded. - newMainDict = null; - } else { - newMainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN); - } - - final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>(); - for (final String dictType : SUB_DICT_TYPES) { - if (!subDictTypesToUse.contains(dictType)) { - // This dictionary will not be used. + // Gather all dictionaries. We'll remove them from the list to clean up later. + for (final Locale newLocale : newLocales) { + final ArrayList<String> dictsForLocale = new ArrayList<>(); + existingDictsToCleanup.put(newLocale, dictsForLocale); + final DictionaryGroup currentDictionaryGroupForLocale = + findDictionaryGroupWithLocale(mDictionaryGroups, newLocale); + if (null == currentDictionaryGroupForLocale) { continue; } - final ExpandableBinaryDictionary dict; - if (!localeHasBeenChanged && mDictionaryGroup.hasDict(dictType)) { - // Continue to use current dictionary. - dict = mDictionaryGroup.getSubDict(dictType); + for (final String dictType : SUB_DICT_TYPES) { + if (currentDictionaryGroupForLocale.hasDict(dictType)) { + dictsForLocale.add(dictType); + } + } + if (currentDictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN)) { + dictsForLocale.add(Dictionary.TYPE_MAIN); + } + } + + final DictionaryGroup[] newDictionaryGroups = new DictionaryGroup[newLocales.length]; + for (int i = 0; i < newLocales.length; ++i) { + final Locale newLocale = newLocales[i]; + final DictionaryGroup dictionaryGroupForLocale = + findDictionaryGroupWithLocale(mDictionaryGroups, newLocale); + final ArrayList<String> dictsToCleanupForLocale = existingDictsToCleanup.get(newLocale); + final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale); + + final Dictionary mainDict; + if (forceReloadMainDictionary || noExistingDictsForThisLocale + || !dictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN)) { + mainDict = null; } else { - // Start to use new dictionary. - dict = getSubDict(dictType, context, newLocale, null /* dictFile */, - dictNamePrefix); + mainDict = dictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN); + dictsToCleanupForLocale.remove(Dictionary.TYPE_MAIN); + } + + final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>(); + for (final String subDictType : subDictTypesToUse) { + final ExpandableBinaryDictionary subDict; + if (noExistingDictsForThisLocale + || !dictionaryGroupForLocale.hasDict(subDictType)) { + // Create a new dictionary. + subDict = getSubDict(subDictType, context, newLocale, null /* dictFile */, + dictNamePrefix); + } else { + // Reuse the existing dictionary, and don't close it at the end + subDict = dictionaryGroupForLocale.getSubDict(subDictType); + dictsToCleanupForLocale.remove(subDictType); + } + subDicts.put(subDictType, subDict); } - subDicts.put(dictType, dict); + newDictionaryGroups[i] = new DictionaryGroup(newLocale, mainDict, subDicts); } - // Replace DictionaryGroup. - final DictionaryGroup newDictionaryGroup = new DictionaryGroup(newLocale, newMainDict, subDicts); - final DictionaryGroup oldDictionaryGroup; + // Replace Dictionaries. + final DictionaryGroup[] oldDictionaryGroups; synchronized (mLock) { - oldDictionaryGroup = mDictionaryGroup; - mDictionaryGroup = newDictionaryGroup; + oldDictionaryGroups = mDictionaryGroups; + mDictionaryGroups = newDictionaryGroups; mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context); - if (reloadMainDictionary) { - asyncReloadMainDictionary(context, newLocale, listener); + if (hasAtLeastOneUninitializedMainDictionary()) { + asyncReloadUninitializedMainDictionaries(context, newLocales, listener); } } if (listener != null) { - listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary()); + listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary()); } + // Clean up old dictionaries. - if (reloadMainDictionary) { - oldDictionaryGroup.closeDict(Dictionary.TYPE_MAIN); - } - for (final String dictType : SUB_DICT_TYPES) { - if (localeHasBeenChanged || !subDictTypesToUse.contains(dictType)) { - oldDictionaryGroup.closeDict(dictType); + for (final Locale localeToCleanUp : existingDictsToCleanup.keySet()) { + final ArrayList<String> dictTypesToCleanUp = + existingDictsToCleanup.get(localeToCleanUp); + final DictionaryGroup dictionarySetToCleanup = + findDictionaryGroupWithLocale(oldDictionaryGroups, localeToCleanUp); + for (final String dictType : dictTypesToCleanUp) { + dictionarySetToCleanup.closeDict(dictType); } } - oldDictionaryGroup.mSubDictMap.clear(); } - private void asyncReloadMainDictionary(final Context context, final Locale locale, - final DictionaryInitializationListener listener) { + private void asyncReloadUninitializedMainDictionaries(final Context context, + final Locale[] locales, final DictionaryInitializationListener listener) { final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1); - mLatchForWaitingLoadingMainDictionary = latchForWaitingLoadingMainDictionary; + mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary; ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() { @Override public void run() { - final Dictionary mainDict = - DictionaryFactory.createMainDictionaryFromManager(context, locale); - synchronized (mLock) { - if (locale.equals(mDictionaryGroup.mLocale)) { - mDictionaryGroup.setMainDict(mainDict); - } else { - // Dictionary facilitator has been reset for another locale. - mainDict.close(); + for (final Locale locale : locales) { + final DictionaryGroup dictionaryGroup = + findDictionaryGroupWithLocale(mDictionaryGroups, locale); + if (null == dictionaryGroup) { + // This should never happen, but better safe than crashy + Log.w(TAG, "Expected a dictionary group for " + locale + " but none found"); + continue; + } + final Dictionary mainDict = + DictionaryFactory.createMainDictionaryFromManager(context, locale); + synchronized (mLock) { + if (locale.equals(dictionaryGroup.mLocale)) { + dictionaryGroup.setMainDict(mainDict); + } else { + // Dictionary facilitator has been reset for another locale. + mainDict.close(); + } } } if (listener != null) { - listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary()); + listener.onUpdateMainDictionaryAvailability( + hasAtLeastOneInitializedMainDictionary()); } latchForWaitingLoadingMainDictionary.countDown(); } @@ -349,60 +404,94 @@ public class DictionaryFacilitator { subDicts.put(dictType, dict); } } - mDictionaryGroup = new DictionaryGroup(locale, mainDictionary, subDicts); + mDictionaryGroups = new DictionaryGroup[] { + new DictionaryGroup(locale, mainDictionary, subDicts) }; } public void closeDictionaries() { - final DictionaryGroup dictionaryGroup; + final DictionaryGroup[] dictionaryGroups; synchronized (mLock) { - dictionaryGroup = mDictionaryGroup; - mDictionaryGroup = new DictionaryGroup(); + dictionaryGroups = mDictionaryGroups; + mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() }; } - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { - dictionaryGroup.closeDict(dictType); + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { + dictionaryGroup.closeDict(dictType); + } } mDistracterFilter.close(); - if (mPersonalizationDictionaryFacilitator != null) { - mPersonalizationDictionaryFacilitator.close(); + if (mPersonalizationHelper != null) { + mPersonalizationHelper.close(); } } @UsedForTesting public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) { - return mDictionaryGroup.getSubDict(dictName); + return mDictionaryGroups[0].getSubDict(dictName); } - // The main dictionary could have been loaded asynchronously. Don't cache the return value - // of this method. - public boolean hasInitializedMainDictionary() { - final Dictionary mainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN); - return mainDict != null && mainDict.isInitialized(); + // The main dictionaries are loaded asynchronously. Don't cache the return value + // of these methods. + public boolean hasAtLeastOneInitializedMainDictionary() { + final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN); + if (mainDict != null && mainDict.isInitialized()) { + return true; + } + } + return false; + } + + public boolean hasAtLeastOneUninitializedMainDictionary() { + final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN); + if (mainDict == null || !mainDict.isInitialized()) { + return true; + } + } + return false; } public boolean hasPersonalizationDictionary() { - return mDictionaryGroup.hasDict(Dictionary.TYPE_PERSONALIZATION); + final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + if (dictionaryGroup.hasDict(Dictionary.TYPE_PERSONALIZATION)) { + return true; + } + } + return false; } public void flushPersonalizationDictionary() { - final ExpandableBinaryDictionary personalizationDictUsedForSuggestion = - mDictionaryGroup.getSubDict(Dictionary.TYPE_PERSONALIZATION); - mPersonalizationDictionaryFacilitator.flushPersonalizationDictionariesToUpdate( - personalizationDictUsedForSuggestion); + final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion = + new HashSet<>(); + final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + final ExpandableBinaryDictionary personalizationDictUsedForSuggestion = + dictionaryGroup.getSubDict(Dictionary.TYPE_PERSONALIZATION); + personalizationDictsUsedForSuggestion.add(personalizationDictUsedForSuggestion); + } + mPersonalizationHelper.flushPersonalizationDictionariesToUpdate( + personalizationDictsUsedForSuggestion); mDistracterFilter.close(); } - public void waitForLoadingMainDictionary(final long timeout, final TimeUnit unit) + public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit) throws InterruptedException { - mLatchForWaitingLoadingMainDictionary.await(timeout, unit); + mLatchForWaitingLoadingMainDictionaries.await(timeout, unit); } @UsedForTesting public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit) throws InterruptedException { - waitForLoadingMainDictionary(timeout, unit); - final Map<String, ExpandableBinaryDictionary> dictMap = mDictionaryGroup.mSubDictMap; - for (final ExpandableBinaryDictionary dict : dictMap.values()) { - dict.waitAllTasksForTests(); + waitForLoadingMainDictionaries(timeout, unit); + final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + for (final ExpandableBinaryDictionary dict : dictionaryGroup.mSubDictMap.values()) { + dict.waitAllTasksForTests(); + } } } @@ -421,7 +510,7 @@ public class DictionaryFacilitator { public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized, final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds, final boolean blockPotentiallyOffensive) { - final DictionaryGroup dictionaryGroup = mDictionaryGroup; + final DictionaryGroup dictionaryGroup = getDictionaryGroupForActiveLanguage(); final String[] words = suggestion.split(Constants.WORD_SEPARATOR); PrevWordsInfo prevWordsInfoForCurrentWord = prevWordsInfo; for (int i = 0; i < words.length; i++) { @@ -488,7 +577,8 @@ public class DictionaryFacilitator { } private void removeWord(final String dictName, final String word) { - final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName); + final ExpandableBinaryDictionary dictionary = + getDictionaryGroupForActiveLanguage().getSubDict(dictName); if (dictionary != null) { dictionary.removeUnigramEntryDynamically(word); } @@ -504,20 +594,22 @@ public class DictionaryFacilitator { public SuggestionResults getSuggestionResults(final WordComposer composer, final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) { - final DictionaryGroup dictionaryGroup = mDictionaryGroup; + final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; final SuggestionResults suggestionResults = new SuggestionResults(SuggestedWords.MAX_SUGGESTIONS); final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT }; - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { - final Dictionary dictionary = dictionaryGroup.getDict(dictType); - if (null == dictionary) continue; - final ArrayList<SuggestedWordInfo> dictionarySuggestions = - dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo, - settingsValuesForSuggestion, sessionId, languageWeight); - if (null == dictionarySuggestions) continue; - suggestionResults.addAll(dictionarySuggestions); - if (null != suggestionResults.mRawSuggestions) { - suggestionResults.mRawSuggestions.addAll(dictionarySuggestions); + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { + final Dictionary dictionary = dictionaryGroup.getDict(dictType); + if (null == dictionary) continue; + final ArrayList<SuggestedWordInfo> dictionarySuggestions = + dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo, + settingsValuesForSuggestion, sessionId, languageWeight); + if (null == dictionarySuggestions) continue; + suggestionResults.addAll(dictionarySuggestions); + if (null != suggestionResults.mRawSuggestions) { + suggestionResults.mRawSuggestions.addAll(dictionarySuggestions); + } } } return suggestionResults; @@ -527,20 +619,22 @@ public class DictionaryFacilitator { if (TextUtils.isEmpty(word)) { return false; } - final DictionaryGroup dictionaryGroup = mDictionaryGroup; - if (dictionaryGroup.mLocale == null) { - return false; - } - final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale); - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { - final Dictionary dictionary = dictionaryGroup.getDict(dictType); - // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and - // would be immutable once it's finished initializing, but concretely a null test is - // probably good enough for the time being. - if (null == dictionary) continue; - if (dictionary.isValidWord(word) - || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) { - return true; + final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + if (dictionaryGroup.mLocale == null) { + continue; + } + final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale); + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { + final Dictionary dictionary = dictionaryGroup.getDict(dictType); + // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and + // would be immutable once it's finished initializing, but concretely a null test is + // probably good enough for the time being. + if (null == dictionary) continue; + if (dictionary.isValidWord(word) + || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) { + return true; + } } } return false; @@ -552,18 +646,20 @@ public class DictionaryFacilitator { return Dictionary.NOT_A_PROBABILITY; } int maxFreq = Dictionary.NOT_A_PROBABILITY; - final DictionaryGroup dictionaryGroup = mDictionaryGroup; - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { - final Dictionary dictionary = dictionaryGroup.getDict(dictType); - if (dictionary == null) continue; - final int tempFreq; - if (isGettingMaxFrequencyOfExactMatches) { - tempFreq = dictionary.getMaxFrequencyOfExactMatches(word); - } else { - tempFreq = dictionary.getFrequency(word); - } - if (tempFreq >= maxFreq) { - maxFreq = tempFreq; + final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { + final Dictionary dictionary = dictionaryGroup.getDict(dictType); + if (dictionary == null) continue; + final int tempFreq; + if (isGettingMaxFrequencyOfExactMatches) { + tempFreq = dictionary.getMaxFrequencyOfExactMatches(word); + } else { + tempFreq = dictionary.getFrequency(word); + } + if (tempFreq >= maxFreq) { + maxFreq = tempFreq; + } } } return maxFreq; @@ -578,9 +674,12 @@ public class DictionaryFacilitator { } private void clearSubDictionary(final String dictName) { - final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName); - if (dictionary != null) { - dictionary.clear(); + final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictName); + if (dictionary != null) { + dictionary.clear(); + } } } @@ -592,7 +691,7 @@ public class DictionaryFacilitator { // personalization dictionary. public void clearPersonalizationDictionary() { clearSubDictionary(Dictionary.TYPE_PERSONALIZATION); - mPersonalizationDictionaryFacilitator.clearDictionariesToUpdate(); + mPersonalizationHelper.clearDictionariesToUpdate(); } public void clearContextualDictionary() { @@ -603,14 +702,16 @@ public class DictionaryFacilitator { final PersonalizationDataChunk personalizationDataChunk, final SpacingAndPunctuations spacingAndPunctuations, final AddMultipleDictionaryEntriesCallback callback) { - mPersonalizationDictionaryFacilitator.addEntriesToPersonalizationDictionariesToUpdate( + mPersonalizationHelper.addEntriesToPersonalizationDictionariesToUpdate( getLocale(), personalizationDataChunk, spacingAndPunctuations, callback); } public void addPhraseToContextualDictionary(final String[] phrase, final int probability, final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) { + // TODO: we're inserting the phrase into the dictionary for the active language. Rethink + // this a bit from a theoretical point of view. final ExpandableBinaryDictionary contextualDict = - mDictionaryGroup.getSubDict(Dictionary.TYPE_CONTEXTUAL); + getDictionaryGroupForActiveLanguage().getSubDict(Dictionary.TYPE_CONTEXTUAL); if (contextualDict == null) { return; } @@ -643,22 +744,27 @@ public class DictionaryFacilitator { } public void dumpDictionaryForDebug(final String dictName) { - final ExpandableBinaryDictionary dictToDump = mDictionaryGroup.getSubDict(dictName); - if (dictToDump == null) { - Log.e(TAG, "Cannot dump " + dictName + ". " - + "The dictionary is not being used for suggestion or cannot be dumped."); - return; + final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + final ExpandableBinaryDictionary dictToDump = dictionaryGroup.getSubDict(dictName); + if (dictToDump == null) { + Log.e(TAG, "Cannot dump " + dictName + ". " + + "The dictionary is not being used for suggestion or cannot be dumped."); + return; + } + dictToDump.dumpAllWordsForDebug(); } - dictToDump.dumpAllWordsForDebug(); } public ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts() { final ArrayList<Pair<String, DictionaryStats>> statsOfEnabledSubDicts = new ArrayList<>(); - final DictionaryGroup dictionaryGroup = mDictionaryGroup; - for (final String dictType : SUB_DICT_TYPES) { - final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictType); - if (dictionary == null) continue; - statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats())); + final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; + for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { + for (final String dictType : SUB_DICT_TYPES) { + final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictType); + if (dictionary == null) continue; + statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats())); + } } return statsOfEnabledSubDicts; } diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java index fa0265d86..ff4a6bde1 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java @@ -84,7 +84,7 @@ public class DictionaryFacilitatorLruCache { private void waitForLoadingMainDictionary(final DictionaryFacilitator dictionaryFacilitator) { for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) { try { - dictionaryFacilitator.waitForLoadingMainDictionary( + dictionaryFacilitator.waitForLoadingMainDictionaries( WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS); return; } catch (final InterruptedException e) { diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index a1dd67f27..671ba6714 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -156,23 +156,25 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } private void asyncExecuteTaskWithWriteLock(final Runnable task) { - asyncExecuteTaskWithLock(mLock.writeLock(), task); + asyncExecuteTaskWithLock(mLock.writeLock(), mDictName /* executorName */, task); } - private void asyncExecuteTaskWithLock(final Lock lock, final Runnable task) { - asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, task); + private void asyncExecuteTaskWithLock(final Lock lock, final String executorName, + final Runnable task) { + asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, executorName, task); } private void asyncPreCheckAndExecuteTaskWithWriteLock( final Callable<Boolean> preCheckTask, final Runnable task) { - asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask, task); + asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask, + mDictName /* executorName */, task); } // Execute task with lock when the result of preCheckTask is true or preCheckTask is null. private void asyncPreCheckAndExecuteTaskWithLock(final Lock lock, - final Callable<Boolean> preCheckTask, final Runnable task) { - ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { + final Callable<Boolean> preCheckTask, final String executorName, final Runnable task) { + ExecutorUtils.getExecutor(executorName).execute(new Runnable() { @Override public void run() { if (preCheckTask != null) { @@ -676,10 +678,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { public void dumpAllWordsForDebug() { reloadDictionaryIfRequired(); - asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() { + asyncExecuteTaskWithLock(mLock.readLock(), "dumpAllWordsForDebug", new Runnable() { @Override public void run() { - Log.d(TAG, "Dump dictionary: " + mDictName); + Log.d(TAG, "Dump dictionary: " + mDictName + " for " + mLocale); try { final DictionaryHeader header = mBinaryDictionary.getHeader(); Log.d(TAG, "Format version: " + mBinaryDictionary.getFormatVersion()); diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index 782e18255..50a6e48e3 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -49,6 +49,7 @@ public final class InputAttributes { * {@link com.android.inputmethod.latin.settings.SettingsValues#mGestureFloatingPreviewTextEnabled} */ final public boolean mDisableGestureFloatingPreviewText; + final public boolean mIsGeneralTextInput; final private int mInputType; final private EditorInfo mEditorInfo; final private String mPackageNameForPrivateImeOptions; @@ -84,6 +85,7 @@ public final class InputAttributes { mShouldInsertSpacesAutomatically = false; mShouldShowVoiceInputKey = false; mDisableGestureFloatingPreviewText = false; + mIsGeneralTextInput = false; return; } // inputClass == InputType.TYPE_CLASS_TEXT @@ -128,6 +130,15 @@ public final class InputAttributes { || (!flagAutoCorrect && !flagMultiLine); mApplicationSpecifiedCompletionOn = flagAutoComplete && isFullscreenMode; + + // If we come here, inputClass is always TYPE_CLASS_TEXT + mIsGeneralTextInput = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS != variation + && InputType.TYPE_TEXT_VARIATION_PASSWORD != variation + && InputType.TYPE_TEXT_VARIATION_PHONETIC != variation + && InputType.TYPE_TEXT_VARIATION_URI != variation + && InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD != variation + && InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != variation + && InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD != variation; } public boolean isTypeNull() { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index d2104997c..475782042 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -46,13 +46,14 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup.LayoutParams; +import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodSubtype; +import android.widget.TextView; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.annotations.UsedForTesting; @@ -86,6 +87,7 @@ import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor; import com.android.inputmethod.latin.utils.ApplicationUtils; import com.android.inputmethod.latin.utils.CapsModeUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; +import com.android.inputmethod.latin.utils.CursorAnchorInfoUtils; import com.android.inputmethod.latin.utils.DialogUtils; import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatchesAndSuggestions; import com.android.inputmethod.latin.utils.ImportantNoticeUtils; @@ -152,6 +154,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO: Move these {@link View}s to {@link KeyboardSwitcher}. private View mInputView; private SuggestionStripView mSuggestionStripView; + private TextView mExtractEditText; private RichInputMethodManager mRichImm; @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; @@ -159,6 +162,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private final SubtypeState mSubtypeState = new SubtypeState(); private final SpecialKeyDetector mSpecialKeyDetector; private StatsUtilsManager mStatsUtilsManager; + // Working variable for {@link #startShowingInputView()} and + // {@link #onEvaluateInputViewShown()}. + private boolean mIsExecutingStartShowingInputView; // Object for reacting to adding/removing a dictionary pack. private final BroadcastReceiver mDictionaryPackInstallReceiver = @@ -740,6 +746,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public View onCreateInputView() { + StatsUtils.onCreateInputView(); return mKeyboardSwitcher.onCreateInputView(mIsHardwareAcceleratedDrawingEnabled); } @@ -755,6 +762,49 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } @Override + public void setExtractView(final View view) { + final TextView prevExtractEditText = mExtractEditText; + super.setExtractView(view); + TextView nextExtractEditText = null; + if (view != null) { + final View extractEditText = view.findViewById(android.R.id.inputExtractEditText); + if (extractEditText instanceof TextView) { + nextExtractEditText = (TextView)extractEditText; + } + } + if (prevExtractEditText == nextExtractEditText) { + return; + } + if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK && prevExtractEditText != null) { + prevExtractEditText.getViewTreeObserver().removeOnPreDrawListener( + mExtractTextViewPreDrawListener); + } + mExtractEditText = nextExtractEditText; + if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK && mExtractEditText != null) { + mExtractEditText.getViewTreeObserver().addOnPreDrawListener( + mExtractTextViewPreDrawListener); + } + } + + private final ViewTreeObserver.OnPreDrawListener mExtractTextViewPreDrawListener = + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + onExtractTextViewPreDraw(); + return true; + } + }; + + private void onExtractTextViewPreDraw() { + if (!ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK || !isFullscreenMode() + || mExtractEditText == null) { + return; + } + final CursorAnchorInfo info = CursorAnchorInfoUtils.getCursorAnchorInfo(mExtractEditText); + mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info)); + } + + @Override public void setCandidatesView(final View view) { // To ensure that CandidatesView will never be set. return; @@ -772,6 +822,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onFinishInputView(final boolean finishingInput) { + StatsUtils.onFinishInputView(); mHandler.onFinishInputView(finishingInput); } @@ -849,6 +900,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo); final boolean isDifferentTextField = !restarting || inputTypeChanged; + + StatsUtils.onStartInputView(editorInfo.inputType, + Settings.getInstance().getCurrent().mDisplayOrientation, + !isDifferentTextField); + if (isDifferentTextField) { mSubtypeSwitcher.updateParametersOnStartInputView(); } @@ -944,7 +1000,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.cancelUpdateSuggestionStrip(); mainKeyboardView.setMainDictionaryAvailability( - mDictionaryFacilitator.hasInitializedMainDictionary()); + mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()); mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn, currentSettingsValues.mKeyPreviewPopupDismissDelay); mainKeyboardView.setSlidingKeyInputPreviewEnabled( @@ -1016,9 +1072,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // We cannot mark this method as @Override until new SDK becomes publicly available. // @Override public void onUpdateCursorAnchorInfo(final CursorAnchorInfo info) { - if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK) { - mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info)); + if (!ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK || isFullscreenMode()) { + return; } + mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info)); } /** @@ -1137,22 +1194,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen outInsets.visibleTopInsets = visibleTopY; } - @Override - public boolean onEvaluateInputViewShown() { - // Always show {@link InputView}. - return true; + public void startShowingInputView() { + mIsExecutingStartShowingInputView = true; + // This {@link #showWindow(boolean)} will eventually call back + // {@link #onEvaluateInputViewShown()}. + showWindow(true /* showInput */); + mIsExecutingStartShowingInputView = false; + } + + public void stopShowingInputView() { + showWindow(false /* showInput */); } @Override - public boolean onShowInputRequested(final int flags, final boolean configChange) { - final SettingsValues settingsValues = mSettings.getCurrent(); - if ((flags & InputMethod.SHOW_EXPLICIT) == 0 && settingsValues.mHasHardwareKeyboard) { - // Even when IME is implicitly shown and physical keyboard is connected, we should - // show {@link InputView}. - // See {@link InputMethodService#onShowInputRequested(int,boolean)}. + public boolean onEvaluateInputViewShown() { + if (mIsExecutingStartShowingInputView) { return true; } - return super.onShowInputRequested(flags, configChange); + return super.onEvaluateInputViewShown(); } @Override @@ -1171,9 +1230,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // hack for now. Let's get rid of this once the framework gets fixed. final EditorInfo ei = getCurrentInputEditorInfo(); return !(ei != null && ((ei.imeOptions & EditorInfo.IME_FLAG_NO_EXTRACT_UI) != 0)); - } else { - return false; } + return false; } @Override @@ -1219,9 +1277,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (null == keyboard) { return CoordinateUtils.newCoordinateArray(codePoints.length, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); - } else { - return keyboard.getCoordinates(codePoints); } + return keyboard.getCoordinates(codePoints); } // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is diff --git a/java/src/com/android/inputmethod/latin/PersonalizationDictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java index aa8e312a4..396d062f8 100644 --- a/java/src/com/android/inputmethod/latin/PersonalizationDictionaryFacilitator.java +++ b/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java @@ -38,15 +38,15 @@ import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; /** * Class for managing and updating personalization dictionaries. */ -public class PersonalizationDictionaryFacilitator { +public class PersonalizationHelperForDictionaryFacilitator { private final Context mContext; private final DistracterFilter mDistracterFilter; private final HashMap<String, HashSet<Locale>> mLangToLocalesMap = new HashMap<>(); private final HashMap<Locale, ExpandableBinaryDictionary> mPersonalizationDictsToUpdate = new HashMap<>(); - private boolean mIsMonolingualUser = false;; + private boolean mIsMonolingualUser = false; - PersonalizationDictionaryFacilitator(final Context context, + PersonalizationHelperForDictionaryFacilitator(final Context context, final DistracterFilter distracterFilter) { mContext = context; mDistracterFilter = distracterFilter; @@ -88,17 +88,17 @@ public class PersonalizationDictionaryFacilitator { /** * Flush personalization dictionaries to dictionary files. Close dictionaries after writing - * files except the dictionary that is used for generating suggestions. + * files except the dictionaries that is used for generating suggestions. * - * @param personalizationDictUsedForSuggestion the personalization dictionary used for + * @param personalizationDictsUsedForSuggestion the personalization dictionaries used for * generating suggestions that won't be closed. */ public void flushPersonalizationDictionariesToUpdate( - final ExpandableBinaryDictionary personalizationDictUsedForSuggestion) { + final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion) { for (final ExpandableBinaryDictionary personalizationDict : mPersonalizationDictsToUpdate.values()) { personalizationDict.asyncFlushBinaryDictionary(); - if (personalizationDict != personalizationDictUsedForSuggestion) { + if (!personalizationDictsUsedForSuggestion.contains(personalizationDict)) { // Close if the dictionary is not being used for suggestion. personalizationDict.close(); } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 9e4aa40a2..9bf017578 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -157,7 +157,7 @@ public final class Suggest { if (!isCorrectionEnabled || !allowsToBeAutoCorrected || resultsArePredictions || suggestionResults.isEmpty() || wordComposer.hasDigits() || wordComposer.isMostlyCaps() || wordComposer.isResumed() - || !mDictionaryFacilitator.hasInitializedMainDictionary() + || !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary() || suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) { // If we don't have a main dictionary, we never want to auto-correct. The reason for // this is, the user may have a contact whose name happens to match a valid word in diff --git a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java new file mode 100644 index 000000000..f5e4d33a2 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.settings; + +import android.os.Bundle; + +import com.android.inputmethod.latin.R; + + +/** + * "Appearance" settings sub screen. + */ +public final class AppearanceSettingsFragment extends SubScreenFragment { + @Override + public void onCreate(final Bundle icicle) { + super.onCreate(icicle); + addPreferencesFromResource(R.xml.prefs_screen_appearance); + } + + @Override + public void onResume() { + super.onResume(); + CustomInputStyleSettingsFragment.updateCustomInputStylesSummary( + findPreference(Settings.PREF_CUSTOM_INPUT_STYLES)); + ThemeSettingsFragment.updateKeyboardThemeSummary(findPreference(Settings.SCREEN_THEME)); + } +} diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java index d53a61654..9bc398654 100644 --- a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java @@ -31,6 +31,7 @@ import android.preference.Preference; import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; import android.support.v4.view.ViewCompat; +import android.text.TextUtils; import android.util.Pair; import android.view.LayoutInflater; import android.view.Menu; @@ -396,6 +397,25 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment { // Empty constructor for fragment generation. } + static void updateCustomInputStylesSummary(final Preference pref) { + // When we are called from the Settings application but we are not already running, some + // singleton and utility classes may not have been initialized. We have to call + // initialization method of these classes here. See {@link LatinIME#onCreate()}. + SubtypeLocaleUtils.init(pref.getContext()); + + final Resources res = pref.getContext().getResources(); + final SharedPreferences prefs = pref.getSharedPreferences(); + final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res); + final InputMethodSubtype[] subtypes = + AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype); + final ArrayList<String> subtypeNames = new ArrayList<>(); + for (final InputMethodSubtype subtype : subtypes) { + subtypeNames.add(SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)); + } + // TODO: A delimiter of custom input styles should be localized. + pref.setSummary(TextUtils.join(", ", subtypeNames)); + } + @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java index fcdd39316..b073c50a4 100644 --- a/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java @@ -16,66 +16,27 @@ package com.android.inputmethod.latin.settings; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Resources; import android.os.Bundle; -import android.preference.PreferenceScreen; -import android.text.TextUtils; -import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import java.util.ArrayList; /** - * "Multi lingual options" settings sub screen. + * "Multilingual options" settings sub screen. * * This settings sub screen handles the following input preferences. * - Language switch key * - Switch to other input methods - * - Custom input styles */ public final class MultiLingualSettingsFragment extends SubScreenFragment { @Override public void onCreate(final Bundle icicle) { super.onCreate(icicle); - addPreferencesFromResource(R.xml.prefs_screen_multi_lingual); - - final Context context = getActivity(); - - // When we are called from the Settings application but we are not already running, some - // singleton and utility classes may not have been initialized. We have to call - // initialization method of these classes here. See {@link LatinIME#onCreate()}. - SubtypeLocaleUtils.init(context); - + addPreferencesFromResource(R.xml.prefs_screen_multilingual); if (!Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS) { removePreference(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY); removePreference(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST); } } - - @Override - public void onResume() { - super.onResume(); - updateCustomInputStylesSummary(); - } - - private void updateCustomInputStylesSummary() { - final SharedPreferences prefs = getSharedPreferences(); - final Resources res = getResources(); - final PreferenceScreen customInputStyles = - (PreferenceScreen)findPreference(Settings.PREF_CUSTOM_INPUT_STYLES); - final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res); - final InputMethodSubtype[] subtypes = - AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype); - final ArrayList<String> subtypeNames = new ArrayList<>(); - for (final InputMethodSubtype subtype : subtypes) { - subtypeNames.add(SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)); - } - // TODO: A delimiter of custom input styles should be localized. - customInputStyles.setSummary(TextUtils.join(", ", subtypeNames)); - } } diff --git a/java/src/com/android/inputmethod/latin/settings/InputSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java index f459d68dd..49db2bdc0 100644 --- a/java/src/com/android/inputmethod/latin/settings/InputSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java @@ -27,7 +27,7 @@ import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SubtypeSwitcher; /** - * "Input preferences" settings sub screen. + * "Preferences" settings sub screen. * * This settings sub screen handles the following input preferences. * - Auto-capitalization @@ -37,11 +37,11 @@ import com.android.inputmethod.latin.SubtypeSwitcher; * - Popup on keypress * - Voice input key */ -public final class InputSettingsFragment extends SubScreenFragment { +public final class PreferencesSettingsFragment extends SubScreenFragment { @Override public void onCreate(final Bundle icicle) { super.onCreate(icicle); - addPreferencesFromResource(R.xml.prefs_screen_input); + addPreferencesFromResource(R.xml.prefs_screen_preferences); final Resources res = getResources(); final Context context = getActivity(); diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java index 3c7a99102..0de2d8831 100644 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ b/java/src/com/android/inputmethod/latin/settings/Settings.java @@ -42,9 +42,10 @@ import java.util.concurrent.locks.ReentrantLock; public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = Settings.class.getSimpleName(); // Settings screens - public static final String SCREEN_INPUT = "screen_input"; + public static final String SCREEN_PREFERENCES = "screen_preferences"; + public static final String SCREEN_APPEARANCE = "screen_appearance"; public static final String SCREEN_THEME = "screen_theme"; - public static final String SCREEN_MULTI_LINGUAL = "screen_multi_lingual"; + public static final String SCREEN_MULTILINGUAL = "screen_multilingual"; public static final String SCREEN_GESTURE = "screen_gesture"; public static final String SCREEN_CORRECTION = "screen_correction"; public static final String SCREEN_ADVANCED = "screen_advanced"; @@ -69,6 +70,9 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang "pref_key_use_double_space_period"; public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE = "pref_key_block_potentially_offensive"; + // No multilingual options in Android L and above for now. + public static final boolean SHOW_MULTILINGUAL_SETTINGS = + BuildCompatUtils.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.KITKAT; public static final boolean ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS = BuildCompatUtils.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.KITKAT; public static final boolean HAS_UI_TO_ACCEPT_TYPED_WORD = diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java index ff7495853..4fc17387f 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin.settings; import android.content.Intent; import android.os.Bundle; +import android.preference.Preference; import android.preference.PreferenceScreen; import android.view.Menu; import android.view.MenuInflater; @@ -46,12 +47,10 @@ public final class SettingsFragment extends InputMethodSettingsFragment { final PreferenceScreen preferenceScreen = getPreferenceScreen(); preferenceScreen.setTitle( ApplicationUtils.getActivityTitleResId(getActivity(), SettingsActivity.class)); - } - - @Override - public void onResume() { - super.onResume(); - ThemeSettingsFragment.updateKeyboardThemeSummary(findPreference(Settings.SCREEN_THEME)); + if (!Settings.SHOW_MULTILINGUAL_SETTINGS) { + final Preference multilingualOptions = findPreference(Settings.SCREEN_MULTILINGUAL); + preferenceScreen.removePreference(multilingualOptions); + } } @Override diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index c891a2e14..270b22a4a 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -149,7 +149,8 @@ public class SettingsValues { ? Settings.readShowsLanguageSwitchKey(prefs) : true /* forcibly */; mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true); mUsePersonalizedDicts = prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true); - mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true); + mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true) + && inputAttributes.mIsGeneralTextInput; mBlockPotentiallyOffensive = Settings.readBlockPotentiallyOffensive(prefs, res); mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(autoCorrectionThresholdRawValue, res); mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 49b34d391..352391611 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -185,7 +185,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService try { final DictionaryFacilitator dictionaryFacilitator = mDictionaryFacilitatorCache.get(locale); - return dictionaryFacilitator.hasInitializedMainDictionary(); + return dictionaryFacilitator.hasAtLeastOneInitializedMainDictionary(); } finally { mSemaphore.release(); } diff --git a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java new file mode 100644 index 000000000..9dc0524a2 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.utils; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.inputmethodservice.ExtractEditText; +import android.inputmethodservice.InputMethodService; +import android.text.Layout; +import android.text.Spannable; +import android.view.View; +import android.view.ViewParent; +import android.view.inputmethod.CursorAnchorInfo; +import android.widget.TextView; + +/** + * This class allows input methods to extract {@link CursorAnchorInfo} directly from the given + * {@link TextView}. This is useful and even necessary to support full-screen mode where the default + * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} event callback must be + * ignored because it reports the character locations of the target application rather than + * characters on {@link ExtractEditText}. + */ +public final class CursorAnchorInfoUtils { + private CursorAnchorInfoUtils() { + // This helper class is not instantiable. + } + + private static boolean isPositionVisible(final View view, final float positionX, + final float positionY) { + final float[] position = new float[] { positionX, positionY }; + View currentView = view; + + while (currentView != null) { + if (currentView != view) { + // Local scroll is already taken into account in positionX/Y + position[0] -= currentView.getScrollX(); + position[1] -= currentView.getScrollY(); + } + + if (position[0] < 0 || position[1] < 0 || + position[0] > currentView.getWidth() || position[1] > currentView.getHeight()) { + return false; + } + + if (!currentView.getMatrix().isIdentity()) { + currentView.getMatrix().mapPoints(position); + } + + position[0] += currentView.getLeft(); + position[1] += currentView.getTop(); + + final ViewParent parent = currentView.getParent(); + if (parent instanceof View) { + currentView = (View) parent; + } else { + // We've reached the ViewRoot, stop iterating + currentView = null; + } + } + + // We've been able to walk up the view hierarchy and the position was never clipped + return true; + } + + /** + * Returns {@link CursorAnchorInfo} from the given {@link TextView}. + * @param textView the target text view from which {@link CursorAnchorInfo} is to be extracted. + * @return the {@link CursorAnchorInfo} object based on the current layout. {@code null} if it + * is not feasible. + */ + public static CursorAnchorInfo getCursorAnchorInfo(final TextView textView) { + Layout layout = textView.getLayout(); + if (layout == null) { + return null; + } + + final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); + + final int selectionStart = textView.getSelectionStart(); + builder.setSelectionRange(selectionStart, textView.getSelectionEnd()); + + // Construct transformation matrix from view local coordinates to screen coordinates. + final Matrix viewToScreenMatrix = new Matrix(textView.getMatrix()); + final int[] viewOriginInScreen = new int[2]; + textView.getLocationOnScreen(viewOriginInScreen); + viewToScreenMatrix.postTranslate(viewOriginInScreen[0], viewOriginInScreen[1]); + builder.setMatrix(viewToScreenMatrix); + + if (layout.getLineCount() == 0) { + return null; + } + final Rect lineBoundsWithoutOffset = new Rect(); + final Rect lineBoundsWithOffset = new Rect(); + layout.getLineBounds(0, lineBoundsWithoutOffset); + textView.getLineBounds(0, lineBoundsWithOffset); + final float viewportToContentHorizontalOffset = lineBoundsWithOffset.left + - lineBoundsWithoutOffset.left - textView.getScrollX(); + final float viewportToContentVerticalOffset = lineBoundsWithOffset.top + - lineBoundsWithoutOffset.top - textView.getScrollY(); + + final CharSequence text = textView.getText(); + if (text instanceof Spannable) { + // Here we assume that the composing text is marked as SPAN_COMPOSING flag. This is not + // necessarily true, but basically works. + int composingTextStart = text.length(); + int composingTextEnd = 0; + final Spannable spannable = (Spannable) text; + final Object[] spans = spannable.getSpans(0, text.length(), Object.class); + for (Object span : spans) { + final int spanFlag = spannable.getSpanFlags(span); + if ((spanFlag & Spannable.SPAN_COMPOSING) != 0) { + composingTextStart = Math.min(composingTextStart, + spannable.getSpanStart(span)); + composingTextEnd = Math.max(composingTextEnd, spannable.getSpanEnd(span)); + } + } + + final boolean hasComposingText = + (0 <= composingTextStart) && (composingTextStart < composingTextEnd); + if (hasComposingText) { + final CharSequence composingText = text.subSequence(composingTextStart, + composingTextEnd); + builder.setComposingText(composingTextStart, composingText); + + final int minLine = layout.getLineForOffset(composingTextStart); + final int maxLine = layout.getLineForOffset(composingTextEnd - 1); + for (int line = minLine; line <= maxLine; ++line) { + final int lineStart = layout.getLineStart(line); + final int lineEnd = layout.getLineEnd(line); + final int offsetStart = Math.max(lineStart, composingTextStart); + final int offsetEnd = Math.min(lineEnd, composingTextEnd); + final boolean ltrLine = + layout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; + final float[] widths = new float[offsetEnd - offsetStart]; + layout.getPaint().getTextWidths(text, offsetStart, offsetEnd, widths); + final float top = layout.getLineTop(line); + final float bottom = layout.getLineBottom(line); + for (int offset = offsetStart; offset < offsetEnd; ++offset) { + final float charWidth = widths[offset - offsetStart]; + final boolean isRtl = layout.isRtlCharAt(offset); + final float primary = layout.getPrimaryHorizontal(offset); + final float secondary = layout.getSecondaryHorizontal(offset); + // TODO: This doesn't work perfectly for text with custom styles and TAB + // chars. + final float left; + final float right; + if (ltrLine) { + if (isRtl) { + left = secondary - charWidth; + right = secondary; + } else { + left = primary; + right = primary + charWidth; + } + } else { + if (!isRtl) { + left = secondary; + right = secondary + charWidth; + } else { + left = primary - charWidth; + right = primary; + } + } + // TODO: Check top-right and bottom-left as well. + final float localLeft = left + viewportToContentHorizontalOffset; + final float localRight = right + viewportToContentHorizontalOffset; + final float localTop = top + viewportToContentVerticalOffset; + final float localBottom = bottom + viewportToContentVerticalOffset; + final boolean isTopLeftVisible = isPositionVisible(textView, + localLeft, localTop); + final boolean isBottomRightVisible = + isPositionVisible(textView, localRight, localBottom); + int characterBoundsFlags = 0; + if (isTopLeftVisible || isBottomRightVisible) { + characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; + } + if (!isTopLeftVisible || !isTopLeftVisible) { + characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; + } + if (isRtl) { + characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; + } + // Here offset is the index in Java chars. + builder.addCharacterBounds(offset, localLeft, localTop, localRight, + localBottom, characterBoundsFlags); + } + } + } + } + + // Treat selectionStart as the insertion point. + if (0 <= selectionStart) { + final int offset = selectionStart; + final int line = layout.getLineForOffset(offset); + final float insertionMarkerX = layout.getPrimaryHorizontal(offset) + + viewportToContentHorizontalOffset; + final float insertionMarkerTop = layout.getLineTop(line) + + viewportToContentVerticalOffset; + final float insertionMarkerBaseline = layout.getLineBaseline(line) + + viewportToContentVerticalOffset; + final float insertionMarkerBottom = layout.getLineBottom(line) + + viewportToContentVerticalOffset; + final boolean isTopVisible = + isPositionVisible(textView, insertionMarkerX, insertionMarkerTop); + final boolean isBottomVisible = + isPositionVisible(textView, insertionMarkerX, insertionMarkerBottom); + int insertionMarkerFlags = 0; + if (isTopVisible || isBottomVisible) { + insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; + } + if (!isTopVisible || !isBottomVisible) { + insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; + } + if (layout.isRtlCharAt(offset)) { + insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL; + } + builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, + insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags); + } + return builder.build(); + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java index 08f5b0b41..c2167a76b 100644 --- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java @@ -19,12 +19,13 @@ package com.android.inputmethod.latin.utils; import com.android.inputmethod.dictionarypack.DictionarySettingsFragment; import com.android.inputmethod.latin.about.AboutPreferences; import com.android.inputmethod.latin.settings.AdvancedSettingsFragment; +import com.android.inputmethod.latin.settings.AppearanceSettingsFragment; import com.android.inputmethod.latin.settings.CorrectionSettingsFragment; import com.android.inputmethod.latin.settings.CustomInputStyleSettingsFragment; import com.android.inputmethod.latin.settings.DebugSettingsFragment; import com.android.inputmethod.latin.settings.GestureSettingsFragment; -import com.android.inputmethod.latin.settings.InputSettingsFragment; import com.android.inputmethod.latin.settings.MultiLingualSettingsFragment; +import com.android.inputmethod.latin.settings.PreferencesSettingsFragment; import com.android.inputmethod.latin.settings.SettingsFragment; import com.android.inputmethod.latin.settings.ThemeSettingsFragment; import com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsFragment; @@ -40,7 +41,8 @@ public class FragmentUtils { static { sLatinImeFragments.add(DictionarySettingsFragment.class.getName()); sLatinImeFragments.add(AboutPreferences.class.getName()); - sLatinImeFragments.add(InputSettingsFragment.class.getName()); + sLatinImeFragments.add(PreferencesSettingsFragment.class.getName()); + sLatinImeFragments.add(AppearanceSettingsFragment.class.getName()); sLatinImeFragments.add(ThemeSettingsFragment.class.getName()); sLatinImeFragments.add(MultiLingualSettingsFragment.class.getName()); sLatinImeFragments.add(CustomInputStyleSettingsFragment.class.getName()); |