1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package android.inputmethodservice.cts.devicetest; 18 19 import static android.inputmethodservice.cts.DeviceEvent.isFrom; 20 import static android.inputmethodservice.cts.DeviceEvent.isNewerThan; 21 import static android.inputmethodservice.cts.DeviceEvent.isType; 22 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType 23 .ON_BIND_INPUT; 24 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_CREATE; 25 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_DESTROY; 26 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT; 27 28 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType 29 .ON_UNBIND_INPUT; 30 import static android.inputmethodservice.cts.common.ImeCommandConstants.ACTION_IME_COMMAND; 31 import static android.inputmethodservice.cts.common.ImeCommandConstants 32 .COMMAND_SWITCH_INPUT_METHOD_WITH_SUBTYPE; 33 import static android.inputmethodservice.cts.common.ImeCommandConstants.COMMAND_SWITCH_INPUT_METHOD; 34 import static android.inputmethodservice.cts.common.ImeCommandConstants 35 .COMMAND_SWITCH_TO_PREVIOUS_INPUT; 36 import static android.inputmethodservice.cts.common.ImeCommandConstants 37 .COMMAND_SWITCH_TO_NEXT_INPUT; 38 import static android.inputmethodservice.cts.common.ImeCommandConstants.EXTRA_ARG_STRING1; 39 import static android.inputmethodservice.cts.common.ImeCommandConstants.EXTRA_COMMAND; 40 import static android.inputmethodservice.cts.devicetest.BusyWaitUtils.pollingCheck; 41 import static android.inputmethodservice.cts.devicetest.MoreCollectors.startingFrom; 42 43 import android.inputmethodservice.cts.DeviceEvent; 44 import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType; 45 import android.inputmethodservice.cts.common.EditTextAppConstants; 46 import android.inputmethodservice.cts.common.Ime1Constants; 47 import android.inputmethodservice.cts.common.Ime2Constants; 48 import android.inputmethodservice.cts.common.test.DeviceTestConstants; 49 import android.inputmethodservice.cts.common.test.ShellCommandUtils; 50 import android.inputmethodservice.cts.devicetest.SequenceMatcher.MatchResult; 51 import android.os.SystemClock; 52 import android.support.test.runner.AndroidJUnit4; 53 54 import org.junit.Test; 55 import org.junit.runner.RunWith; 56 57 import java.util.Arrays; 58 import java.util.concurrent.TimeUnit; 59 import java.util.function.IntFunction; 60 import java.util.function.Predicate; 61 import java.util.stream.Collector; 62 63 @RunWith(AndroidJUnit4.class) 64 public class InputMethodServiceDeviceTest { 65 66 private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5); 67 68 /** Test to check CtsInputMethod1 receives onCreate and onStartInput. */ 69 @Test testCreateIme1()70 public void testCreateIme1() throws Throwable { 71 final TestHelper helper = new TestHelper(getClass(), DeviceTestConstants.TEST_CREATE_IME1); 72 73 final long startActivityTime = SystemClock.uptimeMillis(); 74 helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS, 75 EditTextAppConstants.URI); 76 77 pollingCheck(() -> helper.queryAllEvents() 78 .collect(startingFrom(helper.isStartOfTest())) 79 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE))), 80 TIMEOUT, "CtsInputMethod1.onCreate is called"); 81 pollingCheck(() -> helper.queryAllEvents() 82 .filter(isNewerThan(startActivityTime)) 83 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))), 84 TIMEOUT, "CtsInputMethod1.onStartInput is called"); 85 } 86 87 /** Test to check IME is switched from CtsInputMethod1 to CtsInputMethod2. */ 88 @Test testSwitchIme1ToIme2()89 public void testSwitchIme1ToIme2() throws Throwable { 90 final TestHelper helper = new TestHelper( 91 getClass(), DeviceTestConstants.TEST_SWITCH_IME1_TO_IME2); 92 93 final long startActivityTime = SystemClock.uptimeMillis(); 94 helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS, 95 EditTextAppConstants.URI); 96 97 pollingCheck(() -> helper.queryAllEvents() 98 .collect(startingFrom(helper.isStartOfTest())) 99 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE))), 100 TIMEOUT, "CtsInputMethod1.onCreate is called"); 101 pollingCheck(() -> helper.queryAllEvents() 102 .filter(isNewerThan(startActivityTime)) 103 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))), 104 TIMEOUT, "CtsInputMethod1.onStartInput is called"); 105 106 helper.findUiObject(EditTextAppConstants.EDIT_TEXT_RES_NAME).click(); 107 108 // Switch IME from CtsInputMethod1 to CtsInputMethod2. 109 final long switchImeTime = SystemClock.uptimeMillis(); 110 helper.shell(ShellCommandUtils.broadcastIntent( 111 ACTION_IME_COMMAND, Ime1Constants.PACKAGE, 112 "-e", EXTRA_COMMAND, COMMAND_SWITCH_INPUT_METHOD, 113 "-e", EXTRA_ARG_STRING1, Ime2Constants.IME_ID)); 114 115 pollingCheck(() -> helper.shell(ShellCommandUtils.getCurrentIme()) 116 .equals(Ime2Constants.IME_ID), 117 TIMEOUT, "CtsInputMethod2 is current IME"); 118 pollingCheck(() -> helper.queryAllEvents() 119 .filter(isNewerThan(switchImeTime)) 120 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_DESTROY))), 121 TIMEOUT, "CtsInputMethod1.onDestroy is called"); 122 pollingCheck(() -> helper.queryAllEvents() 123 .filter(isNewerThan(switchImeTime)) 124 .filter(isFrom(Ime2Constants.CLASS)) 125 .collect(sequenceOfTypes(ON_CREATE, ON_BIND_INPUT, ON_START_INPUT)) 126 .matched(), 127 TIMEOUT, 128 "CtsInputMethod2.onCreate, onBindInput, and onStartInput are called" 129 + " in sequence"); 130 } 131 132 @Test testSwitchInputMethod()133 public void testSwitchInputMethod() throws Throwable { 134 final TestHelper helper = new TestHelper( 135 getClass(), DeviceTestConstants.TEST_SWITCH_INPUTMETHOD); 136 final long startActivityTime = SystemClock.uptimeMillis(); 137 helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS, 138 EditTextAppConstants.URI); 139 pollingCheck(() -> helper.queryAllEvents() 140 .filter(isNewerThan(startActivityTime)) 141 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))), 142 TIMEOUT, "CtsInputMethod1.onStartInput is called"); 143 helper.findUiObject(EditTextAppConstants.EDIT_TEXT_RES_NAME).click(); 144 145 final long setImeTime = SystemClock.uptimeMillis(); 146 // call setInputMethodAndSubtype(IME2, null) 147 helper.shell(ShellCommandUtils.broadcastIntent( 148 ACTION_IME_COMMAND, Ime1Constants.PACKAGE, 149 "-e", EXTRA_COMMAND, COMMAND_SWITCH_INPUT_METHOD_WITH_SUBTYPE, 150 "-e", EXTRA_ARG_STRING1, Ime2Constants.IME_ID)); 151 pollingCheck(() -> helper.shell(ShellCommandUtils.getCurrentIme()) 152 .equals(Ime2Constants.IME_ID), 153 TIMEOUT, "CtsInputMethod2 is current IME"); 154 pollingCheck(() -> helper.queryAllEvents() 155 .filter(isNewerThan(setImeTime)) 156 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_DESTROY))), 157 TIMEOUT, "CtsInputMethod1.onDestroy is called"); 158 } 159 160 @Test testSwitchToNextInputMethod()161 public void testSwitchToNextInputMethod() throws Throwable { 162 final TestHelper helper = new TestHelper( 163 getClass(), DeviceTestConstants.TEST_SWITCH_NEXT_INPUT); 164 final long startActivityTime = SystemClock.uptimeMillis(); 165 helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS, 166 EditTextAppConstants.URI); 167 pollingCheck(() -> helper.queryAllEvents() 168 .filter(isNewerThan(startActivityTime)) 169 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))), 170 TIMEOUT, "CtsInputMethod1.onStartInput is called"); 171 helper.findUiObject(EditTextAppConstants.EDIT_TEXT_RES_NAME).click(); 172 173 pollingCheck(() -> helper.shell(ShellCommandUtils.getCurrentIme()) 174 .equals(Ime1Constants.IME_ID), 175 TIMEOUT, "CtsInputMethod1 is current IME"); 176 helper.shell(ShellCommandUtils.broadcastIntent( 177 ACTION_IME_COMMAND, Ime1Constants.PACKAGE, 178 "-e", EXTRA_COMMAND, COMMAND_SWITCH_TO_NEXT_INPUT)); 179 pollingCheck(() -> !helper.shell(ShellCommandUtils.getCurrentIme()) 180 .equals(Ime1Constants.IME_ID), 181 TIMEOUT, "CtsInputMethod1 shouldn't be current IME"); 182 } 183 184 @Test switchToPreviousInputMethod()185 public void switchToPreviousInputMethod() throws Throwable { 186 final TestHelper helper = new TestHelper( 187 getClass(), DeviceTestConstants.TEST_SWITCH_PREVIOUS_INPUT); 188 final long startActivityTime = SystemClock.uptimeMillis(); 189 helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS, 190 EditTextAppConstants.URI); 191 helper.findUiObject(EditTextAppConstants.EDIT_TEXT_RES_NAME).click(); 192 193 final String initialIme = helper.shell(ShellCommandUtils.getCurrentIme()); 194 helper.shell(ShellCommandUtils.setCurrentIme(Ime2Constants.IME_ID)); 195 pollingCheck(() -> helper.queryAllEvents() 196 .filter(isNewerThan(startActivityTime)) 197 .anyMatch(isFrom(Ime2Constants.CLASS).and(isType(ON_START_INPUT))), 198 TIMEOUT, "CtsInputMethod2.onStartInput is called"); 199 helper.shell(ShellCommandUtils.broadcastIntent( 200 ACTION_IME_COMMAND, Ime2Constants.PACKAGE, 201 "-e", EXTRA_COMMAND, COMMAND_SWITCH_TO_PREVIOUS_INPUT)); 202 pollingCheck(() -> helper.shell(ShellCommandUtils.getCurrentIme()) 203 .equals(initialIme), 204 TIMEOUT, initialIme + " is current IME"); 205 } 206 207 @Test testInputUnbindsOnImeStopped()208 public void testInputUnbindsOnImeStopped() throws Throwable { 209 final TestHelper helper = new TestHelper( 210 getClass(), DeviceTestConstants.TEST_INPUT_UNBINDS_ON_IME_STOPPED); 211 final long startActivityTime = SystemClock.uptimeMillis(); 212 helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS, 213 EditTextAppConstants.URI); 214 helper.findUiObject(EditTextAppConstants.EDIT_TEXT_RES_NAME).click(); 215 216 pollingCheck(() -> helper.queryAllEvents() 217 .filter(isNewerThan(startActivityTime)) 218 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))), 219 TIMEOUT, "CtsInputMethod1.onStartInput is called"); 220 pollingCheck(() -> helper.queryAllEvents() 221 .filter(isNewerThan(startActivityTime)) 222 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_BIND_INPUT))), 223 TIMEOUT, "CtsInputMethod1.onBindInput is called"); 224 225 final long imeForceStopTime = SystemClock.uptimeMillis(); 226 helper.shell(ShellCommandUtils.uninstallPackage(Ime1Constants.PACKAGE)); 227 228 helper.shell(ShellCommandUtils.setCurrentIme(Ime2Constants.IME_ID)); 229 helper.findUiObject(EditTextAppConstants.EDIT_TEXT_RES_NAME).click(); 230 pollingCheck(() -> helper.queryAllEvents() 231 .filter(isNewerThan(imeForceStopTime)) 232 .anyMatch(isFrom(Ime2Constants.CLASS).and(isType(ON_START_INPUT))), 233 TIMEOUT, "CtsInputMethod2.onStartInput is called"); 234 pollingCheck(() -> helper.queryAllEvents() 235 .filter(isNewerThan(imeForceStopTime)) 236 .anyMatch(isFrom(Ime2Constants.CLASS).and(isType(ON_BIND_INPUT))), 237 TIMEOUT, "CtsInputMethod2.onBindInput is called"); 238 } 239 240 @Test testInputUnbindsOnAppStopped()241 public void testInputUnbindsOnAppStopped() throws Throwable { 242 final TestHelper helper = new TestHelper( 243 getClass(), DeviceTestConstants.TEST_INPUT_UNBINDS_ON_APP_STOPPED); 244 final long startActivityTime = SystemClock.uptimeMillis(); 245 helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS, 246 EditTextAppConstants.URI); 247 helper.findUiObject(EditTextAppConstants.EDIT_TEXT_RES_NAME).click(); 248 249 pollingCheck(() -> helper.queryAllEvents() 250 .filter(isNewerThan(startActivityTime)) 251 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))), 252 TIMEOUT, "CtsInputMethod1.onStartInput is called"); 253 pollingCheck(() -> helper.queryAllEvents() 254 .filter(isNewerThan(startActivityTime)) 255 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_BIND_INPUT))), 256 TIMEOUT, "CtsInputMethod1.onBindInput is called"); 257 258 helper.shell(ShellCommandUtils.uninstallPackage(EditTextAppConstants.PACKAGE)); 259 260 pollingCheck(() -> helper.queryAllEvents() 261 .filter(isNewerThan(startActivityTime)) 262 .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_UNBIND_INPUT))), 263 TIMEOUT, "CtsInputMethod1.onUnBindInput is called"); 264 } 265 266 /** 267 * Build stream collector of {@link DeviceEvent} collecting sequence that elements have 268 * specified types. 269 * 270 * @param types {@link DeviceEventType}s that elements of sequence should have. 271 * @return {@link java.util.stream.Collector} that corrects the sequence. 272 */ sequenceOfTypes( final DeviceEventType... types)273 private static Collector<DeviceEvent, ?, MatchResult<DeviceEvent>> sequenceOfTypes( 274 final DeviceEventType... types) { 275 final IntFunction<Predicate<DeviceEvent>[]> arraySupplier = Predicate[]::new; 276 return SequenceMatcher.of(Arrays.stream(types) 277 .map(DeviceEvent::isType) 278 .toArray(arraySupplier)); 279 } 280 } 281