1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.android.launcher3.ui; 17 18 import static androidx.test.InstrumentationRegistry.getInstrumentation; 19 20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 21 22 import static org.junit.Assert.assertTrue; 23 24 import android.content.ComponentName; 25 import android.content.Intent; 26 import android.os.Process; 27 import android.system.OsConstants; 28 import android.util.Log; 29 30 import androidx.test.uiautomator.By; 31 import androidx.test.uiautomator.BySelector; 32 import androidx.test.uiautomator.Until; 33 34 import com.android.launcher3.Launcher; 35 import com.android.launcher3.LauncherState; 36 import com.android.launcher3.Utilities; 37 import com.android.launcher3.tapl.LauncherInstrumentation; 38 import com.android.launcher3.tapl.TestHelpers; 39 import com.android.launcher3.testcomponent.TestCommandReceiver; 40 import com.android.launcher3.util.LooperExecutor; 41 import com.android.launcher3.util.TestUtil; 42 import com.android.launcher3.util.Wait; 43 import com.android.launcher3.util.rule.FailureWatcher; 44 import com.android.launcher3.util.rule.ShellCommandRule; 45 import com.android.launcher3.util.rule.TestIsolationRule; 46 import com.android.launcher3.util.rule.ViewCaptureRule; 47 48 import org.junit.rules.RuleChain; 49 import org.junit.rules.TestRule; 50 51 import java.util.Objects; 52 import java.util.concurrent.Callable; 53 import java.util.concurrent.TimeUnit; 54 import java.util.concurrent.TimeoutException; 55 import java.util.function.Consumer; 56 import java.util.function.Function; 57 import java.util.function.Supplier; 58 59 /** 60 * Base class for all instrumentation tests providing various utility methods. 61 */ 62 public abstract class AbstractLauncherUiTest<LAUNCHER_TYPE extends Launcher> 63 extends BaseLauncherTaplTest { 64 65 private static final String TAG = "AbstractLauncherUiTest"; 66 67 protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR; 68 AbstractLauncherUiTest()69 protected AbstractLauncherUiTest() { 70 if (TestHelpers.isInLauncherProcess()) { 71 Utilities.enableRunningInTestHarnessForTests(); 72 mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand( 73 TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()) 74 .getString("result")); 75 } 76 } 77 78 /** 79 * @deprecated call {@link #performInitialization} instead 80 */ 81 @Deprecated initialize(AbstractLauncherUiTest test)82 public static void initialize(AbstractLauncherUiTest test) throws Exception { 83 test.performInitialization(); 84 } 85 86 @Override performInitialization()87 protected void performInitialization() { 88 reinitializeLauncherData(); 89 mDevice.pressHome(); 90 // Check that we switched to home. 91 mLauncher.getWorkspace(); 92 93 waitForLauncherCondition("Launcher didn't start", Objects::nonNull); 94 waitForState("Launcher internal state didn't switch to Home", 95 () -> LauncherState.NORMAL); 96 waitForResumed("Launcher internal state is still Background"); 97 98 checkDetectedLeaks(mLauncher, true); 99 } 100 101 @Override getRulesInsideActivityMonitor()102 protected TestRule getRulesInsideActivityMonitor() { 103 final ViewCaptureRule viewCaptureRule = new ViewCaptureRule( 104 Launcher.ACTIVITY_TRACKER::getCreatedContext); 105 final RuleChain inner = RuleChain 106 .outerRule(new PortraitLandscapeRunner<>(this)) 107 .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData)) 108 // .around(viewCaptureRule) // b/315482167 109 .around(new TestIsolationRule(mLauncher, true)); 110 111 return TestHelpers.isInLauncherProcess() 112 ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner) 113 : inner; 114 } 115 116 /** 117 * Runs the callback on the UI thread and returns the result. 118 */ getOnUiThread(final Callable<T> callback)119 protected <T> T getOnUiThread(final Callable<T> callback) { 120 try { 121 return mMainThreadExecutor.submit(callback).get(TestUtil.DEFAULT_UI_TIMEOUT, 122 TimeUnit.MILLISECONDS); 123 } catch (TimeoutException e) { 124 Log.e(TAG, "Timeout in getOnUiThread, sending SIGABRT", e); 125 Process.sendSignal(Process.myPid(), OsConstants.SIGABRT); 126 throw new RuntimeException(e); 127 } catch (Throwable e) { 128 throw new RuntimeException(e); 129 } 130 } 131 getFromLauncher(Function<LAUNCHER_TYPE, T> f)132 protected <T> T getFromLauncher(Function<LAUNCHER_TYPE, T> f) { 133 if (!TestHelpers.isInLauncherProcess()) return null; 134 return getOnUiThread(() -> f.apply(Launcher.ACTIVITY_TRACKER.getCreatedContext())); 135 } 136 executeOnLauncher(Consumer<LAUNCHER_TYPE> f)137 protected void executeOnLauncher(Consumer<LAUNCHER_TYPE> f) { 138 getFromLauncher(launcher -> { 139 f.accept(launcher); 140 return null; 141 }); 142 } 143 144 // Execute an action on Launcher, but forgive it when launcher is null. 145 // Launcher can be null if teardown is happening after a failed setup step where launcher 146 // activity failed to be created. executeOnLauncherInTearDown(Consumer<LAUNCHER_TYPE> f)147 protected void executeOnLauncherInTearDown(Consumer<LAUNCHER_TYPE> f) { 148 executeOnLauncher(launcher -> { 149 if (launcher != null) f.accept(launcher); 150 }); 151 } 152 153 // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call 154 // expecting the results of that gesture because the wait can hide flakeness. waitForState(String message, Supplier<LauncherState> state)155 protected void waitForState(String message, Supplier<LauncherState> state) { 156 waitForLauncherCondition(message, 157 launcher -> launcher.getStateManager().getCurrentStableState() == state.get()); 158 } 159 160 // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call 161 // expecting the results of that gesture because the wait can hide flakeness. waitForStateTransitionToEnd(String message, Supplier<LauncherState> state)162 protected void waitForStateTransitionToEnd(String message, Supplier<LauncherState> state) { 163 waitForLauncherCondition(message, 164 launcher -> launcher.getStateManager().isInStableState(state.get()) 165 && !launcher.getStateManager().isInTransition()); 166 } 167 waitForResumed(String message)168 protected void waitForResumed(String message) { 169 waitForLauncherCondition(message, launcher -> launcher.hasBeenResumed()); 170 } 171 172 // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide 173 // flakiness. waitForLauncherCondition(String message, Function<LAUNCHER_TYPE, Boolean> condition)174 protected void waitForLauncherCondition(String 175 message, Function<LAUNCHER_TYPE, Boolean> condition) { 176 waitForLauncherCondition(message, condition, TestUtil.DEFAULT_UI_TIMEOUT); 177 } 178 179 // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide 180 // flakiness. waitForLauncherCondition( String message, Function<LAUNCHER_TYPE, Boolean> condition, long timeout)181 protected void waitForLauncherCondition( 182 String message, Function<LAUNCHER_TYPE, Boolean> condition, long timeout) { 183 verifyKeyguardInvisible(); 184 if (!TestHelpers.isInLauncherProcess()) return; 185 Wait.atMost(message, () -> getFromLauncher(condition), mLauncher, timeout); 186 } 187 188 // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide 189 // flakiness. getOnceNotNull(String message, Function<LAUNCHER_TYPE, T> f)190 protected <T> T getOnceNotNull(String message, Function<LAUNCHER_TYPE, T> f) { 191 if (!TestHelpers.isInLauncherProcess()) return null; 192 193 final Object[] output = new Object[1]; 194 Wait.atMost(message, () -> { 195 final Object fromLauncher = getFromLauncher(f); 196 output[0] = fromLauncher; 197 return fromLauncher != null; 198 }, mLauncher); 199 return (T) output[0]; 200 } 201 202 // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide 203 // flakiness. waitForLauncherCondition( String message, Runnable testThreadAction, Function<LAUNCHER_TYPE, Boolean> condition, long timeout)204 protected void waitForLauncherCondition( 205 String message, 206 Runnable testThreadAction, Function<LAUNCHER_TYPE, Boolean> condition, 207 long timeout) { 208 if (!TestHelpers.isInLauncherProcess()) return; 209 Wait.atMost(message, () -> { 210 testThreadAction.run(); 211 return getFromLauncher(condition); 212 }, mLauncher, timeout); 213 } 214 startAppFast(String packageName)215 public static void startAppFast(String packageName) { 216 startIntent( 217 getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage( 218 packageName), 219 By.pkg(packageName).depth(0), 220 true /* newTask */); 221 } 222 startTestActivity(String activityName, String activityLabel)223 public static void startTestActivity(String activityName, String activityLabel) { 224 final String packageName = getAppPackageName(); 225 final Intent intent = getInstrumentation().getContext().getPackageManager(). 226 getLaunchIntentForPackage(packageName); 227 intent.setComponent(new ComponentName(packageName, 228 "com.android.launcher3.tests." + activityName)); 229 startIntent(intent, By.pkg(packageName).text(activityLabel), 230 false /* newTask */); 231 } 232 startTestActivity(int activityNumber)233 public static void startTestActivity(int activityNumber) { 234 startTestActivity("Activity" + activityNumber, "TestActivity" + activityNumber); 235 } 236 startImeTestActivity()237 public static void startImeTestActivity() { 238 final String packageName = getAppPackageName(); 239 final Intent intent = getInstrumentation().getContext().getPackageManager(). 240 getLaunchIntentForPackage(packageName); 241 intent.setComponent(new ComponentName(packageName, 242 "com.android.launcher3.testcomponent.ImeTestActivity")); 243 startIntent(intent, By.pkg(packageName).text("ImeTestActivity"), 244 false /* newTask */); 245 } 246 247 /** Starts ExcludeFromRecentsTestActivity, which has excludeFromRecents="true". */ startExcludeFromRecentsTestActivity()248 public static void startExcludeFromRecentsTestActivity() { 249 final String packageName = getAppPackageName(); 250 final Intent intent = getInstrumentation().getContext().getPackageManager() 251 .getLaunchIntentForPackage(packageName); 252 intent.setComponent(new ComponentName(packageName, 253 "com.android.launcher3.testcomponent.ExcludeFromRecentsTestActivity")); 254 startIntent(intent, By.pkg(packageName).text("ExcludeFromRecentsTestActivity"), 255 false /* newTask */); 256 } 257 startIntent(Intent intent, BySelector selector, boolean newTask)258 private static void startIntent(Intent intent, BySelector selector, boolean newTask) { 259 intent.addCategory(Intent.CATEGORY_LAUNCHER); 260 if (newTask) { 261 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 262 } else { 263 intent.addFlags( 264 Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 265 } 266 getInstrumentation().getTargetContext().startActivity(intent); 267 assertTrue("App didn't start: " + selector, 268 TestHelpers.wait(Until.hasObject(selector), TestUtil.DEFAULT_UI_TIMEOUT)); 269 270 // Wait for the Launcher to stop. 271 final LauncherInstrumentation launcherInstrumentation = new LauncherInstrumentation(); 272 Wait.atMost("Launcher activity didn't stop", 273 () -> !launcherInstrumentation.isLauncherActivityStarted(), 274 launcherInstrumentation); 275 } 276 277 resolveSystemApp(String category)278 public static String resolveSystemApp(String category) { 279 return resolveSystemAppInfo(category).packageName; 280 } 281 closeLauncherActivity()282 protected void closeLauncherActivity() { 283 // Destroy Launcher activity. 284 executeOnLauncher(launcher -> { 285 if (launcher != null) { 286 onLauncherActivityClose(launcher); 287 launcher.finish(); 288 } 289 }); 290 waitForLauncherCondition("Launcher still active", launcher -> launcher == null); 291 } 292 isInLaunchedApp(LAUNCHER_TYPE launcher)293 protected boolean isInLaunchedApp(LAUNCHER_TYPE launcher) { 294 return launcher == null || !launcher.hasBeenResumed(); 295 } 296 isInState(Supplier<LauncherState> state)297 protected boolean isInState(Supplier<LauncherState> state) { 298 if (!TestHelpers.isInLauncherProcess()) return true; 299 return getFromLauncher( 300 launcher -> launcher.getStateManager().getState() == state.get()); 301 } 302 getAllAppsScroll(LAUNCHER_TYPE launcher)303 protected int getAllAppsScroll(LAUNCHER_TYPE launcher) { 304 return launcher.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset(); 305 } 306 onLauncherActivityClose(LAUNCHER_TYPE launcher)307 protected void onLauncherActivityClose(LAUNCHER_TYPE launcher) { 308 } 309 } 310