Merge "Call close for AbstractFloatingViews that were removed while still open." into ub-launcher3-edmonton
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index c24850d..fe8d65e 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -20,7 +20,6 @@
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.launcher3">
-    <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
 
     <!--
     The manifest defines the common entries that should be present in any derivative of Launcher3.
diff --git a/build.gradle b/build.gradle
index 0030b8b..4ae6600 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,14 +1,16 @@
 buildscript {
     repositories {
         mavenCentral()
-        jcenter()
+        google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.3.3'
-        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
+        classpath 'com.android.tools.build:gradle:3.2.0-alpha12'
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.3'
     }
 }
 
+final String SUPPORT_LIBS_VERSION = '28.0.0-SNAPSHOT'
+
 apply plugin: 'com.android.application'
 apply plugin: 'com.google.protobuf'
 
@@ -23,6 +25,7 @@
         versionName "1.0"
 
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+        vectorDrawables.useSupportLibrary = true
     }
     buildTypes {
         debug {
@@ -30,18 +33,28 @@
         }
     }
 
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    flavorDimensions "default"
+
     productFlavors {
         aosp {
+            dimension "default"
             applicationId 'com.android.launcher3'
             testApplicationId 'com.android.launcher3.tests'
         }
 
         l3go {
+            dimension "default"
             applicationId 'com.android.launcher3'
             testApplicationId 'com.android.launcher3.tests'
         }
 
         quickstep {
+            dimension "default"
             applicationId 'com.android.launcher3'
             testApplicationId 'com.android.launcher3.tests'
         }
@@ -98,27 +111,28 @@
 }
 
 repositories {
+    maven { url "../../../prebuilts/fullsdk-darwin/extras/android/m2repository" }
+    maven { url "../../../prebuilts/fullsdk-linux/extras/android/m2repository" }
     mavenCentral()
-    jcenter()
+    google()
 }
 
-final String SUPPORT_LIBS_VERSION = '28.0.0-SNAPSHOT'
 dependencies {
-    compile "com.android.support:support-v4:${SUPPORT_LIBS_VERSION}"
-    compile "com.android.support:support-dynamic-animation:${SUPPORT_LIBS_VERSION}"
-    compile "com.android.support:recyclerview-v7:${SUPPORT_LIBS_VERSION}"
-    compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7'
+    implementation "com.android.support:support-v4:${SUPPORT_LIBS_VERSION}"
+    implementation "com.android.support:support-dynamic-animation:${SUPPORT_LIBS_VERSION}"
+    implementation "com.android.support:recyclerview-v7:${SUPPORT_LIBS_VERSION}"
+    implementation 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7'
 
-    quickstepCompile fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
+    quickstepImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
 
-    testCompile 'junit:junit:4.12'
-    androidTestCompile "org.mockito:mockito-core:1.9.5"
-    androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
-    androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
-    androidTestCompile 'com.android.support.test:runner:1.0.0'
-    androidTestCompile 'com.android.support.test:rules:1.0.0'
-    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
-    androidTestCompile "com.android.support:support-annotations:${SUPPORT_LIBS_VERSION}"
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation "org.mockito:mockito-core:1.9.5"
+    androidTestImplementation 'com.google.dexmaker:dexmaker:1.2'
+    androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2'
+    androidTestImplementation 'com.android.support.test:runner:1.0.0'
+    androidTestImplementation 'com.android.support.test:rules:1.0.0'
+    androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
+    androidTestImplementation "com.android.support:support-annotations:${SUPPORT_LIBS_VERSION}"
 }
 
 protobuf {
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index f62d1d6..bab2cd7 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -21,8 +21,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.launcher3" >
 
-    <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
-
     <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index d2989ef..0adc83a 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index b7888e4..29399142 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -34,20 +35,31 @@
 public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
 
     private final Handler mHandler;
+    private final boolean mStartAtFrontOfQueue;
     private AnimationResult mAnimationResult;
 
-    public LauncherAnimationRunner(Handler handler) {
+    /**
+     * @param startAtFrontOfQueue If true, the animation start will be posted at the front of the
+     *                            queue to minimize latency.
+     */
+    public LauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue) {
         mHandler = handler;
+        mStartAtFrontOfQueue = startAtFrontOfQueue;
     }
 
     @BinderThread
     @Override
     public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats, Runnable runnable) {
-        postAsyncCallback(mHandler, () -> {
+        Runnable r = () -> {
             finishExistingAnimation();
             mAnimationResult = new AnimationResult(runnable);
             onCreateAnimation(targetCompats, mAnimationResult);
-        });
+        };
+        if (mStartAtFrontOfQueue) {
+            postAtFrontOfQueueAsynchronously(mHandler, r);
+        } else {
+            postAsyncCallback(mHandler, r);
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 353ed84..ab350e4 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -162,7 +162,8 @@
     @Override
     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
         if (hasControlRemoteAppTransitionPermission()) {
-            RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler) {
+            RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler,
+                    true /* startAtFrontOfQueue */) {
 
                 @Override
                 public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
@@ -572,7 +573,7 @@
      *         ie. pressing home, swiping up from nav bar.
      */
     private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
-        return new LauncherAnimationRunner(mHandler) {
+        return new LauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */) {
             @Override
             public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
                     AnimationResult result) {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 0185ab9..31bead1 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -172,7 +172,8 @@
         }
 
         final TaskView taskView = (TaskView) v;
-        RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mUiHandler) {
+        RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mUiHandler,
+                true /* startAtFrontOfQueue */) {
 
             @Override
             public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
@@ -226,6 +227,14 @@
     }
 
     @Override
+    protected void onStop() {
+        super.onStop();
+
+        // Workaround for b/78520668, explicitly trim memory once UI is hidden
+        UiFactory.onTrimMemory(this, TRIM_MEMORY_UI_HIDDEN);
+    }
+
+    @Override
     public void onTrimMemory(int level) {
         super.onTrimMemory(level);
         UiFactory.onTrimMemory(this, level);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index ebe2311..4ba9e02 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -115,4 +115,15 @@
             }
         });
     }
+
+    public void hideCurrentInputMethod() {
+        BackgroundExecutor.get().submit(() -> {
+            synchronized (this) {
+                TraceHelper.partitionSection("RecentsController", "Hiding currentinput method");
+                if (controller != null) {
+                    controller.hideCurrentInputMethod();
+                }
+            }
+        });
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index ed7b7ab..b8be6b8 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -451,30 +451,24 @@
             }
         }
 
-        if (mLauncherTransitionController != null) {
-            Runnable runOnUi = () -> {
-                if (mLauncherTransitionController == null) {
-                    return;
-                }
-                mLauncherTransitionController.setPlayFraction(shift);
-
-                if (mRecentsAnimationWrapper.controller != null) {
-                    // TODO: This logic is spartanic!
-                    boolean passedThreshold = shift > 0.12f;
-                    mRecentsAnimationWrapper.setAnimationTargetsBehindSystemBars(!passedThreshold);
-                    if (mActivityControlHelper.shouldMinimizeSplitScreen()) {
-                        mRecentsAnimationWrapper
-                                .setSplitScreenMinimizedForTransaction(passedThreshold);
-                    }
-                }
-            };
-            if (Looper.getMainLooper() == Looper.myLooper()) {
-                runOnUi.run();
-            } else {
-                // The fling operation completed even before the launcher was drawn
-                mMainExecutor.execute(runOnUi);
+        if (mRecentsAnimationWrapper.controller != null) {
+            // TODO: This logic is spartanic!
+            boolean passedThreshold = shift > 0.12f;
+            mRecentsAnimationWrapper.setAnimationTargetsBehindSystemBars(!passedThreshold);
+            if (mActivityControlHelper.shouldMinimizeSplitScreen()) {
+                mRecentsAnimationWrapper
+                        .setSplitScreenMinimizedForTransaction(passedThreshold);
             }
         }
+
+        mMainExecutor.execute(this::updateFinalShiftUi);
+    }
+
+    private void updateFinalShiftUi() {
+        if (mLauncherTransitionController == null) {
+            return;
+        }
+        mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
     }
 
     public void onRecentsAnimationStart(RecentsAnimationControllerCompat controller,
@@ -521,6 +515,7 @@
         notifyGestureStartedAsync();
         setStateOnUiThread(STATE_GESTURE_STARTED);
         mGestureStarted = true;
+        mRecentsAnimationWrapper.hideCurrentInputMethod();
         mRecentsAnimationWrapper.enableInputConsumer();
         ActivityManagerWrapper.getInstance().closeSystemWindows(
                 CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index a4d3a50..fb4aa02 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -35,7 +35,6 @@
     public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setOverviewStateEnabled(true);
-        updateEmptyMessage();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index eb67155..057e0c4 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -50,8 +50,6 @@
     private final RectF mSourceRect = new RectF();
     // The bounds of the task view in launcher window coordinates
     private final RectF mTargetRect = new RectF();
-    // Doesn't change after initialized, used as an anchor when changing mTargetOffset
-    private final PointF mInitialTargetOffset = new PointF();
     // Set when the final window destination is changed, such as offsetting for quick scrub
     private final PointF mTargetOffset = new PointF();
     // The insets to be used for clipping the app window, which can be larger than mSourceInsets
@@ -84,9 +82,8 @@
                 mSourceStackBounds.width() - mSourceInsets.right,
                 mSourceStackBounds.height() - mSourceInsets.bottom);
         mTargetRect.set(targetRect);
-        mInitialTargetOffset.set(mHomeStackBounds.left - mSourceStackBounds.left,
+        mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
                 mHomeStackBounds.top - mSourceStackBounds.top);
-        mTargetOffset.set(mInitialTargetOffset);
 
         // Calculate the clip based on the target rect (since the content insets and the
         // launcher insets may differ, so the aspect ratio of the target rect can differ
@@ -106,10 +103,11 @@
 
     public void applyTransform(RemoteAnimationTargetSet targetSet, float progress) {
         RectF currentRect;
-            mTmpRectF.set(mTargetRect);
-            Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale);
-            currentRect =  mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
-            synchronized (mTargetOffset) {
+        mTmpRectF.set(mTargetRect);
+        Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale);
+        currentRect =  mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
+
+        synchronized (mTargetOffset) {
             // Stay lined up with the center of the target, since it moves for quick scrub.
             currentRect.offset(mTargetOffset.x * SCROLL.getInterpolation(progress),
                     mTargetOffset.y  * LINEAR.getInterpolation(progress));
@@ -144,8 +142,7 @@
     public void offsetTarget(float scale, float offsetX, float offsetY) {
         synchronized (mTargetOffset) {
             mTargetScale = scale;
-            mTargetOffset.set(mInitialTargetOffset);
-            mTargetOffset.offset(offsetX, offsetY);
+            mTargetOffset.set(offsetX, offsetY);
         }
     }
 
@@ -199,11 +196,10 @@
 
     public void drawForProgress(TaskThumbnailView ttv, Canvas canvas, float progress) {
         RectF currentRect =  mRectFEvaluator.evaluate(progress, mSourceRect, mTargetRect);
-
-        synchronized (mTargetOffset) {
-            canvas.translate(-mTargetOffset.x, -mTargetOffset.y);
-        }
+        canvas.translate(mSourceStackBounds.left - mHomeStackBounds.left,
+                mSourceStackBounds.top - mHomeStackBounds.top);
         mTmpMatrix.setRectToRect(mTargetRect, currentRect, ScaleToFit.FILL);
+
         canvas.concat(mTmpMatrix);
         canvas.translate(mTargetRect.left, mTargetRect.top);
 
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 7ee16c1..dfe1984 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -31,7 +31,8 @@
     AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targets);
 
     default ActivityOptions toActivityOptions(Handler handler, long duration) {
-        LauncherAnimationRunner runner = new LauncherAnimationRunner(handler) {
+        LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
+                false /* startAtFrontOfQueue */) {
 
             @Override
             public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index a7b018a..9ab1e04 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -30,8 +30,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -85,8 +83,7 @@
  * A list of recent tasks.
  */
 @TargetApi(Build.VERSION_CODES.P)
-public abstract class RecentsView<T extends BaseActivity>
-        extends PagedView implements OnSharedPreferenceChangeListener, Insettable {
+public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable {
 
     public static final boolean DEBUG_SHOW_CLEAR_ALL_BUTTON = false;
 
@@ -104,7 +101,7 @@
             return recentsView.mAdjacentScale;
         }
     };
-    private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
+    private static final boolean FLIP_RECENTS = true;
     private static final int DISMISS_TASK_DURATION = 300;
 
     protected final T mActivity;
@@ -201,7 +198,11 @@
         mQuickScrubController = new QuickScrubController(mActivity, this);
         mModel = RecentsModel.getInstance(context);
 
-        onSharedPreferenceChanged(Utilities.getPrefs(context), PREF_FLIP_RECENTS);
+        mIsRtl = Utilities.isRtl(getResources());
+        if (FLIP_RECENTS) {
+            mIsRtl = !mIsRtl;
+        }
+        setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
 
         mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
         mEmptyIcon.setCallback(this);
@@ -213,17 +214,7 @@
         mEmptyMessagePadding = getResources()
                 .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
         setWillNotDraw(false);
-    }
-
-    @Override
-    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
-        if (s.equals(PREF_FLIP_RECENTS)) {
-            mIsRtl = Utilities.isRtl(getResources());
-            if (sharedPreferences.getBoolean(PREF_FLIP_RECENTS, false)) {
-                mIsRtl = !mIsRtl;
-            }
-            setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
-        }
+        updateEmptyMessage();
     }
 
     public boolean isRtl() {
@@ -248,7 +239,6 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         updateTaskStackListenerState();
-        Utilities.getPrefs(getContext()).registerOnSharedPreferenceChangeListener(this);
         mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
     }
 
@@ -256,7 +246,6 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         updateTaskStackListenerState();
-        Utilities.getPrefs(getContext()).unregisterOnSharedPreferenceChangeListener(this);
         mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
     }
 
@@ -310,6 +299,56 @@
         }
     }
 
+    private float calculateClearAllButtonAlpha() {
+        if (mClearAllButton.getVisibility() != View.VISIBLE || getChildCount() == 0) return 0;
+
+        // Current visible coordinate of the right border of the rightmost task.
+        final int carouselCurrentRight = getChildAt(getChildCount() - 1).getRight() - getScrollX();
+
+        // As the right border (let's call it E aka carouselCurrentRight) of the carousel moves
+        // over Clear all button, the button changes trasparency.
+        // leftOfAlphaChange < rightOfAlphaChange; these are the points of the 100% and 0% alpha
+        // correspondingly. Alpha changes linearly between 100% and 0% as E moves through this
+        // range. It doesn't change outside of the range.
+
+        // Once E hits the left border of the Clear-All button, the whole button is uncovered,
+        // and it should have alpha 100%.
+        final float leftOfAlphaChange = mClearAllButton.getX();
+
+        // The rightmost possible right coordinate of the carousel.
+        final int carouselMotionLimit = getScrollForPage(getChildCount() - 1) + getWidth()
+                - getPaddingRight() - mInsets.right;
+
+        // The carousel might not be able to ever cover a part of the Clear-all button. Then
+        // always show the button as 100%. Technically, this check also prevents dividing by zero
+        // or a negative number when calculating the transparency ratio below.
+        if (carouselMotionLimit <= leftOfAlphaChange) return 1;
+
+        // If the carousel is able to cover the button completely, we make the button completely
+        // transparent when E hits the right border of the button.
+        // Or, the carousel may not be able to move that far to the right so it completely covers
+        // the button. Then we set the rightmost possible position of the carousel as the point
+        // where the button reaches 0 alpha.
+        final float rightOfAlphaChange = Math.min(
+                mClearAllButton.getX() + mClearAllButton.getWidth(), carouselMotionLimit);
+
+        return Utilities.boundToRange(
+                (rightOfAlphaChange - carouselCurrentRight) /
+                        (rightOfAlphaChange - leftOfAlphaChange), 0, 1);
+    }
+
+    private void updateClearAllButtonAlpha() {
+        if (mClearAllButton != null) {
+            mClearAllButton.setAlpha(calculateClearAllButtonAlpha() * mContentAlpha);
+        }
+    }
+
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        super.onScrollChanged(l, t, oldl, oldt);
+        updateClearAllButtonAlpha();
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         if (DEBUG_SHOW_CLEAR_ALL_BUTTON && ev.getAction() == MotionEvent.ACTION_DOWN
@@ -674,8 +713,23 @@
         mIgnoreResetTaskViews.remove(taskView);
     }
 
+    private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) {
+        addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
+        addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
+                duration, LINEAR, anim);
+    }
+
+    private void removeTask(Task task, PendingAnimation.OnEndListener onEndListener) {
+        if (task != null) {
+            ActivityManagerWrapper.getInstance().removeTask(task.key.id);
+            mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
+                    onEndListener.logAction, Direction.UP,
+                    TaskUtils.getComponentKeyForTask(task.key));
+        }
+    }
+
     public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
-            boolean removeTask, long duration) {
+            boolean shouldRemoveTask, long duration) {
         if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
             throw new IllegalStateException("Another pending animation is still running");
         }
@@ -707,9 +761,7 @@
             View child = getChildAt(i);
             if (child == taskView) {
                 if (animateTaskView) {
-                    addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
-                    addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
-                            duration, LINEAR, anim);
+                    addDismissedTaskAnimations(taskView, anim, duration);
                 }
             } else {
                 // If we just take newScroll - oldScroll, everything to the right of dragged task
@@ -754,14 +806,8 @@
         mPendingAnimation = pendingAnimation;
         mPendingAnimation.addEndListener((onEndListener) -> {
            if (onEndListener.isSuccess) {
-               if (removeTask) {
-                   Task task = taskView.getTask();
-                   if (task != null) {
-                       ActivityManagerWrapper.getInstance().removeTask(task.key.id);
-                       mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
-                               onEndListener.logAction, Direction.UP,
-                               TaskUtils.getComponentKeyForTask(task.key));
-                   }
+               if (shouldRemoveTask) {
+                   removeTask(taskView.getTask(), onEndListener);
                }
                int pageToSnapTo = mCurrentPage;
                if (draggedIndex < pageToSnapTo) {
@@ -780,6 +826,33 @@
         return pendingAnimation;
     }
 
+    public PendingAnimation createAllTasksDismissAnimation(long duration) {
+        if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
+            throw new IllegalStateException("Another pending animation is still running");
+        }
+        AnimatorSet anim = new AnimatorSet();
+        PendingAnimation pendingAnimation = new PendingAnimation(anim);
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            addDismissedTaskAnimations(getChildAt(i), anim, duration);
+        }
+
+        mPendingAnimation = pendingAnimation;
+        mPendingAnimation.addEndListener((onEndListener) -> {
+            if (onEndListener.isSuccess) {
+                while (getChildCount() != 0) {
+                    TaskView taskView = getPageAt(getChildCount() - 1);
+                    removeTask(taskView.getTask(), onEndListener);
+                    removeView(taskView);
+                }
+                onAllTasksRemoved();
+            }
+            mPendingAnimation = null;
+        });
+        return pendingAnimation;
+    }
+
     private static void addAnim(ObjectAnimator anim, long duration,
             TimeInterpolator interpolator, AnimatorSet set) {
         anim.setDuration(duration).setInterpolator(interpolator);
@@ -803,9 +876,7 @@
         }
     }
 
-    public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
-        PendingAnimation pendingAnim = createTaskDismissAnimation(taskView, animateTaskView,
-                removeTask, DISMISS_TASK_DURATION);
+    private void runDismissAnimation(PendingAnimation pendingAnim) {
         AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(
                 pendingAnim.anim, DISMISS_TASK_DURATION);
         controller.dispatchOnStart();
@@ -814,6 +885,15 @@
         controller.start();
     }
 
+    public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
+        runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask,
+                DISMISS_TASK_DURATION));
+    }
+
+    public void dismissAllTasks() {
+        runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
+    }
+
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -864,6 +944,7 @@
         int alphaInt = Math.round(alpha * 255);
         mEmptyMessagePaint.setAlpha(alphaInt);
         mEmptyIcon.setAlpha(alphaInt);
+        updateClearAllButtonAlpha();
     }
 
     public void setAdjacentScale(float adjacentScale) {
@@ -948,6 +1029,7 @@
             mEmptyTextLayout = null;
             mLastMeasureSize.set(getWidth(), getHeight());
         }
+        updateClearAllButtonVisibility();
 
         if (!mShowEmptyMessage) return;
 
@@ -1118,16 +1200,6 @@
         return "";
     }
 
-    public void dismissAllTasks() {
-        for (int i = 0; i < getChildCount(); ++i) {
-            Task task = getPageAt(i).getTask();
-            if (task != null) {
-                ActivityManagerWrapper.getInstance().removeTask(task.key.id);
-            }
-        }
-        onAllTasksRemoved();
-    }
-
     @Override
     protected int computeMaxScrollX() {
         if (!DEBUG_SHOW_CLEAR_ALL_BUTTON || getChildCount() == 0) {
@@ -1140,8 +1212,10 @@
     }
 
     private void updateClearAllButtonVisibility() {
+        if (mClearAllButton == null) return;
         mClearAllButton.setVisibility(
                 !DEBUG_SHOW_CLEAR_ALL_BUTTON || mShowEmptyMessage ? GONE : VISIBLE);
+        updateClearAllButtonAlpha();
     }
 
     public void setClearAllButton(View clearAllButton) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 9ed9fce..1285a2b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -737,6 +737,8 @@
         NotificationListener.removeNotificationsChangedListener();
         getStateManager().moveToRestState();
 
+        // Workaround for b/78520668, explicitly trim memory once UI is hidden
+        UiFactory.onTrimMemory(this, TRIM_MEMORY_UI_HIDDEN);
     }
 
     @Override
@@ -1278,7 +1280,7 @@
             }
 
             if (mLauncherCallbacks != null) {
-                mLauncherCallbacks.onHomeIntent();
+                mLauncherCallbacks.onHomeIntent(internalStateHandled);
             }
         }
 
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 35faaea..6aef658 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -50,7 +50,7 @@
     void onAttachedToWindow();
     void onDetachedFromWindow();
     void dump(String prefix, FileDescriptor fd, PrintWriter w, String[] args);
-    void onHomeIntent();
+    void onHomeIntent(boolean internalStateHandled);
     boolean handleBackPressed();
     void onTrimMemory(int level);
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 7ecafdb..d2a126f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -477,7 +477,7 @@
         super.onViewAdded(child);
     }
 
-    boolean isTouchActive() {
+    public boolean isTouchActive() {
         return mTouchState != TOUCH_STATE_REST;
     }
 
@@ -974,19 +974,9 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
-        case MotionEvent.ACTION_DOWN:
+        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
             mXDown = ev.getX();
             mYDown = ev.getY();
-            break;
-        case MotionEvent.ACTION_POINTER_UP:
-        case MotionEvent.ACTION_UP:
-            if (mTouchState == TOUCH_STATE_REST) {
-                final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
-                if (currentPage != null) {
-                    onWallpaperTap(ev);
-                }
-            }
         }
         return super.onInterceptTouchEvent(ev);
     }
@@ -1443,7 +1433,7 @@
         }
     }
 
-    protected void onWallpaperTap(MotionEvent ev) {
+    public void onWallpaperTap(MotionEvent ev) {
         final int[] position = mTempXY;
         getLocationOnScreen(position);
 
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 23f55aa..f59f14e 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -17,6 +17,7 @@
 
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.ViewConfiguration.getLongPressTimeout;
 
@@ -30,6 +31,7 @@
 import android.view.View.OnTouchListener;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Workspace;
@@ -71,8 +73,7 @@
         int action = ev.getActionMasked();
         if (action == ACTION_DOWN) {
             // Check if we can handle long press.
-            boolean handleLongPress = AbstractFloatingView.getTopOpenView(mLauncher) == null
-                    && mLauncher.isInState(NORMAL);
+            boolean handleLongPress = canHandleLongPress();
 
             if (handleLongPress) {
                 // Check if the event is not near the edges
@@ -122,12 +123,28 @@
             // We don't want to handle touch, let workspace handle it as usual.
             result = false;
         }
+
+        if (action == ACTION_UP || action == ACTION_POINTER_UP) {
+            if (!mWorkspace.isTouchActive()) {
+                final CellLayout currentPage =
+                        (CellLayout) mWorkspace.getChildAt(mWorkspace.getCurrentPage());
+                if (currentPage != null) {
+                    mWorkspace.onWallpaperTap(ev);
+                }
+            }
+        }
+
         if (action == ACTION_UP || action == ACTION_CANCEL) {
             cancelLongPress();
         }
         return result;
     }
 
+    private boolean canHandleLongPress() {
+        return AbstractFloatingView.getTopOpenView(mLauncher) == null
+                && mLauncher.isInState(NORMAL);
+    }
+
     private void cancelLongPress() {
         mWorkspace.removeCallbacks(this);
         mLongPressState = STATE_CANCELLED;
@@ -136,15 +153,19 @@
     @Override
     public void run() {
         if (mLongPressState == STATE_REQUESTED) {
-            mLongPressState = STATE_PENDING_PARENT_INFORM;
-            mWorkspace.getParent().requestDisallowInterceptTouchEvent(true);
+            if (canHandleLongPress()) {
+                mLongPressState = STATE_PENDING_PARENT_INFORM;
+                mWorkspace.getParent().requestDisallowInterceptTouchEvent(true);
 
-            mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
-                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-            mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                    Action.Direction.NONE, ContainerType.WORKSPACE,
-                    mWorkspace.getCurrentPage());
-            OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
+                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+                mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+                        Action.Direction.NONE, ContainerType.WORKSPACE,
+                        mWorkspace.getCurrentPage());
+                OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
+            } else {
+                cancelLongPress();
+            }
         }
     }
 }
diff --git a/src/com/android/launcher3/views/SpringRelativeLayout.java b/src/com/android/launcher3/views/SpringRelativeLayout.java
index 090b3e6..a508191 100644
--- a/src/com/android/launcher3/views/SpringRelativeLayout.java
+++ b/src/com/android/launcher3/views/SpringRelativeLayout.java
@@ -24,7 +24,6 @@
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.RecyclerView.EdgeEffectFactory;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.SparseBooleanArray;
 import android.view.View;
 import android.widget.EdgeEffect;
@@ -58,6 +57,7 @@
     private final SpringAnimation mSpring;
 
     private float mDampedScrollShift = 0;
+    private SpringEdgeEffect mActiveEdge;
 
     public SpringRelativeLayout(Context context) {
         this(context, null);
@@ -90,6 +90,13 @@
         return super.drawChild(canvas, child, drawingTime);
     }
 
+    private void setActiveEdge(SpringEdgeEffect edge) {
+        if (mActiveEdge != edge && mActiveEdge != null) {
+            mActiveEdge.mDistance = 0;
+        }
+        mActiveEdge = edge;
+    }
+
     private void setDampedScrollShift(float shift) {
         if (shift != mDampedScrollShift) {
             mDampedScrollShift = shift;
@@ -144,6 +151,7 @@
 
         @Override
         public void onPull(float deltaDistance, float displacement) {
+            setActiveEdge(this);
             mDistance += deltaDistance * (mVelocityMultiplier / 3f);
             setDampedScrollShift(mDistance * getHeight());
         }
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index a54268a..af8b15c 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -18,8 +18,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.launcher3.tests">
 
-    <uses-sdk android:targetSdkVersion="25" android:minSdkVersion="21"/>
-
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />