From 6b22120e8f7dc3b20e7d5c04199bfe570c189283 Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Thu, 18 Feb 2021 16:03:38 -0800 Subject: [PATCH] InputMethodInfo attr to suppress spell checker. If InputMethodService declares suppressesSpellChecker="true", the system SpellCheckerService will be disabled. With TextView, when IMSs are switched while the user is editing, SuggestionSpans that were previously added will not be removed even if the next IMS declares suppressesSpellChecker="true". This is because we don't know if the SuggestionSpans were from Spell Checker, App, or IMS. Bug: 153473490 Test: atest CtsInputMethodTestCases:SpellCheckerTest Change-Id: Ia22b8758111087818beea8c07b1c173f1a94b8e4 --- core/api/current.txt | 3 +++ .../view/inputmethod/InputMethodInfo.java | 23 ++++++++++++++++++- .../view/inputmethod/InputMethodManager.java | 20 ++++++++++++++++ .../view/textservice/SpellCheckerSession.java | 11 +++++++++ .../view/textservice/TextServicesManager.java | 19 ++++++++++++--- core/java/android/widget/Editor.java | 6 +++++ .../internal/view/InputBindResult.java | 11 +++++++-- core/res/res/values/attrs.xml | 1 + core/res/res/values/public.xml | 1 + .../InputMethodManagerService.java | 15 +++++++----- .../MultiClientInputMethodManagerService.java | 4 ++-- 11 files changed, 100 insertions(+), 14 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index c0940d6b81bcf..6078538524fb6 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1394,6 +1394,7 @@ package android { field public static final int supportsRtl = 16843695; // 0x10103af field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb field public static final int supportsUploading = 16843419; // 0x101029b + field public static final int suppressesSpellChecker = 16844354; // 0x1010642 field public static final int switchMinWidth = 16843632; // 0x1010370 field public static final int switchPadding = 16843633; // 0x1010371 field public static final int switchPreferenceStyle = 16843629; // 0x101036d @@ -51469,6 +51470,7 @@ package android.view.inputmethod { method public int getSubtypeCount(); method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager); method public CharSequence loadLabel(android.content.pm.PackageManager); + method public boolean suppressesSpellChecker(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -51490,6 +51492,7 @@ package android.view.inputmethod { method public boolean isActive(android.view.View); method public boolean isActive(); method public boolean isFullscreenMode(); + method public boolean isInputMethodSuppressingSpellChecker(); method @Deprecated public boolean isWatchingCursor(android.view.View); method public void restartInput(android.view.View); method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle); diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 5d876a6f62d35..cc533eb3b5d73 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -60,6 +60,7 @@ import java.util.List; * @attr ref android.R.styleable#InputMethod_isDefault * @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod * @attr ref android.R.styleable#InputMethod_supportsInlineSuggestions + * @attr ref android.R.styleable#InputMethod_suppressesSpellChecker */ public final class InputMethodInfo implements Parcelable { static final String TAG = "InputMethodInfo"; @@ -117,6 +118,11 @@ public final class InputMethodInfo implements Parcelable { */ private final boolean mInlineSuggestionsEnabled; + /** + * The flag whether this IME suppresses spell checker. + */ + private final boolean mSuppressesSpellChecker; + /** * @param service the {@link ResolveInfo} corresponds in which the IME is implemented. * @return a unique ID to be returned by {@link #getId()}. We have used @@ -160,6 +166,7 @@ public final class InputMethodInfo implements Parcelable { boolean isAuxIme = true; boolean supportsSwitchingToNextInputMethod = false; // false as default boolean inlineSuggestionsEnabled = false; // false as default + boolean suppressesSpellChecker = false; // false as default mForceDefault = false; PackageManager pm = context.getPackageManager(); @@ -203,6 +210,8 @@ public final class InputMethodInfo implements Parcelable { false); inlineSuggestionsEnabled = sa.getBoolean( com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions, false); + suppressesSpellChecker = sa.getBoolean( + com.android.internal.R.styleable.InputMethod_suppressesSpellChecker, false); sa.recycle(); final int depth = parser.getDepth(); @@ -274,6 +283,7 @@ public final class InputMethodInfo implements Parcelable { mIsAuxIme = isAuxIme; mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; mInlineSuggestionsEnabled = inlineSuggestionsEnabled; + mSuppressesSpellChecker = suppressesSpellChecker; mIsVrOnly = isVrOnly; } @@ -284,6 +294,7 @@ public final class InputMethodInfo implements Parcelable { mIsAuxIme = source.readInt() == 1; mSupportsSwitchingToNextInputMethod = source.readInt() == 1; mInlineSuggestionsEnabled = source.readInt() == 1; + mSuppressesSpellChecker = source.readBoolean(); mIsVrOnly = source.readBoolean(); mService = ResolveInfo.CREATOR.createFromParcel(source); mSubtypes = new InputMethodSubtypeArray(source); @@ -342,6 +353,7 @@ public final class InputMethodInfo implements Parcelable { mForceDefault = forceDefault; mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; mInlineSuggestionsEnabled = inlineSuggestionsEnabled; + mSuppressesSpellChecker = false; mIsVrOnly = isVrOnly; } @@ -494,7 +506,8 @@ public final class InputMethodInfo implements Parcelable { + " mSettingsActivityName=" + mSettingsActivityName + " mIsVrOnly=" + mIsVrOnly + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod - + " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled); + + " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled + + " mSuppressesSpellChecker=" + mSuppressesSpellChecker); pw.println(prefix + "mIsDefaultResId=0x" + Integer.toHexString(mIsDefaultResId)); pw.println(prefix + "Service:"); @@ -562,6 +575,13 @@ public final class InputMethodInfo implements Parcelable { return mInlineSuggestionsEnabled; } + /** + * Return {@code true} if this input method suppresses spell checker. + */ + public boolean suppressesSpellChecker() { + return mSuppressesSpellChecker; + } + /** * Used to package this object into a {@link Parcel}. * @@ -576,6 +596,7 @@ public final class InputMethodInfo implements Parcelable { dest.writeInt(mIsAuxIme ? 1 : 0); dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0); dest.writeInt(mInlineSuggestionsEnabled ? 1 : 0); + dest.writeBoolean(mSuppressesSpellChecker); dest.writeBoolean(mIsVrOnly); mService.writeToParcel(dest, flags); mSubtypes.writeToParcel(dest); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 53bbc0ab1a02f..c6e5eee3568e0 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -444,6 +444,13 @@ public final class InputMethodManager { */ private Matrix mActivityViewToScreenMatrix = null; + /** + * As reported by {@link InputBindResult}. This value is determined by + * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}. + */ + @GuardedBy("mH") + private boolean mIsInputMethodSuppressingSpellChecker = false; + // ----------------------------------------------------------- /** @@ -858,6 +865,8 @@ public final class InputMethodManager { mCurId = res.id; mBindSequence = res.sequence; mActivityViewToScreenMatrix = res.getActivityViewToScreenMatrix(); + mIsInputMethodSuppressingSpellChecker = + res.isInputMethodSuppressingSpellChecker; } startInputInner(StartInputReason.BOUND_TO_IMMS, null, 0, 0, 0); return; @@ -1469,6 +1478,15 @@ public final class InputMethodManager { } } + /** + * Return {@code true} if the input method is suppressing system spell checker. + */ + public boolean isInputMethodSuppressingSpellChecker() { + synchronized (mH) { + return mIsInputMethodSuppressingSpellChecker; + } + } + /** * Reset all of the state associated with being bound to an input method. */ @@ -1513,6 +1531,7 @@ public final class InputMethodManager { @UnsupportedAppUsage void finishInputLocked() { mActivityViewToScreenMatrix = null; + mIsInputMethodSuppressingSpellChecker = false; setNextServedViewLocked(null); if (getServedViewLocked() != null) { if (DEBUG) { @@ -2037,6 +2056,7 @@ public final class InputMethodManager { return false; } mActivityViewToScreenMatrix = res.getActivityViewToScreenMatrix(); + mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker; if (res.id != null) { setInputChannelLocked(res.channel); mBindSequence = res.sequence; diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java index 35d84458bd35e..ba58b6525a6de 100644 --- a/core/java/android/view/textservice/SpellCheckerSession.java +++ b/core/java/android/view/textservice/SpellCheckerSession.java @@ -25,6 +25,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.util.Log; +import android.view.inputmethod.InputMethodManager; import com.android.internal.textservice.ISpellCheckerSession; import com.android.internal.textservice.ISpellCheckerSessionListener; @@ -176,6 +177,11 @@ public class SpellCheckerSession { * @param suggestionsLimit the maximum number of suggestions that will be returned */ public void getSentenceSuggestions(TextInfo[] textInfos, int suggestionsLimit) { + final InputMethodManager imm = mTextServicesManager.getInputMethodManager(); + if (imm != null && imm.isInputMethodSuppressingSpellChecker()) { + handleOnGetSentenceSuggestionsMultiple(new SentenceSuggestionsInfo[0]); + return; + } mSpellCheckerSessionListenerImpl.getSentenceSuggestionsMultiple( textInfos, suggestionsLimit); } @@ -204,6 +210,11 @@ public class SpellCheckerSession { if (DBG) { Log.w(TAG, "getSuggestions from " + mSpellCheckerInfo.getId()); } + final InputMethodManager imm = mTextServicesManager.getInputMethodManager(); + if (imm != null && imm.isInputMethodSuppressingSpellChecker()) { + handleOnGetSuggestionsMultiple(new SuggestionsInfo[0]); + return; + } mSpellCheckerSessionListenerImpl.getSuggestionsMultiple( textInfos, suggestionsLimit, sequentialWords); } diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java index 996757da06418..6fb01a309402d 100644 --- a/core/java/android/view/textservice/TextServicesManager.java +++ b/core/java/android/view/textservice/TextServicesManager.java @@ -30,6 +30,7 @@ import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.UserHandle; import android.util.Log; +import android.view.inputmethod.InputMethodManager; import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener; import com.android.internal.textservice.ISpellCheckerSessionListener; @@ -88,10 +89,15 @@ public final class TextServicesManager { @UserIdInt private final int mUserId; - private TextServicesManager(@UserIdInt int userId) throws ServiceNotFoundException { + @Nullable + private final InputMethodManager mInputMethodManager; + + private TextServicesManager(@UserIdInt int userId, + @Nullable InputMethodManager inputMethodManager) throws ServiceNotFoundException { mService = ITextServicesManager.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE)); mUserId = userId; + mInputMethodManager = inputMethodManager; } /** @@ -105,7 +111,8 @@ public final class TextServicesManager { @NonNull public static TextServicesManager createInstance(@NonNull Context context) throws ServiceNotFoundException { - return new TextServicesManager(context.getUserId()); + return new TextServicesManager(context.getUserId(), context.getSystemService( + InputMethodManager.class)); } /** @@ -118,7 +125,7 @@ public final class TextServicesManager { synchronized (TextServicesManager.class) { if (sInstance == null) { try { - sInstance = new TextServicesManager(UserHandle.myUserId()); + sInstance = new TextServicesManager(UserHandle.myUserId(), null); } catch (ServiceNotFoundException e) { throw new IllegalStateException(e); } @@ -127,6 +134,12 @@ public final class TextServicesManager { } } + /** @hide */ + @Nullable + public InputMethodManager getInputMethodManager() { + return mInputMethodManager; + } + /** * Returns the language component of a given locale string. */ diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 7517b805da69d..ca8967794c815 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -988,6 +988,12 @@ public class Editor { if (mTextView.isTextEditable() && mTextView.isSuggestionsEnabled() && !(mTextView.isInExtractedMode())) { + final InputMethodManager imm = getInputMethodManager(); + if (imm != null && imm.isInputMethodSuppressingSpellChecker()) { + // Do not close mSpellChecker here as it may be reused when the current IME has been + // changed. + return; + } if (mSpellChecker == null && createSpellChecker) { mSpellChecker = new SpellChecker(mTextView); } diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java index f29e95ccdf658..c9755a3dcedc7 100644 --- a/core/java/com/android/internal/view/InputBindResult.java +++ b/core/java/com/android/internal/view/InputBindResult.java @@ -204,6 +204,8 @@ public final class InputBindResult implements Parcelable { @Nullable private final float[] mActivityViewToScreenMatrixValues; + public final boolean isInputMethodSuppressingSpellChecker; + /** * @return {@link Matrix} that corresponds to {@link #mActivityViewToScreenMatrixValues}. * {@code null} if {@link #mActivityViewToScreenMatrixValues} is {@code null}. @@ -220,7 +222,8 @@ public final class InputBindResult implements Parcelable { public InputBindResult(@ResultCode int _result, IInputMethodSession _method, InputChannel _channel, String _id, int _sequence, - @Nullable Matrix activityViewToScreenMatrix) { + @Nullable Matrix activityViewToScreenMatrix, + boolean isInputMethodSuppressingSpellChecker) { result = _result; method = _method; channel = _channel; @@ -232,6 +235,7 @@ public final class InputBindResult implements Parcelable { mActivityViewToScreenMatrixValues = new float[9]; activityViewToScreenMatrix.getValues(mActivityViewToScreenMatrixValues); } + this.isInputMethodSuppressingSpellChecker = isInputMethodSuppressingSpellChecker; } InputBindResult(Parcel source) { @@ -245,6 +249,7 @@ public final class InputBindResult implements Parcelable { id = source.readString(); sequence = source.readInt(); mActivityViewToScreenMatrixValues = source.createFloatArray(); + isInputMethodSuppressingSpellChecker = source.readBoolean(); } @Override @@ -252,6 +257,7 @@ public final class InputBindResult implements Parcelable { return "InputBindResult{result=" + getResultString() + " method="+ method + " id=" + id + " sequence=" + sequence + " activityViewToScreenMatrix=" + getActivityViewToScreenMatrix() + + " isInputMethodSuppressingSpellChecker=" + isInputMethodSuppressingSpellChecker + "}"; } @@ -274,6 +280,7 @@ public final class InputBindResult implements Parcelable { dest.writeString(id); dest.writeInt(sequence); dest.writeFloatArray(mActivityViewToScreenMatrixValues); + dest.writeBoolean(isInputMethodSuppressingSpellChecker); } /** @@ -340,7 +347,7 @@ public final class InputBindResult implements Parcelable { } private static InputBindResult error(@ResultCode int result) { - return new InputBindResult(result, null, null, null, -1, null); + return new InputBindResult(result, null, null, null, -1, null, false); } /** diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 6ebd8786b5f13..62278d5b86f17 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3564,6 +3564,7 @@ +