Merge "Add test app SimpleTestIme to test InputMethodService"
This commit is contained in:
committed by
Android (Google) Code Review
commit
8e023a00fe
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"/>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user