diff options
| author | 2023-07-20 16:30:42 +0000 | |
|---|---|---|
| committer | 2023-07-20 16:30:42 +0000 | |
| commit | 0a546b89d209750af6fd26addb0494f269401362 (patch) | |
| tree | 39e46a5a41a7660f6f32d09b13ee912147c7041d | |
| parent | ba2e98cf3e899ba41669d660679d2b2c8b8d6113 (diff) | |
| parent | b4a37db4ea653ad1ff7fabab7ca61f8767aebe4c (diff) | |
Merge changes from topic "revert-24042550-revert-23844944-ime_implicit_explicit_fix-VSYYMXMNII-GLIKJUHZWS" into udc-qpr-dev am: a5fa0bed6e am: b4a37db4ea
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24130484
Change-Id: I282d7fc5c1d07e463cbe13607ae732717fcc3994
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
24 files changed, 638 insertions, 233 deletions
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 70b72c809524..b99996ff83c8 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -433,7 +433,7 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, - int flags, ResultReceiver resultReceiver) { + @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER); mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT, flags, showInputToken, resultReceiver, statsToken)); diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index e472a40617ee..44fed677318c 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -606,6 +606,7 @@ public class InputMethodService extends AbstractInputMethodService { InputConnection mStartedInputConnection; EditorInfo mInputEditorInfo; + @InputMethod.ShowFlags int mShowInputFlags; boolean mShowInputRequested; boolean mLastShowInputRequested; @@ -930,8 +931,9 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver, - IBinder showInputToken, @Nullable ImeTracker.Token statsToken) { + public void showSoftInputWithToken(@InputMethod.ShowFlags int flags, + ResultReceiver resultReceiver, IBinder showInputToken, + @Nullable ImeTracker.Token statsToken) { mSystemCallingShowSoftInput = true; mCurShowInputToken = showInputToken; mCurStatsToken = statsToken; @@ -949,7 +951,7 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public void showSoftInput(int flags, ResultReceiver resultReceiver) { + public void showSoftInput(@InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) { ImeTracker.forLogging().onProgress( mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT); if (DEBUG) Log.v(TAG, "showSoftInput()"); @@ -1325,7 +1327,8 @@ public class InputMethodService extends AbstractInputMethodService { * InputMethodService#requestShowSelf} or {@link InputMethodService#requestHideSelf} */ @Deprecated - public void toggleSoftInput(int showFlags, int hideFlags) { + public void toggleSoftInput(@InputMethodManager.ShowFlags int showFlags, + @InputMethodManager.HideFlags int hideFlags) { InputMethodService.this.onToggleSoftInput(showFlags, hideFlags); } @@ -2797,18 +2800,16 @@ public class InputMethodService extends AbstractInputMethodService { * {@link #onEvaluateInputViewShown()}, {@link #onEvaluateFullscreenMode()}, * and the current configuration to decide whether the input view should * be shown at this point. - * - * @param flags Provides additional information about the show request, - * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}. + * * @param configChange This is true if we are re-showing due to a * configuration change. * @return Returns true to indicate that the window should be shown. */ - public boolean onShowInputRequested(int flags, boolean configChange) { + public boolean onShowInputRequested(@InputMethod.ShowFlags int flags, boolean configChange) { if (!onEvaluateInputViewShown()) { return false; } - if ((flags&InputMethod.SHOW_EXPLICIT) == 0) { + if ((flags & InputMethod.SHOW_EXPLICIT) == 0) { if (!configChange && onEvaluateFullscreenMode() && !isInputViewShown()) { // Don't show if this is not explicitly requested by the user and // the input method is fullscreen unless it is already shown. That @@ -2834,14 +2835,14 @@ public class InputMethodService extends AbstractInputMethodService { * exposed to IME authors as an overridable public method without {@code @CallSuper}, we have * to have this method to ensure that those internal states are always updated no matter how * {@link #onShowInputRequested(int, boolean)} is overridden by the IME author. - * @param flags Provides additional information about the show request, - * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}. + * * @param configChange This is true if we are re-showing due to a * configuration change. * @return Returns true to indicate that the window should be shown. * @see #onShowInputRequested(int, boolean) */ - private boolean dispatchOnShowInputRequested(int flags, boolean configChange) { + private boolean dispatchOnShowInputRequested(@InputMethod.ShowFlags int flags, + boolean configChange) { final boolean result = onShowInputRequested(flags, configChange); mInlineSuggestionSessionController.notifyOnShowInputRequested(result); if (result) { @@ -3274,16 +3275,13 @@ public class InputMethodService extends AbstractInputMethodService { * * The input method will continue running, but the user can no longer use it to generate input * by touching the screen. - * - * @see InputMethodManager#HIDE_IMPLICIT_ONLY - * @see InputMethodManager#HIDE_NOT_ALWAYS - * @param flags Provides additional operating flags. */ - public void requestHideSelf(int flags) { + public void requestHideSelf(@InputMethodManager.HideFlags int flags) { requestHideSelf(flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME); } - private void requestHideSelf(int flags, @SoftInputShowHideReason int reason) { + private void requestHideSelf(@InputMethodManager.HideFlags int flags, + @SoftInputShowHideReason int reason) { ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestHideSelf", mDumper, null /* icProto */); mPrivOps.hideMySoftInput(flags, reason); @@ -3292,12 +3290,8 @@ public class InputMethodService extends AbstractInputMethodService { /** * Show the input method's soft input area, so the user sees the input method window and can * interact with it. - * - * @see InputMethodManager#SHOW_IMPLICIT - * @see InputMethodManager#SHOW_FORCED - * @param flags Provides additional operating flags. */ - public final void requestShowSelf(int flags) { + public final void requestShowSelf(@InputMethodManager.ShowFlags int flags) { ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestShowSelf", mDumper, null /* icProto */); mPrivOps.showMySoftInput(flags); @@ -3457,7 +3451,8 @@ public class InputMethodService extends AbstractInputMethodService { /** * Handle a request by the system to toggle the soft input area. */ - private void onToggleSoftInput(int showFlags, int hideFlags) { + private void onToggleSoftInput(@InputMethodManager.ShowFlags int showFlags, + @InputMethodManager.HideFlags int hideFlags) { if (DEBUG) Log.v(TAG, "toggleSoftInput()"); if (isInputViewShown()) { requestHideSelf( diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index ce2c18080b91..467daa028afd 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -295,8 +295,8 @@ final class IInputMethodManagerGlobalInvoker { @AnyThread static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken, - @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType, - @Nullable ResultReceiver resultReceiver, + @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, + int lastClickToolType, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodManager service = getService(); if (service == null) { @@ -312,7 +312,7 @@ final class IInputMethodManagerGlobalInvoker { @AnyThread static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken, - @Nullable ImeTracker.Token statsToken, int flags, + @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodManager service = getService(); if (service == null) { diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 6340388b5d5b..5b4efd8ced85 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -17,6 +17,7 @@ package android.view.inputmethod; import android.annotation.DurationMillisLong; +import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,6 +37,8 @@ import com.android.internal.inputmethod.IInputMethod; import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; import com.android.internal.inputmethod.InputMethodNavButtonFlags; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -269,6 +272,14 @@ public interface InputMethod { */ @MainThread public void revokeSession(InputMethodSession session); + + /** @hide */ + @IntDef(flag = true, prefix = { "SHOW_" }, value = { + SHOW_EXPLICIT, + SHOW_FORCED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface ShowFlags {} /** * Flag for {@link #showSoftInput}: this show has been explicitly @@ -292,8 +303,6 @@ public interface InputMethod { /** * Request that any soft input part of the input method be shown to the user. * - * @param flags Provides additional information about the show request. - * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set. * @param resultReceiver The client requesting the show may wish to * be told the impact of their request, which should be supplied here. * The result code should be @@ -308,7 +317,7 @@ public interface InputMethod { * @hide */ @MainThread - public default void showSoftInputWithToken(int flags, ResultReceiver resultReceiver, + public default void showSoftInputWithToken(@ShowFlags int flags, ResultReceiver resultReceiver, IBinder showInputToken, @Nullable ImeTracker.Token statsToken) { showSoftInput(flags, resultReceiver); } @@ -316,8 +325,6 @@ public interface InputMethod { /** * Request that any soft input part of the input method be shown to the user. * - * @param flags Provides additional information about the show request. - * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set. * @param resultReceiver The client requesting the show may wish to * be told the impact of their request, which should be supplied here. * The result code should be @@ -327,11 +334,12 @@ public interface InputMethod { * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}. */ @MainThread - public void showSoftInput(int flags, ResultReceiver resultReceiver); + public void showSoftInput(@ShowFlags int flags, ResultReceiver resultReceiver); /** * Request that any soft input part of the input method be hidden from the user. - * @param flags Provides additional information about the show request. + * + * @param flags Provides additional information about the hide request. * Currently always 0. * @param resultReceiver The client requesting the show may wish to * be told the impact of their request, which should be supplied here. @@ -354,7 +362,8 @@ public interface InputMethod { /** * Request that any soft input part of the input method be hidden from the user. - * @param flags Provides additional information about the show request. + * + * @param flags Provides additional information about the hide request. * Currently always 0. * @param resultReceiver The client requesting the show may wish to * be told the impact of their request, which should be supplied here. diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 48bf973db115..e4cc6d550cc3 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -39,6 +39,7 @@ import android.Manifest; import android.annotation.DisplayContext; import android.annotation.DrawableRes; import android.annotation.DurationMillisLong; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; @@ -122,6 +123,8 @@ import com.android.internal.view.IInputMethodManager; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collections; @@ -2034,6 +2037,14 @@ public final class InputMethodManager { } } + /** @hide */ + @IntDef(flag = true, prefix = { "SHOW_" }, value = { + SHOW_IMPLICIT, + SHOW_FORCED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ShowFlags {} + /** * Flag for {@link #showSoftInput} to indicate that this is an implicit * request to show the input window, not as the result of a direct request @@ -2065,10 +2076,8 @@ public final class InputMethodManager { * {@link View#isFocused view focus}, and its containing window has * {@link View#hasWindowFocus window focus}. Otherwise the call fails and * returns {@code false}. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link #SHOW_IMPLICIT} bit set. */ - public boolean showSoftInput(View view, int flags) { + public boolean showSoftInput(View view, @ShowFlags int flags) { // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { @@ -2131,21 +2140,20 @@ public final class InputMethodManager { * {@link View#isFocused view focus}, and its containing window has * {@link View#hasWindowFocus window focus}. Otherwise the call fails and * returns {@code false}. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link #SHOW_IMPLICIT} bit set. * @param resultReceiver If non-null, this will be called by the IME when * it has processed your request to tell you what it has done. The result * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN}, * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or * {@link #RESULT_HIDDEN}. */ - public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) { + public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) { return showSoftInput(view, null /* statsToken */, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT); } - private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags, - ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, + @ShowFlags int flags, ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) { if (statsToken == null) { statsToken = ImeTracker.forLogging().onRequestShow(null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason); @@ -2199,7 +2207,7 @@ public final class InputMethodManager { */ @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499) - public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) { + public void showSoftInputUnchecked(@ShowFlags int flags, ResultReceiver resultReceiver) { synchronized (mH) { final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow( null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, @@ -2230,6 +2238,14 @@ public final class InputMethodManager { } } + /** @hide */ + @IntDef(flag = true, prefix = { "HIDE_" }, value = { + HIDE_IMPLICIT_ONLY, + HIDE_NOT_ALWAYS, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HideFlags {} + /** * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)} * to indicate that the soft input window should only be hidden if it was not explicitly shown @@ -2251,10 +2267,8 @@ public final class InputMethodManager { * * @param windowToken The token of the window that is making the request, * as returned by {@link View#getWindowToken() View.getWindowToken()}. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set. */ - public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) { + public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags) { return hideSoftInputFromWindow(windowToken, flags, null); } @@ -2276,21 +2290,19 @@ public final class InputMethodManager { * * @param windowToken The token of the window that is making the request, * as returned by {@link View#getWindowToken() View.getWindowToken()}. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set. * @param resultReceiver If non-null, this will be called by the IME when * it has processed your request to tell you what it has done. The result * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN}, * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or * {@link #RESULT_HIDDEN}. */ - public boolean hideSoftInputFromWindow(IBinder windowToken, int flags, + public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags, ResultReceiver resultReceiver) { return hideSoftInputFromWindow(windowToken, flags, resultReceiver, SoftInputShowHideReason.HIDE_SOFT_INPUT); } - private boolean hideSoftInputFromWindow(IBinder windowToken, int flags, + private boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide( null /* component */, Process.myUid(), @@ -2493,12 +2505,6 @@ public final class InputMethodManager { * If not the input window will be displayed. * @param windowToken The token of the window that is making the request, * as returned by {@link View#getWindowToken() View.getWindowToken()}. - * @param showFlags Provides additional operating flags. May be - * 0 or have the {@link #SHOW_IMPLICIT}, - * {@link #SHOW_FORCED} bit set. - * @param hideFlags Provides additional operating flags. May be - * 0 or have the {@link #HIDE_IMPLICIT_ONLY}, - * {@link #HIDE_NOT_ALWAYS} bit set. * * @deprecated Use {@link #showSoftInput(View, int)} or * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead. @@ -2507,7 +2513,8 @@ public final class InputMethodManager { * has an effect if the calling app is the current IME focus. */ @Deprecated - public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) { + public void toggleSoftInputFromWindow(IBinder windowToken, @ShowFlags int showFlags, + @HideFlags int hideFlags) { ImeTracing.getInstance().triggerClientDump( "InputMethodManager#toggleSoftInputFromWindow", InputMethodManager.this, null /* icProto */); @@ -2525,12 +2532,6 @@ public final class InputMethodManager { * * If the input window is already displayed, it gets hidden. * If not the input window will be displayed. - * @param showFlags Provides additional operating flags. May be - * 0 or have the {@link #SHOW_IMPLICIT}, - * {@link #SHOW_FORCED} bit set. - * @param hideFlags Provides additional operating flags. May be - * 0 or have the {@link #HIDE_IMPLICIT_ONLY}, - * {@link #HIDE_NOT_ALWAYS} bit set. * * @deprecated Use {@link #showSoftInput(View, int)} or * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead. @@ -2539,7 +2540,7 @@ public final class InputMethodManager { * has an effect if the calling app is the current IME focus. */ @Deprecated - public void toggleSoftInput(int showFlags, int hideFlags) { + public void toggleSoftInput(@ShowFlags int showFlags, @HideFlags int hideFlags) { ImeTracing.getInstance().triggerClientDump( "InputMethodManager#toggleSoftInput", InputMethodManager.this, null /* icProto */); @@ -3552,15 +3553,12 @@ public final class InputMethodManager { * @param token Supplies the identifying token given to an input method * when it was started, which allows it to perform this operation on * itself. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link #HIDE_IMPLICIT_ONLY}, - * {@link #HIDE_NOT_ALWAYS} bit set. * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was * intended for IME developers who should be accessing APIs through the service. APIs in this * class are intended for app developers interacting with the IME. */ @Deprecated - public void hideSoftInputFromInputMethod(IBinder token, int flags) { + public void hideSoftInputFromInputMethod(IBinder token, @HideFlags int flags) { InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput( flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION); } @@ -3574,15 +3572,12 @@ public final class InputMethodManager { * @param token Supplies the identifying token given to an input method * when it was started, which allows it to perform this operation on * itself. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link #SHOW_IMPLICIT} or - * {@link #SHOW_FORCED} bit set. * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was * intended for IME developers who should be accessing APIs through the service. APIs in this * class are intended for app developers interacting with the IME. */ @Deprecated - public void showSoftInputFromInputMethod(IBinder token, int flags) { + public void showSoftInputFromInputMethod(IBinder token, @ShowFlags int flags) { InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(flags); } diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java index af6af14472db..4f48cb684e8c 100644 --- a/core/java/android/view/inputmethod/InputMethodSession.java +++ b/core/java/android/view/inputmethod/InputMethodSession.java @@ -169,12 +169,6 @@ public interface InputMethodSession { /** * Toggle the soft input window. * Applications can toggle the state of the soft input window. - * @param showFlags Provides additional operating flags. May be - * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT}, - * {@link InputMethodManager#SHOW_FORCED} bit set. - * @param hideFlags Provides additional operating flags. May be - * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY}, - * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set. * * @deprecated Starting in {@link android.os.Build.VERSION_CODES#S} the system no longer invokes * this method, instead it explicitly shows or hides the IME. An {@code InputMethodService} @@ -182,7 +176,8 @@ public interface InputMethodSession { * InputMethodService#requestShowSelf} or {@link InputMethodService#requestHideSelf} */ @Deprecated - public void toggleSoftInput(int showFlags, int hideFlags); + public void toggleSoftInput(@InputMethodManager.ShowFlags int showFlags, + @InputMethodManager.HideFlags int hideFlags); /** * This method is called when the cursor and/or the character position relevant to text input diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 66e3333acf7c..8a5c7ef18621 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -26,6 +26,7 @@ import android.os.RemoteException; import android.util.Log; import android.view.View; import android.view.inputmethod.ImeTracker; +import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.GuardedBy; @@ -253,13 +254,11 @@ public final class InputMethodPrivilegedOperations { /** * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)} * - * @param flags additional operating flags * @param reason the reason to hide soft input - * @see android.view.inputmethod.InputMethodManager#HIDE_IMPLICIT_ONLY - * @see android.view.inputmethod.InputMethodManager#HIDE_NOT_ALWAYS */ @AnyThread - public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason) { + public void hideMySoftInput(@InputMethodManager.HideFlags int flags, + @SoftInputShowHideReason int reason) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { return; @@ -275,13 +274,9 @@ public final class InputMethodPrivilegedOperations { /** * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int, AndroidFuture)} - * - * @param flags additional operating flags - * @see android.view.inputmethod.InputMethodManager#SHOW_IMPLICIT - * @see android.view.inputmethod.InputMethodManager#SHOW_FORCED */ @AnyThread - public void showMySoftInput(int flags) { + public void showMySoftInput(@InputMethodManager.ShowFlags int flags) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { return; diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java index a1b67e105dd4..f1698dd0f025 100644 --- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java @@ -37,6 +37,7 @@ import android.os.ResultReceiver; import android.util.EventLog; import android.util.Slog; import android.view.inputmethod.ImeTracker; +import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.GuardedBy; @@ -75,7 +76,8 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override public void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, - int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) { final IInputMethodInvoker curMethod = mService.getCurMethodLocked(); if (curMethod != null) { if (DEBUG) { diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index c53f1a52306d..b12a816738da 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -30,6 +30,7 @@ import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputBinding; +import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodSubtype; import android.window.ImeOnBackInvokedDispatcher; @@ -198,8 +199,8 @@ final class IInputMethodInvoker { // TODO(b/192412909): Convert this back to void method @AnyThread - boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, int flags, - ResultReceiver resultReceiver) { + boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, + @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) { try { mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java index 27f6a89a73b3..29fa36982351 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.os.IBinder; import android.os.ResultReceiver; import android.view.inputmethod.ImeTracker; +import android.view.inputmethod.InputMethod; import com.android.internal.inputmethod.SoftInputShowHideReason; @@ -34,13 +35,13 @@ interface ImeVisibilityApplier { * * @param showInputToken A token that represents the requester to show IME. * @param statsToken A token that tracks the progress of an IME request. - * @param showFlags Provides additional operating flags to show IME. * @param resultReceiver If non-null, this will be called back to the caller when * it has processed request to tell what it has done. * @param reason The reason for requesting to show IME. */ default void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, - int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {} + @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) {} /** * Performs hiding IME to the given window diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java index f012d917b05e..9ad4628596fc 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java @@ -221,17 +221,21 @@ public final class ImeVisibilityStateComputer { /** * Called when {@link InputMethodManagerService} is processing the show IME request. - * @param statsToken The token for tracking this show request - * @param showFlags The additional operation flags to indicate whether this show request mode is - * implicit or explicit. - * @return {@code true} when the computer has proceed this show request operation. + * + * @param statsToken The token for tracking this show request. + * @return {@code true} when the show request can proceed. */ - boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, int showFlags) { + boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, + @InputMethodManager.ShowFlags int showFlags) { if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY); return false; } ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY); + // We only "set" the state corresponding to the flags, as this will be reset + // in clearImeShowFlags during a hide request. + // Thus, we keep the strongest values set (e.g. an implicit show right after + // an explicit show will still be considered explicit, likewise for forced). if ((showFlags & InputMethodManager.SHOW_FORCED) != 0) { mRequestedShowExplicitly = true; mShowForced = true; @@ -243,12 +247,12 @@ public final class ImeVisibilityStateComputer { /** * Called when {@link InputMethodManagerService} is processing the hide IME request. - * @param statsToken The token for tracking this hide request - * @param hideFlags The additional operation flags to indicate whether this hide request mode is - * implicit or explicit. - * @return {@code true} when the computer has proceed this hide request operations. + * + * @param statsToken The token for tracking this hide request. + * @return {@code true} when the hide request can proceed. */ - boolean canHideIme(@NonNull ImeTracker.Token statsToken, int hideFlags) { + boolean canHideIme(@NonNull ImeTracker.Token statsToken, + @InputMethodManager.HideFlags int hideFlags) { if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 && (mRequestedShowExplicitly || mShowForced)) { if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide"); @@ -264,13 +268,31 @@ public final class ImeVisibilityStateComputer { return true; } - int getImeShowFlags() { + /** + * Returns the show flags for IME. This translates from {@link InputMethodManager.ShowFlags} + * to {@link InputMethod.ShowFlags}. + */ + @InputMethod.ShowFlags + int getShowFlagsForInputMethodServiceOnly() { int flags = 0; if (mShowForced) { flags |= InputMethod.SHOW_FORCED | InputMethod.SHOW_EXPLICIT; } else if (mRequestedShowExplicitly) { flags |= InputMethod.SHOW_EXPLICIT; - } else { + } + return flags; + } + + /** + * Returns the show flags for IMM. This translates from {@link InputMethod.ShowFlags} + * to {@link InputMethodManager.ShowFlags}. + */ + @InputMethodManager.ShowFlags + int getShowFlags() { + int flags = 0; + if (mShowForced) { + flags |= InputMethodManager.SHOW_FORCED; + } else if (!mRequestedShowExplicitly) { flags |= InputMethodManager.SHOW_IMPLICIT; } return flags; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 7bda2c1fa5ab..c5fbcb968ab6 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2468,7 +2468,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final ImeTracker.Token statsToken = mCurStatsToken; mCurStatsToken = null; showCurrentInputLocked(mCurFocusedWindow, statsToken, - mVisibilityStateComputer.getImeShowFlags(), + mVisibilityStateComputer.getShowFlags(), null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT); } @@ -3404,8 +3404,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, - @Nullable ImeTracker.Token statsToken, int flags, int lastClickTooType, - ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, + int lastClickTooType, ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput"); int uid = Binder.getCallingUid(); ImeTracing.getInstance().triggerManagerServiceDump( @@ -3578,15 +3579,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken, - int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + @InputMethodManager.ShowFlags int flags, ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) { return showCurrentInputLocked(windowToken, statsToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason); } @GuardedBy("ImfLock.class") private boolean showCurrentInputLocked(IBinder windowToken, - @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType, - ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, + int lastClickToolType, ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) { // Create statsToken is none exists. if (statsToken == null) { statsToken = createStatsTokenForFocusedClient(true /* show */, @@ -3617,7 +3620,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub curMethod.updateEditorToolType(lastClickToolType); } mVisibilityApplier.performShowIme(windowToken, statsToken, - mVisibilityStateComputer.getImeShowFlags(), resultReceiver, reason); + mVisibilityStateComputer.getShowFlagsForInputMethodServiceOnly(), + resultReceiver, reason); mVisibilityStateComputer.setInputShown(true); return true; } else { @@ -3629,8 +3633,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, - @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) { + @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, + ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { int uid = Binder.getCallingUid(); ImeTracing.getInstance().triggerManagerServiceDump( "InputMethodManagerService#hideSoftInput"); @@ -3660,7 +3664,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken, - int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) { // Create statsToken is none exists. if (statsToken == null) { statsToken = createStatsTokenForFocusedClient(false /* show */, @@ -4847,7 +4852,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - private void hideMySoftInput(@NonNull IBinder token, int flags, + private void hideMySoftInput(@NonNull IBinder token, @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput"); @@ -4869,7 +4874,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - private void showMySoftInput(@NonNull IBinder token, int flags) { + private void showMySoftInput(@NonNull IBinder token, @InputMethodManager.ShowFlags int flags) { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput"); synchronized (ImfLock.class) { @@ -6828,8 +6833,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason, - AndroidFuture future /* T=Void */) { + public void hideMySoftInput(@InputMethodManager.HideFlags int flags, + @SoftInputShowHideReason int reason, AndroidFuture future /* T=Void */) { @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { @@ -6842,7 +6847,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void showMySoftInput(int flags, AndroidFuture future /* T=Void */) { + public void showMySoftInput(@InputMethodManager.ShowFlags int flags, + AndroidFuture future /* T=Void */) { @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml index 212ec14b4939..bef56cec3385 100644 --- a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml +++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml @@ -17,7 +17,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.frameworks.inputmethodtests"> - <uses-sdk android:targetSdkVersion="31" /> <queries> <intent> <action android:name="android.view.InputMethod" /> diff --git a/services/tests/InputMethodSystemServerTests/TEST_MAPPING b/services/tests/InputMethodSystemServerTests/TEST_MAPPING index 77e32a776d57..cedbfd2b2dde 100644 --- a/services/tests/InputMethodSystemServerTests/TEST_MAPPING +++ b/services/tests/InputMethodSystemServerTests/TEST_MAPPING @@ -9,5 +9,16 @@ {"exclude-annotation": "org.junit.Ignore"} ] } + ], + "postsubmit": [ + { + "name": "FrameworksImeTests", + "options": [ + {"include-filter": "com.android.inputmethodservice"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + } ] } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml index 0104f7142bea..b7de74987eb8 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml +++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml @@ -18,8 +18,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.inputmethod.imetests"> - <uses-sdk android:targetSdkVersion="31" /> - <!-- Permissions required for granting and logging --> <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java index 898658e759c0..e8acb067f625 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java @@ -20,6 +20,8 @@ import static com.android.compatibility.common.util.SystemUtil.eventually; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + import android.app.Instrumentation; import android.content.Context; import android.content.res.Configuration; @@ -45,6 +47,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -56,9 +59,9 @@ public class InputMethodServiceTest { private static final String EDIT_TEXT_DESC = "Input box"; private static final long TIMEOUT_IN_SECONDS = 3; private static final String ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD = - "settings put secure show_ime_with_hard_keyboard 1"; + "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 1"; private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD = - "settings put secure show_ime_with_hard_keyboard 0"; + "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 0"; private Instrumentation mInstrumentation; private UiDevice mUiDevice; @@ -82,29 +85,19 @@ public class InputMethodServiceTest { mUiDevice.freezeRotation(); mUiDevice.setOrientationNatural(); // Waits for input binding ready. - eventually( - () -> { - mInputMethodService = - InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting(); - assertThat(mInputMethodService).isNotNull(); - - // The editor won't bring up keyboard by default. - assertThat(mInputMethodService.getCurrentInputStarted()).isTrue(); - assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse(); - }); - // Save the original value of show_ime_with_hard_keyboard in Settings. + eventually(() -> { + mInputMethodService = + InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting(); + assertThat(mInputMethodService).isNotNull(); + + // The editor won't bring up keyboard by default. + assertThat(mInputMethodService.getCurrentInputStarted()).isTrue(); + assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse(); + }); + // Save the original value of show_ime_with_hard_keyboard from Settings. mShowImeWithHardKeyboardEnabled = Settings.Secure.getInt( mInputMethodService.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0; - // Disable showing Ime with hard keyboard because it is the precondition the for most test - // cases - if (mShowImeWithHardKeyboardEnabled) { - executeShellCommand(DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD); - } - mInputMethodService.getResources().getConfiguration().keyboard = - Configuration.KEYBOARD_NOKEYS; - mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = - Configuration.HARDKEYBOARDHIDDEN_YES; } @After @@ -112,82 +105,141 @@ public class InputMethodServiceTest { mUiDevice.unfreezeRotation(); executeShellCommand("ime disable " + mInputMethodId); // Change back the original value of show_ime_with_hard_keyboard in Settings. - executeShellCommand(mShowImeWithHardKeyboardEnabled ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD + executeShellCommand(mShowImeWithHardKeyboardEnabled + ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD); } + /** + * This checks that the IME can be shown and hidden by user actions + * (i.e. tapping on an EditText, tapping the Home button). + */ @Test - public void testShowHideKeyboard_byUserAction() throws InterruptedException { + public void testShowHideKeyboard_byUserAction() throws Exception { + setShowImeWithHardKeyboard(true /* enabled */); + // Performs click on editor box to bring up the soft keyboard. Log.i(TAG, "Click on EditText."); - verifyInputViewStatus(() -> clickOnEditorText(), true /* inputViewStarted */); + verifyInputViewStatus( + () -> clickOnEditorText(), + true /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); - // Press back key to hide soft keyboard. - Log.i(TAG, "Press back"); + // Press home key to hide soft keyboard. + Log.i(TAG, "Press home"); verifyInputViewStatus( - () -> assertThat(mUiDevice.pressHome()).isTrue(), false /* inputViewStarted */); + () -> assertThat(mUiDevice.pressHome()).isTrue(), + true /* expected */, + false /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isFalse(); } + /** + * This checks that the IME can be shown and hidden using the WindowInsetsController APIs. + */ @Test - public void testShowHideKeyboard_byApi() throws InterruptedException { + public void testShowHideKeyboard_byApi() throws Exception { + setShowImeWithHardKeyboard(true /* enabled */); + // Triggers to show IME via public API. verifyInputViewStatus( () -> assertThat(mActivity.showImeWithWindowInsetsController()).isTrue(), + true /* expected */, true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); // Triggers to hide IME via public API. verifyInputViewStatusOnMainSync( - () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(), + () -> assertThat(mActivity.hideImeWithWindowInsetsController()).isTrue(), + true /* expected */, false /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isFalse(); } + /** + * This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf. + */ @Test - public void testShowHideSelf() throws InterruptedException { - // IME requests to show itself without any flags: expect shown. + public void testShowHideSelf() throws Exception { + setShowImeWithHardKeyboard(true /* enabled */); + + // IME request to show itself without any flags, expect shown. Log.i(TAG, "Call IMS#requestShowSelf(0)"); verifyInputViewStatusOnMainSync( - () -> mInputMethodService.requestShowSelf(0), true /* inputViewStarted */); + () -> mInputMethodService.requestShowSelf(0 /* flags */), + true /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); - // IME requests to hide itself with flag: HIDE_IMPLICIT_ONLY, expect not hide (shown). + // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect not hide (shown). Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)"); verifyInputViewStatusOnMainSync( () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY), + false /* expected */, true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); - // IME request to hide itself without any flags: expect hidden. + // IME request to hide itself without any flags, expect hidden. Log.i(TAG, "Call IMS#requestHideSelf(0)"); verifyInputViewStatusOnMainSync( - () -> mInputMethodService.requestHideSelf(0), false /* inputViewStarted */); + () -> mInputMethodService.requestHideSelf(0 /* flags */), + true /* expected */, + false /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isFalse(); - // IME request to show itself with flag SHOW_IMPLICIT: expect shown. + // IME request to show itself with flag SHOW_IMPLICIT, expect shown. Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)"); verifyInputViewStatusOnMainSync( () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT), + true /* expected */, true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); - // IME request to hide itself with flag: HIDE_IMPLICIT_ONLY, expect hidden. + // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect hidden. Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)"); verifyInputViewStatusOnMainSync( () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY), + true /* expected */, false /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isFalse(); } + /** + * This checks the return value of IMS#onEvaluateInputViewShown, + * when show_ime_with_hard_keyboard is enabled. + */ @Test public void testOnEvaluateInputViewShown_showImeWithHardKeyboard() throws Exception { - executeShellCommand(ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD); - mInstrumentation.waitForIdleSync(); + setShowImeWithHardKeyboard(true /* enabled */); - // Simulate connecting a hard keyboard mInputMethodService.getResources().getConfiguration().keyboard = Configuration.KEYBOARD_QWERTY; mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; + eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue()); + + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_NOKEYS; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_NO; + eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue()); + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_QWERTY; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_YES; eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue()); } + /** + * This checks the return value of IMSonEvaluateInputViewShown, + * when show_ime_with_hard_keyboard is disabled. + */ @Test - public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() { + public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() throws Exception { + setShowImeWithHardKeyboard(false /* enabled */); + mInputMethodService.getResources().getConfiguration().keyboard = Configuration.KEYBOARD_QWERTY; mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = @@ -196,6 +248,8 @@ public class InputMethodServiceTest { mInputMethodService.getResources().getConfiguration().keyboard = Configuration.KEYBOARD_NOKEYS; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_NO; eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue()); mInputMethodService.getResources().getConfiguration().keyboard = @@ -205,149 +259,386 @@ public class InputMethodServiceTest { eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue()); } + /** + * This checks that any (implicit or explicit) show request, + * when IMS#onEvaluateInputViewShown returns false, results in the IME not being shown. + */ @Test public void testShowSoftInput_disableShowImeWithHardKeyboard() throws Exception { - // Simulate connecting a hard keyboard + setShowImeWithHardKeyboard(false /* enabled */); + + // Simulate connecting a hard keyboard. mInputMethodService.getResources().getConfiguration().keyboard = Configuration.KEYBOARD_QWERTY; mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; + // When InputMethodService#onEvaluateInputViewShown() returns false, the Ime should not be // shown no matter what the show flag is. verifyInputViewStatusOnMainSync(() -> assertThat( mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), + false /* expected */, false /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isFalse(); + verifyInputViewStatusOnMainSync( () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), + false /* expected */, false /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isFalse(); } + /** + * This checks that an explicit show request results in the IME being shown. + */ @Test public void testShowSoftInputExplicitly() throws Exception { + setShowImeWithHardKeyboard(true /* enabled */); + // When InputMethodService#onEvaluateInputViewShown() returns true and flag is EXPLICIT, the // Ime should be shown. verifyInputViewStatusOnMainSync( () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), + true /* expected */, true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); } + /** + * This checks that an implicit show request results in the IME being shown. + */ @Test public void testShowSoftInputImplicitly() throws Exception { - // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT, the - // Ime should be shown. + setShowImeWithHardKeyboard(true /* enabled */); + + // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT, + // the IME should be shown. verifyInputViewStatusOnMainSync(() -> assertThat( mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), + true /* expected */, true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); } + /** + * This checks that an explicit show request when the IME is not previously shown, + * and it should be shown in fullscreen mode, results in the IME being shown. + */ @Test - public void testShowSoftInputImplicitly_fullScreenMode() throws Exception { - // When keyboard is off, InputMethodService#onEvaluateInputViewShown returns true, flag is - // IMPLICIT and InputMethodService#onEvaluateFullScreenMode returns true, the Ime should not - // be shown. + public void testShowSoftInputExplicitly_fullScreenMode() throws Exception { + setShowImeWithHardKeyboard(true /* enabled */); + + // Set orientation landscape to enable fullscreen mode. setOrientation(2); eventually(() -> assertThat(mUiDevice.isNaturalOrientation()).isFalse()); - // Wait for the TestActivity to be recreated + // Wait for the TestActivity to be recreated. eventually(() -> assertThat(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity)); - // Get the new TestActivity + // Get the new TestActivity. mActivity = TestActivity.getLastCreatedInstance(); assertThat(mActivity).isNotNull(); InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); - // Wait for the new EditText to be served by InputMethodManager + // Wait for the new EditText to be served by InputMethodManager. + eventually(() -> assertThat( + imm.hasActiveInputConnection(mActivity.getEditText())).isTrue()); + + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), + true /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + } + + /** + * This checks that an implicit show request when the IME is not previously shown, + * and it should be shown in fullscreen mode, results in the IME not being shown. + */ + @Test + public void testShowSoftInputImplicitly_fullScreenMode() throws Exception { + setShowImeWithHardKeyboard(true /* enabled */); + + // Set orientation landscape to enable fullscreen mode. + setOrientation(2); + eventually(() -> assertThat(mUiDevice.isNaturalOrientation()).isFalse()); + // Wait for the TestActivity to be recreated. eventually(() -> - assertThat(imm.hasActiveInputConnection(mActivity.getEditText())).isTrue()); + assertThat(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity)); + // Get the new TestActivity. + mActivity = TestActivity.getLastCreatedInstance(); + assertThat(mActivity).isNotNull(); + InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); + // Wait for the new EditText to be served by InputMethodManager. + eventually(() -> assertThat( + imm.hasActiveInputConnection(mActivity.getEditText())).isTrue()); + verifyInputViewStatusOnMainSync(() -> assertThat( mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), + false /* expected */, false /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isFalse(); } + /** + * This checks that an explicit show request when a hard keyboard is connected, + * results in the IME being shown. + */ + @Test + public void testShowSoftInputExplicitly_withHardKeyboard() throws Exception { + setShowImeWithHardKeyboard(false /* enabled */); + + // Simulate connecting a hard keyboard. + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_QWERTY; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_YES; + + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), + true /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + } + + /** + * This checks that an implicit show request when a hard keyboard is connected, + * results in the IME not being shown. + */ @Test public void testShowSoftInputImplicitly_withHardKeyboard() throws Exception { + setShowImeWithHardKeyboard(false /* enabled */); + + // Simulate connecting a hard keyboard. mInputMethodService.getResources().getConfiguration().keyboard = Configuration.KEYBOARD_QWERTY; - // When connecting to a hard keyboard and the flag is IMPLICIT, the Ime should not be shown. + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_YES; + verifyInputViewStatusOnMainSync(() -> assertThat( mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), + false /* expected */, false /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isFalse(); } + /** + * This checks that an explicit show request followed by connecting a hard keyboard + * and a configuration change, still results in the IME being shown. + */ @Test - public void testConfigurationChanged_withKeyboardShownExplicitly() throws InterruptedException { + public void testShowSoftInputExplicitly_thenConfigurationChanged() throws Exception { + setShowImeWithHardKeyboard(false /* enabled */); + + // Start with no hard keyboard. + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_NOKEYS; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_YES; + verifyInputViewStatusOnMainSync( () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), + true /* expected */, true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + + // Simulate connecting a hard keyboard. + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_QWERTY; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_YES; + // Simulate a fake configuration change to avoid triggering the recreation of TestActivity. mInputMethodService.getResources().getConfiguration().orientation = Configuration.ORIENTATION_LANDSCAPE; + verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged( mInputMethodService.getResources().getConfiguration()), + true /* expected */, true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); } + /** + * This checks that an implicit show request followed by connecting a hard keyboard + * and a configuration change, does not trigger IMS#onFinishInputView, + * but results in the IME being hidden. + */ @Test - public void testConfigurationChanged_withKeyboardShownImplicitly() throws InterruptedException { + public void testShowSoftInputImplicitly_thenConfigurationChanged() throws Exception { + setShowImeWithHardKeyboard(false /* enabled */); + + // Start with no hard keyboard. + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_NOKEYS; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_YES; + verifyInputViewStatusOnMainSync(() -> assertThat( mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), + true /* expected */, true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + + // Simulate connecting a hard keyboard. + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_QWERTY; + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.HARDKEYBOARDHIDDEN_YES; + // Simulate a fake configuration change to avoid triggering the recreation of TestActivity. mInputMethodService.getResources().getConfiguration().orientation = Configuration.ORIENTATION_LANDSCAPE; - mInputMethodService.getResources().getConfiguration().keyboard = - Configuration.KEYBOARD_QWERTY; // Normally, IMS#onFinishInputView will be called when finishing the input view by the user. // But if IMS#hideWindow is called when receiving a new configuration change, we don't // expect that it's user-driven to finish the lifecycle of input view with // IMS#onFinishInputView, because the input view will be re-initialized according to the - // last mShowSoftRequested state. So in this case we treat the input view is still alive. + // last #mShowInputRequested state. So in this case we treat the input view as still alive. verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged( - mInputMethodService.getResources().getConfiguration()), + mInputMethodService.getResources().getConfiguration()), + true /* expected */, true /* inputViewStarted */); assertThat(mInputMethodService.isInputViewShown()).isFalse(); } - private void verifyInputViewStatus(Runnable runnable, boolean inputViewStarted) + /** + * This checks that an explicit show request directly followed by an implicit show request, + * while a hardware keyboard is connected, still results in the IME being shown + * (i.e. the implicit show request is treated as explicit). + */ + @Test + public void testShowSoftInputExplicitly_thenShowSoftInputImplicitly_withHardKeyboard() + throws Exception { + setShowImeWithHardKeyboard(false /* enabled */); + + // Simulate connecting a hard keyboard. + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_QWERTY; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_YES; + + // Explicit show request. + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), + true /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + + // Implicit show request. + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), + false /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + + // Simulate a fake configuration change to avoid triggering the recreation of TestActivity. + // This should now consider the implicit show request, but keep the state from the + // explicit show request, and thus not hide the keyboard. + verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged( + mInputMethodService.getResources().getConfiguration()), + true /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + } + + /** + * This checks that a forced show request directly followed by an explicit show request, + * and then a hide not always request, still results in the IME being shown + * (i.e. the explicit show request retains the forced state). + */ + @Test + public void testShowSoftInputForced_testShowSoftInputExplicitly_thenHideSoftInputNotAlways() + throws Exception { + setShowImeWithHardKeyboard(true /* enabled */); + + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_FORCED)).isTrue(), + true /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), + false /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + + verifyInputViewStatusOnMainSync(() -> + mActivity.hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS), + false /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + } + + /** + * This checks that the IME fullscreen mode state is updated after changing orientation. + */ + @Test + public void testFullScreenMode() throws Exception { + setShowImeWithHardKeyboard(true /* enabled */); + + Log.i(TAG, "Set orientation natural"); + verifyFullscreenMode(() -> setOrientation(0), + false /* expected */, + true /* orientationPortrait */); + + Log.i(TAG, "Set orientation left"); + verifyFullscreenMode(() -> setOrientation(1), + true /* expected */, + false /* orientationPortrait */); + + Log.i(TAG, "Set orientation right"); + verifyFullscreenMode(() -> setOrientation(2), + false /* expected */, + false /* orientationPortrait */); + } + + private void verifyInputViewStatus( + Runnable runnable, boolean expected, boolean inputViewStarted) throws InterruptedException { - verifyInputViewStatusInternal(runnable, inputViewStarted, false /*runOnMainSync*/); + verifyInputViewStatusInternal(runnable, expected, inputViewStarted, + false /* runOnMainSync */); } - private void verifyInputViewStatusOnMainSync(Runnable runnable, boolean inputViewStarted) + private void verifyInputViewStatusOnMainSync( + Runnable runnable, boolean expected, boolean inputViewStarted) throws InterruptedException { - verifyInputViewStatusInternal(runnable, inputViewStarted, true /*runOnMainSync*/); + verifyInputViewStatusInternal(runnable, expected, inputViewStarted, + true /* runOnMainSync */); } + /** + * Verifies the status of the Input View after executing the given runnable. + * + * @param runnable the runnable to execute for showing or hiding the IME. + * @param expected whether the runnable is expected to trigger the signal. + * @param inputViewStarted the expected state of the Input View after executing the runnable. + * @param runOnMainSync whether to execute the runnable on the main thread. + */ private void verifyInputViewStatusInternal( - Runnable runnable, boolean inputViewStarted, boolean runOnMainSync) + Runnable runnable, boolean expected, boolean inputViewStarted, boolean runOnMainSync) throws InterruptedException { CountDownLatch signal = new CountDownLatch(1); mInputMethodService.setCountDownLatchForTesting(signal); - // Runnable to trigger onStartInputView()/ onFinishInputView() + // Runnable to trigger onStartInputView() / onFinishInputView() / onConfigurationChanged() if (runOnMainSync) { mInstrumentation.runOnMainSync(runnable); } else { runnable.run(); } - // Waits for onStartInputView() to finish. mInstrumentation.waitForIdleSync(); - signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS); + boolean completed = signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS); + if (expected && !completed) { + fail("Timed out waiting for" + + " onStartInputView() / onFinishInputView() / onConfigurationChanged()"); + } else if (!expected && completed) { + fail("Unexpected call" + + " onStartInputView() / onFinishInputView() / onConfigurationChanged()"); + } // Input is not finished. assertThat(mInputMethodService.getCurrentInputStarted()).isTrue(); assertThat(mInputMethodService.getCurrentInputViewStarted()).isEqualTo(inputViewStarted); } - @Test - public void testFullScreenMode() throws Exception { - Log.i(TAG, "Set orientation natural"); - verifyFullscreenMode(() -> setOrientation(0), true /* orientationPortrait */); - - Log.i(TAG, "Set orientation left"); - verifyFullscreenMode(() -> setOrientation(1), false /* orientationPortrait */); - - Log.i(TAG, "Set orientation right"); - verifyFullscreenMode(() -> setOrientation(2), false /* orientationPortrait */); - } - private void setOrientation(int orientation) { // Simple wrapper for catching RemoteException. try { @@ -366,7 +657,15 @@ public class InputMethodServiceTest { } } - private void verifyFullscreenMode(Runnable runnable, boolean orientationPortrait) + /** + * Verifies the IME fullscreen mode state after executing the given runnable. + * + * @param runnable the runnable to execute for setting the orientation. + * @param expected whether the runnable is expected to trigger the signal. + * @param orientationPortrait whether the orientation is expected to be portrait. + */ + private void verifyFullscreenMode( + Runnable runnable, boolean expected, boolean orientationPortrait) throws InterruptedException { CountDownLatch signal = new CountDownLatch(1); mInputMethodService.setCountDownLatchForTesting(signal); @@ -379,7 +678,12 @@ public class InputMethodServiceTest { } // Waits for onConfigurationChanged() to finish. mInstrumentation.waitForIdleSync(); - signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS); + boolean completed = signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS); + if (expected && !completed) { + fail("Timed out waiting for onConfigurationChanged()"); + } else if (!expected && completed) { + fail("Unexpected call onConfigurationChanged()"); + } clickOnEditorText(); eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isTrue()); @@ -416,7 +720,21 @@ public class InputMethodServiceTest { return mTargetPackageName + "/" + INPUT_METHOD_SERVICE_NAME; } - private String executeShellCommand(String cmd) throws Exception { + /** + * Sets the value of show_ime_with_hard_keyboard, only if it is different to the default value. + * + * @param enabled the value to be set. + */ + private void setShowImeWithHardKeyboard(boolean enabled) throws IOException { + if (mShowImeWithHardKeyboardEnabled != enabled) { + executeShellCommand(enabled + ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD + : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD); + mInstrumentation.waitForIdleSync(); + } + } + + private String executeShellCommand(String cmd) throws IOException { Log.i(TAG, "Run command: " + cmd); return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) .executeShellCommand(cmd); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java index 869497c28def..3199e062418f 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -40,7 +40,6 @@ import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.view.Display; -import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -77,9 +76,9 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe public void testPerformShowIme() throws Exception { synchronized (ImfLock.class) { mVisibilityApplier.performShowIme(new Binder() /* showInputToken */, - null /* statsToken */, InputMethodManager.SHOW_IMPLICIT, null, SHOW_SOFT_INPUT); + null /* statsToken */, 0 /* showFlags */, null, SHOW_SOFT_INPUT); } - verifyShowSoftInput(false, true, InputMethodManager.SHOW_IMPLICIT); + verifyShowSoftInput(false, true, 0 /* showFlags */); } @Test @@ -126,7 +125,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe @Test public void testApplyImeVisibility_showImeImplicit() throws Exception { mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT); - verifyShowSoftInput(true, true, InputMethodManager.SHOW_IMPLICIT); + verifyShowSoftInput(true, true, 0 /* showFlags */); } @Test diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java index a38c1626aea1..fae5f86e4007 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java @@ -106,7 +106,7 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes @Test public void testRequestImeVisibility_showExplicit() { initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(null, 0 /* show explicit */); + boolean res = mComputer.onImeShowFlags(null, 0 /* showFlags */); mComputer.requestImeVisibility(mWindowToken, res); final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); @@ -118,6 +118,34 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes assertThat(mComputer.mRequestedShowExplicitly).isTrue(); } + /** + * This checks that the state after an explicit show request does not get reset during + * a subsequent implicit show request, without an intermediary hide request. + */ + @Test + public void testRequestImeVisibility_showExplicit_thenShowImplicit() { + initImeTargetWindowState(mWindowToken); + mComputer.onImeShowFlags(null, 0 /* showFlags */); + assertThat(mComputer.mRequestedShowExplicitly).isTrue(); + + mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); + assertThat(mComputer.mRequestedShowExplicitly).isTrue(); + } + + /** + * This checks that the state after a forced show request does not get reset during + * a subsequent explicit show request, without an intermediary hide request. + */ + @Test + public void testRequestImeVisibility_showForced_thenShowExplicit() { + initImeTargetWindowState(mWindowToken); + mComputer.onImeShowFlags(null, InputMethodManager.SHOW_FORCED); + assertThat(mComputer.mShowForced).isTrue(); + + mComputer.onImeShowFlags(null, 0 /* showFlags */); + assertThat(mComputer.mShowForced).isTrue(); + } + @Test public void testRequestImeVisibility_showImplicit_a11yNoImePolicy() { // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java index 42d373b9bf3e..e87a34ea17d7 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.inputmethod; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; @@ -162,7 +163,10 @@ public class InputMethodBindingControllerTest extends InputMethodManagerServiceT assertThat(mBindingController.getCurToken()).isNotNull(); } // Wait for onServiceConnected() - mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS); + boolean completed = mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS); + if (!completed) { + fail("Timed out waiting for onServiceConnected()"); + } // Verify onServiceConnected() is called and bound successfully. synchronized (ImfLock.class) { diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp index 8d0e0c4260e8..e1fd2b34d881 100644 --- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp +++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp @@ -43,6 +43,8 @@ android_test_helper_app { }, export_package_resources: true, sdk_version: "current", + + certificate: "platform", } android_library { diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml index 996322de2c5e..cf7d660a68ef 100644 --- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml +++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml @@ -18,8 +18,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.apps.inputmethod.simpleime"> - <uses-sdk android:targetSdkVersion="31" /> - <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application android:debuggable="true" diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java index 0c267b27490b..320daeeb2e54 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java @@ -17,8 +17,9 @@ package com.android.inputmethod.stresstest; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; +import static com.android.compatibility.common.util.SystemUtil.eventually; import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE; import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent; import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync; @@ -26,11 +27,16 @@ import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowA import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden; import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown; +import static com.google.common.truth.Truth.assertWithMessage; + import android.content.Intent; import android.platform.test.annotations.RootPermissionTest; import android.platform.test.rule.UnlockScreenRule; +import android.support.test.uiautomator.UiDevice; import android.widget.EditText; +import androidx.test.platform.app.InstrumentationRegistry; + import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,6 +45,7 @@ import org.junit.runners.Parameterized; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; /** * Test IME visibility by using system default IME to ensure the behavior is consistent @@ -59,8 +66,12 @@ public final class DefaultImeVisibilityTest { public ScreenCaptureRule mScreenCaptureRule = new ScreenCaptureRule("/sdcard/InputMethodStressTest"); + private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(3); + private static final int NUM_TEST_ITERATIONS = 10; + private final boolean mIsPortrait; + @Parameterized.Parameters(name = "isPortrait={0}") public static List<Boolean> isPortraitCases() { // Test in both portrait and landscape mode. @@ -68,6 +79,7 @@ public final class DefaultImeVisibilityTest { } public DefaultImeVisibilityTest(boolean isPortrait) { + mIsPortrait = isPortrait; mImeStressTestRule.setIsPortrait(isPortrait); } @@ -75,14 +87,26 @@ public final class DefaultImeVisibilityTest { public void showHideDefaultIme() { Intent intent = createIntent( - 0x0, /* No window focus flags */ - SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE, + 0x0 /* No window focus flags */, + SOFT_INPUT_STATE_HIDDEN | SOFT_INPUT_ADJUST_RESIZE, Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); ImeStressTestUtil.TestActivity activity = ImeStressTestUtil.TestActivity.start(intent); EditText editText = activity.getEditText(); + + UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + eventually( + () -> + assertWithMessage("Display rotation should be updated.") + .that(uiDevice.getDisplayRotation()) + .isEqualTo(mIsPortrait ? 0 : 1), + TIMEOUT); + for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { - callOnMainSync(activity::showImeWithInputMethodManager); + // TODO(b/291752364): Remove the explicit focus request once the issue with view focus + // change between fullscreen IME and actual editText is fixed. + callOnMainSync(editText::requestFocus); verifyWindowAndViewFocus(editText, true, true); + callOnMainSync(activity::showImeWithInputMethodManager); waitOnMainUntilImeIsShown(editText); callOnMainSync(activity::hideImeWithInputMethodManager); diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java index 12104b298dac..c7463218b646 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java @@ -20,9 +20,9 @@ import android.app.Instrumentation; import android.os.RemoteException; import android.support.test.uiautomator.UiDevice; +import androidx.annotation.NonNull; import androidx.test.platform.app.InstrumentationRegistry; -import org.checkerframework.checker.nullness.qual.NonNull; import org.junit.rules.TestWatcher; import org.junit.runner.Description; @@ -35,8 +35,6 @@ import java.io.IOException; public class ImeStressTestRule extends TestWatcher { private static final String LOCK_SCREEN_OFF_COMMAND = "locksettings set-disabled true"; private static final String LOCK_SCREEN_ON_COMMAND = "locksettings set-disabled false"; - private static final String SET_PORTRAIT_MODE_COMMAND = "settings put system user_rotation 0"; - private static final String SET_LANDSCAPE_MODE_COMMAND = "settings put system user_rotation 1"; private static final String SIMPLE_IME_ID = "com.android.apps.inputmethod.simpleime/.SimpleInputMethodService"; private static final String ENABLE_IME_COMMAND = "ime enable " + SIMPLE_IME_ID; @@ -44,8 +42,10 @@ public class ImeStressTestRule extends TestWatcher { private static final String DISABLE_IME_COMMAND = "ime disable " + SIMPLE_IME_ID; private static final String RESET_IME_COMMAND = "ime reset"; - @NonNull private final Instrumentation mInstrumentation; - @NonNull private final UiDevice mUiDevice; + @NonNull + private final Instrumentation mInstrumentation; + @NonNull + private final UiDevice mUiDevice; // Whether the screen orientation is set to portrait. private boolean mIsPortrait; // Whether to use a simple test Ime or system default Ime for test. @@ -105,12 +105,13 @@ public class ImeStressTestRule extends TestWatcher { private void setOrientation() { try { mUiDevice.freezeRotation(); - executeShellCommand( - mIsPortrait ? SET_PORTRAIT_MODE_COMMAND : SET_LANDSCAPE_MODE_COMMAND); - } catch (IOException e) { - throw new RuntimeException("Could not set screen orientation.", e); + if (mIsPortrait) { + mUiDevice.setOrientationNatural(); + } else { + mUiDevice.setOrientationLeft(); + } } catch (RemoteException e) { - throw new RuntimeException("Could not freeze rotation.", e); + throw new RuntimeException("Could not freeze rotation or set screen orientation.", e); } } @@ -147,7 +148,8 @@ public class ImeStressTestRule extends TestWatcher { } } - private @NonNull String executeShellCommand(@NonNull String cmd) throws IOException { + @NonNull + private String executeShellCommand(@NonNull String cmd) throws IOException { return mUiDevice.executeShellCommand(cmd); } } diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java index f3c81942ae42..c9b5c96c9920 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java @@ -392,7 +392,7 @@ public final class ImeStressTestUtil { public boolean showImeWithInputMethodManager() { boolean showResult = getInputMethodManager() - .showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT); + .showSoftInput(mEditText, 0 /* flags */); if (showResult) { Log.i(TAG, "IMM#showSoftInput successfully"); } else { @@ -404,7 +404,8 @@ public final class ImeStressTestUtil { /** Hide IME with InputMethodManager. */ public boolean hideImeWithInputMethodManager() { boolean hideResult = - getInputMethodManager().hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + getInputMethodManager() + .hideSoftInputFromWindow(mEditText.getWindowToken(), 0 /* flags */); if (hideResult) { Log.i(TAG, "IMM#hideSoftInput successfully"); } else { |