Add test app SimpleTestIme to test InputMethodService

Add a test app SimpleTestIme under services/tests/InputMethodSystemServerTests/test-apps/ which provides the basic functionality of a
keyboard. Using it as the target package of instrumentation test, which
means we have access to all the class instances running in it under the
tests, including InputMethodService. In this way we can get a real InputMethodService instance, then manipulate and test the states of it.

Add simple test cases to verify the behavior of some public methods like:
  * onStartInput()/onFinishInput()
  * onStartInputView()/onFinishInputView()
  * requestShowSelf()/requestHideSelf()
  * onEvaluateFullscreenMode()/isFullScreenMode()

Bug: 240359838
Test: com.android.inputmethodservice.InputMethodServiceTest

Change-Id: I84f1a1b25eef4a757d8da27807285e08417458b4
This commit is contained in:
Lan Wei
2022-11-21 14:57:43 +08:00
parent 894ef374e5
commit 640b2b793f
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;
}
}