Merge "Adjust notification dots" into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 3945746..2a32857 100644
--- a/Android.mk
+++ b/Android.mk
@@ -193,7 +193,8 @@
     $(LOCAL_PATH)/quickstep/res \
     $(LOCAL_PATH)/go/res
 
-LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+LOCAL_PROGUARD_ENABLED := full
 
 LOCAL_SDK_VERSION := system_current
 LOCAL_MIN_SDK_VERSION := 26
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 27de1e9..9d91d7e 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 14633af..37d0b12 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -537,6 +537,9 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 // Reset launcher to normal state
+                if (isBubbleTextView) {
+                    ((BubbleTextView) v).setStayPressed(false);
+                }
                 v.setVisibility(View.VISIBLE);
                 ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
             }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
index 722f51b..693ae60 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
@@ -35,7 +35,7 @@
 
     public BackButtonAlphaHandler(Launcher launcher) {
         mLauncher = launcher;
-        mOverviewInteractionState = OverviewInteractionState.getInstance(mLauncher);
+        mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(mLauncher);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
index 6d10619..fd4bf9b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
@@ -73,7 +73,7 @@
     protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
         super.onSwipeInteractionCompleted(targetState, logAction);
         if (mStartState == NORMAL && targetState == OVERVIEW) {
-            RecentsModel.getInstance(mLauncher).onOverviewShown(true, TAG);
+            RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 7f956f8..25b5f57 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 
 /**
  * Definition for overview state
@@ -77,7 +78,7 @@
     public void onStateDisabled(Launcher launcher) {
         RecentsView rv = launcher.getOverviewPanel();
         rv.setOverviewStateEnabled(false);
-        RecentsModel.getInstance(launcher).resetAssistCache();
+        RecentsModel.INSTANCE.get(launcher).resetAssistCache();
     }
 
     @Override
@@ -130,4 +131,14 @@
         DeviceProfile dp = launcher.getDeviceProfile();
         return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
     }
+
+    @Override
+    public void onBackPressed(Launcher launcher) {
+        TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
+        if (taskView != null) {
+            taskView.launchTask(true);
+        } else {
+            super.onBackPressed(launcher);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 1d1b7da..a1ae99e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -256,7 +256,7 @@
     protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
         super.onSwipeInteractionCompleted(targetState, logAction);
         if (mStartState == NORMAL && targetState == OVERVIEW) {
-            RecentsModel.getInstance(mLauncher).onOverviewShown(true, TAG);
+            RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 9a920c8..88f2315 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -121,7 +121,7 @@
                     if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
                             .isEventOverView(view, ev)) {
                         mTaskBeingDragged = view;
-                        if (!OverviewInteractionState.getInstance(mActivity)
+                        if (!OverviewInteractionState.INSTANCE.get(mActivity)
                                 .isSwipeUpGestureEnabled()) {
                             // Don't allow swipe down to open if we don't support swipe up
                             // to enter overview.
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 2d0946b..c939330 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -57,7 +57,7 @@
 public class UiFactory {
 
     public static TouchController[] createTouchControllers(Launcher launcher) {
-        boolean swipeUpEnabled = OverviewInteractionState.getInstance(launcher)
+        boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
                 .isSwipeUpGestureEnabled();
         if (!swipeUpEnabled) {
             return new TouchController[] {
@@ -80,7 +80,7 @@
     }
 
     public static void setOnTouchControllersChangedListener(Context context, Runnable listener) {
-        OverviewInteractionState.getInstance(context).setOnSwipeUpSettingChangedListener(listener);
+        OverviewInteractionState.INSTANCE.get(context).setOnSwipeUpSettingChangedListener(listener);
     }
 
     public static StateHandler[] getStateHandler(Launcher launcher) {
@@ -100,7 +100,7 @@
             shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(launcher,
                     TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
         }
-        OverviewInteractionState.getInstance(launcher)
+        OverviewInteractionState.INSTANCE.get(launcher)
                 .setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
     }
 
@@ -122,7 +122,7 @@
 
                 @Override
                 public void onStateTransitionComplete(LauncherState finalState) {
-                    boolean swipeUpEnabled = OverviewInteractionState.getInstance(launcher)
+                    boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
                             .isSwipeUpGestureEnabled();
                     LauncherState prevState = launcher.getStateManager().getLastState();
 
@@ -159,7 +159,7 @@
     }
 
     public static void onStart(Context context) {
-        RecentsModel model = RecentsModel.getInstance(context);
+        RecentsModel model = RecentsModel.INSTANCE.get(context);
         if (model != null) {
             model.onStart();
         }
@@ -169,17 +169,19 @@
         // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
         // as a part of quickstep/scrub, so that high-res thumbnails can load the next time we
         // enter overview
-        RecentsModel.getInstance(context).getRecentsTaskLoader()
+        RecentsModel.INSTANCE.get(context).getRecentsTaskLoader()
                 .getHighResThumbnailLoader().setVisible(true);
     }
 
     public static void onLauncherStateOrResumeChanged(Launcher launcher) {
         LauncherState state = launcher.getStateManager().getState();
-        DeviceProfile profile = launcher.getDeviceProfile();
-        WindowManagerWrapper.getInstance().setShelfHeight(
-                (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
-                        && !profile.isVerticalBarLayout(),
-                profile.hotseatBarSizePx);
+        if (!OverviewInteractionState.INSTANCE.get(launcher).swipeGestureInitializing()) {
+            DeviceProfile profile = launcher.getDeviceProfile();
+            WindowManagerWrapper.getInstance().setShelfHeight(
+                    (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
+                            && !profile.isVerticalBarLayout(),
+                    profile.hotseatBarSizePx);
+        }
 
         if (state == NORMAL) {
             launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
@@ -187,7 +189,7 @@
     }
 
     public static void onTrimMemory(Context context, int level) {
-        RecentsModel model = RecentsModel.getInstance(context);
+        RecentsModel model = RecentsModel.INSTANCE.get(context);
         if (model != null) {
             model.onTrimMemory(level);
         }
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 3c69973..bb5bf6e 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.uioverrides.FastOverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -230,6 +231,8 @@
 
                 // Optimization, hide the all apps view to prevent layout while initializing
                 activity.getAppsView().getContentView().setVisibility(View.GONE);
+
+                AccessibilityManagerCompat.sendEventToTest(activity, "TAPL_WENT_TO_STATE");
             }
 
             return new AnimationFactory() {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 7c6eb32..3d54b82 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -111,7 +111,7 @@
         mContext = context;
         mAM = ActivityManagerWrapper.getInstance();
         mMainThreadExecutor = new MainThreadExecutor();
-        mRecentsModel = RecentsModel.getInstance(mContext);
+        mRecentsModel = RecentsModel.INSTANCE.get(mContext);
 
         Intent myHomeIntent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_HOME)
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 922a7ff..a5ff681 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -22,24 +22,20 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.support.annotation.WorkerThread;
 import android.util.Log;
 
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
-import java.util.concurrent.ExecutionException;
-
 /**
  * Sets overview interaction flags, such as:
  *
@@ -54,29 +50,10 @@
     private static final String TAG = "OverviewFlags";
 
     private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
-    private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
-            "config_swipe_up_gesture_setting_available";
-    private static final String SWIPE_UP_ENABLED_DEFAULT_RES_NAME =
-            "config_swipe_up_gesture_default";
 
     // We do not need any synchronization for this variable as its only written on UI thread.
-    private static OverviewInteractionState INSTANCE;
-
-    public static OverviewInteractionState getInstance(final Context context) {
-        if (INSTANCE == null) {
-            if (Looper.myLooper() == Looper.getMainLooper()) {
-                INSTANCE = new OverviewInteractionState(context.getApplicationContext());
-            } else {
-                try {
-                    return new MainThreadExecutor().submit(
-                            () -> OverviewInteractionState.getInstance(context)).get();
-                } catch (InterruptedException|ExecutionException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-        return INSTANCE;
-    }
+    public static final MainThreadInitializedObject<OverviewInteractionState> INSTANCE =
+            new MainThreadInitializedObject<>((c) -> new OverviewInteractionState(c));
 
     private static final int MSG_SET_PROXY = 200;
     private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
@@ -88,6 +65,8 @@
     private final Handler mUiHandler;
     private final Handler mBgHandler;
 
+    private boolean mSwipeGestureInitializing = false;
+
     // These are updated on the background thread
     private ISystemUiProxy mISystemUiProxy;
     private boolean mSwipeUpEnabled = true;
@@ -104,13 +83,13 @@
         mUiHandler = new Handler(this::handleUiMessage);
         mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
 
-        if (getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME)) {
+        if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
             mSwipeUpSettingObserver = new SwipeUpGestureEnabledSettingObserver(mUiHandler,
                     context.getContentResolver());
             mSwipeUpSettingObserver.register();
         } else {
             mSwipeUpSettingObserver = null;
-            mSwipeUpEnabled = getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
+            mSwipeUpEnabled = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
         }
     }
 
@@ -197,6 +176,15 @@
         }
     }
 
+    @WorkerThread
+    public void setSwipeGestureInitializing(boolean swipeGestureInitializing) {
+        mSwipeGestureInitializing = swipeGestureInitializing;
+    }
+
+    public boolean swipeGestureInitializing() {
+        return mSwipeGestureInitializing;
+    }
+
     private class SwipeUpGestureEnabledSettingObserver extends ContentObserver {
         private Handler mHandler;
         private ContentResolver mResolver;
@@ -206,7 +194,7 @@
             super(handler);
             mHandler = handler;
             mResolver = resolver;
-            defaultValue = getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME) ? 1 : 0;
+            defaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue() ? 1 : 0;
         }
 
         public void register() {
@@ -228,18 +216,6 @@
         }
     }
 
-    private boolean getSystemBooleanRes(String resName) {
-        Resources res = Resources.getSystem();
-        int resId = res.getIdentifier(resName, "bool", "android");
-
-        if (resId != 0) {
-            return res.getBoolean(resId);
-        } else {
-            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
-            return false;
-        }
-    }
-
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
         if (mSwipeUpEnabled && !Utilities.getPrefs(mContext).getBoolean(
                 HAS_ENABLED_QUICKSTEP_ONCE, true)) {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 32079bf..b93a54b 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -232,6 +232,12 @@
     }
 
     @Override
+    public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+        UiFactory.onEnterAnimationComplete(this);
+    }
+
+    @Override
     public void onTrimMemory(int level) {
         super.onTrimMemory(level);
         UiFactory.onTrimMemory(this, level);
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 0b97f01..fa4e016 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -27,7 +27,6 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.support.annotation.WorkerThread;
@@ -38,6 +37,7 @@
 
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
+import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.IconLoader;
@@ -50,7 +50,6 @@
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.ArrayList;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
 /**
@@ -59,23 +58,8 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class RecentsModel extends TaskStackChangeListener {
     // We do not need any synchronization for this variable as its only written on UI thread.
-    private static RecentsModel INSTANCE;
-
-    public static RecentsModel getInstance(final Context context) {
-        if (INSTANCE == null) {
-            if (Looper.myLooper() == Looper.getMainLooper()) {
-                INSTANCE = new RecentsModel(context.getApplicationContext());
-            } else {
-                try {
-                    return new MainThreadExecutor().submit(
-                            () -> RecentsModel.getInstance(context)).get();
-                } catch (InterruptedException|ExecutionException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-        return INSTANCE;
-    }
+    public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
+            new MainThreadInitializedObject<>(c -> new RecentsModel(c));
 
     private final SparseArray<Bundle> mCachedAssistData = new SparseArray<>(1);
     private final ArrayList<AssistDataListener> mAssistDataListeners = new ArrayList<>();
diff --git a/quickstep/src/com/android/quickstep/SwipeUpSetting.java b/quickstep/src/com/android/quickstep/SwipeUpSetting.java
new file mode 100644
index 0000000..0f91f97
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SwipeUpSetting.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import android.content.res.Resources;
+import android.util.Log;
+
+public final class SwipeUpSetting {
+    private static final String TAG = "SwipeUpSetting";
+
+    private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
+            "config_swipe_up_gesture_setting_available";
+
+    private static final String SWIPE_UP_ENABLED_DEFAULT_RES_NAME =
+            "config_swipe_up_gesture_default";
+
+    private static boolean getSystemBooleanRes(String resName) {
+        Resources res = Resources.getSystem();
+        int resId = res.getIdentifier(resName, "bool", "android");
+
+        if (resId != 0) {
+            return res.getBoolean(resId);
+        } else {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return false;
+        }
+    }
+
+    public static boolean isSwipeUpSettingAvailable() {
+        return getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME);
+    }
+
+    public static boolean isSwipeUpEnabledDefaultValue() {
+        return getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index 24e199b..e64d04a 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -160,7 +160,7 @@
                 boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
                 if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
                         ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft))) {
-                    ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
+                    ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
                     try {
                         sysUiProxy.onSplitScreenInvoked();
                     } catch (RemoteException e) {
@@ -225,7 +225,7 @@
         @Override
         public View.OnClickListener getOnClickListener(
                 BaseDraggingActivity activity, TaskView taskView) {
-            ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
+            ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
             if (sysUiProxy == null) {
                 return null;
             }
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index f9b5e30..5cae2b9 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -18,8 +18,6 @@
 
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
-import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.ValueAnimator;
@@ -30,7 +28,6 @@
 import android.graphics.RectF;
 import android.os.UserHandle;
 import android.util.Log;
-import android.view.Surface;
 import android.view.View;
 
 import com.android.launcher3.BaseDraggingActivity;
@@ -92,10 +89,11 @@
      */
     public static TaskView findTaskViewToLaunch(
             BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
-        if (v instanceof TaskView) {
-            return (TaskView) v;
-        }
         RecentsView recentsView = activity.getOverviewPanel();
+        if (v instanceof TaskView) {
+            TaskView taskView = (TaskView) v;
+            return recentsView.isTaskViewVisible(taskView) ? taskView : null;
+        }
 
         // It's possible that the launched view can still be resolved to a visible task view, check
         // the task id of the opening task and see if we can find a match.
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 5a1f523..bd79301 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -90,7 +90,14 @@
         public void onMotionEvent(MotionEvent ev) {
             mEventQueue.queue(ev);
 
-            String name = sMotionEventNames.get(ev.getActionMasked());
+            int action = ev.getActionMasked();
+            if (action == ACTION_DOWN) {
+                mOverviewInteractionState.setSwipeGestureInitializing(true);
+            } else if (action == ACTION_UP || action == ACTION_CANCEL) {
+                mOverviewInteractionState.setSwipeGestureInitializing(false);
+            }
+
+            String name = sMotionEventNames.get(action);
             if (name != null){
                 TraceHelper.partitionSection("SysUiBinder", name);
             }
@@ -106,6 +113,7 @@
         @Override
         public void onQuickScrubStart() {
             mEventQueue.onQuickScrubStart();
+            mOverviewInteractionState.setSwipeGestureInitializing(false);
             TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart");
         }
 
@@ -146,8 +154,8 @@
         @Override
         public void onQuickStep(MotionEvent motionEvent) {
             mEventQueue.onQuickStep(motionEvent);
+            mOverviewInteractionState.setSwipeGestureInitializing(false);
             TraceHelper.endSection("SysUiBinder", "onQuickStep");
-
         }
 
         @Override
@@ -179,13 +187,13 @@
     public void onCreate() {
         super.onCreate();
         mAM = ActivityManagerWrapper.getInstance();
-        mRecentsModel = RecentsModel.getInstance(this);
+        mRecentsModel = RecentsModel.INSTANCE.get(this);
         mRecentsModel.setPreloadTasksInBackground(true);
         mMainThreadExecutor = new MainThreadExecutor();
         mOverviewCommandHelper = new OverviewCommandHelper(this);
         mMainThreadChoreographer = Choreographer.getInstance();
         mEventQueue = new MotionEventQueue(mMainThreadChoreographer, TouchConsumer.NO_OP);
-        mOverviewInteractionState = OverviewInteractionState.getInstance(this);
+        mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
         mOverviewCallbacks = OverviewCallbacks.get(this);
         mTaskOverlayFactory = TaskOverlayFactory.get(this);
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 8e34490..f8ef49b 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -256,6 +256,11 @@
             }
         };
 
+        // Re-setup the recents UI when gesture starts, as the state could have been changed during
+        // that time by a previous window transition.
+        mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_GESTURE_STARTED_QUICKSTEP,
+                this::setupRecentsViewUi);
+
         mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED_QUICKSCRUB,
                 this::initializeLauncherAnimationController);
         mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED_QUICKSTEP,
@@ -429,11 +434,15 @@
             });
         }
 
+        setupRecentsViewUi();
+        mLayoutListener.open();
+        mStateCallback.setState(STATE_LAUNCHER_STARTED);
+    }
+
+    private void setupRecentsViewUi() {
         mRecentsView.showTask(mRunningTaskId);
         mRecentsView.setRunningTaskHidden(true);
         mRecentsView.setRunningTaskIconScaledDown(true);
-        mLayoutListener.open();
-        mStateCallback.setState(STATE_LAUNCHER_STARTED);
     }
 
     public void setLauncherOnDrawCallback(Runnable callback) {
@@ -481,7 +490,7 @@
         // This method is only called when STATE_GESTURE_STARTED_QUICKSTEP/
         // STATE_GESTURE_STARTED_QUICKSCRUB is set, so we can enable the high-res thumbnail loader
         // here once we are sure that we will end up in an overview state
-        RecentsModel.getInstance(mContext).getRecentsTaskLoader()
+        RecentsModel.INSTANCE.get(mContext).getRecentsTaskLoader()
                 .getHighResThumbnailLoader().setVisible(true);
     }
 
@@ -632,10 +641,8 @@
                 overviewStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
             }
             // If we are not in multi-window mode, home insets should be same as system insets.
-            Rect insets = new Rect();
-            WindowManagerWrapper.getInstance().getStableInsets(insets);
             dp = dp.copy(mContext);
-            dp.updateInsets(insets);
+            dp.updateInsets(homeContentInsets);
         }
         dp.updateIsSeascape(mContext.getSystemService(WindowManager.class));
 
@@ -940,7 +947,7 @@
         mRecentsView.animateUpRunningTaskIconScale();
         mRecentsView.setSwipeDownShouldLaunchApp(true);
 
-        RecentsModel.getInstance(mContext).onOverviewShown(false, TAG);
+        RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
 
         doLogGesture(true /* toLauncher */);
         reset();
@@ -970,7 +977,7 @@
         mQuickScrubController.onFinishedTransitionToQuickScrub();
 
         mRecentsView.animateUpRunningTaskIconScale();
-        RecentsModel.getInstance(mContext).onOverviewShown(false, TAG);
+        RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
     }
 
     public void onQuickScrubProgress(float progress) {
@@ -1104,7 +1111,7 @@
     }
 
     private void preloadAssistData() {
-        RecentsModel.getInstance(mContext).preloadAssistData(mRunningTaskId, mAssistData);
+        RecentsModel.INSTANCE.get(mContext).preloadAssistData(mRunningTaskId, mAssistData);
     }
 
     public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index df70a8a..0597a2e 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -30,7 +30,6 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.support.annotation.Nullable;
-import android.view.Surface;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.BaseDraggingActivity;
@@ -50,7 +49,6 @@
 import com.android.systemui.shared.system.TransactionCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
-import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 
 /**
@@ -260,7 +258,7 @@
     }
 
     private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
-        ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
+        ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
         if (sysUiProxy != null) {
             try {
                 mSourceStackBounds.set(sysUiProxy.getNonMinimizedSplitScreenSecondaryBounds());
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 7c5828b..697bb4f 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -124,7 +124,7 @@
             ClipAnimationHelper helper) {
         AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv, helper);
 
-        if (!OverviewInteractionState.getInstance(mActivity).isSwipeUpGestureEnabled()) {
+        if (!OverviewInteractionState.INSTANCE.get(mActivity).isSwipeUpGestureEnabled()) {
             // Hotseat doesn't move when opening recents with the button,
             // so don't animate it here either.
             return anim;
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2680a26..814d02d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -277,7 +277,7 @@
                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
         mActivity = (T) BaseActivity.fromContext(context);
         mQuickScrubController = new QuickScrubController(mActivity, this);
-        mModel = RecentsModel.getInstance(context);
+        mModel = RecentsModel.INSTANCE.get(context);
 
         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
                 .inflate(R.layout.overview_clear_all_button, this, false);
@@ -415,7 +415,7 @@
             case MotionEvent.ACTION_DOWN:
                 // Touch down anywhere but the deadzone around the visible clear all button and
                 // between the task views will start home on touch up
-                if (mTouchState == TOUCH_STATE_REST) {
+                if (!isHandlingTouch()) {
                     updateDeadZoneRects();
                     final boolean clearAllButtonDeadZoneConsumed = mClearAllButton.getAlpha() == 1
                             && mClearAllButtonDeadZoneRect.contains(x, y);
@@ -560,7 +560,7 @@
         boolean scrolling = super.computeScrollHelper();
         boolean isFlingingFast = false;
         updateCurveProperties();
-        if (scrolling || (mTouchState == TOUCH_STATE_SCROLLING)) {
+        if (scrolling || isHandlingTouch()) {
             if (scrolling) {
                 // Check if we are flinging quickly to disable high res thumbnail loading
                 isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
@@ -714,12 +714,16 @@
         setCurrentTask(runningTaskId);
     }
 
+    public TaskView getRunningTaskView() {
+        return getTaskView(mRunningTaskId);
+    }
+
     /**
      * Hides the tile associated with {@link #mRunningTaskId}
      */
     public void setRunningTaskHidden(boolean isHidden) {
         mRunningTaskTileHidden = isHidden;
-        TaskView runningTask = getTaskView(mRunningTaskId);
+        TaskView runningTask = getRunningTaskView();
         if (runningTask != null) {
             runningTask.setAlpha(isHidden ? 0 : mContentAlpha);
         }
@@ -745,7 +749,7 @@
     }
 
     public void showNextTask() {
-        TaskView runningTaskView = getTaskView(mRunningTaskId);
+        TaskView runningTaskView = getRunningTaskView();
         if (runningTaskView == null) {
             // Launch the first task
             if (getTaskViewCount() > 0) {
@@ -773,7 +777,7 @@
     }
 
     private void applyRunningTaskIconScale() {
-        TaskView firstTask = getTaskView(mRunningTaskId);
+        TaskView firstTask = getRunningTaskView();
         if (firstTask != null) {
             firstTask.setIconScaleAndDim(mRunningTaskIconScaledDown ? 0 : 1);
         }
@@ -781,7 +785,7 @@
 
     public void animateUpRunningTaskIconScale() {
         mRunningTaskIconScaledDown = false;
-        TaskView firstTask = getTaskView(mRunningTaskId);
+        TaskView firstTask = getRunningTaskView();
         if (firstTask != null) {
             firstTask.animateIconScaleAndDimIntoView();
         }
@@ -990,10 +994,8 @@
         mPendingAnimation = pendingAnimation;
         mPendingAnimation.addEndListener((onEndListener) -> {
             if (onEndListener.isSuccess) {
-                int taskViewCount = getTaskViewCount();
-                for (int i = 0; i < taskViewCount; i++) {
-                    removeTask(getTaskViewAt(i).getTask(), -1, onEndListener, false);
-                }
+                // Remove all the task views now
+                ActivityManagerWrapper.getInstance().removeAllRecentTasks();
                 removeAllViews();
                 onAllTasksRemoved();
             }
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index e609a40..41626c6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -87,6 +87,7 @@
     private static final int REVEAL_OPEN_DURATION = 150;
     private static final int REVEAL_CLOSE_DURATION = 100;
 
+    private final float mThumbnailTopMargin;
     private BaseDraggingActivity mActivity;
     private TextView mTaskName;
     private IconView mTaskIcon;
@@ -103,6 +104,7 @@
         super(context, attrs, defStyleAttr);
 
         mActivity = BaseDraggingActivity.fromContext(context);
+        mThumbnailTopMargin = getResources().getDimension(R.dimen.task_thumbnail_top_margin);
     }
 
     @Override
@@ -154,11 +156,16 @@
         return (type & TYPE_TASK_MENU) != 0;
     }
 
-    public static boolean showForTask(TaskView taskView) {
+    public void setPosition(float x, float y) {
+        setX(x);
+        setY(y + mThumbnailTopMargin);
+    }
+
+    public static TaskMenuView showForTask(TaskView taskView) {
         BaseDraggingActivity activity = BaseDraggingActivity.fromContext(taskView.getContext());
         final TaskMenuView taskMenuView = (TaskMenuView) activity.getLayoutInflater().inflate(
                         R.layout.task_menu, activity.getDragLayer(), false);
-        return taskMenuView.populateAndShowForTask(taskView);
+        return taskMenuView.populateAndShowForTask(taskView) ? taskMenuView : null;
     }
 
     private boolean populateAndShowForTask(TaskView taskView) {
@@ -188,7 +195,7 @@
         // Move the icon and text up half an icon size to lay over the TaskView
         LinearLayout.LayoutParams params =
                 (LinearLayout.LayoutParams) mTaskIcon.getLayoutParams();
-        params.topMargin = (int) -getResources().getDimension(R.dimen.task_thumbnail_top_margin);
+        params.topMargin = (int) -mThumbnailTopMargin;
         mTaskIcon.setLayoutParams(params);
 
         for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
@@ -213,12 +220,12 @@
         mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
         Rect insets = mActivity.getDragLayer().getInsets();
         BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
-        params.width = sTempRect.width();
-        params.gravity = Gravity.LEFT;
+        params.width = taskView.getMeasuredWidth();
+        params.gravity = Gravity.START;
         setLayoutParams(params);
-        setX(Math.round(sTempRect.left - insets.left));
-        setY(Math.round(sTempRect.top - insets.top
-                + getResources().getDimension(R.dimen.task_thumbnail_top_margin)));
+        setScaleX(taskView.getScaleX());
+        setScaleY(taskView.getScaleY());
+        setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top);
     }
 
     private void animateOpen() {
@@ -232,7 +239,7 @@
 
     private void animateOpenOrClosed(boolean closing) {
         if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
-            return;
+            mOpenCloseAnimator.end();
         }
         mOpenCloseAnimator = LauncherAnimUtils.createAnimatorSet();
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 1653038..ee542d5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -110,8 +110,24 @@
         }
     };
 
+    private final OnAttachStateChangeListener mTaskMenuStateListener =
+            new OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View view) {
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View view) {
+                    if (mMenuView != null) {
+                        mMenuView.removeOnAttachStateChangeListener(this);
+                        mMenuView = null;
+                    }
+                }
+            };
+
     private Task mTask;
     private TaskThumbnailView mSnapshotView;
+    private TaskMenuView mMenuView;
     private IconView mIconView;
     private float mCurveScale;
     private float mZoomScale;
@@ -200,13 +216,22 @@
     public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
         mSnapshotView.setThumbnail(task, thumbnailData);
         mIconView.setDrawable(task.icon);
-        mIconView.setOnClickListener(icon -> TaskMenuView.showForTask(this));
+        mIconView.setOnClickListener(icon -> showTaskMenu());
         mIconView.setOnLongClickListener(icon -> {
             requestDisallowInterceptTouchEvent(true);
-            return TaskMenuView.showForTask(this);
+            return showTaskMenu();
         });
     }
 
+    private boolean showTaskMenu() {
+        getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
+        mMenuView = TaskMenuView.showForTask(this);
+        if (mMenuView != null) {
+            mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener);
+        }
+        return mMenuView != null;
+    }
+
     @Override
     public void onTaskDataUnloaded() {
         mSnapshotView.setThumbnail(null, null);
@@ -266,6 +291,12 @@
 
         mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
         setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation));
+
+        if (mMenuView != null) {
+            mMenuView.setPosition(getX() - getRecentsView().getScrollX(), getY());
+            mMenuView.setScaleX(getScaleX());
+            mMenuView.setScaleY(getScaleY());
+        }
     }
 
     @Override
diff --git a/res/drawable-hdpi/ic_allapps.png b/res/drawable-hdpi/ic_allapps.png
deleted file mode 100644
index 253755f..0000000
--- a/res/drawable-hdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_allapps_pressed.png b/res/drawable-hdpi/ic_allapps_pressed.png
deleted file mode 100644
index 1e644c5..0000000
--- a/res/drawable-hdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps.png b/res/drawable-mdpi/ic_allapps.png
deleted file mode 100644
index 6936b20..0000000
--- a/res/drawable-mdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps_pressed.png b/res/drawable-mdpi/ic_allapps_pressed.png
deleted file mode 100644
index 850ded6..0000000
--- a/res/drawable-mdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps.png b/res/drawable-xhdpi/ic_allapps.png
deleted file mode 100644
index c11c103..0000000
--- a/res/drawable-xhdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps_pressed.png b/res/drawable-xhdpi/ic_allapps_pressed.png
deleted file mode 100644
index f319bf1..0000000
--- a/res/drawable-xhdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps.png b/res/drawable-xxhdpi/ic_allapps.png
deleted file mode 100644
index cf6a2cb..0000000
--- a/res/drawable-xxhdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps_pressed.png b/res/drawable-xxhdpi/ic_allapps_pressed.png
deleted file mode 100644
index 379389a..0000000
--- a/res/drawable-xxhdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/all_apps_button_icon.xml b/res/drawable/all_apps_button_icon.xml
deleted file mode 100644
index 7c69cad..0000000
--- a/res/drawable/all_apps_button_icon.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true" android:drawable="@drawable/ic_allapps_pressed" />
-    <item android:state_pressed="true" android:drawable="@drawable/ic_allapps_pressed" />
-    <item android:drawable="@drawable/ic_allapps" />
-</selector>
diff --git a/res/layout/all_apps_button.xml b/res/layout/all_apps_button.xml
deleted file mode 100644
index 4bc780a..0000000
--- a/res/layout/all_apps_button.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 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.
--->
-
-<TextView style="@style/BaseIcon" />
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index b211207..691219a 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -16,7 +16,6 @@
 
 <resources>
     <!-- All Apps -->
-    <dimen name="all_apps_button_scale_down">8dp</dimen>
     <dimen name="all_apps_empty_search_message_top_offset">64dp</dimen>
     <dimen name="all_apps_empty_search_bg_top_offset">180dp</dimen>
 
diff --git a/res/values/config.xml b/res/values/config.xml
index f462b9c..8d31bd2 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -148,6 +148,4 @@
     <item type="id" name="overview_panel"/>
     <integer name="config_recentsMaxThumbnailCacheSize">6</integer>
     <integer name="config_recentsMaxIconCacheSize">12</integer>
-
-    <item name="workspace_page_container" type="id" />
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3bb7a79..58fce34 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -76,7 +76,6 @@
     <dimen name="fastscroll_end_margin">-26dp</dimen>
 
     <!-- All Apps -->
-    <dimen name="all_apps_button_scale_down">0dp</dimen>
     <dimen name="all_apps_search_bar_field_height">48dp</dimen>
     <dimen name="all_apps_search_bar_bottom_padding">30dp</dimen>
     <dimen name="all_apps_empty_search_message_top_offset">40dp</dimen>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index a34f225..ef6e145 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -133,13 +133,13 @@
         launcher:name="Nexus 7"
         launcher:minWidthDps="575"
         launcher:minHeightDps="904"
-        launcher:numRows="5"
+        launcher:numRows="6"
         launcher:numColumns="6"
         launcher:numFolderRows="4"
         launcher:numFolderColumns="5"
         launcher:iconSize="64"
         launcher:iconTextSize="14.4"
-        launcher:numHotseatIcons="7"
+        launcher:numHotseatIcons="6"
         launcher:defaultLayoutId="@xml/default_workspace_5x6"
         />
 
@@ -147,8 +147,8 @@
         launcher:name="Nexus 10"
         launcher:minWidthDps="727"
         launcher:minHeightDps="1207"
-        launcher:numRows="5"
-        launcher:numColumns="6"
+        launcher:numRows="6"
+        launcher:numColumns="7"
         launcher:numFolderRows="4"
         launcher:numFolderColumns="5"
         launcher:iconSize="76"
diff --git a/res/xml/dw_phone_hotseat.xml b/res/xml/dw_phone_hotseat.xml
index b58994d..c691ebc 100644
--- a/res/xml/dw_phone_hotseat.xml
+++ b/res/xml/dw_phone_hotseat.xml
@@ -16,7 +16,7 @@
 
 <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
     <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
-    <!-- Dialer, Messaging, [All Apps], Browser, Camera -->
+    <!-- Dialer, Messaging, [Maps/Music], Browser, Camera -->
     <resolve
         launcher:container="-101"
         launcher:screen="0"
@@ -39,7 +39,14 @@
         <favorite launcher:uri="mmsto:" />
     </resolve>
 
-    <!-- All Apps -->
+    <resolve
+        launcher:container="-101"
+        launcher:screen="2"
+        launcher:x="2"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;end" />
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MUSIC;end" />
+    </resolve>
 
     <resolve
         launcher:container="-101"
diff --git a/res/xml/dw_tablet_hotseat.xml b/res/xml/dw_tablet_hotseat.xml
index 671ccba..6fe7f93 100644
--- a/res/xml/dw_tablet_hotseat.xml
+++ b/res/xml/dw_tablet_hotseat.xml
@@ -16,7 +16,7 @@
 
 <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
     <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
-    <!-- Messaging, Email, Browser, [All Apps], Music, Gallery, Camera -->
+    <!-- Messaging, Email, Browser, Maps, Music, Gallery, Camera -->
     <resolve
         launcher:container="-101"
         launcher:screen="0"
@@ -48,7 +48,13 @@
         <favorite launcher:uri="http://www.example.com/" />
     </resolve>
 
-    <!-- All Apps -->
+    <resolve
+        launcher:container="-101"
+        launcher:screen="3"
+        launcher:x="3"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;end" />
+    </resolve>
 
     <favorite
         launcher:container="-101"
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 3223837..e58e73d 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -69,9 +69,9 @@
     public static final int TYPE_DISCOVERY_BOUNCE = 1 << 6;
 
     // Popups related to quickstep UI
-    public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 6;
-    public static final int TYPE_TASK_MENU = 1 << 7;
-    public static final int TYPE_OPTIONS_POPUP = 1 << 8;
+    public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 7;
+    public static final int TYPE_TASK_MENU = 1 << 8;
+    public static final int TYPE_OPTIONS_POPUP = 1 << 9;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 469b8bb..6b0a90a 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -28,7 +28,6 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Build.VERSION;
 import android.os.Bundle;
 import android.os.Process;
@@ -37,16 +36,18 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.Patterns;
+
 import com.android.launcher3.LauncherProvider.SqlArguments;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.util.Thunk;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Locale;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
 
 /**
  * Layout parsing code for auto installs layout
@@ -230,9 +231,7 @@
         if (HOTSEAT_CONTAINER_NAME.equals(getAttributeValue(parser, ATTR_CONTAINER))) {
             out[0] = Favorites.CONTAINER_HOTSEAT;
             // Hack: hotseat items are stored using screen ids
-            long rank = Long.parseLong(getAttributeValue(parser, ATTR_RANK));
-            out[1] = (FeatureFlags.NO_ALL_APPS_ICON || rank < mIdp.getAllAppsButtonRank())
-                    ? rank : (rank + 1);
+            out[1] = Long.parseLong(getAttributeValue(parser, ATTR_RANK));
         } else {
             out[0] = Favorites.CONTAINER_DESKTOP;
             out[1] = Long.parseLong(getAttributeValue(parser, ATTR_SCREEN));
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 820c125..9839c12 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -59,6 +59,9 @@
 
     private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
 
+    // To evenly space the icons, increase the left/right margins for tablets in portrait mode.
+    private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4;
+
     // Workspace
     public final int desiredWorkspaceLeftRightMarginPx;
     public final int cellLayoutPaddingLeftRightPx;
@@ -172,7 +175,9 @@
         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
         desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
-        cellLayoutPaddingLeftRightPx =
+        int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
+                ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
+        cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier *
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
         cellLayoutBottomPaddingPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_bottom_padding);
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 6668f2c..15a9f2e 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -16,26 +16,18 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.LauncherState.ALL_APPS;
-
 import android.content.Context;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.Gravity;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
-import android.widget.TextView;
 
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
 public class Hotseat extends FrameLayout implements LogContainerProvider, Insettable {
@@ -92,45 +84,6 @@
         } else {
             mContent.setGridSize(idp.numHotseatIcons, 1);
         }
-
-        if (!FeatureFlags.NO_ALL_APPS_ICON) {
-            // Add the Apps button
-            Context context = getContext();
-            DeviceProfile grid = mLauncher.getDeviceProfile();
-            int allAppsButtonRank = grid.inv.getAllAppsButtonRank();
-
-            LayoutInflater inflater = LayoutInflater.from(context);
-            TextView allAppsButton = (TextView)
-                    inflater.inflate(R.layout.all_apps_button, mContent, false);
-            Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
-            d.setBounds(0, 0, grid.iconSizePx, grid.iconSizePx);
-
-            int scaleDownPx = getResources().getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
-            Rect bounds = d.getBounds();
-            d.setBounds(bounds.left, bounds.top + scaleDownPx / 2, bounds.right - scaleDownPx,
-                    bounds.bottom - scaleDownPx / 2);
-            allAppsButton.setCompoundDrawables(null, d, null, null);
-
-            allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label));
-            if (mLauncher != null) {
-                allAppsButton.setOnClickListener((v) -> {
-                    if (!mLauncher.isInState(ALL_APPS)) {
-                        mLauncher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
-                                ControlType.ALL_APPS_BUTTON);
-                        mLauncher.getStateManager().goToState(ALL_APPS);
-                    }
-                });
-                allAppsButton.setOnFocusChangeListener(mLauncher.mFocusHandler);
-            }
-
-            // Note: We do this to ensure that the hotseat is always laid out in the orientation of
-            // the hotseat in order regardless of which orientation they were added
-            int x = getCellXFromOrder(allAppsButtonRank);
-            int y = getCellYFromOrder(allAppsButtonRank);
-            CellLayout.LayoutParams lp = new CellLayout.LayoutParams(x, y, 1, 1);
-            lp.canReorder = false;
-            mContent.addViewToCellLayout(allAppsButton, -1, allAppsButton.getId(), lp, true);
-        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 22bc162..7084554 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -28,7 +28,6 @@
 import android.view.Display;
 import android.view.WindowManager;
 
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Thunk;
@@ -314,17 +313,6 @@
         return this;
     }
 
-    public int getAllAppsButtonRank() {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && FeatureFlags.NO_ALL_APPS_ICON) {
-            throw new IllegalAccessError("Accessing all apps rank when all-apps is disabled");
-        }
-        return numHotseatIcons / 2;
-    }
-
-    public boolean isAllAppsButtonRank(int rank) {
-        return rank == getAllAppsButtonRank();
-    }
-
     public DeviceProfile getDeviceProfile(Context context) {
         return context.getResources().getConfiguration().orientation
                 == Configuration.ORIENTATION_LANDSCAPE ? landscapeProfile : portraitProfile;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 534d6e3..a545122 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 
@@ -39,7 +40,6 @@
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
@@ -85,6 +85,7 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.FolderIconPreviewVerifier;
 import com.android.launcher3.keyboard.CustomActionsPopup;
@@ -355,6 +356,11 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         int diff = newConfig.diff(mOldConfig);
+
+        if ((diff & CONFIG_LOCALE) != 0) {
+            Folder.setLocaleDependentFields(getResources(), true /* force */);
+        }
+
         if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
             mUserEventDispatcher = null;
             initDeviceProfile(mDeviceProfile.inv);
@@ -1240,7 +1246,7 @@
                     mAppsView.reset(isStarted() /* animate */);
                 }
 
-                if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()) {
+                if (shouldMoveToDefaultScreen && !mWorkspace.isHandlingTouch()) {
                     mWorkspace.post(mWorkspace::moveToDefaultScreen);
                 }
             }
@@ -1599,14 +1605,8 @@
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
         if (topView != null && topView.onBackPressed()) {
             // Handled by the floating view.
-        } else if (!isInState(NORMAL)) {
-            LauncherState lastState = mStateManager.getLastState();
-            ued.logActionCommand(Action.Command.BACK, mStateManager.getState().containerType,
-                    lastState.containerType);
-            mStateManager.goToState(lastState);
         } else {
-            // Back button is a no-op here, but give at least some feedback for the button press
-            mWorkspace.showOutlinesTemporarily();
+            mStateManager.getState().onBackPressed(this);
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 8a15b24..bbe44c0 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.uioverrides.FastOverviewState;
 import com.android.launcher3.uioverrides.OverviewState;
 import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 import java.util.Arrays;
@@ -251,6 +252,16 @@
         }
     }
 
+    public void onBackPressed(Launcher launcher) {
+        if (this != NORMAL) {
+            LauncherStateManager lsm = launcher.getStateManager();
+            LauncherState lastState = lsm.getLastState();
+            launcher.getUserEventDispatcher().logActionCommand(Action.Command.BACK,
+                    containerType, lastState.containerType);
+            lsm.goToState(lastState);
+        }
+    }
+
     protected static void dispatchWindowStateChanged(Launcher launcher) {
         launcher.getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
     }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 3c7c1aa..5bbfc1d 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -306,7 +306,13 @@
      */
     public AnimatorPlaybackController createAnimationToNewWorkspace(
             LauncherState fromState, LauncherState state, long duration) {
+        // Since we are creating a state animation to a different state, temporarily prevent state
+        // change as part of config reset.
+        LauncherState originalRestState = mRestState;
+        mRestState = state;
         mConfig.reset();
+        mRestState = originalRestState;
+
         for (StateHandler handler : getStateHandlers()) {
             handler.setState(fromState);
         }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index db5dc66..c81f679 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -63,7 +63,6 @@
     protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
 
     public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
-    public static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
 
     // OverScroll constants
     private final static int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270;
@@ -109,13 +108,7 @@
     private float mTotalMotionX;
 
     protected int[] mPageScrolls;
-
-    protected final static int TOUCH_STATE_REST = 0;
-    protected final static int TOUCH_STATE_SCROLLING = 1;
-    protected final static int TOUCH_STATE_PREV_PAGE = 2;
-    protected final static int TOUCH_STATE_NEXT_PAGE = 3;
-
-    protected int mTouchState = TOUCH_STATE_REST;
+    private boolean mIsBeingDragged;
 
     protected int mTouchSlop;
     private int mMaximumVelocity;
@@ -451,7 +444,7 @@
 
             // We don't want to trigger a page end moving unless the page has settled
             // and the user has stopped scrolling
-            if (mTouchState == TOUCH_STATE_REST) {
+            if (!mIsBeingDragged) {
                 pageEndTransition();
             }
 
@@ -812,9 +805,9 @@
     }
 
     /** Returns whether x and y originated within the buffered viewport */
-    private boolean isTouchPointInViewportWithBuffer(int x, int y) {
+    private boolean isTouchPointInViewportWithBuffer(float x, float y) {
         sTmpRect.set(-getMeasuredWidth() / 2, 0, 3 * getMeasuredWidth() / 2, getMeasuredHeight());
-        return sTmpRect.contains(x, y);
+        return sTmpRect.contains((int) x, (int) y);
     }
 
     @Override
@@ -835,8 +828,7 @@
          * motion.
          */
         final int action = ev.getAction();
-        if ((action == MotionEvent.ACTION_MOVE) &&
-                (mTouchState == TOUCH_STATE_SCROLLING)) {
+        if ((action == MotionEvent.ACTION_MOVE) && mIsBeingDragged) {
             return true;
         }
 
@@ -877,17 +869,13 @@
                 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
 
                 if (finishedScrolling) {
-                    mTouchState = TOUCH_STATE_REST;
+                    mIsBeingDragged = false;
                     if (!mScroller.isFinished() && !mFreeScroll) {
                         setCurrentPage(getNextPage());
                         pageEndTransition();
                     }
                 } else {
-                    if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
-                        mTouchState = TOUCH_STATE_SCROLLING;
-                    } else {
-                        mTouchState = TOUCH_STATE_REST;
-                    }
+                    mIsBeingDragged = isTouchPointInViewportWithBuffer(mDownMotionX, mDownMotionY);
                 }
 
                 break;
@@ -908,11 +896,11 @@
          * The only time we want to intercept motion events is if we are in the
          * drag mode.
          */
-        return mTouchState != TOUCH_STATE_REST;
+        return mIsBeingDragged;
     }
 
     public boolean isHandlingTouch() {
-        return mTouchState != TOUCH_STATE_REST;
+        return mIsBeingDragged;
     }
 
     protected void determineScrollingStart(MotionEvent ev) {
@@ -931,7 +919,7 @@
         // Disallow scrolling if we started the gesture from outside the viewport
         final float x = ev.getX(pointerIndex);
         final float y = ev.getY(pointerIndex);
-        if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return;
+        if (!isTouchPointInViewportWithBuffer(x, y)) return;
 
         final int xDiff = (int) Math.abs(x - mLastMotionX);
 
@@ -940,7 +928,7 @@
 
         if (xMoved) {
             // Scroll if the user moved far enough along the X axis
-            mTouchState = TOUCH_STATE_SCROLLING;
+            mIsBeingDragged = true;
             mTotalMotionX += Math.abs(mLastMotionX - x);
             mLastMotionX = x;
             mLastMotionXRemainder = 0;
@@ -1077,14 +1065,14 @@
             mTotalMotionX = 0;
             mActivePointerId = ev.getPointerId(0);
 
-            if (mTouchState == TOUCH_STATE_SCROLLING) {
+            if (mIsBeingDragged) {
                 onScrollInteractionBegin();
                 pageBeginTransition();
             }
             break;
 
         case MotionEvent.ACTION_MOVE:
-            if (mTouchState == TOUCH_STATE_SCROLLING) {
+            if (mIsBeingDragged) {
                 // Scroll to follow the motion event
                 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
 
@@ -1111,7 +1099,7 @@
             break;
 
         case MotionEvent.ACTION_UP:
-            if (mTouchState == TOUCH_STATE_SCROLLING) {
+            if (mIsBeingDragged) {
                 final int activePointerId = mActivePointerId;
                 final int pointerIndex = ev.findPointerIndex(activePointerId);
                 final float x = ev.getX(pointerIndex);
@@ -1193,26 +1181,6 @@
                     invalidate();
                 }
                 onScrollInteractionEnd();
-            } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
-                // at this point we have not moved beyond the touch slop
-                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
-                // we can just page
-                int nextPage = Math.max(0, mCurrentPage - 1);
-                if (nextPage != mCurrentPage) {
-                    snapToPage(nextPage);
-                } else {
-                    snapToDestination();
-                }
-            } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
-                // at this point we have not moved beyond the touch slop
-                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
-                // we can just page
-                int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
-                if (nextPage != mCurrentPage) {
-                    snapToPage(nextPage);
-                } else {
-                    snapToDestination();
-                }
             }
 
             // End any intermediate reordering states
@@ -1220,7 +1188,7 @@
             break;
 
         case MotionEvent.ACTION_CANCEL:
-            if (mTouchState == TOUCH_STATE_SCROLLING) {
+            if (mIsBeingDragged) {
                 snapToDestination();
                 onScrollInteractionEnd();
             }
@@ -1242,7 +1210,7 @@
 
     private void resetTouchState() {
         releaseVelocityTracker();
-        mTouchState = TOUCH_STATE_REST;
+        mIsBeingDragged = false;
         mActivePointerId = INVALID_POINTER;
     }
 
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8683b21..a851318 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import android.app.ActivityManager;
 import android.app.WallpaperManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -130,6 +131,9 @@
             CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
             TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
 
+    public static final boolean IS_RUNNING_IN_TEST_HARNESS =
+                    ActivityManager.isRunningInTestHarness();
+
     public static boolean isPropertyEnabled(String propertyName) {
         return Log.isLoggable(propertyName, Log.VERBOSE);
     }
@@ -598,4 +602,8 @@
         msg.setAsynchronous(true);
         handler.sendMessage(msg);
     }
+
+    public interface Consumer<T> {
+        void accept(T var1);
+    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 67bdd3b..28783fa 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -54,6 +54,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Toast;
 
 import com.android.launcher3.Launcher.LauncherOverlay;
@@ -286,7 +287,9 @@
         mInsets.set(insets);
 
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
+        mMaxDistanceForFolderCreation = grid.isTablet
+                ? 0.75f * grid.iconSizePx
+                : 0.55f * grid.iconSizePx;
         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
 
         Rect padding = grid.workspacePadding;
@@ -471,10 +474,6 @@
         super.onViewAdded(child);
     }
 
-    public boolean isTouchActive() {
-        return mTouchState != TOUCH_STATE_REST;
-    }
-
     /**
      * Initializes and binds the first page
      * @param qsb an existing qsb to recycle or null.
@@ -547,7 +546,6 @@
         // created CellLayout.
         CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                         R.layout.workspace_screen, this, false /* attachToRoot */);
-        newScreen.getShortcutsAndWidgets().setId(R.id.workspace_page_container);
         int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx;
         int paddingBottom = mLauncher.getDeviceProfile().cellLayoutBottomPaddingPx;
         newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
@@ -1034,7 +1032,7 @@
     }
 
     protected void onScrollInteractionBegin() {
-        super.onScrollInteractionEnd();
+        super.onScrollInteractionBegin();
         mScrollInteractionBegan = true;
     }
 
@@ -1289,12 +1287,6 @@
         }
     }
 
-    public void showOutlinesTemporarily() {
-        if (!mIsPageInTransition && !isTouchActive()) {
-            snapToPage(mCurrentPage);
-        }
-    }
-
     private void updatePageAlphaValues() {
         // We need to check the isDragging case because updatePageAlphaValues is called between
         // goToState(SPRING_LOADED) and onStartStateTransition.
@@ -1493,6 +1485,18 @@
         }
     }
 
+    @Override
+    public AccessibilityNodeInfo createAccessibilityNodeInfo() {
+        if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
+            // TAPL tests verify that workspace is not present in Overview and AllApps states.
+            // TAPL can work only if UIDevice is set up as setCompressedLayoutHeirarchy(false).
+            // Hiding workspace from the tests when it's
+            // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS.
+            return null;
+        }
+        return super.createAccessibilityNodeInfo();
+    }
+
     private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page) {
         page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
         page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
@@ -2019,22 +2023,8 @@
     }
 
     public void onNoCellFound(View dropTargetLayout) {
-        if (mLauncher.isHotseatLayout(dropTargetLayout)) {
-            Hotseat hotseat = mLauncher.getHotseat();
-            boolean droppedOnAllAppsIcon = !FeatureFlags.NO_ALL_APPS_ICON
-                    && mTargetCell != null && !mLauncher.getDeviceProfile().inv.isAllAppsButtonRank(
-                    hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]));
-            if (!droppedOnAllAppsIcon) {
-                // Only show message when hotseat is full and drop target was not AllApps button
-                showOutOfSpaceMessage(true);
-            }
-        } else {
-            showOutOfSpaceMessage(false);
-        }
-    }
-
-    private void showOutOfSpaceMessage(boolean isHotseatLayout) {
-        int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
+        int strId = mLauncher.isHotseatLayout(dropTargetLayout)
+                ? R.string.hotseat_out_of_space : R.string.out_of_space;
         Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show();
     }
 
@@ -2630,16 +2620,10 @@
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                if (info.container == NO_ID) {
+                if (info.container == NO_ID && info instanceof AppInfo) {
                     // Came from all apps -- make a copy
-                    if (info instanceof AppInfo) {
-                        info = ((AppInfo) info).makeShortcut();
-                        d.dragInfo = info;
-                    } else if (info instanceof ShortcutInfo) {
-                        info = new ShortcutInfo((ShortcutInfo) info);
-                        d.dragInfo = info;
-                    }
-
+                    info = ((AppInfo) info).makeShortcut();
+                    d.dragInfo = info;
                 }
                 view = mLauncher.createShortcut(cellLayout, (ShortcutInfo) info);
                 break;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index fdf32af..40cf0f3 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -21,6 +21,7 @@
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Process;
 import android.support.animation.DynamicAnimation;
 import android.support.annotation.NonNull;
@@ -48,6 +49,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -549,4 +551,16 @@
                     && verticalFadingEdge);
         }
     }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        if (AccessibilityManagerCompat.processTestRequest(
+                mLauncher, "TAPL_GET_SCROLL", action, arguments,
+                response ->
+                        response.putInt("scrollY", getActiveRecyclerView().getCurrentScrollY()))) {
+            return true;
+        }
+
+        return super.performAccessibilityAction(action, arguments);
+    }
 }
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index e7cf092..d766398 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -24,6 +24,7 @@
 
 import com.android.launcher3.compat.UserManagerCompat;
 
+import java.lang.ref.WeakReference;
 import java.util.List;
 
 public class WorkModeSwitch extends Switch {
@@ -61,34 +62,57 @@
     }
 
     private void trySetQuietModeEnabledToAllProfilesAsync(boolean enabled) {
-        new AsyncTask<Void, Void, Boolean>() {
+        new SetQuietModeEnabledAsyncTask(enabled, new WeakReference<>(this)).execute();
+    }
 
-            @Override
-            protected void onPreExecute() {
-                super.onPreExecute();
-                setEnabled(false);
+    private static final class SetQuietModeEnabledAsyncTask
+            extends AsyncTask<Void, Void, Boolean> {
+
+        private final boolean enabled;
+        private final WeakReference<WorkModeSwitch> switchWeakReference;
+
+        SetQuietModeEnabledAsyncTask(boolean enabled,
+                                     WeakReference<WorkModeSwitch> switchWeakReference) {
+            this.enabled = enabled;
+            this.switchWeakReference = switchWeakReference;
+        }
+
+        @Override
+        protected void onPreExecute() {
+            super.onPreExecute();
+            WorkModeSwitch workModeSwitch = switchWeakReference.get();
+            if (workModeSwitch != null) {
+                workModeSwitch.setEnabled(false);
             }
+        }
 
-            @Override
-            protected Boolean doInBackground(Void... voids) {
-                UserManagerCompat userManager = UserManagerCompat.getInstance(getContext());
-                List<UserHandle> userProfiles = userManager.getUserProfiles();
-                boolean showConfirm = false;
-                for (UserHandle userProfile : userProfiles) {
-                    if (Process.myUserHandle().equals(userProfile)) {
-                        continue;
-                    }
-                    showConfirm |= !userManager.requestQuietModeEnabled(enabled, userProfile);
+        @Override
+        protected Boolean doInBackground(Void... voids) {
+            WorkModeSwitch workModeSwitch = switchWeakReference.get();
+            if (workModeSwitch == null) {
+                return false;
+            }
+            UserManagerCompat userManager =
+                    UserManagerCompat.getInstance(workModeSwitch.getContext());
+            List<UserHandle> userProfiles = userManager.getUserProfiles();
+            boolean showConfirm = false;
+            for (UserHandle userProfile : userProfiles) {
+                if (Process.myUserHandle().equals(userProfile)) {
+                    continue;
                 }
-                return showConfirm;
+                showConfirm |= !userManager.requestQuietModeEnabled(enabled, userProfile);
             }
+            return showConfirm;
+        }
 
-            @Override
-            protected void onPostExecute(Boolean showConfirm) {
-                if (showConfirm) {
-                    setEnabled(true);
+        @Override
+        protected void onPostExecute(Boolean showConfirm) {
+            if (showConfirm) {
+                WorkModeSwitch workModeSwitch = switchWeakReference.get();
+                if (workModeSwitch != null) {
+                    workModeSwitch.setEnabled(true);
                 }
             }
-        }.execute();
+        }
     }
 }
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 0c78381..3b0226e 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -17,9 +17,13 @@
 package com.android.launcher3.compat;
 
 import android.content.Context;
+import android.os.Bundle;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.launcher3.Utilities;
 
 public class AccessibilityManagerCompat {
 
@@ -44,4 +48,56 @@
     private static AccessibilityManager getManager(Context context) {
         return (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
     }
+
+    public static void sendEventToTest(Context context, String eventTag) {
+        final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
+        if (accessibilityManager == null) return;
+
+        sendEventToTest(accessibilityManager, eventTag, null);
+    }
+
+    private static void sendEventToTest(
+            AccessibilityManager accessibilityManager, String eventTag, Bundle data) {
+        final AccessibilityEvent e = AccessibilityEvent.obtain(
+                AccessibilityEvent.TYPE_ANNOUNCEMENT);
+        e.setClassName(eventTag);
+        e.setParcelableData(data);
+        accessibilityManager.sendAccessibilityEvent(e);
+    }
+
+    /**
+     * Returns accessibility manager to be used for communication with UI Automation tests.
+     * The tests may exchange custom accessibility messages with the launcher; the accessibility
+     * manager is used in these communications.
+     *
+     * If the launcher runs not under a test, the return is null, and no attempt to process or send
+     * custom accessibility messages should be made.
+     */
+    private static AccessibilityManager getAccessibilityManagerForTest(Context context) {
+        // If not running in a test harness, don't participate in test exchanges.
+        if (!Utilities.IS_RUNNING_IN_TEST_HARNESS) return null;
+
+        final AccessibilityManager accessibilityManager = getManager(context);
+        if (!accessibilityManager.isEnabled()) return null;
+
+        return accessibilityManager;
+    }
+
+    public static boolean processTestRequest(Context context, String eventTag, int action,
+            Bundle request, Utilities.Consumer<Bundle> responseFiller) {
+        final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
+        if (accessibilityManager == null) return false;
+
+        // The test sends a request via a ACTION_SET_TEXT.
+        if (action == AccessibilityNodeInfo.ACTION_SET_TEXT &&
+                eventTag.equals(request.getCharSequence(
+                        AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE))) {
+            final Bundle response = new Bundle();
+            responseFiller.accept(response);
+            AccessibilityManagerCompat.sendEventToTest(
+                    accessibilityManager, eventTag + "_RESPONSE", response);
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index f4c6380..05fc33f 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -31,12 +31,8 @@
     public static final boolean IS_DOGFOOD_BUILD = false;
     public static final String AUTHORITY = "com.android.launcher3.settings".intern();
 
-    // When enabled allows to use any point on the fast scrollbar to start dragging.
-    public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
     // When enabled the promise icon is visible in all apps while installation an app.
     public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
-    // When enabled allows use of spring motions on the icons.
-    public static final boolean LAUNCHER3_SPRING_ICONS = true;
 
     // Feature flag to enable moving the QSB on the 0th screen of the workspace.
     public static final boolean QSB_ON_FIRST_SCREEN = true;
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 8a216fc..47bbbcb 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.dragndrop;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.NORMAL;
 
@@ -31,6 +32,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.ItemInfo;
@@ -145,6 +147,7 @@
 
         // Hide soft keyboard, if visible
         UiThreadHelper.hideKeyboardAsync(mLauncher, mWindowToken);
+        AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_DISCOVERY_BOUNCE);
 
         mOptions = options;
         if (mOptions.systemDndStartPoint != null) {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 551567a..b3d9bdd 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.dragndrop;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.FloatArrayEvaluator;
@@ -57,8 +59,6 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.IconNormalizer;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
@@ -70,8 +70,6 @@
 import java.util.Arrays;
 import java.util.List;
 
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
-
 public class DragView extends View {
     private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
     private static final ColorMatrix sTempMatrix2 = new ColorMatrix();
@@ -198,7 +196,7 @@
      */
     @TargetApi(Build.VERSION_CODES.O)
     public void setItemInfo(final ItemInfo info) {
-        if (!(FeatureFlags.LAUNCHER3_SPRING_ICONS && Utilities.ATLEAST_OREO)) {
+        if (!Utilities.ATLEAST_OREO) {
             return;
         }
         if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 6b13da7..6a3ebcf 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -25,6 +25,7 @@
 import android.animation.AnimatorSet;
 import android.annotation.SuppressLint;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.text.InputType;
@@ -191,14 +192,9 @@
     public Folder(Context context, AttributeSet attrs) {
         super(context, attrs);
         setAlwaysDrawnWithCacheEnabled(false);
-        Resources res = getResources();
 
-        if (sDefaultFolderName == null) {
-            sDefaultFolderName = res.getString(R.string.folder_name);
-        }
-        if (sHintText == null) {
-            sHintText = res.getString(R.string.folder_hint_text);
-        }
+        setLocaleDependentFields(getResources(), false /* force */);
+
         mLauncher = Launcher.getLauncher(context);
         // We need this view to be focusable in touch mode so that when text editing of the folder
         // name is complete, we have something to focus on, thus hiding the cursor and giving
@@ -1473,4 +1469,13 @@
         }
         return false;
     }
+
+    public static void setLocaleDependentFields(Resources res, boolean force) {
+        if (sDefaultFolderName == null || force) {
+            sDefaultFolderName = res.getString(R.string.folder_name);
+        }
+        if (sHintText == null || force) {
+            sHintText = res.getString(R.string.folder_hint_text);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index bdc40d7..a60216c 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -76,7 +76,6 @@
     @Thunk Launcher mLauncher;
     @Thunk Folder mFolder;
     private FolderInfo mInfo;
-    @Thunk static boolean sStaticValuesDirty = true;
 
     private CheckLongPressHelper mLongPressHelper;
     private StylusEventHelper mStylusEventHelper;
@@ -184,12 +183,6 @@
         return icon;
     }
 
-    @Override
-    protected Parcelable onSaveInstanceState() {
-        sStaticValuesDirty = true;
-        return super.onSaveInstanceState();
-    }
-
     public Folder getFolder() {
         return mFolder;
     }
diff --git a/src/com/android/launcher3/graphics/NinePatchDrawHelper.java b/src/com/android/launcher3/graphics/NinePatchDrawHelper.java
index fc20926..5872689 100644
--- a/src/com/android/launcher3/graphics/NinePatchDrawHelper.java
+++ b/src/com/android/launcher3/graphics/NinePatchDrawHelper.java
@@ -33,7 +33,9 @@
 
     private final Rect mSrc = new Rect();
     private final RectF mDst = new RectF();
-    public final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    // Enable filtering to always get a nice edge. This avoids jagged line, when bitmap is
+    // translated by half pixel.
+    public final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
 
     /**
      * Draws the bitmap split into three parts horizontally, with the middle part having width
diff --git a/src/com/android/launcher3/graphics/ShadowDrawable.java b/src/com/android/launcher3/graphics/ShadowDrawable.java
index b40bf78..19e2768 100644
--- a/src/com/android/launcher3/graphics/ShadowDrawable.java
+++ b/src/com/android/launcher3/graphics/ShadowDrawable.java
@@ -32,7 +32,6 @@
 import android.util.AttributeSet;
 
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -146,7 +145,7 @@
             d.draw(canvas);
         }
 
-        if (Utilities.ATLEAST_OREO) {
+        if (BitmapRenderer.USE_HARDWARE_BITMAP) {
             bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
         }
         mState.mLastDrawnBitmap = bitmap;
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index e12095b..52d45ac 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -126,13 +126,13 @@
         }
 
         public Bitmap createPill(int width, int height) {
-            radius = height / 2;
+            radius = height / 2f;
 
-            int centerX = Math.round(width / 2 + shadowBlur);
+            int centerX = Math.round(width / 2f + shadowBlur);
             int centerY = Math.round(radius + shadowBlur + keyShadowDistance);
             int center = Math.max(centerX, centerY);
             bounds.set(0, 0, width, height);
-            bounds.offsetTo(center - width / 2, center - height / 2);
+            bounds.offsetTo(center - width / 2f, center - height / 2f);
 
             int size = center * 2;
             Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index d9b1a3f..12daea5 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -138,10 +138,7 @@
      */
     protected boolean migrateHotseat() throws Exception {
         ArrayList<DbEntry> items = loadHotseatEntries();
-
-        int requiredCount = FeatureFlags.NO_ALL_APPS_ICON ? mDestHotseatSize : mDestHotseatSize - 1;
-
-        while (items.size() > requiredCount) {
+        while (items.size() > mDestHotseatSize) {
             // Pick the center item by default.
             DbEntry toRemove = items.get(items.size() / 2);
 
@@ -171,9 +168,6 @@
             }
 
             newScreenId++;
-            if (!FeatureFlags.NO_ALL_APPS_ICON && mIdp.isAllAppsButtonRank(newScreenId)) {
-                newScreenId++;
-            }
         }
 
         return applyOperations();
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 6378ea1..744e98a 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -24,7 +24,6 @@
 import android.content.pm.LauncherActivityInfo;
 import android.database.Cursor;
 import android.database.CursorWrapper;
-import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.UserHandle;
 import android.provider.BaseColumns;
@@ -387,15 +386,6 @@
     protected boolean checkItemPlacement(ItemInfo item, ArrayList<Long> workspaceScreens) {
         long containerIndex = item.screenId;
         if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-            // Return early if we detect that an item is under the hotseat button
-            if (!FeatureFlags.NO_ALL_APPS_ICON &&
-                    mIDP.isAllAppsButtonRank((int) item.screenId)) {
-                Log.e(TAG, "Error loading shortcut into hotseat " + item
-                        + " into position (" + item.screenId + ":" + item.cellX + ","
-                        + item.cellY + ") occupied by all apps");
-                return false;
-            }
-
             final GridOccupancy hotseatOccupancy =
                     occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
 
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index 1216a27..66f525c 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -157,7 +157,7 @@
         Rect fromBounds = sTempRect;
         firstNotification.getGlobalVisibleRect(fromBounds);
         float scale = (float) toBounds.height() / fromBounds.height();
-        Animator moveAndScaleIcon = LauncherAnimUtils.ofPropertyValuesHolder(firstNotification,
+        Animator moveAndScaleIcon = ObjectAnimator.ofPropertyValuesHolder(firstNotification,
                 new PropertyListBuilder().scale(scale).translationY(toBounds.top - fromBounds.top
                         + (fromBounds.height() * scale - fromBounds.height()) / 2).build());
         moveAndScaleIcon.addListener(new AnimatorListenerAdapter() {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 635e043..10be925 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -84,7 +84,7 @@
 
     private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
     private final PointF mInterceptTouchDown = new PointF();
-    private final Point mIconLastTouchPos = new Point();
+    protected final Point mIconLastTouchPos = new Point();
 
     private final int mStartDragThreshold;
     private final LauncherAccessibilityDelegate mAccessibilityDelegate;
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index b1dd003..16c7417 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -308,9 +308,6 @@
 
         LongArrayMap<Object> hotseatItems = GridSizeMigrationTask.removeBrokenHotseatItems(mContext);
         int myHotseatCount = LauncherAppState.getIDP(mContext).numHotseatIcons;
-        if (!FeatureFlags.NO_ALL_APPS_ICON) {
-            myHotseatCount--;
-        }
         if (hotseatItems.size() < myHotseatCount) {
             // Insufficient hotseat items. Add a few more.
             HotseatParserCallback parserCallback = new HotseatParserCallback(
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 7d0ea7b..c856cdb 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -141,4 +141,8 @@
     public View getIconView() {
         return mIconView;
     }
+
+    public ShortcutInfoCompat getDetail() {
+        return mDetail;
+    }
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 55f850c..0e277ea 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -515,6 +516,8 @@
                 logReachedState(logAction, targetState);
             }
             mLauncher.getStateManager().goToState(targetState, false /* animated */);
+
+            AccessibilityManagerCompat.sendEventToTest(mLauncher, "TAPL_WENT_TO_STATE");
         }
     }
 
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 97f836f..52fef9f 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -218,7 +218,7 @@
         if (item instanceof ShortcutInfo) {
             ShortcutInfo si = (ShortcutInfo) item;
             if (si.hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI)
-                    && intent.getAction() == Intent.ACTION_VIEW) {
+                    && Intent.ACTION_VIEW.equals(intent.getAction())) {
                 // make a copy of the intent that has the package set to null
                 // we do this because the platform sometimes disables instant
                 // apps temporarily (triggered by the user) and fallbacks to the
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index f59f14e..6688927 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -125,7 +125,7 @@
         }
 
         if (action == ACTION_UP || action == ACTION_POINTER_UP) {
-            if (!mWorkspace.isTouchActive()) {
+            if (!mWorkspace.isHandlingTouch()) {
                 final CellLayout currentPage =
                         (CellLayout) mWorkspace.getChildAt(mWorkspace.getCurrentPage());
                 if (currentPage != null) {
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index b793f54..4f4cccd 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -201,10 +201,6 @@
         ViewGroup hotseatParent = hotseatLayout.getShortcutsAndWidgets();
 
         boolean isHotseatHorizontal = !dp.isVerticalBarLayout();
-        boolean moreIconsInHotseatThanWorkspace = !FeatureFlags.NO_ALL_APPS_ICON &&
-                (isHotseatHorizontal
-                        ? hotseatLayout.getCountX() > iconLayout.getCountX()
-                        : hotseatLayout.getCountY() > iconLayout.getCountY());
 
         int m, n;
         if (isHotseatHorizontal) {
@@ -215,19 +211,7 @@
             n = hotseatLayout.getCountY();
         }
         int[][] matrix = createFullMatrix(m, n);
-        if (moreIconsInHotseatThanWorkspace) {
-            int allappsiconRank = dp.inv.getAllAppsButtonRank();
-            if (isHotseatHorizontal) {
-                for (int j = 0; j < n; j++) {
-                    matrix[allappsiconRank][j] = ALL_APPS_COLUMN;
-                }
-            } else {
-                for (int j = 0; j < m; j++) {
-                    matrix[j][allappsiconRank] = ALL_APPS_COLUMN;
-                }
-            }
-        }
-        // Iterate thru the children of the workspace.
+        // Iterate through the children of the workspace.
         for (int i = 0; i < iconParent.getChildCount(); i++) {
             View cell = iconParent.getChildAt(i);
             if (!cell.isFocusable()) {
@@ -235,17 +219,6 @@
             }
             int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
             int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
-            if (moreIconsInHotseatThanWorkspace) {
-                int allappsiconRank = dp.inv.getAllAppsButtonRank();
-                if (isHotseatHorizontal && cx >= allappsiconRank) {
-                    // Add 1 to account for the All Apps button.
-                    cx++;
-                }
-                if (!isHotseatHorizontal && cy >= allappsiconRank) {
-                    // Add 1 to account for the All Apps button.
-                    cy++;
-                }
-            }
             matrix[cx][cy] = i;
         }
 
diff --git a/src/com/android/launcher3/util/OverScroller.java b/src/com/android/launcher3/util/OverScroller.java
new file mode 100644
index 0000000..ec9abce
--- /dev/null
+++ b/src/com/android/launcher3/util/OverScroller.java
@@ -0,0 +1,963 @@
+/*
+ * Copyright (C) 2010 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;
+
+import static com.android.launcher3.anim.Interpolators.SCROLL;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.util.Log;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * This class encapsulates scrolling with the ability to overshoot the bounds
+ * of a scrolling operation. This class is a drop-in replacement for
+ * {@link android.widget.Scroller} in most cases.
+ */
+public class OverScroller {
+    private int mMode;
+
+    private final SplineOverScroller mScrollerX;
+    private final SplineOverScroller mScrollerY;
+
+    private Interpolator mInterpolator;
+
+    private final boolean mFlywheel;
+
+    private static final int DEFAULT_DURATION = 250;
+    private static final int SCROLL_MODE = 0;
+    private static final int FLING_MODE = 1;
+
+    /**
+     * Creates an OverScroller with a viscous fluid scroll interpolator and flywheel.
+     * @param context
+     */
+    public OverScroller(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Creates an OverScroller with flywheel enabled.
+     * @param context The context of this application.
+     * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+     * be used.
+     */
+    public OverScroller(Context context, Interpolator interpolator) {
+        this(context, interpolator, true);
+    }
+
+    /**
+     * Creates an OverScroller.
+     * @param context The context of this application.
+     * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+     * be used.
+     * @param flywheel If true, successive fling motions will keep on increasing scroll speed.
+     * @hide
+     */
+    public OverScroller(Context context, Interpolator interpolator, boolean flywheel) {
+        if (interpolator == null) {
+            mInterpolator = SCROLL;
+        } else {
+            mInterpolator = interpolator;
+        }
+        mFlywheel = flywheel;
+        mScrollerX = new SplineOverScroller(context);
+        mScrollerY = new SplineOverScroller(context);
+    }
+
+    /**
+     * Creates an OverScroller with flywheel enabled.
+     * @param context The context of this application.
+     * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+     * be used.
+     * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
+     * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
+     * means no bounce. This behavior is no longer supported and this coefficient has no effect.
+     * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. This
+     * behavior is no longer supported and this coefficient has no effect.
+     * @deprecated Use {@link #OverScroller(Context, Interpolator)} instead.
+     */
+    @Deprecated
+    public OverScroller(Context context, Interpolator interpolator,
+            float bounceCoefficientX, float bounceCoefficientY) {
+        this(context, interpolator, true);
+    }
+
+    /**
+     * Creates an OverScroller.
+     * @param context The context of this application.
+     * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+     * be used.
+     * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
+     * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
+     * means no bounce. This behavior is no longer supported and this coefficient has no effect.
+     * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. This
+     * behavior is no longer supported and this coefficient has no effect.
+     * @param flywheel If true, successive fling motions will keep on increasing scroll speed.
+     * @deprecated Use {@link #OverScroller(Context, Interpolator)} instead.
+     */
+    @Deprecated
+    public OverScroller(Context context, Interpolator interpolator,
+            float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) {
+        this(context, interpolator, flywheel);
+    }
+
+    void setInterpolator(Interpolator interpolator) {
+        if (interpolator == null) {
+            mInterpolator = SCROLL;
+        } else {
+            mInterpolator = interpolator;
+        }
+    }
+
+    /**
+     * The amount of friction applied to flings. The default value
+     * is {@link ViewConfiguration#getScrollFriction}.
+     *
+     * @param friction A scalar dimension-less value representing the coefficient of
+     *         friction.
+     */
+    public final void setFriction(float friction) {
+        mScrollerX.setFriction(friction);
+        mScrollerY.setFriction(friction);
+    }
+
+    /**
+     *
+     * Returns whether the scroller has finished scrolling.
+     *
+     * @return True if the scroller has finished scrolling, false otherwise.
+     */
+    public final boolean isFinished() {
+        return mScrollerX.mFinished && mScrollerY.mFinished;
+    }
+
+    /**
+     * Force the finished field to a particular value. Contrary to
+     * {@link #abortAnimation()}, forcing the animation to finished
+     * does NOT cause the scroller to move to the final x and y
+     * position.
+     *
+     * @param finished The new finished value.
+     */
+    public final void forceFinished(boolean finished) {
+        mScrollerX.mFinished = mScrollerY.mFinished = finished;
+    }
+
+    /**
+     * Returns the current X offset in the scroll.
+     *
+     * @return The new X offset as an absolute distance from the origin.
+     */
+    public final int getCurrX() {
+        return mScrollerX.mCurrentPosition;
+    }
+
+    /**
+     * Returns the current Y offset in the scroll.
+     *
+     * @return The new Y offset as an absolute distance from the origin.
+     */
+    public final int getCurrY() {
+        return mScrollerY.mCurrentPosition;
+    }
+
+    /**
+     * Returns the absolute value of the current velocity.
+     *
+     * @return The original velocity less the deceleration, norm of the X and Y velocity vector.
+     */
+    public float getCurrVelocity() {
+        return (float) Math.hypot(mScrollerX.mCurrVelocity, mScrollerY.mCurrVelocity);
+    }
+
+    /**
+     * Returns the start X offset in the scroll.
+     *
+     * @return The start X offset as an absolute distance from the origin.
+     */
+    public final int getStartX() {
+        return mScrollerX.mStart;
+    }
+
+    /**
+     * Returns the start Y offset in the scroll.
+     *
+     * @return The start Y offset as an absolute distance from the origin.
+     */
+    public final int getStartY() {
+        return mScrollerY.mStart;
+    }
+
+    /**
+     * Returns where the scroll will end. Valid only for "fling" scrolls.
+     *
+     * @return The final X offset as an absolute distance from the origin.
+     */
+    public final int getFinalX() {
+        return mScrollerX.mFinal;
+    }
+
+    /**
+     * Returns where the scroll will end. Valid only for "fling" scrolls.
+     *
+     * @return The final Y offset as an absolute distance from the origin.
+     */
+    public final int getFinalY() {
+        return mScrollerY.mFinal;
+    }
+
+    /**
+     * Returns how long the scroll event will take, in milliseconds.
+     *
+     * @return The duration of the scroll in milliseconds.
+     *
+     * @hide Pending removal once nothing depends on it
+     * @deprecated OverScrollers don't necessarily have a fixed duration.
+     *             This function will lie to the best of its ability.
+     */
+    @Deprecated
+    public final int getDuration() {
+        return Math.max(mScrollerX.mDuration, mScrollerY.mDuration);
+    }
+
+    /**
+     * Extend the scroll animation. This allows a running animation to scroll
+     * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
+     *
+     * @param extend Additional time to scroll in milliseconds.
+     * @see #setFinalX(int)
+     * @see #setFinalY(int)
+     *
+     * @hide Pending removal once nothing depends on it
+     * @deprecated OverScrollers don't necessarily have a fixed duration.
+     *             Instead of setting a new final position and extending
+     *             the duration of an existing scroll, use startScroll
+     *             to begin a new animation.
+     */
+    @Deprecated
+    public void extendDuration(int extend) {
+        mScrollerX.extendDuration(extend);
+        mScrollerY.extendDuration(extend);
+    }
+
+    /**
+     * Sets the final position (X) for this scroller.
+     *
+     * @param newX The new X offset as an absolute distance from the origin.
+     * @see #extendDuration(int)
+     * @see #setFinalY(int)
+     *
+     * @hide Pending removal once nothing depends on it
+     * @deprecated OverScroller's final position may change during an animation.
+     *             Instead of setting a new final position and extending
+     *             the duration of an existing scroll, use startScroll
+     *             to begin a new animation.
+     */
+    @Deprecated
+    public void setFinalX(int newX) {
+        mScrollerX.setFinalPosition(newX);
+    }
+
+    /**
+     * Sets the final position (Y) for this scroller.
+     *
+     * @param newY The new Y offset as an absolute distance from the origin.
+     * @see #extendDuration(int)
+     * @see #setFinalX(int)
+     *
+     * @hide Pending removal once nothing depends on it
+     * @deprecated OverScroller's final position may change during an animation.
+     *             Instead of setting a new final position and extending
+     *             the duration of an existing scroll, use startScroll
+     *             to begin a new animation.
+     */
+    @Deprecated
+    public void setFinalY(int newY) {
+        mScrollerY.setFinalPosition(newY);
+    }
+
+    /**
+     * Call this when you want to know the new location. If it returns true, the
+     * animation is not yet finished.
+     */
+    public boolean computeScrollOffset() {
+        if (isFinished()) {
+            return false;
+        }
+
+        switch (mMode) {
+            case SCROLL_MODE:
+                long time = AnimationUtils.currentAnimationTimeMillis();
+                // Any scroller can be used for time, since they were started
+                // together in scroll mode. We use X here.
+                final long elapsedTime = time - mScrollerX.mStartTime;
+
+                final int duration = mScrollerX.mDuration;
+                if (elapsedTime < duration) {
+                    final float q = mInterpolator.getInterpolation(elapsedTime / (float) duration);
+                    mScrollerX.updateScroll(q);
+                    mScrollerY.updateScroll(q);
+                } else {
+                    abortAnimation();
+                }
+                break;
+
+            case FLING_MODE:
+                if (!mScrollerX.mFinished) {
+                    if (!mScrollerX.update()) {
+                        if (!mScrollerX.continueWhenFinished()) {
+                            mScrollerX.finish();
+                        }
+                    }
+                }
+
+                if (!mScrollerY.mFinished) {
+                    if (!mScrollerY.update()) {
+                        if (!mScrollerY.continueWhenFinished()) {
+                            mScrollerY.finish();
+                        }
+                    }
+                }
+
+                break;
+        }
+
+        return true;
+    }
+
+    /**
+     * Start scrolling by providing a starting point and the distance to travel.
+     * The scroll will use the default value of 250 milliseconds for the
+     * duration.
+     *
+     * @param startX Starting horizontal scroll offset in pixels. Positive
+     *        numbers will scroll the content to the left.
+     * @param startY Starting vertical scroll offset in pixels. Positive numbers
+     *        will scroll the content up.
+     * @param dx Horizontal distance to travel. Positive numbers will scroll the
+     *        content to the left.
+     * @param dy Vertical distance to travel. Positive numbers will scroll the
+     *        content up.
+     */
+    public void startScroll(int startX, int startY, int dx, int dy) {
+        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+    }
+
+    /**
+     * Start scrolling by providing a starting point and the distance to travel.
+     *
+     * @param startX Starting horizontal scroll offset in pixels. Positive
+     *        numbers will scroll the content to the left.
+     * @param startY Starting vertical scroll offset in pixels. Positive numbers
+     *        will scroll the content up.
+     * @param dx Horizontal distance to travel. Positive numbers will scroll the
+     *        content to the left.
+     * @param dy Vertical distance to travel. Positive numbers will scroll the
+     *        content up.
+     * @param duration Duration of the scroll in milliseconds.
+     */
+    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+        mMode = SCROLL_MODE;
+        mScrollerX.startScroll(startX, dx, duration);
+        mScrollerY.startScroll(startY, dy, duration);
+    }
+
+    /**
+     * Call this when you want to 'spring back' into a valid coordinate range.
+     *
+     * @param startX Starting X coordinate
+     * @param startY Starting Y coordinate
+     * @param minX Minimum valid X value
+     * @param maxX Maximum valid X value
+     * @param minY Minimum valid Y value
+     * @param maxY Minimum valid Y value
+     * @return true if a springback was initiated, false if startX and startY were
+     *          already within the valid range.
+     */
+    public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) {
+        mMode = FLING_MODE;
+
+        // Make sure both methods are called.
+        final boolean spingbackX = mScrollerX.springback(startX, minX, maxX);
+        final boolean spingbackY = mScrollerY.springback(startY, minY, maxY);
+        return spingbackX || spingbackY;
+    }
+
+    public void fling(int startX, int startY, int velocityX, int velocityY,
+            int minX, int maxX, int minY, int maxY) {
+        fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
+    }
+
+    /**
+     * Start scrolling based on a fling gesture. The distance traveled will
+     * depend on the initial velocity of the fling.
+     *
+     * @param startX Starting point of the scroll (X)
+     * @param startY Starting point of the scroll (Y)
+     * @param velocityX Initial velocity of the fling (X) measured in pixels per
+     *            second.
+     * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+     *            second
+     * @param minX Minimum X value. The scroller will not scroll past this point
+     *            unless overX > 0. If overfling is allowed, it will use minX as
+     *            a springback boundary.
+     * @param maxX Maximum X value. The scroller will not scroll past this point
+     *            unless overX > 0. If overfling is allowed, it will use maxX as
+     *            a springback boundary.
+     * @param minY Minimum Y value. The scroller will not scroll past this point
+     *            unless overY > 0. If overfling is allowed, it will use minY as
+     *            a springback boundary.
+     * @param maxY Maximum Y value. The scroller will not scroll past this point
+     *            unless overY > 0. If overfling is allowed, it will use maxY as
+     *            a springback boundary.
+     * @param overX Overfling range. If > 0, horizontal overfling in either
+     *            direction will be possible.
+     * @param overY Overfling range. If > 0, vertical overfling in either
+     *            direction will be possible.
+     */
+    public void fling(int startX, int startY, int velocityX, int velocityY,
+            int minX, int maxX, int minY, int maxY, int overX, int overY) {
+        // Continue a scroll or fling in progress
+        if (mFlywheel && !isFinished()) {
+            float oldVelocityX = mScrollerX.mCurrVelocity;
+            float oldVelocityY = mScrollerY.mCurrVelocity;
+            if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
+                    Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+                velocityX += oldVelocityX;
+                velocityY += oldVelocityY;
+            }
+        }
+
+        mMode = FLING_MODE;
+        mScrollerX.fling(startX, velocityX, minX, maxX, overX);
+        mScrollerY.fling(startY, velocityY, minY, maxY, overY);
+    }
+
+    /**
+     * Notify the scroller that we've reached a horizontal boundary.
+     * Normally the information to handle this will already be known
+     * when the animation is started, such as in a call to one of the
+     * fling functions. However there are cases where this cannot be known
+     * in advance. This function will transition the current motion and
+     * animate from startX to finalX as appropriate.
+     *
+     * @param startX Starting/current X position
+     * @param finalX Desired final X position
+     * @param overX Magnitude of overscroll allowed. This should be the maximum
+     *              desired distance from finalX. Absolute value - must be positive.
+     */
+    public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
+        mScrollerX.notifyEdgeReached(startX, finalX, overX);
+    }
+
+    /**
+     * Notify the scroller that we've reached a vertical boundary.
+     * Normally the information to handle this will already be known
+     * when the animation is started, such as in a call to one of the
+     * fling functions. However there are cases where this cannot be known
+     * in advance. This function will animate a parabolic motion from
+     * startY to finalY.
+     *
+     * @param startY Starting/current Y position
+     * @param finalY Desired final Y position
+     * @param overY Magnitude of overscroll allowed. This should be the maximum
+     *              desired distance from finalY. Absolute value - must be positive.
+     */
+    public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
+        mScrollerY.notifyEdgeReached(startY, finalY, overY);
+    }
+
+    /**
+     * Returns whether the current Scroller is currently returning to a valid position.
+     * Valid bounds were provided by the
+     * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method.
+     *
+     * One should check this value before calling
+     * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress
+     * to restore a valid position will then be stopped. The caller has to take into account
+     * the fact that the started scroll will start from an overscrolled position.
+     *
+     * @return true when the current position is overscrolled and in the process of
+     *         interpolating back to a valid value.
+     */
+    public boolean isOverScrolled() {
+        return ((!mScrollerX.mFinished &&
+                mScrollerX.mState != SplineOverScroller.SPLINE) ||
+                (!mScrollerY.mFinished &&
+                        mScrollerY.mState != SplineOverScroller.SPLINE));
+    }
+
+    /**
+     * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+     * aborting the animating causes the scroller to move to the final x and y
+     * positions.
+     *
+     * @see #forceFinished(boolean)
+     */
+    public void abortAnimation() {
+        mScrollerX.finish();
+        mScrollerY.finish();
+    }
+
+    /**
+     * Returns the time elapsed since the beginning of the scrolling.
+     *
+     * @return The elapsed time in milliseconds.
+     *
+     * @hide
+     */
+    public int timePassed() {
+        final long time = AnimationUtils.currentAnimationTimeMillis();
+        final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime);
+        return (int) (time - startTime);
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isScrollingInDirection(float xvel, float yvel) {
+        final int dx = mScrollerX.mFinal - mScrollerX.mStart;
+        final int dy = mScrollerY.mFinal - mScrollerY.mStart;
+        return !isFinished() && Math.signum(xvel) == Math.signum(dx) &&
+                Math.signum(yvel) == Math.signum(dy);
+    }
+
+    static class SplineOverScroller {
+        // Initial position
+        private int mStart;
+
+        // Current position
+        private int mCurrentPosition;
+
+        // Final position
+        private int mFinal;
+
+        // Initial velocity
+        private int mVelocity;
+
+        // Current velocity
+        private float mCurrVelocity;
+
+        // Constant current deceleration
+        private float mDeceleration;
+
+        // Animation starting time, in system milliseconds
+        private long mStartTime;
+
+        // Animation duration, in milliseconds
+        private int mDuration;
+
+        // Duration to complete spline component of animation
+        private int mSplineDuration;
+
+        // Distance to travel along spline animation
+        private int mSplineDistance;
+
+        // Whether the animation is currently in progress
+        private boolean mFinished;
+
+        // The allowed overshot distance before boundary is reached.
+        private int mOver;
+
+        // Fling friction
+        private float mFlingFriction = ViewConfiguration.getScrollFriction();
+
+        // Current state of the animation.
+        private int mState = SPLINE;
+
+        // Constant gravity value, used in the deceleration phase.
+        private static final float GRAVITY = 2000.0f;
+
+        // A context-specific coefficient adjusted to physical values.
+        private float mPhysicalCoeff;
+
+        private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
+        private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
+        private static final float START_TENSION = 0.5f;
+        private static final float END_TENSION = 1.0f;
+        private static final float P1 = START_TENSION * INFLEXION;
+        private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
+
+        private static final int NB_SAMPLES = 100;
+        private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
+        private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
+
+        private static final int SPLINE = 0;
+        private static final int CUBIC = 1;
+        private static final int BALLISTIC = 2;
+
+        static {
+            float x_min = 0.0f;
+            float y_min = 0.0f;
+            for (int i = 0; i < NB_SAMPLES; i++) {
+                final float alpha = (float) i / NB_SAMPLES;
+
+                float x_max = 1.0f;
+                float x, tx, coef;
+                while (true) {
+                    x = x_min + (x_max - x_min) / 2.0f;
+                    coef = 3.0f * x * (1.0f - x);
+                    tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
+                    if (Math.abs(tx - alpha) < 1E-5) break;
+                    if (tx > alpha) x_max = x;
+                    else x_min = x;
+                }
+                SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
+
+                float y_max = 1.0f;
+                float y, dy;
+                while (true) {
+                    y = y_min + (y_max - y_min) / 2.0f;
+                    coef = 3.0f * y * (1.0f - y);
+                    dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
+                    if (Math.abs(dy - alpha) < 1E-5) break;
+                    if (dy > alpha) y_max = y;
+                    else y_min = y;
+                }
+                SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
+            }
+            SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
+        }
+
+        void setFriction(float friction) {
+            mFlingFriction = friction;
+        }
+
+        SplineOverScroller(Context context) {
+            mFinished = true;
+            final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
+            mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
+                    * 39.37f // inch/meter
+                    * ppi
+                    * 0.84f; // look and feel tuning
+        }
+
+        void updateScroll(float q) {
+            mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
+        }
+
+        /*
+         * Get a signed deceleration that will reduce the velocity.
+         */
+        static private float getDeceleration(int velocity) {
+            return velocity > 0 ? -GRAVITY : GRAVITY;
+        }
+
+        /*
+         * Modifies mDuration to the duration it takes to get from start to newFinal using the
+         * spline interpolation. The previous duration was needed to get to oldFinal.
+         */
+        private void adjustDuration(int start, int oldFinal, int newFinal) {
+            final int oldDistance = oldFinal - start;
+            final int newDistance = newFinal - start;
+            final float x = Math.abs((float) newDistance / oldDistance);
+            final int index = (int) (NB_SAMPLES * x);
+            if (index < NB_SAMPLES) {
+                final float x_inf = (float) index / NB_SAMPLES;
+                final float x_sup = (float) (index + 1) / NB_SAMPLES;
+                final float t_inf = SPLINE_TIME[index];
+                final float t_sup = SPLINE_TIME[index + 1];
+                final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
+                mDuration *= timeCoef;
+            }
+        }
+
+        void startScroll(int start, int distance, int duration) {
+            mFinished = false;
+
+            mCurrentPosition = mStart = start;
+            mFinal = start + distance;
+
+            mStartTime = AnimationUtils.currentAnimationTimeMillis();
+            mDuration = duration;
+
+            // Unused
+            mDeceleration = 0.0f;
+            mVelocity = 0;
+        }
+
+        void finish() {
+            mCurrentPosition = mFinal;
+            // Not reset since WebView relies on this value for fast fling.
+            // TODO: restore when WebView uses the fast fling implemented in this class.
+            // mCurrVelocity = 0.0f;
+            mFinished = true;
+        }
+
+        void setFinalPosition(int position) {
+            mFinal = position;
+            mFinished = false;
+        }
+
+        void extendDuration(int extend) {
+            final long time = AnimationUtils.currentAnimationTimeMillis();
+            final int elapsedTime = (int) (time - mStartTime);
+            mDuration = elapsedTime + extend;
+            mFinished = false;
+        }
+
+        boolean springback(int start, int min, int max) {
+            mFinished = true;
+
+            mCurrentPosition = mStart = mFinal = start;
+            mVelocity = 0;
+
+            mStartTime = AnimationUtils.currentAnimationTimeMillis();
+            mDuration = 0;
+
+            if (start < min) {
+                startSpringback(start, min, 0);
+            } else if (start > max) {
+                startSpringback(start, max, 0);
+            }
+
+            return !mFinished;
+        }
+
+        private void startSpringback(int start, int end, int velocity) {
+            // mStartTime has been set
+            mFinished = false;
+            mState = CUBIC;
+            mCurrentPosition = mStart = start;
+            mFinal = end;
+            final int delta = start - end;
+            mDeceleration = getDeceleration(delta);
+            // TODO take velocity into account
+            mVelocity = -delta; // only sign is used
+            mOver = Math.abs(delta);
+            mDuration = (int) (1000.0 * Math.sqrt(-2.0 * delta / mDeceleration));
+        }
+
+        void fling(int start, int velocity, int min, int max, int over) {
+            mOver = over;
+            mFinished = false;
+            mCurrVelocity = mVelocity = velocity;
+            mDuration = mSplineDuration = 0;
+            mStartTime = AnimationUtils.currentAnimationTimeMillis();
+            mCurrentPosition = mStart = start;
+
+            if (start > max || start < min) {
+                startAfterEdge(start, min, max, velocity);
+                return;
+            }
+
+            mState = SPLINE;
+            double totalDistance = 0.0;
+
+            if (velocity != 0) {
+                mDuration = mSplineDuration = getSplineFlingDuration(velocity);
+                totalDistance = getSplineFlingDistance(velocity);
+            }
+
+            mSplineDistance = (int) (totalDistance * Math.signum(velocity));
+            mFinal = start + mSplineDistance;
+
+            // Clamp to a valid final position
+            if (mFinal < min) {
+                adjustDuration(mStart, mFinal, min);
+                mFinal = min;
+            }
+
+            if (mFinal > max) {
+                adjustDuration(mStart, mFinal, max);
+                mFinal = max;
+            }
+        }
+
+        private double getSplineDeceleration(int velocity) {
+            return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
+        }
+
+        private double getSplineFlingDistance(int velocity) {
+            final double l = getSplineDeceleration(velocity);
+            final double decelMinusOne = DECELERATION_RATE - 1.0;
+            return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
+        }
+
+        /* Returns the duration, expressed in milliseconds */
+        private int getSplineFlingDuration(int velocity) {
+            final double l = getSplineDeceleration(velocity);
+            final double decelMinusOne = DECELERATION_RATE - 1.0;
+            return (int) (1000.0 * Math.exp(l / decelMinusOne));
+        }
+
+        private void fitOnBounceCurve(int start, int end, int velocity) {
+            // Simulate a bounce that started from edge
+            final float durationToApex = - velocity / mDeceleration;
+            // The float cast below is necessary to avoid integer overflow.
+            final float velocitySquared = (float) velocity * velocity;
+            final float distanceToApex = velocitySquared / 2.0f / Math.abs(mDeceleration);
+            final float distanceToEdge = Math.abs(end - start);
+            final float totalDuration = (float) Math.sqrt(
+                    2.0 * (distanceToApex + distanceToEdge) / Math.abs(mDeceleration));
+            mStartTime -= (int) (1000.0f * (totalDuration - durationToApex));
+            mCurrentPosition = mStart = end;
+            mVelocity = (int) (- mDeceleration * totalDuration);
+        }
+
+        private void startBounceAfterEdge(int start, int end, int velocity) {
+            mDeceleration = getDeceleration(velocity == 0 ? start - end : velocity);
+            fitOnBounceCurve(start, end, velocity);
+            onEdgeReached();
+        }
+
+        private void startAfterEdge(int start, int min, int max, int velocity) {
+            if (start > min && start < max) {
+                Log.e("OverScroller", "startAfterEdge called from a valid position");
+                mFinished = true;
+                return;
+            }
+            final boolean positive = start > max;
+            final int edge = positive ? max : min;
+            final int overDistance = start - edge;
+            boolean keepIncreasing = overDistance * velocity >= 0;
+            if (keepIncreasing) {
+                // Will result in a bounce or a to_boundary depending on velocity.
+                startBounceAfterEdge(start, edge, velocity);
+            } else {
+                final double totalDistance = getSplineFlingDistance(velocity);
+                if (totalDistance > Math.abs(overDistance)) {
+                    fling(start, velocity, positive ? min : start, positive ? start : max, mOver);
+                } else {
+                    startSpringback(start, edge, velocity);
+                }
+            }
+        }
+
+        void notifyEdgeReached(int start, int end, int over) {
+            // mState is used to detect successive notifications
+            if (mState == SPLINE) {
+                mOver = over;
+                mStartTime = AnimationUtils.currentAnimationTimeMillis();
+                // We were in fling/scroll mode before: current velocity is such that distance to
+                // edge is increasing. This ensures that startAfterEdge will not start a new fling.
+                startAfterEdge(start, end, end, (int) mCurrVelocity);
+            }
+        }
+
+        private void onEdgeReached() {
+            // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
+            // The float cast below is necessary to avoid integer overflow.
+            final float velocitySquared = (float) mVelocity * mVelocity;
+            float distance = velocitySquared / (2.0f * Math.abs(mDeceleration));
+            final float sign = Math.signum(mVelocity);
+
+            if (distance > mOver) {
+                // Default deceleration is not sufficient to slow us down before boundary
+                mDeceleration = - sign * velocitySquared / (2.0f * mOver);
+                distance = mOver;
+            }
+
+            mOver = (int) distance;
+            mState = BALLISTIC;
+            mFinal = mStart + (int) (mVelocity > 0 ? distance : -distance);
+            mDuration = - (int) (1000.0f * mVelocity / mDeceleration);
+        }
+
+        boolean continueWhenFinished() {
+            switch (mState) {
+                case SPLINE:
+                    // Duration from start to null velocity
+                    if (mDuration < mSplineDuration) {
+                        // If the animation was clamped, we reached the edge
+                        mCurrentPosition = mStart = mFinal;
+                        // TODO Better compute speed when edge was reached
+                        mVelocity = (int) mCurrVelocity;
+                        mDeceleration = getDeceleration(mVelocity);
+                        mStartTime += mDuration;
+                        onEdgeReached();
+                    } else {
+                        // Normal stop, no need to continue
+                        return false;
+                    }
+                    break;
+                case BALLISTIC:
+                    mStartTime += mDuration;
+                    startSpringback(mFinal, mStart, 0);
+                    break;
+                case CUBIC:
+                    return false;
+            }
+
+            update();
+            return true;
+        }
+
+        /*
+         * Update the current position and velocity for current time. Returns
+         * true if update has been done and false if animation duration has been
+         * reached.
+         */
+        boolean update() {
+            final long time = AnimationUtils.currentAnimationTimeMillis();
+            final long currentTime = time - mStartTime;
+
+            if (currentTime == 0) {
+                // Skip work but report that we're still going if we have a nonzero duration.
+                return mDuration > 0;
+            }
+            if (currentTime > mDuration) {
+                return false;
+            }
+
+            double distance = 0.0;
+            switch (mState) {
+                case SPLINE: {
+                    final float t = (float) currentTime / mSplineDuration;
+                    final int index = (int) (NB_SAMPLES * t);
+                    float distanceCoef = 1.f;
+                    float velocityCoef = 0.f;
+                    if (index < NB_SAMPLES) {
+                        final float t_inf = (float) index / NB_SAMPLES;
+                        final float t_sup = (float) (index + 1) / NB_SAMPLES;
+                        final float d_inf = SPLINE_POSITION[index];
+                        final float d_sup = SPLINE_POSITION[index + 1];
+                        velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
+                        distanceCoef = d_inf + (t - t_inf) * velocityCoef;
+                    }
+
+                    distance = distanceCoef * mSplineDistance;
+                    mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0f;
+                    break;
+                }
+
+                case BALLISTIC: {
+                    final float t = currentTime / 1000.0f;
+                    mCurrVelocity = mVelocity + mDeceleration * t;
+                    distance = mVelocity * t + mDeceleration * t * t / 2.0f;
+                    break;
+                }
+
+                case CUBIC: {
+                    final float t = (float) (currentTime) / mDuration;
+                    final float t2 = t * t;
+                    final float sign = Math.signum(mVelocity);
+                    distance = sign * mOver * (3.0f * t2 - 2.0f * t * t2);
+                    mCurrVelocity = sign * mOver * 6.0f * (- t + t2);
+                    break;
+                }
+            }
+
+            mCurrentPosition = mStart + (int) Math.round(distance);
+
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index c8d1457..59bea48 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -30,7 +30,6 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.touch.SwipeDetector;
@@ -76,7 +75,7 @@
         mScrollInterpolator = Interpolators.SCROLL_CUBIC;
         mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
 
-        mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
+        mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
         mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -103,7 +102,7 @@
                 directionsToDetectScroll, false);
         mSwipeDetector.onTouchEvent(ev);
         return mSwipeDetector.isDraggingOrSettling()
-                || !mLauncher.getDragLayer().isEventOverView(mContent, ev);
+                || !getPopupContainer().isEventOverView(mContent, ev);
     }
 
     @Override
@@ -111,7 +110,7 @@
         mSwipeDetector.onTouchEvent(ev);
         if (ev.getAction() == MotionEvent.ACTION_UP && mSwipeDetector.isIdleState()) {
             // If we got ACTION_UP without ever starting swipe, close the panel.
-            if (!mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
+            if (!getPopupContainer().isEventOverView(mContent, ev)) {
                 close(true);
             }
         }
@@ -178,6 +177,10 @@
 
     protected void onCloseComplete() {
         mIsOpen = false;
-        mLauncher.getDragLayer().removeView(this);
+        getPopupContainer().removeView(this);
+    }
+
+    protected BaseDragLayer getPopupContainer() {
+        return mLauncher.getDragLayer();
     }
 }
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 5046639..dc6d2ff 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.AttributeSet;
@@ -133,6 +134,11 @@
         popup.reorderAndShow(popup.getChildCount());
     }
 
+    @VisibleForTesting
+    public static OptionsPopupView getOptionsPopup(Launcher launcher) {
+        return launcher.findViewById(R.id.deep_shortcuts_container);
+    }
+
     public static void showDefaultOptions(Launcher launcher, float x, float y) {
         float halfSize = launcher.getResources().getDimension(R.dimen.options_menu_thumb_size) / 2;
         if (x < 0 || y < 0) {
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 05bab8b..ed1cf4f 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -224,8 +224,7 @@
                 }
                 if (isNearThumb(x, y)) {
                     mTouchOffsetY = mDownY - mThumbOffsetY;
-                } else if (FeatureFlags.LAUNCHER3_DIRECT_SCROLL
-                        && mRv.supportsFastScrolling()
+                } else if (mRv.supportsFastScrolling()
                         && isNearScrollBar(mDownX)) {
                     calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
                     updateFastScrollSectionNameAndThumbOffset(mLastY, y);
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 10708d6..20c8876 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -71,7 +71,7 @@
     }
 
     @Override
-    public final boolean onLongClick(View v) {
+    public boolean onLongClick(View v) {
         if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
 
         if (v instanceof WidgetCell) {
@@ -96,7 +96,7 @@
         }
 
         int[] loc = new int[2];
-        mLauncher.getDragLayer().getLocationInDragLayer(image, loc);
+        getPopupContainer().getLocationInDragLayer(image, loc);
 
         new PendingItemDragHelper(v).startDrag(
                 image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(),
@@ -119,13 +119,13 @@
     }
 
     protected void clearNavBarColor() {
-        mLauncher.getSystemUiController().updateUiState(
+        getSystemUiController().updateUiState(
                 SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0);
     }
 
     protected void setupNavBarColor() {
-        boolean isSheetDark = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
-        mLauncher.getSystemUiController().updateUiState(
+        boolean isSheetDark = Themes.getAttrBoolean(getContext(), R.attr.isMainColorDark);
+        getSystemUiController().updateUiState(
                 SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET,
                 isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
     }
@@ -145,4 +145,7 @@
 
     protected abstract int getElementsRowCount();
 
+    protected SystemUiController getSystemUiController() {
+        return mLauncher.getSystemUiController();
+    }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 5ce7e04..4ba6b5b 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -71,7 +71,7 @@
 
         onWidgetsBound();
 
-        mLauncher.getDragLayer().addView(this);
+        getPopupContainer().addView(this);
         mIsOpen = false;
         animateOpen();
     }
@@ -118,7 +118,7 @@
         LayoutInflater.from(getContext()).inflate(R.layout.widget_list_divider, parent, true);
     }
 
-    private WidgetCell addItemCell(ViewGroup parent) {
+    protected WidgetCell addItemCell(ViewGroup parent) {
         WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()).inflate(
                 R.layout.widget_cell, parent, false);
 
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index e94d81d..2a7bb03 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -20,6 +20,7 @@
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Rect;
+import android.support.annotation.VisibleForTesting;
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.view.LayoutInflater;
@@ -159,7 +160,7 @@
 
     private void open(boolean animate) {
         if (animate) {
-            if (mLauncher.getDragLayer().getInsets().bottom > 0) {
+            if (getPopupContainer().getInsets().bottom > 0) {
                 mContent.setAlpha(0);
                 setTranslationShift(VERTICAL_START_POSITION);
             }
@@ -206,10 +207,10 @@
             mNoIntercept = false;
             RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar();
             if (scroller.getThumbOffsetY() >= 0 &&
-                    mLauncher.getDragLayer().isEventOverView(scroller, ev)) {
+                    getPopupContainer().isEventOverView(scroller, ev)) {
                 mNoIntercept = true;
-            } else if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
-                mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer());
+            } else if (getPopupContainer().isEventOverView(mContent, ev)) {
+                mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, getPopupContainer());
             }
         }
         return super.onControllerInterceptTouchEvent(ev);
@@ -219,11 +220,16 @@
         WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
                 .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
         sheet.mIsOpen = true;
-        launcher.getDragLayer().addView(sheet);
+        sheet.getPopupContainer().addView(sheet);
         sheet.open(animate);
         return sheet;
     }
 
+    @VisibleForTesting
+    public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
+        return launcher.findViewById(R.id.widgets_list_view);
+    }
+
     @Override
     protected int getElementsRowCount() {
         return mAdapter.getItemCount();
diff --git a/tests/Android.mk b/tests/Android.mk
index f6f02fe..c7a222a 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -30,3 +30,17 @@
 LOCAL_INSTRUMENTATION_FOR := Launcher3
 
 include $(BUILD_PACKAGE)
+
+#
+# Build rule for Tapl library.
+#
+include $(CLEAR_VARS)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
+
+LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
+  ../quickstep/src/com/android/quickstep/SwipeUpSetting.java
+
+LOCAL_SDK_VERSION := current
+LOCAL_MODULE := ub-launcher-aosp-tapl
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
index b92f612..031909f 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -16,7 +16,6 @@
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
 import com.android.launcher3.util.TestLauncherProvider;
 
@@ -87,13 +86,8 @@
         mIdp.numHotseatIcons = 3;
         new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
                 .migrateHotseat();
-        if (FeatureFlags.NO_ALL_APPS_ICON) {
-            // First item is dropped as it has the least weight.
-            verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
-        } else {
-            // First & last items are dropped as they have the least weight.
-            verifyHotseat(hotseatItems[1], -1, hotseatItems[3]);
-        }
+        // First item is dropped as it has the least weight.
+        verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
     }
 
     @Test
@@ -109,13 +103,8 @@
         mIdp.numHotseatIcons = 3;
         new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
                 .migrateHotseat();
-        if (FeatureFlags.NO_ALL_APPS_ICON) {
-            // First item is dropped as it has the least weight.
-            verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
-        } else {
-            // First & third items are dropped as they have the least weight.
-            verifyHotseat(hotseatItems[1], -1, hotseatItems[4]);
-        }
+        // First item is dropped as it has the least weight.
+        verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
     }
 
     private void verifyHotseat(long... sortedIds) {
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index f16f514..dd91fe8 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -43,13 +43,11 @@
 import com.android.launcher3.R;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.testcomponent.AppWidgetNoConfig;
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
 
 import org.junit.Before;
 
-import java.util.Locale;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -101,19 +99,12 @@
      */
     protected UiObject2 openAllApps() {
         mDevice.waitForIdle();
-        if (FeatureFlags.NO_ALL_APPS_ICON) {
-            UiObject2 hotseat = mDevice.wait(
-                    Until.findObject(getSelectorForId(R.id.hotseat)), 2500);
-            Point start = hotseat.getVisibleCenter();
-            int endY = (int) (mDevice.getDisplayHeight() * 0.1f);
-            // 100 px/step
-            mDevice.swipe(start.x, start.y, start.x, endY, (start.y - endY) / 100);
-
-        } else {
-            mDevice.wait(Until.findObject(
-                    By.desc(mTargetContext.getString(R.string.all_apps_button_label))),
-                    DEFAULT_UI_TIMEOUT).click();
-        }
+        UiObject2 hotseat = mDevice.wait(
+                Until.findObject(getSelectorForId(R.id.hotseat)), 2500);
+        Point start = hotseat.getVisibleCenter();
+        int endY = (int) (mDevice.getDisplayHeight() * 0.1f);
+        // 100 px/step
+        mDevice.swipe(start.x, start.y, start.x, endY, (start.y - endY) / 100);
         return findViewById(R.id.apps_list_view);
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
similarity index 66%
rename from tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java
rename to tests/tapl/com/android/launcher3/tapl/AllApps.java
index 02f8183..e270b46 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -16,36 +16,32 @@
 
 package com.android.launcher3.tapl;
 
+import static org.junit.Assert.assertTrue;
+
 import android.support.annotation.NonNull;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.Direction;
 import android.support.test.uiautomator.UiObject2;
 
 /**
- * Operations on AllApps opened from Home.
+ * Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview.
  */
-public final class AllAppsFromHome {
+public class AllApps extends LauncherInstrumentation.VisibleContainer {
     private static final int MAX_SCROLL_ATTEMPTS = 40;
     private static final int MIN_INTERACT_SIZE = 100;
     private static final int FLING_SPEED = 12000;
 
-    private final Launcher mLauncher;
     private final int mHeight;
 
-    AllAppsFromHome(Launcher launcher) {
-        mLauncher = launcher;
-        final UiObject2 allAppsContainer = assertState();
+    AllApps(LauncherInstrumentation launcher) {
+        super(launcher);
+        final UiObject2 allAppsContainer = verifyActiveContainer();
         mHeight = allAppsContainer.getVisibleBounds().height();
     }
 
-    /**
-     * Asserts that we are in all apps.
-     *
-     * @return All apps container.
-     */
-    @NonNull
-    private UiObject2 assertState() {
-        return mLauncher.assertState(Launcher.State.ALL_APPS);
+    @Override
+    protected LauncherInstrumentation.ContainerType getContainerType() {
+        return LauncherInstrumentation.ContainerType.ALL_APPS;
     }
 
     /**
@@ -57,19 +53,19 @@
      */
     @NonNull
     public AppIcon getAppIcon(String appName) {
-        final UiObject2 allAppsContainer = assertState();
+        final UiObject2 allAppsContainer = verifyActiveContainer();
         final BySelector appIconSelector = AppIcon.getAppIconSelector(appName);
         if (!allAppsContainer.hasObject(appIconSelector)) {
             scrollBackToBeginning();
             int attempts = 0;
             while (!allAppsContainer.hasObject(appIconSelector) &&
                     allAppsContainer.scroll(Direction.DOWN, 0.8f)) {
-                mLauncher.assertTrue("Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
+                assertTrue("Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
                         ++attempts <= MAX_SCROLL_ATTEMPTS);
-                assertState();
+                verifyActiveContainer();
             }
         }
-        assertState();
+        verifyActiveContainer();
 
         final UiObject2 appIcon = mLauncher.getObjectInContainer(allAppsContainer, appIconSelector);
         ensureIconVisible(appIcon, allAppsContainer);
@@ -77,21 +73,30 @@
     }
 
     private void scrollBackToBeginning() {
-        final UiObject2 allAppsContainer = assertState();
+        final UiObject2 allAppsContainer = verifyActiveContainer();
+        final UiObject2 searchBox =
+                mLauncher.waitForObjectInContainer(allAppsContainer, "search_container_all_apps");
 
         int attempts = 0;
-        allAppsContainer.setGestureMargins(5, 500, 5, 5);
+        allAppsContainer.setGestureMargins(0, searchBox.getVisibleBounds().bottom + 1, 0, 5);
 
-        while (allAppsContainer.scroll(Direction.UP, 0.5f)) {
-            mLauncher.waitForIdle();
-            assertState();
+        for (int scroll = getScroll(allAppsContainer);
+                scroll != 0;
+                scroll = getScroll(allAppsContainer)) {
+            assertTrue("Negative scroll position", scroll > 0);
 
-            mLauncher.assertTrue("Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
+            assertTrue("Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
                     ++attempts <= MAX_SCROLL_ATTEMPTS);
+
+            allAppsContainer.scroll(Direction.UP, 1);
         }
 
-        mLauncher.waitForIdle();
-        assertState();
+        verifyActiveContainer();
+    }
+
+    private int getScroll(UiObject2 allAppsContainer) {
+        return mLauncher.getAnswerFromLauncher(allAppsContainer, "TAPL_GET_SCROLL").
+                getInt("scrollY", -1);
     }
 
     private void ensureIconVisible(UiObject2 appIcon, UiObject2 allAppsContainer) {
@@ -102,7 +107,7 @@
             final float pct = Math.max(((float) (MIN_INTERACT_SIZE - appHeight)) / mHeight, 0.2f);
             allAppsContainer.scroll(Direction.DOWN, pct);
             mLauncher.waitForIdle();
-            assertState();
+            verifyActiveContainer();
         }
     }
 
@@ -110,22 +115,22 @@
      * Flings forward (down) and waits the fling's end.
      */
     public void flingForward() {
-        final UiObject2 allAppsContainer = assertState();
+        final UiObject2 allAppsContainer = verifyActiveContainer();
         // Start the gesture in the center to avoid starting at elements near the top.
         allAppsContainer.setGestureMargins(0, 0, 0, mHeight / 2);
         allAppsContainer.fling(Direction.DOWN, FLING_SPEED);
-        assertState();
+        verifyActiveContainer();
     }
 
     /**
      * Flings backward (up) and waits the fling's end.
      */
     public void flingBackward() {
-        final UiObject2 allAppsContainer = assertState();
+        final UiObject2 allAppsContainer = verifyActiveContainer();
         // Start the gesture in the center, for symmetry with forward.
         allAppsContainer.setGestureMargins(0, mHeight / 2, 0, 0);
         allAppsContainer.fling(Direction.UP, FLING_SPEED);
-        assertState();
+        verifyActiveContainer();
     }
 
     /**
@@ -137,6 +142,6 @@
     @Deprecated
     @NonNull
     public UiObject2 getObjectDeprecated() {
-        return assertState();
+        return verifyActiveContainer();
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
index cba7086..7ed2dc2 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
@@ -22,24 +22,12 @@
 
 /**
  * Operations on AllApps opened from Overview.
- * Scroll gestures that are OK for {@link AllAppsFromHome} may close it, so they are not supported.
  */
-public final class AllAppsFromOverview {
-    private final Launcher mLauncher;
+public final class AllAppsFromOverview extends AllApps {
 
-    AllAppsFromOverview(Launcher launcher) {
-        mLauncher = launcher;
-        assertState();
-    }
-
-    /**
-     * Asserts that we are in all apps.
-     *
-     * @return All apps container.
-     */
-    @NonNull
-    private UiObject2 assertState() {
-        return mLauncher.assertState(Launcher.State.ALL_APPS);
+    AllAppsFromOverview(LauncherInstrumentation launcher) {
+        super(launcher);
+        verifyActiveContainer();
     }
 
     /**
@@ -49,13 +37,13 @@
      */
     @NonNull
     public Overview switchBackToOverview() {
-        final UiObject2 allAppsContainer = assertState();
+        final UiObject2 allAppsContainer = verifyActiveContainer();
         // Swipe from the search box to the bottom.
         final UiObject2 qsb = mLauncher.waitForObjectInContainer(
                 allAppsContainer, "search_container_all_apps");
         final Point start = qsb.getVisibleCenter();
         final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.6);
-        mLauncher.swipe(start.x, start.y, start.x, endY, (endY - start.y) / 100);  // 100 px/step
+        mLauncher.swipe(start.x, start.y, start.x, endY);
 
         return new Overview(mLauncher);
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 73a74f2..721f7a8 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.tapl;
 
+import static org.junit.Assert.assertTrue;
+
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiObject2;
@@ -26,25 +28,25 @@
  * App icon, whether in all apps or in workspace/
  */
 public final class AppIcon {
-    private final Launcher mLauncher;
+    private final LauncherInstrumentation mLauncher;
     private final UiObject2 mIcon;
 
-    AppIcon(Launcher launcher, UiObject2 icon) {
+    AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
         mLauncher = launcher;
         mIcon = icon;
     }
 
     static BySelector getAppIconSelector(String appName) {
-        return By.clazz(TextView.class).text(appName).pkg(Launcher.LAUNCHER_PKG);
+        return By.clazz(TextView.class).text(appName).pkg(LauncherInstrumentation.LAUNCHER_PKG);
     }
 
     /**
      * Clicks the icon to launch its app.
      */
-    public void launch() {
-        mLauncher.assertTrue("Launching an app didn't open a new window: " + mIcon.getText(),
-                mIcon.clickAndWait(Until.newWindow(), Launcher.APP_LAUNCH_TIMEOUT_MS));
-        mLauncher.assertState(Launcher.State.BACKGROUND);
+    public Background launch() {
+        assertTrue("Launching an app didn't open a new window: " + mIcon.getText(),
+                mIcon.clickAndWait(Until.newWindow(), LauncherInstrumentation.WAIT_TIME_MS));
+        return new Background(mLauncher);
     }
 
     UiObject2 getIcon() {
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
new file mode 100644
index 0000000..1aef979
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+/**
+ * Operations on a state when Launcher is inactive because some other app is active.
+ */
+public final class Background extends Home {
+
+    Background(LauncherInstrumentation launcher) {
+        super(launcher);
+    }
+
+    @Override
+    protected LauncherInstrumentation.ContainerType getContainerType() {
+        return LauncherInstrumentation.ContainerType.BACKGROUND;
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index 0ec1a64..436e5ff 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -16,38 +16,22 @@
 
 package com.android.launcher3.tapl;
 
-import static junit.framework.TestCase.assertTrue;
-
-import android.graphics.Point;
 import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.test.uiautomator.Direction;
 import android.support.test.uiautomator.UiObject2;
-import android.view.KeyEvent;
 
 /**
  * Operations on the home screen.
+ *
+ * Launcher can be invoked both when its activity is in the foreground and when it is in the
+ * background. This class is a parent of the two classes {@link Background} and {@link Workspace}
+ * that essentially represents these two activity states. Any gestures (e.g., switchToOverview) that
+ * can be performed in both of these states can be defined here.
  */
-public final class Home {
+public abstract class Home extends LauncherInstrumentation.VisibleContainer {
 
-    private final Launcher mLauncher;
-    private final UiObject2 mHotseat;
-    private final int ICON_DRAG_SPEED = 2000;
-
-    Home(Launcher launcher) {
-        mLauncher = launcher;
-        assertState();
-        mHotseat = launcher.waitForLauncherObject("hotseat");
-    }
-
-    /**
-     * Asserts that we are in home.
-     *
-     * @return Workspace.
-     */
-    @NonNull
-    private UiObject2 assertState() {
-        return mLauncher.assertState(Launcher.State.HOME);
+    protected Home(LauncherInstrumentation launcher) {
+        super(launcher);
+        verifyActiveContainer();
     }
 
     /**
@@ -57,131 +41,19 @@
      */
     @NonNull
     public Overview switchToOverview() {
-        assertState();
+        verifyActiveContainer();
         if (mLauncher.isSwipeUpEnabled()) {
             final int height = mLauncher.getDevice().getDisplayHeight();
             final UiObject2 navBar = mLauncher.getSystemUiObject("navigation_bar_frame");
 
-            // Swipe from nav bar to 2/3rd down the screen.
             mLauncher.swipe(
                     navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
-                    navBar.getVisibleBounds().centerX(), height * 2 / 3,
-                    (navBar.getVisibleBounds().centerY() - height * 2 / 3) / 100); // 100 px/step
+                    navBar.getVisibleBounds().centerX(), height - 300
+            );
         } else {
             mLauncher.getSystemUiObject("recent_apps").click();
         }
 
         return new Overview(mLauncher);
     }
-
-    /**
-     * Swipes up to All Apps.
-     *
-     * @return the App Apps object.
-     */
-    @NonNull
-    public AllAppsFromHome switchToAllApps() {
-        assertState();
-        if (mLauncher.isSwipeUpEnabled()) {
-            int midX = mLauncher.getDevice().getDisplayWidth() / 2;
-            int height = mLauncher.getDevice().getDisplayHeight();
-            // Swipe from 6/7ths down the screen to 1/7th down the screen.
-            mLauncher.swipe(
-                    midX,
-                    height * 6 / 7,
-                    midX,
-                    height / 7,
-                    (height * 2 / 3) / 100); // 100 px/step
-        } else {
-            // Swipe from the hotseat to near the top, e.g. 10% of the screen.
-            final UiObject2 hotseat = mHotseat;
-            final Point start = hotseat.getVisibleCenter();
-            final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f);
-            mLauncher.swipe(
-                    start.x,
-                    start.y,
-                    start.x,
-                    endY,
-                    (start.y - endY) / 100); // 100 px/step
-        }
-
-        return new AllAppsFromHome(mLauncher);
-    }
-
-    /**
-     * Returns an icon for the app, if currently visible.
-     *
-     * @param appName name of the app
-     * @return app icon, if found, null otherwise.
-     */
-    @Nullable
-    public AppIcon tryGetWorkspaceAppIcon(String appName) {
-        final UiObject2 workspace = assertState();
-        final UiObject2 icon = workspace.findObject(AppIcon.getAppIconSelector(appName));
-        return icon != null ? new AppIcon(mLauncher, icon) : null;
-    }
-
-    /**
-     * Ensures that workspace is scrollable. If it's not, drags an icon icons from hotseat to the
-     * second screen.
-     */
-    public void ensureWorkspaceIsScrollable() {
-        final UiObject2 workspace = assertState();
-        if (!isWorkspaceScrollable(workspace)) {
-            dragIconToNextScreen(getHotseatAppIcon("Messages"), workspace);
-        }
-        assertTrue("Home screen workspace didn't become scrollable",
-                isWorkspaceScrollable(workspace));
-    }
-
-    private boolean isWorkspaceScrollable(UiObject2 workspace) {
-        return workspace.isScrollable();
-    }
-
-    @NonNull
-    private AppIcon getHotseatAppIcon(String appName) {
-        return new AppIcon(mLauncher, mLauncher.getObjectInContainer(
-                mHotseat, AppIcon.getAppIconSelector(appName)));
-    }
-
-    private void dragIconToNextScreen(AppIcon app, UiObject2 workspace) {
-        final Point dest = new Point(
-                mLauncher.getDevice().getDisplayWidth(), workspace.getVisibleBounds().centerY());
-        app.getIcon().drag(dest, ICON_DRAG_SPEED);
-        assertState();
-    }
-
-    /**
-     * Flings to get to screens on the right. Waits for scrolling and a possible overscroll
-     * recoil to complete.
-     */
-    public void flingForward() {
-        final UiObject2 workspace = assertState();
-        workspace.fling(Direction.RIGHT);
-        mLauncher.waitForIdle();
-        assertState();
-    }
-
-    /**
-     * Flings to get to screens on the left.  Waits for scrolling and a possible overscroll
-     * recoil to complete.
-     */
-    public void flingBackward() {
-        final UiObject2 workspace = assertState();
-        workspace.fling(Direction.LEFT);
-        mLauncher.waitForIdle();
-        assertState();
-    }
-
-    /**
-     * Opens widgets container by pressing Ctrl+W.
-     *
-     * @return the widgets container.
-     */
-    @NonNull
-    public Widgets openAllWidgets() {
-        assertState();
-        mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
-        return new Widgets(mLauncher);
-    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Launcher.java b/tests/tapl/com/android/launcher3/tapl/Launcher.java
deleted file mode 100644
index 5201dc8..0000000
--- a/tests/tapl/com/android/launcher3/tapl/Launcher.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.tapl;
-
-import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
-
-import android.content.res.Resources;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
-import android.util.Log;
-
-import org.junit.Assert;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-/**
- * The main tapl object. The only object that can be explicitly constructed by the using code. It
- * produces all other objects.
- */
-public final class Launcher {
-
-    private static final String WORKSPACE_RES_ID = "workspace";
-    private static final String APPS_RES_ID = "apps_view";
-    private static final String OVERVIEW_RES_ID = "overview_panel";
-    private static final String WIDGETS_RES_ID = "widgets_list_view";
-
-    enum State {HOME, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND}
-
-    static final String LAUNCHER_PKG = "com.google.android.apps.nexuslauncher";
-    static final int APP_LAUNCH_TIMEOUT_MS = 10000;
-    private static final int UI_OBJECT_WAIT_TIMEOUT_MS = 10000;
-    private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
-            "config_swipe_up_gesture_setting_available";
-    private static final String SWIPE_UP_ENABLED_DEFAULT_RES_NAME =
-            "config_swipe_up_gesture_default";
-    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
-    private static final String TAG = "tapl.Launcher";
-    private final UiDevice mDevice;
-    private final boolean mSwipeUpEnabled;
-
-    /**
-     * Constructs the root of TAPL hierarchy. You get all other object from it.
-     */
-    public Launcher(UiDevice device) {
-        mDevice = device;
-        final boolean swipeUpEnabledDefault =
-                !getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME) ||
-                        getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
-        mSwipeUpEnabled = Settings.Secure.getInt(
-                InstrumentationRegistry.getTargetContext().getContentResolver(),
-                SWIPE_UP_SETTING_NAME,
-                swipeUpEnabledDefault ? 1 : 0) == 1;
-    }
-
-    private boolean getSystemBooleanRes(String resName) {
-        final Resources res = Resources.getSystem();
-        final int resId = res.getIdentifier(resName, "bool", "android");
-        assertTrue("Resource not found: " + resName, resId != 0);
-        return res.getBoolean(resId);
-    }
-
-    private void dumpViewHierarchy() {
-        final ByteArrayOutputStream stream = new ByteArrayOutputStream();
-        try {
-            mDevice.dumpWindowHierarchy(stream);
-            stream.flush();
-            stream.close();
-            for (String line : stream.toString().split("\\r?\\n")) {
-                Log.e(TAG, line.trim());
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "error dumping XML to logcat", e);
-        }
-    }
-
-    void fail(String message) {
-        dumpViewHierarchy();
-        Assert.fail(message);
-    }
-
-    void assertTrue(String message, boolean condition) {
-        if (!condition) {
-            fail(message);
-        }
-    }
-
-    void assertNotNull(String message, Object object) {
-        assertTrue(message, object != null);
-    }
-
-    private void failEquals(String message, Object actual) {
-        String formatted = "Values should be different. ";
-        if (message != null) {
-            formatted = message + ". ";
-        }
-
-        formatted += "Actual: " + actual;
-        fail(formatted);
-    }
-
-    void assertNotEquals(String message, int unexpected, int actual) {
-        if (unexpected == actual) {
-            failEquals(message, actual);
-        }
-    }
-
-    boolean isSwipeUpEnabled() {
-        return mSwipeUpEnabled;
-    }
-
-    UiObject2 assertState(State state) {
-        switch (state) {
-            case HOME: {
-                //waitUntilGone(APPS_RES_ID);
-                waitUntilGone(OVERVIEW_RES_ID);
-                waitUntilGone(WIDGETS_RES_ID);
-                return waitForLauncherObject(WORKSPACE_RES_ID);
-            }
-            case WIDGETS: {
-                waitUntilGone(WORKSPACE_RES_ID);
-                waitUntilGone(APPS_RES_ID);
-                waitUntilGone(OVERVIEW_RES_ID);
-                return waitForLauncherObject(WIDGETS_RES_ID);
-            }
-            case ALL_APPS: {
-                waitUntilGone(OVERVIEW_RES_ID);
-                waitUntilGone(WORKSPACE_RES_ID);
-                waitUntilGone(WIDGETS_RES_ID);
-                return waitForLauncherObject(APPS_RES_ID);
-            }
-            case OVERVIEW: {
-                //waitForLauncherObject(APPS_RES_ID);
-                waitUntilGone(WORKSPACE_RES_ID);
-                waitUntilGone(WIDGETS_RES_ID);
-                return waitForLauncherObject(OVERVIEW_RES_ID);
-            }
-            case BACKGROUND: {
-                waitUntilGone(WORKSPACE_RES_ID);
-                waitUntilGone(APPS_RES_ID);
-                waitUntilGone(OVERVIEW_RES_ID);
-                waitUntilGone(WIDGETS_RES_ID);
-                return null;
-            }
-            default:
-                fail("Invalid state: " + state);
-                return null;
-        }
-    }
-
-    /**
-     * Presses nav bar home button.
-     *
-     * @return the Home object.
-     */
-    public Home pressHome() {
-        getSystemUiObject("home").click();
-        return getHome();
-    }
-
-    /**
-     * Gets the Home object if the current state is "active home", i.e. workspace. Fails if the
-     * launcher is not in that state.
-     *
-     * @return Home object.
-     */
-    @NonNull
-    public Home getHome() {
-        return new Home(this);
-    }
-
-    /**
-     * Gets the Widgets object if the current state is showing all widgets. Fails if the launcher is
-     * not in that state.
-     *
-     * @return Widgets object.
-     */
-    @NonNull
-    public Widgets getAllWidgets() {
-        return new Widgets(this);
-    }
-
-    /**
-     * Gets the Overview object if the current state is showing the overview panel. Fails if the
-     * launcher is not in that state.
-     *
-     * @return Overview object.
-     */
-    @NonNull
-    public Overview getOverview() {
-        return new Overview(this);
-    }
-
-    /**
-     * Gets the All Apps object if the current state is showing the all apps panel. Fails if the
-     * launcher is not in that state.
-     *
-     * @return All Aps object.
-     */
-    @NonNull
-    public AllAppsFromHome getAllApps() {
-        return new AllAppsFromHome(this);
-    }
-
-    /**
-     * Gets the All Apps object if the current state is showing the all apps panel. Returns null if
-     * the launcher is not in that state.
-     *
-     * @return All Aps object or null.
-     */
-    @Nullable
-    public AllAppsFromHome tryGetAllApps() {
-        return tryGetLauncherObject(APPS_RES_ID) != null ? getAllApps() : null;
-    }
-
-    private void waitUntilGone(String resId) {
-//        assertTrue("Unexpected launcher object visible: " + resId,
-//                mDevice.wait(Until.gone(getLauncherObjectSelector(resId)),
-//                        UI_OBJECT_WAIT_TIMEOUT_MS));
-    }
-
-    @NonNull
-    UiObject2 getSystemUiObject(String resId) {
-        try {
-            mDevice.wakeUp();
-        } catch (RemoteException e) {
-            fail("Failed to wake up the device: " + e);
-        }
-        final UiObject2 object = mDevice.findObject(By.res(SYSTEMUI_PACKAGE, resId));
-        assertNotNull("Can't find a systemui object with id: " + resId, object);
-        return object;
-    }
-
-    @NonNull
-    UiObject2 getObjectInContainer(UiObject2 container, BySelector selector) {
-        final UiObject2 object = container.findObject(selector);
-        assertNotNull("Can't find an object with selector: " + selector, object);
-        return object;
-    }
-
-    @Nullable
-    private UiObject2 tryGetLauncherObject(String resName) {
-        return mDevice.findObject(getLauncherObjectSelector(resName));
-    }
-
-    @NonNull
-    UiObject2 waitForObjectInContainer(UiObject2 container, String resName) {
-        final UiObject2 object = container.wait(
-                Until.findObject(getLauncherObjectSelector(resName)),
-                UI_OBJECT_WAIT_TIMEOUT_MS);
-        assertNotNull("Can find a launcher object id: " + resName + " in container: " +
-                container.getResourceName(), object);
-        return object;
-    }
-
-    @NonNull
-    UiObject2 waitForLauncherObject(String resName) {
-        final UiObject2 object = mDevice.wait(Until.findObject(getLauncherObjectSelector(resName)),
-                UI_OBJECT_WAIT_TIMEOUT_MS);
-        assertNotNull("Can find a launcher object; id: " + resName, object);
-        return object;
-    }
-
-    static BySelector getLauncherObjectSelector(String resName) {
-        return By.res(LAUNCHER_PKG, resName);
-    }
-
-    @NonNull
-    UiDevice getDevice() {
-        return mDevice;
-    }
-
-    void swipe(int startX, int startY, int endX, int endY, int steps) {
-        mDevice.swipe(startX, startY, endX, endY, steps);
-        waitForIdle();
-    }
-
-    void waitForIdle() {
-        mDevice.waitForIdle();
-    }
-}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
new file mode 100644
index 0000000..c3f27ee
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.app.UiAutomation;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.quickstep.SwipeUpSetting;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * The main tapl object. The only object that can be explicitly constructed by the using code. It
+ * produces all other objects.
+ */
+public final class LauncherInstrumentation {
+
+    // Types for launcher containers that the user is interacting with. "Background" is a
+    // pseudo-container corresponding to inactive launcher covered by another app.
+    enum ContainerType {
+        WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND
+    }
+
+    // Base class for launcher containers.
+    static abstract class VisibleContainer {
+        protected final LauncherInstrumentation mLauncher;
+
+        protected VisibleContainer(LauncherInstrumentation launcher) {
+            mLauncher = launcher;
+            launcher.setActiveContainer(this);
+        }
+
+        protected abstract ContainerType getContainerType();
+
+        /**
+         * Asserts that the launcher is in the mode matching 'this' object.
+         *
+         * @return UI object for the container.
+         */
+        final UiObject2 verifyActiveContainer() {
+            assertTrue("Attempt to use a stale container", this == sActiveContainer.get());
+            return mLauncher.verifyContainerType(getContainerType());
+        }
+    }
+
+    private static final String WORKSPACE_RES_ID = "workspace";
+    private static final String APPS_RES_ID = "apps_view";
+    private static final String OVERVIEW_RES_ID = "overview_panel";
+    private static final String WIDGETS_RES_ID = "widgets_list_view";
+    static final String LAUNCHER_PKG = "com.google.android.apps.nexuslauncher";
+    static final int WAIT_TIME_MS = 60000;
+    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+
+    private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
+
+    private final UiDevice mDevice;
+    private final boolean mSwipeUpEnabled;
+
+    /**
+     * Constructs the root of TAPL hierarchy. You get all other objects from it.
+     */
+    public LauncherInstrumentation(UiDevice device) {
+        mDevice = device;
+        final boolean swipeUpEnabledDefault =
+                !SwipeUpSetting.isSwipeUpSettingAvailable() ||
+                        SwipeUpSetting.isSwipeUpEnabledDefaultValue();
+        mSwipeUpEnabled = Settings.Secure.getInt(
+                InstrumentationRegistry.getTargetContext().getContentResolver(),
+                "swipe_up_to_switch_apps_enabled",
+                swipeUpEnabledDefault ? 1 : 0) == 1;
+        assertTrue("Device must run in a test harness", ActivityManager.isRunningInTestHarness());
+    }
+
+    void setActiveContainer(VisibleContainer container) {
+        sActiveContainer = new WeakReference<>(container);
+    }
+
+    boolean isSwipeUpEnabled() {
+        return mSwipeUpEnabled;
+    }
+
+    private UiObject2 verifyContainerType(ContainerType containerType) {
+        switch (containerType) {
+            case WORKSPACE: {
+                waitUntilGone(APPS_RES_ID);
+                waitUntilGone(OVERVIEW_RES_ID);
+                waitUntilGone(WIDGETS_RES_ID);
+                return waitForLauncherObject(WORKSPACE_RES_ID);
+            }
+            case WIDGETS: {
+                waitUntilGone(WORKSPACE_RES_ID);
+                waitUntilGone(APPS_RES_ID);
+                waitUntilGone(OVERVIEW_RES_ID);
+                return waitForLauncherObject(WIDGETS_RES_ID);
+            }
+            case ALL_APPS: {
+                waitUntilGone(WORKSPACE_RES_ID);
+                waitUntilGone(OVERVIEW_RES_ID);
+                waitUntilGone(WIDGETS_RES_ID);
+                return waitForLauncherObject(APPS_RES_ID);
+            }
+            case OVERVIEW: {
+                waitForLauncherObject(APPS_RES_ID);
+                waitUntilGone(WORKSPACE_RES_ID);
+                waitUntilGone(WIDGETS_RES_ID);
+                return waitForLauncherObject(OVERVIEW_RES_ID);
+            }
+            case BACKGROUND: {
+                waitUntilGone(WORKSPACE_RES_ID);
+                waitUntilGone(APPS_RES_ID);
+                waitUntilGone(OVERVIEW_RES_ID);
+                waitUntilGone(WIDGETS_RES_ID);
+                return null;
+            }
+            default:
+                fail("Invalid state: " + containerType);
+                return null;
+        }
+    }
+
+    private Bundle executeAndWaitForEvent(Runnable command,
+            UiAutomation.AccessibilityEventFilter eventFilter, String message) {
+        try {
+            final AccessibilityEvent event =
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                            .executeAndWaitForEvent(
+                                    command, eventFilter, WAIT_TIME_MS);
+            assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
+            return (Bundle) event.getParcelableData();
+        } catch (TimeoutException e) {
+            fail(message);
+            return null;
+        }
+    }
+
+    Bundle getAnswerFromLauncher(UiObject2 view, String requestTag) {
+        // Send a fake set-text request to Launcher to initiate a response with requested data.
+        final String responseTag = requestTag + "_RESPONSE";
+        return executeAndWaitForEvent(
+                () -> view.setText(requestTag),
+                event -> responseTag.equals(event.getClassName()),
+                "Launcher didn't respond to request: " + requestTag);
+    }
+
+    /**
+     * Presses nav bar home button.
+     *
+     * @return the Workspace object.
+     */
+    public Workspace pressHome() {
+        // Click home, then wait for any accessibility event, then wait until accessibility events
+        // stop.
+        // We need waiting for any accessibility event generated after pressing Home because
+        // otherwise waitForIdle may return immediately in case when there was a big enough pause in
+        // accessibility events prior to pressing Home.
+        executeAndWaitForEvent(
+                () -> getSystemUiObject("home").click(),
+                event -> true,
+                "Pressing Home didn't produce any events");
+        mDevice.waitForIdle();
+        return getWorkspace();
+    }
+
+    /**
+     * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the
+     * launcher is not in that state.
+     *
+     * @return Workspace object.
+     */
+    @NonNull
+    public Workspace getWorkspace() {
+        return new Workspace(this);
+    }
+
+    /**
+     * Gets the Workspace object if the current state is "background home", i.e. some other app is
+     * active. Fails if the launcher is not in that state.
+     *
+     * @return Background object.
+     */
+    @NonNull
+    public Background getBackground() {
+        return new Background(this);
+    }
+
+    /**
+     * Gets the Widgets object if the current state is showing all widgets. Fails if the launcher is
+     * not in that state.
+     *
+     * @return Widgets object.
+     */
+    @NonNull
+    public Widgets getAllWidgets() {
+        return new Widgets(this);
+    }
+
+    /**
+     * Gets the Overview object if the current state is showing the overview panel. Fails if the
+     * launcher is not in that state.
+     *
+     * @return Overview object.
+     */
+    @NonNull
+    public Overview getOverview() {
+        return new Overview(this);
+    }
+
+    /**
+     * Gets the All Apps object if the current state is showing the all apps panel opened by swiping
+     * from workspace. Fails if the launcher is not in that state. Please don't call this method if
+     * App Apps was opened by swiping up from Overview, as it won't fail and will return an
+     * incorrect object.
+     *
+     * @return All Aps object.
+     */
+    @NonNull
+    public AllApps getAllApps() {
+        return new AllApps(this);
+    }
+
+    /**
+     * Gets the All Apps object if the current state is showing the all apps panel opened by swiping
+     * from overview. Fails if the launcher is not in that state. Please don't call this method if
+     * App Apps was opened by swiping up from home, as it won't fail and will return an
+     * incorrect object.
+     *
+     * @return All Aps object.
+     */
+    @NonNull
+    public AllAppsFromOverview getAllAppsFromOverview() {
+        return new AllAppsFromOverview(this);
+    }
+
+    /**
+     * Gets the All Apps object if the current state is showing the all apps panel opened by swiping
+     * from workspace. Returns null if launcher is not in that state. Please don't call this method
+     * if App Apps was opened by swiping up from Overview, as it won't fail and will return an
+     * incorrect object.
+     *
+     * @return All Aps object or null.
+     */
+    @Nullable
+    public AllApps tryGetAllApps() {
+        return tryGetLauncherObject(APPS_RES_ID) != null ? getAllApps() : null;
+    }
+
+    private void waitUntilGone(String resId) {
+        assertTrue("Unexpected launcher object visible: " + resId,
+                mDevice.wait(Until.gone(getLauncherObjectSelector(resId)),
+                        WAIT_TIME_MS));
+    }
+
+    @NonNull
+    UiObject2 getSystemUiObject(String resId) {
+        final UiObject2 object = mDevice.findObject(By.res(SYSTEMUI_PACKAGE, resId));
+        assertNotNull("Can't find a systemui object with id: " + resId, object);
+        return object;
+    }
+
+    @NonNull
+    UiObject2 getObjectInContainer(UiObject2 container, BySelector selector) {
+        final UiObject2 object = container.findObject(selector);
+        assertNotNull("Can't find an object with selector: " + selector, object);
+        return object;
+    }
+
+    @Nullable
+    private UiObject2 tryGetLauncherObject(String resName) {
+        return mDevice.findObject(getLauncherObjectSelector(resName));
+    }
+
+    @NonNull
+    UiObject2 waitForObjectInContainer(UiObject2 container, String resName) {
+        final UiObject2 object = container.wait(
+                Until.findObject(getLauncherObjectSelector(resName)),
+                WAIT_TIME_MS);
+        assertNotNull("Can't find a launcher object id: " + resName + " in container: " +
+                container.getResourceName(), object);
+        return object;
+    }
+
+    @NonNull
+    UiObject2 waitForLauncherObject(String resName) {
+        final UiObject2 object = mDevice.wait(Until.findObject(getLauncherObjectSelector(resName)),
+                WAIT_TIME_MS);
+        assertNotNull("Can't find a launcher object; id: " + resName, object);
+        return object;
+    }
+
+    static BySelector getLauncherObjectSelector(String resName) {
+        return By.res(LAUNCHER_PKG, resName);
+    }
+
+    @NonNull
+    UiDevice getDevice() {
+        return mDevice;
+    }
+
+    void swipe(int startX, int startY, int endX, int endY) {
+        executeAndWaitForEvent(
+                () -> mDevice.swipe(startX, startY, endX, endY, 60),
+                event -> "TAPL_WENT_TO_STATE".equals(event.getClassName()),
+                "Swipe failed to receive an event for the swipe end: " + startX + ", " + startY
+                        + ", " + endX + ", " + endY);
+    }
+
+    void waitForIdle() {
+        mDevice.waitForIdle();
+    }
+}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java
index 2251655..3d504c4 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.tapl;
 
+import static org.junit.Assert.assertNotEquals;
+
 import android.graphics.Point;
 import android.support.annotation.NonNull;
 import android.support.test.uiautomator.Direction;
@@ -27,44 +29,37 @@
 /**
  * Overview pane.
  */
-public final class Overview {
+public final class Overview extends LauncherInstrumentation.VisibleContainer {
     private static final int DEFAULT_FLING_SPEED = 15000;
 
-    private final Launcher mLauncher;
-
-    Overview(Launcher launcher) {
-        mLauncher = launcher;
-        assertState();
+    Overview(LauncherInstrumentation launcher) {
+        super(launcher);
+        verifyActiveContainer();
     }
 
-    /**
-     * Asserts that we are in overview.
-     *
-     * @return Overview panel.
-     */
-    @NonNull
-    private UiObject2 assertState() {
-        return mLauncher.assertState(Launcher.State.OVERVIEW);
+    @Override
+    protected LauncherInstrumentation.ContainerType getContainerType() {
+        return LauncherInstrumentation.ContainerType.OVERVIEW;
     }
 
     /**
      * Flings forward (left) and waits the fling's end.
      */
     public void flingForward() {
-        final UiObject2 overview = assertState();
+        final UiObject2 overview = verifyActiveContainer();
         overview.fling(Direction.LEFT, DEFAULT_FLING_SPEED);
         mLauncher.waitForIdle();
-        assertState();
+        verifyActiveContainer();
     }
 
     /**
      * Flings backward (right) and waits the fling's end.
      */
     public void flingBackward() {
-        final UiObject2 overview = assertState();
+        final UiObject2 overview = verifyActiveContainer();
         overview.fling(Direction.RIGHT, DEFAULT_FLING_SPEED);
         mLauncher.waitForIdle();
-        assertState();
+        verifyActiveContainer();
     }
 
     /**
@@ -74,10 +69,10 @@
      */
     @NonNull
     public OverviewTask getCurrentTask() {
-        assertState();
+        verifyActiveContainer();
         final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
-                Launcher.getLauncherObjectSelector("snapshot"));
-        mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
+                LauncherInstrumentation.getLauncherObjectSelector("snapshot"));
+        assertNotEquals("Unable to find a task", 0, taskViews.size());
 
         // taskViews contains up to 3 task views: the 'main' (having the widest visible
         // part) one in the center, and parts of its right and left siblings. Find the
@@ -86,7 +81,7 @@
                 (t1, t2) -> Integer.compare(t1.getVisibleBounds().width(),
                         t2.getVisibleBounds().width()));
 
-        return new OverviewTask(mLauncher, widestTask);
+        return new OverviewTask(mLauncher, widestTask, this);
     }
 
     /**
@@ -96,7 +91,7 @@
      */
     @NonNull
     public AllAppsFromOverview switchToAllApps() {
-        assertState();
+        verifyActiveContainer();
 
         // Swipe from the hotseat to near the top, e.g. 10% of the screen.
         final UiObject2 predictionRow = mLauncher.waitForLauncherObject(
@@ -104,7 +99,7 @@
         final Point start = predictionRow.getVisibleCenter();
         final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f);
         mLauncher.swipe(
-                start.x, start.y, start.x, endY, (start.y - endY) / 100); // 100 px/step
+                start.x, start.y, start.x, endY);
 
         return new AllAppsFromOverview(mLauncher);
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 68d3082..0b3a264 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.tapl;
 
+import static org.junit.Assert.assertTrue;
+
 import android.support.test.uiautomator.Direction;
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
@@ -24,29 +26,26 @@
  * A recent task in the overview panel carousel.
  */
 public final class OverviewTask {
-    private final Launcher mLauncher;
+    private final LauncherInstrumentation mLauncher;
     private final UiObject2 mTask;
+    private final Overview mOverview;
 
-    OverviewTask(Launcher launcher, UiObject2 task) {
+    OverviewTask(LauncherInstrumentation launcher, UiObject2 task, Overview overview) {
         mLauncher = launcher;
-        assertState();
         mTask = task;
+        mOverview = overview;
+        verifyActiveContainer();
     }
 
-    /**
-     * Asserts that we are in overview.
-     *
-     * @return Overview panel.
-     */
-    private void assertState() {
-        mLauncher.assertState(Launcher.State.OVERVIEW);
+    private void verifyActiveContainer() {
+        mOverview.verifyActiveContainer();
     }
 
     /**
      * Swipes the task up.
      */
     public void dismiss() {
-        assertState();
+        verifyActiveContainer();
         // Dismiss the task via flinging it up.
         mTask.fling(Direction.DOWN);
         mLauncher.waitForIdle();
@@ -55,11 +54,11 @@
     /**
      * Clicks at the task.
      */
-    public void open() {
-        assertState();
-        mLauncher.assertTrue("Launching task didn't open a new window: " +
+    public Background open() {
+        verifyActiveContainer();
+        assertTrue("Launching task didn't open a new window: " +
                         mTask.getParent().getContentDescription(),
-                mTask.clickAndWait(Until.newWindow(), Launcher.APP_LAUNCH_TIMEOUT_MS));
-        mLauncher.assertState(Launcher.State.BACKGROUND);
+                mTask.clickAndWait(Until.newWindow(), LauncherInstrumentation.WAIT_TIME_MS));
+        return new Background(mLauncher);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 7a5198a..f67ef4c 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -16,50 +16,43 @@
 
 package com.android.launcher3.tapl;
 
-import android.support.annotation.NonNull;
 import android.support.test.uiautomator.Direction;
 import android.support.test.uiautomator.UiObject2;
 
 /**
  * All widgets container.
  */
-public final class Widgets {
+public final class Widgets extends LauncherInstrumentation.VisibleContainer {
     private static final int FLING_SPEED = 12000;
 
-    private final Launcher mLauncher;
-
-    Widgets(Launcher launcher) {
-        mLauncher = launcher;
-        assertState();
+    Widgets(LauncherInstrumentation launcher) {
+        super(launcher);
+        verifyActiveContainer();
     }
 
     /**
      * Flings forward (down) and waits the fling's end.
      */
     public void flingForward() {
-        final UiObject2 widgetsContainer = assertState();
+        final UiObject2 widgetsContainer = verifyActiveContainer();
+        widgetsContainer.setGestureMargin(100);
         widgetsContainer.fling(Direction.DOWN, FLING_SPEED);
-        mLauncher.waitForIdle();
-        assertState();
+        verifyActiveContainer();
     }
 
     /**
      * Flings backward (up) and waits the fling's end.
      */
     public void flingBackward() {
-        final UiObject2 widgetsContainer = assertState();
+        final UiObject2 widgetsContainer = verifyActiveContainer();
+        widgetsContainer.setGestureMargin(100);
         widgetsContainer.fling(Direction.UP, FLING_SPEED);
         mLauncher.waitForIdle();
-        assertState();
+        verifyActiveContainer();
     }
 
-    /**
-     * Asserts that we are in widgets.
-     *
-     * @return Widgets container.
-     */
-    @NonNull
-    private UiObject2 assertState() {
-        return mLauncher.assertState(Launcher.State.WIDGETS);
+    @Override
+    protected LauncherInstrumentation.ContainerType getContainerType() {
+        return LauncherInstrumentation.ContainerType.WIDGETS;
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
new file mode 100644
index 0000000..045ab5f
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import static junit.framework.TestCase.assertTrue;
+
+import android.graphics.Point;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+import android.view.KeyEvent;
+
+/**
+ * Operations on the workspace screen.
+ */
+public final class Workspace extends Home {
+    private final UiObject2 mHotseat;
+    private final int ICON_DRAG_SPEED = 2000;
+
+    Workspace(LauncherInstrumentation launcher) {
+        super(launcher);
+        mHotseat = launcher.waitForLauncherObject("hotseat");
+    }
+
+    @Override
+    protected LauncherInstrumentation.ContainerType getContainerType() {
+        return LauncherInstrumentation.ContainerType.WORKSPACE;
+    }
+
+    /**
+     * Swipes up to All Apps.
+     *
+     * @return the App Apps object.
+     */
+    @NonNull
+    public AllApps switchToAllApps() {
+        verifyActiveContainer();
+        if (mLauncher.isSwipeUpEnabled()) {
+            int midX = mLauncher.getDevice().getDisplayWidth() / 2;
+            int height = mLauncher.getDevice().getDisplayHeight();
+            // Swipe from 6/7ths down the screen to 1/7th down the screen.
+            mLauncher.swipe(
+                    midX,
+                    height * 6 / 7,
+                    midX,
+                    height / 7
+            );
+        } else {
+            // Swipe from the hotseat to near the top, e.g. 10% of the screen.
+            final UiObject2 hotseat = mHotseat;
+            final Point start = hotseat.getVisibleCenter();
+            final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f);
+            mLauncher.swipe(
+                    start.x,
+                    start.y,
+                    start.x,
+                    endY
+            );
+        }
+
+        return new AllApps(mLauncher);
+    }
+
+    /**
+     * Returns an icon for the app, if currently visible.
+     *
+     * @param appName name of the app
+     * @return app icon, if found, null otherwise.
+     */
+    @Nullable
+    public AppIcon tryGetWorkspaceAppIcon(String appName) {
+        final UiObject2 workspace = verifyActiveContainer();
+        final UiObject2 icon = workspace.findObject(AppIcon.getAppIconSelector(appName));
+        return icon != null ? new AppIcon(mLauncher, icon) : null;
+    }
+
+    /**
+     * Ensures that workspace is scrollable. If it's not, drags an icon icons from hotseat to the
+     * second screen.
+     */
+    public void ensureWorkspaceIsScrollable() {
+        final UiObject2 workspace = verifyActiveContainer();
+        if (!isWorkspaceScrollable(workspace)) {
+            dragIconToNextScreen(getHotseatAppIcon("Messages"), workspace);
+        }
+        assertTrue("Home screen workspace didn't become scrollable",
+                isWorkspaceScrollable(workspace));
+    }
+
+    private boolean isWorkspaceScrollable(UiObject2 workspace) {
+        return workspace.isScrollable();
+    }
+
+    @NonNull
+    private AppIcon getHotseatAppIcon(String appName) {
+        return new AppIcon(mLauncher, mLauncher.getObjectInContainer(
+                mHotseat, AppIcon.getAppIconSelector(appName)));
+    }
+
+    private void dragIconToNextScreen(AppIcon app, UiObject2 workspace) {
+        final Point dest = new Point(
+                mLauncher.getDevice().getDisplayWidth(), workspace.getVisibleBounds().centerY());
+        app.getIcon().drag(dest, ICON_DRAG_SPEED);
+        verifyActiveContainer();
+    }
+
+    /**
+     * Flings to get to screens on the right. Waits for scrolling and a possible overscroll
+     * recoil to complete.
+     */
+    public void flingForward() {
+        final UiObject2 workspace = verifyActiveContainer();
+        workspace.fling(Direction.RIGHT);
+        mLauncher.waitForIdle();
+        verifyActiveContainer();
+    }
+
+    /**
+     * Flings to get to screens on the left.  Waits for scrolling and a possible overscroll
+     * recoil to complete.
+     */
+    public void flingBackward() {
+        final UiObject2 workspace = verifyActiveContainer();
+        workspace.fling(Direction.LEFT);
+        mLauncher.waitForIdle();
+        verifyActiveContainer();
+    }
+
+    /**
+     * Opens widgets container by pressing Ctrl+W.
+     *
+     * @return the widgets container.
+     */
+    @NonNull
+    public Widgets openAllWidgets() {
+        verifyActiveContainer();
+        mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
+        return new Widgets(mLauncher);
+    }
+}
\ No newline at end of file