| /* |
| * 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 com.android.inputmethodservice; |
| |
| 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; |
| import android.os.RemoteException; |
| import android.provider.Settings; |
| import android.support.test.uiautomator.By; |
| import android.support.test.uiautomator.UiDevice; |
| import android.support.test.uiautomator.UiObject2; |
| import android.support.test.uiautomator.Until; |
| import android.util.Log; |
| import android.view.inputmethod.EditorInfo; |
| import android.view.inputmethod.InputMethodManager; |
| |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| import androidx.test.filters.MediumTest; |
| import androidx.test.platform.app.InstrumentationRegistry; |
| |
| import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper; |
| import com.android.apps.inputmethod.simpleime.testing.TestActivity; |
| |
| import org.junit.After; |
| 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; |
| |
| @RunWith(AndroidJUnit4.class) |
| @MediumTest |
| public class InputMethodServiceTest { |
| private static final String TAG = "SimpleIMSTest"; |
| private static final String INPUT_METHOD_SERVICE_NAME = ".SimpleInputMethodService"; |
| 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 " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 1"; |
| private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD = |
| "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 0"; |
| |
| private Instrumentation mInstrumentation; |
| private UiDevice mUiDevice; |
| private Context mContext; |
| private String mTargetPackageName; |
| private TestActivity mActivity; |
| private InputMethodServiceWrapper mInputMethodService; |
| private String mInputMethodId; |
| private boolean mShowImeWithHardKeyboardEnabled; |
| |
| @Before |
| public void setUp() throws Exception { |
| mInstrumentation = InstrumentationRegistry.getInstrumentation(); |
| mUiDevice = UiDevice.getInstance(mInstrumentation); |
| mContext = mInstrumentation.getContext(); |
| mTargetPackageName = mInstrumentation.getTargetContext().getPackageName(); |
| mInputMethodId = getInputMethodId(); |
| prepareIme(); |
| prepareEditor(); |
| mInstrumentation.waitForIdleSync(); |
| 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 from Settings. |
| mShowImeWithHardKeyboardEnabled = Settings.Secure.getInt( |
| mInputMethodService.getContentResolver(), |
| Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0; |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| 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 |
| : 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 Exception { |
| setShowImeWithHardKeyboard(true /* enabled */); |
| |
| // Performs click on editor box to bring up the soft keyboard. |
| Log.i(TAG, "Click on EditText."); |
| verifyInputViewStatus( |
| () -> clickOnEditorText(), |
| true /* expected */, |
| true /* inputViewStarted */); |
| assertThat(mInputMethodService.isInputViewShown()).isTrue(); |
| |
| // Press home key to hide soft keyboard. |
| Log.i(TAG, "Press home"); |
| verifyInputViewStatus( |
| () -> 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 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.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 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 /* flags */), |
| true /* expected */, |
| true /* inputViewStarted */); |
| assertThat(mInputMethodService.isInputViewShown()).isTrue(); |
| |
| // 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. |
| Log.i(TAG, "Call IMS#requestHideSelf(0)"); |
| verifyInputViewStatusOnMainSync( |
| () -> mInputMethodService.requestHideSelf(0 /* flags */), |
| true /* expected */, |
| false /* inputViewStarted */); |
| assertThat(mInputMethodService.isInputViewShown()).isFalse(); |
| |
| // 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. |
| 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 { |
| setShowImeWithHardKeyboard(true /* enabled */); |
| |
| 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() throws Exception { |
| setShowImeWithHardKeyboard(false /* enabled */); |
| |
| mInputMethodService.getResources().getConfiguration().keyboard = |
| Configuration.KEYBOARD_QWERTY; |
| mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = |
| Configuration.HARDKEYBOARDHIDDEN_NO; |
| eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isFalse()); |
| |
| 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 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 { |
| 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 { |
| 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 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. |
| eventually(() -> |
| 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(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(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; |
| 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 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 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; |
| |
| // 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 #mShowInputRequested state. So in this case we treat the input view as still alive. |
| verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged( |
| mInputMethodService.getResources().getConfiguration()), |
| true /* expected */, |
| true /* inputViewStarted */); |
| assertThat(mInputMethodService.isInputViewShown()).isFalse(); |
| } |
| |
| /** |
| * 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, expected, inputViewStarted, |
| false /* runOnMainSync */); |
| } |
| |
| private void verifyInputViewStatusOnMainSync( |
| Runnable runnable, boolean expected, boolean inputViewStarted) |
| throws InterruptedException { |
| 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 expected, boolean inputViewStarted, boolean runOnMainSync) |
| throws InterruptedException { |
| CountDownLatch signal = new CountDownLatch(1); |
| mInputMethodService.setCountDownLatchForTesting(signal); |
| // Runnable to trigger onStartInputView() / onFinishInputView() / onConfigurationChanged() |
| if (runOnMainSync) { |
| mInstrumentation.runOnMainSync(runnable); |
| } else { |
| runnable.run(); |
| } |
| mInstrumentation.waitForIdleSync(); |
| 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); |
| } |
| |
| private void setOrientation(int orientation) { |
| // Simple wrapper for catching RemoteException. |
| try { |
| switch (orientation) { |
| case 1: |
| mUiDevice.setOrientationLeft(); |
| break; |
| case 2: |
| mUiDevice.setOrientationRight(); |
| break; |
| default: |
| mUiDevice.setOrientationNatural(); |
| } |
| } catch (RemoteException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * 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); |
| |
| // Runnable to trigger onConfigurationChanged() |
| try { |
| runnable.run(); |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| // Waits for onConfigurationChanged() to finish. |
| mInstrumentation.waitForIdleSync(); |
| 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()); |
| |
| assertThat(mInputMethodService.getResources().getConfiguration().orientation) |
| .isEqualTo( |
| orientationPortrait |
| ? Configuration.ORIENTATION_PORTRAIT |
| : Configuration.ORIENTATION_LANDSCAPE); |
| EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo(); |
| assertThat(editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN).isEqualTo(0); |
| assertThat(editorInfo.internalImeOptions & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT) |
| .isEqualTo( |
| orientationPortrait ? EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT : 0); |
| assertThat(mInputMethodService.onEvaluateFullscreenMode()).isEqualTo(!orientationPortrait); |
| assertThat(mInputMethodService.isFullscreenMode()).isEqualTo(!orientationPortrait); |
| |
| mUiDevice.pressBack(); |
| } |
| |
| private void prepareIme() throws Exception { |
| executeShellCommand("ime enable " + mInputMethodId); |
| executeShellCommand("ime set " + mInputMethodId); |
| mInstrumentation.waitForIdleSync(); |
| Log.i(TAG, "Finish preparing IME"); |
| } |
| |
| private void prepareEditor() { |
| mActivity = TestActivity.start(mInstrumentation); |
| Log.i(TAG, "Finish preparing activity with editor."); |
| } |
| |
| private String getInputMethodId() { |
| return mTargetPackageName + "/" + INPUT_METHOD_SERVICE_NAME; |
| } |
| |
| /** |
| * 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); |
| } |
| |
| private void clickOnEditorText() { |
| // Find the editText and click it. |
| UiObject2 editTextUiObject = |
| mUiDevice.wait( |
| Until.findObject(By.desc(EDIT_TEXT_DESC)), |
| TimeUnit.SECONDS.toMillis(TIMEOUT_IN_SECONDS)); |
| assertThat(editTextUiObject).isNotNull(); |
| editTextUiObject.click(); |
| mInstrumentation.waitForIdleSync(); |
| } |
| } |