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