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