Adding tests that would reliably reproduce shifted search.
This would guaranteed to repro b/118441555 before it was fixed.
The test doesn't use the most powerful feature of race condition
reproducer, which is enumerating all possible event sequences.
Instead, it uses explicit repro sequences, which makes the test much
faster.
Bug: 120628042
Test: The added test
Change-Id: I89a7a9964f160a8a20ba3d9dda2f248237713014
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index b11260e..95be188 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -22,6 +22,8 @@
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
+import static com.android.launcher3.util.RaceConditionTracker.ENTER;
+import static com.android.launcher3.util.RaceConditionTracker.EXIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper
.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -47,6 +49,7 @@
import android.view.WindowManager;
import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.util.RaceConditionTracker;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -70,6 +73,7 @@
public class OtherActivityTouchConsumer extends ContextWrapper implements TouchConsumer {
private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150;
+ public static final String DOWN_EVT = "OtherActivityTouchConsumer.DOWN";
private final SparseArray<RecentsAnimationState> mAnimationStates = new SparseArray<>();
private final RunningTaskInfo mRunningTask;
@@ -135,6 +139,7 @@
mTouchInteractionLog.addMotionEvent(ev);
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
+ RaceConditionTracker.onEvent(DOWN_EVT, ENTER);
TraceHelper.beginSection("TouchInt");
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
@@ -151,6 +156,7 @@
Display display = getSystemService(WindowManager.class).getDefaultDisplay();
mDisplayRotation = display.getRotation();
WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+ RaceConditionTracker.onEvent(DOWN_EVT, EXIT);
break;
}
case ACTION_POINTER_UP: {
diff --git a/quickstep/tests/src/com/android/quickstep/RaceConditionsTests.java b/quickstep/tests/src/com/android/quickstep/RaceConditionsTests.java
new file mode 100644
index 0000000..f132b12
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/RaceConditionsTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static com.android.launcher3.util.RaceConditionTracker.enterEvt;
+import static com.android.launcher3.util.RaceConditionTracker.exitEvt;
+
+import android.content.Intent;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.util.RaceConditionReproducer;
+import com.android.quickstep.QuickStepOnOffRule.Mode;
+import com.android.quickstep.QuickStepOnOffRule.QuickstepOnOff;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class RaceConditionsTests extends AbstractQuickStepTest {
+ private void runTest(String... eventSequence) {
+ final RaceConditionReproducer eventProcessor = new RaceConditionReproducer(eventSequence);
+
+ // Destroy Launcher activity.
+ executeOnLauncher(launcher -> {
+ if (launcher != null) {
+ launcher.finish();
+ }
+ });
+ waitForLauncherCondition(
+ "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT);
+
+ // Start an activity where we'll press home.
+ startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+
+ // The test action.
+ eventProcessor.startIteration();
+ mLauncher.pressHome();
+ eventProcessor.finishIteration();
+ }
+
+ @Test
+ @QuickstepOnOff(mode = Mode.ON)
+ public void testPressHomeToStartLauncher() {
+ runTest(enterEvt(Launcher.ON_CREATE_EVT),
+ exitEvt(Launcher.ON_CREATE_EVT),
+ enterEvt(OtherActivityTouchConsumer.DOWN_EVT),
+ exitEvt(OtherActivityTouchConsumer.DOWN_EVT));
+
+ runTest(enterEvt(OtherActivityTouchConsumer.DOWN_EVT),
+ exitEvt(OtherActivityTouchConsumer.DOWN_EVT),
+ enterEvt(Launcher.ON_CREATE_EVT),
+ exitEvt(Launcher.ON_CREATE_EVT));
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTests.java b/quickstep/tests/src/com/android/quickstep/TaplTests.java
index 701229c..6a1123e 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTests.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTests.java
@@ -24,9 +24,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import android.app.Instrumentation;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.util.Log;
@@ -66,7 +64,6 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TaplTests extends AbstractQuickStepTest {
- private static final int WAIT_TIME_MS = 60000;
private static final String TAG = "TaplTests";
private static int sScreenshotCount = 0;
@@ -112,13 +109,6 @@
waitForResumed("Launcher internal state is still Background");
}
- private String resolveSystemApp(String category) {
- return getInstrumentation().getContext().getPackageManager().resolveActivity(
- new Intent(Intent.ACTION_MAIN).addCategory(category),
- PackageManager.MATCH_SYSTEM_ONLY).
- activityInfo.packageName;
- }
-
private boolean isInState(LauncherState state) {
if (!TestHelpers.isInLauncherProcess()) return true;
return getFromLauncher(launcher -> launcher.getStateManager().getState() == state);
@@ -143,17 +133,6 @@
return !launcher.hasBeenResumed();
}
- private void startAppFast(String packageName) {
- final Instrumentation instrumentation = getInstrumentation();
- final Intent intent = instrumentation.getContext().getPackageManager().
- getLaunchIntentForPackage(packageName);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- instrumentation.getTargetContext().startActivity(intent);
- assertTrue(packageName + " didn't start",
- mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), WAIT_TIME_MS));
- }
-
private void startTestApps() throws Exception {
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_MESSAGING));
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
@@ -338,7 +317,8 @@
assertNotNull("overview.getCurrentTask() returned null (1)", task);
assertNotNull("OverviewTask.open returned null", task.open());
assertTrue("Contacts app didn't open from Overview", mDevice.wait(Until.hasObject(
- By.pkg(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS)).depth(0)), WAIT_TIME_MS));
+ By.pkg(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS)).depth(0)),
+ LONG_WAIT_TIME_MS));
executeOnLauncher(launcher -> assertTrue(
"Launcher activity is the top activity; expecting another activity to be the top "
+ "one",
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index f8b44d0..9125a98 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -19,6 +19,7 @@
import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+
import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -26,6 +27,8 @@
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.logging.LoggerUtils.newTarget;
+import static com.android.launcher3.util.RaceConditionTracker.ENTER;
+import static com.android.launcher3.util.RaceConditionTracker.EXIT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -71,16 +74,18 @@
import android.view.animation.OvershootInterpolator;
import android.widget.Toast;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompatVO;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
@@ -116,6 +121,7 @@
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.RaceConditionTracker;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
@@ -143,8 +149,6 @@
import java.util.List;
import java.util.Set;
-import androidx.annotation.Nullable;
-
/**
* Default launcher application.
*/
@@ -184,6 +188,7 @@
private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
// Type: SparseArray<Parcelable>
private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
+ public static final String ON_CREATE_EVT = "Launcher.onCreate";
private LauncherStateManager mStateManager;
@@ -255,6 +260,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
+ RaceConditionTracker.onEvent(ON_CREATE_EVT, ENTER);
if (DEBUG_STRICT_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
@@ -349,6 +355,7 @@
mRotationHelper.initialize();
TraceHelper.endSection("Launcher-onCreate");
+ RaceConditionTracker.onEvent(ON_CREATE_EVT, EXIT);
}
@Override
diff --git a/src/com/android/launcher3/util/RaceConditionTracker.java b/src/com/android/launcher3/util/RaceConditionTracker.java
index 8eafb55..8b06787 100644
--- a/src/com/android/launcher3/util/RaceConditionTracker.java
+++ b/src/com/android/launcher3/util/RaceConditionTracker.java
@@ -22,6 +22,9 @@
* orders.
*/
public class RaceConditionTracker {
+ public final static boolean ENTER = true;
+ public final static boolean EXIT = false;
+
public interface EventProcessor {
void onEvent(String eventName);
}
@@ -35,4 +38,22 @@
public static void onEvent(String eventName) {
if (sEventProcessor != null) sEventProcessor.onEvent(eventName);
}
+
+ public static void onEvent(String eventName, boolean isEnter) {
+ if (sEventProcessor != null) {
+ sEventProcessor.onEvent(enterExitEvt(eventName, isEnter));
+ }
+ }
+
+ public static String enterExitEvt(String eventName, boolean isEnter) {
+ return eventName + ":" + (isEnter ? "enter" : "exit");
+ }
+
+ public static String enterEvt(String eventName) {
+ return enterExitEvt(eventName, ENTER);
+ }
+
+ public static String exitEvt(String eventName) {
+ return enterExitEvt(eventName, EXIT);
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index bc5aaee..338fd85 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -17,6 +17,7 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.Instrumentation;
@@ -25,11 +26,13 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageManager;
import android.os.Process;
import android.os.RemoteException;
import android.view.Surface;
import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiDevice;
@@ -77,6 +80,7 @@
public static final long SHORT_UI_TIMEOUT= 300;
public static final long DEFAULT_UI_TIMEOUT = 10000;
+ protected static final int LONG_WAIT_TIME_MS = 60000;
protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
protected final UiDevice mDevice;
@@ -325,4 +329,22 @@
return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
}
}
+
+ protected void startAppFast(String packageName) {
+ final Instrumentation instrumentation = getInstrumentation();
+ final Intent intent = instrumentation.getContext().getPackageManager().
+ getLaunchIntentForPackage(packageName);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ instrumentation.getTargetContext().startActivity(intent);
+ assertTrue(packageName + " didn't start",
+ mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), LONG_WAIT_TIME_MS));
+ }
+
+ protected String resolveSystemApp(String category) {
+ return getInstrumentation().getContext().getPackageManager().resolveActivity(
+ new Intent(Intent.ACTION_MAIN).addCategory(category),
+ PackageManager.MATCH_SYSTEM_ONLY).
+ activityInfo.packageName;
+ }
}
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
index c4350de..316e40d 100644
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
+++ b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
@@ -43,6 +43,11 @@
* executing events in previously unseen order. It does it by postponing execution of threads that
* would lead to an already seen sequence.
*
+ * If an event A occurs before event B in the sequence, this is how execution order looks like:
+ * Events: ... A ... B ...
+ * Events and instructions, guaranteed order:
+ * (instructions executed prior to A) A ... B (instructions executed after B)
+ *
* Each iteration has 3 parts (phases).
* Phase 1. Picking a previously seen event subsequence that we believe can have previously unseen
* continuations. Reproducing this sequence by pausing threads that would lead to other sequences.
@@ -178,6 +183,10 @@
mReproString = reproString;
}
+ public RaceConditionReproducer(String... reproSequence) {
+ this(String.join("|", reproSequence));
+ }
+
public synchronized String getCurrentSequenceString() {
return mCurrentSequence.toString();
}