diff options
Diffstat (limited to 'java/src')
50 files changed, 1395 insertions, 896 deletions
diff --git a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java index 5af31795c..f4f54b624 100644 --- a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java @@ -16,13 +16,20 @@ package com.android.inputmethod.compat; +import android.annotation.TargetApi; import android.graphics.Matrix; import android.graphics.RectF; +import android.os.Build; +import android.view.inputmethod.CursorAnchorInfo; -import com.android.inputmethod.annotations.UsedForTesting; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -@UsedForTesting -public final class CursorAnchorInfoCompatWrapper { +/** + * A wrapper for {@link CursorAnchorInfo}, which has been introduced in API Level 21. You can use + * this wrapper to avoid direct dependency on newly introduced types. + */ +public class CursorAnchorInfoCompatWrapper { /** * The insertion marker or character bounds have at least one visible region. @@ -39,123 +46,138 @@ public final class CursorAnchorInfoCompatWrapper { */ public static final int FLAG_IS_RTL = 0x04; - // Note that CursorAnchorInfo has been introduced in API level XX (Build.VERSION_CODE.LXX). - private static final CompatUtils.ClassWrapper sCursorAnchorInfoClass; - private static final CompatUtils.ToIntMethodWrapper sGetSelectionStartMethod; - private static final CompatUtils.ToIntMethodWrapper sGetSelectionEndMethod; - private static final CompatUtils.ToObjectMethodWrapper<RectF> sGetCharacterBoundsMethod; - private static final CompatUtils.ToIntMethodWrapper sGetCharacterBoundsFlagsMethod; - private static final CompatUtils.ToObjectMethodWrapper<CharSequence> sGetComposingTextMethod; - private static final CompatUtils.ToIntMethodWrapper sGetComposingTextStartMethod; - private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerBaselineMethod; - private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerBottomMethod; - private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerHorizontalMethod; - private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerTopMethod; - private static final CompatUtils.ToObjectMethodWrapper<Matrix> sGetMatrixMethod; - private static final CompatUtils.ToIntMethodWrapper sGetInsertionMarkerFlagsMethod; - - private static int INVALID_TEXT_INDEX = -1; - static { - sCursorAnchorInfoClass = CompatUtils.getClassWrapper( - "android.view.inputmethod.CursorAnchorInfo"); - sGetSelectionStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getSelectionStart", INVALID_TEXT_INDEX); - sGetSelectionEndMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getSelectionEnd", INVALID_TEXT_INDEX); - sGetCharacterBoundsMethod = sCursorAnchorInfoClass.getMethod( - "getCharacterBounds", (RectF)null, int.class); - sGetCharacterBoundsFlagsMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getCharacterBoundsFlags", 0, int.class); - sGetComposingTextMethod = sCursorAnchorInfoClass.getMethod( - "getComposingText", (CharSequence)null); - sGetComposingTextStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getComposingTextStart", INVALID_TEXT_INDEX); - sGetInsertionMarkerBaselineMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getInsertionMarkerBaseline", 0.0f); - sGetInsertionMarkerBottomMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getInsertionMarkerBottom", 0.0f); - sGetInsertionMarkerHorizontalMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getInsertionMarkerHorizontal", 0.0f); - sGetInsertionMarkerTopMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getInsertionMarkerTop", 0.0f); - sGetMatrixMethod = sCursorAnchorInfoClass.getMethod("getMatrix", (Matrix)null); - sGetInsertionMarkerFlagsMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getInsertionMarkerFlags", 0); - } - - @UsedForTesting - public boolean isAvailable() { - return sCursorAnchorInfoClass.exists() && mInstance != null; - } - - private Object mInstance; - - private CursorAnchorInfoCompatWrapper(final Object instance) { - mInstance = instance; + private CursorAnchorInfoCompatWrapper() { + // This class is not publicly instantiable. } - @UsedForTesting - public static CursorAnchorInfoCompatWrapper fromObject(final Object instance) { - if (!sCursorAnchorInfoClass.exists()) { - return new CursorAnchorInfoCompatWrapper(null); + @TargetApi(BuildCompatUtils.VERSION_CODES_LXX) + @Nullable + public static CursorAnchorInfoCompatWrapper wrap(@Nullable final CursorAnchorInfo instance) { + if (Build.VERSION.SDK_INT < BuildCompatUtils.VERSION_CODES_LXX) { + return null; } - return new CursorAnchorInfoCompatWrapper(instance); - } - - private static final class FakeHolder { - static CursorAnchorInfoCompatWrapper sInstance = new CursorAnchorInfoCompatWrapper(null); - } - - @UsedForTesting - public static CursorAnchorInfoCompatWrapper getFake() { - return FakeHolder.sInstance; + if (instance == null) { + return null; + } + return new RealWrapper(instance); } public int getSelectionStart() { - return sGetSelectionStartMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public int getSelectionEnd() { - return sGetSelectionEndMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public CharSequence getComposingText() { - return sGetComposingTextMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public int getComposingTextStart() { - return sGetComposingTextStartMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public Matrix getMatrix() { - return sGetMatrixMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public RectF getCharacterBounds(final int index) { - return sGetCharacterBoundsMethod.invoke(mInstance, index); + throw new UnsupportedOperationException("not supported."); } public int getCharacterBoundsFlags(final int index) { - return sGetCharacterBoundsFlagsMethod.invoke(mInstance, index); + throw new UnsupportedOperationException("not supported."); } public float getInsertionMarkerBaseline() { - return sGetInsertionMarkerBaselineMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public float getInsertionMarkerBottom() { - return sGetInsertionMarkerBottomMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public float getInsertionMarkerHorizontal() { - return sGetInsertionMarkerHorizontalMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public float getInsertionMarkerTop() { - return sGetInsertionMarkerTopMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public int getInsertionMarkerFlags() { - return sGetInsertionMarkerFlagsMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); + } + + @TargetApi(BuildCompatUtils.VERSION_CODES_LXX) + private static final class RealWrapper extends CursorAnchorInfoCompatWrapper { + + @Nonnull + private final CursorAnchorInfo mInstance; + + public RealWrapper(@Nonnull final CursorAnchorInfo info) { + mInstance = info; + } + + @Override + public int getSelectionStart() { + return mInstance.getSelectionStart(); + } + + @Override + public int getSelectionEnd() { + return mInstance.getSelectionEnd(); + } + + @Override + public CharSequence getComposingText() { + return mInstance.getComposingText(); + } + + @Override + public int getComposingTextStart() { + return mInstance.getComposingTextStart(); + } + + @Override + public Matrix getMatrix() { + return mInstance.getMatrix(); + } + + @Override + public RectF getCharacterBounds(final int index) { + return mInstance.getCharacterBounds(index); + } + + @Override + public int getCharacterBoundsFlags(final int index) { + return mInstance.getCharacterBoundsFlags(index); + } + + @Override + public float getInsertionMarkerBaseline() { + return mInstance.getInsertionMarkerBaseline(); + } + + @Override + public float getInsertionMarkerBottom() { + return mInstance.getInsertionMarkerBottom(); + } + + @Override + public float getInsertionMarkerHorizontal() { + return mInstance.getInsertionMarkerHorizontal(); + } + + @Override + public float getInsertionMarkerTop() { + return mInstance.getInsertionMarkerTop(); + } + + @Override + public int getInsertionMarkerFlags() { + return mInstance.getInsertionMarkerFlags(); + } } } diff --git a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java index 0f00be133..16260ab6a 100644 --- a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java @@ -31,9 +31,6 @@ public final class ViewCompatUtils { private static final Method METHOD_setPaddingRelative = CompatUtils.getMethod( View.class, "setPaddingRelative", int.class, int.class, int.class, int.class); - // Note that View.setElevation(float) has been introduced in API level 21. - private static final Method METHOD_setElevation = CompatUtils.getMethod( - View.class, "setElevation", float.class); // Note that View.setTextAlignment(int) has been introduced in API level 17. private static final Method METHOD_setTextAlignment = CompatUtils.getMethod( View.class, "setTextAlignment", int.class); @@ -58,10 +55,6 @@ public final class ViewCompatUtils { CompatUtils.invoke(view, null, METHOD_setPaddingRelative, start, top, end, bottom); } - public static void setElevation(final View view, final float elevation) { - CompatUtils.invoke(view, null, METHOD_setElevation, elevation); - } - // These TEXT_ALIGNMENT_* constants have been introduced in API 17. public static final int TEXT_ALIGNMENT_INHERIT = 0; public static final int TEXT_ALIGNMENT_GRAVITY = 1; diff --git a/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtils.java new file mode 100644 index 000000000..52b8b74e8 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtils.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.compat; + +import android.inputmethodservice.InputMethodService; +import android.view.View; + +public class ViewOutlineProviderCompatUtils { + private ViewOutlineProviderCompatUtils() { + // This utility class is not publicly instantiable. + } + + public interface InsetsUpdater { + public void setInsets(final InputMethodService.Insets insets); + } + + private static final InsetsUpdater EMPTY_INSETS_UPDATER = new InsetsUpdater() { + @Override + public void setInsets(final InputMethodService.Insets insets) {} + }; + + public static InsetsUpdater setInsetsOutlineProvider(final View view) { + if (BuildCompatUtils.EFFECTIVE_SDK_INT < BuildCompatUtils.VERSION_CODES_LXX) { + return EMPTY_INSETS_UPDATER; + } + return ViewOutlineProviderCompatUtilsLXX.setInsetsOutlineProvider(view); + } +} diff --git a/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java b/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java new file mode 100644 index 000000000..5bbb5ce99 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.compat; + +import android.annotation.TargetApi; +import android.graphics.Outline; +import android.inputmethodservice.InputMethodService; +import android.os.Build; +import android.view.View; +import android.view.ViewOutlineProvider; + +import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater; + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +class ViewOutlineProviderCompatUtilsLXX { + private ViewOutlineProviderCompatUtilsLXX() { + // This utility class is not publicly instantiable. + } + + static InsetsUpdater setInsetsOutlineProvider(final View view) { + final InsetsOutlineProvider provider = new InsetsOutlineProvider(view); + view.setOutlineProvider(provider); + return provider; + } + + private static class InsetsOutlineProvider extends ViewOutlineProvider + implements InsetsUpdater { + private final View mView; + private static final int NO_DATA = -1; + private int mLastVisibleTopInsets = NO_DATA; + + public InsetsOutlineProvider(final View view) { + mView = view; + view.setOutlineProvider(this); + } + + @Override + public void setInsets(final InputMethodService.Insets insets) { + final int visibleTopInsets = insets.visibleTopInsets; + if (mLastVisibleTopInsets != visibleTopInsets) { + mLastVisibleTopInsets = visibleTopInsets; + mView.invalidateOutline(); + } + } + + @Override + public void getOutline(final View view, final Outline outline) { + if (mLastVisibleTopInsets == NO_DATA) { + // Call default implementation. + ViewOutlineProvider.BACKGROUND.getOutline(view, outline); + return; + } + // TODO: Revisit this when floating/resize keyboard is supported. + outline.setRect( + view.getLeft(), mLastVisibleTopInsets, view.getRight(), view.getBottom()); + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index 52b9284be..1c66c37d3 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -28,6 +28,7 @@ import android.util.Log; import android.util.SparseArray; import android.util.Xml; import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; @@ -39,6 +40,7 @@ import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodSubtype; import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.define.DebugFlags; +import com.android.inputmethod.latin.utils.DebugLogUtils; import com.android.inputmethod.latin.utils.InputTypeUtils; import com.android.inputmethod.latin.utils.ScriptUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; @@ -313,23 +315,78 @@ public final class KeyboardLayoutSet { return this; } + private final static HashMap<InputMethodSubtype, Integer> sScriptIdsForSubtypes = + new HashMap<>(); + public static int getScriptId(final Resources resources, final InputMethodSubtype subtype) { + final Integer value = sScriptIdsForSubtypes.get(subtype); + if (null == value) { + final int scriptId = readScriptId(resources, subtype); + sScriptIdsForSubtypes.put(subtype, scriptId); + return scriptId; + } + return value; + } + + // Super redux version of reading the script ID for some subtype from Xml. + private static int readScriptId(final Resources resources, + final InputMethodSubtype subtype) { + final String layoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX + + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); + final int xmlId = getXmlId(resources, layoutSetName); + final XmlResourceParser parser = resources.getXml(xmlId); + try { + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + // Bovinate through the XML stupidly searching for TAG_FEATURE, and read + // the script Id from it. + parser.next(); + final String tag = parser.getName(); + if (TAG_FEATURE.equals(tag)) { + return readScriptIdFromTagFeature(resources, parser); + } + } + } catch (final IOException | XmlPullParserException e) { + throw new RuntimeException(e.getMessage() + " in " + layoutSetName, e); + } finally { + parser.close(); + } + // If the tag is not found, then the default script is Latin. + return ScriptUtils.SCRIPT_LATIN; + } + + private static int readScriptIdFromTagFeature(final Resources resources, + final XmlPullParser parser) throws IOException, XmlPullParserException { + final TypedArray featureAttr = resources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.KeyboardLayoutSet_Feature); + try { + final int scriptId = + featureAttr.getInt(R.styleable.KeyboardLayoutSet_Feature_supportedScript, + ScriptUtils.SCRIPT_UNKNOWN); + XmlParseUtils.checkEndTag(TAG_FEATURE, parser); + return scriptId; + } finally { + featureAttr.recycle(); + } + } + public KeyboardLayoutSet build() { if (mParams.mSubtype == null) throw new RuntimeException("KeyboardLayoutSet subtype is not specified"); - final String packageName = mResources.getResourcePackageName( - R.xml.keyboard_layout_set_qwerty); - final String keyboardLayoutSetName = mParams.mKeyboardLayoutSetName; - final int xmlId = mResources.getIdentifier(keyboardLayoutSetName, "xml", packageName); + final int xmlId = getXmlId(mResources, mParams.mKeyboardLayoutSetName); try { parseKeyboardLayoutSet(mResources, xmlId); - } catch (final IOException e) { - throw new RuntimeException(e.getMessage() + " in " + keyboardLayoutSetName, e); - } catch (final XmlPullParserException e) { - throw new RuntimeException(e.getMessage() + " in " + keyboardLayoutSetName, e); + } catch (final IOException | XmlPullParserException e) { + throw new RuntimeException(e.getMessage() + " in " + mParams.mKeyboardLayoutSetName, + e); } return new KeyboardLayoutSet(mContext, mParams); } + private static int getXmlId(final Resources resources, final String keyboardLayoutSetName) { + final String packageName = resources.getResourcePackageName( + R.xml.keyboard_layout_set_qwerty); + return resources.getIdentifier(keyboardLayoutSetName, "xml", packageName); + } + private void parseKeyboardLayoutSet(final Resources res, final int resId) throws XmlPullParserException, IOException { final XmlResourceParser parser = res.getXml(resId); @@ -407,17 +464,8 @@ public final class KeyboardLayoutSet { private void parseKeyboardLayoutSetFeature(final XmlPullParser parser) throws XmlPullParserException, IOException { - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.KeyboardLayoutSet_Feature); - try { - final int scriptId = a.getInt( - R.styleable.KeyboardLayoutSet_Feature_supportedScript, - ScriptUtils.SCRIPT_LATIN); - XmlParseUtils.checkEndTag(TAG_FEATURE, parser); - setScriptId(scriptId); - } finally { - a.recycle(); - } + final int scriptId = readScriptIdFromTagFeature(mResources, parser); + setScriptId(scriptId); } private static int getKeyboardMode(final EditorInfo editorInfo) { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java index 6d8c8b76f..8a9688ac4 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java @@ -22,7 +22,6 @@ import android.os.Build.VERSION_CODES; import android.preference.PreferenceManager; import android.util.Log; -import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.BuildCompatUtils; import com.android.inputmethod.latin.R; @@ -45,7 +44,7 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> { private static KeyboardTheme[] AVAILABLE_KEYBOARD_THEMES; - @UsedForTesting + /* package private for testing */ static final KeyboardTheme[] KEYBOARD_THEMES = { new KeyboardTheme(THEME_ID_ICS, "ICS", R.style.KeyboardTheme_ICS, // This has never been selected because we support ICS or later. @@ -57,6 +56,7 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> { // Default theme for LXX. BuildCompatUtils.VERSION_CODES_LXX), new KeyboardTheme(THEME_ID_LXX_DARK, "LXXDark", R.style.KeyboardTheme_LXX_Dark, + // This has never been selected as default theme. VERSION_CODES.BASE), }; @@ -68,7 +68,7 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> { public final int mThemeId; public final int mStyleId; public final String mThemeName; - private final int mMinApiVersion; + public final int mMinApiVersion; // Note: The themeId should be aligned with "themeId" attribute of Keyboard style // in values/themes-<style>.xml. @@ -98,7 +98,7 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> { return mThemeId; } - @UsedForTesting + /* package private for testing */ static KeyboardTheme searchKeyboardThemeById(final int themeId, final KeyboardTheme[] availableThemeIds) { // TODO: This search algorithm isn't optimal if there are many themes. @@ -110,7 +110,7 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> { return null; } - @UsedForTesting + /* package private for testing */ static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs, final int sdkVersion, final KeyboardTheme[] availableThemeArray) { final String klpThemeIdString = prefs.getString(KLP_KEYBOARD_THEME_KEY, null); @@ -150,7 +150,7 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> { saveKeyboardThemeId(themeId, prefs, BuildCompatUtils.EFFECTIVE_SDK_INT); } - @UsedForTesting + /* package private for testing */ static String getPreferenceKey(final int sdkVersion) { if (sdkVersion <= VERSION_CODES.KITKAT) { return KLP_KEYBOARD_THEME_KEY; @@ -158,7 +158,7 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> { return LXX_KEYBOARD_THEME_KEY; } - @UsedForTesting + /* package private for testing */ static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs, final int sdkVersion) { final String prefKey = getPreferenceKey(sdkVersion); @@ -171,6 +171,7 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> { return getKeyboardTheme(prefs, BuildCompatUtils.EFFECTIVE_SDK_INT, availableThemeArray); } + /* package private for testing */ static KeyboardTheme[] getAvailableThemeArray(final Context context) { if (AVAILABLE_KEYBOARD_THEMES == null) { final int[] availableThemeIdStringArray = context.getResources().getIntArray( @@ -184,11 +185,12 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> { } AVAILABLE_KEYBOARD_THEMES = availableThemeList.toArray( new KeyboardTheme[availableThemeList.size()]); + Arrays.sort(AVAILABLE_KEYBOARD_THEMES); } return AVAILABLE_KEYBOARD_THEMES; } - @UsedForTesting + /* package private for testing */ static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion, final KeyboardTheme[] availableThemeArray) { final String lxxThemeIdString = prefs.getString(LXX_KEYBOARD_THEME_KEY, null); diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index e7be6de4c..06f9ced92 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -28,6 +28,7 @@ import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Typeface; import android.preference.PreferenceManager; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; @@ -57,8 +58,10 @@ import com.android.inputmethod.latin.RichInputMethodSubtype; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.settings.DebugSettings; import com.android.inputmethod.latin.utils.CoordinateUtils; +import com.android.inputmethod.latin.utils.StringUtils; import com.android.inputmethod.latin.utils.TypefaceUtils; +import java.util.Locale; import java.util.WeakHashMap; /** @@ -855,8 +858,13 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack private String layoutLanguageOnSpacebar(final Paint paint, final RichInputMethodSubtype subtype, final int width) { if (mLanguageOnSpacebarFormatType == LanguageOnSpacebarHelper.FORMAT_TYPE_MULTIPLE) { - // TODO: return an appropriate string - return ""; + final Locale[] locales = subtype.getLocales(); + final String[] languages = new String[locales.length]; + for (int i = 0; i < locales.length; ++i) { + languages[i] = StringUtils.toUpperCaseOfStringForLocale( + locales[i].getLanguage(), true /* needsToUpperCase */, Locale.ROOT); + } + return TextUtils.join(" / ", languages); } // Choose appropriate language name to fit into the width. diff --git a/java/src/com/android/inputmethod/keyboard/TextDecorator.java b/java/src/com/android/inputmethod/keyboard/TextDecorator.java index c22717f95..2b7d2ce3c 100644 --- a/java/src/com/android/inputmethod/keyboard/TextDecorator.java +++ b/java/src/com/android/inputmethod/keyboard/TextDecorator.java @@ -30,6 +30,7 @@ import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * A controller class of the add-to-dictionary indicator (a.k.a. TextDecorator). This class @@ -56,6 +57,7 @@ public class TextDecorator { private String mWaitingWord = null; private int mWaitingCursorStart = INVALID_CURSOR_INDEX; private int mWaitingCursorEnd = INVALID_CURSOR_INDEX; + @Nullable private CursorAnchorInfoCompatWrapper mCursorAnchorInfoWrapper = null; @Nonnull @@ -150,7 +152,7 @@ public class TextDecorator { * mode.</p> * @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}. */ - public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) { + public void onUpdateCursorAnchorInfo(@Nullable final CursorAnchorInfoCompatWrapper info) { mCursorAnchorInfoWrapper = info; // Do not use layoutLater() to minimize the latency. layoutImmediately(); @@ -182,7 +184,7 @@ public class TextDecorator { private void layoutMain() { final CursorAnchorInfoCompatWrapper info = mCursorAnchorInfoWrapper; - if (info == null || !info.isAvailable()) { + if (info == null) { cancelLayoutInternalExpectedly("CursorAnchorInfo isn't available."); return; } @@ -301,7 +303,7 @@ public class TextDecorator { */ private static final class LayoutInvalidator { private final HandlerImpl mHandler; - public LayoutInvalidator(final TextDecorator ownerInstance) { + public LayoutInvalidator(@Nonnull final TextDecorator ownerInstance) { mHandler = new HandlerImpl(ownerInstance); } @@ -309,7 +311,7 @@ public class TextDecorator { private static final class HandlerImpl extends LeakGuardHandlerWrapper<TextDecorator> { - public HandlerImpl(final TextDecorator ownerInstance) { + public HandlerImpl(@Nonnull final TextDecorator ownerInstance) { super(ownerInstance); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java index 4f8a105d5..1a55359f5 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java +++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java @@ -23,6 +23,8 @@ import com.android.inputmethod.keyboard.internal.DrawingHandler.Callbacks; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; +import javax.annotation.Nonnull; + // TODO: Separate this class into KeyPreviewHandler and BatchInputPreviewHandler or so. public class DrawingHandler extends LeakGuardHandlerWrapper<Callbacks> { public interface Callbacks { @@ -34,7 +36,7 @@ public class DrawingHandler extends LeakGuardHandlerWrapper<Callbacks> { private static final int MSG_DISMISS_KEY_PREVIEW = 0; private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; - public DrawingHandler(final Callbacks ownerInstance) { + public DrawingHandler(@Nonnull final Callbacks ownerInstance) { super(ownerInstance); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java index ec7b9b024..80b299bf5 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java +++ b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java @@ -27,6 +27,8 @@ import com.android.inputmethod.keyboard.internal.TimerHandler.Callbacks; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; +import javax.annotation.Nonnull; + // TODO: Separate this class into KeyTimerHandler and BatchInputTimerHandler or so. public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> implements TimerProxy { public interface Callbacks { @@ -45,7 +47,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> imple private final int mIgnoreAltCodeKeyTimeout; private final int mGestureRecognitionUpdateTime; - public TimerHandler(final Callbacks ownerInstance, final int ignoreAltCodeKeyTimeout, + public TimerHandler(@Nonnull final Callbacks ownerInstance, final int ignoreAltCodeKeyTimeout, final int gestureRecognitionUpdateTime) { super(ownerInstance); mIgnoreAltCodeKeyTimeout = ignoreAltCodeKeyTimeout; diff --git a/java/src/com/android/inputmethod/latin/BackupAgent.java b/java/src/com/android/inputmethod/latin/BackupAgent.java index 1f044618a..b2d92b30c 100644 --- a/java/src/com/android/inputmethod/latin/BackupAgent.java +++ b/java/src/com/android/inputmethod/latin/BackupAgent.java @@ -17,15 +17,41 @@ package com.android.inputmethod.latin; import android.app.backup.BackupAgentHelper; +import android.app.backup.BackupDataInput; import android.app.backup.SharedPreferencesBackupHelper; +import android.content.SharedPreferences; +import android.os.ParcelFileDescriptor; + +import com.android.inputmethod.latin.settings.LocalSettingsConstants; + +import java.io.IOException; /** - * Backs up the Latin IME shared preferences. + * Backup/restore agent for LatinIME. + * Currently it backs up the default shared preferences. */ public final class BackupAgent extends BackupAgentHelper { + private static final String PREF_SUFFIX = "_preferences"; + @Override public void onCreate() { addHelper("shared_pref", new SharedPreferencesBackupHelper(this, - getPackageName() + "_preferences")); + getPackageName() + PREF_SUFFIX)); + } + + @Override + public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) + throws IOException { + // Let the restore operation go through + super.onRestore(data, appVersionCode, newState); + + // Remove the preferences that we don't want restored. + final SharedPreferences.Editor prefEditor = getSharedPreferences( + getPackageName() + PREF_SUFFIX, MODE_PRIVATE).edit(); + for (final String key : LocalSettingsConstants.PREFS_TO_SKIP_RESTORING) { + prefEditor.remove(key); + } + // Flush the changes to disk. + prefEditor.commit(); } } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 10dea749d..2fece7c85 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -70,7 +70,7 @@ public final class BinaryDictionary extends Dictionary { private static final int FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT = 5; private static final int FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX = 0; private static final int FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX = 1; - private static final int FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX = 2; + private static final int FORMAT_WORD_PROPERTY_HAS_NGRAMS_INDEX = 2; private static final int FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX = 3; private static final int FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX = 4; @@ -179,9 +179,10 @@ public final class BinaryDictionary extends Dictionary { boolean[] isBeginningOfSentenceArray, int[] word); private static native void getWordPropertyNative(long dict, int[] word, boolean isBeginningOfSentence, int[] outCodePoints, boolean[] outFlags, - int[] outProbabilityInfo, ArrayList<int[]> outBigramTargets, - ArrayList<int[]> outBigramProbabilityInfo, ArrayList<int[]> outShortcutTargets, - ArrayList<Integer> outShortcutProbabilities); + int[] outProbabilityInfo, ArrayList<int[][]> outNgramPrevWordsArray, + ArrayList<boolean[]> outNgramPrevWordIsBeginningOfSentenceArray, + ArrayList<int[]> outNgramTargets, ArrayList<int[]> outNgramProbabilityInfo, + ArrayList<int[]> outShortcutTargets, ArrayList<Integer> outShortcutProbabilities); private static native int getNextWordNative(long dict, int token, int[] outCodePoints, boolean[] outIsBeginningOfSentence); private static native void getSuggestionsNative(long dict, long proximityInfo, @@ -201,8 +202,7 @@ public final class BinaryDictionary extends Dictionary { int[] word, int probability, int timestamp); private static native boolean removeNgramEntryNative(long dict, int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray, int[] word); - // TODO: Rename to updateEntriesForWordWithNgramContextNative. - private static native boolean updateCounterNative(long dict, + private static native boolean updateEntriesForWordWithNgramContextNative(long dict, int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray, int[] word, boolean isValidWord, int count, int timestamp); private static native int addMultipleDictionaryEntriesNative(long dict, @@ -292,6 +292,7 @@ public final class BinaryDictionary extends Dictionary { settingsValuesForSuggestion.mSpaceAwareGestureEnabled); session.mNativeSuggestOptions.setAdditionalFeaturesOptions( settingsValuesForSuggestion.mAdditionalFeaturesSettingValues); + session.mNativeSuggestOptions.setWeightForLocale(weightForLocale); if (inOutWeightOfLangModelVsSpatialModel != null) { session.mInputOutputWeightOfLangModelVsSpatialModel[0] = inOutWeightOfLangModelVsSpatialModel[0]; @@ -388,20 +389,25 @@ public final class BinaryDictionary extends Dictionary { final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT]; final int[] outProbabilityInfo = new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT]; - final ArrayList<int[]> outBigramTargets = new ArrayList<>(); - final ArrayList<int[]> outBigramProbabilityInfo = new ArrayList<>(); + final ArrayList<int[][]> outNgramPrevWordsArray = new ArrayList<>(); + final ArrayList<boolean[]> outNgramPrevWordIsBeginningOfSentenceArray = + new ArrayList<>(); + final ArrayList<int[]> outNgramTargets = new ArrayList<>(); + final ArrayList<int[]> outNgramProbabilityInfo = new ArrayList<>(); final ArrayList<int[]> outShortcutTargets = new ArrayList<>(); final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>(); getWordPropertyNative(mNativeDict, codePoints, isBeginningOfSentence, outCodePoints, - outFlags, outProbabilityInfo, outBigramTargets, outBigramProbabilityInfo, - outShortcutTargets, outShortcutProbabilities); + outFlags, outProbabilityInfo, outNgramPrevWordsArray, + outNgramPrevWordIsBeginningOfSentenceArray, outNgramTargets, + outNgramProbabilityInfo, outShortcutTargets, outShortcutProbabilities); return new WordProperty(codePoints, outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX], outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX], - outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX], + outFlags[FORMAT_WORD_PROPERTY_HAS_NGRAMS_INDEX], outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX], outFlags[FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX], outProbabilityInfo, - outBigramTargets, outBigramProbabilityInfo, outShortcutTargets, + outNgramPrevWordsArray, outNgramPrevWordIsBeginningOfSentenceArray, + outNgramTargets, outNgramProbabilityInfo, outShortcutTargets, outShortcutProbabilities); } @@ -506,7 +512,7 @@ public final class BinaryDictionary extends Dictionary { final boolean[] isBeginningOfSentenceArray = new boolean[ngramContext.getPrevWordCount()]; ngramContext.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); final int[] wordCodePoints = StringUtils.toCodePointArray(word); - if (!updateCounterNative(mNativeDict, prevWordCodePointArrays, + if (!updateEntriesForWordWithNgramContextNative(mNativeDict, prevWordCodePointArrays, isBeginningOfSentenceArray, wordCodePoints, isValidWord, count, timestamp)) { return false; } diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 43561ba4b..e66847b56 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; @@ -36,19 +37,19 @@ public abstract class Dictionary { // The following types do not actually come from real dictionary instances, so we create // corresponding instances. public static final String TYPE_USER_TYPED = "user_typed"; - public static final Dictionary DICTIONARY_USER_TYPED = new PhonyDictionary(TYPE_USER_TYPED); + public static final PhonyDictionary DICTIONARY_USER_TYPED = new PhonyDictionary(TYPE_USER_TYPED); public static final String TYPE_APPLICATION_DEFINED = "application_defined"; - public static final Dictionary DICTIONARY_APPLICATION_DEFINED = + public static final PhonyDictionary DICTIONARY_APPLICATION_DEFINED = new PhonyDictionary(TYPE_APPLICATION_DEFINED); public static final String TYPE_HARDCODED = "hardcoded"; // punctuation signs and such - public static final Dictionary DICTIONARY_HARDCODED = + public static final PhonyDictionary DICTIONARY_HARDCODED = new PhonyDictionary(TYPE_HARDCODED); // Spawned by resuming suggestions. Comes from a span that was in the TextView. public static final String TYPE_RESUMED = "resumed"; - public static final Dictionary DICTIONARY_RESUMED = + public static final PhonyDictionary DICTIONARY_RESUMED = new PhonyDictionary(TYPE_RESUMED); // The following types of dictionary have actual functional instances. We don't need final @@ -182,9 +183,10 @@ public abstract class Dictionary { * Not a true dictionary. A placeholder used to indicate suggestions that don't come from any * real dictionary. */ - private static class PhonyDictionary extends Dictionary { - // This class is not publicly instantiable. - private PhonyDictionary(final String type) { + @UsedForTesting + static class PhonyDictionary extends Dictionary { + @UsedForTesting + PhonyDictionary(final String type) { super(type, null); } diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java index 6a63bfda7..08035dfd6 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -257,6 +257,12 @@ public class DictionaryFacilitator { } public void switchMostProbableLanguage(final Locale locale) { + if (null == locale) { + // In many cases, there is no locale to a committed word. For example, a typed word + // that does not auto-correct has no locale. In this case we simply do not change + // the most probable language. + return; + } final DictionaryGroup newMostProbableDictionaryGroup = findDictionaryGroupWithLocale(mDictionaryGroups, locale); mMostProbableDictionaryGroup.mWeightForTypingInLocale = diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index d24f80a45..d58373ff6 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -64,9 +64,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100; - private static final int DEFAULT_MAX_UNIGRAM_COUNT = 10000; - private static final int DEFAULT_MAX_BIGRAM_COUNT = 10000; - /** * The maximum length of a word in this dictionary. */ @@ -225,10 +222,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { attributeMap.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, mLocale.toString()); attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY, String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))); - attributeMap.put(DictionaryHeader.MAX_UNIGRAM_COUNT_KEY, - String.valueOf(DEFAULT_MAX_UNIGRAM_COUNT)); - attributeMap.put(DictionaryHeader.MAX_BIGRAM_COUNT_KEY, - String.valueOf(DEFAULT_MAX_BIGRAM_COUNT)); return attributeMap; } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index ec45462c4..91a4ec84c 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -60,6 +60,8 @@ import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper; import com.android.inputmethod.compat.InputMethodServiceCompatUtils; +import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils; +import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater; import com.android.inputmethod.dictionarypack.DictionaryPackConstants; import com.android.inputmethod.event.Event; import com.android.inputmethod.event.HardwareEventDecoder; @@ -85,7 +87,6 @@ import com.android.inputmethod.latin.settings.SettingsActivity; import com.android.inputmethod.latin.settings.SettingsValues; import com.android.inputmethod.latin.suggestions.SuggestionStripView; import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor; -import com.android.inputmethod.latin.sync.BeanstalkManager; import com.android.inputmethod.latin.touchinputconsumer.GestureConsumer; import com.android.inputmethod.latin.utils.ApplicationUtils; import com.android.inputmethod.latin.utils.CapsModeUtils; @@ -109,6 +110,8 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + /** * Input method implementation for Qwerty'ish keyboard. */ @@ -154,6 +157,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO: Move these {@link View}s to {@link KeyboardSwitcher}. private View mInputView; + private InsetsUpdater mInsetsUpdater; private SuggestionStripView mSuggestionStripView; private TextView mExtractEditText; @@ -206,7 +210,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private int mDelayInMillisecondsToUpdateSuggestions; private int mDelayInMillisecondsToUpdateShiftState; - public UIHandler(final LatinIME ownerInstance) { + public UIHandler(@Nonnull final LatinIME ownerInstance) { super(ownerInstance); } @@ -558,7 +562,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen AudioAndHapticFeedbackManager.init(this); AccessibilityUtils.init(this); mStatsUtilsManager.onCreate(this /* context */); - BeanstalkManager.getInstance(this /* context */).onCreate(); super.onCreate(); mHandler.onCreate(); @@ -567,6 +570,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO: Resolve mutual dependencies of {@link #loadSettings()} and // {@link #resetDictionaryFacilitatorIfNecessary()}. loadSettings(); + mSubtypeSwitcher.onSubtypeChanged(mRichImm.getCurrentRawSubtype()); resetDictionaryFacilitatorIfNecessary(); // Register to receive ringer mode change and network state change. @@ -706,7 +710,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen unregisterReceiver(mDictionaryPackInstallReceiver); unregisterReceiver(mDictionaryDumpBroadcastReceiver); mStatsUtilsManager.onDestroy(); - BeanstalkManager.getInstance(this /* context */).onDestroy(); super.onDestroy(); } @@ -754,6 +757,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void setInputView(final View view) { super.setInputView(view); mInputView = view; + mInsetsUpdater = ViewOutlineProviderCompatUtils.setInsetsOutlineProvider(view); updateSoftInputWindowLayoutParameters(); mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view); if (hasSuggestionStripView()) { @@ -791,23 +795,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { - onExtractTextViewPreDraw(); + // CursorAnchorInfo is used on L and later. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (isFullscreenMode() && mExtractEditText != null) { + mInputLogic.onUpdateCursorAnchorInfo( + CursorAnchorInfoUtils.extractFromTextView(mExtractEditText)); + } + } return true; } }; - private void onExtractTextViewPreDraw() { - // CursorAnchorInfo is available on L and later. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.L) { - return; - } - if (!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. @@ -842,8 +840,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) { // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged() // is not guaranteed. It may even be called at the same time on a different thread. - final RichInputMethodSubtype richSubtype = new RichInputMethodSubtype(subtype); - mSubtypeSwitcher.onSubtypeChanged(richSubtype); + mSubtypeSwitcher.onSubtypeChanged(subtype); mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype), mSettings.getCurrent()); loadKeyboard(); @@ -860,6 +857,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // also wouldn't be consuming gesture data. mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; mRichImm.clearSubtypeCaches(); + mSubtypeSwitcher.refreshSubtypeInfo(); final KeyboardSwitcher switcher = mKeyboardSwitcher; switcher.updateKeyboardTheme(); final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView(); @@ -1078,7 +1076,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // with cursor movement when we have a hardware keyboard since we are not in charge. final SettingsValues settingsValues = mSettings.getCurrent(); if ((!settingsValues.mHasHardwareKeyboard || ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) - && mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd)) { + && mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, + settingsValues)) { mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), getCurrentRecapitalizeState()); } @@ -1090,7 +1089,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (isFullscreenMode()) { return; } - mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info)); + mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.wrap(info)); } /** @@ -1191,6 +1190,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // no visual element will be shown on the screen. outInsets.touchableInsets = inputHeight; outInsets.visibleTopInsets = inputHeight; + mInsetsUpdater.setInsets(outInsets); return; } final int suggestionsHeight = (!mKeyboardSwitcher.isShowingEmojiPalettes() @@ -1211,6 +1211,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } outInsets.contentTopInsets = visibleTopY; outInsets.visibleTopInsets = visibleTopY; + mInsetsUpdater.setInsets(outInsets); } public void startShowingInputView(final boolean needsToLoadKeyboard) { @@ -1627,7 +1628,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } @Override - public void showAddToDictionaryHint(final String word) { + public void suggestAddingToDictionary(final String word, final boolean isFromSuggestionStrip) { if (!hasSuggestionStripView()) { return; } @@ -1637,7 +1638,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else { wordToShow = word; } - mSuggestionStripView.showAddToDictionaryHint(wordToShow); + mSuggestionStripView.showAddToDictionaryHint(wordToShow, + isFromSuggestionStrip /* shouldShowWordToSave */); } // This will show either an empty suggestion strip (if prediction is enabled) or diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 62a258b20..a3f7bb4d6 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -860,9 +860,10 @@ public final class RichInputConnection implements PrivateCommandPerformer { * than it really is. */ public void tryFixLyingCursorPosition() { + mIC = mParent.getCurrentInputConnection(); final CharSequence textBeforeCursor = getTextBeforeCursor( Constants.EDITOR_CONTENTS_CACHE_SIZE, 0); - final CharSequence selectedText = mIC.getSelectedText(0 /* flags */); + final CharSequence selectedText = null == mIC ? null : mIC.getSelectedText(0 /* flags */); if (null == textBeforeCursor || (!TextUtils.isEmpty(selectedText) && mExpectedSelEnd == mExpectedSelStart)) { // If textBeforeCursor is null, we have no idea what kind of text field we have or if diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java index 0d5ce7d6d..e6df35bea 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -20,6 +20,7 @@ import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE; import android.content.Context; import android.content.SharedPreferences; +import android.content.res.Resources; import android.os.Build; import android.os.IBinder; import android.preference.PreferenceManager; @@ -29,6 +30,7 @@ import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.latin.settings.AdditionalFeaturesSettingUtils; import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; @@ -50,6 +52,7 @@ public class RichInputMethodManager { private static final RichInputMethodManager sInstance = new RichInputMethodManager(); + private Context mContext; private InputMethodManagerCompatWrapper mImmWrapper; private InputMethodInfoCache mInputMethodInfoCache; final HashMap<InputMethodInfo, List<InputMethodSubtype>> @@ -83,6 +86,7 @@ public class RichInputMethodManager { return; } mImmWrapper = new InputMethodManagerCompatWrapper(context); + mContext = context; mInputMethodInfoCache = new InputMethodInfoCache( mImmWrapper.mImm, context.getPackageName()); @@ -298,14 +302,14 @@ public class RichInputMethodManager { return INDEX_NOT_FOUND; } - public RichInputMethodSubtype getCurrentInputMethodSubtype( - final RichInputMethodSubtype defaultSubtype) { - final InputMethodSubtype currentSubtype = mImmWrapper.mImm.getCurrentInputMethodSubtype(); - if (currentSubtype == null) { - return defaultSubtype; - } - // TODO: Determine locales to use for multi-lingual use. - return new RichInputMethodSubtype(currentSubtype); + public InputMethodSubtype getCurrentRawSubtype() { + return mImmWrapper.mImm.getCurrentInputMethodSubtype(); + } + + public RichInputMethodSubtype createCurrentRichInputMethodSubtype( + final InputMethodSubtype rawSubtype) { + return AdditionalFeaturesSettingUtils.createRichInputMethodSubtype(this, rawSubtype, + mContext); } public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) { diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index c339e96fb..6fc549549 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -36,7 +36,6 @@ import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper; import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.utils.LocaleUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import java.util.HashSet; @@ -58,6 +57,7 @@ public final class SubtypeSwitcher { new LanguageOnSpacebarHelper(); private InputMethodInfo mShortcutInputMethodInfo; private InputMethodSubtype mShortcutSubtype; + private RichInputMethodSubtype mCurrentRichInputMethodSubtype; private RichInputMethodSubtype mNoLanguageSubtype; private RichInputMethodSubtype mEmojiSubtype; private boolean mIsNetworkConnected; @@ -117,10 +117,14 @@ public final class SubtypeSwitcher { final NetworkInfo info = connectivityManager.getActiveNetworkInfo(); mIsNetworkConnected = (info != null && info.isConnected()); - onSubtypeChanged(getCurrentSubtype()); + refreshSubtypeInfo(); updateParametersOnStartInputView(); } + public void refreshSubtypeInfo() { + onSubtypeChanged(mRichImm.getCurrentRawSubtype()); + } + /** * Update parameters which are changed outside LatinIME. This parameters affect UI so that they * should be updated every time onStartInputView is called. @@ -165,12 +169,14 @@ public final class SubtypeSwitcher { } // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. - public void onSubtypeChanged(final RichInputMethodSubtype newSubtype) { + public void onSubtypeChanged(final InputMethodSubtype newSubtype) { + final RichInputMethodSubtype richSubtype = + mRichImm.createCurrentRichInputMethodSubtype(newSubtype); if (DBG) { - Log.w(TAG, "onSubtypeChanged: " + newSubtype.getNameForLogging()); + Log.w(TAG, "onSubtypeChanged: " + richSubtype.getNameForLogging()); } - - final Locale[] newLocales = newSubtype.getLocales(); + mCurrentRichInputMethodSubtype = richSubtype; + final Locale[] newLocales = richSubtype.getLocales(); if (newLocales.length > 1) { // In multi-locales mode, the system language is never the same as the input language // because there is no single input language. @@ -181,7 +187,7 @@ public final class SubtypeSwitcher { final boolean sameLocale = systemLocale.equals(newLocale); final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage()); final boolean implicitlyEnabled = mRichImm - .checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype.getRawSubtype()); + .checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype); mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage( sameLocale || (sameLanguage && implicitlyEnabled)); } @@ -301,7 +307,7 @@ public final class SubtypeSwitcher { if (null != sForcedSubtypeForTesting) { return sForcedSubtypeForTesting; } - return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype()); + return mCurrentRichInputMethodSubtype; } public RichInputMethodSubtype getNoLanguageSubtype() { diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index e181237a6..e6c138407 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -28,6 +28,7 @@ import com.android.inputmethod.latin.utils.StringUtils; import com.android.inputmethod.latin.utils.SuggestionResults; import java.util.ArrayList; +import java.util.HashMap; import java.util.Locale; /** @@ -49,6 +50,16 @@ public final class Suggest { private static final boolean DBG = DebugFlags.DEBUG_ENABLED; private final DictionaryFacilitator mDictionaryFacilitator; + private static final int MAXIMUM_AUTO_CORRECT_LENGTH_FOR_GERMAN = 12; + private static final HashMap<String, Integer> sLanguageToMaximumAutoCorrectionWithSpaceLength = + new HashMap<>(); + static { + // TODO: should we add Finnish here? + // TODO: This should not be hardcoded here but be written in the dictionary header + sLanguageToMaximumAutoCorrectionWithSpaceLength.put(Locale.GERMAN.getLanguage(), + MAXIMUM_AUTO_CORRECT_LENGTH_FOR_GERMAN); + } + private float mAutoCorrectionThreshold; public Suggest(final DictionaryFacilitator dictionaryFacilitator) { @@ -168,8 +179,18 @@ public final class Suggest { // TODO: we may want to have shortcut-only entries auto-correct in the future. hasAutoCorrection = false; } else { - hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold( - suggestionResults.first(), consideredWord, mAutoCorrectionThreshold); + final SuggestedWordInfo firstSuggestion = suggestionResults.first(); + if (!AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold( + firstSuggestion, consideredWord, mAutoCorrectionThreshold)) { + // Score is too low for autocorrect + hasAutoCorrection = false; + } else { + // We have a high score, so we need to check if this suggestion is in the correct + // form to allow auto-correcting to it in this language. For details of how this + // is determined, see #isAllowedByAutoCorrectionWithSpaceFilter. + // TODO: this should not have its own logic here but be handled by the dictionary. + hasAutoCorrection = isAllowedByAutoCorrectionWithSpaceFilter(firstSuggestion); + } } if (!TextUtils.isEmpty(typedWord)) { @@ -287,6 +308,41 @@ public final class Suggest { return suggestionsList; } + /** + * Computes whether this suggestion should be blocked or not in this language + * + * This function implements a filter that avoids auto-correcting to suggestions that contain + * spaces that are above a certain language-dependent character limit. In languages like German + * where it's possible to concatenate many words, it often happens our dictionary does not + * have the longer words. In this case, we offer a lot of unhelpful suggestions that contain + * one or several spaces. Ideally we should understand what the user wants and display useful + * suggestions by improving the dictionary and possibly having some specific logic. Until + * that's possible we should avoid displaying unhelpful suggestions. But it's hard to tell + * whether a suggestion is useful or not. So at least for the time being we block + * auto-correction when the suggestion is long and contains a space, which should avoid the + * worst damage. + * This function is implementing that filter. If the language enforces no such limit, then it + * always returns true. If the suggestion contains no space, it also returns true. Otherwise, + * it checks the length against the language-specific limit. + * + * @param info the suggestion info + * @return whether it's fine to auto-correct to this. + */ + private boolean isAllowedByAutoCorrectionWithSpaceFilter(final SuggestedWordInfo info) { + final Locale locale = info.mSourceDict.mLocale; + if (null == locale) { + return true; + } + final Integer maximumLengthForThisLanguage = + sLanguageToMaximumAutoCorrectionWithSpaceLength.get(locale.getLanguage()); + if (null == maximumLengthForThisLanguage) { + // This language does not enforce a maximum length to auto-correction + return true; + } + return info.mWord.length() <= maximumLengthForThisLanguage + || -1 == info.mWord.indexOf(Constants.CODE_SPACE); + } + /* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo( final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase, final boolean isOnlyFirstCharCapitalized, final int trailingSingleQuotesCount) { diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 466576465..e5bf25d5f 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -243,7 +243,8 @@ public class SuggestedWords { return candidate.isEligibleForAutoCommit() ? candidate : null; } - public static final class SuggestedWordInfo { + // non-final for testability. + public static class SuggestedWordInfo { public static final int NOT_AN_INDEX = -1; public static final int NOT_A_CONFIDENCE = -1; public static final int MAX_SCORE = Integer.MAX_VALUE; @@ -413,28 +414,6 @@ public class SuggestedWords { return isPrediction(mInputStyle); } - // SuggestedWords is an immutable object, as much as possible. We must not just remove - // words from the member ArrayList as some other parties may expect the object to never change. - // This is only ever called by recorrection at the moment, hence the ForRecorrection moniker. - public SuggestedWords getSuggestedWordsExcludingTypedWordForRecorrection() { - final ArrayList<SuggestedWordInfo> newSuggestions = new ArrayList<>(); - String typedWord = null; - for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) { - final SuggestedWordInfo info = mSuggestedWordInfoList.get(i); - if (!info.isKindOf(SuggestedWordInfo.KIND_TYPED)) { - newSuggestions.add(info); - } else { - assert(null == typedWord); - typedWord = info.mWord; - } - } - // We should never autocorrect, so we say the typed word is valid. Also, in this case, - // no auto-correction should take place hence willAutoCorrect = false. - return new SuggestedWords(newSuggestions, null /* rawSuggestions */, typedWord, - true /* typedWordValid */, false /* willAutoCorrect */, mIsObsoleteSuggestions, - SuggestedWords.INPUT_STYLE_RECORRECTION, NOT_A_SEQUENCE_NUMBER); - } - // Creates a new SuggestedWordInfo from the currently suggested words that removes all but the // last word of all suggestions, separated by a space. This is necessary because when we commit // a multiple-word suggestion, the IME only retains the last word as the composing word, and diff --git a/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java b/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java index 9445ce4c3..00bcecf52 100644 --- a/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java +++ b/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java @@ -26,7 +26,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.settings.Settings; +import com.android.inputmethod.latin.settings.LocalSettingsConstants; /** * {@link BroadcastReceiver} for {@link AccountManager#LOGIN_ACCOUNTS_CHANGED_ACTION}. @@ -41,25 +41,14 @@ public class AccountsChangedReceiver extends BroadcastReceiver { return; } + // Ideally the account preference could live in a different preferences file + // that wasn't being backed up and restored, however the preference fragments + // currently only deal with the default shared preferences which is why + // separating this out into a different file is not trivial currently. final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - final String currentAccount = prefs.getString(Settings.PREF_ACCOUNT_NAME, null); - if (currentAccount != null) { - final String[] accounts = getAccountsForLogin(context); - boolean accountFound = false; - for (String account : accounts) { - if (TextUtils.equals(currentAccount, account)) { - accountFound = true; - break; - } - } - // The current account was not found in the list of accounts, remove it. - if (!accountFound) { - Log.i(TAG, "The current account was removed from the system: " + currentAccount); - prefs.edit() - .remove(Settings.PREF_ACCOUNT_NAME) - .apply(); - } - } + final String currentAccount = prefs.getString( + LocalSettingsConstants.PREF_ACCOUNT_NAME, null); + removeUnknownAccountFromPreference(prefs, getAccountsForLogin(context), currentAccount); } /** @@ -69,4 +58,24 @@ public class AccountsChangedReceiver extends BroadcastReceiver { protected String[] getAccountsForLogin(Context context) { return LoginAccountUtils.getAccountsForLogin(context); } + + /** + * Removes the currentAccount from preferences if it's not found + * in the list of current accounts. + */ + private static void removeUnknownAccountFromPreference(final SharedPreferences prefs, + final String[] accounts, final String currentAccount) { + if (currentAccount == null) { + return; + } + for (final String account : accounts) { + if (TextUtils.equals(currentAccount, account)) { + return; + } + } + Log.i(TAG, "The current account was removed from the system: " + currentAccount); + prefs.edit() + .remove(LocalSettingsConstants.PREF_ACCOUNT_NAME) + .apply(); + } } diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 07f2ed3db..5cc61db79 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -305,6 +305,7 @@ public final class InputLogic { currentKeyboardScriptId, handler); } + mDictionaryFacilitator.switchMostProbableLanguage(suggestionInfo.mSourceDict.mLocale); final Event event = Event.createSuggestionPickedEvent(suggestionInfo); final InputTransaction inputTransaction = new InputTransaction(settingsValues, event, SystemClock.uptimeMillis(), mSpaceState, keyboardShiftState); @@ -348,7 +349,8 @@ public final class InputLogic { inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); if (shouldShowAddToDictionaryHint) { - mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion); + mSuggestionStripViewAccessor.suggestAddingToDictionary(suggestion, + true /* isFromSuggestionStrip */); } else { // If we're not showing the "Touch again to save", then update the suggestion strip. // That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE. @@ -369,10 +371,11 @@ public final class InputLogic { * @param oldSelEnd old selection end * @param newSelStart new selection start * @param newSelEnd new selection end + * @param settingsValues the current values of the settings. * @return whether the cursor has moved as a result of user interaction. */ public boolean onUpdateSelection(final int oldSelStart, final int oldSelEnd, - final int newSelStart, final int newSelEnd) { + final int newSelStart, final int newSelEnd, final SettingsValues settingsValues) { if (mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart, oldSelEnd, newSelEnd)) { return false; } @@ -397,8 +400,9 @@ public final class InputLogic { // should be true, but that is if the framework had taken that wrong cursor position // into account, which means we have to reset the entire composing state whenever there // is or was a selection regardless of whether it changed or not. - if (hasOrHadSelection || (selectionChangedOrSafeToReset - && !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) { + if (hasOrHadSelection || !settingsValues.needsToLookupSuggestions() + || (selectionChangedOrSafeToReset + && !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) { // If we are composing a word and moving the cursor, we would want to set a // suggestion span for recorrection to work correctly. Unfortunately, that // would involve the keyboard committing some new text, which would move the @@ -1484,6 +1488,11 @@ public final class InputLogic { if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return; final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>(); final String typedWord = range.mWord.toString(); + suggestions.add(new SuggestedWordInfo(typedWord, + SuggestedWords.MAX_SUGGESTIONS + 1, + SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED, + SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, + SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */)); if (!isResumableWord(settingsValues, typedWord)) { mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); return; @@ -1516,30 +1525,14 @@ public final class InputLogic { mConnection.maybeMoveTheCursorAroundAndRestoreToWorkaroundABug(); mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor, expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor()); - if (suggestions.size() <= 0) { + if (suggestions.size() <= 1) { // If there weren't any suggestion spans on this word, suggestions#size() will be 1 // if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we // have no useful suggestions, so we will try to compute some for it instead. mInputLogicHandler.getSuggestedWords(Suggest.SESSION_ID_TYPING, SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() { @Override - public void onGetSuggestedWords( - final SuggestedWords suggestedWordsIncludingTypedWord) { - final SuggestedWords suggestedWords; - if (suggestedWordsIncludingTypedWord.size() > 1) { - // We were able to compute new suggestions for this word. - // Remove the typed word, since we don't want to display it in this - // case. The #getSuggestedWordsExcludingTypedWordForRecorrection() - // method sets willAutoCorrect to false. - suggestedWords = suggestedWordsIncludingTypedWord - .getSuggestedWordsExcludingTypedWordForRecorrection(); - } else { - // No saved suggestions, and we were unable to compute any good one - // either. Rather than displaying an empty suggestion strip, we'll - // display the original word alone in the middle. - // Since there is only one word, willAutoCorrect is false. - suggestedWords = suggestedWordsIncludingTypedWord; - } + public void onGetSuggestedWords(final SuggestedWords suggestedWords) { mIsAutoCorrectionIndicatorOn = false; mLatinIME.mHandler.showSuggestionStrip(suggestedWords); }}); @@ -1683,7 +1676,8 @@ public final class InputLogic { mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd()); } - mSuggestionStripViewAccessor.showAddToDictionaryHint(originallyTypedWordString); + mSuggestionStripViewAccessor.suggestAddingToDictionary(originallyTypedWordString, + false /* isFromSuggestionStrip */); } else { // We have a separator between the word and the cursor: we should show predictions. inputTransaction.setRequiresUpdateSuggestions(); @@ -2100,6 +2094,10 @@ public final class InputLogic { final boolean isBatchMode = mWordComposer.isBatchMode(); commitChosenWord(settingsValues, stringToCommit, LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator); + if (null != autoCorrectionOrNull) { + mDictionaryFacilitator.switchMostProbableLanguage( + autoCorrectionOrNull.mSourceDict.mLocale); + } if (!typedWord.equals(stringToCommit)) { // This will make the correction flash for a short while as a visual clue // to the user that auto-correction happened. It has no other effect; in particular diff --git a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java index fa7c2c417..644818ba6 100644 --- a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java +++ b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java @@ -40,8 +40,9 @@ public final class DictionaryHeader { public static final String USES_FORGETTING_CURVE_KEY = "USES_FORGETTING_CURVE"; public static final String FORGETTING_CURVE_PROBABILITY_VALUES_TABLE_ID_KEY = "FORGETTING_CURVE_PROBABILITY_VALUES_TABLE_ID"; - public static final String MAX_UNIGRAM_COUNT_KEY = "MAX_UNIGRAM_COUNT"; - public static final String MAX_BIGRAM_COUNT_KEY = "MAX_BIGRAM_COUNT"; + public static final String MAX_UNIGRAM_COUNT_KEY = "MAX_UNIGRAM_ENTRY_COUNT"; + public static final String MAX_BIGRAM_COUNT_KEY = "MAX_BIGRAM_ENTRY_COUNT"; + public static final String MAX_TRIGRAM_COUNT_KEY = "MAX_TRIGRAM_ENTRY_COUNT"; public static final String ATTRIBUTE_VALUE_TRUE = "1"; public static final String CODE_POINT_TABLE_KEY = "codePointTable"; diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java index a180d060e..1e6cadf03 100644 --- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java +++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java @@ -26,6 +26,8 @@ import com.android.inputmethod.latin.utils.StringUtils; import java.util.ArrayList; import java.util.Arrays; +import javax.annotation.Nullable; + /** * Utility class for a word with a probability. * @@ -49,7 +51,7 @@ public final class WordProperty implements Comparable<WordProperty> { @UsedForTesting public WordProperty(final String word, final ProbabilityInfo probabilityInfo, final ArrayList<WeightedString> shortcutTargets, - final ArrayList<WeightedString> bigrams, + @Nullable final ArrayList<WeightedString> bigrams, final boolean isNotAWord, final boolean isBlacklistEntry) { mWord = word; mProbabilityInfo = probabilityInfo; @@ -85,7 +87,9 @@ public final class WordProperty implements Comparable<WordProperty> { public WordProperty(final int[] codePoints, final boolean isNotAWord, final boolean isBlacklisted, final boolean hasBigram, final boolean hasShortcuts, final boolean isBeginningOfSentence, final int[] probabilityInfo, - final ArrayList<int[]> bigramTargets, final ArrayList<int[]> bigramProbabilityInfo, + final ArrayList<int[][]> ngramPrevWordsArray, + final ArrayList<boolean[]> outNgramPrevWordIsBeginningOfSentenceArray, + final ArrayList<int[]> ngramTargets, final ArrayList<int[]> ngramProbabilityInfo, final ArrayList<int[]> shortcutTargets, final ArrayList<Integer> shortcutProbabilities) { mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints); @@ -98,15 +102,15 @@ public final class WordProperty implements Comparable<WordProperty> { mHasShortcuts = hasShortcuts; mHasNgrams = hasBigram; - final int relatedNgramCount = bigramTargets.size(); + final int relatedNgramCount = ngramTargets.size(); final WordInfo currentWordInfo = mIsBeginningOfSentence ? WordInfo.BEGINNING_OF_SENTENCE : new WordInfo(mWord); final NgramContext ngramContext = new NgramContext(currentWordInfo); for (int i = 0; i < relatedNgramCount; i++) { final String ngramTargetString = - StringUtils.getStringFromNullTerminatedCodePointArray(bigramTargets.get(i)); + StringUtils.getStringFromNullTerminatedCodePointArray(ngramTargets.get(i)); final WeightedString ngramTarget = new WeightedString(ngramTargetString, - createProbabilityInfoFromArray(bigramProbabilityInfo.get(i))); + createProbabilityInfoFromArray(ngramProbabilityInfo.get(i))); // TODO: Support n-gram. ngrams.add(new NgramProperty(ngramTarget, ngramContext)); } @@ -180,7 +184,8 @@ public final class WordProperty implements Comparable<WordProperty> { && mHasNgrams == w.mHasNgrams && mHasShortcuts && w.mHasNgrams; } - private <T> boolean equals(final ArrayList<T> a, final ArrayList<T> b) { + // TDOO: Have a utility method like java.util.Objects.equals. + private static <T> boolean equals(final ArrayList<T> a, final ArrayList<T> b) { if (null == a) { return null == b; } diff --git a/java/src/com/android/inputmethod/latin/network/AuthException.java b/java/src/com/android/inputmethod/latin/network/AuthException.java new file mode 100644 index 000000000..1bce4c156 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/network/AuthException.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.network; + +/** + * Authentication exception. When this exception is thrown, the client may + * try to refresh the authentication token and try again. + */ +public class AuthException extends Exception { + public AuthException() { + super(); + } + + public AuthException(Throwable throwable) { + super(throwable); + } + + public AuthException(String detailMessage) { + super(detailMessage); + } +}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java b/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java index 0d0cbe169..e2d24fd0a 100644 --- a/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java +++ b/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java @@ -16,7 +16,7 @@ package com.android.inputmethod.latin.network; -import com.android.inputmethod.annotations.UsedForTesting; +import android.util.Log; import java.io.BufferedOutputStream; import java.io.IOException; @@ -30,25 +30,17 @@ import javax.annotation.Nullable; /** * A client for executing HTTP requests synchronously. * This must never be called from the main thread. - * - * TODO: Remove @UsedForTesting after this is actually used. */ -@UsedForTesting public class BlockingHttpClient { + private static final boolean DEBUG = false; + private static final String TAG = BlockingHttpClient.class.getSimpleName(); + private final HttpURLConnection mConnection; /** * Interface that handles processing the response for a request. */ - public interface ResponseProcessor { - /** - * Called when the HTTP request fails with an error. - * - * @param httpStatusCode The status code of the HTTP response. - * @param message The HTTP response message, if any, or null. - */ - void onError(int httpStatusCode, @Nullable String message); - + public interface ResponseProcessor<T> { /** * Called when the HTTP request finishes successfully. * The {@link InputStream} is closed by the client after the method finishes, @@ -56,13 +48,9 @@ public class BlockingHttpClient { * * @param response An input stream that can be used to read the HTTP response. */ - void onSuccess(InputStream response); + T onSuccess(InputStream response) throws IOException; } - /** - * TODO: Remove @UsedForTesting after this is actually used. - */ - @UsedForTesting public BlockingHttpClient(HttpURLConnection connection) { mConnection = connection; } @@ -70,16 +58,19 @@ public class BlockingHttpClient { /** * Executes the request on the underlying {@link HttpURLConnection}. * - * TODO: Remove @UsedForTesting after this is actually used. - * * @param request The request payload, if any, or null. - * @param responeProcessor A processor for the HTTP response. + * @param responseProcessor A processor for the HTTP response. */ - @UsedForTesting - public void execute(@Nullable byte[] request, @Nonnull ResponseProcessor responseProcessor) - throws IOException { + public <T> T execute(@Nullable byte[] request, @Nonnull ResponseProcessor<T> responseProcessor) + throws IOException, AuthException, HttpException { + if (DEBUG) { + Log.d(TAG, "execute: " + mConnection.getURL()); + } try { if (request != null) { + if (DEBUG) { + Log.d(TAG, "request size: " + request.length); + } OutputStream out = new BufferedOutputStream(mConnection.getOutputStream()); out.write(request); out.flush(); @@ -88,9 +79,17 @@ public class BlockingHttpClient { final int responseCode = mConnection.getResponseCode(); if (responseCode != HttpURLConnection.HTTP_OK) { - responseProcessor.onError(responseCode, mConnection.getResponseMessage()); + Log.w(TAG, "Response error: " + responseCode + ", Message: " + + mConnection.getResponseMessage()); + if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { + throw new AuthException(mConnection.getResponseMessage()); + } + throw new HttpException(responseCode); } else { - responseProcessor.onSuccess(mConnection.getInputStream()); + if (DEBUG) { + Log.d(TAG, "request executed successfully"); + } + return responseProcessor.onSuccess(mConnection.getInputStream()); } } finally { mConnection.disconnect(); diff --git a/java/src/com/android/inputmethod/latin/network/HttpException.java b/java/src/com/android/inputmethod/latin/network/HttpException.java new file mode 100644 index 000000000..b9d8b6372 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/network/HttpException.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.network; + +import com.android.inputmethod.annotations.UsedForTesting; + +/** + * The HttpException exception represents a XML/HTTP fault with a HTTP status code. + */ +public class HttpException extends Exception { + + /** + * The HTTP status code. + */ + private final int mStatusCode; + + /** + * @param statusCode int HTTP status code. + */ + public HttpException(int statusCode) { + super("Response Code: " + statusCode); + mStatusCode = statusCode; + } + + /** + * @return the HTTP status code related to this exception. + */ + @UsedForTesting + public int getHttpStatusCode() { + return mStatusCode; + } +}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java b/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java index 35b65be56..502f72f17 100644 --- a/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java +++ b/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java @@ -37,6 +37,11 @@ public class HttpUrlConnectionBuilder { private static final int DEFAULT_TIMEOUT_MILLIS = 5 * 1000; /** + * Request header key for authentication. + */ + public static final String HTTP_HEADER_AUTHORIZATION = "Authorization"; + + /** * Request header key for cache control. */ public static final String KEY_CACHE_CONTROL = "Cache-Control"; @@ -78,7 +83,7 @@ public class HttpUrlConnectionBuilder { * Sets the URL that'll be used for the request. * This *must* be set before calling {@link #build()} * - * TODO: Remove @UsedForTesting after this is actually used. + * TODO: Remove @UsedForTesting after this method is actually used. */ @UsedForTesting public HttpUrlConnectionBuilder setUrl(String url) throws MalformedURLException { @@ -92,7 +97,7 @@ public class HttpUrlConnectionBuilder { /** * Sets the connect timeout. Defaults to {@value #DEFAULT_TIMEOUT} milliseconds. * - * TODO: Remove @UsedForTesting after this is actually used. + * TODO: Remove @UsedForTesting after this method is actually used. */ @UsedForTesting public HttpUrlConnectionBuilder setConnectTimeout(int timeoutMillis) { @@ -107,7 +112,7 @@ public class HttpUrlConnectionBuilder { /** * Sets the read timeout. Defaults to {@value #DEFAULT_TIMEOUT} milliseconds. * - * TODO: Remove @UsedForTesting after this is actually used. + * TODO: Remove @UsedForTesting after this method is actually used. */ @UsedForTesting public HttpUrlConnectionBuilder setReadTimeout(int timeoutMillis) { @@ -122,7 +127,7 @@ public class HttpUrlConnectionBuilder { /** * Adds an entry to the request header. * - * TODO: Remove @UsedForTesting after this is actually used. + * TODO: Remove @UsedForTesting after this method is actually used. */ @UsedForTesting public HttpUrlConnectionBuilder addHeader(String key, String value) { @@ -131,10 +136,21 @@ public class HttpUrlConnectionBuilder { } /** + * Sets an authentication token. + * + * TODO: Remove @UsedForTesting after this method is actually used. + */ + @UsedForTesting + public HttpUrlConnectionBuilder setAuthToken(String value) { + mHeaderMap.put(HTTP_HEADER_AUTHORIZATION, value); + return this; + } + + /** * Sets the request to be executed such that the input is not buffered. * This may be set when the request size is known beforehand. * - * TODO: Remove @UsedForTesting after this is actually used. + * TODO: Remove @UsedForTesting after this method is actually used. */ @UsedForTesting public HttpUrlConnectionBuilder setFixedLengthForStreaming(int length) { @@ -145,7 +161,7 @@ public class HttpUrlConnectionBuilder { /** * Indicates if the request can use cached responses or not. * - * TODO: Remove @UsedForTesting after this is actually used. + * TODO: Remove @UsedForTesting after this method is actually used. */ @UsedForTesting public HttpUrlConnectionBuilder setUseCache(boolean useCache) { @@ -161,7 +177,7 @@ public class HttpUrlConnectionBuilder { * @see #MODE_DOWNLOAD_ONLY * @see #MODE_BI_DIRECTIONAL * - * TODO: Remove @UsedForTesting after this is actually used. + * TODO: Remove @UsedForTesting after this method is actually used */ @UsedForTesting public HttpUrlConnectionBuilder setMode(int mode) { @@ -177,7 +193,7 @@ public class HttpUrlConnectionBuilder { /** * Builds the {@link HttpURLConnection} instance that can be used to execute the request. * - * TODO: Remove @UsedForTesting after this is actually used. + * TODO: Remove @UsedForTesting after this method is actually used. */ @UsedForTesting public HttpURLConnection build() throws IOException { @@ -210,4 +226,4 @@ public class HttpUrlConnectionBuilder { } return connection; } -} +}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java index 32880038f..4bd15d037 100644 --- a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java @@ -16,6 +16,9 @@ package com.android.inputmethod.latin.settings; +import static com.android.inputmethod.latin.settings.LocalSettingsConstants.PREF_ACCOUNT_NAME; +import static com.android.inputmethod.latin.settings.LocalSettingsConstants.PREF_ENABLE_CLOUD_SYNC; + import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; @@ -32,8 +35,8 @@ import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.accounts.LoginAccountUtils; +import com.android.inputmethod.latin.accounts.AccountStateChangedListener; import com.android.inputmethod.latin.define.ProductionFlags; -import com.android.inputmethod.latin.sync.BeanstalkManager; import javax.annotation.Nullable; @@ -41,19 +44,17 @@ import javax.annotation.Nullable; * "Accounts & Privacy" settings sub screen. * * This settings sub screen handles the following preferences: - * <li> Account selection/management for IME - * <li> TODO: Sync preferences - * <li> TODO: Privacy preferences - * <li> Sync now + * <li> Account selection/management for IME </li> + * <li> Sync preferences </li> + * <li> Privacy preferences </li> */ public final class AccountsSettingsFragment extends SubScreenFragment { + private static final String PREF_SYNC_NOW = "pref_beanstalk"; + static final String PREF_ACCCOUNT_SWITCHER = "account_switcher"; - static final String PREF_SYNC_NOW = "pref_beanstalk"; - private final DialogInterface.OnClickListener mAccountSelectedListener = - new AccountSelectedListener(); - private final DialogInterface.OnClickListener mAccountSignedOutListener = - new AccountSignedOutListener(); + private final DialogInterface.OnClickListener mAccountChangedListener = + new AccountChangedListener(); private final Preference.OnPreferenceClickListener mSyncNowListener = new SyncNowListener(); @Override @@ -81,47 +82,56 @@ public final class AccountsSettingsFragment extends SubScreenFragment { removePreference(Settings.PREF_ENABLE_METRICS_LOGGING); } + if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) { + removePreference(PREF_ACCCOUNT_SWITCHER); + removePreference(PREF_ENABLE_CLOUD_SYNC); + removePreference(PREF_SYNC_NOW); + } if (!ProductionFlags.ENABLE_PERSONAL_DICTIONARY_SYNC) { + removePreference(PREF_ENABLE_CLOUD_SYNC); removePreference(PREF_SYNC_NOW); } else { final Preference syncNowPreference = findPreference(PREF_SYNC_NOW); - if (syncNowPreference != null) { - syncNowPreference.setOnPreferenceClickListener(mSyncNowListener); - } + syncNowPreference.setOnPreferenceClickListener(mSyncNowListener); } } @Override public void onResume() { super.onResume(); - refreshUi(); + refreshAccountAndDependentPreferences(getSignedInAccountName()); } @Override public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - // TODO: Look at the preference that changed before refreshing the view. - refreshUi(); - } - - private void refreshUi() { - refreshAccountSelection(); - refreshSyncNow(); + if (TextUtils.equals(key, PREF_ACCOUNT_NAME)) { + refreshAccountAndDependentPreferences( + prefs.getString(PREF_ACCOUNT_NAME, null)); + } else if (TextUtils.equals(key, PREF_ENABLE_CLOUD_SYNC)) { + final boolean syncEnabled = prefs.getBoolean(PREF_ENABLE_CLOUD_SYNC, false); + AccountStateChangedListener.onSyncPreferenceChanged( + getSignedInAccountName(), syncEnabled); + } } - private void refreshAccountSelection() { + private void refreshAccountAndDependentPreferences(@Nullable final String currentAccount) { if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) { return; } - final String currentAccount = getCurrentlySelectedAccount(); final Preference accountSwitcher = findPreference(PREF_ACCCOUNT_SWITCHER); if (currentAccount == null) { // No account is currently selected. accountSwitcher.setSummary(getString(R.string.no_accounts_selected)); + // Disable the sync preference UI. + disableSyncPreference(); } else { // Set the currently selected account. accountSwitcher.setSummary(getString(R.string.account_selected, currentAccount)); + // Enable the sync preference UI. + enableSyncPreference(); } + // Set up onClick listener for the account picker preference. final Context context = getActivity(); final String[] accountsForLogin = LoginAccountUtils.getAccountsForLogin(context); accountSwitcher.setOnPreferenceClickListener(new OnPreferenceClickListener() { @@ -129,45 +139,50 @@ public final class AccountsSettingsFragment extends SubScreenFragment { public boolean onPreferenceClick(Preference preference) { if (accountsForLogin.length == 0) { // TODO: Handle account addition. - Toast.makeText(getActivity(), - getString(R.string.account_select_cancel), Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), getString(R.string.account_select_cancel), + Toast.LENGTH_SHORT).show(); } else { createAccountPicker(accountsForLogin, currentAccount).show(); } return true; } }); - - // TODO: Depending on the account selection, enable/disable preferences that - // depend on an account. } /** - * Refreshes the "Sync Now" feature + * Enables the Sync preference UI and updates its summary. */ - private void refreshSyncNow() { + private void enableSyncPreference() { if (!ProductionFlags.ENABLE_PERSONAL_DICTIONARY_SYNC) { return; } - final Preference syncNowPreference = findPreference(PREF_SYNC_NOW); - if (syncNowPreference == null) { + final Preference syncPreference = findPreference(PREF_ENABLE_CLOUD_SYNC); + syncPreference.setEnabled(true); + syncPreference.setSummary(R.string.cloud_sync_summary); + } + + /** + * Disables the Sync preference UI and updates its summary to indicate + * the fact that an account needs to be selected for sync. + */ + private void disableSyncPreference() { + if (!ProductionFlags.ENABLE_PERSONAL_DICTIONARY_SYNC) { return; } - final String currentAccount = getCurrentlySelectedAccount(); - if (currentAccount == null) { - syncNowPreference.setEnabled(false); - syncNowPreference.setSummary(R.string.sync_now_summary); - } else { - syncNowPreference.setEnabled(true); - syncNowPreference.setSummary(R.string.sync_now_summary_disabled_signed_out); - } + final Preference syncPreference = findPreference(PREF_ENABLE_CLOUD_SYNC); + syncPreference.setEnabled(false); + syncPreference.setSummary(R.string.cloud_sync_summary_disabled_signed_out); } @Nullable - private String getCurrentlySelectedAccount() { - return getSharedPreferences().getString(Settings.PREF_ACCOUNT_NAME, null); + String getSignedInAccountName() { + return getSharedPreferences().getString(LocalSettingsConstants.PREF_ACCOUNT_NAME, null); + } + + boolean isSyncEnabled() { + return getSharedPreferences().getBoolean(PREF_ENABLE_CLOUD_SYNC, false); } /** @@ -200,51 +215,51 @@ public final class AccountsSettingsFragment extends SubScreenFragment { final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) .setTitle(R.string.account_select_title) .setSingleChoiceItems(accounts, index, null) - .setPositiveButton(R.string.account_select_ok, mAccountSelectedListener) + .setPositiveButton(R.string.account_select_ok, mAccountChangedListener) .setNegativeButton(R.string.account_select_cancel, null); if (isSignedIn) { - builder.setNeutralButton(R.string.account_select_sign_out, mAccountSignedOutListener); + builder.setNeutralButton(R.string.account_select_sign_out, mAccountChangedListener); } return builder.create(); } /** - * Listener for an account being selected from the picker. - * Persists the account to shared preferences. - */ - class AccountSelectedListener implements DialogInterface.OnClickListener { - @Override - public void onClick(DialogInterface dialog, int which) { - final ListView lv = ((AlertDialog)dialog).getListView(); - final Object selectedItem = lv.getItemAtPosition(lv.getCheckedItemPosition()); - getSharedPreferences() - .edit() - .putString(Settings.PREF_ACCOUNT_NAME, (String) selectedItem) - .apply(); - } - } - - /** - * Listener for sign-out being initiated from from the picker. - * Removed the account from shared preferences. + * Listener for a account selection changes from the picker. + * Persists/removes the account to/from shared preferences and sets up sync if required. */ - class AccountSignedOutListener implements DialogInterface.OnClickListener { + class AccountChangedListener implements DialogInterface.OnClickListener { @Override public void onClick(DialogInterface dialog, int which) { - getSharedPreferences() - .edit() - .remove(Settings.PREF_ACCOUNT_NAME) - .apply(); + final String oldAccount = getSignedInAccountName(); + switch (which) { + case DialogInterface.BUTTON_POSITIVE: // Signed in + final ListView lv = ((AlertDialog)dialog).getListView(); + final String newAccount = + (String) lv.getItemAtPosition(lv.getCheckedItemPosition()); + getSharedPreferences() + .edit() + .putString(PREF_ACCOUNT_NAME, newAccount) + .apply(); + AccountStateChangedListener.onAccountSignedIn(oldAccount, newAccount); + break; + case DialogInterface.BUTTON_NEUTRAL: // Signed out + AccountStateChangedListener.onAccountSignedOut(oldAccount); + getSharedPreferences() + .edit() + .remove(PREF_ACCOUNT_NAME) + .apply(); + break; + } } } /** - * Listener that initates the process of sync in the background. + * Listener that initiates the process of sync in the background. */ class SyncNowListener implements Preference.OnPreferenceClickListener { @Override public boolean onPreferenceClick(final Preference preference) { - BeanstalkManager.getInstance(getActivity() /* context */).requestSync(); + AccountStateChangedListener.forceSync(getSignedInAccountName()); return true; } } diff --git a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java index 3303ab093..d2c9dbbe9 100644 --- a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java @@ -113,6 +113,7 @@ public final class AdvancedSettingsFragment extends SubScreenFragment { setupKeypressVibrationDurationSettings(); setupKeypressSoundVolumeSettings(); + setupKeyLongpressTimeoutSettings(); refreshEnablingsOfKeypressSoundAndVibrationSettings(); } @@ -249,4 +250,43 @@ public final class AdvancedSettingsFragment extends SubScreenFragment { } }); } + + private void setupKeyLongpressTimeoutSettings() { + final SharedPreferences prefs = getSharedPreferences(); + final Resources res = getResources(); + final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference( + Settings.PREF_KEY_LONGPRESS_TIMEOUT); + if (pref == null) { + return; + } + pref.setInterface(new SeekBarDialogPreference.ValueProxy() { + @Override + public void writeValue(final int value, final String key) { + prefs.edit().putInt(key, value).apply(); + } + + @Override + public void writeDefaultValue(final String key) { + prefs.edit().remove(key).apply(); + } + + @Override + public int readValue(final String key) { + return Settings.readKeyLongpressTimeout(prefs, res); + } + + @Override + public int readDefaultValue(final String key) { + return Settings.readDefaultKeyLongpressTimeout(res); + } + + @Override + public String getValueText(final int value) { + return res.getString(R.string.abbreviation_unit_milliseconds, value); + } + + @Override + public void feedbackValue(final int value) {} + }); + } } diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java new file mode 100644 index 000000000..c07b60a0b --- /dev/null +++ b/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.settings; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Parcel; +import android.os.Parcelable; +import android.preference.DialogPreference; +import android.preference.Preference; +import android.util.Log; +import android.view.View; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; + +import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; +import com.android.inputmethod.compat.ViewCompatUtils; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.RichInputMethodManager; +import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; +import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; + +import java.util.TreeSet; + +final class CustomInputStylePreference extends DialogPreference + implements DialogInterface.OnCancelListener { + private static final boolean DEBUG_SUBTYPE_ID = false; + + interface Listener { + public void onRemoveCustomInputStyle(CustomInputStylePreference stylePref); + public void onSaveCustomInputStyle(CustomInputStylePreference stylePref); + public void onAddCustomInputStyle(CustomInputStylePreference stylePref); + public SubtypeLocaleAdapter getSubtypeLocaleAdapter(); + public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter(); + } + + private static final String KEY_PREFIX = "subtype_pref_"; + private static final String KEY_NEW_SUBTYPE = KEY_PREFIX + "new"; + + private InputMethodSubtype mSubtype; + private InputMethodSubtype mPreviousSubtype; + + private final Listener mProxy; + private Spinner mSubtypeLocaleSpinner; + private Spinner mKeyboardLayoutSetSpinner; + + public static CustomInputStylePreference newIncompleteSubtypePreference( + final Context context, final Listener proxy) { + return new CustomInputStylePreference(context, null, proxy); + } + + public CustomInputStylePreference(final Context context, final InputMethodSubtype subtype, + final Listener proxy) { + super(context, null); + setDialogLayoutResource(R.layout.additional_subtype_dialog); + setPersistent(false); + mProxy = proxy; + setSubtype(subtype); + } + + public void show() { + showDialog(null); + } + + public final boolean isIncomplete() { + return mSubtype == null; + } + + public InputMethodSubtype getSubtype() { + return mSubtype; + } + + public void setSubtype(final InputMethodSubtype subtype) { + mPreviousSubtype = mSubtype; + mSubtype = subtype; + if (isIncomplete()) { + setTitle(null); + setDialogTitle(R.string.add_style); + setKey(KEY_NEW_SUBTYPE); + } else { + final String displayName = + SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype); + setTitle(displayName); + setDialogTitle(displayName); + setKey(KEY_PREFIX + subtype.getLocale() + "_" + + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype)); + } + } + + public void revert() { + setSubtype(mPreviousSubtype); + } + + public boolean hasBeenModified() { + return mSubtype != null && !mSubtype.equals(mPreviousSubtype); + } + + @Override + protected View onCreateDialogView() { + final View v = super.onCreateDialogView(); + mSubtypeLocaleSpinner = (Spinner) v.findViewById(R.id.subtype_locale_spinner); + mSubtypeLocaleSpinner.setAdapter(mProxy.getSubtypeLocaleAdapter()); + mKeyboardLayoutSetSpinner = (Spinner) v.findViewById(R.id.keyboard_layout_set_spinner); + mKeyboardLayoutSetSpinner.setAdapter(mProxy.getKeyboardLayoutSetAdapter()); + // All keyboard layout names are in the Latin script and thus left to right. That means + // the view would align them to the left even if the system locale is RTL, but that + // would look strange. To fix this, we align them to the view's start, which will be + // natural for any direction. + ViewCompatUtils.setTextAlignment( + mKeyboardLayoutSetSpinner, ViewCompatUtils.TEXT_ALIGNMENT_VIEW_START); + return v; + } + + @Override + protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) { + builder.setCancelable(true).setOnCancelListener(this); + if (isIncomplete()) { + builder.setPositiveButton(R.string.add, this) + .setNegativeButton(android.R.string.cancel, this); + } else { + builder.setPositiveButton(R.string.save, this) + .setNeutralButton(android.R.string.cancel, this) + .setNegativeButton(R.string.remove, this); + final SubtypeLocaleItem localeItem = new SubtypeLocaleItem(mSubtype); + final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(mSubtype); + setSpinnerPosition(mSubtypeLocaleSpinner, localeItem); + setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem); + } + } + + private static void setSpinnerPosition(final Spinner spinner, final Object itemToSelect) { + final SpinnerAdapter adapter = spinner.getAdapter(); + final int count = adapter.getCount(); + for (int i = 0; i < count; i++) { + final Object item = spinner.getItemAtPosition(i); + if (item.equals(itemToSelect)) { + spinner.setSelection(i); + return; + } + } + } + + @Override + public void onCancel(final DialogInterface dialog) { + if (isIncomplete()) { + mProxy.onRemoveCustomInputStyle(this); + } + } + + @Override + public void onClick(final DialogInterface dialog, final int which) { + super.onClick(dialog, which); + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + final boolean isEditing = !isIncomplete(); + final SubtypeLocaleItem locale = + (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem(); + final KeyboardLayoutSetItem layout = + (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem(); + final InputMethodSubtype subtype = + AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + locale.mLocaleString, layout.mLayoutName); + setSubtype(subtype); + notifyChanged(); + if (isEditing) { + mProxy.onSaveCustomInputStyle(this); + } else { + mProxy.onAddCustomInputStyle(this); + } + break; + case DialogInterface.BUTTON_NEUTRAL: + // Nothing to do + break; + case DialogInterface.BUTTON_NEGATIVE: + mProxy.onRemoveCustomInputStyle(this); + break; + } + } + + private static int getSpinnerPosition(final Spinner spinner) { + if (spinner == null) return -1; + return spinner.getSelectedItemPosition(); + } + + private static void setSpinnerPosition(final Spinner spinner, final int position) { + if (spinner == null || position < 0) return; + spinner.setSelection(position); + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + final Dialog dialog = getDialog(); + if (dialog == null || !dialog.isShowing()) { + return superState; + } + + final SavedState myState = new SavedState(superState); + myState.mSubtype = mSubtype; + myState.mSubtypeLocaleSelectedPos = getSpinnerPosition(mSubtypeLocaleSpinner); + myState.mKeyboardLayoutSetSelectedPos = getSpinnerPosition(mKeyboardLayoutSetSpinner); + return myState; + } + + @Override + protected void onRestoreInstanceState(final Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + + final SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + setSpinnerPosition(mSubtypeLocaleSpinner, myState.mSubtypeLocaleSelectedPos); + setSpinnerPosition(mKeyboardLayoutSetSpinner, myState.mKeyboardLayoutSetSelectedPos); + setSubtype(myState.mSubtype); + } + + static final class SavedState extends Preference.BaseSavedState { + InputMethodSubtype mSubtype; + int mSubtypeLocaleSelectedPos; + int mKeyboardLayoutSetSelectedPos; + + public SavedState(final Parcelable superState) { + super(superState); + } + + @Override + public void writeToParcel(final Parcel dest, final int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mSubtypeLocaleSelectedPos); + dest.writeInt(mKeyboardLayoutSetSelectedPos); + dest.writeParcelable(mSubtype, 0); + } + + public SavedState(final Parcel source) { + super(source); + mSubtypeLocaleSelectedPos = source.readInt(); + mKeyboardLayoutSetSelectedPos = source.readInt(); + mSubtype = (InputMethodSubtype)source.readParcelable(null); + } + + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + @Override + public SavedState createFromParcel(final Parcel source) { + return new SavedState(source); + } + + @Override + public SavedState[] newArray(final int size) { + return new SavedState[size]; + } + }; + } + + static final class SubtypeLocaleItem implements Comparable<SubtypeLocaleItem> { + public final String mLocaleString; + private final String mDisplayName; + + public SubtypeLocaleItem(final InputMethodSubtype subtype) { + mLocaleString = subtype.getLocale(); + mDisplayName = SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale( + mLocaleString); + } + + // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()} + // to get display name. + @Override + public String toString() { + return mDisplayName; + } + + @Override + public int compareTo(final SubtypeLocaleItem o) { + return mLocaleString.compareTo(o.mLocaleString); + } + } + + static final class SubtypeLocaleAdapter extends ArrayAdapter<SubtypeLocaleItem> { + private static final String TAG_SUBTYPE = SubtypeLocaleAdapter.class.getSimpleName(); + + public SubtypeLocaleAdapter(final Context context) { + super(context, android.R.layout.simple_spinner_item); + setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + + final TreeSet<SubtypeLocaleItem> items = new TreeSet<>(); + final InputMethodInfo imi = RichInputMethodManager.getInstance() + .getInputMethodInfoOfThisIme(); + final int count = imi.getSubtypeCount(); + for (int i = 0; i < count; i++) { + final InputMethodSubtype subtype = imi.getSubtypeAt(i); + if (DEBUG_SUBTYPE_ID) { + Log.d(TAG_SUBTYPE, String.format("%-6s 0x%08x %11d %s", + subtype.getLocale(), subtype.hashCode(), subtype.hashCode(), + SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype))); + } + if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) { + items.add(new SubtypeLocaleItem(subtype)); + } + } + // TODO: Should filter out already existing combinations of locale and layout. + addAll(items); + } + } + + static final class KeyboardLayoutSetItem { + public final String mLayoutName; + private final String mDisplayName; + + public KeyboardLayoutSetItem(final InputMethodSubtype subtype) { + mLayoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); + mDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype); + } + + // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()} + // to get display name. + @Override + public String toString() { + return mDisplayName; + } + } + + static final class KeyboardLayoutSetAdapter extends ArrayAdapter<KeyboardLayoutSetItem> { + public KeyboardLayoutSetAdapter(final Context context) { + super(context, android.R.layout.simple_spinner_item); + setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + + // TODO: Should filter out already existing combinations of locale and layout. + for (final String layout : SubtypeLocaleUtils.getPredefinedKeyboardLayoutSet()) { + // This is a dummy subtype with NO_LANGUAGE, only for display. + final InputMethodSubtype subtype = + AdditionalSubtypeUtils.createDummyAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, layout); + add(new KeyboardLayoutSetItem(subtype)); + } + } + } +} diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java index c633fc167..46fcc7106 100644 --- a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java @@ -17,16 +17,12 @@ package com.android.inputmethod.latin.settings; import android.app.AlertDialog; -import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.preference.DialogPreference; import android.preference.Preference; import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; @@ -39,15 +35,9 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; -import android.widget.ArrayAdapter; -import android.widget.Spinner; -import android.widget.SpinnerAdapter; import android.widget.Toast; -import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; -import com.android.inputmethod.compat.ViewCompatUtils; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; @@ -56,19 +46,18 @@ import com.android.inputmethod.latin.utils.IntentUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import java.util.ArrayList; -import java.util.TreeSet; -public final class CustomInputStyleSettingsFragment extends PreferenceFragment { +public final class CustomInputStyleSettingsFragment extends PreferenceFragment + implements CustomInputStylePreference.Listener { private static final String TAG = CustomInputStyleSettingsFragment.class.getSimpleName(); - private static final boolean DEBUG_SUBTYPE_ID = false; // Note: We would like to turn this debug flag true in order to see what input styles are // defined in a bug-report. private static final boolean DEBUG_CUSTOM_INPUT_STYLES = true; private RichInputMethodManager mRichImm; private SharedPreferences mPrefs; - private SubtypeLocaleAdapter mSubtypeLocaleAdapter; - private KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter; + private CustomInputStylePreference.SubtypeLocaleAdapter mSubtypeLocaleAdapter; + private CustomInputStylePreference.KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter; private boolean mIsAddingNewSubtype; private AlertDialog mSubtypeEnablerNotificationDialog; @@ -79,320 +68,6 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment { "is_subtype_enabler_notification_dialog_open"; private static final String KEY_SUBTYPE_FOR_SUBTYPE_ENABLER = "subtype_for_subtype_enabler"; - static final class SubtypeLocaleItem implements Comparable<SubtypeLocaleItem> { - public final String mLocaleString; - private final String mDisplayName; - - public SubtypeLocaleItem(final InputMethodSubtype subtype) { - mLocaleString = subtype.getLocale(); - mDisplayName = SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale( - mLocaleString); - } - - // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()} - // to get display name. - @Override - public String toString() { - return mDisplayName; - } - - @Override - public int compareTo(final SubtypeLocaleItem o) { - return mLocaleString.compareTo(o.mLocaleString); - } - } - - static final class SubtypeLocaleAdapter extends ArrayAdapter<SubtypeLocaleItem> { - private static final String TAG_SUBTYPE = SubtypeLocaleAdapter.class.getSimpleName(); - - public SubtypeLocaleAdapter(final Context context) { - super(context, android.R.layout.simple_spinner_item); - setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - - final TreeSet<SubtypeLocaleItem> items = new TreeSet<>(); - final InputMethodInfo imi = RichInputMethodManager.getInstance() - .getInputMethodInfoOfThisIme(); - final int count = imi.getSubtypeCount(); - for (int i = 0; i < count; i++) { - final InputMethodSubtype subtype = imi.getSubtypeAt(i); - if (DEBUG_SUBTYPE_ID) { - Log.d(TAG_SUBTYPE, String.format("%-6s 0x%08x %11d %s", - subtype.getLocale(), subtype.hashCode(), subtype.hashCode(), - SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype))); - } - if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) { - items.add(new SubtypeLocaleItem(subtype)); - } - } - // TODO: Should filter out already existing combinations of locale and layout. - addAll(items); - } - } - - static final class KeyboardLayoutSetItem { - public final String mLayoutName; - private final String mDisplayName; - - public KeyboardLayoutSetItem(final InputMethodSubtype subtype) { - mLayoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); - mDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype); - } - - // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()} - // to get display name. - @Override - public String toString() { - return mDisplayName; - } - } - - static final class KeyboardLayoutSetAdapter extends ArrayAdapter<KeyboardLayoutSetItem> { - public KeyboardLayoutSetAdapter(final Context context) { - super(context, android.R.layout.simple_spinner_item); - setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - - // TODO: Should filter out already existing combinations of locale and layout. - for (final String layout : SubtypeLocaleUtils.getPredefinedKeyboardLayoutSet()) { - // This is a dummy subtype with NO_LANGUAGE, only for display. - final InputMethodSubtype subtype = - AdditionalSubtypeUtils.createDummyAdditionalSubtype( - SubtypeLocaleUtils.NO_LANGUAGE, layout); - add(new KeyboardLayoutSetItem(subtype)); - } - } - } - - private interface SubtypeDialogProxy { - public void onRemovePressed(SubtypePreference subtypePref); - public void onSavePressed(SubtypePreference subtypePref); - public void onAddPressed(SubtypePreference subtypePref); - public SubtypeLocaleAdapter getSubtypeLocaleAdapter(); - public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter(); - } - - static final class SubtypePreference extends DialogPreference - implements DialogInterface.OnCancelListener { - private static final String KEY_PREFIX = "subtype_pref_"; - private static final String KEY_NEW_SUBTYPE = KEY_PREFIX + "new"; - - private InputMethodSubtype mSubtype; - private InputMethodSubtype mPreviousSubtype; - - private final SubtypeDialogProxy mProxy; - private Spinner mSubtypeLocaleSpinner; - private Spinner mKeyboardLayoutSetSpinner; - - public static SubtypePreference newIncompleteSubtypePreference(final Context context, - final SubtypeDialogProxy proxy) { - return new SubtypePreference(context, null, proxy); - } - - public SubtypePreference(final Context context, final InputMethodSubtype subtype, - final SubtypeDialogProxy proxy) { - super(context, null); - setDialogLayoutResource(R.layout.additional_subtype_dialog); - setPersistent(false); - mProxy = proxy; - setSubtype(subtype); - } - - public void show() { - showDialog(null); - } - - public final boolean isIncomplete() { - return mSubtype == null; - } - - public InputMethodSubtype getSubtype() { - return mSubtype; - } - - public void setSubtype(final InputMethodSubtype subtype) { - mPreviousSubtype = mSubtype; - mSubtype = subtype; - if (isIncomplete()) { - setTitle(null); - setDialogTitle(R.string.add_style); - setKey(KEY_NEW_SUBTYPE); - } else { - final String displayName = - SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype); - setTitle(displayName); - setDialogTitle(displayName); - setKey(KEY_PREFIX + subtype.getLocale() + "_" - + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype)); - } - } - - public void revert() { - setSubtype(mPreviousSubtype); - } - - public boolean hasBeenModified() { - return mSubtype != null && !mSubtype.equals(mPreviousSubtype); - } - - @Override - protected View onCreateDialogView() { - final View v = super.onCreateDialogView(); - mSubtypeLocaleSpinner = (Spinner) v.findViewById(R.id.subtype_locale_spinner); - mSubtypeLocaleSpinner.setAdapter(mProxy.getSubtypeLocaleAdapter()); - mKeyboardLayoutSetSpinner = (Spinner) v.findViewById(R.id.keyboard_layout_set_spinner); - mKeyboardLayoutSetSpinner.setAdapter(mProxy.getKeyboardLayoutSetAdapter()); - // All keyboard layout names are in the Latin script and thus left to right. That means - // the view would align them to the left even if the system locale is RTL, but that - // would look strange. To fix this, we align them to the view's start, which will be - // natural for any direction. - ViewCompatUtils.setTextAlignment( - mKeyboardLayoutSetSpinner, ViewCompatUtils.TEXT_ALIGNMENT_VIEW_START); - return v; - } - - @Override - protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) { - builder.setCancelable(true).setOnCancelListener(this); - if (isIncomplete()) { - builder.setPositiveButton(R.string.add, this) - .setNegativeButton(android.R.string.cancel, this); - } else { - builder.setPositiveButton(R.string.save, this) - .setNeutralButton(android.R.string.cancel, this) - .setNegativeButton(R.string.remove, this); - final SubtypeLocaleItem localeItem = new SubtypeLocaleItem(mSubtype); - final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(mSubtype); - setSpinnerPosition(mSubtypeLocaleSpinner, localeItem); - setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem); - } - } - - private static void setSpinnerPosition(final Spinner spinner, final Object itemToSelect) { - final SpinnerAdapter adapter = spinner.getAdapter(); - final int count = adapter.getCount(); - for (int i = 0; i < count; i++) { - final Object item = spinner.getItemAtPosition(i); - if (item.equals(itemToSelect)) { - spinner.setSelection(i); - return; - } - } - } - - @Override - public void onCancel(final DialogInterface dialog) { - if (isIncomplete()) { - mProxy.onRemovePressed(this); - } - } - - @Override - public void onClick(final DialogInterface dialog, final int which) { - super.onClick(dialog, which); - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - final boolean isEditing = !isIncomplete(); - final SubtypeLocaleItem locale = - (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem(); - final KeyboardLayoutSetItem layout = - (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem(); - final InputMethodSubtype subtype = - AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( - locale.mLocaleString, layout.mLayoutName); - setSubtype(subtype); - notifyChanged(); - if (isEditing) { - mProxy.onSavePressed(this); - } else { - mProxy.onAddPressed(this); - } - break; - case DialogInterface.BUTTON_NEUTRAL: - // Nothing to do - break; - case DialogInterface.BUTTON_NEGATIVE: - mProxy.onRemovePressed(this); - break; - } - } - - private static int getSpinnerPosition(final Spinner spinner) { - if (spinner == null) return -1; - return spinner.getSelectedItemPosition(); - } - - private static void setSpinnerPosition(final Spinner spinner, final int position) { - if (spinner == null || position < 0) return; - spinner.setSelection(position); - } - - @Override - protected Parcelable onSaveInstanceState() { - final Parcelable superState = super.onSaveInstanceState(); - final Dialog dialog = getDialog(); - if (dialog == null || !dialog.isShowing()) { - return superState; - } - - final SavedState myState = new SavedState(superState); - myState.mSubtype = mSubtype; - myState.mSubtypeLocaleSelectedPos = getSpinnerPosition(mSubtypeLocaleSpinner); - myState.mKeyboardLayoutSetSelectedPos = getSpinnerPosition(mKeyboardLayoutSetSpinner); - return myState; - } - - @Override - protected void onRestoreInstanceState(final Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - - final SavedState myState = (SavedState) state; - super.onRestoreInstanceState(myState.getSuperState()); - setSpinnerPosition(mSubtypeLocaleSpinner, myState.mSubtypeLocaleSelectedPos); - setSpinnerPosition(mKeyboardLayoutSetSpinner, myState.mKeyboardLayoutSetSelectedPos); - setSubtype(myState.mSubtype); - } - - static final class SavedState extends Preference.BaseSavedState { - InputMethodSubtype mSubtype; - int mSubtypeLocaleSelectedPos; - int mKeyboardLayoutSetSelectedPos; - - public SavedState(final Parcelable superState) { - super(superState); - } - - @Override - public void writeToParcel(final Parcel dest, final int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(mSubtypeLocaleSelectedPos); - dest.writeInt(mKeyboardLayoutSetSelectedPos); - dest.writeParcelable(mSubtype, 0); - } - - public SavedState(final Parcel source) { - super(source); - mSubtypeLocaleSelectedPos = source.readInt(); - mKeyboardLayoutSetSelectedPos = source.readInt(); - mSubtype = (InputMethodSubtype)source.readParcelable(null); - } - - public static final Parcelable.Creator<SavedState> CREATOR = - new Parcelable.Creator<SavedState>() { - @Override - public SavedState createFromParcel(final Parcel source) { - return new SavedState(source); - } - - @Override - public SavedState[] newArray(final int size) { - return new SavedState[size]; - } - }; - } - } - public CustomInputStyleSettingsFragment() { // Empty constructor for fragment generation. } @@ -440,8 +115,9 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment { @Override public void onActivityCreated(final Bundle savedInstanceState) { final Context context = getActivity(); - mSubtypeLocaleAdapter = new SubtypeLocaleAdapter(context); - mKeyboardLayoutSetAdapter = new KeyboardLayoutSetAdapter(context); + mSubtypeLocaleAdapter = new CustomInputStylePreference.SubtypeLocaleAdapter(context); + mKeyboardLayoutSetAdapter = + new CustomInputStylePreference.KeyboardLayoutSetAdapter(context); final String prefSubtypes = Settings.readPrefAdditionalSubtypes(mPrefs, getResources()); @@ -454,7 +130,7 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment { && savedInstanceState.containsKey(KEY_IS_ADDING_NEW_SUBTYPE); if (mIsAddingNewSubtype) { getPreferenceScreen().addPreference( - SubtypePreference.newIncompleteSubtypePreference(context, mSubtypeProxy)); + CustomInputStylePreference.newIncompleteSubtypePreference(context, this)); } super.onActivityCreated(savedInstanceState); @@ -482,62 +158,60 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment { } } - private final SubtypeDialogProxy mSubtypeProxy = new SubtypeDialogProxy() { - @Override - public void onRemovePressed(final SubtypePreference subtypePref) { - mIsAddingNewSubtype = false; - final PreferenceGroup group = getPreferenceScreen(); - group.removePreference(subtypePref); + @Override + public void onRemoveCustomInputStyle(final CustomInputStylePreference stylePref) { + mIsAddingNewSubtype = false; + final PreferenceGroup group = getPreferenceScreen(); + group.removePreference(stylePref); + mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); + } + + @Override + public void onSaveCustomInputStyle(final CustomInputStylePreference stylePref) { + final InputMethodSubtype subtype = stylePref.getSubtype(); + if (!stylePref.hasBeenModified()) { + return; + } + if (findDuplicatedSubtype(subtype) == null) { mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); + return; } - @Override - public void onSavePressed(final SubtypePreference subtypePref) { - final InputMethodSubtype subtype = subtypePref.getSubtype(); - if (!subtypePref.hasBeenModified()) { - return; - } - if (findDuplicatedSubtype(subtype) == null) { - mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); - return; - } + // Saved subtype is duplicated. + final PreferenceGroup group = getPreferenceScreen(); + group.removePreference(stylePref); + stylePref.revert(); + group.addPreference(stylePref); + showSubtypeAlreadyExistsToast(subtype); + } - // Saved subtype is duplicated. - final PreferenceGroup group = getPreferenceScreen(); - group.removePreference(subtypePref); - subtypePref.revert(); - group.addPreference(subtypePref); - showSubtypeAlreadyExistsToast(subtype); + @Override + public void onAddCustomInputStyle(final CustomInputStylePreference stylePref) { + mIsAddingNewSubtype = false; + final InputMethodSubtype subtype = stylePref.getSubtype(); + if (findDuplicatedSubtype(subtype) == null) { + mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); + mSubtypePreferenceKeyForSubtypeEnabler = stylePref.getKey(); + mSubtypeEnablerNotificationDialog = createDialog(); + mSubtypeEnablerNotificationDialog.show(); + return; } - @Override - public void onAddPressed(final SubtypePreference subtypePref) { - mIsAddingNewSubtype = false; - final InputMethodSubtype subtype = subtypePref.getSubtype(); - if (findDuplicatedSubtype(subtype) == null) { - mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); - mSubtypePreferenceKeyForSubtypeEnabler = subtypePref.getKey(); - mSubtypeEnablerNotificationDialog = createDialog(); - mSubtypeEnablerNotificationDialog.show(); - return; - } - - // Newly added subtype is duplicated. - final PreferenceGroup group = getPreferenceScreen(); - group.removePreference(subtypePref); - showSubtypeAlreadyExistsToast(subtype); - } + // Newly added subtype is duplicated. + final PreferenceGroup group = getPreferenceScreen(); + group.removePreference(stylePref); + showSubtypeAlreadyExistsToast(subtype); + } - @Override - public SubtypeLocaleAdapter getSubtypeLocaleAdapter() { - return mSubtypeLocaleAdapter; - } + @Override + public CustomInputStylePreference.SubtypeLocaleAdapter getSubtypeLocaleAdapter() { + return mSubtypeLocaleAdapter; + } - @Override - public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter() { - return mKeyboardLayoutSetAdapter; - } - }; + @Override + public CustomInputStylePreference.KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter() { + return mKeyboardLayoutSetAdapter; + } private void showSubtypeAlreadyExistsToast(final InputMethodSubtype subtype) { final Context context = getActivity(); @@ -555,6 +229,7 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment { } private AlertDialog createDialog() { + final String imeId = mRichImm.getInputMethodIdOfThisIme(); final AlertDialog.Builder builder = new AlertDialog.Builder( DialogUtils.getPlatformDialogThemeContext(getActivity())); builder.setTitle(R.string.custom_input_styles_title) @@ -564,7 +239,7 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment { @Override public void onClick(DialogInterface dialog, int which) { final Intent intent = IntentUtils.getInputLanguageSelectionIntent( - mRichImm.getInputMethodIdOfThisIme(), + imeId, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -584,8 +259,8 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment { final InputMethodSubtype[] subtypesArray = AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtypes); for (final InputMethodSubtype subtype : subtypesArray) { - final SubtypePreference pref = new SubtypePreference( - context, subtype, mSubtypeProxy); + final CustomInputStylePreference pref = + new CustomInputStylePreference(context, subtype, this); group.addPreference(pref); } } @@ -596,8 +271,8 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment { final int count = group.getPreferenceCount(); for (int i = 0; i < count; i++) { final Preference pref = group.getPreference(i); - if (pref instanceof SubtypePreference) { - final SubtypePreference subtypePref = (SubtypePreference)pref; + if (pref instanceof CustomInputStylePreference) { + final CustomInputStylePreference subtypePref = (CustomInputStylePreference)pref; // We should not save newly adding subtype to preference because it is incomplete. if (subtypePref.isIncomplete()) continue; subtypes.add(subtypePref.getSubtype()); @@ -631,8 +306,8 @@ public final class CustomInputStyleSettingsFragment extends PreferenceFragment { public boolean onOptionsItemSelected(final MenuItem item) { final int itemId = item.getItemId(); if (itemId == R.id.action_add_style) { - final SubtypePreference newSubtype = - SubtypePreference.newIncompleteSubtypePreference(getActivity(), mSubtypeProxy); + final CustomInputStylePreference newSubtype = + CustomInputStylePreference.newIncompleteSubtypePreference(getActivity(), this); getPreferenceScreen().addPreference(newSubtype); newSubtype.show(); mIsAddingNewSubtype = true; diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java index 091ca43c6..768cba9b2 100644 --- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java +++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java @@ -16,29 +16,36 @@ package com.android.inputmethod.latin.settings; +/** + * Debug settings for the application. + * + * Note: Even though these settings are stored in the default shared preferences file, + * they shouldn't be restored across devices. + * If a new key is added here, it should also be blacklisted for restore in + * {@link LocalSettingsConstants}. + */ public final class DebugSettings { public static final String PREF_DEBUG_MODE = "debug_mode"; public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch"; public static final String PREF_FORCE_PHYSICAL_KEYBOARD_SPECIAL_KEY = "force_physical_keyboard_special_key"; - public static final String PREF_SHOULD_SHOW_LXX_SUGGESTION_UI = - "pref_should_show_lxx_suggestion_ui"; public static final String PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS = "pref_has_custom_key_preview_animation_params"; - public static final String PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE = - "pref_key_preview_show_up_start_x_scale"; - public static final String PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE = - "pref_key_preview_show_up_start_y_scale"; + public static final String PREF_KEY_PREVIEW_DISMISS_DURATION = + "pref_key_preview_dismiss_duration"; public static final String PREF_KEY_PREVIEW_DISMISS_END_X_SCALE = "pref_key_preview_dismiss_end_x_scale"; public static final String PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE = "pref_key_preview_dismiss_end_y_scale"; public static final String PREF_KEY_PREVIEW_SHOW_UP_DURATION = "pref_key_preview_show_up_duration"; - public static final String PREF_KEY_PREVIEW_DISMISS_DURATION = - "pref_key_preview_dismiss_duration"; + public static final String PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE = + "pref_key_preview_show_up_start_x_scale"; + public static final String PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE = + "pref_key_preview_show_up_start_y_scale"; + public static final String PREF_SHOULD_SHOW_LXX_SUGGESTION_UI = + "pref_should_show_lxx_suggestion_ui"; public static final String PREF_SLIDING_KEY_INPUT_PREVIEW = "pref_sliding_key_input_preview"; - public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout"; private DebugSettings() { // This class is not publicly instantiable. diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java index e9f8d45aa..475f1def7 100644 --- a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java @@ -73,7 +73,6 @@ public final class DebugSettingsFragment extends SubScreenFragment dictDumpPreferenceGroup.addPreference(pref); } final Resources res = getResources(); - setupKeyLongpressTimeoutSettings(); setupKeyPreviewAnimationDuration(DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION, res.getInteger(R.integer.config_key_preview_show_up_duration)); setupKeyPreviewAnimationDuration(DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION, @@ -163,45 +162,6 @@ public final class DebugSettingsFragment extends SubScreenFragment } } - private void setupKeyLongpressTimeoutSettings() { - final SharedPreferences prefs = getSharedPreferences(); - final Resources res = getResources(); - final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference( - DebugSettings.PREF_KEY_LONGPRESS_TIMEOUT); - if (pref == null) { - return; - } - pref.setInterface(new SeekBarDialogPreference.ValueProxy() { - @Override - public void writeValue(final int value, final String key) { - prefs.edit().putInt(key, value).apply(); - } - - @Override - public void writeDefaultValue(final String key) { - prefs.edit().remove(key).apply(); - } - - @Override - public int readValue(final String key) { - return Settings.readKeyLongpressTimeout(prefs, res); - } - - @Override - public int readDefaultValue(final String key) { - return Settings.readDefaultKeyLongpressTimeout(res); - } - - @Override - public String getValueText(final int value) { - return res.getString(R.string.abbreviation_unit_milliseconds, value); - } - - @Override - public void feedbackValue(final int value) {} - }); - } - private void setupKeyPreviewAnimationScale(final String prefKey, final float defaultValue) { final SharedPreferences prefs = getSharedPreferences(); final Resources res = getResources(); diff --git a/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java b/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java new file mode 100644 index 000000000..c9e9dc8d9 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.settings; + +/** + * Collection of device specific preference constants. + */ +public class LocalSettingsConstants { + // Preference file for storing preferences that are tied to a device + // and are not backed up. + public static final String PREFS_FILE = "local_prefs"; + + // Preference key for the current account. + // Do not restore. + public static final String PREF_ACCOUNT_NAME = "pref_account_name"; + // Preference key for enabling cloud sync feature. + // Do not restore. + public static final String PREF_ENABLE_CLOUD_SYNC = "pref_enable_cloud_sync"; + + // List of preference keys to skip from being restored by backup agent. + // These preferences are tied to a device and hence should not be restored. + // e.g. account name. + // Ideally they could have been kept in a separate file that wasn't backed up + // however the preference UI currently only deals with the default + // shared preferences which makes it non-trivial to move these out to + // a different shared preferences file. + public static final String[] PREFS_TO_SKIP_RESTORING = new String[] { + PREF_ACCOUNT_NAME, + PREF_ENABLE_CLOUD_SYNC, + // The debug settings are not restored on a new device. + // If a feature relies on these, it should ensure that the defaults are + // correctly set for it to work on a new device. + DebugSettings.PREF_DEBUG_MODE, + DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, + DebugSettings.PREF_FORCE_PHYSICAL_KEYBOARD_SPECIAL_KEY, + DebugSettings.PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS, + DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION, + DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_X_SCALE, + DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE, + DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION, + DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE, + DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE, + DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, + DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW + }; +} diff --git a/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java index b073c50a4..b71f8829b 100644 --- a/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java @@ -38,5 +38,6 @@ public final class MultiLingualSettingsFragment extends SubScreenFragment { removePreference(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY); removePreference(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST); } + AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(getActivity(), this); } } diff --git a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java index 31a20c4db..7603dbba5 100644 --- a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java +++ b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java @@ -22,7 +22,8 @@ public class NativeSuggestOptions { private static final int USE_FULL_EDIT_DISTANCE = 1; private static final int BLOCK_OFFENSIVE_WORDS = 2; private static final int SPACE_AWARE_GESTURE_ENABLED = 3; - private static final int OPTIONS_SIZE = 4; + private static final int WEIGHT_FOR_LOCALE_IN_THOUSANDS = 4; + private static final int OPTIONS_SIZE = 5; private final int[] mOptions = new int[OPTIONS_SIZE + AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE]; @@ -43,6 +44,12 @@ public class NativeSuggestOptions { setBooleanOption(SPACE_AWARE_GESTURE_ENABLED, value); } + public void setWeightForLocale(final float value) { + // We're passing this option as a fixed point value, in thousands. This is decoded in + // native code by SuggestOptions#weightForLocale(). + setIntegerOption(WEIGHT_FOR_LOCALE_IN_THOUSANDS, (int) (value * 1000)); + } + public void setAdditionalFeaturesOptions(final int[] additionalOptions) { if (additionalOptions == null) { return; diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java index 84596b4ad..391fc1982 100644 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ b/java/src/com/android/inputmethod/latin/settings/Settings.java @@ -93,8 +93,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_GESTURE_INPUT = "gesture_input"; public static final String PREF_VIBRATION_DURATION_SETTINGS = "pref_vibration_duration_settings"; - public static final String PREF_KEYPRESS_SOUND_VOLUME = - "pref_keypress_sound_volume"; + public static final String PREF_KEYPRESS_SOUND_VOLUME = "pref_keypress_sound_volume"; + public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout"; public static final String PREF_GESTURE_PREVIEW_TRAIL = "pref_gesture_preview_trail"; public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT = "pref_gesture_floating_preview_text"; @@ -106,8 +106,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_KEY_IS_INTERNAL = "pref_key_is_internal"; public static final String PREF_ENABLE_METRICS_LOGGING = "pref_enable_metrics_logging"; - public static final String PREF_ACCOUNT_NAME = "pref_account_name"; - // 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 = @@ -319,7 +317,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static int readKeyLongpressTimeout(final SharedPreferences prefs, final Resources res) { final int milliseconds = prefs.getInt( - DebugSettings.PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT); + PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT); return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds : readDefaultKeyLongpressTimeout(res); } diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java index 8c4801798..6c21accf6 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java @@ -56,6 +56,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment { final Preference accountsPreference = findPreference(Settings.SCREEN_ACCOUNTS); preferenceScreen.removePreference(accountsPreference); } + AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(getActivity(), this); } @Override diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index ce8a0ab9c..660b4e095 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -64,7 +64,6 @@ public class SettingsValues { public final boolean mSoundOn; public final boolean mKeyPreviewPopupOn; public final boolean mShowsVoiceInputKey; - public final String mAccountName; public final boolean mIncludesOtherImesInLanguageSwitchList; public final boolean mShowsLanguageSwitchKey; public final boolean mUseContactsDict; @@ -141,7 +140,6 @@ public class SettingsValues { mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res) && mInputAttributes.mShouldShowVoiceInputKey && SubtypeSwitcher.getInstance().isShortcutImeEnabled(); - mAccountName = prefs.getString(Settings.PREF_ACCOUNT_NAME, null); final String autoCorrectionThresholdRawValue = prefs.getString( Settings.PREF_AUTO_CORRECTION_THRESHOLD, res.getString(R.string.auto_correction_threshold_mode_index_modest)); @@ -385,8 +383,6 @@ public class SettingsValues { sb.append("" + mKeyPreviewPopupOn); sb.append("\n mShowsVoiceInputKey = "); sb.append("" + mShowsVoiceInputKey); - sb.append("\n mAccountName = "); - sb.append("" + mAccountName); sb.append("\n mIncludesOtherImesInLanguageSwitchList = "); sb.append("" + mIncludesOtherImesInLanguageSwitchList); sb.append("\n mShowsLanguageSwitchKey = "); diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java index 54562f39d..c3b30dcb4 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java @@ -42,6 +42,8 @@ import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils; import java.util.ArrayList; +import javax.annotation.Nonnull; + // TODO: Use Fragment to implement welcome screen and setup steps. public final class SetupWizardActivity extends Activity implements View.OnClickListener { static final String TAG = SetupWizardActivity.class.getSimpleName(); @@ -82,7 +84,7 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL private final InputMethodManager mImmInHandler; - public SettingsPoolingHandler(final SetupWizardActivity ownerInstance, + public SettingsPoolingHandler(@Nonnull final SetupWizardActivity ownerInstance, final InputMethodManager imm) { super(ownerInstance); mImmInHandler = imm; diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java index d55939971..7b66bbb75 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -321,18 +321,6 @@ final class SuggestionStripLayoutHelper { } else { color = mColorSuggested; } - if (DebugFlags.DEBUG_ENABLED && suggestedWords.size() > 1) { - // If we auto-correct, then the autocorrection is in slot 0 and the typed word - // is in slot 1. - if (indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION - && suggestedWords.mWillAutoCorrect - && AutoCorrectionUtils.shouldBlockAutoCorrectionBySafetyNet( - suggestedWords.getLabel(SuggestedWords.INDEX_OF_AUTO_CORRECTION), - suggestedWords.getLabel(SuggestedWords.INDEX_OF_TYPED_WORD))) { - return 0xFFFF0000; - } - } - if (suggestedWords.mIsObsoleteSuggestions && !isTypedWord) { return applyAlpha(color, mAlphaObsoleted); } @@ -553,12 +541,12 @@ final class SuggestionStripLayoutHelper { return countInStrip; } - public void layoutAddToDictionaryHint(final String word, final ViewGroup addToDictionaryStrip) { - final boolean shouldShowUiToAcceptTypedWord = Settings.getInstance().getCurrent() - .mShouldShowLxxSuggestionUi; + public void layoutAddToDictionaryHint(final String word, final ViewGroup addToDictionaryStrip, + final boolean shouldShowWordToSave) { + final boolean showsHintWithWord = shouldShowWordToSave + || !Settings.getInstance().getCurrent().mShouldShowLxxSuggestionUi; final int stripWidth = addToDictionaryStrip.getWidth(); - final int width = shouldShowUiToAcceptTypedWord ? stripWidth - : stripWidth - mDividerWidth - mPadding * 2; + final int width = stripWidth - (showsHintWithWord ? mDividerWidth + mPadding * 2 : 0); final TextView wordView = (TextView)addToDictionaryStrip.findViewById(R.id.word_to_save); wordView.setTextColor(mColorTypedWord); @@ -569,7 +557,7 @@ final class SuggestionStripLayoutHelper { wordView.setText(wordToSave); wordView.setTextScaleX(wordScaleX); setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); - final int wordVisibility = shouldShowUiToAcceptTypedWord ? View.GONE : View.VISIBLE; + final int wordVisibility = showsHintWithWord ? View.VISIBLE : View.GONE; wordView.setVisibility(wordVisibility); addToDictionaryStrip.findViewById(R.id.word_to_save_divider).setVisibility(wordVisibility); @@ -579,12 +567,7 @@ final class SuggestionStripLayoutHelper { final float hintWeight; final TextView hintView = (TextView)addToDictionaryStrip.findViewById( R.id.hint_add_to_dictionary); - if (shouldShowUiToAcceptTypedWord) { - hintText = res.getText(R.string.hint_add_to_dictionary_without_word); - hintWidth = width; - hintWeight = 1.0f; - hintView.setGravity(Gravity.CENTER); - } else { + if (showsHintWithWord) { final boolean isRtlLanguage = (ViewCompat.getLayoutDirection(addToDictionaryStrip) == ViewCompat.LAYOUT_DIRECTION_RTL); final String arrow = isRtlLanguage ? RIGHTWARDS_ARROW : LEFTWARDS_ARROW; @@ -595,6 +578,11 @@ final class SuggestionStripLayoutHelper { hintWidth = width - wordWidth; hintWeight = 1.0f - mCenterSuggestionWeight; hintView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START); + } else { + hintText = res.getText(R.string.hint_add_to_dictionary_without_word); + hintWidth = width; + hintWeight = 1.0f; + hintView.setGravity(Gravity.CENTER); } hintView.setTextColor(mColorAutoCorrect); final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint()); diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index e40fd8800..789d549d7 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -231,8 +231,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return mStripVisibilityGroup.isShowingAddToDictionaryStrip(); } - public void showAddToDictionaryHint(final String word) { - mLayoutHelper.layoutAddToDictionaryHint(word, mAddToDictionaryStrip); + public void showAddToDictionaryHint(final String word, final boolean shouldShowWordToSave) { + mLayoutHelper.layoutAddToDictionaryHint(word, mAddToDictionaryStrip, shouldShowWordToSave); // {@link TextView#setTag()} is used to hold the word to be added to dictionary. The word // will be extracted at {@link #onClick(View)}. mAddToDictionaryStrip.setTag(word); @@ -501,7 +501,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return; } final Object tag = view.getTag(); - // {@link String} tag is set at {@link #showAddToDictionaryHint(String,CharSequence)}. + // {@link String} tag is set at {@link #suggestAddingToDictionary(String,CharSequence)}. if (tag instanceof String) { final String wordToSave = (String)tag; mListener.addWordToUserDictionary(wordToSave); diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java index 52708455e..5c86a02af 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java @@ -22,7 +22,7 @@ import com.android.inputmethod.latin.SuggestedWords; * An object that gives basic control of a suggestion strip and some info on it. */ public interface SuggestionStripViewAccessor { - public void showAddToDictionaryHint(final String word); + public void suggestAddingToDictionary(final String word, final boolean isFromSuggestionStrip); public boolean isShowingAddToDictionaryHint(); public void dismissAddToDictionaryHint(); public void setNeutralSuggestionStrip(); diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java index 156fcf57c..cba769521 100644 --- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java @@ -52,41 +52,9 @@ public final class AutoCorrectionUtils { if (DBG) { Log.d(TAG, "Auto corrected by S-threshold."); } - return !shouldBlockAutoCorrectionBySafetyNet(consideredWord, suggestion.mWord); + return true; } } return false; } - - // TODO: Resolve the inconsistencies between the native auto correction algorithms and - // this safety net - public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord, - final String suggestion) { - // Safety net for auto correction. - // Actually if we hit this safety net, it's a bug. - // If user selected aggressive auto correction mode, there is no need to use the safety - // net. - // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH, - // we should not use net because relatively edit distance can be big. - final int typedWordLength = typedWord.length(); - if (typedWordLength < MINIMUM_SAFETY_NET_CHAR_LENGTH) { - return false; - } - final int maxEditDistanceOfNativeDictionary = (typedWordLength / 2) + 1; - final int distance = BinaryDictionaryUtils.editDistance(typedWord, suggestion); - if (DBG) { - Log.d(TAG, "Autocorrected edit distance = " + distance - + ", " + maxEditDistanceOfNativeDictionary); - } - if (distance > maxEditDistanceOfNativeDictionary) { - if (DBG) { - Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestion); - Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. " - + "Turning off auto-correction."); - } - return true; - } else { - return false; - } - } } diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java index 5d7deba15..ce25fe6a4 100644 --- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java @@ -43,7 +43,6 @@ public final class BinaryDictionaryUtils { private static native boolean createEmptyDictFileNative(String filePath, long dictVersion, String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray); private static native float calcNormalizedScoreNative(int[] before, int[] after, int score); - private static native int editDistanceNative(int[] before, int[] after); private static native int setCurrentTimeForTestNative(int currentTime); public static DictionaryHeader getHeader(final File dictFile) @@ -112,14 +111,6 @@ public final class BinaryDictionaryUtils { StringUtils.toCodePointArray(after), score); } - public static int editDistance(final String before, final String after) { - if (before == null || after == null) { - throw new IllegalArgumentException(); - } - return editDistanceNative(StringUtils.toCodePointArray(before), - StringUtils.toCodePointArray(after)); - } - /** * Control the current time to be used in the native code. If currentTime >= 0, this method sets * the current time and gets into test mode. diff --git a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java index 9dc0524a2..e05618901 100644 --- a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java @@ -16,10 +16,12 @@ package com.android.inputmethod.latin.utils; +import android.annotation.TargetApi; import android.graphics.Matrix; import android.graphics.Rect; import android.inputmethodservice.ExtractEditText; import android.inputmethodservice.InputMethodService; +import android.os.Build; import android.text.Layout; import android.text.Spannable; import android.view.View; @@ -27,6 +29,12 @@ import android.view.ViewParent; import android.view.inputmethod.CursorAnchorInfo; import android.widget.TextView; +import com.android.inputmethod.compat.BuildCompatUtils; +import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + /** * This class allows input methods to extract {@link CursorAnchorInfo} directly from the given * {@link TextView}. This is useful and even necessary to support full-screen mode where the default @@ -77,13 +85,32 @@ public final class CursorAnchorInfoUtils { } /** + * Extracts {@link CursorAnchorInfoCompatWrapper} from the given {@link TextView}. + * @param textView the target text view from which {@link CursorAnchorInfoCompatWrapper} is to + * be extracted. + * @return the {@link CursorAnchorInfoCompatWrapper} object based on the current layout. + * {@code null} if {@code Build.VERSION.SDK_INT} is 20 or prior or {@link TextView} is not + * ready to provide layout information. + */ + @Nullable + public static CursorAnchorInfoCompatWrapper extractFromTextView( + @Nonnull final TextView textView) { + if (Build.VERSION.SDK_INT < BuildCompatUtils.VERSION_CODES_LXX) { + return null; + } + return CursorAnchorInfoCompatWrapper.wrap(extractFromTextViewInternal(textView)); + } + + /** * Returns {@link CursorAnchorInfo} from the given {@link TextView}. * @param textView the target text view from which {@link CursorAnchorInfo} is to be extracted. * @return the {@link CursorAnchorInfo} object based on the current layout. {@code null} if it * is not feasible. */ - public static CursorAnchorInfo getCursorAnchorInfo(final TextView textView) { - Layout layout = textView.getLayout(); + @TargetApi(BuildCompatUtils.VERSION_CODES_LXX) + @Nullable + private static CursorAnchorInfo extractFromTextViewInternal(@Nonnull final TextView textView) { + final Layout layout = textView.getLayout(); if (layout == null) { return null; } diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java index 249478785..e29aabacd 100644 --- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java @@ -230,6 +230,7 @@ public class DictionaryInfoUtils { /** * Helper method to return a dictionary res id for a locale, or 0 if none. + * @param res resources for the app * @param locale dictionary locale * @return main dictionary resource id */ @@ -258,6 +259,7 @@ public class DictionaryInfoUtils { /** * Returns a main dictionary resource id + * @param res resources for the app * @param locale dictionary locale * @return main dictionary resource id */ @@ -283,6 +285,25 @@ public class DictionaryInfoUtils { BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.getLanguage().toString(); } + /** + * Returns whether a main dictionary is readily available for this locale. + * + * This does not query the content provider. + * + * @param context context to open files upon + * @param locale dictionary locale + * @return true if a dictionary is available right away, false otherwise + */ + public static boolean hasReadilyAvailableMainDictionaryForLocale(final Context context, + final Locale locale) { + final Resources res = context.getResources(); + if (0 != getMainDictionaryResourceIdIfAvailableForLocale(res, locale)) { + return true; + } + final String fileName = getCacheFileName(getMainDictId(locale), locale.toString(), context); + return new File(fileName).exists(); + } + public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file) { return getDictionaryFileHeaderOrNull(file, 0, file.length()); } diff --git a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java index dd6fac671..9a5be99b3 100644 --- a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java +++ b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java @@ -21,21 +21,22 @@ import android.os.Looper; import java.lang.ref.WeakReference; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + public class LeakGuardHandlerWrapper<T> extends Handler { private final WeakReference<T> mOwnerInstanceRef; - public LeakGuardHandlerWrapper(final T ownerInstance) { + public LeakGuardHandlerWrapper(@Nonnull final T ownerInstance) { this(ownerInstance, Looper.myLooper()); } - public LeakGuardHandlerWrapper(final T ownerInstance, final Looper looper) { + public LeakGuardHandlerWrapper(@Nonnull final T ownerInstance, final Looper looper) { super(looper); - if (ownerInstance == null) { - throw new NullPointerException("ownerInstance is null"); - } mOwnerInstanceRef = new WeakReference<>(ownerInstance); } + @Nullable public T getOwnerInstance() { return mOwnerInstanceRef.get(); } |