diff options
Diffstat (limited to 'tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java')
-rw-r--r-- | tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java | 159 |
1 files changed, 142 insertions, 17 deletions
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 5240e6a02b..79d8c60049 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -15,10 +15,11 @@ */ package com.android.launcher3.ui; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; + import static androidx.test.InstrumentationRegistry.getInstrumentation; import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING; -import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static org.junit.Assert.assertEquals; @@ -40,9 +41,11 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.system.OsConstants; import android.util.Log; +import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; @@ -68,6 +71,7 @@ import com.android.launcher3.util.rule.FailureWatcher; import com.android.launcher3.util.rule.SamplerRule; import com.android.launcher3.util.rule.ScreenRecordRule; import com.android.launcher3.util.rule.ShellCommandRule; +import com.android.launcher3.util.rule.TestIsolationRule; import com.android.launcher3.util.rule.TestStabilityRule; import com.android.launcher3.util.rule.ViewCaptureRule; @@ -101,35 +105,62 @@ public abstract class AbstractLauncherUiTest { private static boolean sDumpWasGenerated = false; private static boolean sActivityLeakReported = false; private static boolean sSeenKeyguard = false; + private static boolean sFirstTimeWaitingForWizard = true; private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR; - protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation()); - protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation(); + protected final UiDevice mDevice = getUiDevice(); + protected final LauncherInstrumentation mLauncher = createLauncherInstrumentation(); + + @NonNull + public static LauncherInstrumentation createLauncherInstrumentation() { + waitForSetupWizardDismissal(); // precondition for creating LauncherInstrumentation + return new LauncherInstrumentation(true); + } + protected Context mTargetContext; protected String mTargetPackage; private int mLauncherPid; + /** Detects activity leaks and throws an exception if a leak is found. */ public static void checkDetectedLeaks(LauncherInstrumentation launcher) { + checkDetectedLeaks(launcher, false); + } + + /** Detects activity leaks and throws an exception if a leak is found. */ + public static void checkDetectedLeaks(LauncherInstrumentation launcher, + boolean requireOneActiveActivityUnused) { + if (TestStabilityRule.isPresubmit()) return; // b/313501215 + + final boolean requireOneActiveActivity = + false; // workaround for leaks when there is an unexpected Recents activity + if (sActivityLeakReported) return; // Check whether activity leak detector has found leaked activities. - Wait.atMost(() -> getActivityLeakErrorMessage(launcher), + Wait.atMost(() -> getActivityLeakErrorMessage(launcher, requireOneActiveActivity), () -> { launcher.forceGc(); return MAIN_EXECUTOR.submit( - () -> launcher.noLeakedActivities()).get(); + () -> launcher.noLeakedActivities(requireOneActiveActivity)).get(); }, DEFAULT_UI_TIMEOUT, launcher); } - private static String getActivityLeakErrorMessage(LauncherInstrumentation launcher) { + public static String getAppPackageName() { + return getInstrumentation().getContext().getPackageName(); + } + + private static String getActivityLeakErrorMessage(LauncherInstrumentation launcher, + boolean requireOneActiveActivity) { sActivityLeakReported = true; - return "Activity leak detector has found leaked activities, " - + dumpHprofData(launcher, false) + "."; + return "Activity leak detector has found leaked activities, requirining 1 activity: " + + requireOneActiveActivity + "; " + + dumpHprofData(launcher, false, requireOneActiveActivity) + "."; } - public static String dumpHprofData(LauncherInstrumentation launcher, boolean intentionalLeak) { + private static String dumpHprofData(LauncherInstrumentation launcher, boolean intentionalLeak, + boolean requireOneActiveActivity) { if (intentionalLeak) return "intentional leak; not generating dump"; String result; @@ -143,12 +174,12 @@ public abstract class AbstractLauncherUiTest { if (TestHelpers.isInLauncherProcess()) { Debug.dumpHprofData(fileName); } else { - final UiDevice device = UiDevice.getInstance(getInstrumentation()); + final UiDevice device = getUiDevice(); device.executeShellCommand( "am dumpheap " + device.getLauncherPackageName() + " " + fileName); } Log.d(TAG, "Saved leak dump, the leak is still present: " - + !launcher.noLeakedActivities()); + + !launcher.noLeakedActivities(requireOneActiveActivity)); sDumpWasGenerated = true; result = "saved memory dump as an artifact"; } catch (Throwable e) { @@ -169,8 +200,8 @@ public abstract class AbstractLauncherUiTest { if (TestHelpers.isInLauncherProcess()) { Utilities.enableRunningInTestHarnessForTests(); mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand( - TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()). - getString("result")); + TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()) + .getString("result")); mLauncher.setOnSettledStateAction( containerType -> executeOnLauncher( launcher -> @@ -188,12 +219,32 @@ public abstract class AbstractLauncherUiTest { @Rule public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule(); + @Rule + public SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + + public static void initialize(AbstractLauncherUiTest test) throws Exception { + initialize(test, false); + } + + public static void initialize( + AbstractLauncherUiTest test, boolean clearWorkspace) throws Exception { + test.reinitializeLauncherData(clearWorkspace); + test.mDevice.pressHome(); + test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null); + test.waitForState("Launcher internal state didn't switch to Home", + () -> LauncherState.NORMAL); + test.waitForResumed("Launcher internal state is still Background"); + // Check that we switched to home. + test.mLauncher.getWorkspace(); + AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher, true); + } + protected void clearPackageData(String pkg) throws IOException, InterruptedException { final CountDownLatch count = new CountDownLatch(2); final SimpleBroadcastReceiver broadcastReceiver = new SimpleBroadcastReceiver(i -> count.countDown()); broadcastReceiver.registerPkgActions(mTargetContext, pkg, - Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED); + Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED); mDevice.executeShellCommand("pm clear " + pkg); assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS)); @@ -206,7 +257,8 @@ public abstract class AbstractLauncherUiTest { final RuleChain inner = RuleChain .outerRule(new PortraitLandscapeRunner(this)) .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData)) - .around(viewCaptureRule); + // .around(viewCaptureRule) // b/315482167 + .around(new TestIsolationRule(mLauncher, true)); return TestHelpers.isInLauncherProcess() ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner) @@ -227,8 +279,6 @@ public abstract class AbstractLauncherUiTest { public void setUp() throws Exception { mLauncher.onTestStart(); - verifyKeyguardInvisible(); - final String launcherPackageName = mDevice.getLauncherPackageName(); try { final Context context = InstrumentationRegistry.getContext(); @@ -258,6 +308,66 @@ public abstract class AbstractLauncherUiTest { } } } + + onTestStart(); + } + + /** Method that should be called when a test starts. */ + public static void onTestStart() { + waitForSetupWizardDismissal(); + + if (TestStabilityRule.isPresubmit()) { + aggressivelyUnlockSysUi(); + } else { + verifyKeyguardInvisible(); + } + } + + private static boolean hasSystemUiObject(String resId) { + return getUiDevice().hasObject( + By.res(SYSTEMUI_PACKAGE, resId)); + } + + @NonNull + private static UiDevice getUiDevice() { + return UiDevice.getInstance(getInstrumentation()); + } + + private static void aggressivelyUnlockSysUi() { + final UiDevice device = getUiDevice(); + for (int i = 0; i < 10 && hasSystemUiObject("keyguard_status_view"); ++i) { + Log.d(TAG, "Before attempting to unlock the phone"); + try { + device.executeShellCommand("input keyevent 82"); + } catch (IOException e) { + throw new RuntimeException(e); + } + device.waitForIdle(); + } + Assert.assertTrue("Keyguard still visible", + TestHelpers.wait( + Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000)); + Log.d(TAG, "Keyguard is not visible"); + } + + /** Waits for setup wizard to go away. */ + private static void waitForSetupWizardDismissal() { + if (!TestStabilityRule.isPresubmit()) return; + + if (sFirstTimeWaitingForWizard) { + try { + getUiDevice().executeShellCommand( + "am force-stop com.google.android.setupwizard"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + final boolean wizardDismissed = TestHelpers.wait( + Until.gone(By.pkg("com.google.android.setupwizard").depth(0)), + sFirstTimeWaitingForWizard ? 120000 : 0); + sFirstTimeWaitingForWizard = false; + Assert.assertTrue("Setup wizard is still visible", wizardDismissed); } private static void verifyKeyguardInvisible() { @@ -329,6 +439,15 @@ public abstract class AbstractLauncherUiTest { }); } + // Execute an action on Launcher, but forgive it when launcher is null. + // Launcher can be null if teardown is happening after a failed setup step where launcher + // activity failed to be created. + protected void executeOnLauncherInTearDown(Consumer<Launcher> f) { + executeOnLauncher(launcher -> { + if (launcher != null) f.accept(launcher); + }); + } + // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call // expecting the results of that gesture because the wait can hide flakeness. protected void waitForState(String message, Supplier<LauncherState> state) { @@ -473,6 +592,12 @@ public abstract class AbstractLauncherUiTest { getInstrumentation().getTargetContext().startActivity(intent); assertTrue("App didn't start: " + selector, TestHelpers.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT)); + + // Wait for the Launcher to stop. + final LauncherInstrumentation launcherInstrumentation = new LauncherInstrumentation(); + Wait.atMost("Launcher activity didn't stop", + () -> !launcherInstrumentation.isLauncherActivityStarted(), + DEFAULT_ACTIVITY_TIMEOUT, launcherInstrumentation); } public static ActivityInfo resolveSystemAppInfo(String category) { |