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.isType;
21 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.TEST_START;
22 
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.inputmethodservice.cts.DeviceEvent;
28 import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
29 import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
30 import android.inputmethodservice.cts.common.test.TestInfo;
31 import android.net.Uri;
32 import android.support.test.uiautomator.By;
33 import android.support.test.uiautomator.UiDevice;
34 import android.support.test.uiautomator.UiObject2;
35 import android.support.test.uiautomator.Until;
36 
37 import androidx.test.InstrumentationRegistry;
38 
39 import org.junit.Test;
40 
41 import java.io.IOException;
42 import java.lang.reflect.Method;
43 import java.util.concurrent.TimeUnit;
44 import java.util.function.Predicate;
45 import java.util.stream.Stream;
46 
47 /**
48  * Helper object for device side test.
49  */
50 final class TestHelper {
51 
52     /** Content URI of device event provider. */
53     private static final Uri DEVICE_EVENTS_CONTENT_URI = Uri.parse(EventTableConstants.CONTENT_URI);
54     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
55 
56     private final TestInfo mTestInfo;
57     private final ContentResolver mResolver;
58     private final Context mTargetContext;
59     private final UiDevice mUiDevice;
60 
61     /**
62      * @return {@link Method} that has {@link Test} annotation in the call stack.
63      *
64      * <p>Note: This can be removed once we switch to JUnit5, where org.junit.jupiter.api.TestInfo
65      * is supported in the framework level.</p>
66      */
getTestMethod()67     private static Method getTestMethod() {
68         for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) {
69             try {
70                 final Class<?> clazz = Class.forName(stackTraceElement.getClassName());
71                 final Method method = clazz.getDeclaredMethod(stackTraceElement.getMethodName());
72                 final Test annotation = method.getAnnotation(Test.class);
73                 if (annotation != null) {
74                     return method;
75                 }
76             } catch (ClassNotFoundException | NoSuchMethodException e) {
77             }
78         }
79         throw new IllegalStateException(
80                 "Method that has @Test annotation is not found in the call stack.");
81     }
82 
83     /**
84      * Construct a helper object of specified test method.
85      */
TestHelper()86     TestHelper() {
87         final Method testMethod = getTestMethod();
88         final Class<?> testClass = testMethod.getDeclaringClass();
89         final Context testContext = InstrumentationRegistry.getContext();
90         mTestInfo = new TestInfo(testContext.getPackageName(), testClass.getName(),
91                 testMethod.getName());
92         mResolver = testContext.getContentResolver();
93         mTargetContext = InstrumentationRegistry.getTargetContext();
94         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
95     }
96 
97     /**
98      * Execute a shell {@code command} and return its standard out.
99      * @param command a shell command text to execute.
100      * @return command's standard output without ending newline.
101      * @throws IOException
102      */
shell(String command)103     String shell(String command) throws IOException {
104         return mUiDevice.executeShellCommand(command).trim();
105     }
106 
107     /**
108      * Launching an Activity for test, and wait for completions of launch.
109      * @param packageName activity's app package name.
110      * @param className activity's class name.
111      * @param uri uri to be handled.
112      */
launchActivity(String packageName, String className, String uri)113     void launchActivity(String packageName, String className, String uri) {
114         final Intent intent = new Intent()
115                 .setAction(Intent.ACTION_VIEW)
116                 .addCategory(Intent.CATEGORY_BROWSABLE)
117                 .addCategory(Intent.CATEGORY_DEFAULT)
118                 .setData(Uri.parse(uri))
119                 .setClassName(packageName, className)
120                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
121                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
122         InstrumentationRegistry.getContext().startActivity(intent);
123         mUiDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), TIMEOUT);
124     }
125 
126     /**
127      * Find an UI element from resource ID.
128      * @param resourceName name of finding UI element.
129      * @return {@link UiObject2} of found UI element.
130      */
findUiObject(String resourceName)131     UiObject2 findUiObject(String resourceName) {
132         return mUiDevice.findObject(By.res(resourceName));
133     }
134 
135     /**
136      * Return all device events as {@link Stream}
137      * @return {@link Stream<DeviceEvent>} of all device events.
138      */
queryAllEvents()139     Stream<DeviceEvent> queryAllEvents() {
140         try (Cursor cursor = mResolver.query(
141                 DEVICE_EVENTS_CONTENT_URI,
142                 null /* projection */,
143                 null /* selection */,
144                 null /* selectionArgs */,
145                 null /* sortOrder */)) {
146             return DeviceEvent.buildStream(cursor);
147         }
148     }
149 
150     /**
151      * Build a {@link Predicate} can be used for skipping device events in {@link Stream} until
152      * {@link DeviceEventType#TEST_START TEST_START} device event of this test method.
153      * @return {@llink Predicate<DeviceEvent>} that return true after accepting
154      *         {@link DeviceEventType#TEST_START TEST_START} of this test method.
155      */
isStartOfTest()156     Predicate<DeviceEvent> isStartOfTest() {
157         return isFrom(mTestInfo.getTestName()).and(isType(TEST_START));
158     }
159 }
160