diff --git a/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java b/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java index 7e9e2e37b..6e43cc9a7 100644 --- a/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java +++ b/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java @@ -23,10 +23,10 @@ import android.os.Build.VERSION_CODES; * A class to encapsulate work-arounds specific to particular apps. */ public class AppWorkaroundsUtils { - private PackageInfo mPackageInfo; // May be null - private boolean mIsBrokenByRecorrection = false; + private final PackageInfo mPackageInfo; // May be null + private final boolean mIsBrokenByRecorrection; - public void setPackageInfo(final PackageInfo packageInfo) { + public AppWorkaroundsUtils(final PackageInfo packageInfo) { mPackageInfo = packageInfo; mIsBrokenByRecorrection = AppWorkaroundsHelper.evaluateIsBrokenByRecorrection( packageInfo); diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index fcf043031..b01bc4ba5 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -29,6 +29,7 @@ import com.android.inputmethod.latin.utils.StringUtils; public final class InputAttributes { private final String TAG = InputAttributes.class.getSimpleName(); + final public String mTargetApplicationPackageName; final public boolean mInputTypeNoAutoCorrect; final public boolean mIsSettingsSuggestionStripOn; final public boolean mApplicationSpecifiedCompletionOn; @@ -36,6 +37,7 @@ public final class InputAttributes { final private int mInputType; public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) { + mTargetApplicationPackageName = null != editorInfo ? editorInfo.packageName : null; final int inputType = null != editorInfo ? editorInfo.inputType : 0; final int inputClass = inputType & InputType.TYPE_MASK_CLASS; mInputType = inputType; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index a941dc3f4..6f5cc9fee 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -28,7 +28,6 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.pm.PackageInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; @@ -61,7 +60,6 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.compat.AppWorkaroundsUtils; import com.android.inputmethod.compat.InputMethodServiceCompatUtils; import com.android.inputmethod.dictionarypack.DictionaryPackConstants; import com.android.inputmethod.keyboard.Keyboard; @@ -89,7 +87,6 @@ import com.android.inputmethod.latin.utils.IntentUtils; import com.android.inputmethod.latin.utils.JniUtils; import com.android.inputmethod.latin.utils.LatinImeLoggerUtils; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; -import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; import com.android.inputmethod.latin.utils.TextRange; import com.android.inputmethod.research.ResearchLogger; @@ -102,8 +99,7 @@ import java.util.Locale; * Input method implementation for Qwerty'ish keyboard. */ public class LatinIME extends InputMethodService implements KeyboardActionListener, - SuggestionStripView.Listener, TargetPackageInfoGetterTask.OnTargetPackageInfoKnownListener, - Suggest.SuggestInitializationListener { + SuggestionStripView.Listener, Suggest.SuggestInitializationListener { private static final String TAG = LatinIME.class.getSimpleName(); private static final boolean TRACE = false; private static boolean DEBUG = false; @@ -128,8 +124,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private SuggestionStripView mSuggestionStripView; private CompletionInfo[] mApplicationSpecifiedCompletions; - // TODO[IL]: Make this an AsyncResultHolder or a Future in SettingsValues - public AppWorkaroundsUtils mAppWorkAroundsUtils = new AppWorkaroundsUtils(); private RichInputMethodManager mRichImm; @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; @@ -519,9 +513,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @UsedForTesting void loadSettings() { final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale(); - final InputAttributes inputAttributes = - new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode()); - mSettings.loadSettings(locale, inputAttributes); + final EditorInfo editorInfo = getCurrentInputEditorInfo(); + final InputAttributes inputAttributes = new InputAttributes(editorInfo, isFullscreenMode()); + mSettings.loadSettings(this, locale, inputAttributes); AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(mSettings.getCurrent()); // To load the keyboard we need to load all the settings once, but resetting the // contacts dictionary should be deferred until after the new layout has been displayed @@ -798,13 +792,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (isDifferentTextField) { mainKeyboardView.closing(); loadSettings(); - final PackageInfo packageInfo = - TargetPackageInfoGetterTask.getCachedPackageInfo(editorInfo.packageName); - mAppWorkAroundsUtils.setPackageInfo(packageInfo); - if (null == packageInfo) { - new TargetPackageInfoGetterTask(this /* context */, this /* listener */) - .execute(editorInfo.packageName); - } currentSettingsValues = mSettings.getCurrent(); if (suggest != null && currentSettingsValues.mCorrectionEnabled) { @@ -912,12 +899,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - // Callback for the TargetPackageInfoGetterTask - @Override - public void onTargetPackageInfoKnown(final PackageInfo info) { - mAppWorkAroundsUtils.setPackageInfo(info); - } - @Override public void onWindowHidden() { super.onWindowHidden(); @@ -1991,12 +1972,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // HACK: We may want to special-case some apps that exhibit bad behavior in case of // recorrection. This is a temporary, stopgap measure that will be removed later. // TODO: remove this. - if (mAppWorkAroundsUtils.isBrokenByRecorrection()) return; + final SettingsValues settingsValues = mSettings.getCurrent(); + if (settingsValues.isBrokenByRecorrection()) return; // A simple way to test for support from the TextView. if (!isSuggestionsStripVisible()) return; // Recorrection is not supported in languages without spaces because we don't know // how to segment them yet. - if (!mSettings.getCurrent().mCurrentLanguageHasSpaces) return; + if (!settingsValues.mCurrentLanguageHasSpaces) return; // If the cursor is not touching a word, or if there is a selection, return right away. if (mInputLogic.mLastSelectionStart != mInputLogic.mLastSelectionEnd) return; // If we don't know the cursor location, return. @@ -2322,8 +2304,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void debugDumpStateAndCrashWithException(final String context) { - final StringBuilder s = new StringBuilder(mAppWorkAroundsUtils.toString()); - s.append("\nAttributes : ").append(mSettings.getCurrent().mInputAttributes) + final SettingsValues settingsValues = mSettings.getCurrent(); + final StringBuilder s = new StringBuilder(settingsValues.toString()); + s.append("\nAttributes : ").append(settingsValues.mInputAttributes) .append("\nContext : ").append(context); throw new RuntimeException(s.toString()); } @@ -2348,5 +2331,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen p.println(" mVibrateOn=" + settingsValues.mVibrateOn); p.println(" mKeyPreviewPopupOn=" + settingsValues.mKeyPreviewPopupOn); p.println(" inputAttributes=" + settingsValues.mInputAttributes); + // TODO: Dump all settings values } } diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index db27d22b7..db9bf348f 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -387,7 +387,7 @@ public final class InputLogic { final boolean swapWeakSpace = maybeStripSpace(settingsValues, codePoint, spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x); - sendKeyCodePoint(codePoint); + sendKeyCodePoint(settingsValues, codePoint); if (swapWeakSpace) { swapSwapperAndSpace(keyboardSwitcher); @@ -449,7 +449,7 @@ public final class InputLogic { } if (!shouldAvoidSendingCode) { - sendKeyCodePoint(codePoint); + sendKeyCodePoint(settingsValues, codePoint); } if (Constants.CODE_SPACE == codePoint) { @@ -593,7 +593,7 @@ public final class InputLogic { // This should never happen. Log.e(TAG, "Backspace when we don't know the selection position"); } - if (mLatinIME.mAppWorkAroundsUtils.isBeforeJellyBean() || + if (settingsValues.isBeforeJellyBean() || settingsValues.mInputAttributes.isTypeNull()) { // There are two possible reasons to send a key event: either the field has // type TYPE_NULL, in which case the keyboard should send events, or we are @@ -1167,9 +1167,10 @@ public final class InputLogic { * Normally we send code points with commitText, but there are some cases (where backward * compatibility is a concern for example) where we want to use deprecated methods. * + * @param settingsValues the current values of the settings. * @param codePoint the code point to send. */ - private void sendKeyCodePoint(final int codePoint) { + private void sendKeyCodePoint(final SettingsValues settingsValues, final int codePoint) { if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_sendKeyCodePoint(codePoint); } @@ -1181,8 +1182,7 @@ public final class InputLogic { } // TODO: we should do this also when the editor has TYPE_NULL - if (Constants.CODE_ENTER == codePoint - && mLatinIME.mAppWorkAroundsUtils.isBeforeJellyBean()) { + if (Constants.CODE_ENTER == codePoint && settingsValues.isBeforeJellyBean()) { // Backward compatibility mode. Before Jelly bean, the keyboard would simulate // a hardware keyboard event on pressing enter or delete. This is bad for many // reasons (there are race conditions with commits) but some applications are @@ -1209,7 +1209,7 @@ public final class InputLogic { if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_promotePhantomSpace(); } - sendKeyCodePoint(Constants.CODE_SPACE); + sendKeyCodePoint(settingsValues, Constants.CODE_SPACE); } } diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java index 94f145813..9bd2b9389 100644 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ b/java/src/com/android/inputmethod/latin/settings/Settings.java @@ -101,6 +101,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id"; public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_ID = "last_shown_emoji_category_id"; + private Context mContext; private Resources mRes; private SharedPreferences mPrefs; private SettingsValues mSettingsValues; @@ -121,6 +122,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang } private void onCreate(final Context context) { + mContext = context; mRes = context.getResources(); mPrefs = PreferenceManager.getDefaultSharedPreferences(context); mPrefs.registerOnSharedPreferenceChangeListener(this); @@ -140,20 +142,22 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang Log.w(TAG, "onSharedPreferenceChanged called before loadSettings."); return; } - loadSettings(mSettingsValues.mLocale, mSettingsValues.mInputAttributes); + loadSettings(mContext, mSettingsValues.mLocale, mSettingsValues.mInputAttributes); } finally { mSettingsValuesLock.unlock(); } } - public void loadSettings(final Locale locale, final InputAttributes inputAttributes) { + public void loadSettings(final Context context, final Locale locale, + final InputAttributes inputAttributes) { mSettingsValuesLock.lock(); + mContext = context; try { final SharedPreferences prefs = mPrefs; final RunInLocale job = new RunInLocale() { @Override protected SettingsValues job(final Resources res) { - return new SettingsValues(prefs, locale, res, inputAttributes); + return new SettingsValues(context, prefs, locale, res, inputAttributes); } }; mSettingsValues = job.runInLocale(mRes, locale); diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index d98e547fc..f97e9e1fc 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -16,13 +16,16 @@ package com.android.inputmethod.latin.settings; +import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.PackageInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.util.Log; import android.view.inputmethod.EditorInfo; import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.compat.AppWorkaroundsUtils; import com.android.inputmethod.keyboard.internal.KeySpecParser; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; @@ -31,8 +34,10 @@ import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.utils.AsyncResultHolder; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.StringUtils; +import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; import java.util.ArrayList; import java.util.Arrays; @@ -48,6 +53,7 @@ public final class SettingsValues { // Float.NEGATIVE_INFINITE and Float.MAX_VALUE. Currently used for auto-correction settings. private static final String FLOAT_MAX_VALUE_MARKER_STRING = "floatMaxValue"; private static final String FLOAT_NEGATIVE_INFINITY_MARKER_STRING = "floatNegativeInfinity"; + private static final int TIMEOUT_TO_GET_TARGET_PACKAGE = 5; // seconds // From resources: public final int mDelayUpdateOldSuggestions; @@ -95,6 +101,7 @@ public final class SettingsValues { public final int mSuggestionVisibility; public final boolean mBoostPersonalizationDictionaryForDebug; public final boolean mUseOnlyPersonalizationDictionaryForDebug; + private final AsyncResultHolder mAppWorkarounds; // Setting values for additional features public final int[] mAdditionalFeaturesSettingValues = @@ -103,8 +110,8 @@ public final class SettingsValues { // Debug settings public final boolean mIsInternal; - public SettingsValues(final SharedPreferences prefs, final Locale locale, final Resources res, - final InputAttributes inputAttributes) { + public SettingsValues(final Context context, final SharedPreferences prefs, final Locale locale, + final Resources res, final InputAttributes inputAttributes) { mLocale = locale; // Get the resources mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions); @@ -177,6 +184,15 @@ public final class SettingsValues { Settings.readBoostPersonalizationDictionaryForDebug(prefs); mUseOnlyPersonalizationDictionaryForDebug = Settings.readUseOnlyPersonalizationDictionaryForDebug(prefs); + mAppWorkarounds = new AsyncResultHolder(); + final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo( + mInputAttributes.mTargetApplicationPackageName); + if (null != packageInfo) { + mAppWorkarounds.set(new AppWorkaroundsUtils(packageInfo)); + } else { + new TargetPackageInfoGetterTask(context, mAppWorkarounds) + .execute(mInputAttributes.mTargetApplicationPackageName); + } } // Only for tests @@ -225,6 +241,8 @@ public final class SettingsValues { mIsInternal = false; mBoostPersonalizationDictionaryForDebug = false; mUseOnlyPersonalizationDictionaryForDebug = false; + mAppWorkarounds = new AsyncResultHolder(); + mAppWorkarounds.set(null); } @UsedForTesting @@ -288,6 +306,14 @@ public final class SettingsValues { return mInputAttributes.isSameInputType(editorInfo); } + public boolean isBeforeJellyBean() { + return mAppWorkarounds.get(null, TIMEOUT_TO_GET_TARGET_PACKAGE).isBeforeJellyBean(); + } + + public boolean isBrokenByRecorrection() { + return mAppWorkarounds.get(null, TIMEOUT_TO_GET_TARGET_PACKAGE).isBrokenByRecorrection(); + } + // Helper functions to create member values. private static SuggestedWords createSuggestPuncList(final String[] puncs) { final ArrayList puncList = CollectionUtils.newArrayList(); diff --git a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java index afbe2ecad..42ea3c959 100644 --- a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java +++ b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java @@ -22,6 +22,8 @@ import android.content.pm.PackageManager; import android.os.AsyncTask; import android.util.LruCache; +import com.android.inputmethod.compat.AppWorkaroundsUtils; + public final class TargetPackageInfoGetterTask extends AsyncTask { private static final int MAX_CACHE_ENTRIES = 64; // arbitrary @@ -37,17 +39,13 @@ public final class TargetPackageInfoGetterTask extends sCache.remove(packageName); } - public interface OnTargetPackageInfoKnownListener { - public void onTargetPackageInfoKnown(final PackageInfo info); - } - private Context mContext; - private final OnTargetPackageInfoKnownListener mListener; + private final AsyncResultHolder mResult; public TargetPackageInfoGetterTask(final Context context, - final OnTargetPackageInfoKnownListener listener) { + final AsyncResultHolder result) { mContext = context; - mListener = listener; + mResult = result; } @Override @@ -65,6 +63,6 @@ public final class TargetPackageInfoGetterTask extends @Override protected void onPostExecute(final PackageInfo info) { - mListener.onTargetPackageInfoKnown(info); + mResult.set(new AppWorkaroundsUtils(info)); } } diff --git a/tests/src/com/android/inputmethod/latin/AppWorkaroundsTests.java b/tests/src/com/android/inputmethod/latin/AppWorkaroundsTests.java new file mode 100644 index 000000000..c29257d34 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/AppWorkaroundsTests.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin; + +import com.android.inputmethod.latin.settings.Settings; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Build.VERSION_CODES; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.inputmethod.EditorInfo; + +@LargeTest +public class AppWorkaroundsTests extends InputTestsBase { + String packageNameOfAppBeforeJellyBean; + String packageNameOfAppAfterJellyBean; + + @Override + protected void setUp() throws Exception { + // NOTE: this will fail if there is no app installed that targets an SDK + // before Jelly Bean. For the moment, it's fine. + final PackageManager pm = getContext().getPackageManager(); + for (ApplicationInfo ai : pm.getInstalledApplications(0 /* flags */)) { + if (ai.targetSdkVersion < VERSION_CODES.JELLY_BEAN) { + packageNameOfAppBeforeJellyBean = ai.packageName; + } else { + packageNameOfAppAfterJellyBean = ai.packageName; + } + } + super.setUp(); + } + + // We want to test if the app package info is correctly retrieved by LatinIME. Since it + // asks this information to the package manager from the package name, and that it takes + // the package name from the EditorInfo, all we have to do it put the correct package + // name in the editor info. + // To this end, our base class InputTestsBase offers a hook for us to touch the EditorInfo. + // We override this hook to write the package name that we need. + @Override + protected EditorInfo enrichEditorInfo(final EditorInfo ei) { + if ("testBeforeJellyBeanTrue".equals(getName())) { + ei.packageName = packageNameOfAppBeforeJellyBean; + } else if ("testBeforeJellyBeanFalse".equals(getName())) { + ei.packageName = packageNameOfAppAfterJellyBean; + } + return ei; + } + + public void testBeforeJellyBeanTrue() { + assertTrue("Couldn't successfully detect this app targets < Jelly Bean (package is " + + packageNameOfAppBeforeJellyBean + ")", + Settings.getInstance().getCurrent().isBeforeJellyBean()); + } + + public void testBeforeJellyBeanFalse() { + assertFalse("Couldn't successfully detect this app targets >= Jelly Bean (package is " + + packageNameOfAppAfterJellyBean + ")", + Settings.getInstance().getCurrent().isBeforeJellyBean()); + } +} \ No newline at end of file diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java index aaad740e4..cee73c917 100644 --- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java +++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java @@ -162,6 +162,13 @@ public class InputTestsBase extends ServiceTestCase { return setBooleanPreference(PREF_DEBUG_MODE, value, false); } + protected EditorInfo enrichEditorInfo(final EditorInfo ei) { + // Some tests that inherit from us need to add some data in the EditorInfo (see + // AppWorkaroundsTests#enrichEditorInfo() for a concrete example of this). Since we + // control the EditorInfo, we supply a hook here for children to override. + return ei; + } + @Override protected void setUp() throws Exception { super.setUp(); @@ -176,12 +183,13 @@ public class InputTestsBase extends ServiceTestCase { mPreviousAutoCorrectSetting = setStringPreference(PREF_AUTO_CORRECTION_THRESHOLD, DEFAULT_AUTO_CORRECTION_THRESHOLD, DEFAULT_AUTO_CORRECTION_THRESHOLD); mLatinIME.onCreate(); - final EditorInfo ei = new EditorInfo(); + EditorInfo ei = new EditorInfo(); final InputConnection ic = mEditText.onCreateInputConnection(ei); final LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); final ViewGroup vg = new FrameLayout(getContext()); mInputView = inflater.inflate(R.layout.input_view, vg); + ei = enrichEditorInfo(ei); mLatinIME.onCreateInputMethodInterface().startInput(ic, ei); mLatinIME.setInputView(mInputView); mLatinIME.onBindInput();