blob: e8acb067f6250bf4e804a8a274cfeb5371f8bafa [file] [log] [blame]
/*
* 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();
}
}