Merge "Updating owners to include immediate Launcher team" into ub-launcher3-master
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 34b2bdb..d4c746f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -3,8 +3,8 @@
 import android.app.Activity;
 import android.content.Context;
 import android.os.Bundle;
-import android.util.Log;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
@@ -64,6 +64,12 @@
                         taskBaseIntentComponents);
                 return response;
             }
+
+            case TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED: {
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get());
+                return response;
+            }
         }
 
         return super.call(method);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index c6eafe6..a59c99c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -165,7 +165,7 @@
         mRectYAnim = new FlingSpringAnim(this, RECT_Y, startY, endY, startVelocityY,
                 mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener);
 
-        float minVisibleChange = 1f / mStartRect.height();
+        float minVisibleChange = Math.abs(1f / mStartRect.height());
         mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
                 .setSpring(new SpringForce(1f)
                 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 1229a63..cd94704 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -35,6 +35,7 @@
 
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.tapl.TestHelpers;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -199,7 +200,7 @@
                         + launcher.getNavigationModeMismatchError(),
                 () -> launcher.getNavigationModeMismatchError() == null,
                 60000 /* b/148422894 */, launcher);
-
+        AbstractLauncherUiTest.checkDetectedLeaks();
         return true;
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index ecfdb55..03d7c3e 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -81,6 +81,10 @@
 
     @Test
     public void testAllAppsFromOverview() throws Exception {
+        if (!mLauncher.hasAllAppsInOverview()) {
+            return;
+        }
+
         // Test opening all apps from Overview.
         assertNotNull("switchToAllApps() returned null",
                 mLauncher.getWorkspace().switchToOverview().switchToAllApps());
@@ -141,8 +145,8 @@
                 launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
                         numTasks - 1, getTaskCount(launcher)));
 
-        if (!TestHelpers.isInLauncherProcess() ||
-                getFromLauncher(launcher -> !launcher.getDeviceProfile().isLandscape)) {
+        if (mLauncher.hasAllAppsInOverview() && (!TestHelpers.isInLauncherProcess()
+                || getFromLauncher(launcher -> !launcher.getDeviceProfile().isLandscape))) {
             // Test switching to all apps and back.
             final AllAppsFromOverview allApps = overview.switchToAllApps();
             assertNotNull("overview.switchToAllApps() returned null (1)", allApps);
@@ -185,6 +189,10 @@
 
     @Test
     public void testAppIconLaunchFromAllAppsFromOverview() throws Exception {
+        if (!mLauncher.hasAllAppsInOverview()) {
+            return;
+        }
+
         final AllApps allApps =
                 mLauncher.getWorkspace().switchToOverview().switchToAllApps();
         assertTrue("Launcher internal state is not All Apps",
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 1dbe195..114c491 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -56,6 +56,7 @@
     private final ArrayMap<UserHandle, Boolean> mUninstallDisabledCache = new ArrayMap<>(1);
 
     private final Alarm mCacheExpireAlarm;
+    private boolean mHadPendingAlarm;
 
     protected int mCurrentAccessibilityAction = -1;
     public SecondaryDropTarget(Context context, AttributeSet attrs) {
@@ -70,6 +71,24 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mHadPendingAlarm) {
+            mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
+            mHadPendingAlarm = false;
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mCacheExpireAlarm.alarmPending()) {
+            mCacheExpireAlarm.cancelAlarm();
+            mHadPendingAlarm = true;
+        }
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         setupUi(UNINSTALL);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 3d8a9d7..d7498a5 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -124,7 +124,7 @@
             "Show launcher preview in grid picker");
 
     public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
-            "ENABLE_OVERVIEW_ACTIONS", false, "Show app actions instead of the shelf in Overview."
+            "ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview."
             + " As part of this decoupling, also distinguish swipe up from nav bar vs above it.");
 
     public static final BooleanFlag ENABLE_DATABASE_RESTORE = getDebugFlag(
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index f995c61..97ce65e 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -89,6 +89,8 @@
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
     public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
 
+    public static final String REQUEST_OVERVIEW_ACTIONS_ENABLED = "overview-actions-enabled";
+
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
 
     public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index cae2c3a..254655c 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -29,7 +29,6 @@
 import android.graphics.RectF;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.Property;
 import android.view.MotionEvent;
 import android.view.View;
@@ -41,8 +40,8 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.TouchController;
@@ -150,10 +149,20 @@
         return findActiveController(ev);
     }
 
+    private boolean isEventInLauncher(MotionEvent ev) {
+        final float x = ev.getX();
+        final float y = ev.getY();
+
+        return x >= mSystemGestureRegion.left && x < getWidth() - mSystemGestureRegion.right
+                && y >= mSystemGestureRegion.top && y < getHeight() - mSystemGestureRegion.bottom;
+    }
+
     private TouchController findControllerToHandleTouch(MotionEvent ev) {
-        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
-        if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
-            return topView;
+        if (isEventInLauncher(ev)) {
+            AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+            if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
+                return topView;
+            }
         }
 
         for (TouchController controller : mControllers) {
@@ -245,17 +254,12 @@
     public boolean dispatchTouchEvent(MotionEvent ev) {
         switch (ev.getAction()) {
             case ACTION_DOWN: {
-                float x = ev.getX();
-                float y = ev.getY();
                 mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
 
-                if ((y < mSystemGestureRegion.top
-                        || x < mSystemGestureRegion.left
-                        || x > (getWidth() - mSystemGestureRegion.right)
-                        || y > (getHeight() - mSystemGestureRegion.bottom))) {
-                    mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
-                } else {
+                if (isEventInLauncher(ev)) {
                     mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
+                } else {
+                    mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
                 }
                 break;
             }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 5e98184..e93df96 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -37,6 +37,7 @@
 import android.content.pm.PackageManager;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.StrictMode;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
@@ -95,6 +96,9 @@
     public static final long DEFAULT_UI_TIMEOUT = 10000;
     private static final String TAG = "AbstractLauncherUiTest";
 
+    private static String sDetectedActivityLeak;
+    private static boolean sActivityLeakReported;
+
     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
     protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
     protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation();
@@ -102,6 +106,41 @@
     protected String mTargetPackage;
     private int mLauncherPid;
 
+    static {
+        if (TestHelpers.isInLauncherProcess()) {
+            StrictMode.VmPolicy.Builder builder =
+                    new StrictMode.VmPolicy.Builder()
+                            .detectActivityLeaks()
+                            .penaltyLog()
+                            .penaltyListener(Runnable::run, violation -> {
+                                // Runs in the main thread. We can't dumpheap in the main thread,
+                                // so let's just mark the fact that the leak has happened.
+                                if (sDetectedActivityLeak == null) {
+                                    sDetectedActivityLeak = violation.toString();
+                                }
+                            });
+            StrictMode.setVmPolicy(builder.build());
+        }
+    }
+
+    public static void checkDetectedLeaks() {
+        if (sDetectedActivityLeak != null && !sActivityLeakReported) {
+            sActivityLeakReported = true;
+
+            final UiDevice device = UiDevice.getInstance(getInstrumentation());
+            try {
+                device.executeShellCommand(
+                        "am dumpheap "
+                                + device.getLauncherPackageName()
+                                + " "
+                                + getInstrumentation().getTargetContext().getFilesDir().getPath()
+                                + "/ActivityLeakHeapDump.hprof");
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
     protected AbstractLauncherUiTest() {
         mLauncher.enableCheckEventsForSuccessfulGestures();
         try {
@@ -120,6 +159,8 @@
                                     checkLauncherIntegrity(launcher, containerType)));
         }
         mLauncher.enableDebugTracing();
+        // Avoid double-reporting of Launcher crashes.
+        mLauncher.setOnLauncherCrashed(() -> mLauncherPid = 0);
     }
 
     protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
@@ -190,6 +231,7 @@
         if (mLauncherPid != 0) {
             assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
         }
+        checkDetectedLeaks();
     }
 
     protected void clearLauncherData() throws IOException, InterruptedException {
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 1a68122..38f50c1 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -56,6 +56,7 @@
             private void evaluateInPortrait() throws Throwable {
                 mTest.mDevice.setOrientationNatural();
                 mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
+                AbstractLauncherUiTest.checkDetectedLeaks();
                 base.evaluate();
                 mTest.getDevice().pressHome();
             }
@@ -63,6 +64,7 @@
             private void evaluateInLandscape() throws Throwable {
                 mTest.mDevice.setOrientationLeft();
                 mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
+                AbstractLauncherUiTest.checkDetectedLeaks();
                 base.evaluate();
                 mTest.getDevice().pressHome();
             }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 7475efe..f8bbf21 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -41,7 +41,6 @@
 import com.android.launcher3.widget.WidgetsRecyclerView;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -65,6 +64,7 @@
         test.waitForResumed("Launcher internal state is still Background");
         // Check that we switched to home.
         test.mLauncher.getWorkspace();
+        AbstractLauncherUiTest.checkDetectedLeaks();
     }
 
     // Please don't add negative test cases for methods that fail only after a long wait.
@@ -110,7 +110,6 @@
     }
 
     @Test
-    @Ignore
     public void testPressHomeOnAllAppsContextMenu() throws Exception {
         final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
index ebf7991..e4f520f 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -102,7 +102,7 @@
             logSinceBoot =
                     UiDevice.getInstance(getInstrumentation())
                             .executeShellCommand("logcat -d -t " + systemBootTime.replace(" ", ""));
-        } catch (IOException e) {
+        } catch (IOException | OutOfMemoryError e) {
             return 0;
         }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 6775521..b1aab50 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -167,6 +167,8 @@
     private static boolean sCheckingEvents;
 
     private boolean mCheckEventsForSuccessfulGestures = false;
+    private int mExpectedPid;
+    private Runnable mOnLauncherCrashed;
 
     private static Pattern getTouchEventPattern(String prefix, String action) {
         // The pattern includes sanity checks that we don't get a multi-touch events or other
@@ -249,6 +251,10 @@
         mCheckEventsForSuccessfulGestures = true;
     }
 
+    public void setOnLauncherCrashed(Runnable onLauncherCrashed) {
+        mOnLauncherCrashed = onLauncherCrashed;
+    }
+
     Context getContext() {
         return mInstrumentation.getContext();
     }
@@ -324,7 +330,7 @@
         }
     }
 
-    private String getAnomalyMessage() {
+    private String getSystemAnomalyMessage() {
         UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
         if (object != null) {
             return "System alert popup is visible: " + object.getText();
@@ -343,15 +349,33 @@
         return null;
     }
 
+    private String getAnomalyMessage() {
+        if (mExpectedPid != 0 && mExpectedPid != getPid()) {
+            mExpectedPid = 0;
+            if (mOnLauncherCrashed != null) mOnLauncherCrashed.run();
+            return "Launcher crashed";
+        }
+
+        final String systemAnomalyMessage = getSystemAnomalyMessage();
+        if (systemAnomalyMessage != null) {
+            return "http://go/tapl : Tests are broken by a non-Launcher system error: "
+                    + systemAnomalyMessage;
+        }
+
+        return null;
+    }
+
     public void checkForAnomaly() {
         final String anomalyMessage = getAnomalyMessage();
         if (anomalyMessage != null) {
-            String message = "http://go/tapl : Tests are broken by a non-Launcher system error: "
-                    + anomalyMessage;
-            log("Hierarchy dump for: " + message);
+            if (sCheckingEvents) {
+                sCheckingEvents = false;
+                sEventChecker.finishNoWait();
+            }
+            log("Hierarchy dump for: " + anomalyMessage);
             dumpViewHierarchy();
 
-            Assert.fail(formatSystemHealthMessage(message));
+            Assert.fail(formatSystemHealthMessage(anomalyMessage));
         }
     }
 
@@ -541,7 +565,7 @@
                     return waitForLauncherObject(APPS_RES_ID);
                 }
                 case OVERVIEW: {
-                    if (mDevice.isNaturalOrientation()) {
+                    if (hasAllAppsInOverview()) {
                         waitForLauncherObject(APPS_RES_ID);
                     } else {
                         waitUntilGone(APPS_RES_ID);
@@ -650,14 +674,6 @@
                     mDevice.waitForIdle();
 
                     if (getNavigationModel() == NavigationModel.TWO_BUTTON) {
-                        if (hasLauncherObject(CONTEXT_MENU_RES_ID) ||
-                                hasLauncherObject(WIDGETS_RES_ID)
-                                        && !mDevice.isNaturalOrientation()) {
-                            expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_PILFER_POINTERS);
-                        }
-                    }
-
-                    if (getNavigationModel() == NavigationModel.TWO_BUTTON) {
                         expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
                         expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
                     }
@@ -1197,6 +1213,28 @@
         getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
     }
 
+    public boolean hasAllAppsInOverview() {
+        // Vertical bar layouts don't contain all apps
+        if (!mDevice.isNaturalOrientation()) {
+            return false;
+        }
+        // Portrait two button (quickstep) always has all apps.
+        if (getNavigationModel() == NavigationModel.TWO_BUTTON) {
+            return true;
+        }
+        // Overview actions hide all apps
+        if (overviewActionsEnabled()) {
+            return false;
+        }
+        // ...otherwise there should be all apps
+        return true;
+    }
+
+    private boolean overviewActionsEnabled() {
+        return getTestInfo(TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED).getBoolean(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     public void disableDebugTracing() {
         getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
     }
@@ -1240,10 +1278,13 @@
             };
         }
         sCheckingEvents = true;
+        mExpectedPid = getPid();
         if (sEventChecker == null) sEventChecker = new LogEventChecker();
         sEventChecker.start();
 
         return () -> {
+            checkForAnomaly();
+
             if (sCheckingEvents) {
                 sCheckingEvents = false;
                 if (mCheckEventsForSuccessfulGestures) {