upload android base code part4

This commit is contained in:
August 2018-08-08 17:00:29 +08:00
parent b9e30e05b1
commit 78ea2404cd
23455 changed files with 5250148 additions and 0 deletions

View file

@ -0,0 +1,15 @@
# Copyright (C) 2017 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.
include $(call all-subdir-makefiles)

View file

@ -0,0 +1,50 @@
# Copyright (C) 2017 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.
###############################################################################
# Build the common library for use device-side
###############################################################################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := junit
LOCAL_MODULE_TAGS := tests
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_MODULE := CtsInputMethodServiceCommon
LOCAL_SDK_VERSION := current
include $(BUILD_STATIC_JAVA_LIBRARY)
###############################################################################
# Build the common library for use host-side
###############################################################################
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := junit-host
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE := cts-inputmethodservice-common-host
include $(BUILD_HOST_JAVA_LIBRARY)

View file

@ -0,0 +1,38 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.common;
/**
* Utility class to build Android's component name.
*/
final class ComponentNameUtils {
// This is utility class, can't instantiate.
private ComponentNameUtils() {}
/**
* Build Android component name from {@code packageName} and {@code className}.
* @param packageName package name of a component.
* @param className class name of a component.
* @return a component of {@code packageName/className} that can be used to specify component,
* for example, for {@code android.content.Intent}.
*/
static String buildComponentName(final String packageName, final String className) {
return packageName + "/" + (className.startsWith(packageName)
? className.substring(packageName.length()) : className);
}
}

View file

@ -0,0 +1,149 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.common;
import android.inputmethodservice.cts.common.test.TestInfo;
/**
* Constants of device event.
*/
public final class DeviceEventConstants {
// This is constants holding class, can't instantiate.
private DeviceEventConstants() {}
/** Intent action in order to record IME events. */
public static final String ACTION_DEVICE_EVENT =
"android.inputmethodservice.cts.action.DEVICE_EVENT";
/**
* Intent receiver's package, class, and component name.
*/
public static final String RECEIVER_PACKAGE = "android.inputmethodservice.cts.provider";
public static final String RECEIVER_CLASS =
"android.inputmethodservice.cts.receiver.EventReceiver";
public static final String RECEIVER_COMPONENT = ComponentNameUtils.buildComponentName(
RECEIVER_PACKAGE, RECEIVER_CLASS);
/**
* Intent extra key for who sends a device event.
* Values are Input Method class name, for example {@link Ime1Constants#CLASS}, or device test
* method name, for example {@link TestInfo#getTestName()}).
*
* @see android.content.Intent#putExtra(String,String)
* @see android.content.Intent#getStringExtra(String)
*/
public static final String EXTRA_EVENT_SENDER = "event_sender";
/**
* Intent extra key for Event parameters like
* {@link DeviceEventTypeParam#ON_START_INPUT_RESTARTING}
*/
public static final String EXTRA_EVENT_PARAMS = "event_params";
/**
* Intent extra key for what type a device event is. Values are {@link DeviceEventType#name()}.
*
* @see android.content.Intent#putExtra(String,String)
* @see android.content.Intent#getStringExtra(String)
*/
public static final String EXTRA_EVENT_TYPE = "event_type";
/**
* Intent extra key for at what time a device event happens. Value is taken from
* {@code android.os.SystemClock.uptimeMillis()}.
*
* @see android.content.Intent#putExtra(String,long)
* @see android.content.Intent#getLongExtra(String,long)
*/
public static final String EXTRA_EVENT_TIME = "event_time";
/**
* Parameter for {@link DeviceEventType}.
*/
public enum DeviceEventTypeParam {
/**
* Param for {@link DeviceEventType#ON_START_INPUT}. Represents if IME is restarting.
*/
ON_START_INPUT_RESTARTING(DeviceEventType.ON_START_INPUT, "onStartInput.restarting");
private final DeviceEventType mType;
private final String mName;
DeviceEventTypeParam(DeviceEventType type, String name) {
mType = type;
mName = name;
}
public String getName() {
return mName;
}
}
/**
* Types of device event, a value of {@link #EXTRA_EVENT_TYPE}.
*/
public enum DeviceEventType {
/**
* {@link android.inputmethodservice.InputMethodService#onCreate() onCreate()} callback.
*/
ON_CREATE,
/**
* {@link android.inputmethodservice.InputMethodService#onStartInput(android.view.inputmethod.EditorInfo,boolean) onStartInput(EditorInfo,boolean}
* callback.
*/
ON_START_INPUT,
/**
* {@link android.inputmethodservice.InputMethodService#onStartInputView(android.view.inputmethod.EditorInfo, boolean) onStartInputView(EditorInfo,boolean}
*/
ON_START_INPUT_VIEW,
/**
* {@link android.inputmethodservice.InputMethodService#onFinishInputView(boolean) onFinishInputView(boolean)}
* callback.
*/
ON_FINISH_INPUT_VIEW,
/**
* {@link android.inputmethodservice.InputMethodService#onFinishInput() onFinishInput()}
* callback.
*/
ON_FINISH_INPUT,
/**
* {@link android.inputmethodservice.InputMethodService#onDestroy() onDestroy()} callback.
*/
ON_DESTROY,
/** Test start and end event types. */
TEST_START,
TEST_END,
/**
* {@link android.view.inputmethod.InputMethod#showSoftInput}
*/
SHOW_SOFT_INPUT,
/**
* {@link android.view.inputmethod.InputMethod#hideSoftInput}
*/
HIDE_SOFT_INPUT,
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.common;
/**
* Constants of CtsInputMethodServiceEventProvider.apk that keeps IME event records.
*/
public final class EventProviderConstants {
// This is constants holding class, can't instantiate.
private EventProviderConstants() {}
/** Package name of the IME event provider. */
public static final String PACKAGE = "android.inputmethodservice.cts.provider";
/** Class name of IME event provider. */
public static final String CLASS =
"android.inputmethodservice.cts.provider.EventProviderConstants";
/** APK file name. */
public static final String APK = "CtsInputMethodServiceEventProvider.apk";
/** The authority of IME event provider. */
public static final String AUTHORITY = "android.inputmethodservice.cts.provider";
/** The base URI of IME event provider. */
private static final String BASE_URI = "content://" + AUTHORITY;
/**
* The Android platform's base MIME type for a content: URI containing a Cursor of a single
* item. Copied from android.content.ContentResolver.CURSOR_ITEM_BASE_TYPE.
*/
private static final String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
/**
* The Android platform's base MIME type for a content: URI containing a Cursor of zero or more
* items. Copied from android.content.ContentResolver.CURSOR_DIR_BASE_TYPE.
*/
private static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
/** Constants of Events table of IME event provider. */
public static final class EventTableConstants {
// This is constants holding class, can't instantiate.
private EventTableConstants() {}
/** Column name of the table that holds Event extras in json format. */
public static final String EXTRAS = "extras";
/** Name of the table in content provider and database. */
public static final String NAME = "events";
/** Column name of the table that holds who sends an event. */
public static final String SENDER = "sender";
/** Column name of the table that holds what type of event is. */
public static final String TYPE = "type";
/** Column name of the table that holds when an event happens. */
public static final String TIME = "time";
/** Content URI of the table. */
public static final String CONTENT_URI = BASE_URI + "/" + NAME;
/** MIME type of a cursor of zero or more items of the table. */
public static final String TYPE_DIR =
CURSOR_DIR_BASE_TYPE + "/" + AUTHORITY + ".ime_event";
/** MIME tye of a cursor of a single item of the table. */
public static final String TYPE_ITEM =
CURSOR_ITEM_BASE_TYPE + "/" + AUTHORITY + ".ime_event";
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.common;
public final class Ime1Constants {
// This is constants holding class, can't instantiate.
private Ime1Constants() {}
public static final String PACKAGE = "android.inputmethodservice.cts.ime1";
public static final String CLASS = "android.inputmethodservice.cts.ime1.CtsInputMethod1";
public static final String APK = "CtsInputMethod1.apk";
public static final String IME_ID = ComponentNameUtils.buildComponentName(PACKAGE, CLASS);
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.common;
public final class Ime2Constants {
// This is constants holding class, can't instantiate.
private Ime2Constants() {}
public static final String PACKAGE = "android.inputmethodservice.cts.ime2";
public static final String CLASS = "android.inputmethodservice.cts.ime2.CtsInputMethod2";
public static final String APK = "CtsInputMethod2.apk";
public static final String IME_ID = ComponentNameUtils.buildComponentName(PACKAGE, CLASS);
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.common;
/**
* Constants of IME command android.content.Intent.
*/
public final class ImeCommandConstants {
// This is constants holding class, can't instantiate.
private ImeCommandConstants() {}
/** Intent action in order to record IME events. */
public static final String ACTION_IME_COMMAND =
"android.inputmethodservice.cts.action.IME_COMMAND";
public static final String EXTRA_COMMAND = "command";
public static final String EXTRA_ARG_CHARSEQUENCE1 = "arg_charsequence1";
public static final String EXTRA_ARG_STRING1 = "arg_string1";
public static final String EXTRA_ARG_INT1 = "arg_int1";
/**
* This command has the mock IME call {@link android.view.inputmethod.InputConnection#commitText(CharSequence,int) InputConnection#commitText(CharSequence text, int newCursorPosition)}.
* <ul>
* <li>argument {@code text} needs to be specified by {@link #EXTRA_ARG_CHARSEQUENCE1}.</li>
* <li>argument {@code newCursorPosition} needs to be specified by {@link #EXTRA_ARG_INT1}.</li>
* </ul>
*/
public static final String COMMAND_COMMIT_TEXT = "commitText";
/**
* This command has the mock IME call {@link android.inputmethodservice.InputMethodService#switchInputMethod(String)} InputMethodService#switchInputMethod(String imeId)}.
* <ul>
* <li>argument {@code imeId} needs to be specified by {@link #EXTRA_ARG_STRING1}.</li>
* </ul>
*/
public static final String COMMAND_SWITCH_INPUT_METHOD = "switchInputMethod";
/**
* This command has the mock IME call {@link android.inputmethodservice.InputMethodService#requestHideSelf(int)} InputMethodService#requestHideSelf(int flags)}.
* <ul>
* <li>argument {@code flags} needs to be specified by {@link #EXTRA_ARG_INT1}.</li>
* </ul>
*/
public static final String COMMAND_REQUEST_HIDE_SELF = "requestHideSelf";
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.common.test;
/**
* Constants of CtsInputMethodServiceDeviceTests.apk that contains tests on device side and
* related activities for test.
*/
public final class DeviceTestConstants {
// This is constants holding class, can't instantiate.
private DeviceTestConstants() {}
/** Package name of the APK. */
public static final String PACKAGE = "android.inputmethodservice.cts.devicetest";
/** APK file name. */
public static final String APK = "CtsInputMethodServiceDeviceTests.apk";
/** Device test activity name. */
public static final String TEST_ACTIVITY_CLASS =
"android.inputmethodservice.cts.devicetest.InputMethodServiceTestActivity";
/**
* Device test class name and methods name.
*/
public static final String TEST_CLASS =
"android.inputmethodservice.cts.devicetest.InputMethodServiceDeviceTest";
public static final String TEST_CREATE_IME1 = "testCreateIme1";
public static final String TEST_SWITCH_IME1_TO_IME2 = "testSwitchIme1ToIme2";
public static final String TEST_IME1_IS_NOT_CURRENT_IME = "testIme1IsNotCurrentIme";
public static final String TEST_SEARCH_VIEW_GIVE_FOCUS_SHOW_IME1
= "testSearchView_giveFocusShowIme1";
public static final String TEST_SEARCH_VIEW_SET_QUERY_HIDE_IME1
= "testSearchView_setQueryHideIme1";
public static final String TEST_ON_START_INPUT_CALLED_ONCE_IME1
= "testOnStartInputCalledOnceIme1";
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.common.test;
import java.util.Arrays;
/**
* Utility class for preparing "adb shell" command.
*/
public final class ShellCommandUtils {
// This is utility class, can't instantiate.
private ShellCommandUtils() {}
// Copied from android.content.pm.PackageManager#FEATURE_INPUT_METHODS.
public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
/** Command to check whether system has specified {@code featureName} feature. */
public static String hasFeature(final String featureName) {
return "cmd package has-feature " + featureName;
}
private static final String SETTING_DEFAULT_IME = "secure default_input_method";
/** Command to get ID of current IME. */
public static String getCurrentIme() {
return "settings get " + SETTING_DEFAULT_IME;
}
/** Command to set current IME to {@code imeId}. */
public static String setCurrentIme(final String imeId) {
return "settings put " + SETTING_DEFAULT_IME + " " + imeId;
}
/** Command to enable IME of {@code imeId}. */
public static String enableIme(final String imeId) {
return "ime enable " + imeId;
}
/** Command to disable IME of {@code imeId}. */
public static String disableIme(final String imeId) {
return "ime disable " + imeId;
}
/** Command to delete all records of IME event provider. */
public static String deleteContent(final String contentUri) {
return "content delete --uri " + contentUri;
}
/**
* Command to send broadcast {@code Intent}.
*
* @param action action of intent.
* @param targetComponent target of intent.
* @param extras extra of intent, must be specified as triplet of option flag, key, and value.
* @return shell command to send broadcast intent.
*/
public static String broadcastIntent(final String action, final String targetComponent,
final String... extras) {
if (extras.length % 3 != 0) {
throw new IllegalArgumentException(
"extras must be triplets: " + Arrays.toString(extras));
}
final StringBuilder sb = new StringBuilder("am broadcast -a ")
.append(action);
for (int index = 0; index < extras.length; index += 3) {
final String optionFlag = extras[index];
final String extraKey = extras[index + 1];
final String extraValue = extras[index + 2];
sb.append(" ").append(optionFlag)
.append(" ").append(extraKey)
.append(" ").append(extraValue);
}
return sb.append(" ").append(targetComponent).toString();
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.common.test;
import android.inputmethodservice.cts.common.DeviceEventConstants;
/**
* Abstraction of test information on device.
*/
public final class TestInfo {
public final String testPackage;
public final String testClass;
public final String testMethod;
public TestInfo(final String testPackage, final String testClass, final String testMethod) {
this.testPackage = testPackage;
this.testClass = testClass;
this.testMethod = testMethod;
}
/**
* Get fully qualified test method name that can be used as
* {@link DeviceEventConstants#EXTRA_EVENT_SENDER}.
* @return string representation of fully qualified test method name.
*/
public String getTestName() {
return testClass + "#" + testMethod;
}
}

View file

@ -0,0 +1,15 @@
# Copyright (C) 2017 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.
include $(call all-subdir-makefiles)

View file

@ -0,0 +1,45 @@
# Copyright (C) 2014 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.
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
# Don't include this package in any target
LOCAL_MODULE_TAGS := tests
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_DEX_PREOPT := false
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_RESOURCE_DIR := res
LOCAL_JAVA_LIBRARY := android.test.runner
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
hamcrest hamcrest-library \
ub-uiautomator \
CtsInputMethodServiceCommon \
CtsInputMethodServiceLib
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_PACKAGE_NAME := CtsInputMethodServiceDeviceTests
LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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="android.inputmethodservice.cts.devicetest">
<!--
TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
latest OS behaviors.
-->
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
<application
android:label="CtsInputMethodServiceDeviceTests"
android:icon="@mipmap/ic_launcher"
android:allowBackup="false"
>
<uses-library android:name="android.test.runner" />
<activity
android:name=".InputMethodServiceTestActivity"
android:label="InputMethodSercuceTestActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<instrumentation
android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.inputmethodservice.cts.devicetest" />
</manifest>

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10px">
<EditText
android:id="@+id/text_entry"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"/>
<SearchView
android:id="@+id/search_view"
android:layout_below="@id/text_entry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:iconifiedByDefault="false"
android:queryHint="hint"
android:inputType="textCapCharacters"
android:imeOptions="actionDone" />
</RelativeLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,70 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.devicetest;
import static org.junit.Assert.fail;
import java.util.concurrent.TimeUnit;
/**
* Utility class for busy waiting.
*/
final class BusyWaitUtils {
private static final long POLLING_INTERVAL = TimeUnit.MILLISECONDS.toMillis(50);
@FunctionalInterface
interface PollingCondition {
boolean check() throws Exception;
}
// This is utility class, can't instantiate.
private BusyWaitUtils() {}
/**
* Busy waiting until {@link PollingCondition#check()} returns {@code true}.
* @param condition {@link PollingCondition} to be checked.
* @param timeout milliseconds before time out happens.
* @param message when time out happens, {@link org.junit.Assert#fail(String)} is called with
* this message.
* @throws Exception
*/
static void pollingCheck(final PollingCondition condition, final long timeout,
final String message) throws Exception {
if (waitFor(condition, timeout)) {
return;
}
fail(message);
}
/**
* Busy waiting until {@link PollingCondition#check()} returns {@code true}.
* @param condition {@link PollingCondition} to be checked.
* @param timeout milliseconds before time out happens.
* @return true when {@code condition} returns {@code true}, false when timed out.
* @throws Exception
*/
static boolean waitFor(final PollingCondition condition, final long timeout) throws Exception {
for (long remaining = timeout; remaining > 0; remaining -= POLLING_INTERVAL) {
if (condition.check()) {
return true;
}
Thread.sleep(POLLING_INTERVAL);
}
return false;
}
}

View file

@ -0,0 +1,241 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.devicetest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static android.inputmethodservice.cts.DeviceEvent.isFrom;
import static android.inputmethodservice.cts.DeviceEvent.isNewerThan;
import static android.inputmethodservice.cts.DeviceEvent.isType;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.HIDE_SOFT_INPUT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_CREATE;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_DESTROY;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.SHOW_SOFT_INPUT;
import static android.inputmethodservice.cts.common.ImeCommandConstants.ACTION_IME_COMMAND;
import static android.inputmethodservice.cts.common.ImeCommandConstants.COMMAND_SWITCH_INPUT_METHOD;
import static android.inputmethodservice.cts.common.ImeCommandConstants.EXTRA_ARG_STRING1;
import static android.inputmethodservice.cts.common.ImeCommandConstants.EXTRA_COMMAND;
import static android.inputmethodservice.cts.devicetest.BusyWaitUtils.pollingCheck;
import static android.inputmethodservice.cts.devicetest.MoreCollectors.startingFrom;
import android.app.Activity;
import android.inputmethodservice.cts.DeviceEvent;
import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
import android.inputmethodservice.cts.common.Ime1Constants;
import android.inputmethodservice.cts.common.Ime2Constants;
import android.inputmethodservice.cts.common.test.DeviceTestConstants;
import android.inputmethodservice.cts.common.test.ShellCommandUtils;
import android.inputmethodservice.cts.devicetest.SequenceMatcher.MatchResult;
import android.os.SystemClock;
import android.widget.SearchView;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
@RunWith(AndroidJUnit4.class)
public class InputMethodServiceDeviceTest {
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
/** Test to check CtsInputMethod1 receives onCreate and onStartInput. */
@Test
public void testCreateIme1() throws Throwable {
final TestHelper helper = new TestHelper(getClass(), DeviceTestConstants.TEST_CREATE_IME1);
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
.filter(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE)))
.findAny().isPresent(),
TIMEOUT, "CtsInputMethod1.onCreate is called");
final long startActivityTime = SystemClock.uptimeMillis();
helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
pollingCheck(() -> helper.queryAllEvents()
.filter(isNewerThan(startActivityTime))
.filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
.findAny().isPresent(),
TIMEOUT, "CtsInputMethod1.onStartInput is called");
}
/** Test to check IME is switched from CtsInputMethod1 to CtsInputMethod2. */
@Test
public void testSwitchIme1ToIme2() throws Throwable {
final TestHelper helper = new TestHelper(
getClass(), DeviceTestConstants.TEST_SWITCH_IME1_TO_IME2);
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
.filter(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE)))
.findAny().isPresent(),
TIMEOUT, "CtsInputMethod1.onCreate is called");
final long startActivityTime = SystemClock.uptimeMillis();
helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
pollingCheck(() -> helper.queryAllEvents()
.filter(isNewerThan(startActivityTime))
.filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
.findAny().isPresent(),
TIMEOUT, "CtsInputMethod1.onStartInput is called");
helper.findUiObject(R.id.text_entry).click();
// Switch IME from CtsInputMethod1 to CtsInputMethod2.
final long switchImeTime = SystemClock.uptimeMillis();
helper.shell(ShellCommandUtils.broadcastIntent(
ACTION_IME_COMMAND, Ime1Constants.PACKAGE,
"-e", EXTRA_COMMAND, COMMAND_SWITCH_INPUT_METHOD,
"-e", EXTRA_ARG_STRING1, Ime2Constants.IME_ID));
pollingCheck(() -> helper.shell(ShellCommandUtils.getCurrentIme())
.equals(Ime2Constants.IME_ID),
TIMEOUT, "CtsInputMethod2 is current IME");
pollingCheck(() -> helper.queryAllEvents()
.filter(isNewerThan(switchImeTime))
.filter(isFrom(Ime1Constants.CLASS).and(isType(ON_DESTROY)))
.findAny().isPresent(),
TIMEOUT, "CtsInputMethod1.onDestroy is called");
pollingCheck(() -> helper.queryAllEvents()
.filter(isNewerThan(switchImeTime))
.filter(isFrom(Ime2Constants.CLASS))
.collect(sequenceOfTypes(ON_CREATE, ON_START_INPUT))
.matched(),
TIMEOUT,
"CtsInputMethod2.onCreate and onStartInput are called in sequence");
}
/** Test to check CtsInputMethod1 isn't current IME. */
@Test
public void testIme1IsNotCurrentIme() throws Throwable {
final TestHelper helper =
new TestHelper(getClass(), DeviceTestConstants.TEST_IME1_IS_NOT_CURRENT_IME);
helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
helper.findUiObject(R.id.text_entry).click();
pollingCheck(() -> !helper.shell(ShellCommandUtils.getCurrentIme())
.equals(Ime1Constants.IME_ID),
TIMEOUT,
"CtsInputMethod1 is uninstalled or disabled, and current IME becomes other IME");
}
@Test
public void testSearchView_giveFocusShowIme1() throws Throwable {
final TestHelper helper = new TestHelper(
getClass(), DeviceTestConstants.TEST_SEARCH_VIEW_GIVE_FOCUS_SHOW_IME1);
helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
helper.findUiObject(R.id.search_view).click();
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
.filter(isFrom(Ime1Constants.CLASS).and(isType(SHOW_SOFT_INPUT)))
.findAny().isPresent(),
TIMEOUT, "CtsInputMethod1.showSoftInput is called");
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
.filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
.findAny().isPresent(),
TIMEOUT, "CtsInputMethod1.onStartInput is called");
}
@Test
public void testSearchView_setQueryHideIme1() throws Throwable {
final TestHelper helper = new TestHelper(
getClass(), DeviceTestConstants.TEST_SEARCH_VIEW_SET_QUERY_HIDE_IME1);
final Activity activity = helper.launchActivitySync(
DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
final SearchView searchView = (SearchView) activity.findViewById(R.id.search_view);
helper.findUiObject(R.id.search_view).click();
// test SearchView.onSubmitQuery() closes IME. Alternatively, onCloseClicked() closes IME.
// submits the query, should dismiss the inputMethod.
activity.runOnUiThread(() -> searchView.setQuery("test", true /* submit */));
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
.filter(isFrom(Ime1Constants.CLASS).and(isType(ON_FINISH_INPUT)))
.findAny().isPresent(),
TIMEOUT, "CtsInputMethod1.onFinishInput is called");
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
.filter(isFrom(Ime1Constants.CLASS).and(isType(HIDE_SOFT_INPUT)))
.findAny().isPresent(),
TIMEOUT, "CtsInputMethod1.hideSoftInput is called");
}
@Test
public void testOnStartInputCalledOnceIme1() throws Exception {
final TestHelper helper = new TestHelper(
getClass(), DeviceTestConstants.TEST_ON_START_INPUT_CALLED_ONCE_IME1);
helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
helper.findUiObject(R.id.text_entry).click();
// we should've only one onStartInput call.
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
.filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
.findAny()
.isPresent(),
TIMEOUT, "CtsInputMethod1.onStartInput is called");
List<DeviceEvent> startInputEvents = helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
.filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
.collect(Collectors.toList());
assertEquals("CtsInputMethod1.onStartInput is called exactly once",
startInputEvents.size(),
1);
// check if that single event didn't cause IME restart.
final DeviceEvent event = startInputEvents.get(0);
Boolean isRestarting = DeviceEvent.getEventParamBoolean(
DeviceEventTypeParam.ON_START_INPUT_RESTARTING, event);
assertTrue(isRestarting != null);
assertFalse(isRestarting);
}
/**
* Build stream collector of {@link DeviceEvent} collecting sequence that elements have
* specified types.
*
* @param types {@link DeviceEventType}s that elements of sequence should have.
* @return {@link java.util.stream.Collector} that corrects the sequence.
*/
private static Collector<DeviceEvent, ?, MatchResult<DeviceEvent>> sequenceOfTypes(
final DeviceEventType... types) {
final IntFunction<Predicate<DeviceEvent>[]> arraySupplier = Predicate[]::new;
return SequenceMatcher.of(Arrays.stream(types)
.map(DeviceEvent::isType)
.toArray(arraySupplier));
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.devicetest;
import android.app.Activity;
import android.os.Bundle;
public class InputMethodServiceTestActivity extends Activity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inputmethod_test);
}
}

View file

@ -0,0 +1,88 @@
package android.inputmethodservice.cts.devicetest;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Stream;
import java.util.stream.Stream.Builder;
/**
* More stream collectors.
*/
final class MoreCollectors {
/**
* Create a collector that collects elements ending at an element that satisfies specified
* predicate. For example,
* <pre>
* Stream.of("a", "b", "c", "d", "c", "d", "e").collect(endingAt(s -> s.equals("d")))
* </pre>
* returns {@code Stream.of("a", "b", "c", "d")}.
*
* @param predicate a predicator to find a specific element.
* @param <E> a type of element.
* @return {@link Collector} object that collects elements ending at an element that is
* accepted by {@code predicate}.
*/
static <E> Collector<E, ?, Stream<E>> endingAt(final Predicate<E> predicate) {
final BiConsumer<Builder<E>, E> endingAtAccumulator = new BiConsumer<Builder<E>, E>() {
private boolean mFound = false;
@Override
public void accept(final Builder<E> builder, final E element) {
if (mFound) {
return;
}
if (predicate.test(element)) {
mFound = true;
}
builder.accept(element);
}
};
return Collector.of(
Stream::builder,
endingAtAccumulator,
(builder, builder2) -> {
throw new UnsupportedOperationException("Do not use on parallel stream.");
},
Builder::build);
}
/**
* Create a collector that collects elements starting from an element that satisfies specified
* predicate. For example,
* <pre>
* Stream.of("a", "b", "c", "d", "c", "d", "e").collect(startingFrom(s -> s.equals("d")))
* </pre>
* returns {@code Stream.of("d", "c", "d", "e")}.
*
* @param predicate a predicator to find a specific element.
* @param <E> a type of element.
* @return {@link Collector} object that collects elements starting from an element that is
* accepted by {@code predicate}.
*/
static <E> Collector<E, ?, Stream<E>> startingFrom(final Predicate<E> predicate) {
final BiConsumer<Builder<E>, E> startingFromAccumulator = new BiConsumer<Builder<E>, E>() {
private boolean mFound = false;
@Override
public void accept(final Builder<E> builder, final E element) {
if (mFound) {
builder.accept(element);
return;
}
if (predicate.test(element)) {
mFound = true;
builder.accept(element);
}
}
};
return Collector.of(
Stream::builder,
startingFromAccumulator,
(builder, builder2) -> {
throw new UnsupportedOperationException("Do not use on parallel stream.");
},
Builder::build);
}
}

View file

@ -0,0 +1,140 @@
package android.inputmethodservice.cts.devicetest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Stream;
/**
* Sequence matcher on {@link Stream}.
*/
final class SequenceMatcher {
/**
* Return type of this matcher.
* @param <E> type of stream element.
*/
static final class MatchResult<E> {
private final boolean mMatched;
private final List<E> mMatchedSequence;
MatchResult(final boolean matched, final List<E> matchedSequence) {
mMatched = matched;
mMatchedSequence = matchedSequence;
}
boolean matched() {
return mMatched;
}
List<E> getMatchedSequence() {
return mMatchedSequence;
}
}
/**
* Accumulate continuous sequence of elements that satisfy specified {@link Predicate}s.
* @param <E> type of stream element.
*/
private static final class SequenceAccumulator<E> {
private final Predicate<E>[] mPredicates;
private final List<E> mSequence = new ArrayList<>();
SequenceAccumulator(final Predicate<E>... predicates) {
mPredicates = predicates;
}
void accumulate(final E element) {
if (mSequence.isEmpty()) {
// Search for the first element of sequence.
if (mPredicates[0].test(element)) {
mSequence.add(element);
}
return;
}
final int currentIndex = mSequence.size();
if (currentIndex >= mPredicates.length) {
// Already found sequence.
return;
}
if (mPredicates[currentIndex].test(element)) {
mSequence.add(element);
} else {
// Not continuous, restart searching from the first.
mSequence.clear();
}
}
MatchResult<E> getResult() {
return new MatchResult<>(mSequence.size() == mPredicates.length, mSequence);
}
}
/**
* Create a {@link Collector} that collects continuous sequence of elements that equal to
* specified {@code elements}. It returns {@link MatchResult<E>} of found such sequence.
* <pre>
* Stream.of(1, 2, 3, 4, 5).collect(
* SequenceMatcher.of(1)).matched(); // true
* Stream.of(1, 2, 3, 4, 5).collect(
* SequenceMatcher.of(2)).matched(); // true
* Stream.of(1, 2, 3, 4, 5).collect(
* SequenceMatcher.of(0)).matched(); // false
* Stream.of(1, 2, 3, 4, 5).collect(
* SequenceMatcher.of(2, 3, 4)).matched(); // true
* Stream.of(1, 2, 3, 4, 5).collect(
* SequenceMatcher.of(2, 3, 5)).matched(); // false, not continuous.
* Stream.of(1, 2, 3, 4, 5).collect(
* SequenceMatcher.of(2, 1)).matched(); // false
* Stream.of(1, 2, 3, 4, 5).collect(
* SequenceMatcher.of(1, 2, 3, 4, 5, 6)).matched(); // false
* Stream.of(1, 1, 1, 1, 1).collect(
* SequenceMatcher.of(1, 1)).matched(); // true
* Stream.of(1, 1, 1, 1, 1).collect(
* SequenceMatcher.of(1, 1, 1)).matched(); // true
* Stream.of(1, 1, 1, 1, 1).collect(
* SequenceMatcher.of(1, 1, 1, 1, 1, 1)).matched(); // false
* Stream.of(1, 1, 0, 1, 1).collect(
* SequenceMatcher.of(1, 1, 1)).matched(); // false, not continuous.
* </pre>
*
* @param elements elements of matching sequence.
* @param <E> type of stream element
* @return {@link MatchResult<E>} of matcher sequence.
*/
static <E> Collector<E, ?, MatchResult<E>> of(final E... elements) {
if (elements == null || elements.length == 0) {
throw new IllegalArgumentException("At least one element.");
}
final IntFunction<Predicate<E>[]> arraySupplier = Predicate[]::new;
return of(Arrays.stream(elements).map(Predicate::isEqual).toArray(arraySupplier));
}
/**
* Create a {@link Collector} that collects continuous sequence of elements that satisfied
* specified {@code predicates}. It returns {@link MatchResult<E>} of found such sequence.
* <p>Please see examples in {@link #of(Object...)}.
*
* @param predicates array of {@link Predicate<E>} that each of sequence element should satisfy.
* @param <E> type of stream element.
* @return {@link MatchResult<E>} of matched sequence.
*/
static <E> Collector<E, ?, MatchResult<E>> of(final Predicate<E>... predicates) {
if (predicates == null || predicates.length == 0) {
throw new IllegalArgumentException("At least one Predicate.");
}
return Collector.of(
() -> new SequenceAccumulator<>(predicates),
SequenceAccumulator::accumulate,
(accumulate, accumulate2) -> {
throw new UnsupportedOperationException("Do not use on parallel stream.");
},
SequenceAccumulator::getResult);
}
}

View file

@ -0,0 +1,152 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.devicetest;
import static android.inputmethodservice.cts.DeviceEvent.isFrom;
import static android.inputmethodservice.cts.DeviceEvent.isType;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.TEST_START;
import android.app.Instrumentation;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.inputmethodservice.cts.DeviceEvent;
import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
import android.inputmethodservice.cts.common.test.TestInfo;
import android.net.Uri;
import android.support.annotation.IdRes;
import android.support.test.InstrumentationRegistry;
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 java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Helper object for device side test.
*/
final class TestHelper {
/** Content URI of device event provider. */
private static final Uri DEVICE_EVENTS_CONTENT_URI = Uri.parse(EventTableConstants.CONTENT_URI);
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
private final TestInfo mTestInfo;
private final ContentResolver mResolver;
private final Context mTargetContext;
private final UiDevice mUiDevice;
private final Instrumentation mInstrumentation;
/**
* Construct a helper object of specified test method.
*
* @param testClass a {@link Class} of test.
* @param testMethod a name of test method.
*/
TestHelper(final Class<?> testClass, final String testMethod) {
final Context testContext = InstrumentationRegistry.getContext();
mTestInfo = new TestInfo(testContext.getPackageName(), testClass.getName(), testMethod);
mResolver = testContext.getContentResolver();
mTargetContext = InstrumentationRegistry.getTargetContext();
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mUiDevice = UiDevice.getInstance(mInstrumentation);
}
/**
* Execute a shell {@code command} and return its standard out.
* @param command a shell command text to execute.
* @return command's standard output without ending newline.
* @throws IOException
*/
String shell(final String command) throws IOException {
return mUiDevice.executeShellCommand(command).trim();
}
/**
* Launching an Activity for test, and wait for completions of launch.
* @param packageName activity's app package name.
* @param className activity's class name.
*/
void launchActivity(final String packageName, final String className) {
final Intent intent = new Intent()
.setAction(Intent.ACTION_MAIN)
.setClassName(packageName, className)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
InstrumentationRegistry.getContext().startActivity(intent);
mUiDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), TIMEOUT);
}
/**
* Find an UI element from resource ID.
* @param resId id of finding UI element.
* @return {@link UiObject2} of found UI element.
*/
UiObject2 findUiObject(@IdRes int resId) {
final String resourceName = mTargetContext.getResources().getResourceName(resId);
return mUiDevice.findObject(By.res(resourceName));
}
/**
* Launch test activity synchronously.
*
* @param packageName activity's app package name.
* @param className activity's class name.
* @return instance of Activity
*/
Activity launchActivitySync(final String packageName, final String className) {
final Intent intent = new Intent()
.setAction(Intent.ACTION_MAIN)
.setClassName(packageName, className)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
return mInstrumentation.startActivitySync(intent);
}
/**
* Return all device events as {@link Stream}
* @return {@link Stream<DeviceEvent>} of all device events.
*/
Stream<DeviceEvent> queryAllEvents() {
try (final Cursor cursor = mResolver.query(
DEVICE_EVENTS_CONTENT_URI,
null /* projection */,
null /* selection */,
null /* selectionArgs */,
null /* sortOrder */)) {
return DeviceEvent.buildStream(cursor);
}
}
/**
* Build a {@link Predicate} can be used for skipping device events in {@link Stream} until
* {@link DeviceEventType#TEST_START TEST_START} device event of this test method.
* @return {@llink Predicate<DeviceEvent>} that return true after accepting
* {@link DeviceEventType#TEST_START TEST_START} of this test method.
*/
Predicate<DeviceEvent> isStartOfTest() {
return isFrom(mTestInfo.getTestName()).and(isType(TEST_START));
}
}

View file

@ -0,0 +1,41 @@
# Copyright (C) 2014 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.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Don't include this package in any target
LOCAL_MODULE_TAGS := tests
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_DEX_PREOPT := false
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_RESOURCE_DIR := res
LOCAL_STATIC_JAVA_LIBRARIES := \
CtsInputMethodServiceCommon \
CtsInputMethodServiceLib
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_PACKAGE_NAME := CtsInputMethod1
LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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="android.inputmethodservice.cts.ime1">
<!--
TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
latest OS behaviors.
-->
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
<application
android:label="@string/ime_name"
android:allowBackup="false"
android:theme="@android:style/Theme.InputMethod"
>
<service
android:name=".CtsInputMethod1"
android:label="@string/ime_name"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data
android:name="android.view.im"
android:resource="@xml/ime1" />
</service>
</application>
</manifest>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_orange_dark"
android:padding="80dp"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="72sp"
android:gravity="center"
android:id="@+id/ime_name"
android:text="@string/ime_name" />
</RelativeLayout>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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>
<color name="input_view_background">@android:color/holo_orange_dark</color>
</resources>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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="ime_name" translatable="false">IME1</string>
</resources>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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"
android:supportsSwitchingToNextInputMethod="true">
</input-method>

View file

@ -0,0 +1,28 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.ime1;
import android.inputmethodservice.cts.ime.CtsBaseInputMethod;
import android.view.View;
public final class CtsInputMethod1 extends CtsBaseInputMethod {
@Override
public View onCreateInputView() {
return getLayoutInflater().inflate(R.layout.input_view, null /* root */);
}
}

View file

@ -0,0 +1,41 @@
# Copyright (C) 2014 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.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Don't include this package in any target
LOCAL_MODULE_TAGS := tests
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_DEX_PREOPT := false
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_RESOURCE_DIR := res
LOCAL_STATIC_JAVA_LIBRARIES := \
CtsInputMethodServiceCommon \
CtsInputMethodServiceLib
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_PACKAGE_NAME := CtsInputMethod2
LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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="android.inputmethodservice.cts.ime2">
<!--
TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
latest OS behaviors.
-->
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
<application
android:label="@string/ime_name"
android:allowBackup="false"
android:theme="@android:style/Theme.InputMethod"
>
<service
android:name=".CtsInputMethod2"
android:label="@string/ime_name"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data
android:name="android.view.im"
android:resource="@xml/ime2" />
</service>
</application>
</manifest>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_dark"
android:padding="80dp"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="72sp"
android:gravity="center"
android:id="@+id/ime_name"
android:text="@string/ime_name" />
</RelativeLayout>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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>
<color name="input_view_background">@android:color/holo_blue_dark</color>
</resources>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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="ime_name" translatable="false">IME2</string>
</resources>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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"
android:supportsSwitchingToNextInputMethod="true">
</input-method>

View file

@ -0,0 +1,28 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.ime2;
import android.inputmethodservice.cts.ime.CtsBaseInputMethod;
import android.view.View;
public final class CtsInputMethod2 extends CtsBaseInputMethod {
@Override
public View onCreateInputView() {
return getLayoutInflater().inflate(R.layout.input_view, null /* root */);
}
}

View file

@ -0,0 +1,34 @@
# Copyright (C) 2017 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.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-annotations \
json \
CtsInputMethodServiceCommon
LOCAL_MODULE_TAGS := tests
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_MODULE := CtsInputMethodServiceLib
LOCAL_SDK_VERSION := current
include $(BUILD_STATIC_JAVA_LIBRARY)

View file

@ -0,0 +1,354 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts;
import static android.inputmethodservice.cts.common.DeviceEventConstants.ACTION_DEVICE_EVENT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_PARAMS;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TIME;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_SENDER;
import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_CLASS;
import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_PACKAGE;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.inputmethodservice.cts.common.DeviceEventConstants;
import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
import android.inputmethodservice.cts.common.test.TestInfo;
import android.inputmethodservice.cts.db.Entity;
import android.inputmethodservice.cts.db.Field;
import android.inputmethodservice.cts.db.Table;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import com.android.json.stream.JsonReader;
import com.android.json.stream.JsonWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Device event object.
* <p>Device event is one of IME event and Test event, and is used to test behaviors of Input Method
* Framework.</p>
*/
public final class DeviceEvent {
private static final boolean DEBUG_STREAM = false;
public static final Table<DeviceEvent> TABLE = new DeviceEventTable(EventTableConstants.NAME);
public static IntentBuilder builder() {
return new IntentBuilder();
}
/**
* Builder to create an intent to send a device event.
* The built intent that has event {@code sender}, {@code type}, {@code paramsString}, time from
* {@link SystemClock#uptimeMillis()}, and target component of event receiver.
*
*/
public static final class IntentBuilder {
String mSender;
DeviceEventType mType;
JsonWriter mJsonWriter;
StringWriter mStringWriter;
/**
* @param type an event type defined at {@link DeviceEventType}.
*/
public IntentBuilder setType(DeviceEventType type) {
mType = type;
return this;
}
/**
* @param sender an event sender.
*/
public void setSender(String sender) {
mSender = sender;
}
public IntentBuilder with(DeviceEventTypeParam eventParam, boolean value) {
appendToJson(eventParam, value);
return this;
}
public Intent build() {
Intent intent = new Intent()
.setAction(ACTION_DEVICE_EVENT)
.setClassName(RECEIVER_PACKAGE, RECEIVER_CLASS)
.putExtra(EXTRA_EVENT_SENDER, mSender)
.putExtra(EXTRA_EVENT_TYPE, mType.name())
.putExtra(EXTRA_EVENT_PARAMS, getJsonString())
.putExtra(EXTRA_EVENT_TIME, SystemClock.uptimeMillis());
mJsonWriter = null;
mStringWriter = null;
return intent;
}
private String getJsonString() {
if (mJsonWriter == null) {
return "";
}
try {
mJsonWriter.endObject();
mJsonWriter.flush();
} catch (IOException e) {
throw new RuntimeException("IntentBuilder.getJsonString() failed.", e);
}
return mStringWriter.toString();
}
private void appendToJson(DeviceEventTypeParam eventParam, boolean value) {
final String key = eventParam.getName();
if (TextUtils.isEmpty(key)) {
return;
}
try {
if (mJsonWriter == null) {
mStringWriter = new StringWriter();
mJsonWriter = new JsonWriter(mStringWriter);
mJsonWriter.beginObject();
}
mJsonWriter.name(key).value(value);
} catch (IOException e) {
throw new RuntimeException("IntentBuilder.appendToJson() failed.", e);
}
}
}
/**
* Create an {@link DeviceEvent} object from an intent.
* @param intent a device event intent defined at {@link DeviceEventConstants}.
* @return {@link DeviceEvent} object that has event sender, type, and time form an
* {@code intent}.
*/
public static DeviceEvent newEvent(final Intent intent) {
final String sender = intent.getStringExtra(EXTRA_EVENT_SENDER);
if (sender == null) {
throw new IllegalArgumentException(
"Intent must have " + EXTRA_EVENT_SENDER + ": " + intent);
}
final String typeString = intent.getStringExtra(EXTRA_EVENT_TYPE);
if (typeString == null) {
throw new IllegalArgumentException(
"Intent must have " + EXTRA_EVENT_TYPE + ": " + intent);
}
final DeviceEventType type = DeviceEventType.valueOf(typeString);
if (!intent.hasExtra(EXTRA_EVENT_TIME)) {
throw new IllegalArgumentException(
"Intent must have " + EXTRA_EVENT_TIME + ": " + intent);
}
String paramsString = intent.getStringExtra(DeviceEventConstants.EXTRA_EVENT_PARAMS);
if (paramsString == null) {
paramsString = "";
}
return new DeviceEvent(
sender,
type,
paramsString,
intent.getLongExtra(EXTRA_EVENT_TIME, 0L));
}
/**
* Build {@link ContentValues} object from {@link DeviceEvent} object.
* @param event a {@link DeviceEvent} object to be converted.
* @return a converted {@link ContentValues} object.
*/
public static ContentValues buildContentValues(final DeviceEvent event) {
return TABLE.buildContentValues(event);
}
/**
* Build {@link Stream<DeviceEvent>} object from {@link Cursor} comes from Content Provider.
* @param cursor a {@link Cursor} object to be converted.
* @return a converted {@link Stream<DeviceEvent>} object.
*/
public static Stream<DeviceEvent> buildStream(final Cursor cursor) {
return TABLE.buildStream(cursor);
}
/**
* Build {@link Predicate<DeviceEvent>} whether a device event comes from {@code sender}
*
* @param sender event sender.
* @return {@link Predicate<DeviceEvent>} object.
*/
public static Predicate<DeviceEvent> isFrom(final String sender) {
return e -> e.sender.equals(sender);
}
/**
* Build {@link Predicate<DeviceEvent>} whether a device event has an event {@code type}.
*
* @param type a event type defined in {@link DeviceEventType}.
* @return {@link Predicate<DeviceEvent>} object.
*/
public static Predicate<DeviceEvent> isType(final DeviceEventType type) {
return e -> e.type == type;
}
/**
* Build {@link Predicate<DeviceEvent>} whether a device event is newer than or equals to
* {@code time}.
*
* @param time a time to compare against.
* @return {@link Predicate<DeviceEvent>} object.
*/
public static Predicate<DeviceEvent> isNewerThan(final long time) {
return e -> e.time >= time;
}
/**
* Event source, either Input Method class name or {@link TestInfo#getTestName()}.
*/
@NonNull
public final String sender;
/**
* Event type, either IME event or Test event.
*/
@NonNull
public final DeviceEventType type;
/**
* Event parameters formatted as json string.
* e.g. {@link DeviceEventTypeParam#ON_START_INPUT_RESTARTING}
*/
public final String paramsString;
/**
* Event time, value is from {@link SystemClock#uptimeMillis()}.
*/
public final long time;
private DeviceEvent(
final String sender, final DeviceEventType type, String paramsString, final long time) {
this.sender = sender;
this.type = type;
this.paramsString = paramsString;
this.time = time;
}
@Override
public String toString() {
return "Event{ time:" + time + " type:" + type + " sender:" + sender + " }";
}
/**
* @param eventParam {@link DeviceEventTypeParam} to look for.
* @param event {@link DeviceEvent} to look in.
* @return Event parameter for provided key. If key is not found in
* {@link DeviceEvent#paramsString}, null is returned.
*
* TODO: Support other primitive and custom types.
*/
@Nullable
public static Boolean getEventParamBoolean(
DeviceEventTypeParam eventParam, final DeviceEvent event) {
StringReader stringReader = new StringReader(event.paramsString);
JsonReader reader = new JsonReader(stringReader);
try {
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
if (name.equals(eventParam.getName())) {
Boolean value = reader.nextBoolean();
reader.endObject();
return value;
}
}
reader.endObject();
} catch (IOException e) {
throw new RuntimeException("DeviceEvent.getEventParamBoolean() failed.", e);
}
return null;
}
/**
* Abstraction of device event table in database.
*/
private static final class DeviceEventTable extends Table<DeviceEvent> {
private static final String LOG_TAG = DeviceEventTable.class.getSimpleName();
private final Field SENDER;
private final Field TYPE;
private final Field TIME;
private final Field PARAMS;
private DeviceEventTable(final String name) {
super(name, new Entity.Builder<DeviceEvent>()
.addField(EventTableConstants.SENDER, Cursor.FIELD_TYPE_STRING)
.addField(EventTableConstants.TYPE, Cursor.FIELD_TYPE_STRING)
.addField(EventTableConstants.TIME, Cursor.FIELD_TYPE_INTEGER)
.addField(EventTableConstants.EXTRAS, Cursor.FIELD_TYPE_STRING)
.build());
SENDER = getField(EventTableConstants.SENDER);
TYPE = getField(EventTableConstants.TYPE);
TIME = getField(EventTableConstants.TIME);
PARAMS = getField(EventTableConstants.EXTRAS);
}
@Override
public ContentValues buildContentValues(final DeviceEvent event) {
final ContentValues values = new ContentValues();
SENDER.putString(values, event.sender);
TYPE.putString(values, event.type.name());
PARAMS.putString(values, event.paramsString);
TIME.putLong(values, event.time);
return values;
}
@Override
public Stream<DeviceEvent> buildStream(Cursor cursor) {
if (DEBUG_STREAM) {
Log.d(LOG_TAG, "buildStream:");
}
final Stream.Builder<DeviceEvent> builder = Stream.builder();
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
final DeviceEvent event = new DeviceEvent(
SENDER.getString(cursor),
DeviceEventType.valueOf(TYPE.getString(cursor)),
PARAMS.getString(cursor),
TIME.getLong(cursor));
builder.accept(event);
if (DEBUG_STREAM) {
Log.d(LOG_TAG, " event=" +event);
}
}
return builder.build();
}
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.db;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import java.util.List;
/**
* Abstraction of SQLite database.
*/
public abstract class Database {
private final SQLiteOpenHelper mHelper;
public Database(final Context context, final String name, final int version) {
mHelper = new SQLiteOpenHelper(context, name, null /* factory */, version) {
@Override
public void onCreate(final SQLiteDatabase db) {
db.beginTransaction();
try {
for (final Table table : getTables()) {
db.execSQL(table.createTableSql());
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion,
final int newVersion) {
// nothing to do so far.
}
};
}
@NonNull
protected abstract List<Table> getTables();
public Cursor query(final String table, final String[] projection, final String selection,
final String[] selectionArgs, final String orderBy) {
return mHelper.getReadableDatabase()
.query(table, projection, selection, selectionArgs, null /* groupBy */,
null /* having */, orderBy);
}
public long insert(final String table, final ContentValues values) {
return mHelper.getWritableDatabase().insert(table, null /* nullColumnHack */, values);
}
public int delete(final String table, final String selection, final String[] selectionArgs) {
return mHelper.getWritableDatabase().delete(table, selection, selectionArgs);
}
public int update(final String table, final ContentValues values, final String selection,
final String[] selectionArgs) {
return mHelper.getWritableDatabase().update(table, values, selection, selectionArgs);
}
public void close() {
mHelper.close();
}
}

View file

@ -0,0 +1,92 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.db;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Abstraction of SQLite database row.
*/
public final class Entity<E> {
private final List<Field> mFields;
private final Map<String, Field> mFieldMap;
private Entity(final Builder<E> builder) {
mFields = builder.mFields;
mFieldMap = builder.mFieldMap;
}
/**
* Returns SQL statement to create this entity/row, such that
* "(_id INTEGER PRIMARY KEY AUTOINCREMENT, column2_name column2_type, ...)".
*/
public String createEntitySql() {
final StringBuilder sb = new StringBuilder("(");
for (final Field field : mFields) {
if (field.pos > 0) sb.append(", ");
sb.append(field.name).append(" ").append(field.sqLiteType);
if (field.name.equals(BaseColumns._ID)) {
sb.append(" PRIMARY KEY AUTOINCREMENT");
}
}
return sb.append(")").toString();
}
public Field getField(final String fieldName) {
return mFieldMap.get(fieldName);
}
/**
* {@link Entity} builder.
*/
public static final class Builder<E> {
private final List<Field> mFields = new ArrayList<>();
private final Map<String, Field> mFieldMap = new HashMap<>();
private int mPos = 0;
public Builder() {
addFieldInternal(BaseColumns._ID, Cursor.FIELD_TYPE_INTEGER);
}
public Builder<E> addField(@NonNull final String name, final int fieldType) {
addFieldInternal(name, fieldType);
return this;
}
public Entity<E> build() {
return new Entity<>(this);
}
private void addFieldInternal(final String name, final int fieldType) {
if (mFieldMap.containsKey(name)) {
throw new IllegalArgumentException("Field " + name + " already exists at "
+ mFieldMap.get(name).pos);
}
final Field field = Field.newInstance(mPos++, name, fieldType);
mFields.add(field);
mFieldMap.put(field.name, field);
}
}
}

View file

@ -0,0 +1,130 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.db;
import android.content.ContentValues;
import android.database.Cursor;
/**
* Abstraction of SQLite database column.
*/
public abstract class Field {
/** Field position in a row. */
final int pos;
/** Column name of this field. */
final String name;
/** Field type of SQLite. */
final String sqLiteType;
public static Field newInstance(final int pos, final String name, final int fieldType) {
switch (fieldType) {
case Cursor.FIELD_TYPE_INTEGER:
return new IntegerField(pos, name);
case Cursor.FIELD_TYPE_STRING:
return new StringField(pos, name);
default:
throw new IllegalArgumentException("Unknown field type: " + fieldType);
}
}
private Field(final int pos, final String name, final int fieldType) {
this.pos = pos;
this.name = name;
this.sqLiteType = toSqLiteType(fieldType);
}
public long getLong(final Cursor cursor) {
throw buildException(Cursor.FIELD_TYPE_INTEGER);
}
public String getString(final Cursor cursor) {
throw buildException(Cursor.FIELD_TYPE_STRING);
}
public void putLong(final ContentValues values, final long value) {
throw buildException(Cursor.FIELD_TYPE_INTEGER);
}
public void putString(final ContentValues values, final String value) {
throw buildException(Cursor.FIELD_TYPE_STRING);
}
private UnsupportedOperationException buildException(final int expectedFieldType) {
return new UnsupportedOperationException("Illegal type: " + name + " is " + sqLiteType
+ ", expected " + toSqLiteType(expectedFieldType));
}
private static String toSqLiteType(final int fieldType) {
switch (fieldType) {
case Cursor.FIELD_TYPE_NULL:
return "NULL";
case Cursor.FIELD_TYPE_INTEGER:
return "INTEGER";
case Cursor.FIELD_TYPE_FLOAT:
return "REAL";
case Cursor.FIELD_TYPE_STRING:
return "TEXT";
case Cursor.FIELD_TYPE_BLOB:
return "BLOB";
default:
throw new IllegalArgumentException("Unknown field type: " + fieldType);
}
}
/**
* Abstraction of INTEGER type field.
*/
private static final class IntegerField extends Field {
IntegerField(final int pos, final String name) {
super(pos, name, Cursor.FIELD_TYPE_INTEGER);
}
@Override
public long getLong(final Cursor cursor) {
return cursor.getLong(pos);
}
@Override
public void putLong(final ContentValues values, final long value) {
values.put(name, value);
}
}
/**
* Abstraction of STRING type field.
*/
private static final class StringField extends Field {
StringField(final int pos, final String name) {
super(pos, name, Cursor.FIELD_TYPE_STRING);
}
@Override
public String getString(final Cursor cursor) {
return cursor.getString(pos);
}
@Override
public void putString(final ContentValues values, final String value) {
values.put(name, value);
}
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.db;
import android.content.ContentValues;
import android.database.Cursor;
import android.support.annotation.NonNull;
import java.util.stream.Stream;
/**
* Abstraction of SQLite database table.
*/
public abstract class Table<E> {
public final String mName;
private final Entity<E> mEntity;
protected Table(final String name, final Entity<E> entity) {
mName = name;
mEntity = entity;
}
public String name() {
return mName;
}
public abstract ContentValues buildContentValues(final E entity);
public abstract Stream<E> buildStream(final Cursor cursor);
/**
* Returns SQL statement to create this table, such that
* "CREATE TABLE IF NOT EXISTS table_name \
* (_id INTEGER PRIMARY KEY AUTOINCREMENT, column2_name column2_type, ...)"
*/
@NonNull
public String createTableSql() {
return "CREATE TABLE IF NOT EXISTS " + mName + " " + mEntity.createEntitySql();
}
protected Field getField(final String fieldName) {
return mEntity.getField(fieldName);
}
}

View file

@ -0,0 +1,185 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.ime;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.HIDE_SOFT_INPUT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_CREATE;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_DESTROY;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT_VIEW;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT_VIEW;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.SHOW_SOFT_INPUT;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.cts.DeviceEvent;
import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
import android.inputmethodservice.cts.ime.ImeCommandReceiver.ImeCommandCallbacks;
import android.os.ResultReceiver;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import java.util.function.Consumer;
public abstract class CtsBaseInputMethod extends InputMethodService implements ImeCommandCallbacks {
protected static final boolean DEBUG = false;
private final ImeCommandReceiver<CtsBaseInputMethod> mImeCommandReceiver =
new ImeCommandReceiver<>();
private String mLogTag;
private class CtsInputMethodImpl extends InputMethodImpl {
@Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
sendEvent(DeviceEvent.builder().setType(SHOW_SOFT_INPUT));
if (DEBUG) {
Log.d(mLogTag, "showSoftInput called");
}
super.showSoftInput(flags, resultReceiver);
}
@Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
sendEvent(DeviceEvent.builder().setType(HIDE_SOFT_INPUT));
if (DEBUG) {
Log.d(mLogTag, "hideSoftInput called");
}
super.hideSoftInput(flags, resultReceiver);
}
}
@Override
public void onCreate() {
mLogTag = getClass().getSimpleName();
if (DEBUG) {
Log.d(mLogTag, "onCreate:");
}
sendEvent(DeviceEvent.builder().setType(ON_CREATE));
super.onCreate();
mImeCommandReceiver.register(this /* ime */);
}
@Override
public void onStartInput(EditorInfo editorInfo, boolean restarting) {
if (DEBUG) {
Log.d(mLogTag, "onStartInput:"
+ " editorInfo=" + editorInfo
+ " restarting=" + restarting);
}
sendEvent(DeviceEvent.builder()
.setType(ON_START_INPUT)
.with(DeviceEventTypeParam.ON_START_INPUT_RESTARTING, restarting));
super.onStartInput(editorInfo, restarting);
}
@Override
public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
if (DEBUG) {
Log.d(mLogTag, "onStartInputView:"
+ " editorInfo=" + editorInfo
+ " restarting=" + restarting);
}
sendEvent(DeviceEvent.builder().setType(ON_START_INPUT_VIEW));
super.onStartInputView(editorInfo, restarting);
}
@Override
public void onFinishInputView(boolean finishingInput) {
if (DEBUG) {
Log.d(mLogTag, "onFinishInputView: finishingInput=" + finishingInput);
}
sendEvent(DeviceEvent.builder().setType(ON_FINISH_INPUT_VIEW));
super.onFinishInputView(finishingInput);
}
@Override
public void onFinishInput() {
if (DEBUG) {
Log.d(mLogTag, "onFinishInput:");
}
sendEvent(DeviceEvent.builder().setType(ON_FINISH_INPUT));
super.onFinishInput();
}
@Override
public void onDestroy() {
if (DEBUG) {
Log.d(mLogTag, "onDestroy:");
}
sendEvent(DeviceEvent.builder().setType(ON_DESTROY));
super.onDestroy();
unregisterReceiver(mImeCommandReceiver);
}
@Override
public AbstractInputMethodImpl onCreateInputMethodInterface() {
final CtsInputMethodImpl inputMethod = new CtsInputMethodImpl();
if (DEBUG) {
Log.d(mLogTag, "onCreateInputMethodInterface");
}
return inputMethod;
}
//
// Implementations of {@link ImeCommandCallbacks}.
//
@Override
public void commandCommitText(final CharSequence text, final int newCursorPosition) {
executeOnInputConnection(ic -> {
// TODO: Log the return value of {@link InputConnection#commitText(CharSequence,int)}.
ic.commitText(text, newCursorPosition);
});
}
@Override
public void commandSwitchInputMethod(final String imeId) {
switchInputMethod(imeId);
}
@Override
public void commandRequestHideSelf(final int flags) {
requestHideSelf(flags);
}
private void executeOnInputConnection(final Consumer<InputConnection> consumer) {
final InputConnection ic = getCurrentInputConnection();
// TODO: Check and log whether {@code ic} is null or equals to
// {@link #getCurrentInputBindin().getConnection()}.
if (ic != null) {
consumer.accept(ic);
}
}
private void sendEvent(final DeviceEvent.IntentBuilder intentBuilder) {
intentBuilder.setSender(getClass().getName());
sendBroadcast(intentBuilder.build());
}
}

View file

@ -0,0 +1,110 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.ime;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.cts.common.ImeCommandConstants;
import android.inputmethodservice.cts.ime.ImeCommandReceiver.ImeCommandCallbacks;
import android.util.Log;
/**
* {@link ImeCommandConstants#ACTION_IME_COMMAND} intent receiver.
*/
final class ImeCommandReceiver<T extends InputMethodService & ImeCommandCallbacks>
extends BroadcastReceiver {
private static final boolean DEBUG = false;
interface ImeCommandCallbacks {
/**
* Callback method for {@link ImeCommandConstants#COMMAND_COMMIT_TEXT} intent.
*
* @param text text to be committed via {@link android.view.inputmethod.InputConnection}.
* @param newCursorPosition new cursor position after commit.
*/
void commandCommitText(final CharSequence text, final int newCursorPosition);
/**
* Callback method for {@link ImeCommandConstants#COMMAND_SWITCH_INPUT_METHOD} intent.
*
* @param imeId IME id to switch.
*/
void commandSwitchInputMethod(final String imeId);
/**
* Callback method for {@link ImeCommandConstants#COMMAND_REQUEST_HIDE_SELF} intent.
*/
void commandRequestHideSelf(final int flags);
}
private T mIme;
void register(final T ime) {
mIme = ime;
ime.registerReceiver(this, new IntentFilter(ImeCommandConstants.ACTION_IME_COMMAND));
}
@Override
public void onReceive(final Context context, final Intent intent) {
final String command = intent.getStringExtra(ImeCommandConstants.EXTRA_COMMAND);
if (DEBUG) {
Log.d(mIme.getClass().getSimpleName(), "onReceive: command=" + command);
}
switch (command) {
case ImeCommandConstants.COMMAND_COMMIT_TEXT: {
final CharSequence text = getCharSequence1(intent);
final int newCursorPosition = getInt1(intent);
mIme.commandCommitText(text, newCursorPosition);
return;
}
case ImeCommandConstants.COMMAND_SWITCH_INPUT_METHOD: {
final String imeId = getString1(intent);
mIme.commandSwitchInputMethod(imeId);
return;
}
case ImeCommandConstants.COMMAND_REQUEST_HIDE_SELF: {
final int flags = getInt1(intent);
mIme.commandRequestHideSelf(flags);
return;
}
default: {
throw new UnsupportedOperationException("Unknown IME command: " + command);
}
}
}
private static CharSequence getCharSequence1(final Intent intent) {
return intent.getCharSequenceExtra(ImeCommandConstants.EXTRA_ARG_CHARSEQUENCE1);
}
private static String getString1(final Intent intent) {
return intent.getStringExtra(ImeCommandConstants.EXTRA_ARG_STRING1);
}
private static int getInt1(final Intent intent) {
if (intent.hasExtra(ImeCommandConstants.EXTRA_ARG_INT1)) {
return intent.getIntExtra(ImeCommandConstants.EXTRA_ARG_INT1, 0);
}
throw new IllegalArgumentException(
"Needs " + ImeCommandConstants.EXTRA_ARG_INT1 + " in " + intent);
}
}

View file

@ -0,0 +1,40 @@
# Copyright (C) 2017 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.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Don't include this package in any target
LOCAL_MODULE_TAGS := tests
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_DEX_PREOPT := false
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
CtsInputMethodServiceCommon \
CtsInputMethodServiceLib
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_PACKAGE_NAME := CtsInputMethodServiceEventProvider
LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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="android.inputmethodservice.cts.provider">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
<application android:label="CtsInputMethodServiceEventProvider">
<provider
android:authorities="android.inputmethodservice.cts.provider"
android:name="android.inputmethodservice.cts.provider.EventProvider"
android:exported="true" />
<receiver android:name="android.inputmethodservice.cts.receiver.EventReceiver">
<intent-filter>
<action android:name="android.inputmethodservice.cts.action.IME_EVENT" />
</intent-filter>
</receiver>
</application>
</manifest>

View file

@ -0,0 +1,161 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.provider;
import static android.inputmethodservice.cts.common.EventProviderConstants.AUTHORITY;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
import android.inputmethodservice.cts.db.Database;
import android.inputmethodservice.cts.db.Table;
import android.inputmethodservice.cts.DeviceEvent;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* IME event content provider.
*/
public final class EventProvider extends ContentProvider {
private static final String TAG = EventProvider.class.getSimpleName();
private static final boolean DEBUG = false;
private static final String DB_NAME = "database";
private static final int DB_VERSION = 1;
private UriHelper.Factory mUriFactory;
private Database mDatabase;
@Override
public boolean onCreate() {
mUriFactory = UriHelper.Factory.builder()
.addUri(AUTHORITY, EventTableConstants.NAME, EventTableConstants.TYPE_DIR)
.addUri(AUTHORITY, EventTableConstants.NAME + "/#", EventTableConstants.TYPE_ITEM)
.build();
mDatabase = new Database(getContext(), DB_NAME, DB_VERSION) {
@Override
@NonNull
protected List<Table> getTables() {
return Collections.singletonList(DeviceEvent.TABLE);
}
};
return true;
}
@Override
public Cursor query(@NonNull final Uri uri, @Nullable final String[] projection,
final @Nullable String selection, @Nullable final String[] selectionArgs,
@Nullable final String orderBy) {
final UriHelper uriHelper = mUriFactory.newInstance(uri);
if (DEBUG) {
Log.d(TAG, "query:"
+ " uri=" + uri
+ " projection=" + Arrays.toString(projection)
+ " selection=" + uriHelper.buildSelection(selection)
+ " selectionArgs=" + Arrays.toString(
uriHelper.buildSelectionArgs(selectionArgs))
+ " orderBy=" + orderBy);
}
final Cursor cursor = mDatabase.query(
uriHelper.table, projection, uriHelper.buildSelection(selection),
uriHelper.buildSelectionArgs(selectionArgs), orderBy);
if (DEBUG) {
Log.d(TAG, " query.count=" + cursor.getCount());
}
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public Uri insert(@NonNull final Uri uri, @Nullable final ContentValues values) {
final UriHelper uriHelper = mUriFactory.newInstance(uri);
if (DEBUG) {
Log.d(TAG, "insert: uri=" + uri + " values={" + values + "}");
}
final long rowId = mDatabase.insert(uriHelper.table, values);
final Uri insertedUri = ContentUris.withAppendedId(uri, rowId);
if (DEBUG) {
Log.d(TAG, " insert.uri=" + insertedUri);
}
getContext().getContentResolver().notifyChange(insertedUri, null);
return insertedUri;
}
@Override
public int delete(@NonNull final Uri uri, @Nullable final String selection,
@Nullable final String[] selectionArgs) {
final UriHelper uriHelper = mUriFactory.newInstance(uri);
if (DEBUG) {
Log.d(TAG, "delete:"
+ " uri=" + uri
+ " selection=" + uriHelper.buildSelection(selection)
+ " selectionArgs=" + Arrays.toString(
uriHelper.buildSelectionArgs(selectionArgs)));
}
final int count = mDatabase.delete(uriHelper.table, uriHelper.buildSelection(selection),
uriHelper.buildSelectionArgs(selectionArgs));
if (DEBUG) {
Log.d(TAG, " delete.count=" + count);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public int update(@NonNull final Uri uri, @Nullable final ContentValues values,
final @Nullable String selection, @Nullable final String[] selectionArgs) {
final UriHelper uriHelper = mUriFactory.newInstance(uri);
if (DEBUG) {
Log.d(TAG, "update:"
+ " uri=" + uri
+ " values={" + values + "}"
+ " selection=" + uriHelper.buildSelection(selection)
+ " selectionArgs=" + Arrays.toString(
uriHelper.buildSelectionArgs(selectionArgs)));
}
final int count = mDatabase.update(uriHelper.table, values,
uriHelper.buildSelection(selection), uriHelper.buildSelectionArgs(selectionArgs));
if (DEBUG) {
Log.d(TAG, " update.count=" + count);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
@Nullable
public String getType(@NonNull final Uri uri) {
return mUriFactory.getTypeOf(uri);
}
@Override
public void shutdown() {
super.shutdown();
mDatabase.close();
mDatabase = null;
mUriFactory = null;
}
}

View file

@ -0,0 +1,160 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.provider;
import android.content.UriMatcher;
import android.net.Uri;
import android.provider.BaseColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.SparseArray;
import java.util.List;
/**
* Content URI helper.
*
* Helper object to parse content URI passed to content provider. A helper object is instantiated
* via {@link Factory#newInstance(Uri)}, and a {@link Factory} object should be instantiated using
* {@link FactoryBuilder}.
*
* A content URI is assumed to have a format "content://authority/table[/id]?" where table is a
* SQLite table name in content provider and id is a primary key.
*/
final class UriHelper {
static final class Factory {
private final UriMatcher mUriMatcher;
private final SparseArray<String> mUriTypeMap;
public static FactoryBuilder builder() {
return new FactoryBuilder();
}
private Factory(final FactoryBuilder builder) {
mUriMatcher = builder.mUriMatcher;
mUriTypeMap = builder.mUriTypeMap;
}
@NonNull
UriHelper newInstance(final Uri uri) {
if (mUriMatcher.match(uri) == UriMatcher.NO_MATCH) {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
return new UriHelper(uri);
}
@Nullable
String getTypeOf(final Uri uri) {
return mUriTypeMap.get(mUriMatcher.match(uri), null);
}
}
static final class FactoryBuilder {
private final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private final SparseArray<String> mUriTypeMap = new SparseArray<>();
private int mMatcherCode;
private FactoryBuilder() {
mMatcherCode = 0;
}
FactoryBuilder addUri(final String authority, final String path, final String type) {
if (TextUtils.isEmpty(authority)) {
throw new IllegalArgumentException("Authority must not be empty");
}
if (TextUtils.isEmpty(path)) {
throw new IllegalArgumentException("Path must not be empty");
}
final int matcherCode = mMatcherCode++;
mUriMatcher.addURI(authority, path, matcherCode);
mUriTypeMap.append(matcherCode, type);
return this;
}
Factory build() {
if (mMatcherCode == 0) {
throw new IllegalStateException("No URI is defined");
}
return new Factory(this);
}
}
/** Name of SQLite table specified by content uri. */
@NonNull
final String table;
/** Primary id that is specified by content uri. Null if not. */
@Nullable
private final String mId;
private UriHelper(final Uri uri) {
final List<String> segments = uri.getPathSegments();
table = segments.get(0);
mId = (segments.size() >= 2) ? segments.get(1) : null;
}
/**
* Composes selection SQL text from content uri and {@code selection} specified.
* When content uri has a primary key, it needs to be composed with a selection text specified
* as content provider parameter.
*
* @param selection selection text specified as a parameter to content provider.
* @return composed selection SQL text, null if no selection specified.
*/
@Nullable
String buildSelection(@Nullable final String selection) {
if (mId == null) {
return selection;
}
// A primary key is specified by uri, so that selection should be at least "_id = ?".
final StringBuilder sb = new StringBuilder().append(BaseColumns._ID).append(" = ?");
if (selection != null) {
// Selection is also specified as a parameter to content provider, so that it should be
// appended with AND, such that "_id = ? AND (selection_text)".
sb.append(" AND (").append(selection).append(")");
}
return sb.toString();
}
/**
* Composes selection argument array from context uri and {@code selectionArgs} specified.
* When content uri has a primary key, it needs to be provided in a final selection argument
* array.
*
* @param selectionArgs selection argument array specified as a parameter to content provider.
* @return composed selection argument array, null if selection argument is unnecessary.
*/
@Nullable
String[] buildSelectionArgs(@Nullable final String[] selectionArgs) {
if (mId == null) {
return selectionArgs;
}
// A primary key is specified by uri but not as a parameter to content provider, the primary
// key value should be the sole selection argument.
if (selectionArgs == null || selectionArgs.length == 0) {
return new String[]{ mId };
}
// Selection args are also specified as a parameter to content provider, the primary key
// value should be prepended to those selection args.
final String[] args = new String[selectionArgs.length + 1];
System.arraycopy(selectionArgs, 0, args, 1, selectionArgs.length);
args[0] = mId;
return args;
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.receiver;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TIME;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.inputmethodservice.cts.DeviceEvent;
import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
import android.net.Uri;
import android.os.SystemClock;
import android.util.Log;
public final class EventReceiver extends BroadcastReceiver {
private static final String TAG = EventReceiver.class.getSimpleName();
private static final boolean DEBUG = false;
private static final Uri CONTENT_URI = Uri.parse(EventTableConstants.CONTENT_URI);
@Override
public void onReceive(final Context context, final Intent intent) {
// Since {@code intent} which comes from host has no
// {@link DeviceEventConstants#EXTRA_EVENT_TIME EXTRA_EVENT_TIME} extra, here we record the
// time.
if (!intent.hasExtra(EXTRA_EVENT_TIME)) {
intent.putExtra(EXTRA_EVENT_TIME, SystemClock.uptimeMillis());
}
final DeviceEvent event = DeviceEvent.newEvent(intent);
if (DEBUG) {
Log.d(TAG, "onReceive: event=" + event);
}
final ContentValues values = DeviceEvent.buildContentValues(event);
context.getContentResolver().insert(CONTENT_URI, values);
}
}

View file

@ -0,0 +1,35 @@
# Copyright (C) 2017 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.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MODULE_TAGS := tests
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_MODULE := CtsInputMethodServiceHostTestCases
LOCAL_JAVA_LIBRARIES := \
compatibility-host-util \
cts-tradefed \
tradefed
LOCAL_STATIC_JAVA_LIBRARIES := \
cts-inputmethodservice-common-host
include $(BUILD_CTS_HOST_JAVA_LIBRARY)

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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="Config for CTS Input Method Service host test cases">
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
<option name="run-command" value="wm dismiss-keyguard" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsInputMethodServiceEventProvider.apk" />
<option name="test-file-name" value="CtsInputMethodServiceDeviceTests.apk" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsInputMethodServiceHostTestCases.jar" />
</test>
</configuration>

View file

@ -0,0 +1,173 @@
/*
* Copyright (C) 2017 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 android.inputmethodservice.cts.hostside;
import static android.inputmethodservice.cts.common.DeviceEventConstants.ACTION_DEVICE_EVENT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.TEST_START;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_SENDER;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE;
import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_COMPONENT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
import android.inputmethodservice.cts.common.Ime1Constants;
import android.inputmethodservice.cts.common.Ime2Constants;
import android.inputmethodservice.cts.common.test.DeviceTestConstants;
import android.inputmethodservice.cts.common.test.ShellCommandUtils;
import android.inputmethodservice.cts.common.test.TestInfo;
import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBase;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(DeviceJUnit4ClassRunner.class)
public class InputMethodServiceLifecycleTest extends CompatibilityHostTestBase {
private String mDefaultImeId;
@Before
public void setUp() throws Exception {
// Skip whole tests when DUT has no android.software.input_methods feature.
assumeTrue(Boolean.parseBoolean(shell(
ShellCommandUtils.hasFeature(ShellCommandUtils.FEATURE_INPUT_METHODS))));
mDefaultImeId = shell(ShellCommandUtils.getCurrentIme());
cleanUpTestImes();
shell(ShellCommandUtils.deleteContent(EventTableConstants.CONTENT_URI));
}
@After
public void tearDown() throws Exception {
shell(ShellCommandUtils.setCurrentIme(mDefaultImeId));
cleanUpTestImes();
}
@Test
public void testSwitchIme() throws Exception {
final TestInfo testSwitchIme1ToIme2 = new TestInfo(DeviceTestConstants.PACKAGE,
DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_SWITCH_IME1_TO_IME2);
sendTestStartEvent(testSwitchIme1ToIme2);
installPackage(Ime1Constants.APK, "-r");
installPackage(Ime2Constants.APK, "-r");
shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID));
shell(ShellCommandUtils.setCurrentIme(Ime1Constants.IME_ID));
assertTrue(runDeviceTestMethod(testSwitchIme1ToIme2));
}
@Test
public void testUninstallCurrentIme() throws Exception {
installAndSetIme1();
final TestInfo testIme1IsNotCurrentIme = new TestInfo(DeviceTestConstants.PACKAGE,
DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_IME1_IS_NOT_CURRENT_IME);
sendTestStartEvent(testIme1IsNotCurrentIme);
uninstallPackageIfExists(Ime1Constants.PACKAGE);
assertTrue(runDeviceTestMethod(testIme1IsNotCurrentIme));
assertEquals(shell(ShellCommandUtils.getCurrentIme()), mDefaultImeId);
}
@Test
public void testDisableCurrentIme() throws Exception {
installAndSetIme1();
final TestInfo testIme1IsNotCurrentIme = new TestInfo(DeviceTestConstants.PACKAGE,
DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_IME1_IS_NOT_CURRENT_IME);
sendTestStartEvent(testIme1IsNotCurrentIme);
shell(ShellCommandUtils.disableIme(Ime1Constants.IME_ID));
assertTrue(runDeviceTestMethod(testIme1IsNotCurrentIme));
assertEquals(shell(ShellCommandUtils.getCurrentIme()), mDefaultImeId);
}
@Test
public void testSearchView_giveFocusShowIme() throws Exception {
installAndSetIme1();
final TestInfo testGiveFocusShowIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
DeviceTestConstants.TEST_CLASS,
DeviceTestConstants.TEST_SEARCH_VIEW_GIVE_FOCUS_SHOW_IME1);
sendTestStartEvent(testGiveFocusShowIme1);
assertTrue(runDeviceTestMethod(testGiveFocusShowIme1));
}
@Test
public void testSearchView_setQueryHideIme() throws Exception {
installAndSetIme1();
final TestInfo testSetQueryHideIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
DeviceTestConstants.TEST_CLASS,
DeviceTestConstants.TEST_SEARCH_VIEW_SET_QUERY_HIDE_IME1);
sendTestStartEvent(testSetQueryHideIme1);
assertTrue(runDeviceTestMethod(testSetQueryHideIme1));
}
@Test
public void testOnStartInputCalledOnce() throws Exception {
installAndSetIme1();
final TestInfo testSetQueryHideIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
DeviceTestConstants.TEST_CLASS,
DeviceTestConstants.TEST_ON_START_INPUT_CALLED_ONCE_IME1);
sendTestStartEvent(testSetQueryHideIme1);
assertTrue(runDeviceTestMethod(testSetQueryHideIme1));
}
private void installAndSetIme1() throws Exception {
final TestInfo testCreateIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_CREATE_IME1);
sendTestStartEvent(testCreateIme1);
installPackage(Ime1Constants.APK, "-r");
shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
shell(ShellCommandUtils.setCurrentIme(Ime1Constants.IME_ID));
assertTrue(runDeviceTestMethod(testCreateIme1));
}
private void sendTestStartEvent(final TestInfo deviceTest) throws Exception {
final String sender = deviceTest.getTestName();
// {@link EventType#EXTRA_EVENT_TIME} will be recorded at device side.
shell(ShellCommandUtils.broadcastIntent(
ACTION_DEVICE_EVENT, RECEIVER_COMPONENT,
"--es", EXTRA_EVENT_SENDER, sender,
"--es", EXTRA_EVENT_TYPE, TEST_START.name()));
}
private boolean runDeviceTestMethod(final TestInfo deviceTest) throws Exception {
return runDeviceTests(deviceTest.testPackage, deviceTest.testClass, deviceTest.testMethod);
}
private String shell(final String command) throws Exception {
return getDevice().executeShellCommand(command).trim();
}
private void cleanUpTestImes() throws Exception {
uninstallPackageIfExists(Ime1Constants.PACKAGE);
uninstallPackageIfExists(Ime2Constants.PACKAGE);
}
private void uninstallPackageIfExists(final String packageName) throws Exception {
if (isPackageInstalled(packageName)) {
uninstallPackage(packageName);
}
}
}