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.view.inputmethod.cts.util;
18 
19 import static org.junit.Assert.fail;
20 import static org.junit.Assume.assumeFalse;
21 import static org.junit.Assume.assumeTrue;
22 
23 import android.Manifest;
24 import android.app.ActivityManager;
25 import android.app.ActivityTaskManager;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.content.res.Resources;
30 import android.os.SystemClock;
31 import android.platform.test.annotations.AppModeFull;
32 import android.platform.test.annotations.AppModeInstant;
33 
34 import androidx.annotation.NonNull;
35 import androidx.test.platform.app.InstrumentationRegistry;
36 
37 import com.android.compatibility.common.util.CtsTouchUtils;
38 import com.android.compatibility.common.util.FeatureUtil;
39 import com.android.compatibility.common.util.SystemUtil;
40 
41 import org.junit.After;
42 import org.junit.AfterClass;
43 import org.junit.Before;
44 import org.junit.BeforeClass;
45 import org.junit.Rule;
46 import org.junit.rules.TestName;
47 
48 import java.lang.reflect.Method;
49 import java.util.List;
50 
51 public class EndToEndImeTestBase {
52 
53     @Rule
54     public TestName mTestName = new TestName();
55 
56     protected final CtsTouchUtils mCtsTouchUtils = new CtsTouchUtils(
57             InstrumentationRegistry.getInstrumentation().getTargetContext());
58 
59     /** Command to get verbose ImeTracker logging state. */
60     private static final String GET_VERBOSE_IME_TRACKER_LOGGING_CMD =
61             "getprop persist.debug.imetracker";
62 
63     /** Command to set verbose ImeTracker logging state. */
64     private static final String SET_VERBOSE_IME_TRACKER_LOGGING_CMD =
65             "setprop persist.debug.imetracker";
66 
67     /**
68      * Whether verbose ImeTracker logging was enabled prior to running the tests,
69      * used to handle reverting the state when the test run ends.
70      */
71     private static boolean sWasVerboseImeTrackerLoggingEnabled;
72 
73     /** Tag for the single EditText in the test case. */
74     protected static final String EDIT_TEXT_TAG = "EditText";
75     /** Tag for the initially focused EditText. */
76     protected static final String FOCUSED_EDIT_TEXT_TAG = "focused-EditText";
77     /** Tag for the initially unfocused EditText. */
78     protected static final String NON_FOCUSED_EDIT_TEXT_TAG = "non-focused-EditText";
79     /** Tag for the first EditText. */
80     protected static final String FIRST_EDIT_TEXT_TAG = "first-EditText";
81     /** Tag for the second EditText. */
82     protected static final String SECOND_EDIT_TEXT_TAG = "second-EditText";
83 
84     /**
85      * Skip test executions for know broken platforms.
86      */
87     @Before
checkSupportedPlatforms()88     public final void checkSupportedPlatforms() {
89         // STOPSHIP(b/288952673): Re-enable tests for wear once it becomes stable enough.
90         assumeFalse(FeatureUtil.isWatch());
91     }
92 
93     /**
94      * Enters touch mode when instrumenting.
95      *
96      * Making the view focus state in instrumentation process more reliable in case when
97      * {@link android.view.View#clearFocus()} invoked but system may reFocus again when the view
98      * was not in touch mode. (i.e {@link android.view.View#isInTouchMode()} is {@code false}).
99      */
100     @Before
enterTouchMode()101     public final void enterTouchMode() {
102         InstrumentationRegistry.getInstrumentation().setInTouchMode(true);
103     }
104 
105     /**
106      * Restore to the default touch mode state after the test.
107      */
108     @After
restoreTouchMode()109     public final void restoreTouchMode() {
110         InstrumentationRegistry.getInstrumentation().resetInTouchMode();
111     }
112 
113     /**
114      * Our own safeguard in case "atest" command is regressed and start running tests with
115      * {@link AppModeInstant} even when {@code --instant} option is not specified.
116      *
117      * <p>Unfortunately this scenario had regressed at least 3 times.  That's why we also check
118      * this in our side.  See Bug 158617529, Bug 187211725 and Bug 187222205 for examples.</p>
119      */
120     @Before
verifyAppModeConsistency()121     public void verifyAppModeConsistency() {
122         final Class<?> thisClass = this.getClass();
123         final String testMethodName = mTestName.getMethodName();
124         final String fullTestMethodName = thisClass.getSimpleName() + "#" + testMethodName;
125 
126         final Method testMethod;
127         try {
128             testMethod = thisClass.getMethod(testMethodName);
129         } catch (NoSuchMethodException e) {
130             throw new IllegalStateException("Failed to find " + fullTestMethodName, e);
131         }
132 
133         final boolean hasAppModeFull = testMethod.getAnnotation(AppModeFull.class) != null;
134         final boolean hasAppModeInstant = testMethod.getAnnotation(AppModeInstant.class) != null;
135 
136         if (hasAppModeFull && hasAppModeInstant) {
137             fail("Both @AppModeFull and @AppModeInstant are found in " + fullTestMethodName
138                     + ", which does not make sense. "
139                     + "Remove both to make it clear that this test is app-mode agnostic, "
140                     + "or specify one of them otherwise.");
141         }
142 
143         // We want to explicitly check this condition in case tests are executed with atest
144         // command.  See Bug 158617529 for details.
145         if (hasAppModeFull) {
146             assumeFalse("This test should run under and only under the full app mode.",
147                     InstrumentationRegistry.getInstrumentation().getTargetContext()
148                             .getPackageManager().isInstantApp());
149         }
150         if (hasAppModeInstant) {
151             assumeTrue("This test should run under and only under the instant app mode.",
152                     InstrumentationRegistry.getInstrumentation().getTargetContext()
153                             .getPackageManager().isInstantApp());
154         }
155     }
156 
157     @Before
showStateInitializeActivity()158     public void showStateInitializeActivity() {
159         // TODO(b/37502066): Move this back to @BeforeClass once b/37502066 is fixed.
160         assumeTrue("MockIme cannot be used for devices that do not support installable IMEs",
161                 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
162                         .hasSystemFeature(PackageManager.FEATURE_INPUT_METHODS));
163 
164         final Intent intent = new Intent()
165                 .setAction(Intent.ACTION_MAIN)
166                 .setClass(InstrumentationRegistry.getInstrumentation().getTargetContext(),
167                         StateInitializeActivity.class)
168                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
169                 .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
170                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
171         InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
172     }
173 
174     @Before
clearLaunchParams()175     public void clearLaunchParams() {
176         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
177         final ActivityTaskManager atm = context.getSystemService(ActivityTaskManager.class);
178         SystemUtil.runWithShellPermissionIdentity(() -> {
179             // Clear launch params for all test packages to make sure each test is run in a clean
180             // state.
181             atm.clearLaunchParamsForPackages(List.of(context.getPackageName()));
182         }, Manifest.permission.MANAGE_ACTIVITY_TASKS);
183     }
184 
isPreventImeStartup()185     protected static boolean isPreventImeStartup() {
186         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
187         try {
188             return context.getResources().getBoolean(
189                     android.R.bool.config_preventImeStartupUnlessTextEditor);
190         } catch (Resources.NotFoundException e) {
191             // Assume this is not enabled.
192             return false;
193         }
194     }
195 
196     /**
197      * Enables verbose logging in {@link android.view.inputmethod.ImeTracker}.
198      */
199     @BeforeClass
enableVerboseImeTrackerLogging()200     public static void enableVerboseImeTrackerLogging() {
201         sWasVerboseImeTrackerLoggingEnabled = getVerboseImeTrackerLogging();
202         if (!sWasVerboseImeTrackerLoggingEnabled) {
203             setVerboseImeTrackerLogging(true);
204         }
205     }
206 
207     /**
208      * Reverts verbose logging in {@link android.view.inputmethod.ImeTracker} to the previous value.
209      */
210     @AfterClass
revertVerboseImeTrackerLogging()211     public static void revertVerboseImeTrackerLogging() {
212         if (!sWasVerboseImeTrackerLoggingEnabled) {
213             setVerboseImeTrackerLogging(false);
214         }
215     }
216 
217     /**
218      * Returns a unique test marker based on the concrete class name, given tag and elapsed time.
219      *
220      * @param tag a tag describing the marker (e.g. EditText, Fence).
221      */
222     @NonNull
getTestMarker(@onNull String tag)223     protected String getTestMarker(@NonNull String tag) {
224         return getClass().getName() + "/" + tag + "/" + SystemClock.elapsedRealtimeNanos();
225     }
226 
227     /** Returns a unique test marker for an EditText. */
getTestMarker()228     protected String getTestMarker() {
229         return getTestMarker(EDIT_TEXT_TAG);
230     }
231 
232     /**
233      * Gets the verbose logging state in {@link android.view.inputmethod.ImeTracker}.
234      *
235      * @return {@code true} iff verbose logging is enabled.
236      */
getVerboseImeTrackerLogging()237     private static boolean getVerboseImeTrackerLogging() {
238         return SystemUtil.runShellCommand(GET_VERBOSE_IME_TRACKER_LOGGING_CMD).trim().equals("1");
239     }
240 
241     /**
242      * Sets verbose logging in {@link android.view.inputmethod.ImeTracker}.
243      *
244      * @param enabled whether to enable or disable verbose logging.
245      *
246      * @implNote This must use {@link ActivityManager#notifySystemPropertiesChanged()} to listen
247      *           for changes to the system property for the verbose ImeTracker logging.
248      */
setVerboseImeTrackerLogging(boolean enabled)249     private static void setVerboseImeTrackerLogging(boolean enabled) {
250         final var context = InstrumentationRegistry.getInstrumentation().getContext();
251         final var am = context.getSystemService(ActivityManager.class);
252 
253         SystemUtil.runShellCommand(
254                 SET_VERBOSE_IME_TRACKER_LOGGING_CMD + " " + (enabled ? "1" : "0"));
255         am.notifySystemPropertiesChanged();
256     }
257 }
258