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