Merge "Update RecentsActivityRotation when device rotate"
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 2d9d092..470a442 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -258,6 +258,18 @@
             @NonNull RemoteAnimationTargetCompat[] appTargets,
             @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing);
 
+    private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTargetCompat[] targets) {
+        boolean isAllOpeningTargetTrs = true;
+        for (int i = 0; i < targets.length; i++) {
+            RemoteAnimationTargetCompat target = targets[i];
+            if (target.mode == MODE_OPENING) {
+                isAllOpeningTargetTrs &= target.isTranslucent;
+            }
+            if (!isAllOpeningTargetTrs) break;
+        }
+        return isAllOpeningTargetTrs;
+    }
+
     /**
      * Compose the animations for a launch from the app icon.
      *
@@ -275,16 +287,8 @@
         mLauncher.getStateManager().setCurrentAnimation(anim);
 
         Rect windowTargetBounds = getWindowTargetBounds(appTargets);
-        boolean isAllOpeningTargetTrs = true;
-        for (int i = 0; i < appTargets.length; i++) {
-            RemoteAnimationTargetCompat target = appTargets[i];
-            if (target.mode == MODE_OPENING) {
-                isAllOpeningTargetTrs &= target.isTranslucent;
-            }
-            if (!isAllOpeningTargetTrs) break;
-        }
         anim.play(getOpeningWindowAnimators(v, appTargets, wallpaperTargets, windowTargetBounds,
-                !isAllOpeningTargetTrs));
+                areAllTargetsTranslucent(appTargets)));
         if (launcherClosing) {
             Pair<AnimatorSet, Runnable> launcherContentAnimator =
                     getLauncherContentAnimator(true /* isAppOpening */,
@@ -450,10 +454,10 @@
     private Animator getOpeningWindowAnimators(View v,
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets,
-            Rect windowTargetBounds, boolean toggleVisibility) {
+            Rect windowTargetBounds, boolean appTargetsAreTranslucent) {
         RectF launcherIconBounds = new RectF();
         FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
-                toggleVisibility, launcherIconBounds, true /* isOpening */);
+                !appTargetsAreTranslucent, launcherIconBounds, true /* isOpening */);
         Rect crop = new Rect();
         Matrix matrix = new Matrix();
 
@@ -500,6 +504,7 @@
                 : 0f;
         final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
                 ? 0 : getWindowCornerRadius(mLauncher.getResources());
+        final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius;
 
         appAnimator.addUpdateListener(new MultiValueUpdateListener() {
             FloatProp mDx = new FloatProp(0, prop.dX, 0, prop.xDuration, AGGRESSIVE_EASE);
@@ -512,7 +517,7 @@
 
             FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0,
                     RADIUS_DURATION, EXAGGERATED_EASE);
-            FloatProp mShadowRadius = new FloatProp(0, mMaxShadowRadius, 0,
+            FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0,
                     APP_LAUNCH_DURATION, EXAGGERATED_EASE);
 
             FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd,
@@ -765,12 +770,13 @@
         int duration = CLOSING_TRANSITION_DURATION_MS;
         float windowCornerRadius = mDeviceProfile.isMultiWindowMode
                 ? 0 : getWindowCornerRadius(mLauncher.getResources());
+        float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
         closingAnimator.setDuration(duration);
         closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
             FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
             FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7);
             FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
-            FloatProp mShadowRadius = new FloatProp(mMaxShadowRadius, 0, 0, duration,
+            FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, 0, duration,
                     DEACCEL_1_7);
 
             @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 4f62d2f..a00ce56 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -64,7 +64,7 @@
 import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarToOverviewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadHelper;
@@ -285,7 +285,7 @@
                 list.add(new NoButtonNavbarToOverviewTouchController(this));
                 break;
             case TWO_BUTTONS:
-                list.add(new TwoButtonNavbarToOverviewTouchController(this));
+                list.add(new TwoButtonNavbarTouchController(this));
                 list.add(getDeviceProfile().isVerticalBarLayout()
                         ? new TransposedQuickSwitchTouchController(this)
                         : new QuickSwitchTouchController(this));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarToOverviewTouchController.java
deleted file mode 100644
index ff4bfe6..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarToOverviewTouchController.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2020 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.launcher3.uioverrides.touchcontrollers;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
-
-import android.view.MotionEvent;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
-import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.quickstep.SystemUiProxy;
-
-/**
- * Touch controller for handling edge swipes in 2-button mode
- */
-public class TwoButtonNavbarToOverviewTouchController extends AbstractStateChangeTouchController {
-
-    private static final String TAG = "2BtnNavbarTouchCtrl";
-
-    public TwoButtonNavbarToOverviewTouchController(Launcher l) {
-        super(l, l.getDeviceProfile().isVerticalBarLayout()
-                ? SingleAxisSwipeDetector.HORIZONTAL : SingleAxisSwipeDetector.VERTICAL);
-    }
-
-    @Override
-    protected boolean canInterceptTouch(MotionEvent ev) {
-        if (mCurrentAnimation != null) {
-            // If we are already animating from a previous state, we can intercept.
-            return true;
-        }
-        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
-            return false;
-        }
-        return mLauncher.isInState(NORMAL) && (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
-    }
-
-    @Override
-    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            boolean draggingFromNav =
-                    mLauncher.getDeviceProfile().isSeascape() == isDragTowardPositive;
-            return draggingFromNav ? OVERVIEW : NORMAL;
-        } else {
-            return isDragTowardPositive ? OVERVIEW : NORMAL;
-        }
-    }
-
-    @Override
-    protected float getShiftRange() {
-        return mLauncher.getDeviceProfile().isVerticalBarLayout()
-                ? mLauncher.getDragLayer().getWidth() : super.getShiftRange();
-    }
-
-    @Override
-    protected float initCurrentAnimation(@AnimationFlags int animComponent) {
-        float range = getShiftRange();
-        long maxAccuracy = (long) (2 * range);
-        mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
-                maxAccuracy, animComponent);
-        return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
-    }
-
-    @Override
-    protected void onSwipeInteractionCompleted(LauncherState targetState) {
-        super.onSwipeInteractionCompleted(targetState);
-        if (mStartState == NORMAL && targetState == OVERVIEW) {
-            SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
new file mode 100644
index 0000000..6271a44
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 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.launcher3.uioverrides.touchcontrollers;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
+import static com.android.launcher3.AbstractFloatingView.getOpenView;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+
+import android.animation.ValueAnimator;
+import android.view.MotionEvent;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.touch.AbstractStateChangeTouchController;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.views.AllAppsEduView;
+
+/**
+ * Touch controller for handling edge swipes in 2-button mode
+ */
+public class TwoButtonNavbarTouchController extends AbstractStateChangeTouchController {
+
+    private static final int MAX_NUM_SWIPES_TO_TRIGGER_EDU = 3;
+
+    private static final String TAG = "2BtnNavbarTouchCtrl";
+
+    private final boolean mIsTransposed;
+
+    // If true, we will finish the current animation instantly on second touch.
+    private boolean mFinishFastOnSecondTouch;
+
+    private int mContinuousTouchCount = 0;
+
+    public TwoButtonNavbarTouchController(Launcher l) {
+        super(l, l.getDeviceProfile().isVerticalBarLayout()
+                ? SingleAxisSwipeDetector.HORIZONTAL : SingleAxisSwipeDetector.VERTICAL);
+        mIsTransposed = l.getDeviceProfile().isVerticalBarLayout();
+    }
+
+    @Override
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        boolean canIntercept = canInterceptTouchInternal(ev);
+        if (!canIntercept) {
+            mContinuousTouchCount = 0;
+        }
+        return canIntercept;
+    }
+
+    private boolean canInterceptTouchInternal(MotionEvent ev) {
+        if (mCurrentAnimation != null) {
+            if (mFinishFastOnSecondTouch) {
+                mCurrentAnimation.getAnimationPlayer().end();
+            }
+
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return false;
+        }
+        if ((ev.getEdgeFlags() & EDGE_NAV_BAR) == 0) {
+            return false;
+        }
+        if (!mIsTransposed && mLauncher.isInState(OVERVIEW)) {
+            return true;
+        }
+        return mLauncher.isInState(NORMAL);
+    }
+
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        if (mIsTransposed) {
+            boolean draggingFromNav =
+                    mLauncher.getDeviceProfile().isSeascape() == isDragTowardPositive;
+            return draggingFromNav ? OVERVIEW : NORMAL;
+        } else {
+            return isDragTowardPositive ^ (fromState == OVERVIEW) ? OVERVIEW : NORMAL;
+        }
+    }
+
+    @Override
+    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
+            LauncherState targetState, float velocity, boolean isFling) {
+        super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
+                velocity, isFling);
+        mFinishFastOnSecondTouch = !mIsTransposed && mFromState == NORMAL;
+    }
+
+    @Override
+    protected float getShiftRange() {
+        return mLauncher.getDeviceProfile().isVerticalBarLayout()
+                ? mLauncher.getDragLayer().getWidth() : super.getShiftRange();
+    }
+
+    @Override
+    protected float initCurrentAnimation(@AnimationFlags int animComponent) {
+        float range = getShiftRange();
+        long maxAccuracy = (long) (2 * range);
+        mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
+                maxAccuracy, animComponent);
+        return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
+    }
+
+    @Override
+    protected void onSwipeInteractionCompleted(LauncherState targetState) {
+        super.onSwipeInteractionCompleted(targetState);
+        if (!mIsTransposed) {
+            mContinuousTouchCount++;
+        }
+        if (mStartState == NORMAL && targetState == OVERVIEW) {
+            SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
+        } else if (targetState == NORMAL
+                && mContinuousTouchCount >= MAX_NUM_SWIPES_TO_TRIGGER_EDU) {
+            mContinuousTouchCount = 0;
+            if (getOpenView(mLauncher, TYPE_ALL_APPS_EDU) == null) {
+                AllAppsEduView.show(mLauncher);
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3d5b1c6..d648dd6 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1395,6 +1395,7 @@
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
                     UI_HELPER_EXECUTOR.execute(() -> {
+                        if (mRecentsAnimationController == null) return;
                         final ThumbnailData taskSnapshot =
                                 mRecentsAnimationController.screenshotTask(runningTaskId);
                         MAIN_EXECUTOR.execute(() -> {
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index c0087b0..ca73041 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -293,7 +293,7 @@
             builder.withMatrix(mMatrix)
                     .withWindowCrop(mCropRect)
                     .withCornerRadius(params.getCornerRadius())
-                    .withShadowRadius(params.getShadowRadius());
+                    .withShadowRadius(app.isTranslucent ? 0 : params.getShadowRadius());
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 5a7f541..65bcf26 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -325,7 +325,7 @@
         builder.withMatrix(mMatrix)
                 .withWindowCrop(mTmpCropRect)
                 .withCornerRadius(getCurrentCornerRadius())
-                .withShadowRadius(params.getShadowRadius());
+                .withShadowRadius(app.isTranslucent ? 0 : params.getShadowRadius());
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
             // When relativeLayer = 0, it reverts the surfaces back to the original order.
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 475953b..2e7e6e0 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -56,7 +56,6 @@
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.FailureRewriterRule;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.quickstep.views.RecentsView;
 
@@ -103,8 +102,7 @@
         }
 
         mOrderSensitiveRules = RuleChain
-                .outerRule(new FailureRewriterRule())
-                .around(new NavigationModeSwitchRule(mLauncher))
+                .outerRule(new NavigationModeSwitchRule(mLauncher))
                 .around(new FailureWatcher(mDevice));
 
         mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
@@ -221,7 +219,7 @@
         OverviewTask task = overview.getCurrentTask();
         assertNotNull("overview.getCurrentTask() returned null (1)", task);
         assertNotNull("OverviewTask.open returned null", task.open());
-        assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject(
+        assertTrue("Test activity didn't open from Overview", TestHelpers.wait(Until.hasObject(
                 By.pkg(getAppPackageName()).text("TestActivity2")),
                 DEFAULT_UI_TIMEOUT));
 
@@ -238,7 +236,7 @@
 
         // Test dismissing all tasks.
         pressHomeAndGoToOverview().dismissAllTasks();
-        assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
+        assertTrue("Fallback Launcher not visible", TestHelpers.wait(Until.hasObject(By.pkg(
                 mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
     }
 
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index ba28e82..2da06e9 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -76,7 +76,8 @@
     private boolean mInitialized;
     private boolean mDestroyed;
 
-    private int mLastActivityFlags = -1;
+    // Initialize mLastActivityFlags to a value not used by SCREEN_ORIENTATION flags
+    private int mLastActivityFlags = -999;
 
     public RotationHelper(Activity activity) {
         mActivity = activity;
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index 539768e..14df0f3 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -34,7 +34,6 @@
 
     public static final String HOME_BOUNCE_SEEN = "launcher.apps_view_shown";
     public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
-    public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count";
     public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
     public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen";
 
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 37dd4d2..b3c1240 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -63,7 +63,6 @@
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.FailureRewriterRule;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
@@ -100,6 +99,7 @@
     private static final String TAG = "AbstractLauncherUiTest";
 
     private static String sStrictmodeDetectedActivityLeak;
+    private static boolean sDumpWasGenerated = false;
     private static boolean sActivityLeakReported;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
     protected static final ActivityLeakTracker ACTIVITY_LEAK_TRACKER = new ActivityLeakTracker();
@@ -151,10 +151,18 @@
     }
 
     public static String dumpHprofData() {
+        if (sDumpWasGenerated) return "dump has already been generated by another test";
         try {
             final String fileName = getInstrumentation().getTargetContext().getFilesDir().getPath()
                     + "/ActivityLeakHeapDump.hprof";
-            Debug.dumpHprofData(fileName);
+            if (TestHelpers.isInLauncherProcess()) {
+                Debug.dumpHprofData(fileName);
+            } else {
+                final UiDevice device = UiDevice.getInstance(getInstrumentation());
+                device.executeShellCommand(
+                        "am dumpheap " + device.getLauncherPackageName() + " " + fileName);
+            }
+            sDumpWasGenerated = true;
             return "memory dump filename: " + fileName;
         } catch (Throwable e) {
             Log.e(TAG, "dumpHprofData failed", e);
@@ -224,9 +232,8 @@
     }
 
     @Rule
-    public TestRule mOrderSensitiveRules = RuleChain.
-            outerRule(new FailureRewriterRule())
-            .around(new TestStabilityRule())
+    public TestRule mOrderSensitiveRules = RuleChain
+            .outerRule(new TestStabilityRule())
             .around(mActivityMonitor)
             .around(getRulesInsideActivityMonitor());
 
@@ -237,7 +244,7 @@
     @Before
     public void setUp() throws Exception {
         Assert.assertTrue("Keyguard is visible",
-                mDevice.wait(
+                TestHelpers.wait(
                         Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000));
 
         final String launcherPackageName = mDevice.getLauncherPackageName();
@@ -470,8 +477,7 @@
         }
         getInstrumentation().getTargetContext().startActivity(intent);
         assertTrue("App didn't start: " + selector,
-                UiDevice.getInstance(getInstrumentation())
-                        .wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
+                TestHelpers.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
     }
 
     public static ActivityInfo resolveSystemAppInfo(String category) {
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
deleted file mode 100644
index 77546de..0000000
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2020 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.launcher3.util.rule;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import android.os.SystemClock;
-
-import androidx.test.uiautomator.UiDevice;
-
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.regex.Pattern;
-
-class FailureInvestigator {
-    private static boolean matches(String regex, CharSequence string) {
-        return Pattern.compile(regex).matcher(string).find();
-    }
-
-    static class LogcatMatch {
-        String logcatPattern;
-        int bug;
-
-        LogcatMatch(String logcatPattern, int bug) {
-            this.logcatPattern = logcatPattern;
-            this.bug = bug;
-        }
-    }
-
-    static class ExceptionMatch {
-        String exceptionPattern;
-        LogcatMatch[] logcatMatches;
-
-        ExceptionMatch(String exceptionPattern, LogcatMatch[] logcatMatches) {
-            this.exceptionPattern = exceptionPattern;
-            this.logcatMatches = logcatMatches;
-        }
-    }
-
-    private static final ExceptionMatch[] EXCEPTION_MATCHES = {
-            new ExceptionMatch(
-                    "java.lang.AssertionError: http://go/tapl : Tests are broken by a "
-                            + "non-Launcher system error: (Phone is locked|Screen is empty)",
-                    new LogcatMatch[]{
-                            new LogcatMatch(
-                                    "BroadcastQueue: Can't deliver broadcast to com.android"
-                                            + ".systemui.*Crashing it",
-                                    147845913),
-                            new LogcatMatch(
-                                    "Attempt to invoke virtual method 'boolean android\\"
-                                            + ".graphics\\.Bitmap\\.isRecycled\\(\\)' on a null "
-                                            + "object reference",
-                                    148424291),
-                            new LogcatMatch(
-                                    "java\\.lang\\.IllegalArgumentException\\: Ranking map "
-                                            + "doesn't contain key",
-                                    148570537),
-                    }),
-            new ExceptionMatch("Launcher didn't initialize",
-                    new LogcatMatch[]{
-                            new LogcatMatch(
-                                    "ActivityManager: Reason: executing service com.google"
-                                            + ".android.apps.nexuslauncher/com.android.launcher3"
-                                            + ".notification.NotificationListener",
-                                    148238677),
-                    }),
-    };
-
-    static int getBugForFailure(CharSequence exception) {
-        if ("com.google.android.setupwizard".equals(
-                UiDevice.getInstance(getInstrumentation()).getLauncherPackageName())) {
-            return 145935261;
-        }
-
-        final String logSinceBoot;
-        try {
-            final String systemBootTime =
-                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(
-                            new Date(System.currentTimeMillis() - SystemClock.elapsedRealtime()));
-
-            logSinceBoot =
-                    UiDevice.getInstance(getInstrumentation())
-                            .executeShellCommand("logcat -d -t " + systemBootTime.replace(" ", ""));
-        } catch (IOException | OutOfMemoryError e) {
-            return 0;
-        }
-
-        if (matches("android\\:\\:uirenderer\\:\\:renderthread\\:\\:EglManager\\:\\:swapBuffers",
-                logSinceBoot)) {
-            return 148529608;
-        }
-
-        for (ExceptionMatch exceptionMatch : EXCEPTION_MATCHES) {
-            if (matches(exceptionMatch.exceptionPattern, exception)) {
-                for (LogcatMatch logcatMatch : exceptionMatch.logcatMatches) {
-                    if (matches(logcatMatch.logcatPattern, logSinceBoot)) {
-                        return logcatMatch.bug;
-                    }
-                }
-                break;
-            }
-        }
-
-        return 0;
-    }
-}
diff --git a/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java b/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
deleted file mode 100644
index 99ddee4..0000000
--- a/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2020 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.launcher3.util.rule;
-
-import android.util.Log;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-public class FailureRewriterRule implements TestRule {
-    private static final String TAG = "FailureRewriter";
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                try {
-                    base.evaluate();
-                } catch (Throwable e) {
-                    final int bug = FailureInvestigator.getBugForFailure(e.toString());
-                    if (bug == 0) throw e;
-
-                    Log.e(TAG, "Known bug found for the original failure "
-                            + android.util.Log.getStackTraceString(e));
-                    throw new AssertionError(
-                            "Detected a failure that matches a known bug b/" + bug);
-                }
-            }
-        };
-    }
-}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 093c024..3fc83ff 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -68,8 +68,7 @@
 
         mLauncher.assertTrue(
                 "App didn't start: " + label,
-                mLauncher.getDevice().wait(Until.hasObject(selector),
-                        LauncherInstrumentation.WAIT_TIME_MS));
+                TestHelpers.wait(Until.hasObject(selector), LauncherInstrumentation.WAIT_TIME_MS));
         return new Background(mLauncher);
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index e2a442d..0c8f610 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -933,7 +933,7 @@
 
     @NonNull
     UiObject2 waitForAndroidObject(String resId) {
-        final UiObject2 object = mDevice.wait(
+        final UiObject2 object = TestHelpers.wait(
                 Until.findObject(By.res(ANDROID_PACKAGE, resId)), WAIT_TIME_MS);
         assertNotNull("Can't find a android object with id: " + resId, object);
         return object;
diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
index b8791e8..7f6062f 100644
--- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
+++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
@@ -27,6 +27,11 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.DropBoxManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.uiautomator.SearchCondition;
+import androidx.test.uiautomator.UiDevice;
 
 import org.junit.Assert;
 
@@ -35,6 +40,7 @@
 
 public class TestHelpers {
 
+    private static final String TAG = "Tapl";
     private static Boolean sIsInLauncherProcess;
 
     public static boolean isInLauncherProcess() {
@@ -154,4 +160,12 @@
             return null;
         }
     }
+
+    public static <R> R wait(SearchCondition<R> condition, long timeout) {
+        Log.d(TAG,
+                "TestHelpers.wait, condition=" + timeout + ", time=" + SystemClock.uptimeMillis());
+        final R result = UiDevice.getInstance(getInstrumentation()).wait(condition, timeout);
+        Log.d(TAG, "TestHelpers.wait, result=" + result + ", time=" + SystemClock.uptimeMillis());
+        return result;
+    }
 }