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 com.android.launcher3.tapl.LauncherInstrumentation.ContainerType; 19 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; 20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 21 22 import static org.junit.Assert.assertTrue; 23 24 import static java.lang.System.exit; 25 26 import static androidx.test.InstrumentationRegistry.getInstrumentation; 27 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.UiDevice; 43 import androidx.test.uiautomator.Until; 44 45 import com.android.launcher3.Launcher; 46 import com.android.launcher3.LauncherAppState; 47 import com.android.launcher3.LauncherModel; 48 import com.android.launcher3.LauncherSettings; 49 import com.android.launcher3.LauncherState; 50 import com.android.launcher3.LauncherStateManager; 51 import com.android.launcher3.Utilities; 52 import com.android.launcher3.compat.LauncherAppsCompat; 53 import com.android.launcher3.model.AppLaunchTracker; 54 import com.android.launcher3.tapl.LauncherInstrumentation; 55 import com.android.launcher3.tapl.TestHelpers; 56 import com.android.launcher3.testcomponent.TestCommandReceiver; 57 import com.android.launcher3.testing.TestProtocol; 58 import com.android.launcher3.util.LooperExecutor; 59 import com.android.launcher3.util.PackageManagerHelper; 60 import com.android.launcher3.util.Wait; 61 import com.android.launcher3.util.rule.FailureWatcher; 62 import com.android.launcher3.util.rule.LauncherActivityRule; 63 import com.android.launcher3.util.rule.ShellCommandRule; 64 import com.android.launcher3.util.rule.TestStabilityRule; 65 66 import org.junit.After; 67 import org.junit.Before; 68 import org.junit.Rule; 69 import org.junit.rules.RuleChain; 70 import org.junit.rules.TestRule; 71 72 import java.io.IOException; 73 import java.lang.annotation.ElementType; 74 import java.lang.annotation.Retention; 75 import java.lang.annotation.RetentionPolicy; 76 import java.lang.annotation.Target; 77 import java.util.concurrent.Callable; 78 import java.util.concurrent.CountDownLatch; 79 import java.util.concurrent.TimeUnit; 80 import java.util.function.Consumer; 81 import java.util.function.Function; 82 83 /** 84 * Base class for all instrumentation tests providing various utility methods. 85 */ 86 public abstract class AbstractLauncherUiTest { 87 88 public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10); 89 public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5; 90 91 public static final long DEFAULT_UI_TIMEOUT = 60000; // b/136278866 92 private static final String TAG = "AbstractLauncherUiTest"; 93 94 protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR; 95 protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation()); 96 protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation(); 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()) { 107 Utilities.enableRunningInTestHarnessForTests(); 108 mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand( 109 TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()). 110 getString("result")); 111 mLauncher.setOnSettledStateAction( 112 containerType -> executeOnLauncher( 113 launcher -> 114 checkLauncherIntegrity(launcher, containerType))); 115 } 116 mLauncher.enableDebugTracing(); 117 } 118 119 protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule(); 120 121 @Rule 122 public ShellCommandRule mDefaultLauncherRule = 123 TestHelpers.isInLauncherProcess() ? ShellCommandRule.setDefaultLauncher() : null; 124 125 @Rule 126 public ShellCommandRule mDisableHeadsUpNotification = 127 ShellCommandRule.disableHeadsUpNotification(); 128 clearPackageData(String pkg)129 protected void clearPackageData(String pkg) throws IOException, InterruptedException { 130 final CountDownLatch count = new CountDownLatch(2); 131 final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { 132 @Override 133 public void onReceive(Context context, Intent intent) { 134 count.countDown(); 135 } 136 }; 137 mTargetContext.registerReceiver(broadcastReceiver, 138 PackageManagerHelper.getPackageFilter(pkg, 139 Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED)); 140 141 mDevice.executeShellCommand("pm clear " + pkg); 142 assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS)); 143 mTargetContext.unregisterReceiver(broadcastReceiver); 144 } 145 146 // Annotation for tests that need to be run in portrait and landscape modes. 147 @Retention(RetentionPolicy.RUNTIME) 148 @Target(ElementType.METHOD) 149 protected @interface PortraitLandscape { 150 } 151 getRulesInsideActivityMonitor()152 protected TestRule getRulesInsideActivityMonitor() { 153 return RuleChain. 154 outerRule(new PortraitLandscapeRunner(this)). 155 around(new FailureWatcher(mDevice)); 156 } 157 158 @Rule 159 public TestRule mOrderSensitiveRules = RuleChain. 160 outerRule(new TestStabilityRule()). 161 around(mActivityMonitor). 162 around(getRulesInsideActivityMonitor()); 163 getDevice()164 public UiDevice getDevice() { 165 return mDevice; 166 } 167 168 @Before setUp()169 public void setUp() throws Exception { 170 // Disable app tracker 171 AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker()); 172 173 mTargetContext = InstrumentationRegistry.getTargetContext(); 174 mTargetPackage = mTargetContext.getPackageName(); 175 } 176 177 @After verifyLauncherState()178 public void verifyLauncherState() { 179 try { 180 // Limits UI tests affecting tests running after them. 181 waitForModelLoaded(); 182 } catch (Throwable t) { 183 Log.e(TAG, 184 "Couldn't deinit after a test, exiting tests, see logs for failures that " 185 + "could have caused this", 186 t); 187 exit(1); 188 } 189 } 190 clearLauncherData()191 protected void clearLauncherData() throws IOException, InterruptedException { 192 if (TestHelpers.isInLauncherProcess()) { 193 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 194 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); 195 resetLoaderState(); 196 } else { 197 clearPackageData(mDevice.getLauncherPackageName()); 198 mLauncher.enableDebugTracing(); 199 } 200 } 201 202 /** 203 * Removes all icons from homescreen and hotseat. 204 */ clearHomescreen()205 public void clearHomescreen() throws Throwable { 206 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 207 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); 208 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 209 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); 210 resetLoaderState(); 211 } 212 resetLoaderState()213 protected void resetLoaderState() { 214 try { 215 mMainThreadExecutor.execute( 216 () -> LauncherAppState.getInstance(mTargetContext).getModel().forceReload()); 217 } catch (Throwable t) { 218 throw new IllegalArgumentException(t); 219 } 220 waitForModelLoaded(); 221 } 222 waitForModelLoaded()223 protected void waitForModelLoaded() { 224 waitForLauncherCondition("Launcher model didn't load", launcher -> { 225 final LauncherModel model = LauncherAppState.getInstance(mTargetContext).getModel(); 226 return model.getCallback() == null || model.isModelLoaded(); 227 }); 228 } 229 230 /** 231 * Runs the callback on the UI thread and returns the result. 232 */ getOnUiThread(final Callable<T> callback)233 protected <T> T getOnUiThread(final Callable<T> callback) { 234 try { 235 return mMainThreadExecutor.submit(callback).get(); 236 } catch (Exception e) { 237 throw new RuntimeException(e); 238 } 239 } 240 getFromLauncher(Function<Launcher, T> f)241 protected <T> T getFromLauncher(Function<Launcher, T> f) { 242 if (!TestHelpers.isInLauncherProcess()) return null; 243 return getOnUiThread(() -> f.apply(mActivityMonitor.getActivity())); 244 } 245 executeOnLauncher(Consumer<Launcher> f)246 protected void executeOnLauncher(Consumer<Launcher> f) { 247 getFromLauncher(launcher -> { 248 f.accept(launcher); 249 return null; 250 }); 251 } 252 253 // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting 254 // the results of that gesture because the wait can hide flakeness. waitForState(String message, LauncherState state)255 protected void waitForState(String message, LauncherState state) { 256 waitForLauncherCondition(message, 257 launcher -> launcher.getStateManager().getCurrentStableState() == state); 258 } 259 waitForResumed(String message)260 protected void waitForResumed(String message) { 261 waitForLauncherCondition(message, launcher -> launcher.hasBeenResumed()); 262 } 263 264 // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide 265 // flakiness. waitForLauncherCondition(String message, Function<Launcher, Boolean> condition)266 protected void waitForLauncherCondition(String message, Function<Launcher, Boolean> condition) { 267 waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT); 268 } 269 270 // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide 271 // flakiness. getOnceNotNull(String message, Function<Launcher, T> f)272 protected <T> T getOnceNotNull(String message, Function<Launcher, T> f) { 273 return getOnceNotNull(message, f, 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. getOnceNotNull(String message, Function<Launcher, T> f, long timeout)286 protected <T> T getOnceNotNull(String message, Function<Launcher, T> f, long timeout) { 287 if (!TestHelpers.isInLauncherProcess()) return null; 288 289 final Object[] output = new Object[1]; 290 Wait.atMost(message, () -> { 291 final Object fromLauncher = getFromLauncher(f); 292 output[0] = fromLauncher; 293 return fromLauncher != null; 294 }, timeout); 295 return (T) output[0]; 296 } 297 298 // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide 299 // flakiness. waitForLauncherCondition( String message, Runnable testThreadAction, Function<Launcher, Boolean> condition, long timeout)300 protected void waitForLauncherCondition( 301 String message, 302 Runnable testThreadAction, Function<Launcher, Boolean> condition, 303 long timeout) { 304 if (!TestHelpers.isInLauncherProcess()) return; 305 Wait.atMost(message, () -> { 306 testThreadAction.run(); 307 return getFromLauncher(condition); 308 }, timeout); 309 } 310 getSettingsApp()311 protected LauncherActivityInfo getSettingsApp() { 312 return LauncherAppsCompat.getInstance(mTargetContext) 313 .getActivityList("com.android.settings", 314 Process.myUserHandle()).get(0); 315 } 316 317 /** 318 * Broadcast receiver which blocks until the result is received. 319 */ 320 public class BlockingBroadcastReceiver extends BroadcastReceiver { 321 322 private final CountDownLatch latch = new CountDownLatch(1); 323 private Intent mIntent; 324 BlockingBroadcastReceiver(String action)325 public BlockingBroadcastReceiver(String action) { 326 mTargetContext.registerReceiver(this, new IntentFilter(action)); 327 } 328 329 @Override onReceive(Context context, Intent intent)330 public void onReceive(Context context, Intent intent) { 331 mIntent = intent; 332 latch.countDown(); 333 } 334 blockingGetIntent()335 public Intent blockingGetIntent() throws InterruptedException { 336 latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS); 337 mTargetContext.unregisterReceiver(this); 338 return mIntent; 339 } 340 blockingGetExtraIntent()341 public Intent blockingGetExtraIntent() throws InterruptedException { 342 Intent intent = blockingGetIntent(); 343 return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT); 344 } 345 } 346 startAppFast(String packageName)347 protected void startAppFast(String packageName) { 348 startIntent( 349 getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage( 350 packageName), 351 By.pkg(packageName).depth(0), 352 true /* newTask */); 353 } 354 startTestActivity(int activityNumber)355 protected void startTestActivity(int activityNumber) { 356 final String packageName = getAppPackageName(); 357 final Intent intent = getInstrumentation().getContext().getPackageManager(). 358 getLaunchIntentForPackage(packageName); 359 intent.setComponent(new ComponentName(packageName, 360 "com.android.launcher3.tests.Activity" + activityNumber)); 361 startIntent(intent, By.pkg(packageName).text("TestActivity" + activityNumber), 362 false /* newTask */); 363 } 364 startIntent(Intent intent, BySelector selector, boolean newTask)365 private void startIntent(Intent intent, BySelector selector, boolean newTask) { 366 intent.addCategory(Intent.CATEGORY_LAUNCHER); 367 if (newTask) { 368 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 369 } else { 370 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 371 } 372 getInstrumentation().getTargetContext().startActivity(intent); 373 assertTrue("App didn't start: " + selector, 374 mDevice.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT)); 375 } 376 resolveSystemApp(String category)377 public static String resolveSystemApp(String category) { 378 return getInstrumentation().getContext().getPackageManager().resolveActivity( 379 new Intent(Intent.ACTION_MAIN).addCategory(category), 380 PackageManager.MATCH_SYSTEM_ONLY). 381 activityInfo.packageName; 382 } 383 closeLauncherActivity()384 protected void closeLauncherActivity() { 385 // Destroy Launcher activity. 386 executeOnLauncher(launcher -> { 387 if (launcher != null) { 388 launcher.finish(); 389 } 390 }); 391 waitForLauncherCondition( 392 "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT); 393 } 394 isInBackground(Launcher launcher)395 protected boolean isInBackground(Launcher launcher) { 396 return !launcher.hasBeenResumed(); 397 } 398 isInState(LauncherState state)399 protected boolean isInState(LauncherState state) { 400 if (!TestHelpers.isInLauncherProcess()) return true; 401 return getFromLauncher(launcher -> launcher.getStateManager().getState() == state); 402 } 403 getAllAppsScroll(Launcher launcher)404 protected int getAllAppsScroll(Launcher launcher) { 405 return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY(); 406 } 407 checkLauncherIntegrity( Launcher launcher, ContainerType expectedContainerType)408 private static void checkLauncherIntegrity( 409 Launcher launcher, ContainerType expectedContainerType) { 410 if (launcher != null) { 411 final LauncherStateManager stateManager = launcher.getStateManager(); 412 final LauncherState stableState = stateManager.getCurrentStableState(); 413 414 assertTrue("Stable state != state: " + stableState.getClass().getSimpleName() + ", " 415 + stateManager.getState().getClass().getSimpleName(), 416 stableState == stateManager.getState()); 417 418 final boolean isResumed = launcher.hasBeenResumed(); 419 assertTrue("hasBeenResumed() != isStarted(), hasBeenResumed(): " + isResumed, 420 isResumed == launcher.isStarted()); 421 assertTrue("hasBeenResumed() != isUserActive(), hasBeenResumed(): " + isResumed, 422 isResumed == launcher.isUserActive()); 423 424 final int ordinal = stableState.ordinal; 425 426 switch (expectedContainerType) { 427 case WORKSPACE: 428 case WIDGETS: { 429 assertTrue( 430 "Launcher is not resumed in state: " + expectedContainerType, 431 isResumed); 432 assertTrue(TestProtocol.stateOrdinalToString(ordinal), 433 ordinal == TestProtocol.NORMAL_STATE_ORDINAL); 434 break; 435 } 436 case ALL_APPS: { 437 assertTrue( 438 "Launcher is not resumed in state: " + expectedContainerType, 439 isResumed); 440 assertTrue(TestProtocol.stateOrdinalToString(ordinal), 441 ordinal == TestProtocol.ALL_APPS_STATE_ORDINAL); 442 break; 443 } 444 case OVERVIEW: { 445 assertTrue( 446 "Launcher is not resumed in state: " + expectedContainerType, 447 isResumed); 448 assertTrue(TestProtocol.stateOrdinalToString(ordinal), 449 ordinal == TestProtocol.OVERVIEW_STATE_ORDINAL); 450 break; 451 } 452 case BACKGROUND: { 453 assertTrue("Launcher is resumed in state: " + expectedContainerType, 454 !isResumed); 455 assertTrue(TestProtocol.stateOrdinalToString(ordinal), 456 ordinal == TestProtocol.NORMAL_STATE_ORDINAL); 457 break; 458 } 459 default: 460 throw new IllegalArgumentException( 461 "Illegal container: " + expectedContainerType); 462 } 463 } else { 464 assertTrue( 465 "Container type is not BACKGROUND or FALLBACK_OVERVIEW: " 466 + expectedContainerType, 467 expectedContainerType == ContainerType.BACKGROUND || 468 expectedContainerType == ContainerType.FALLBACK_OVERVIEW); 469 } 470 } 471 } 472