Loading core/java/android/view/inputmethod/EditorInfo.java +37 −0 Original line number Diff line number Diff line Loading @@ -1099,4 +1099,41 @@ public class EditorInfo implements InputType, Parcelable { public int describeContents() { return 0; } /** * Performs a loose equality check, which means there can be false negatives, but if the method * returns {@code true}, then both objects are guaranteed to be equal. * <ul> * <li>{@link #extras} is compared with {@link Bundle#kindofEquals}</li> * <li>{@link #actionLabel}, {@link #hintText}, and {@link #label} are compared with * {@link TextUtils#equals}, which does not account for Spans. </li> * </ul> * @hide */ public boolean kindofEquals(@Nullable EditorInfo that) { if (that == null) return false; if (this == that) return true; return inputType == that.inputType && imeOptions == that.imeOptions && internalImeOptions == that.internalImeOptions && actionId == that.actionId && initialSelStart == that.initialSelStart && initialSelEnd == that.initialSelEnd && initialCapsMode == that.initialCapsMode && fieldId == that.fieldId && Objects.equals(autofillId, that.autofillId) && Objects.equals(privateImeOptions, that.privateImeOptions) && Objects.equals(packageName, that.packageName) && Objects.equals(fieldName, that.fieldName) && Objects.equals(hintLocales, that.hintLocales) && Objects.equals(targetInputMethodUser, that.targetInputMethodUser) && Arrays.equals(contentMimeTypes, that.contentMimeTypes) && TextUtils.equals(actionLabel, that.actionLabel) && TextUtils.equals(hintText, that.hintText) && TextUtils.equals(label, that.label) && (extras == that.extras || (extras != null && extras.kindofEquals(that.extras))) && (mInitialSurroundingText == that.mInitialSurroundingText || (mInitialSurroundingText != null && mInitialSurroundingText.isEqualTo(that.mInitialSurroundingText))); } } core/java/android/view/inputmethod/InputMethodManager.java +95 −0 Original line number Diff line number Diff line Loading @@ -70,6 +70,7 @@ import android.os.Process; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; Loading Loading @@ -397,6 +398,18 @@ public final class InputMethodManager { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // This is a bug id. /** * If {@code true}, avoid calling the * {@link com.android.server.inputmethod.InputMethodManagerService InputMethodManagerService} * by skipping the call to {@link IInputMethodManager#startInputOrWindowGainedFocus} * when we are switching focus between two non-editable views. This saves the cost of a binder * call into the system server. * <p><b>Note:</b> * The default value is {@code true}. */ private static final boolean OPTIMIZE_NONEDITABLE_VIEWS = SystemProperties.getBoolean("debug.imm.optimize_noneditable_views", true); /** * @deprecated Use {@link #mServiceInvoker} instead. */ Loading Loading @@ -646,6 +659,26 @@ public final class InputMethodManager { private final class DelegateImpl implements ImeFocusController.InputMethodManagerDelegate { @GuardedBy("mH") @Nullable private ViewFocusParameterInfo mPreviousViewFocusParameters; @GuardedBy("mH") private void updatePreviousViewFocusParametersLocked( @Nullable EditorInfo currentEditorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, @SoftInputModeFlags int softInputMode, int windowFlags) { mPreviousViewFocusParameters = new ViewFocusParameterInfo(currentEditorInfo, startInputFlags, startInputReason, softInputMode, windowFlags); } @GuardedBy("mH") private void clearStateLocked() { mPreviousViewFocusParameters = null; } /** * Used by {@link ImeFocusController} to start input connection. */ Loading Loading @@ -1692,8 +1725,10 @@ public final class InputMethodManager { * Reset all of the state associated with a served view being connected * to an input method */ @GuardedBy("mH") private void clearConnectionLocked() { mCurrentEditorInfo = null; mDelegate.clearStateLocked(); if (mServedInputConnection != null) { mServedInputConnection.deactivate(); mServedInputConnection = null; Loading Loading @@ -2344,6 +2379,9 @@ public final class InputMethodManager { // Hook 'em up and let 'er rip. mCurrentEditorInfo = tba.createCopyInternal(); // Store the previously served connection so that we can determine whether it is safe // to skip the call to startInputOrWindowGainedFocus in the IMMS final RemoteInputConnectionImpl previouslyServedConnection = mServedInputConnection; mServedConnecting = false; if (mServedInputConnection != null) { Loading Loading @@ -2383,6 +2421,22 @@ public final class InputMethodManager { + ic + " tba=" + tba + " startInputFlags=" + InputMethodDebug.startInputFlagsToString(startInputFlags)); } // When we switch between non-editable views, do not call into the IMMS. final boolean canSkip = OPTIMIZE_NONEDITABLE_VIEWS && previouslyServedConnection == null && ic == null && isSwitchingBetweenEquivalentNonEditableViews( mDelegate.mPreviousViewFocusParameters, startInputFlags, startInputReason, softInputMode, windowFlags); updatePreviousViewFocusParametersLocked(mCurrentEditorInfo, startInputFlags, startInputReason, softInputMode, windowFlags); if (canSkip) { if (DEBUG) { Log.d(TAG, "Not calling IMMS due to switching between non-editable views."); } return false; } res = mServiceInvoker.startInputOrWindowGainedFocus( startInputReason, mClient, windowGainingFocus, startInputFlags, softInputMode, windowFlags, tba, servedInputConnection, Loading Loading @@ -2445,6 +2499,47 @@ public final class InputMethodManager { return true; } /** * This method exists only so that the * <a href="https://errorprone.info/bugpattern/GuardedBy">errorprone</a> false positive warning * can be suppressed without granting a blanket exception to the {@link #startInputInner} * method. * <p> * The warning in question implies that the access to the * {@link DelegateImpl#updatePreviousViewFocusParametersLocked} method should be guarded by * {@code InputMethodManager.this.mH}, but instead {@code mDelegate.mH} is held in the caller. * In this case errorprone fails to realize that it is the same object. */ @GuardedBy("mH") @SuppressWarnings("GuardedBy") private void updatePreviousViewFocusParametersLocked( @Nullable EditorInfo currentEditorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, @SoftInputModeFlags int softInputMode, int windowFlags) { mDelegate.updatePreviousViewFocusParametersLocked(currentEditorInfo, startInputFlags, startInputReason, softInputMode, windowFlags); } /** * @return {@code true} when we are switching focus between two non-editable views * so that we can avoid calling {@link IInputMethodManager#startInputOrWindowGainedFocus}. */ @GuardedBy("mH") private boolean isSwitchingBetweenEquivalentNonEditableViews( @Nullable ViewFocusParameterInfo previousViewFocusParameters, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, @SoftInputModeFlags int softInputMode, int windowFlags) { return (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) == 0 && (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) == 0 && previousViewFocusParameters != null && previousViewFocusParameters.sameAs(mCurrentEditorInfo, startInputFlags, startInputReason, softInputMode, windowFlags); } private void reportInputConnectionOpened( InputConnection ic, EditorInfo tba, Handler icHandler, View view) { view.onInputConnectionOpenedInternal(ic, tba, icHandler); Loading core/java/android/view/inputmethod/SurroundingText.java +10 −0 Original line number Diff line number Diff line Loading @@ -181,4 +181,14 @@ public final class SurroundingText implements Parcelable { } } } /** @hide */ public boolean isEqualTo(@Nullable SurroundingText that) { if (that == null) return false; if (this == that) return true; return mSelectionStart == that.mSelectionStart && mSelectionEnd == that.mSelectionEnd && mOffset == that.mOffset && TextUtils.equals(mText, that.mText); } } core/java/android/view/inputmethod/ViewFocusParameterInfo.java 0 → 100644 +64 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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 android.view.inputmethod; import android.annotation.Nullable; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; import com.android.internal.view.IInputMethodManager; /** * This data class is a container for storing the last arguments used when calling into * {@link IInputMethodManager#startInputOrWindowGainedFocus}. They are used to determine if we * are switching from a non-editable view to another non-editable view, in which case we avoid * a binder call into the {@link com.android.server.inputmethod.InputMethodManagerService}. */ final class ViewFocusParameterInfo { @Nullable final EditorInfo mPreviousEditorInfo; @StartInputFlags final int mPreviousStartInputFlags; @StartInputReason final int mPreviousStartInputReason; @SoftInputModeFlags final int mPreviousSoftInputMode; final int mPreviousWindowFlags; ViewFocusParameterInfo(@Nullable EditorInfo previousEditorInfo, @StartInputFlags int previousStartInputFlags, @StartInputReason int previousStartInputReason, @SoftInputModeFlags int previousSoftInputMode, int previousWindowFlags) { mPreviousEditorInfo = previousEditorInfo; mPreviousStartInputFlags = previousStartInputFlags; mPreviousStartInputReason = previousStartInputReason; mPreviousSoftInputMode = previousSoftInputMode; mPreviousWindowFlags = previousWindowFlags; } boolean sameAs(@Nullable EditorInfo currentEditorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, @SoftInputModeFlags int softInputMode, int windowFlags) { return mPreviousStartInputFlags == startInputFlags && mPreviousStartInputReason == startInputReason && mPreviousSoftInputMode == softInputMode && mPreviousWindowFlags == windowFlags && (mPreviousEditorInfo == currentEditorInfo || (mPreviousEditorInfo != null && mPreviousEditorInfo.kindofEquals(currentEditorInfo))); } } core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java +66 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.view.inputmethod; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; Loading Loading @@ -59,6 +60,28 @@ public class EditorInfoTest { private static final int TEST_USER_ID = 42; private static final int LONG_EXP_TEXT_LENGTH = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH * 2; private static final EditorInfo TEST_EDITOR_INFO = new EditorInfo(); static { TEST_EDITOR_INFO.inputType = InputType.TYPE_CLASS_TEXT; // 0x1 TEST_EDITOR_INFO.imeOptions = EditorInfo.IME_ACTION_GO; // 0x2 TEST_EDITOR_INFO.privateImeOptions = "testOptions"; TEST_EDITOR_INFO.initialSelStart = 0; TEST_EDITOR_INFO.initialSelEnd = 1; TEST_EDITOR_INFO.initialCapsMode = TextUtils.CAP_MODE_CHARACTERS; // 0x1000 TEST_EDITOR_INFO.hintText = "testHintText"; TEST_EDITOR_INFO.label = "testLabel"; TEST_EDITOR_INFO.packageName = "android.view.inputmethod"; TEST_EDITOR_INFO.fieldId = 0; TEST_EDITOR_INFO.autofillId = AutofillId.NO_AUTOFILL_ID; TEST_EDITOR_INFO.fieldName = "testField"; TEST_EDITOR_INFO.extras = new Bundle(); TEST_EDITOR_INFO.extras.putString("testKey", "testValue"); TEST_EDITOR_INFO.hintLocales = LocaleList.forLanguageTags("en,de,ua"); TEST_EDITOR_INFO.contentMimeTypes = new String[] {"image/png"}; TEST_EDITOR_INFO.targetInputMethodUser = UserHandle.of(TEST_USER_ID); } /** * Makes sure that {@code null} {@link EditorInfo#targetInputMethodUser} can be copied via * {@link Parcel}. Loading Loading @@ -526,4 +549,47 @@ public class EditorInfoTest { + "prefix: hintLocales=null\n" + "prefix: contentMimeTypes=null\n"); } @Test public void testKindofEqualsAfterCopyInternal() { final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal(); assertTrue(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } @Test public void testKindofEqualsAfterCloneViaParcel() { // This test demonstrates a false negative case when an EditorInfo is // created from a Parcel and its extras are still parcelled, which in turn // runs into the edge case in Bundle.kindofEquals final EditorInfo infoCopy = cloneViaParcel(TEST_EDITOR_INFO); assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } @Test public void testKindofEqualsComparesAutofillId() { final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal(); infoCopy.autofillId = new AutofillId(42); assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } @Test public void testKindofEqualsComparesFieldId() { final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal(); infoCopy.fieldId = 42; assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } @Test public void testKindofEqualsComparesMimeTypes() { final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal(); infoCopy.contentMimeTypes = new String[] {"image/png", "image/gif"}; assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } @Test public void testKindofEqualsComparesExtras() { final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal(); infoCopy.extras.putString("testKey2", "testValue"); assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } } Loading
core/java/android/view/inputmethod/EditorInfo.java +37 −0 Original line number Diff line number Diff line Loading @@ -1099,4 +1099,41 @@ public class EditorInfo implements InputType, Parcelable { public int describeContents() { return 0; } /** * Performs a loose equality check, which means there can be false negatives, but if the method * returns {@code true}, then both objects are guaranteed to be equal. * <ul> * <li>{@link #extras} is compared with {@link Bundle#kindofEquals}</li> * <li>{@link #actionLabel}, {@link #hintText}, and {@link #label} are compared with * {@link TextUtils#equals}, which does not account for Spans. </li> * </ul> * @hide */ public boolean kindofEquals(@Nullable EditorInfo that) { if (that == null) return false; if (this == that) return true; return inputType == that.inputType && imeOptions == that.imeOptions && internalImeOptions == that.internalImeOptions && actionId == that.actionId && initialSelStart == that.initialSelStart && initialSelEnd == that.initialSelEnd && initialCapsMode == that.initialCapsMode && fieldId == that.fieldId && Objects.equals(autofillId, that.autofillId) && Objects.equals(privateImeOptions, that.privateImeOptions) && Objects.equals(packageName, that.packageName) && Objects.equals(fieldName, that.fieldName) && Objects.equals(hintLocales, that.hintLocales) && Objects.equals(targetInputMethodUser, that.targetInputMethodUser) && Arrays.equals(contentMimeTypes, that.contentMimeTypes) && TextUtils.equals(actionLabel, that.actionLabel) && TextUtils.equals(hintText, that.hintText) && TextUtils.equals(label, that.label) && (extras == that.extras || (extras != null && extras.kindofEquals(that.extras))) && (mInitialSurroundingText == that.mInitialSurroundingText || (mInitialSurroundingText != null && mInitialSurroundingText.isEqualTo(that.mInitialSurroundingText))); } }
core/java/android/view/inputmethod/InputMethodManager.java +95 −0 Original line number Diff line number Diff line Loading @@ -70,6 +70,7 @@ import android.os.Process; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; Loading Loading @@ -397,6 +398,18 @@ public final class InputMethodManager { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // This is a bug id. /** * If {@code true}, avoid calling the * {@link com.android.server.inputmethod.InputMethodManagerService InputMethodManagerService} * by skipping the call to {@link IInputMethodManager#startInputOrWindowGainedFocus} * when we are switching focus between two non-editable views. This saves the cost of a binder * call into the system server. * <p><b>Note:</b> * The default value is {@code true}. */ private static final boolean OPTIMIZE_NONEDITABLE_VIEWS = SystemProperties.getBoolean("debug.imm.optimize_noneditable_views", true); /** * @deprecated Use {@link #mServiceInvoker} instead. */ Loading Loading @@ -646,6 +659,26 @@ public final class InputMethodManager { private final class DelegateImpl implements ImeFocusController.InputMethodManagerDelegate { @GuardedBy("mH") @Nullable private ViewFocusParameterInfo mPreviousViewFocusParameters; @GuardedBy("mH") private void updatePreviousViewFocusParametersLocked( @Nullable EditorInfo currentEditorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, @SoftInputModeFlags int softInputMode, int windowFlags) { mPreviousViewFocusParameters = new ViewFocusParameterInfo(currentEditorInfo, startInputFlags, startInputReason, softInputMode, windowFlags); } @GuardedBy("mH") private void clearStateLocked() { mPreviousViewFocusParameters = null; } /** * Used by {@link ImeFocusController} to start input connection. */ Loading Loading @@ -1692,8 +1725,10 @@ public final class InputMethodManager { * Reset all of the state associated with a served view being connected * to an input method */ @GuardedBy("mH") private void clearConnectionLocked() { mCurrentEditorInfo = null; mDelegate.clearStateLocked(); if (mServedInputConnection != null) { mServedInputConnection.deactivate(); mServedInputConnection = null; Loading Loading @@ -2344,6 +2379,9 @@ public final class InputMethodManager { // Hook 'em up and let 'er rip. mCurrentEditorInfo = tba.createCopyInternal(); // Store the previously served connection so that we can determine whether it is safe // to skip the call to startInputOrWindowGainedFocus in the IMMS final RemoteInputConnectionImpl previouslyServedConnection = mServedInputConnection; mServedConnecting = false; if (mServedInputConnection != null) { Loading Loading @@ -2383,6 +2421,22 @@ public final class InputMethodManager { + ic + " tba=" + tba + " startInputFlags=" + InputMethodDebug.startInputFlagsToString(startInputFlags)); } // When we switch between non-editable views, do not call into the IMMS. final boolean canSkip = OPTIMIZE_NONEDITABLE_VIEWS && previouslyServedConnection == null && ic == null && isSwitchingBetweenEquivalentNonEditableViews( mDelegate.mPreviousViewFocusParameters, startInputFlags, startInputReason, softInputMode, windowFlags); updatePreviousViewFocusParametersLocked(mCurrentEditorInfo, startInputFlags, startInputReason, softInputMode, windowFlags); if (canSkip) { if (DEBUG) { Log.d(TAG, "Not calling IMMS due to switching between non-editable views."); } return false; } res = mServiceInvoker.startInputOrWindowGainedFocus( startInputReason, mClient, windowGainingFocus, startInputFlags, softInputMode, windowFlags, tba, servedInputConnection, Loading Loading @@ -2445,6 +2499,47 @@ public final class InputMethodManager { return true; } /** * This method exists only so that the * <a href="https://errorprone.info/bugpattern/GuardedBy">errorprone</a> false positive warning * can be suppressed without granting a blanket exception to the {@link #startInputInner} * method. * <p> * The warning in question implies that the access to the * {@link DelegateImpl#updatePreviousViewFocusParametersLocked} method should be guarded by * {@code InputMethodManager.this.mH}, but instead {@code mDelegate.mH} is held in the caller. * In this case errorprone fails to realize that it is the same object. */ @GuardedBy("mH") @SuppressWarnings("GuardedBy") private void updatePreviousViewFocusParametersLocked( @Nullable EditorInfo currentEditorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, @SoftInputModeFlags int softInputMode, int windowFlags) { mDelegate.updatePreviousViewFocusParametersLocked(currentEditorInfo, startInputFlags, startInputReason, softInputMode, windowFlags); } /** * @return {@code true} when we are switching focus between two non-editable views * so that we can avoid calling {@link IInputMethodManager#startInputOrWindowGainedFocus}. */ @GuardedBy("mH") private boolean isSwitchingBetweenEquivalentNonEditableViews( @Nullable ViewFocusParameterInfo previousViewFocusParameters, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, @SoftInputModeFlags int softInputMode, int windowFlags) { return (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) == 0 && (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) == 0 && previousViewFocusParameters != null && previousViewFocusParameters.sameAs(mCurrentEditorInfo, startInputFlags, startInputReason, softInputMode, windowFlags); } private void reportInputConnectionOpened( InputConnection ic, EditorInfo tba, Handler icHandler, View view) { view.onInputConnectionOpenedInternal(ic, tba, icHandler); Loading
core/java/android/view/inputmethod/SurroundingText.java +10 −0 Original line number Diff line number Diff line Loading @@ -181,4 +181,14 @@ public final class SurroundingText implements Parcelable { } } } /** @hide */ public boolean isEqualTo(@Nullable SurroundingText that) { if (that == null) return false; if (this == that) return true; return mSelectionStart == that.mSelectionStart && mSelectionEnd == that.mSelectionEnd && mOffset == that.mOffset && TextUtils.equals(mText, that.mText); } }
core/java/android/view/inputmethod/ViewFocusParameterInfo.java 0 → 100644 +64 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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 android.view.inputmethod; import android.annotation.Nullable; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; import com.android.internal.view.IInputMethodManager; /** * This data class is a container for storing the last arguments used when calling into * {@link IInputMethodManager#startInputOrWindowGainedFocus}. They are used to determine if we * are switching from a non-editable view to another non-editable view, in which case we avoid * a binder call into the {@link com.android.server.inputmethod.InputMethodManagerService}. */ final class ViewFocusParameterInfo { @Nullable final EditorInfo mPreviousEditorInfo; @StartInputFlags final int mPreviousStartInputFlags; @StartInputReason final int mPreviousStartInputReason; @SoftInputModeFlags final int mPreviousSoftInputMode; final int mPreviousWindowFlags; ViewFocusParameterInfo(@Nullable EditorInfo previousEditorInfo, @StartInputFlags int previousStartInputFlags, @StartInputReason int previousStartInputReason, @SoftInputModeFlags int previousSoftInputMode, int previousWindowFlags) { mPreviousEditorInfo = previousEditorInfo; mPreviousStartInputFlags = previousStartInputFlags; mPreviousStartInputReason = previousStartInputReason; mPreviousSoftInputMode = previousSoftInputMode; mPreviousWindowFlags = previousWindowFlags; } boolean sameAs(@Nullable EditorInfo currentEditorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, @SoftInputModeFlags int softInputMode, int windowFlags) { return mPreviousStartInputFlags == startInputFlags && mPreviousStartInputReason == startInputReason && mPreviousSoftInputMode == softInputMode && mPreviousWindowFlags == windowFlags && (mPreviousEditorInfo == currentEditorInfo || (mPreviousEditorInfo != null && mPreviousEditorInfo.kindofEquals(currentEditorInfo))); } }
core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java +66 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.view.inputmethod; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; Loading Loading @@ -59,6 +60,28 @@ public class EditorInfoTest { private static final int TEST_USER_ID = 42; private static final int LONG_EXP_TEXT_LENGTH = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH * 2; private static final EditorInfo TEST_EDITOR_INFO = new EditorInfo(); static { TEST_EDITOR_INFO.inputType = InputType.TYPE_CLASS_TEXT; // 0x1 TEST_EDITOR_INFO.imeOptions = EditorInfo.IME_ACTION_GO; // 0x2 TEST_EDITOR_INFO.privateImeOptions = "testOptions"; TEST_EDITOR_INFO.initialSelStart = 0; TEST_EDITOR_INFO.initialSelEnd = 1; TEST_EDITOR_INFO.initialCapsMode = TextUtils.CAP_MODE_CHARACTERS; // 0x1000 TEST_EDITOR_INFO.hintText = "testHintText"; TEST_EDITOR_INFO.label = "testLabel"; TEST_EDITOR_INFO.packageName = "android.view.inputmethod"; TEST_EDITOR_INFO.fieldId = 0; TEST_EDITOR_INFO.autofillId = AutofillId.NO_AUTOFILL_ID; TEST_EDITOR_INFO.fieldName = "testField"; TEST_EDITOR_INFO.extras = new Bundle(); TEST_EDITOR_INFO.extras.putString("testKey", "testValue"); TEST_EDITOR_INFO.hintLocales = LocaleList.forLanguageTags("en,de,ua"); TEST_EDITOR_INFO.contentMimeTypes = new String[] {"image/png"}; TEST_EDITOR_INFO.targetInputMethodUser = UserHandle.of(TEST_USER_ID); } /** * Makes sure that {@code null} {@link EditorInfo#targetInputMethodUser} can be copied via * {@link Parcel}. Loading Loading @@ -526,4 +549,47 @@ public class EditorInfoTest { + "prefix: hintLocales=null\n" + "prefix: contentMimeTypes=null\n"); } @Test public void testKindofEqualsAfterCopyInternal() { final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal(); assertTrue(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } @Test public void testKindofEqualsAfterCloneViaParcel() { // This test demonstrates a false negative case when an EditorInfo is // created from a Parcel and its extras are still parcelled, which in turn // runs into the edge case in Bundle.kindofEquals final EditorInfo infoCopy = cloneViaParcel(TEST_EDITOR_INFO); assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } @Test public void testKindofEqualsComparesAutofillId() { final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal(); infoCopy.autofillId = new AutofillId(42); assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } @Test public void testKindofEqualsComparesFieldId() { final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal(); infoCopy.fieldId = 42; assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } @Test public void testKindofEqualsComparesMimeTypes() { final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal(); infoCopy.contentMimeTypes = new String[] {"image/png", "image/gif"}; assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } @Test public void testKindofEqualsComparesExtras() { final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal(); infoCopy.extras.putString("testKey2", "testValue"); assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy)); } }