Merge "Add test app SimpleTestIme to test InputMethodService"

This commit is contained in:
TreeHugger Robot
2022-12-08 14:54:16 +00:00
committed by Android (Google) Code Review
18 changed files with 1379 additions and 1 deletions

View File

@@ -28,7 +28,7 @@ android_test {
],
srcs: [
"src/**/*.java",
"src/server/**/*.java",
],
static_libs: [
@@ -60,3 +60,52 @@ android_test {
enabled: false,
},
}
android_test {
name: "FrameworksImeTests",
defaults: [
"modules-utils-testable-device-config-defaults",
],
srcs: [
"src/com/android/inputmethodservice/**/*.java",
],
manifest: "src/com/android/inputmethodservice/AndroidManifest.xml",
test_config: "src/com/android/inputmethodservice/AndroidTest.xml",
static_libs: [
"androidx.test.core",
"androidx.test.runner",
"androidx.test.espresso.core",
"androidx.test.espresso.contrib",
"androidx.test.ext.truth",
"frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
"services.core",
"servicestests-core-utils",
"servicestests-utils-mockito-extended",
"truth-prebuilt",
"SimpleImeTestingLib",
"SimpleImeImsLib",
],
libs: [
"android.test.mock",
"android.test.base",
"android.test.runner",
],
data: [
":SimpleTestIme",
],
certificate: "platform",
platform_apis: true,
test_suites: ["device-tests"],
optimize: {
enabled: false,
},
}

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<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"/>
<uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
<uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
<!-- Permissions for reading system info -->
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
<!-- The "targetPackage" reference the instruments APK package, which is the FakeImeApk, while
the test package is "com.android.inputmethod.imetests" (FrameworksImeTests.apk).-->
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.apps.inputmethod.simpleime"
android:label="Frameworks IME Tests" />
</manifest>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<configuration description="Runs Frameworks IME Tests.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
<option name="test-file-name" value="FrameworksImeTests.apk" />
<option name="test-file-name" value="SimpleTestIme.apk" />
</target_preparer>
<option name="test-tag" value="FrameworksImeTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.inputmethod.imetests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
</test>
<!-- Collect the files in the dump directory for debugging -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/sdcard/FrameworksImeTests/" />
<option name="collect-on-run-ended-only" value="true" />
</metrics_collector>
</configuration>

View File

@@ -0,0 +1,276 @@
/*
* 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 android.app.Instrumentation;
import android.content.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
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 android.widget.EditText;
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.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
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;
public Instrumentation mInstrumentation;
public UiDevice mUiDevice;
public Context mContext;
public String mTargetPackageName;
public TestActivity mActivity;
public EditText mEditText;
public InputMethodServiceWrapper mInputMethodService;
public String mInputMethodId;
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mUiDevice = UiDevice.getInstance(mInstrumentation);
mContext = mInstrumentation.getContext();
mTargetPackageName = mInstrumentation.getTargetContext().getPackageName();
mInputMethodId = getInputMethodId();
prepareIme();
prepareEditor();
// 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();
});
}
@Test
public void testShowHideKeyboard_byUserAction() throws InterruptedException {
// Performs click on editor box to bring up the soft keyboard.
Log.i(TAG, "Click on EditText.");
verifyInputViewStatus(() -> clickOnEditorText(), true /* inputViewStarted */);
// Press back key to hide soft keyboard.
Log.i(TAG, "Press back");
verifyInputViewStatus(
() -> assertThat(mUiDevice.pressHome()).isTrue(), false /* inputViewStarted */);
}
@Test
public void testShowHideKeyboard_byApi() throws InterruptedException {
// Triggers to show IME via public API.
verifyInputViewStatus(
() -> assertThat(mActivity.showImeWithWindowInsetsController()).isTrue(),
true /* inputViewStarted */);
// Triggers to hide IME via public API.
// TODO(b/242838873): investigate why WIC#hide(ime()) does not work, likely related to
// triggered from IME process.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
false /* inputViewStarted */);
}
@Test
public void testShowHideSelf() throws InterruptedException {
// IME requests to show itself without any flags: expect shown.
Log.i(TAG, "Call IMS#requestShowSelf(0)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestShowSelf(0), true /* inputViewStarted */);
// IME requests 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),
true /* inputViewStarted */);
// IME request to hide itself without any flags: expect hidden.
Log.i(TAG, "Call IMS#requestHideSelf(0)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestHideSelf(0), false /* inputViewStarted */);
// 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 /* inputViewStarted */);
// 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),
false /* inputViewStarted */);
}
private void verifyInputViewStatus(Runnable runnable, boolean inputViewStarted)
throws InterruptedException {
verifyInputViewStatusInternal(runnable, inputViewStarted, false /*runOnMainSync*/);
}
private void verifyInputViewStatusOnMainSync(Runnable runnable, boolean inputViewStarted)
throws InterruptedException {
verifyInputViewStatusInternal(runnable, inputViewStarted, true /*runOnMainSync*/);
}
private void verifyInputViewStatusInternal(
Runnable runnable, boolean inputViewStarted, boolean runOnMainSync)
throws InterruptedException {
CountDownLatch signal = new CountDownLatch(1);
mInputMethodService.setCountDownLatchForTesting(signal);
// Runnable to trigger onStartInputView()/ onFinishInputView()
if (runOnMainSync) {
mInstrumentation.runOnMainSync(runnable);
} else {
runnable.run();
}
// Waits for onStartInputView() to finish.
mInstrumentation.waitForIdleSync();
signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
// 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 */);
mUiDevice.unfreezeRotation();
}
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);
}
}
private void verifyFullscreenMode(Runnable runnable, 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();
signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
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);
mEditText = mActivity.mEditText;
Log.i(TAG, "Finish preparing activity with editor.");
}
private String getInputMethodId() {
return mTargetPackageName + "/" + INPUT_METHOD_SERVICE_NAME;
}
private String executeShellCommand(String cmd) throws Exception {
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();
}
}

View File

@@ -0,0 +1,62 @@
// 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 {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
android_test_helper_app {
name: "SimpleTestIme",
srcs: [
"src/com/android/apps/inputmethod/simpleime/*.java",
],
static_libs: [
"SimpleImeImsLib",
"SimpleImeTestingLib",
],
resource_dirs: ["res"],
manifest: "AndroidManifest.xml",
dex_preopt: {
enabled: false,
},
optimize: {
enabled: false,
},
export_package_resources: true,
sdk_version: "current",
}
android_library {
name: "SimpleImeImsLib",
srcs: [
"src/com/android/apps/inputmethod/simpleime/ims/*.java",
],
sdk_version: "current",
}
android_library {
name: "SimpleImeTestingLib",
srcs: [
"src/com/android/apps/inputmethod/simpleime/testing/*.java",
],
sdk_version: "current",
}

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.apps.inputmethod.simpleime">
<uses-sdk android:targetSdkVersion="31" />
<application android:debuggable="true"
android:label="@string/app_name">
<service
android:name="com.android.apps.inputmethod.simpleime.SimpleInputMethodService"
android:label="@string/app_name"
android:directBootAware="true"
android:permission="android.permission.BIND_INPUT_METHOD"
android:exported="true">
<meta-data
android:name="android.view.im"
android:resource="@xml/method"/>
<intent-filter>
<action android:name="android.view.InputMethod"/>
</intent-filter>
</service>
<!-- This is for test only. -->
<activity android:name="com.android.apps.inputmethod.simpleime.testing.TestActivity"
android:exported="false"
android:label="TestActivity"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:noHistory="true"
android:taskAffinity="">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid
android:color="#FAFAFA" >
</solid>
<stroke
android:width="1dp"
android:color="#0F000000" >
</stroke>
<corners
android:radius="2dp" >
</corners>
</shape>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/input"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -0,0 +1,200 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/KeyboardArea">
<View style="@style/KeyboardRow.Header"/>
<LinearLayout style="@style/KeyboardRow">
<TextView
android:id="@+id/key_pos_0_0"
android:text="q"
android:tag="KEYCODE_Q"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_0_1"
android:text="w"
android:tag="KEYCODE_W"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_0_2"
android:text="e"
android:tag="KEYCODE_E"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_0_3"
android:text="r"
android:tag="KEYCODE_R"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_0_4"
android:text="t"
android:tag="KEYCODE_T"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_0_5"
android:text="y"
android:tag="KEYCODE_Y"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_0_6"
android:text="u"
android:tag="KEYCODE_U"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_0_7"
android:text="i"
android:tag="KEYCODE_I"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_0_8"
android:text="o"
android:tag="KEYCODE_O"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_0_9"
android:text="p"
android:tag="KEYCODE_P"
style="@style/SoftKey"/>
</LinearLayout>
<LinearLayout style="@style/KeyboardRow">
<TextView
android:id="@+id/key_pos_1_0"
android:text="a"
android:tag="KEYCODE_A"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_1_1"
android:text="s"
android:tag="KEYCODE_S"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_1_2"
android:text="d"
android:tag="KEYCODE_D"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_1_3"
android:text="f"
android:tag="KEYCODE_F"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_1_4"
android:text="g"
android:tag="KEYCODE_G"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_1_5"
android:text="h"
android:tag="KEYCODE_H"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_1_6"
android:text="j"
android:tag="KEYCODE_J"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_1_7"
android:text="k"
android:tag="KEYCODE_K"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_1_8"
android:text="l"
android:tag="KEYCODE_L"
style="@style/SoftKey"/>
</LinearLayout>
<LinearLayout style="@style/KeyboardRow">
<TextView
android:id="@+id/key_pos_shift"
android:text="SHI"
android:tag="KEYCODE_SHIFT"
style="@style/SoftKey.Function"/>
<TextView
android:id="@+id/key_pos_2_0"
android:text="z"
android:tag="KEYCODE_Z"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_2_1"
android:text="x"
android:tag="KEYCODE_X"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_2_2"
style="@style/SoftKey"
android:text="c"
android:tag="KEYCODE_C"/>
<TextView
android:id="@+id/key_pos_2_3"
android:text="v"
android:tag="KEYCODE_V"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_2_4"
android:text="b"
android:tag="KEYCODE_B"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_2_5"
android:text="n"
android:tag="KEYCODE_N"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_2_6"
android:text="m"
android:tag="KEYCODE_M"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_del"
android:text="DEL"
android:tag="KEYCODE_DEL"
style="@style/SoftKey.Function"/>
</LinearLayout>
<LinearLayout style="@style/KeyboardRow">
<TextView
android:id="@+id/key_pos_symbol"
android:text="TAB"
android:tag="KEYCODE_TAB"
style="@style/SoftKey.Function"/>
<TextView
android:id="@+id/key_pos_comma"
android:text=","
android:tag="KEYCODE_COMMA"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_space"
android:text="SPACE"
android:tag="KEYCODE_SPACE"
style="@style/SoftKey.Space"/>
<TextView
android:id="@+id/key_pos_period"
android:text="."
android:tag="KEYCODE_PERIOD"
style="@style/SoftKey"/>
<TextView
android:id="@+id/key_pos_enter"
android:text="ENT"
android:tag="KEYCODE_ENTER"
style="@style/SoftKey.Function.Bottom"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<dimen name="text_size_normal">24dp</dimen>
<dimen name="text_size_symbol">14dp</dimen>
<dimen name="keyboard_header_height">40dp</dimen>
<dimen name="keyboard_row_height">50dp</dimen>
</resources>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<string name="app_name">Fake IME</string>
</resources>

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<style name="KeyboardArea">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">bottom</item>
<item name="android:orientation">vertical</item>
<item name="android:background">#FFFFFFFF</item>
</style>
<style name="KeyboardRow">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/keyboard_row_height</item>
<item name="android:orientation">horizontal</item>
</style>
<style name="KeyboardRow.Header">
<item name="android:layout_height">@dimen/keyboard_header_height</item>
<item name="android:background">#FFEEEEEE</item>
</style>
<style name="SoftKey">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_weight">2</item>
<item name="android:gravity">center</item>
<item name="android:textColor">#FF000000</item>
<item name="android:textSize">@dimen/text_size_normal</item>
<item name="android:fontFamily">roboto-regular</item>
<item name="android:background">@drawable/key_border</item>
</style>
<style name="SoftKey.Function">
<item name="android:layout_weight">3</item>
<item name="android:textColor">#FF333333</item>
<item name="android:textSize">@dimen/text_size_symbol</item>
</style>
<style name="SoftKey.Function.Bottom">
<item name="android:layout_weight">3</item>
<item name="android:textColor">#FF333333</item>
<item name="android:textSize">@dimen/text_size_symbol</item>
</style>
<style name="SoftKey.Space">
<item name="android:layout_weight">10</item>
<item name="android:textColor">#FF333333</item>
<item name="android:textSize">@dimen/text_size_symbol</item>
</style>
</resources>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<input-method xmlns:android="http://schemas.android.com/apk/res/android">
<subtype
android:label="FakeIme"
android:imeSubtypeLocale="en_US"
android:imeSubtypeMode="keyboard"/>
</input-method>

View File

@@ -0,0 +1,68 @@
/*
* 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.apps.inputmethod.simpleime;
import android.view.KeyEvent;
import java.util.HashMap;
/** Holder of key codes and their name. */
public final class KeyCodeConstants {
private KeyCodeConstants() {}
static final HashMap<String, Integer> KEY_NAME_TO_CODE_MAP = new HashMap<>();
static {
KEY_NAME_TO_CODE_MAP.put("KEYCODE_A", KeyEvent.KEYCODE_A);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_B", KeyEvent.KEYCODE_B);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_C", KeyEvent.KEYCODE_C);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_D", KeyEvent.KEYCODE_D);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_E", KeyEvent.KEYCODE_E);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_F", KeyEvent.KEYCODE_F);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_G", KeyEvent.KEYCODE_G);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_H", KeyEvent.KEYCODE_H);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_I", KeyEvent.KEYCODE_I);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_J", KeyEvent.KEYCODE_J);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_K", KeyEvent.KEYCODE_K);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_L", KeyEvent.KEYCODE_L);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_M", KeyEvent.KEYCODE_M);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_N", KeyEvent.KEYCODE_N);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_O", KeyEvent.KEYCODE_O);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_P", KeyEvent.KEYCODE_P);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_Q", KeyEvent.KEYCODE_Q);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_R", KeyEvent.KEYCODE_R);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_S", KeyEvent.KEYCODE_S);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_T", KeyEvent.KEYCODE_T);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_U", KeyEvent.KEYCODE_U);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_V", KeyEvent.KEYCODE_V);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_W", KeyEvent.KEYCODE_W);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_X", KeyEvent.KEYCODE_X);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_Y", KeyEvent.KEYCODE_Y);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_Z", KeyEvent.KEYCODE_Z);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_SHIFT", KeyEvent.KEYCODE_SHIFT_LEFT);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_DEL", KeyEvent.KEYCODE_DEL);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_SPACE", KeyEvent.KEYCODE_SPACE);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_ENTER", KeyEvent.KEYCODE_ENTER);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_COMMA", KeyEvent.KEYCODE_COMMA);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_PERIOD", KeyEvent.KEYCODE_PERIOD);
KEY_NAME_TO_CODE_MAP.put("KEYCODE_TAB", KeyEvent.KEYCODE_TAB);
}
public static boolean isAlphaKeyCode(int keyCode) {
return keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.apps.inputmethod.simpleime;
import android.inputmethodservice.InputMethodService;
import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.FrameLayout;
import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
/** The {@link InputMethodService} implementation for SimpeTestIme app. */
public class SimpleInputMethodService extends InputMethodServiceWrapper {
private static final String TAG = "SimpleIMS";
private FrameLayout mInputView;
@Override
public View onCreateInputView() {
Log.i(TAG, "onCreateInputView()");
mInputView = (FrameLayout) LayoutInflater.from(this).inflate(R.layout.input_view, null);
return mInputView;
}
@Override
public void onStartInputView(EditorInfo info, boolean restarting) {
super.onStartInputView(info, restarting);
mInputView.removeAllViews();
SimpleKeyboard keyboard = new SimpleKeyboard(this, R.layout.qwerty_10_9_9);
mInputView.addView(keyboard.inflateKeyboardView(LayoutInflater.from(this), mInputView));
}
void handle(String data, int keyboardState) {
InputConnection inputConnection = getCurrentInputConnection();
Integer keyCode = KeyCodeConstants.KEY_NAME_TO_CODE_MAP.get(data);
Log.v(TAG, "keyCode: " + keyCode);
if (keyCode != null) {
inputConnection.sendKeyEvent(
new KeyEvent(
SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
KeyEvent.ACTION_DOWN,
keyCode,
0,
KeyCodeConstants.isAlphaKeyCode(keyCode) ? keyboardState : 0));
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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.apps.inputmethod.simpleime;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
/** Controls the visible virtual keyboard view. */
final class SimpleKeyboard {
private static final String TAG = "SimpleKeyboard";
private static final int[] SOFT_KEY_IDS =
new int[] {
R.id.key_pos_0_0,
R.id.key_pos_0_1,
R.id.key_pos_0_2,
R.id.key_pos_0_3,
R.id.key_pos_0_4,
R.id.key_pos_0_5,
R.id.key_pos_0_6,
R.id.key_pos_0_7,
R.id.key_pos_0_8,
R.id.key_pos_0_9,
R.id.key_pos_1_0,
R.id.key_pos_1_1,
R.id.key_pos_1_2,
R.id.key_pos_1_3,
R.id.key_pos_1_4,
R.id.key_pos_1_5,
R.id.key_pos_1_6,
R.id.key_pos_1_7,
R.id.key_pos_1_8,
R.id.key_pos_2_0,
R.id.key_pos_2_1,
R.id.key_pos_2_2,
R.id.key_pos_2_3,
R.id.key_pos_2_4,
R.id.key_pos_2_5,
R.id.key_pos_2_6,
R.id.key_pos_shift,
R.id.key_pos_del,
R.id.key_pos_symbol,
R.id.key_pos_comma,
R.id.key_pos_space,
R.id.key_pos_period,
R.id.key_pos_enter,
};
private final SimpleInputMethodService mSimpleInputMethodService;
private final int mViewResId;
private final SparseArray<TextView> mSoftKeyViews = new SparseArray<>();
private View mKeyboardView;
private int mKeyboardState;
SimpleKeyboard(SimpleInputMethodService simpleInputMethodService, int viewResId) {
this.mSimpleInputMethodService = simpleInputMethodService;
this.mViewResId = viewResId;
this.mKeyboardState = 0;
}
View inflateKeyboardView(LayoutInflater inflater, ViewGroup inputView) {
mKeyboardView = inflater.inflate(mViewResId, inputView, false);
mapSoftKeys();
return mKeyboardView;
}
private void mapSoftKeys() {
for (int id : SOFT_KEY_IDS) {
TextView softKeyView = mKeyboardView.findViewById(id);
mSoftKeyViews.put(id, softKeyView);
String tagData = softKeyView.getTag() != null ? softKeyView.getTag().toString() : null;
softKeyView.setOnClickListener(v -> handle(tagData));
}
}
private void handle(String data) {
Log.i(TAG, "handle(): " + data);
if (TextUtils.isEmpty(data)) {
return;
}
if ("KEYCODE_SHIFT".equals(data)) {
handleShift();
return;
}
mSimpleInputMethodService.handle(data, mKeyboardState);
}
private void handleShift() {
mKeyboardState = toggleShiftState(mKeyboardState);
Log.v(TAG, "currentKeyboardState: " + mKeyboardState);
boolean isShiftOn = isShiftOn(mKeyboardState);
for (int i = 0; i < mSoftKeyViews.size(); i++) {
TextView softKeyView = mSoftKeyViews.valueAt(i);
softKeyView.setAllCaps(isShiftOn);
}
}
private static boolean isShiftOn(int state) {
return (state & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
}
private static int toggleShiftState(int state) {
return state ^ KeyEvent.META_SHIFT_ON;
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.apps.inputmethod.simpleime.ims;
import android.content.res.Configuration;
import android.inputmethodservice.InputMethodService;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import java.util.concurrent.CountDownLatch;
/** Wrapper of {@link InputMethodService} to expose interfaces for testing purpose. */
public class InputMethodServiceWrapper extends InputMethodService {
private static final String TAG = "InputMethodServiceWrapper";
private static InputMethodServiceWrapper sInputMethodServiceWrapper;
public static InputMethodServiceWrapper getInputMethodServiceWrapperForTesting() {
return sInputMethodServiceWrapper;
}
private boolean mInputViewStarted;
private CountDownLatch mCountDownLatchForTesting;
public boolean getCurrentInputViewStarted() {
return mInputViewStarted;
}
public void setCountDownLatchForTesting(CountDownLatch countDownLatchForTesting) {
mCountDownLatchForTesting = countDownLatchForTesting;
}
@Override
public void onCreate() {
Log.i(TAG, "onCreate()");
super.onCreate();
sInputMethodServiceWrapper = this;
}
@Override
public void onStartInput(EditorInfo info, boolean restarting) {
Log.i(TAG, "onStartInput() editor=" + info + ", restarting=" + restarting);
super.onStartInput(info, restarting);
}
@Override
public void onStartInputView(EditorInfo info, boolean restarting) {
Log.i(TAG, "onStartInputView() editor=" + info + ", restarting=" + restarting);
super.onStartInputView(info, restarting);
mInputViewStarted = true;
if (mCountDownLatchForTesting != null) {
mCountDownLatchForTesting.countDown();
}
}
@Override
public void onFinishInput() {
Log.i(TAG, "onFinishInput()");
super.onFinishInput();
}
@Override
public void onFinishInputView(boolean finishingInput) {
Log.i(TAG, "onFinishInputView()");
super.onFinishInputView(finishingInput);
mInputViewStarted = false;
if (mCountDownLatchForTesting != null) {
mCountDownLatchForTesting.countDown();
}
}
@Override
public void requestHideSelf(int flags) {
Log.i(TAG, "requestHideSelf() " + flags);
super.requestHideSelf(flags);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
Log.i(TAG, "onConfigurationChanged() " + newConfig);
super.onConfigurationChanged(newConfig);
if (mCountDownLatchForTesting != null) {
mCountDownLatchForTesting.countDown();
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.apps.inputmethod.simpleime.testing;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;
/**
* A special activity for testing purpose.
*
* <p>This is used when the instruments package is SimpleTestIme, as the Intent needs to be started
* in the instruments package. More details see {@link
* Instrumentation#startActivitySync(Intent)}.</>
*/
public class TestActivity extends Activity {
private static final String TAG = "TestActivity";
/**
* Start a new test activity with an editor and wait for it to begin running before returning.
*
* @param instrumentation application instrumentation
* @return the newly started activity
*/
public static TestActivity start(Instrumentation instrumentation) {
Intent intent =
new Intent()
.setAction(Intent.ACTION_MAIN)
.setClass(instrumentation.getTargetContext(), TestActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
return (TestActivity) instrumentation.startActivitySync(intent);
}
public EditText mEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
LinearLayout rootView = new LinearLayout(this);
mEditText = new EditText(this);
mEditText.setContentDescription("Input box");
rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
setContentView(rootView);
mEditText.requestFocus();
super.onCreate(savedInstanceState);
}
/** Shows soft keyboard via InputMethodManager. */
public boolean showImeWithInputMethodManager(int flags) {
InputMethodManager imm = getSystemService(InputMethodManager.class);
boolean result = imm.showSoftInput(mEditText, flags);
Log.i(TAG, "hideIme() via InputMethodManager, result=" + result);
return result;
}
/** Shows soft keyboard via WindowInsetsController. */
public boolean showImeWithWindowInsetsController() {
WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
windowInsetsController.show(WindowInsets.Type.ime());
Log.i(TAG, "showIme() via WindowInsetsController");
return true;
}
/** Hides soft keyboard via InputMethodManager. */
public boolean hideImeWithInputMethodManager(int flags) {
InputMethodManager imm = getSystemService(InputMethodManager.class);
boolean result = imm.hideSoftInputFromWindow(mEditText.getWindowToken(), flags);
Log.i(TAG, "hideIme() via InputMethodManager, result=" + result);
return result;
}
/** Hides soft keyboard via WindowInsetsController. */
public boolean hideImeWithWindowInsetsController() {
WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
windowInsetsController.hide(WindowInsets.Type.ime());
Log.i(TAG, "hideIme() via WindowInsetsController");
return true;
}
}