summaryrefslogtreecommitdiff
path: root/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java')
-rw-r--r--tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java159
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) {