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.ui.TaplTestsLauncher3.getAppPackageName; 21 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.fail; 24 25 import static java.lang.System.exit; 26 27 import android.app.Instrumentation; 28 import android.content.BroadcastReceiver; 29 import android.content.ComponentName; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.content.pm.LauncherActivityInfo; 34 import android.content.pm.PackageManager; 35 import android.os.Process; 36 import android.os.RemoteException; 37 import android.util.Log; 38 39 import androidx.test.InstrumentationRegistry; 40 import androidx.test.uiautomator.By; 41 import androidx.test.uiautomator.BySelector; 42 import androidx.test.uiautomator.Direction; 43 import androidx.test.uiautomator.UiDevice; 44 import androidx.test.uiautomator.UiObject2; 45 import androidx.test.uiautomator.Until; 46 47 import com.android.launcher3.Launcher; 48 import com.android.launcher3.LauncherAppState; 49 import com.android.launcher3.LauncherModel; 50 import com.android.launcher3.LauncherSettings; 51 import com.android.launcher3.LauncherState; 52 import com.android.launcher3.MainThreadExecutor; 53 import com.android.launcher3.ResourceUtils; 54 import com.android.launcher3.Utilities; 55 import com.android.launcher3.compat.LauncherAppsCompat; 56 import com.android.launcher3.model.AppLaunchTracker; 57 import com.android.launcher3.tapl.LauncherInstrumentation; 58 import com.android.launcher3.tapl.TestHelpers; 59 import com.android.launcher3.util.Wait; 60 import com.android.launcher3.util.rule.FailureWatcher; 61 import com.android.launcher3.util.rule.LauncherActivityRule; 62 import com.android.launcher3.util.rule.ShellCommandRule; 63 64 import org.junit.After; 65 import org.junit.Before; 66 import org.junit.Rule; 67 import org.junit.rules.RuleChain; 68 import org.junit.rules.TestRule; 69 70 import java.io.IOException; 71 import java.lang.annotation.ElementType; 72 import java.lang.annotation.Retention; 73 import java.lang.annotation.RetentionPolicy; 74 import java.lang.annotation.Target; 75 import java.util.concurrent.Callable; 76 import java.util.concurrent.CountDownLatch; 77 import java.util.concurrent.TimeUnit; 78 import java.util.function.Consumer; 79 import java.util.function.Function; 80 81 /** 82 * Base class for all instrumentation tests providing various utility methods. 83 */ 84 public abstract class AbstractLauncherUiTest { 85 86 public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10); 87 public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5; 88 89 public static final long SHORT_UI_TIMEOUT = 300; 90 public static final long DEFAULT_UI_TIMEOUT = 10000; 91 private static final String TAG = "AbstractLauncherUiTest"; 92 93 protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); 94 protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation()); 95 protected final LauncherInstrumentation mLauncher = 96 new LauncherInstrumentation(getInstrumentation()); 97 protected Context mTargetContext; 98 protected String mTargetPackage; 99 AbstractLauncherUiTest()100 protected AbstractLauncherUiTest() { 101 try { 102 mDevice.setOrientationNatural(); 103 } catch (RemoteException e) { 104 throw new RuntimeException(e); 105 } 106 if (TestHelpers.isInLauncherProcess()) Utilities.enableRunningInTestHarnessForTests(); 107 } 108 109 protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule(); 110 111 @Rule 112 public ShellCommandRule mDefaultLauncherRule = 113 TestHelpers.isInLauncherProcess() ? ShellCommandRule.setDefaultLauncher() : null; 114 115 @Rule 116 public ShellCommandRule mDisableHeadsUpNotification = 117 ShellCommandRule.disableHeadsUpNotification(); 118 119 // Annotation for tests that need to be run in portrait and landscape modes. 120 @Retention(RetentionPolicy.RUNTIME) 121 @Target(ElementType.METHOD) 122 protected @interface PortraitLandscape { 123 } 124 getRulesInsideActivityMonitor()125 protected TestRule getRulesInsideActivityMonitor() { 126 return RuleChain. 127 outerRule(new PortraitLandscapeRunner(this)). 128 around(new FailureWatcher(mDevice)); 129 } 130 131 @Rule 132 public TestRule mOrderSensitiveRules = RuleChain. 133 outerRule(mActivityMonitor). 134 around(getRulesInsideActivityMonitor()); 135 getDevice()136 public UiDevice getDevice() { 137 return mDevice; 138 } 139 140 @Before setUp()141 public void setUp() throws Exception { 142 // Disable app tracker 143 AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker()); 144 145 mTargetContext = InstrumentationRegistry.getTargetContext(); 146 mTargetPackage = mTargetContext.getPackageName(); 147 // Unlock the phone 148 mDevice.executeShellCommand("input keyevent 82"); 149 } 150 151 @After verifyLauncherState()152 public void verifyLauncherState() { 153 try { 154 // Limits UI tests affecting tests running after them. 155 waitForModelLoaded(); 156 } catch (Throwable t) { 157 Log.e(TAG, 158 "Couldn't deinit after a test, exiting tests, see logs for failures that " 159 + "could have caused this", 160 t); 161 exit(1); 162 } 163 } 164 lockRotation(boolean naturalOrientation)165 protected void lockRotation(boolean naturalOrientation) throws RemoteException { 166 if (naturalOrientation) { 167 mDevice.setOrientationNatural(); 168 } else { 169 mDevice.setOrientationRight(); 170 } 171 } 172 clearLauncherData()173 protected void clearLauncherData() throws IOException { 174 if (TestHelpers.isInLauncherProcess()) { 175 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 176 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); 177 resetLoaderState(); 178 } else { 179 mDevice.executeShellCommand("pm clear " + mDevice.getLauncherPackageName()); 180 } 181 } 182 183 /** 184 * Scrolls the {@param container} until it finds an object matching {@param condition}. 185 * 186 * @return the matching object. 187 */ scrollAndFind(UiObject2 container, BySelector condition)188 protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) { 189 final int margin = ResourceUtils.getNavbarSize( 190 ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1; 191 container.setGestureMargins(0, 0, 0, margin); 192 193 int i = 0; 194 for (; ; ) { 195 // findObject can only execute after spring settles. 196 mDevice.wait(Until.findObject(condition), SHORT_UI_TIMEOUT); 197 UiObject2 widget = container.findObject(condition); 198 if (widget != null && widget.getVisibleBounds().intersects( 199 0, 0, mDevice.getDisplayWidth(), 200 mDevice.getDisplayHeight() - margin)) { 201 return widget; 202 } 203 if (++i > 40) fail("Too many attempts"); 204 container.scroll(Direction.DOWN, 1f); 205 } 206 } 207 208 /** 209 * Removes all icons from homescreen and hotseat. 210 */ clearHomescreen()211 public void clearHomescreen() throws Throwable { 212 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 213 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); 214 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 215 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); 216 resetLoaderState(); 217 } 218 resetLoaderState()219 protected void resetLoaderState() { 220 try { 221 mMainThreadExecutor.execute( 222 () -> LauncherAppState.getInstance(mTargetContext).getModel().forceReload()); 223 } catch (Throwable t) { 224 throw new IllegalArgumentException(t); 225 } 226 waitForModelLoaded(); 227 } 228 waitForModelLoaded()229 protected void waitForModelLoaded() { 230 waitForLauncherCondition("Launcher model didn't load", launcher -> { 231 final LauncherModel model = LauncherAppState.getInstance(mTargetContext).getModel(); 232 return model.getCallback() == null || model.isModelLoaded(); 233 }); 234 } 235 236 /** 237 * Runs the callback on the UI thread and returns the result. 238 */ getOnUiThread(final Callable<T> callback)239 protected <T> T getOnUiThread(final Callable<T> callback) { 240 try { 241 return mMainThreadExecutor.submit(callback).get(); 242 } catch (Exception e) { 243 throw new RuntimeException(e); 244 } 245 } 246 getFromLauncher(Function<Launcher, T> f)247 protected <T> T getFromLauncher(Function<Launcher, T> f) { 248 if (!TestHelpers.isInLauncherProcess()) return null; 249 return getOnUiThread(() -> f.apply(mActivityMonitor.getActivity())); 250 } 251 executeOnLauncher(Consumer<Launcher> f)252 protected void executeOnLauncher(Consumer<Launcher> f) { 253 getFromLauncher(launcher -> { 254 f.accept(launcher); 255 return null; 256 }); 257 } 258 259 // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting 260 // the results of that gesture because the wait can hide flakeness. waitForState(String message, LauncherState state)261 protected void waitForState(String message, LauncherState state) { 262 waitForLauncherCondition(message, 263 launcher -> launcher.getStateManager().getCurrentStableState() == state); 264 } 265 waitForResumed(String message)266 protected void waitForResumed(String message) { 267 waitForLauncherCondition(message, launcher -> launcher.hasBeenResumed()); 268 } 269 270 // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide 271 // flakiness. waitForLauncherCondition(String message, Function<Launcher, Boolean> condition)272 protected void waitForLauncherCondition(String message, Function<Launcher, Boolean> condition) { 273 waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT); 274 } 275 276 // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide 277 // flakiness. waitForLauncherCondition( String message, Function<Launcher, Boolean> condition, long timeout)278 protected void waitForLauncherCondition( 279 String message, Function<Launcher, Boolean> condition, long timeout) { 280 if (!TestHelpers.isInLauncherProcess()) return; 281 Wait.atMost(message, () -> getFromLauncher(condition), timeout); 282 } 283 284 // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide 285 // flakiness. waitForLauncherCondition( String message, Runnable testThreadAction, Function<Launcher, Boolean> condition, long timeout)286 protected void waitForLauncherCondition( 287 String message, 288 Runnable testThreadAction, Function<Launcher, Boolean> condition, 289 long timeout) { 290 if (!TestHelpers.isInLauncherProcess()) return; 291 Wait.atMost(message, () -> { 292 testThreadAction.run(); 293 return getFromLauncher(condition); 294 }, timeout); 295 } 296 getSettingsApp()297 protected LauncherActivityInfo getSettingsApp() { 298 return LauncherAppsCompat.getInstance(mTargetContext) 299 .getActivityList("com.android.settings", 300 Process.myUserHandle()).get(0); 301 } 302 303 /** 304 * Broadcast receiver which blocks until the result is received. 305 */ 306 public class BlockingBroadcastReceiver extends BroadcastReceiver { 307 308 private final CountDownLatch latch = new CountDownLatch(1); 309 private Intent mIntent; 310 BlockingBroadcastReceiver(String action)311 public BlockingBroadcastReceiver(String action) { 312 mTargetContext.registerReceiver(this, new IntentFilter(action)); 313 } 314 315 @Override onReceive(Context context, Intent intent)316 public void onReceive(Context context, Intent intent) { 317 mIntent = intent; 318 latch.countDown(); 319 } 320 blockingGetIntent()321 public Intent blockingGetIntent() throws InterruptedException { 322 latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS); 323 mTargetContext.unregisterReceiver(this); 324 return mIntent; 325 } 326 blockingGetExtraIntent()327 public Intent blockingGetExtraIntent() throws InterruptedException { 328 Intent intent = blockingGetIntent(); 329 return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT); 330 } 331 } 332 startAppFast(String packageName)333 protected void startAppFast(String packageName) { 334 final Instrumentation instrumentation = getInstrumentation(); 335 final Intent intent = instrumentation.getContext().getPackageManager(). 336 getLaunchIntentForPackage(packageName); 337 intent.addCategory(Intent.CATEGORY_LAUNCHER); 338 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 339 instrumentation.getTargetContext().startActivity(intent); 340 assertTrue(packageName + " didn't start", 341 mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), DEFAULT_UI_TIMEOUT)); 342 } 343 startTestActivity(int activityNumber)344 protected void startTestActivity(int activityNumber) { 345 final String packageName = getAppPackageName(); 346 final Instrumentation instrumentation = getInstrumentation(); 347 final Intent intent = instrumentation.getContext().getPackageManager(). 348 getLaunchIntentForPackage(packageName); 349 intent.addCategory(Intent.CATEGORY_LAUNCHER); 350 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 351 intent.setComponent(new ComponentName(packageName, 352 "com.android.launcher3.tests.Activity" + activityNumber)); 353 instrumentation.getTargetContext().startActivity(intent); 354 assertTrue(packageName + " didn't start", 355 mDevice.wait( 356 Until.hasObject(By.pkg(packageName).text("TestActivity" + activityNumber)), 357 DEFAULT_UI_TIMEOUT)); 358 } 359 resolveSystemApp(String category)360 public static String resolveSystemApp(String category) { 361 return getInstrumentation().getContext().getPackageManager().resolveActivity( 362 new Intent(Intent.ACTION_MAIN).addCategory(category), 363 PackageManager.MATCH_SYSTEM_ONLY). 364 activityInfo.packageName; 365 } 366 closeLauncherActivity()367 protected void closeLauncherActivity() { 368 // Destroy Launcher activity. 369 executeOnLauncher(launcher -> { 370 if (launcher != null) { 371 launcher.finish(); 372 } 373 }); 374 waitForLauncherCondition( 375 "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT); 376 } 377 isInBackground(Launcher launcher)378 protected boolean isInBackground(Launcher launcher) { 379 return !launcher.hasBeenResumed(); 380 } 381 isInState(LauncherState state)382 protected boolean isInState(LauncherState state) { 383 if (!TestHelpers.isInLauncherProcess()) return true; 384 return getFromLauncher(launcher -> launcher.getStateManager().getState() == state); 385 } 386 getAllAppsScroll(Launcher launcher)387 protected int getAllAppsScroll(Launcher launcher) { 388 return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY(); 389 } 390 } 391