Merge "Prevent the work mode switch thumb from being dragged." into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 3945746..ca53fa7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -37,9 +37,8 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
-    android-support-v4 \
-    android-support-v7-recyclerview \
-    android-support-dynamic-animation
+    androidx.recyclerview_recyclerview \
+    androidx.dynamicanimation_dynamicanimation
 
 LOCAL_SRC_FILES := \
     $(call all-proto-files-under, protos) \
@@ -193,7 +192,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/build.gradle b/build.gradle
index 4ae6600..dcb5b81 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,12 +4,12 @@
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.2.0-alpha12'
-        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.3'
+        classpath 'com.android.tools.build:gradle:3.2.0-beta05'
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6'
     }
 }
 
-final String SUPPORT_LIBS_VERSION = '28.0.0-SNAPSHOT'
+final String SUPPORT_LIBS_VERSION = '1.0.0-alpha1'
 
 apply plugin: 'com.android.application'
 apply plugin: 'com.google.protobuf'
@@ -57,6 +57,8 @@
             dimension "default"
             applicationId 'com.android.launcher3'
             testApplicationId 'com.android.launcher3.tests'
+
+            minSdkVersion 28
         }
     }
 
@@ -84,7 +86,7 @@
 
         androidTest {
             res.srcDirs = ['tests/res']
-            java.srcDirs = ['tests/src']
+            java.srcDirs = ['tests/src', 'tests/tapl']
             manifest.srcFile "tests/AndroidManifest-common.xml"
         }
 
@@ -118,9 +120,8 @@
 }
 
 dependencies {
-    implementation "com.android.support:support-v4:${SUPPORT_LIBS_VERSION}"
-    implementation "com.android.support:support-dynamic-animation:${SUPPORT_LIBS_VERSION}"
-    implementation "com.android.support:recyclerview-v7:${SUPPORT_LIBS_VERSION}"
+    implementation "androidx.dynamicanimation:dynamicanimation:${SUPPORT_LIBS_VERSION}"
+    implementation "androidx.recyclerview:recyclerview:${SUPPORT_LIBS_VERSION}"
     implementation 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7'
 
     quickstepImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
@@ -132,7 +133,7 @@
     androidTestImplementation 'com.android.support.test:runner:1.0.0'
     androidTestImplementation 'com.android.support.test:rules:1.0.0'
     androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
-    androidTestImplementation "com.android.support:support-annotations:${SUPPORT_LIBS_VERSION}"
+    androidTestImplementation "androidx.annotation:annotation:${SUPPORT_LIBS_VERSION}"
 }
 
 protobuf {
diff --git a/proguard.flags b/proguard.flags
index ddae07e..a312b91 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -95,7 +95,7 @@
 # next row when focus is on the last item of last row when using a RecyclerView
 # Keep optimized and shrunk proguard to prevent issues like this when using
 # support jar.
--keep class android.support.v7.widget.RecyclerView { *; }
+-keep class androidx.recyclerview.widget.RecyclerView { *; }
 
 # Preference fragments
 -keep class ** extends android.preference.PreferenceFragment {
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/res/layout/task_menu.xml b/quickstep/res/layout/task_menu.xml
index bf55ece..098b34f 100644
--- a/quickstep/res/layout/task_menu.xml
+++ b/quickstep/res/layout/task_menu.xml
@@ -24,11 +24,19 @@
     android:orientation="vertical"
     android:visibility="invisible">
 
+    <com.android.quickstep.views.IconView
+      android:id="@+id/task_icon"
+      android:layout_width="@dimen/task_thumbnail_icon_size"
+      android:layout_height="@dimen/task_thumbnail_icon_size"
+      android:layout_gravity="top|center_horizontal"
+      android:layout_marginBottom="@dimen/deep_shortcut_drawable_padding"
+      android:focusable="false"
+      android:importantForAccessibility="no" />
+
     <TextView
-        android:id="@+id/task_icon_and_name"
+        android:id="@+id/task_name"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
         android:gravity="center_horizontal"
         android:layout_marginBottom="16dp"
         android:textSize="12sp"/>
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index e346310..78f6ffa 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -17,7 +17,8 @@
 
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
+import static com.android.systemui.shared.recents.utilities.Utilities
+        .postAtFrontOfQueueAsynchronously;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -25,12 +26,13 @@
 import android.annotation.TargetApi;
 import android.os.Build;
 import android.os.Handler;
-import android.support.annotation.BinderThread;
-import android.support.annotation.UiThread;
 
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
+import androidx.annotation.BinderThread;
+import androidx.annotation.UiThread;
+
 @TargetApi(Build.VERSION_CODES.P)
 public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
 
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 ac9f863..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,19 +159,29 @@
     }
 
     public static void onStart(Context context) {
-        RecentsModel model = RecentsModel.getInstance(context);
+        RecentsModel model = RecentsModel.INSTANCE.get(context);
         if (model != null) {
             model.onStart();
         }
     }
 
+    public static void onEnterAnimationComplete(Context context) {
+        // 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.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);
@@ -179,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 d37ac49..322e270 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -42,8 +42,6 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
-import android.support.annotation.Nullable;
-import android.support.annotation.UiThread;
 import android.view.View;
 
 import com.android.launcher3.BaseDraggingActivity;
@@ -53,10 +51,12 @@
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.TestProtocol;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 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;
@@ -76,6 +76,9 @@
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
 /**
  * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
  */
@@ -230,6 +233,9 @@
 
                 // Optimization, hide the all apps view to prevent layout while initializing
                 activity.getAppsView().getContentView().setVisibility(View.GONE);
+
+                AccessibilityManagerCompat.sendEventToTest(
+                        activity, TestProtocol.SWITCHED_TO_STATE_MESSAGE);
             }
 
             return new AnimationFactory() {
@@ -299,6 +305,9 @@
         private void playScaleDownAnim(AnimatorSet anim, Launcher launcher) {
             RecentsView recentsView = launcher.getOverviewPanel();
             TaskView v = recentsView.getTaskViewAt(recentsView.getCurrentPage());
+            if (v == null) {
+                return;
+            }
             ClipAnimationHelper clipHelper = new ClipAnimationHelper();
             clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null);
             if (!clipHelper.getSourceRect().isEmpty() && !clipHelper.getTargetRect().isEmpty()) {
diff --git a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
index f875bb7..a4a2e56 100644
--- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
+++ b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
@@ -42,12 +42,15 @@
 
     private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
     private final DrawableFactory mDrawableFactory;
+    private final boolean mDisableColorExtraction;
     private LauncherIcons mLauncherIcons;
 
     public NormalizedIconLoader(Context context, TaskKeyLruCache<Drawable> iconCache,
-            LruCache<ComponentName, ActivityInfo> activityInfoCache) {
+            LruCache<ComponentName, ActivityInfo> activityInfoCache,
+            boolean disableColorExtraction) {
         super(context, iconCache, activityInfoCache);
         mDrawableFactory = DrawableFactory.get(context);
+        mDisableColorExtraction = disableColorExtraction;
     }
 
     @Override
@@ -74,6 +77,9 @@
             int primaryColor, boolean isInstantApp) {
         if (mLauncherIcons == null) {
             mLauncherIcons = LauncherIcons.obtain(mContext);
+            if (mDisableColorExtraction) {
+                mLauncherIcons.disableColorExtraction();
+            }
         }
 
         mLauncherIcons.setWrapperBackgroundColor(primaryColor);
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..d71c08a 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -22,23 +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;
+import androidx.annotation.WorkerThread;
 
 /**
  * Sets overview interaction flags, such as:
@@ -54,29 +51,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 +66,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 +84,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,16 +177,29 @@
         }
     }
 
+    @WorkerThread
+    public void setSwipeGestureInitializing(boolean swipeGestureInitializing) {
+        mSwipeGestureInitializing = swipeGestureInitializing;
+    }
+
+    public boolean swipeGestureInitializing() {
+        return mSwipeGestureInitializing;
+    }
+
+    public void notifySwipeUpSettingChanged(boolean swipeUpEnabled) {
+        mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
+        mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, swipeUpEnabled ? 1 : 0, 0).
+                sendToTarget();
+    }
+
     private class SwipeUpGestureEnabledSettingObserver extends ContentObserver {
-        private Handler mHandler;
         private ContentResolver mResolver;
         private final int defaultValue;
 
         SwipeUpGestureEnabledSettingObserver(Handler handler, ContentResolver resolver) {
             super(handler);
-            mHandler = handler;
             mResolver = resolver;
-            defaultValue = getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME) ? 1 : 0;
+            defaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue() ? 1 : 0;
         }
 
         public void register() {
@@ -219,8 +212,7 @@
         @Override
         public void onChange(boolean selfChange) {
             super.onChange(selfChange);
-            mHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
-            mHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, getValue() ? 1 : 0, 0).sendToTarget();
+            notifySwipeUpSettingChanged(getValue());
         }
 
         private boolean getValue() {
@@ -228,18 +220,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 0c8e47f..196f0cc 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -27,10 +27,8 @@
 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;
 import android.util.Log;
 import android.util.LruCache;
 import android.util.SparseArray;
@@ -38,6 +36,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,32 +49,18 @@
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.ArrayList;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
+import androidx.annotation.WorkerThread;
+
 /**
  * Singleton class to load and manage recents model.
  */
 @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<>();
@@ -110,7 +95,9 @@
             protected IconLoader createNewIconLoader(Context context,
                     TaskKeyLruCache<Drawable> iconCache,
                     LruCache<ComponentName, ActivityInfo> activityInfoCache) {
-                return new NormalizedIconLoader(context, iconCache, activityInfoCache);
+                // Disable finding the dominant color since we don't need to use it
+                return new NormalizedIconLoader(context, iconCache, activityInfoCache,
+                        true /* disableColorExtraction */);
             }
         };
         mRecentsTaskLoader.startLoader(mContext);
@@ -232,7 +219,6 @@
 
     public void onStart() {
         mRecentsTaskLoader.startLoader(mContext);
-        mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(true);
     }
 
     public void onTrimMemory(int level) {
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/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index c272b1a..7289516 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.graphics.Matrix;
-import android.support.annotation.AnyThread;
 import android.view.View;
 
 import com.android.launcher3.R;
@@ -27,6 +26,8 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
+import androidx.annotation.AnyThread;
+
 /**
  * Factory class to create and add an overlays on the TaskView
  */
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index d9da002..e64d04a 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -30,7 +30,6 @@
 import android.util.Log;
 import android.view.View;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ItemInfo;
@@ -161,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) {
@@ -226,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;
             }
@@ -270,9 +269,4 @@
             return null;
         }
     }
-
-    private static void dismissTaskMenuView(BaseDraggingActivity activity) {
-        AbstractFloatingView.closeOpenViews(activity, true,
-                AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
-    }
 }
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/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index 42e9aee..646fc57 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -17,7 +17,6 @@
 
 import android.annotation.TargetApi;
 import android.os.Build;
-import android.support.annotation.IntDef;
 import android.view.Choreographer;
 import android.view.MotionEvent;
 
@@ -25,6 +24,8 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Consumer;
 
+import androidx.annotation.IntDef;
+
 @TargetApi(Build.VERSION_CODES.O)
 @FunctionalInterface
 public interface TouchConsumer extends Consumer<MotionEvent> {
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 d3d38af..66655bd 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -43,9 +43,6 @@
 import android.os.Looper;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.support.annotation.AnyThread;
-import android.support.annotation.UiThread;
-import android.support.annotation.WorkerThread;
 import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
@@ -86,11 +83,14 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier;
 import com.android.systemui.shared.system.WindowCallbacksCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.util.StringJoiner;
 import java.util.function.BiFunction;
 
+import androidx.annotation.AnyThread;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
 @TargetApi(Build.VERSION_CODES.O)
 public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
@@ -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) {
@@ -477,6 +486,12 @@
         if (LatencyTrackerCompat.isEnabled(mContext)) {
             LatencyTrackerCompat.logToggleRecents((int) (mLauncherFrameDrawnTime - mTouchTimeMs));
         }
+
+        // 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.INSTANCE.get(mContext).getRecentsTaskLoader()
+                .getHighResThumbnailLoader().setVisible(true);
     }
 
     public void updateInteractionType(@InteractionType int interactionType) {
@@ -626,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));
 
@@ -934,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();
@@ -964,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) {
@@ -1098,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..c3ce439 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -29,8 +29,6 @@
 import android.graphics.RectF;
 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,9 +48,10 @@
 import com.android.systemui.shared.system.TransactionCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
-import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 
+import androidx.annotation.Nullable;
+
 /**
  * Utility class to handle window clip animation
  */
@@ -260,7 +259,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/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index ec9c7ea..7715f35 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -20,14 +20,15 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
-import android.support.annotation.AnyThread;
-import android.support.annotation.IntDef;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 
 import java.lang.annotation.Retention;
 
+import androidx.annotation.AnyThread;
+import androidx.annotation.IntDef;
+
 public class LayoutUtils {
 
     private static final int MULTI_WINDOW_STRATEGY_HALF_SCREEN = 1;
diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
index c359966..eb8da6e 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.java
+++ b/quickstep/src/com/android/quickstep/views/IconView.java
@@ -21,14 +21,26 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.launcher3.FastBitmapDrawable;
+
+import java.util.ArrayList;
+
+import androidx.annotation.NonNull;
+
 /**
  * A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout
  * when the drawable changes.
  */
 public class IconView extends View {
 
+    public interface OnScaleUpdateListener {
+        public void onScaleUpdate(float scale);
+    }
+
     private Drawable mDrawable;
 
+    private ArrayList<OnScaleUpdateListener> mScaleListeners;
+
     public IconView(Context context) {
         super(context);
     }
@@ -53,6 +65,10 @@
         invalidate();
     }
 
+    public Drawable getDrawable() {
+        return mDrawable;
+    }
+
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
@@ -78,6 +94,16 @@
     }
 
     @Override
+    public void invalidateDrawable(@NonNull Drawable drawable) {
+        super.invalidateDrawable(drawable);
+        if (drawable instanceof FastBitmapDrawable && mScaleListeners != null) {
+            for (OnScaleUpdateListener listener : mScaleListeners) {
+                listener.onScaleUpdate(((FastBitmapDrawable) drawable).getScale());
+            }
+        }
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
         if (mDrawable != null) {
             mDrawable.draw(canvas);
@@ -88,4 +114,20 @@
     public boolean hasOverlappingRendering() {
         return false;
     }
+
+    public void addUpdateScaleListener(OnScaleUpdateListener listener) {
+        if (mScaleListeners == null) {
+            mScaleListeners = new ArrayList<>();
+        }
+        mScaleListeners.add(listener);
+        if (mDrawable instanceof FastBitmapDrawable) {
+            listener.onScaleUpdate(((FastBitmapDrawable) mDrawable).getScale());
+        }
+    }
+
+    public void removeUpdateScaleListener(OnScaleUpdateListener listener) {
+        if (mScaleListeners != null) {
+            mScaleListeners.remove(listener);
+        }
+    }
 }
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..f3dec9a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -44,7 +44,6 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.UserHandle;
-import android.support.annotation.Nullable;
 import android.text.Layout;
 import android.text.StaticLayout;
 import android.text.TextPaint;
@@ -95,6 +94,8 @@
 import java.util.ArrayList;
 import java.util.function.Consumer;
 
+import androidx.annotation.Nullable;
+
 /**
  * A list of recent tasks.
  */
@@ -271,13 +272,13 @@
     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
-        enableFreeScroll(true);
+        setEnableFreeScroll(true);
 
         mFastFlingVelocity = getResources()
                 .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 +416,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 +561,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 +715,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 +750,7 @@
     }
 
     public void showNextTask() {
-        TaskView runningTaskView = getTaskView(mRunningTaskId);
+        TaskView runningTaskView = getRunningTaskView();
         if (runningTaskView == null) {
             // Launch the first task
             if (getTaskViewCount() > 0) {
@@ -773,7 +778,7 @@
     }
 
     private void applyRunningTaskIconScale() {
-        TaskView firstTask = getTaskView(mRunningTaskId);
+        TaskView firstTask = getRunningTaskView();
         if (firstTask != null) {
             firstTask.setIconScaleAndDim(mRunningTaskIconScaledDown ? 0 : 1);
         }
@@ -781,7 +786,7 @@
 
     public void animateUpRunningTaskIconScale() {
         mRunningTaskIconScaledDown = false;
-        TaskView firstTask = getTaskView(mRunningTaskId);
+        TaskView firstTask = getRunningTaskView();
         if (firstTask != null) {
             firstTask.animateIconScaleAndDimIntoView();
         }
@@ -990,10 +995,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();
             }
@@ -1267,11 +1270,10 @@
 
             int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex);
             if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) {
-                anim.play(ObjectAnimator.ofPropertyValuesHolder(getPageAt(otherAdjacentTaskIndex),
-                        new PropertyListBuilder()
-                                .translationX(mIsRtl ? -displacementX : displacementX)
-                                .scale(1)
-                                .build()));
+                anim.play(new PropertyListBuilder()
+                        .translationX(mIsRtl ? -displacementX : displacementX)
+                        .scale(1)
+                        .build(getPageAt(otherAdjacentTaskIndex)));
             }
         }
         return anim;
@@ -1280,11 +1282,10 @@
     private Animator createAnimForChild(TaskView child, float[] toScaleAndTranslation) {
         AnimatorSet anim = new AnimatorSet();
         anim.play(ObjectAnimator.ofFloat(child, TaskView.ZOOM_SCALE, toScaleAndTranslation[0]));
-        anim.play(ObjectAnimator.ofPropertyValuesHolder(child,
-                        new PropertyListBuilder()
-                                .translationX(toScaleAndTranslation[1])
-                                .translationY(toScaleAndTranslation[2])
-                                .build()));
+        anim.play(new PropertyListBuilder()
+                .translationX(toScaleAndTranslation[1])
+                .translationY(toScaleAndTranslation[2])
+                .build(child));
         return anim;
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 8b5e832..8965575 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -15,11 +15,12 @@
  */
 package com.android.quickstep.views;
 
-import static android.support.v4.graphics.ColorUtils.setAlphaComponent;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 
+import static androidx.core.graphics.ColorUtils.setAlphaComponent;
+
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 098349a..28928a8 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -22,7 +22,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
@@ -34,15 +33,15 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.TaskSystemShortcut;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.views.IconView.OnScaleUpdateListener;
 
 /**
  * Contains options for a recent task when long-pressing its icon.
@@ -59,14 +58,42 @@
             new TaskSystemShortcut.Install(),
     };
 
+    private final OnScaleUpdateListener mTaskViewIconScaleListener = new OnScaleUpdateListener() {
+        @Override
+        public void onScaleUpdate(float scale) {
+            final Drawable drawable = mTaskIcon.getDrawable();
+            if (drawable instanceof FastBitmapDrawable) {
+                if (scale != ((FastBitmapDrawable) drawable).getScale()) {
+                    mMenuIconDrawable.setScale(scale);
+                }
+            }
+        }
+    };
+
+    private final OnScaleUpdateListener mMenuIconScaleListener = new OnScaleUpdateListener() {
+        @Override
+        public void onScaleUpdate(float scale) {
+            final Drawable taskViewDrawable = mTaskView.getIconView().getDrawable();
+            if (taskViewDrawable instanceof FastBitmapDrawable) {
+                final float currentScale = ((FastBitmapDrawable) taskViewDrawable).getScale();
+                if (currentScale != scale) {
+                    ((FastBitmapDrawable) taskViewDrawable).setScale(scale);
+                }
+            }
+        }
+    };
+
     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 mTaskIconAndName;
+    private TextView mTaskName;
+    private IconView mTaskIcon;
     private AnimatorSet mOpenCloseAnimator;
     private TaskView mTaskView;
     private LinearLayout mOptionLayout;
+    private FastBitmapDrawable mMenuIconDrawable;
 
     public TaskMenuView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -76,12 +103,14 @@
         super(context, attrs, defStyleAttr);
 
         mActivity = BaseDraggingActivity.fromContext(context);
+        mThumbnailTopMargin = getResources().getDimension(R.dimen.task_thumbnail_top_margin);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mTaskIconAndName = findViewById(R.id.task_icon_and_name);
+        mTaskName = findViewById(R.id.task_name);
+        mTaskIcon = findViewById(R.id.task_icon);
         mOptionLayout = findViewById(R.id.menu_option_layout);
     }
 
@@ -113,15 +142,29 @@
     }
 
     @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        // Remove all scale listeners when menu is removed
+        mTaskView.getIconView().removeUpdateScaleListener(mTaskViewIconScaleListener);
+        mTaskIcon.removeUpdateScaleListener(mMenuIconScaleListener);
+    }
+
+    @Override
     protected boolean isOfType(int type) {
         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) {
@@ -138,17 +181,21 @@
 
     private void addMenuOptions(TaskView taskView) {
         Drawable icon = taskView.getTask().icon.getConstantState().newDrawable();
-        int iconSize = getResources().getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
-        icon.setBounds(0, 0, iconSize, iconSize);
-        mTaskIconAndName.setCompoundDrawables(null, icon, null, null);
-        mTaskIconAndName.setText(TaskUtils.getTitle(getContext(), taskView.getTask()));
-        mTaskIconAndName.setOnClickListener(v -> close(true));
+        mTaskIcon.setDrawable(icon);
+        mTaskIcon.setOnClickListener(v -> close(true));
+        mTaskName.setText(TaskUtils.getTitle(getContext(), taskView.getTask()));
+        mTaskName.setOnClickListener(v -> close(true));
+
+        // Set the icons to match scale by listening to each other's changes
+        mMenuIconDrawable = icon instanceof FastBitmapDrawable ? (FastBitmapDrawable) icon : null;
+        taskView.getIconView().addUpdateScaleListener(mTaskViewIconScaleListener);
+        mTaskIcon.addUpdateScaleListener(mMenuIconScaleListener);
 
         // Move the icon and text up half an icon size to lay over the TaskView
         LinearLayout.LayoutParams params =
-                (LinearLayout.LayoutParams) mTaskIconAndName.getLayoutParams();
-        params.topMargin = (int) -getResources().getDimension(R.dimen.task_thumbnail_top_margin);
-        mTaskIconAndName.setLayoutParams(params);
+                (LinearLayout.LayoutParams) mTaskIcon.getLayoutParams();
+        params.topMargin = (int) -mThumbnailTopMargin;
+        mTaskIcon.setLayoutParams(params);
 
         for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
             OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, taskView);
@@ -172,12 +219,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() {
@@ -191,9 +238,9 @@
 
     private void animateOpenOrClosed(boolean closing) {
         if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
-            return;
+            mOpenCloseAnimator.end();
         }
-        mOpenCloseAnimator = LauncherAnimUtils.createAnimatorSet();
+        mOpenCloseAnimator = new AnimatorSet();
 
         final Animator revealAnimator = createOpenCloseOutlineProvider()
                 .createRevealAnimator(this, closing);
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/attrs.xml b/res/values/attrs.xml
index 30091a5..045e4e9 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -33,6 +33,7 @@
     <attr name="workspaceKeyShadowColor" format="color" />
     <attr name="workspaceStatusBarScrim" format="reference" />
     <attr name="widgetsTheme" format="reference" />
+    <attr name="folderBadgeColor" format="color" />
 
     <!-- BubbleTextView specific attributes. -->
     <declare-styleable name="BubbleTextView">
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/values/styles.xml b/res/values/styles.xml
index 07bd800..7ea28cc 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -42,6 +42,7 @@
         <item name="workspaceKeyShadowColor">#44000000</item>
         <item name="workspaceStatusBarScrim">@drawable/workspace_bg</item>
         <item name="widgetsTheme">@style/WidgetContainerTheme</item>
+        <item name="folderBadgeColor">?android:attr/colorPrimary</item>
     </style>
 
     <style name="LauncherTheme" parent="@style/BaseLauncherThemeWithCustomAttrs"></style>
@@ -70,6 +71,7 @@
         <item name="popupColorSecondary">#424242</item> <!-- Gray 800 -->
         <item name="popupColorTertiary">#757575</item> <!-- Gray 600 -->
         <item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
+        <item name="folderBadgeColor">#FF464646</item>
         <item name="isMainColorDark">true</item>
     </style>
 
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..1c07ea4 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -25,7 +25,6 @@
 
 import android.annotation.SuppressLint;
 import android.content.Context;
-import android.support.annotation.IntDef;
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.view.MotionEvent;
@@ -40,6 +39,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
+import androidx.annotation.IntDef;
+
 /**
  * Base class for a View which shows a floating UI on top of the launcher UI.
  */
@@ -69,9 +70,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/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 5eb6cc7..2482691 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -22,8 +22,6 @@
 import android.content.pm.LauncherActivityInfo;
 import android.os.Process;
 import android.os.UserHandle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.util.Log;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
@@ -35,6 +33,9 @@
 import java.util.HashSet;
 import java.util.List;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 
 /**
  * Stores the list of all applications for the all apps view.
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 7648e30..0f5317b 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -3,8 +3,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
@@ -39,6 +37,7 @@
 
     private final Launcher mLauncher;
     private final DragViewStateAnnouncer mStateAnnouncer;
+    private final FirstFrameAnimatorHelper mFirstFrameAnimatorHelper;
 
     private final View[] mDragHandles = new View[HANDLE_COUNT];
 
@@ -101,6 +100,7 @@
         mBackgroundPadding = getResources()
                 .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
         mTouchTargetWidth = 2 * mBackgroundPadding;
+        mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
     }
 
     @Override
@@ -368,12 +368,7 @@
         mDeltaX = 0;
         mDeltaY = 0;
 
-        post(new Runnable() {
-            @Override
-            public void run() {
-                snapToWidget(true);
-            }
-        });
+        post(() -> snapToWidget(true));
     }
 
     /**
@@ -433,24 +428,19 @@
             }
             requestLayout();
         } else {
-            PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
-            PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
-                    newHeight);
-            PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
-            PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
-            ObjectAnimator oa =
-                    LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
-            oa.addUpdateListener(new AnimatorUpdateListener() {
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    requestLayout();
-                }
-            });
-            AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
+            ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp,
+                    PropertyValuesHolder.ofInt("width", lp.width, newWidth),
+                    PropertyValuesHolder.ofInt("height", lp.height, newHeight),
+                    PropertyValuesHolder.ofInt("x", lp.x, newX),
+                    PropertyValuesHolder.ofInt("y", lp.y, newY));
+            mFirstFrameAnimatorHelper.addTo(oa).addUpdateListener(a -> requestLayout());
+
+            AnimatorSet set = new AnimatorSet();
             set.play(oa);
             for (int i = 0; i < HANDLE_COUNT; i++) {
-                set.play(LauncherAnimUtils.ofFloat(mDragHandles[i], ALPHA, 1.0f));
+                set.play(mFirstFrameAnimatorHelper.addTo(
+                        ObjectAnimator.ofFloat(mDragHandles[i], ALPHA, 1f)));
             }
-
             set.setDuration(SNAP_DURATION);
             set.start();
         }
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index b249c95..0250c36 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -9,7 +9,6 @@
 import android.content.Intent;
 import android.database.Cursor;
 import android.os.Handler;
-import android.support.annotation.WorkerThread;
 import android.util.Log;
 
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -18,6 +17,8 @@
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.util.ContentWriter;
 
+import androidx.annotation.WorkerThread;
+
 public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
 
     private static final String TAG = "AWRestoredReceiver";
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/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 6c82e63..2ec7e01 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -25,7 +25,6 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.support.annotation.IntDef;
 import android.view.ContextThemeWrapper;
 import android.view.View.AccessibilityDelegate;
 
@@ -41,6 +40,8 @@
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
 
+import androidx.annotation.IntDef;
+
 public abstract class BaseActivity extends Activity implements UserEventDelegate{
 
     public static final int INVISIBLE_BY_STATE_HANDLER = 1 << 0;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index d6635dc..f5fbf80 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -19,7 +19,9 @@
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.StrictMode;
@@ -81,13 +83,33 @@
 
     @Override
     public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
+        updateTheme();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateTheme();
+    }
+
+    private void updateTheme() {
+        WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this);
         if (mThemeRes != getThemeRes(wallpaperColorInfo)) {
             recreate();
         }
     }
 
     protected int getThemeRes(WallpaperColorInfo wallpaperColorInfo) {
-        if (wallpaperColorInfo.isDark()) {
+        boolean darkTheme;
+        if (Utilities.ATLEAST_Q) {
+            Configuration configuration = getResources().getConfiguration();
+            int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+            darkTheme = nightMode == Configuration.UI_MODE_NIGHT_YES;
+        } else {
+            darkTheme = wallpaperColorInfo.isDark();
+        }
+
+        if (darkTheme) {
             return wallpaperColorInfo.supportsDarkText() ?
                     R.style.AppTheme_Dark_DarkText : R.style.AppTheme_Dark;
         } else {
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 74b9cfa..f300ef7 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import android.content.Context;
-import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -25,6 +24,8 @@
 
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 
 /**
  * A base {@link RecyclerView}, which does the following:
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index fb7c0ce..01e3a10 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -27,7 +27,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.support.v4.graphics.ColorUtils;
 import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
 import android.util.Property;
@@ -52,6 +51,8 @@
 
 import java.text.NumberFormat;
 
+import androidx.core.graphics.ColorUtils;
+
 /**
  * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
  * because we want to make the bubble taller than the text and TextView's clip is
@@ -386,10 +387,14 @@
     }
 
     public void getIconBounds(Rect outBounds) {
-        int top = getPaddingTop();
-        int left = (getWidth() - mIconSize) / 2;
-        int right = left + mIconSize;
-        int bottom = top + mIconSize;
+        getIconBounds(this, outBounds, mIconSize);
+    }
+
+    public static void getIconBounds(View iconView, Rect outBounds, int iconSize) {
+        int top = iconView.getPaddingTop();
+        int left = (iconView.getWidth() - iconSize) / 2;
+        int right = left + iconSize;
+        int bottom = top + iconSize;
         outBounds.set(left, top, right, bottom);
     }
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 6c2fd8e..f6f1496 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -16,8 +16,11 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -34,11 +37,10 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
-import android.support.annotation.IntDef;
-import android.support.v4.view.ViewCompat;
 import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Property;
 import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.View;
@@ -70,6 +72,9 @@
 import java.util.Comparator;
 import java.util.Stack;
 
+import androidx.annotation.IntDef;
+import androidx.core.view.ViewCompat;
+
 public class CellLayout extends ViewGroup {
     public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
     public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
@@ -243,7 +248,7 @@
 
         for (int i = 0; i < mDragOutlineAnims.length; i++) {
             final InterruptibleInOutAnimator anim =
-                new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
+                new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
             anim.getAnimator().setInterpolator(mEaseOutInterpolator);
             final int thisIndex = i;
             anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
@@ -877,7 +882,7 @@
                 return true;
             }
 
-            ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
+            ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
             va.setDuration(duration);
             mReorderAnimators.put(lp, va);
 
@@ -1884,6 +1889,19 @@
         }
     }
 
+    private static final Property<ReorderPreviewAnimation, Float> ANIMATION_PROGRESS =
+            new Property<ReorderPreviewAnimation, Float>(float.class, "animationProgress") {
+                @Override
+                public Float get(ReorderPreviewAnimation anim) {
+                    return anim.animationProgress;
+                }
+
+                @Override
+                public void set(ReorderPreviewAnimation anim, Float progress) {
+                    anim.setAnimationProgress(progress);
+                }
+            };
+
     // Class which represents the reorder preview animations. These animations show that an item is
     // in a temporary state, and hint at where the item will return to.
     class ReorderPreviewAnimation {
@@ -1904,7 +1922,8 @@
         public static final int MODE_HINT = 0;
         public static final int MODE_PREVIEW = 1;
 
-        Animator a;
+        float animationProgress = 0;
+        ValueAnimator a;
 
         public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
                 int cellY1, int spanX, int spanY) {
@@ -1974,7 +1993,7 @@
             if (noMovement) {
                 return;
             }
-            ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
+            ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
             a = va;
 
             // Animations are disabled in power save mode, causing the repeated animation to jump
@@ -1987,20 +2006,6 @@
 
             va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
             va.setStartDelay((int) (Math.random() * 60));
-            va.addUpdateListener(new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    float r = (Float) animation.getAnimatedValue();
-                    float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
-                    float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
-                    float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
-                    child.setTranslationX(x);
-                    child.setTranslationY(y);
-                    float s = r * finalScale + (1 - r) * initScale;
-                    child.setScaleX(s);
-                    child.setScaleY(s);
-                }
-            });
             va.addListener(new AnimatorListenerAdapter() {
                 public void onAnimationRepeat(Animator animation) {
                     // We make sure to end only after a full period
@@ -2012,6 +2017,18 @@
             va.start();
         }
 
+        private void setAnimationProgress(float progress) {
+            animationProgress = progress;
+            float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
+            float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
+            float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
+            child.setTranslationX(x);
+            child.setTranslationY(y);
+            float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
+            child.setScaleX(s);
+            child.setScaleY(s);
+        }
+
         private void cancel() {
             if (a != null) {
                 a.cancel();
@@ -2024,14 +2041,14 @@
             }
 
             setInitialAnimationValues(true);
-            a = LauncherAnimUtils.ofPropertyValuesHolder(child,
-                    new PropertyListBuilder()
-                            .scale(initScale)
-                            .translationX(initDeltaX)
-                            .translationY(initDeltaY)
-                            .build())
+            a = new PropertyListBuilder()
+                    .scale(initScale)
+                    .translationX(initDeltaX)
+                    .translationY(initDeltaY)
+                    .build(child)
                     .setDuration(REORDER_ANIMATION_DURATION);
-            a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
+            mLauncher.getDragController().addFirstFrameAnimationHelper(a);
+            a.setInterpolator(DEACCEL_1_5);
             a.start();
         }
     }
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/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 9217ca9..7efb6ec 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -109,7 +109,7 @@
 
     @Override
     public final void draw(Canvas canvas) {
-        if (mScaleAnimation != null) {
+        if (mScale != 1f) {
             int count = canvas.save();
             Rect bounds = getBounds();
             canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
@@ -150,10 +150,23 @@
         return mAlpha;
     }
 
+    public void setScale(float scale) {
+        if (mScaleAnimation != null) {
+            mScaleAnimation.cancel();
+            mScaleAnimation = null;
+        }
+        mScale = scale;
+        invalidateSelf();
+    }
+
     public float getAnimatedScale() {
         return mScaleAnimation == null ? 1 : mScale;
     }
 
+    public float getScale() {
+        return mScale;
+    }
+
     @Override
     public int getIntrinsicWidth() {
         return mBitmap.getWidth();
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index e7ca121..c967a96 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -13,126 +13,124 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.launcher3;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+
 import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.util.Log;
 import android.view.View;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
-import com.android.launcher3.util.Thunk;
-
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewTreeObserver.OnDrawListener;
 
 /*
  *  This is a helper class that listens to updates from the corresponding animation.
  *  For the first two frames, it adjusts the current play time of the animation to
  *  prevent jank at the beginning of the animation
  */
-public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter
-    implements ValueAnimator.AnimatorUpdateListener {
+public class FirstFrameAnimatorHelper implements OnDrawListener, OnAttachStateChangeListener {
+
     private static final String TAG = "FirstFrameAnimatorHlpr";
     private static final boolean DEBUG = false;
     private static final int MAX_DELAY = 1000;
-    private final View mTarget;
-    private long mStartFrame;
-    private long mStartTime = -1;
-    private boolean mHandlingOnAnimationUpdate;
-    private boolean mAdjustedSecondFrameTime;
 
-    private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
-    @Thunk static long sGlobalFrameCounter;
-    private static boolean sVisible;
+    private View mRootView;
+    private long mGlobalFrameCount;
 
-    public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
-        mTarget = target;
-        animator.addUpdateListener(this);
-    }
-
-    public FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target) {
-        mTarget = target;
-        vpa.setListener(this);
-    }
-
-    // only used for ViewPropertyAnimators
-    public void onAnimationStart(Animator animation) {
-        final ValueAnimator va = (ValueAnimator) animation;
-        va.addUpdateListener(FirstFrameAnimatorHelper.this);
-        onAnimationUpdate(va);
-    }
-
-    public static void setIsVisible(boolean visible) {
-        sVisible = visible;
-    }
-
-    public static void initializeDrawListener(View view) {
-        if (sGlobalDrawListener != null) {
-            view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
+    public FirstFrameAnimatorHelper(View target) {
+        target.addOnAttachStateChangeListener(this);
+        if (target.isAttachedToWindow()) {
+            onViewAttachedToWindow(target);
         }
-
-        sGlobalDrawListener = () -> sGlobalFrameCounter++;
-        view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
-        sVisible = true;
     }
 
-    public void onAnimationUpdate(final ValueAnimator animation) {
-        final long currentTime = System.currentTimeMillis();
-        if (mStartTime == -1) {
-            mStartFrame = sGlobalFrameCounter;
-            mStartTime = currentTime;
+    public <T extends ValueAnimator> T addTo(T anim) {
+        anim.addUpdateListener(new MyListener());
+        return anim;
+    }
+
+    @Override
+    public void onDraw() {
+        mGlobalFrameCount ++;
+    }
+
+    @Override
+    public void onViewAttachedToWindow(View view) {
+        mRootView = view.getRootView();
+        mRootView.getViewTreeObserver().addOnDrawListener(this);
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(View view) {
+        if (mRootView != null) {
+            mRootView.getViewTreeObserver().removeOnDrawListener(this);
+            mRootView = null;
         }
+    }
 
-        final long currentPlayTime = animation.getCurrentPlayTime();
-        boolean isFinalFrame = Float.compare(1f, animation.getAnimatedFraction()) == 0;
+    private class MyListener implements AnimatorUpdateListener {
 
-        if (!mHandlingOnAnimationUpdate &&
-            sVisible &&
-            // If the current play time exceeds the duration, or the animated fraction is 1,
-            // the animation will get finished, even if we call setCurrentPlayTime -- therefore
-            // don't adjust the animation in that case
-            currentPlayTime < animation.getDuration() && !isFinalFrame) {
-            mHandlingOnAnimationUpdate = true;
-            long frameNum = sGlobalFrameCounter - mStartFrame;
-            // If we haven't drawn our first frame, reset the time to t = 0
-            // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
-            // are no longer in the foreground and no frames are being rendered ever)
-            if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY && currentPlayTime > 0) {
-                // The first frame on animations doesn't always trigger an invalidate...
-                // force an invalidate here to make sure the animation continues to advance
-                mTarget.getRootView().invalidate();
-                animation.setCurrentPlayTime(0);
-            // For the second frame, if the first frame took more than 16ms,
-            // adjust the start time and pretend it took only 16ms anyway. This
-            // prevents a large jump in the animation due to an expensive first frame
-            } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
-                       !mAdjustedSecondFrameTime &&
-                       currentTime > mStartTime + SINGLE_FRAME_MS &&
-                       currentPlayTime > SINGLE_FRAME_MS) {
-                animation.setCurrentPlayTime(SINGLE_FRAME_MS);
-                mAdjustedSecondFrameTime = true;
-            } else {
-                if (frameNum > 1) {
-                    mTarget.post(new Runnable() {
-                            public void run() {
-                                animation.removeUpdateListener(FirstFrameAnimatorHelper.this);
-                            }
-                        });
+        private long mStartFrame;
+        private long mStartTime = -1;
+        private boolean mHandlingOnAnimationUpdate;
+        private boolean mAdjustedSecondFrameTime;
+
+        @Override
+        public void onAnimationUpdate(final ValueAnimator animation) {
+            final long currentTime = System.currentTimeMillis();
+            if (mStartTime == -1) {
+                mStartFrame = mGlobalFrameCount;
+                mStartTime = currentTime;
+            }
+
+            final long currentPlayTime = animation.getCurrentPlayTime();
+            boolean isFinalFrame = Float.compare(1f, animation.getAnimatedFraction()) == 0;
+
+            if (!mHandlingOnAnimationUpdate &&
+                    mRootView != null &&
+                    mRootView.getWindowVisibility() == View.VISIBLE &&
+                    // If the current play time exceeds the duration, or the animated fraction is 1,
+                    // the animation will get finished, even if we call setCurrentPlayTime --
+                    // therefore don't adjust the animation in that case
+                    currentPlayTime < animation.getDuration() && !isFinalFrame) {
+                mHandlingOnAnimationUpdate = true;
+                long frameNum = mGlobalFrameCount - mStartFrame;
+
+                // If we haven't drawn our first frame, reset the time to t = 0
+                // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
+                // are no longer in the foreground and no frames are being rendered ever)
+                if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY && currentPlayTime > 0) {
+                    // The first frame on animations doesn't always trigger an invalidate...
+                    // force an invalidate here to make sure the animation continues to advance
+                    mRootView.invalidate();
+                    animation.setCurrentPlayTime(0);
+                    // For the second frame, if the first frame took more than 16ms,
+                    // adjust the start time and pretend it took only 16ms anyway. This
+                    // prevents a large jump in the animation due to an expensive first frame
+                } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
+                        !mAdjustedSecondFrameTime &&
+                        currentTime > mStartTime + SINGLE_FRAME_MS &&
+                        currentPlayTime > SINGLE_FRAME_MS) {
+                    animation.setCurrentPlayTime(SINGLE_FRAME_MS);
+                    mAdjustedSecondFrameTime = true;
+                } else {
+                    if (frameNum > 1) {
+                        mRootView.post(() -> animation.removeUpdateListener(this));
+                    }
+                    if (DEBUG) print(animation);
                 }
+                mHandlingOnAnimationUpdate = false;
+            } else {
                 if (DEBUG) print(animation);
             }
-            mHandlingOnAnimationUpdate = false;
-        } else {
-            if (DEBUG) print(animation);
         }
-    }
 
-    public void print(ValueAnimator animation) {
-        float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
-        Log.d(TAG, sGlobalFrameCounter +
-              "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
-              mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
+        public void print(ValueAnimator animation) {
+            float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
+            Log.d(TAG, mGlobalFrameCount +
+                    "(" + (mGlobalFrameCount - mStartFrame) + ") " + mRootView + " dirty? " +
+                    mRootView.isDirty() + " " + flatFraction + " " + this + " " + animation);
+        }
     }
 }
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/IconCache.java b/src/com/android/launcher3/IconCache.java
index f496600..abd5538 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -38,8 +38,6 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.support.annotation.NonNull;
-import android.support.v4.graphics.ColorUtils;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -63,6 +61,9 @@
 import java.util.Set;
 import java.util.Stack;
 
+import androidx.annotation.NonNull;
+import androidx.core.graphics.ColorUtils;
+
 /**
  * Cache of application icons.  Icons can be made from any thread.
  */
diff --git a/src/com/android/launcher3/InterruptibleInOutAnimator.java b/src/com/android/launcher3/InterruptibleInOutAnimator.java
index 8501e24..f4395ca 100644
--- a/src/com/android/launcher3/InterruptibleInOutAnimator.java
+++ b/src/com/android/launcher3/InterruptibleInOutAnimator.java
@@ -18,7 +18,9 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.util.Property;
 import android.view.View;
 
 import com.android.launcher3.util.Thunk;
@@ -31,11 +33,27 @@
  * interpolator in the same direction.
  */
 public class InterruptibleInOutAnimator {
+
+    private static final Property<InterruptibleInOutAnimator, Float> VALUE =
+            new Property<InterruptibleInOutAnimator, Float>(Float.TYPE, "value") {
+                @Override
+                public Float get(InterruptibleInOutAnimator anim) {
+                    return anim.mValue;
+                }
+
+                @Override
+                public void set(InterruptibleInOutAnimator anim, Float value) {
+                    anim.mValue = value;
+                }
+            };
+
     private long mOriginalDuration;
     private float mOriginalFromValue;
     private float mOriginalToValue;
     private ValueAnimator mAnimator;
 
+    private float mValue;
+
     private boolean mFirstRun = true;
 
     private Object mTag = null;
@@ -47,8 +65,8 @@
     // TODO: This isn't really necessary, but is here to help diagnose a bug in the drag viz
     @Thunk int mDirection = STOPPED;
 
-    public InterruptibleInOutAnimator(View view, long duration, float fromValue, float toValue) {
-        mAnimator = LauncherAnimUtils.ofFloat(fromValue, toValue).setDuration(duration);
+    public InterruptibleInOutAnimator(long duration, float fromValue, float toValue) {
+        mAnimator = ObjectAnimator.ofFloat(this, VALUE, fromValue, toValue).setDuration(duration);
         mOriginalDuration = duration;
         mOriginalFromValue = fromValue;
         mOriginalToValue = toValue;
@@ -64,8 +82,7 @@
     private void animate(int direction) {
         final long currentPlayTime = mAnimator.getCurrentPlayTime();
         final float toValue = (direction == IN) ? mOriginalToValue : mOriginalFromValue;
-        final float startValue = mFirstRun ? mOriginalFromValue :
-                ((Float) mAnimator.getAnimatedValue()).floatValue();
+        final float startValue = mFirstRun ? mOriginalFromValue : mValue;
 
         // Make sure it's stopped before we modify any values
         cancel();
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 22bc162..f3f2238 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -22,13 +22,11 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.Point;
-import android.support.annotation.VisibleForTesting;
 import android.util.DisplayMetrics;
 import android.util.Xml;
 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;
@@ -41,6 +39,8 @@
 import java.util.Collections;
 import java.util.Comparator;
 
+import androidx.annotation.VisibleForTesting;
+
 public class InvariantDeviceProfile {
 
     // We do not need any synchronization for this variable as its only written on UI thread.
@@ -314,17 +314,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 7ab9768..7f99323 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;
@@ -56,7 +56,6 @@
 import android.os.Process;
 import android.os.StrictMode;
 import android.os.UserHandle;
-import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
 import android.util.Log;
@@ -78,6 +77,7 @@
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompatVO;
@@ -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;
@@ -139,6 +140,8 @@
 import java.util.List;
 import java.util.Set;
 
+import androidx.annotation.Nullable;
+
 /**
  * Default launcher application.
  */
@@ -347,8 +350,19 @@
     }
 
     @Override
+    public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+        UiFactory.onEnterAnimationComplete(this);
+    }
+
+    @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);
@@ -727,8 +741,6 @@
     @Override
     protected void onStop() {
         super.onStop();
-        FirstFrameAnimatorHelper.setIsVisible(false);
-
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStop();
         }
@@ -749,8 +761,6 @@
     @Override
     protected void onStart() {
         super.onStart();
-        FirstFrameAnimatorHelper.setIsVisible(true);
-
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStart();
         }
@@ -1118,7 +1128,6 @@
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
 
-        FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onAttachedToWindow();
         }
@@ -1234,7 +1243,7 @@
                     mAppsView.reset(isStarted() /* animate */);
                 }
 
-                if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()) {
+                if (shouldMoveToDefaultScreen && !mWorkspace.isHandlingTouch()) {
                     mWorkspace.post(mWorkspace::moveToDefaultScreen);
                 }
             }
@@ -1320,9 +1329,6 @@
         }
 
         TextKeyListener.getInstance().release();
-
-        LauncherAnimUtils.onDestroyActivity();
-
         clearPendingBinds();
 
         if (mLauncherCallbacks != null) {
@@ -1593,14 +1599,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);
         }
     }
 
@@ -1835,7 +1835,6 @@
     @Override
     public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
         // Get the list of added items and intersect them with the set of items here
-        final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
         final Collection<Animator> bounceAnims = new ArrayList<>();
         final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
         Workspace workspace = mWorkspace;
@@ -1907,34 +1906,31 @@
             }
         }
 
-        if (animateIcons) {
-            // Animate to the correct page
-            if (newItemsScreenId > -1) {
-                long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
-                final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
-                final Runnable startBounceAnimRunnable = new Runnable() {
-                    public void run() {
-                        anim.playTogether(bounceAnims);
-                        anim.start();
-                    }
-                };
-                if (newItemsScreenId != currentScreenId) {
-                    // We post the animation slightly delayed to prevent slowdowns
-                    // when we are loading right after we return to launcher.
-                    mWorkspace.postDelayed(new Runnable() {
-                        public void run() {
-                            if (mWorkspace != null) {
-                                AbstractFloatingView.closeAllOpenViews(Launcher.this, false);
+        // Animate to the correct page
+        if (animateIcons && newItemsScreenId > -1) {
+            AnimatorSet anim = new AnimatorSet();
+            anim.playTogether(bounceAnims);
 
-                                mWorkspace.snapToPage(newScreenIndex);
-                                mWorkspace.postDelayed(startBounceAnimRunnable,
-                                        NEW_APPS_ANIMATION_DELAY);
-                            }
+            long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
+            final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
+            final Runnable startBounceAnimRunnable = anim::start;
+
+            if (newItemsScreenId != currentScreenId) {
+                // We post the animation slightly delayed to prevent slowdowns
+                // when we are loading right after we return to launcher.
+                mWorkspace.postDelayed(new Runnable() {
+                    public void run() {
+                        if (mWorkspace != null) {
+                            AbstractFloatingView.closeAllOpenViews(Launcher.this, false);
+
+                            mWorkspace.snapToPage(newScreenIndex);
+                            mWorkspace.postDelayed(startBounceAnimRunnable,
+                                    NEW_APPS_ANIMATION_DELAY);
                         }
-                    }, NEW_APPS_PAGE_MOVE_DELAY);
-                } else {
-                    mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
-                }
+                    }
+                }, NEW_APPS_PAGE_MOVE_DELAY);
+            } else {
+                mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
             }
         }
         workspace.requestLayout();
@@ -2157,8 +2153,8 @@
     }
 
     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
-        ValueAnimator bounceAnim = LauncherAnimUtils.ofViewAlphaAndScale(v, 1, 1, 1);
-        bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
+        ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
+                .setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
         bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
         bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
         return bounceAnim;
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 03ffded..ac07e88 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -16,18 +16,9 @@
 
 package com.android.launcher3;
 
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
 import android.graphics.drawable.Drawable;
 import android.util.Property;
 import android.view.View;
-import android.view.ViewTreeObserver;
-
-import java.util.HashSet;
-import java.util.WeakHashMap;
 
 public class LauncherAnimUtils {
     /**
@@ -42,105 +33,6 @@
     // The progress of an animation to all apps must be at least this far along to snap to all apps.
     public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f;
 
-    static WeakHashMap<Animator, Object> sAnimators = new WeakHashMap<Animator, Object>();
-    static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
-        public void onAnimationStart(Animator animation) {
-            sAnimators.put(animation, null);
-        }
-
-        public void onAnimationRepeat(Animator animation) {
-        }
-
-        public void onAnimationEnd(Animator animation) {
-            sAnimators.remove(animation);
-        }
-
-        public void onAnimationCancel(Animator animation) {
-            sAnimators.remove(animation);
-        }
-    };
-
-    public static void cancelOnDestroyActivity(Animator a) {
-        a.addListener(sEndAnimListener);
-    }
-
-    // Helper method. Assumes a draw is pending, and that if the animation's duration is 0
-    // it should be cancelled
-    public static void startAnimationAfterNextDraw(final Animator animator, final View view) {
-        view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
-            private boolean mStarted = false;
-
-            public void onDraw() {
-                if (mStarted) return;
-                mStarted = true;
-                // Use this as a signal that the animation was cancelled
-                if (animator.getDuration() == 0) {
-                    return;
-                }
-                animator.start();
-
-                final ViewTreeObserver.OnDrawListener listener = this;
-                view.post(new Runnable() {
-                    public void run() {
-                        view.getViewTreeObserver().removeOnDrawListener(listener);
-                    }
-                });
-            }
-        });
-    }
-
-    public static void onDestroyActivity() {
-        HashSet<Animator> animators = new HashSet<Animator>(sAnimators.keySet());
-        for (Animator a : animators) {
-            if (a.isRunning()) {
-                a.cancel();
-            }
-            sAnimators.remove(a);
-        }
-    }
-
-    public static AnimatorSet createAnimatorSet() {
-        AnimatorSet anim = new AnimatorSet();
-        cancelOnDestroyActivity(anim);
-        return anim;
-    }
-
-    public static ValueAnimator ofFloat(float... values) {
-        ValueAnimator anim = new ValueAnimator();
-        anim.setFloatValues(values);
-        cancelOnDestroyActivity(anim);
-        return anim;
-    }
-
-    public static ObjectAnimator ofFloat(View target, Property<View, Float> property,
-            float... values) {
-        ObjectAnimator anim = ObjectAnimator.ofFloat(target, property, values);
-        cancelOnDestroyActivity(anim);
-        new FirstFrameAnimatorHelper(anim, target);
-        return anim;
-    }
-
-    public static ObjectAnimator ofViewAlphaAndScale(View target,
-            float alpha, float scaleX, float scaleY) {
-        return ofPropertyValuesHolder(target,
-                PropertyValuesHolder.ofFloat(View.ALPHA, alpha),
-                PropertyValuesHolder.ofFloat(View.SCALE_X, scaleX),
-                PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleY));
-    }
-
-    public static ObjectAnimator ofPropertyValuesHolder(View target,
-            PropertyValuesHolder... values) {
-        return ofPropertyValuesHolder(target, target, values);
-    }
-
-    public static ObjectAnimator ofPropertyValuesHolder(Object target,
-            View view, PropertyValuesHolder... values) {
-        ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(target, values);
-        cancelOnDestroyActivity(anim);
-        new FirstFrameAnimatorHelper(anim, view);
-        return anim;
-    }
-
     public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
             new Property<Drawable, Integer>(Integer.TYPE, "drawableAlpha") {
                 @Override
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 19aa795..fe9f901 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -31,7 +31,6 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
-import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -74,6 +73,8 @@
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.Executor;
 
+import androidx.annotation.Nullable;
+
 /**
  * Maintains in-memory state of the Launcher. It is expected that there should be only one
  * LauncherModel object held in a static. Also provide APIs for updating the database state
diff --git a/src/com/android/launcher3/LauncherScroller.java b/src/com/android/launcher3/LauncherScroller.java
deleted file mode 100644
index e5042c4..0000000
--- a/src/com/android/launcher3/LauncherScroller.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2006 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;
-
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.view.animation.Interpolator;
-import android.widget.Scroller;
-
-/**
- * Extension of {@link android.widget.Scroller} with the ability to modify the
- * Interpolator post-construction.
- */
-public class LauncherScroller extends Scroller {
-
-    private final InterpolatorWrapper mInterpolatorWrapper;
-
-    public LauncherScroller(Context context) {
-        this(context, new InterpolatorWrapper());
-    }
-
-    private LauncherScroller(Context context, InterpolatorWrapper interpolatorWrapper) {
-        super(context, interpolatorWrapper);
-        mInterpolatorWrapper = interpolatorWrapper;
-    }
-
-    public void setInterpolator(TimeInterpolator interpolator) {
-        mInterpolatorWrapper.interpolator = interpolator;
-    }
-
-    private static class InterpolatorWrapper implements Interpolator {
-
-        public TimeInterpolator interpolator;
-
-        @Override
-        public float getInterpolation(float v) {
-            return interpolator == null ? v : interpolator.getInterpolation(v);
-        }
-    }
-}
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..fe3df95 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static android.view.View.VISIBLE;
+
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
@@ -34,7 +35,6 @@
 import android.animation.AnimatorSet;
 import android.os.Handler;
 import android.os.Looper;
-import android.support.annotation.IntDef;
 
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -47,6 +47,8 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
+import androidx.annotation.IntDef;
+
 /**
  * TODO: figure out what kind of tests we can write for this
  *
@@ -306,7 +308,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..394b950 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
+import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR;
 
 import android.animation.LayoutTransition;
 import android.animation.TimeInterpolator;
@@ -47,6 +48,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.util.OverScroller;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -63,7 +65,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;
@@ -83,7 +84,6 @@
     public static final int INVALID_RESTORE_PAGE = -1001;
 
     private boolean mFreeScroll = false;
-    private boolean mSettleOnPageInFreeScroll = false;
 
     protected int mFlingThresholdVelocity;
     protected int mMinFlingVelocity;
@@ -97,7 +97,7 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     protected int mNextPage = INVALID_PAGE;
     protected int mMaxScrollX;
-    protected LauncherScroller mScroller;
+    protected OverScroller mScroller;
     private Interpolator mDefaultInterpolator;
     private VelocityTracker mVelocityTracker;
     protected int mPageSpacing = 0;
@@ -109,13 +109,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;
@@ -129,11 +123,6 @@
 
     protected boolean mWasInOverscroll = false;
 
-    // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
-    // it is equal to the scaled overscroll position. We use a separate value so as to prevent
-    // the screens from continuing to translate beyond the normal bounds.
-    protected int mOverScrollX;
-
     protected int mUnboundedScrollX;
 
     // Page Indicator
@@ -176,7 +165,7 @@
      * Initializes various states for this workspace.
      */
     protected void init() {
-        mScroller = new LauncherScroller(getContext());
+        mScroller = new OverScroller(getContext());
         setDefaultInterpolator(Interpolators.SCROLL);
         mCurrentPage = 0;
 
@@ -251,7 +240,7 @@
             newX = getScrollForPage(mCurrentPage);
         }
         scrollTo(newX, 0);
-        mScroller.setFinalX(newX);
+        mScroller.startScroll(mScroller.getCurrPos(), newX - mScroller.getCurrPos());
         forceFinishScroller(true);
     }
 
@@ -356,17 +345,6 @@
 
     @Override
     public void scrollTo(int x, int y) {
-        // In free scroll mode, we clamp the scrollX
-        if (mFreeScroll) {
-            // If the scroller is trying to move to a location beyond the maximum allowed
-            // in the free scroll mode, we make sure to end the scroll operation.
-            if (!mScroller.isFinished() && (x > mMaxScrollX || x < 0)) {
-                forceFinishScroller(false);
-            }
-
-            x = Utilities.boundToRange(x, 0, mMaxScrollX);
-        }
-
         mUnboundedScrollX = x;
 
         boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0);
@@ -396,9 +374,9 @@
                 overScroll(0);
                 mWasInOverscroll = false;
             }
-            mOverScrollX = x;
             super.scrollTo(x, y);
         }
+
     }
 
     private void sendScrollAccessibilityEvent() {
@@ -432,10 +410,9 @@
     protected boolean computeScrollHelper(boolean shouldInvalidate) {
         if (mScroller.computeScrollOffset()) {
             // Don't bother scrolling if the page does not need to be moved
-            if (getUnboundedScrollX() != mScroller.getCurrX()
-                    || getScrollY() != mScroller.getCurrY()
-                    || mOverScrollX != mScroller.getCurrX()) {
-                scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
+            if (getUnboundedScrollX() != mScroller.getCurrPos()
+                    || getScrollX() != mScroller.getCurrPos()) {
+                scrollTo(mScroller.getCurrPos(), 0);
             }
             if (shouldInvalidate) {
                 invalidate();
@@ -451,7 +428,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 +789,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 +812,7 @@
          * motion.
          */
         final int action = ev.getAction();
-        if ((action == MotionEvent.ACTION_MOVE) &&
-                (mTouchState == TOUCH_STATE_SCROLLING)) {
+        if ((action == MotionEvent.ACTION_MOVE) && mIsBeingDragged) {
             return true;
         }
 
@@ -873,21 +849,17 @@
                  * otherwise don't.  mScroller.isFinished should be false when
                  * being flinged.
                  */
-                final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
+                final int xDist = Math.abs(mScroller.getFinalPos() - mScroller.getCurrPos());
                 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 +880,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 +903,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 +912,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;
@@ -1008,31 +980,34 @@
         }
     }
 
-    protected void dampedOverScroll(float amount) {
-        if (Float.compare(amount, 0f) == 0) return;
+    protected void dampedOverScroll(int amount) {
+        if (amount == 0) return;
 
         int overScrollAmount = OverScroll.dampedScroll(amount, getMeasuredWidth());
         if (amount < 0) {
-            mOverScrollX = overScrollAmount;
-            super.scrollTo(mOverScrollX, getScrollY());
+            super.scrollTo(overScrollAmount, getScrollY());
         } else {
-            mOverScrollX = mMaxScrollX + overScrollAmount;
-            super.scrollTo(mOverScrollX, getScrollY());
+            super.scrollTo(mMaxScrollX + overScrollAmount, getScrollY());
         }
         invalidate();
     }
 
-    protected void overScroll(float amount) {
-        dampedOverScroll(amount);
+    protected void overScroll(int amount) {
+        if (amount == 0) return;
+
+        if (mFreeScroll && !mScroller.isFinished()) {
+            if (amount < 0) {
+                super.scrollTo(amount, getScrollY());
+            } else {
+                super.scrollTo(mMaxScrollX + amount, getScrollY());
+            }
+        } else {
+            dampedOverScroll(amount);
+        }
     }
 
 
-    protected void enableFreeScroll(boolean settleOnPageInFreeScroll) {
-        setEnableFreeScroll(true);
-        mSettleOnPageInFreeScroll = settleOnPageInFreeScroll;
-    }
-
-    private void setEnableFreeScroll(boolean freeScroll) {
+    protected void setEnableFreeScroll(boolean freeScroll) {
         boolean wasFreeScroll = mFreeScroll;
         mFreeScroll = freeScroll;
 
@@ -1041,8 +1016,6 @@
         } else if (wasFreeScroll) {
             snapToPage(getNextPage());
         }
-
-        setEnableOverscroll(!freeScroll);
     }
 
     protected void setEnableOverscroll(boolean enable) {
@@ -1077,14 +1050,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 +1084,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);
@@ -1125,6 +1098,8 @@
 
                 mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
                 boolean isFling = mTotalMotionX > mTouchSlop && shouldFlingForVelocity(velocityX);
+                boolean isDeltaXLeft = mIsRtl ? deltaX > 0 : deltaX < 0;
+                boolean isVelocityXLeft = mIsRtl ? velocityX > 0 : velocityX < 0;
 
                 if (!mFreeScroll) {
                     // In the case that the page is moved far to one direction and then is flung
@@ -1140,8 +1115,7 @@
                     // We give flings precedence over large moves, which is why we short-circuit our
                     // test for a large move if a fling has been registered. That is, a large
                     // move to the left and fling to the right will register as a fling to the right.
-                    boolean isDeltaXLeft = mIsRtl ? deltaX > 0 : deltaX < 0;
-                    boolean isVelocityXLeft = mIsRtl ? velocityX > 0 : velocityX < 0;
+
                     if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
                             (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
                         finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
@@ -1159,60 +1133,44 @@
                         abortScrollerAnimation(true);
                     }
 
-                    float scaleX = getScaleX();
-                    int vX = (int) (-velocityX * scaleX);
-                    int initialScrollX = (int) (getScrollX() * scaleX);
+                    int initialScrollX = getScrollX();
 
-                    mScroller.setInterpolator(mDefaultInterpolator);
-                    mScroller.fling(initialScrollX,
-                            getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
-                    int unscaledScrollX = (int) (mScroller.getFinalX() / scaleX);
-                    mNextPage = getPageNearestToCenterOfScreen(unscaledScrollX);
-                    int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1);
-                    int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0);
-                    if (mSettleOnPageInFreeScroll && unscaledScrollX > 0
-                            && unscaledScrollX < mMaxScrollX) {
-                        // If scrolling ends in the half of the added space that is closer to the
-                        // end, settle to the end. Otherwise snap to the nearest page.
-                        // If flinging past one of the ends, don't change the velocity as it will
-                        // get stopped at the end anyway.
-                        final int finalX = unscaledScrollX < firstPageScroll / 2 ?
-                                0 :
-                                unscaledScrollX > (lastPageScroll + mMaxScrollX) / 2 ?
-                                        mMaxScrollX :
-                                        getScrollForPage(mNextPage);
+                    if (((initialScrollX >= mMaxScrollX) && (isVelocityXLeft || !isFling)) ||
+                            ((initialScrollX <= 0) && (!isVelocityXLeft || !isFling))) {
+                        mScroller.springBack(getScrollX(), 0, mMaxScrollX);
+                    } else {
+                        mScroller.setInterpolator(mDefaultInterpolator);
+                        mScroller.fling(initialScrollX, -velocityX,
+                                0, mMaxScrollX,
+                                Math.round(getWidth() * 0.5f * OVERSCROLL_DAMP_FACTOR));
 
-                        mScroller.setFinalX((int) (finalX * getScaleX()));
-                        // Ensure the scroll/snap doesn't happen too fast;
-                        int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION
-                                - mScroller.getDuration();
-                        if (extraScrollDuration > 0) {
-                            mScroller.extendDuration(extraScrollDuration);
+                        int finalX = mScroller.getFinalPos();
+                        mNextPage = getPageNearestToCenterOfScreen(finalX);
+
+                        int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1);
+                        int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0);
+                        if (finalX > 0 && finalX < mMaxScrollX) {
+                            // If scrolling ends in the half of the added space that is closer to
+                            // the end, settle to the end. Otherwise snap to the nearest page.
+                            // If flinging past one of the ends, don't change the velocity as it
+                            // will get stopped at the end anyway.
+                            int pageSnappedX = finalX < firstPageScroll / 2 ? 0
+                                    : finalX > (lastPageScroll + mMaxScrollX) / 2
+                                            ? mMaxScrollX
+                                            : getScrollForPage(mNextPage);
+
+                            mScroller.setFinalPos(pageSnappedX);
+                            // Ensure the scroll/snap doesn't happen too fast;
+                            int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION
+                                    - mScroller.getDuration();
+                            if (extraScrollDuration > 0) {
+                                mScroller.extendDuration(extraScrollDuration);
+                            }
                         }
                     }
                     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 +1178,7 @@
             break;
 
         case MotionEvent.ACTION_CANCEL:
-            if (mTouchState == TOUCH_STATE_SCROLLING) {
+            if (mIsBeingDragged) {
                 snapToDestination();
                 onScrollInteractionEnd();
             }
@@ -1242,7 +1200,7 @@
 
     private void resetTouchState() {
         releaseVelocityTracker();
-        mTouchState = TOUCH_STATE_REST;
+        mIsBeingDragged = false;
         mActivePointerId = INVALID_POINTER;
     }
 
@@ -1356,7 +1314,7 @@
     }
 
     protected boolean isInOverScroll() {
-        return (mOverScrollX > mMaxScrollX || mOverScrollX < 0);
+        return (getScrollX() > mMaxScrollX || getScrollX() < 0);
     }
 
     protected int getPageSnapDuration() {
@@ -1475,7 +1433,7 @@
             mScroller.setInterpolator(mDefaultInterpolator);
         }
 
-        mScroller.startScroll(getUnboundedScrollX(), 0, delta, 0, duration);
+        mScroller.startScroll(getUnboundedScrollX(), delta, duration);
 
         updatePageIndicator();
 
diff --git a/src/com/android/launcher3/PromiseAppInfo.java b/src/com/android/launcher3/PromiseAppInfo.java
index ea9f752..ea151cd 100644
--- a/src/com/android/launcher3/PromiseAppInfo.java
+++ b/src/com/android/launcher3/PromiseAppInfo.java
@@ -18,11 +18,12 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.support.annotation.NonNull;
 
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.util.PackageManagerHelper;
 
+import androidx.annotation.NonNull;
+
 public class PromiseAppInfo extends AppInfo {
 
     public int level = 0;
diff --git a/src/com/android/launcher3/TestProtocol.java b/src/com/android/launcher3/TestProtocol.java
new file mode 100644
index 0000000..0a3b86d
--- /dev/null
+++ b/src/com/android/launcher3/TestProtocol.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * Protocol for custom accessibility events for communication with UI Automation tests.
+ */
+public final class TestProtocol {
+    public static final String GET_SCROLL_MESSAGE = "TAPL_GET_SCROLL";
+    public static final String SCROLL_Y_FIELD = "scrollY";
+    public static final String SWITCHED_TO_STATE_MESSAGE = "TAPL_SWITCHED_TO_STATE";
+    public static final String RESPONSE_MESSAGE_POSTFIX = "_RESPONSE";
+}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8683b21..1c12464 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;
@@ -82,6 +83,10 @@
     private static final Matrix sMatrix = new Matrix();
     private static final Matrix sInverseMatrix = new Matrix();
 
+    public static final boolean ATLEAST_Q = Build.VERSION.CODENAME.length() == 1
+            && Build.VERSION.CODENAME.charAt(0) >= 'Q'
+            && Build.VERSION.CODENAME.charAt(0) <= 'Z';
+
     public static final boolean ATLEAST_P =
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
 
@@ -130,6 +135,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 +606,8 @@
         msg.setAsynchronous(true);
         handler.sendMessage(msg);
     }
+
+    public interface Consumer<T> {
+        void accept(T var1);
+    }
 }
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 7af4bf9..84bad08 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -25,7 +25,6 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.UserHandle;
-import android.support.annotation.Nullable;
 import android.util.Log;
 import android.util.LongSparseArray;
 
@@ -51,6 +50,8 @@
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 
+import androidx.annotation.Nullable;
+
 public class WidgetPreviewLoader {
 
     private static final String TAG = "WidgetPreviewLoader";
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 67bdd3b..d652fe0 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;
     }
 
@@ -1098,7 +1096,7 @@
     }
 
     @Override
-    protected void overScroll(float amount) {
+    protected void overScroll(int amount) {
         boolean shouldScrollOverlay = mLauncherOverlay != null &&
                 ((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl));
 
@@ -1111,7 +1109,7 @@
                 mLauncherOverlay.onScrollInteractionBegin();
             }
 
-            mLastOverlayScroll = Math.abs(amount / getMeasuredWidth());
+            mLastOverlayScroll = Math.abs(((float) amount) / getMeasuredWidth());
             mLauncherOverlay.onScrollChange(mLastOverlayScroll, mIsRtl);
         } else {
             dampedOverScroll(amount);
@@ -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/accessibility/DragAndDropAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
index bd3bb4d..117296d 100644
--- a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
@@ -19,8 +19,6 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v4.widget.ExploreByTouchHelper;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.accessibility.AccessibilityEvent;
@@ -31,6 +29,9 @@
 
 import java.util.List;
 
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.customview.widget.ExploreByTouchHelper;
+
 /**
  * Helper class to make drag-and-drop in a {@link CellLayout} accessible.
  */
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index e6f120f..1c088db 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.graphics.Rect;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.text.TextUtils;
 import android.view.View;
 
@@ -32,6 +31,8 @@
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DragType;
 import com.android.launcher3.dragndrop.DragLayer;
 
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
 /**
  * Implementation of {@link DragAndDropAccessibilityDelegate} to support DnD on workspace.
  */
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index fdf32af..6908ab4 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -21,13 +21,8 @@
 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;
-import android.support.annotation.Nullable;
-import android.support.annotation.StringRes;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.util.AttributeSet;
@@ -47,7 +42,9 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
+import com.android.launcher3.TestProtocol;
 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;
@@ -57,6 +54,13 @@
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.SpringRelativeLayout;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
 /**
  * The all apps view container.
  */
@@ -388,6 +392,26 @@
         }
     }
 
+    // Used by tests only
+    private boolean isDescendantViewVisible(int viewId) {
+        final View view = findViewById(viewId);
+        if (view == null) return false;
+
+        if (!view.isShown()) return false;
+
+        return view.getGlobalVisibleRect(new Rect());
+    }
+
+    // Used by tests only
+    public boolean isPersonalTabVisible() {
+        return isDescendantViewVisible(R.id.tab_personal);
+    }
+
+    // Used by tests only
+    public boolean isWorkTabVisible() {
+        return isDescendantViewVisible(R.id.tab_work);
+    }
+
     public AlphabeticalAppsList getApps() {
         return mAH[AdapterHolder.MAIN].appsList;
     }
@@ -549,4 +573,17 @@
                     && verticalFadingEdge);
         }
     }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        if (AccessibilityManagerCompat.processTestRequest(
+                mLauncher, TestProtocol.GET_SCROLL_MESSAGE, action, arguments,
+                response ->
+                        response.putInt(TestProtocol.SCROLL_Y_FIELD,
+                                getActiveRecyclerView().getCurrentScrollY()))) {
+            return true;
+        }
+
+        return super.performAccessibilityAction(action, arguments);
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index e08cb15..3ee1293 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -15,13 +15,13 @@
  */
 package com.android.launcher3.allapps;
 
-import android.support.v7.widget.RecyclerView;
-
 import com.android.launcher3.util.Thunk;
 
 import java.util.HashSet;
 import java.util.List;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallback {
 
     private static final int INITIAL_TOUCH_SETTLING_DURATION = 100;
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 27fc53a..0f13550 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -18,11 +18,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.support.v4.view.accessibility.AccessibilityEventCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v4.view.accessibility.AccessibilityRecordCompat;
-import android.support.v7.widget.GridLayoutManager;
-import android.support.v7.widget.RecyclerView;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -44,6 +39,12 @@
 
 import java.util.List;
 
+import androidx.core.view.accessibility.AccessibilityEventCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.core.view.accessibility.AccessibilityRecordCompat;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
 /**
  * The grid view adapter of all the apps.
  */
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index a6c1346..adc4a72 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -21,7 +21,6 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
-import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
@@ -41,6 +40,8 @@
 
 import java.util.List;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 /**
  * A RecyclerView with custom fast scroll support for the all apps view.
  */
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index eaa7774..5348349 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -19,9 +19,6 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -32,6 +29,10 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PropertySetter;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
 public class FloatingHeaderView extends LinearLayout implements
         ValueAnimator.AnimatorUpdateListener {
 
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 31c5914..decdcc0 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -19,8 +19,6 @@
 import android.content.SharedPreferences;
 import android.graphics.Canvas;
 import android.graphics.Paint;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.Button;
@@ -32,6 +30,9 @@
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.util.Themes;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 /**
  * Supports two indicator colors, dedicated for personal and work tabs.
  */
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index d529f6f..717bbd4 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -25,6 +25,7 @@
 
 import com.android.launcher3.compat.UserManagerCompat;
 
+import java.lang.ref.WeakReference;
 import java.util.List;
 
 public class WorkModeSwitch extends Switch {
@@ -67,34 +68,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/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index dcc4554..91be504 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.allapps.search;
 
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index 307f258..fdac5c8 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -20,8 +20,6 @@
 import android.util.SparseArray;
 import android.view.animation.Interpolator;
 
-import com.android.launcher3.LauncherAnimUtils;
-
 import java.util.ArrayList;
 import java.util.List;
 
@@ -56,7 +54,7 @@
     }
 
     public AnimatorSet build() {
-        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+        AnimatorSet anim = new AnimatorSet();
         anim.playTogether(mAnims);
         if (!mOnFinishRunnables.isEmpty()) {
             anim.addListener(new AnimationSuccessListener() {
diff --git a/src/com/android/launcher3/anim/PropertyListBuilder.java b/src/com/android/launcher3/anim/PropertyListBuilder.java
index 33e7f66..acc3b45 100644
--- a/src/com/android/launcher3/anim/PropertyListBuilder.java
+++ b/src/com/android/launcher3/anim/PropertyListBuilder.java
@@ -1,5 +1,6 @@
 package com.android.launcher3.anim;
 
+import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.view.View;
 
@@ -44,7 +45,8 @@
         return this;
     }
 
-    public PropertyValuesHolder[] build() {
-        return mProperties.toArray(new PropertyValuesHolder[mProperties.size()]);
+    public ObjectAnimator build(View view) {
+        return ObjectAnimator.ofPropertyValuesHolder(view,
+                mProperties.toArray(new PropertyValuesHolder[mProperties.size()]));
     }
 }
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index 9487427..8998b24 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -60,6 +60,7 @@
 
         int size = (int) (DOT_SCALE * mDotCenterOffset);
         ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
+        builder.ambientShadowAlpha = 88;
         mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
         mCircleRadius = builder.radius;
 
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 0c78381..02da861 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -17,9 +17,14 @@
 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.TestProtocol;
+import com.android.launcher3.Utilities;
 
 public class AccessibilityManagerCompat {
 
@@ -44,4 +49,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 + TestProtocol.RESPONSE_MESSAGE_POSTFIX, response);
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index fd1f0cc..3243256 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.UserHandle;
-import android.support.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
@@ -35,6 +34,8 @@
 import java.util.HashMap;
 import java.util.List;
 
+import androidx.annotation.Nullable;
+
 public abstract class AppWidgetManagerCompat {
 
     private static final Object sInstanceLock = new Object();
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
index 8430285..1065748 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
@@ -23,7 +23,6 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.support.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
@@ -38,6 +37,8 @@
 import java.util.Iterator;
 import java.util.List;
 
+import androidx.annotation.Nullable;
+
 class AppWidgetManagerCompatVL extends AppWidgetManagerCompat {
 
     private final UserManager mUserManager;
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
index 44158ed..b7b0563 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
@@ -18,7 +18,6 @@
 
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
-import android.support.annotation.Nullable;
 
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.PackageUserKey;
@@ -26,6 +25,8 @@
 import java.util.Collections;
 import java.util.List;
 
+import androidx.annotation.Nullable;
+
 class AppWidgetManagerCompatVO extends AppWidgetManagerCompatVL {
 
     AppWidgetManagerCompatVO(Context context) {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 2cac536..407355c 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -24,12 +24,15 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.UserHandle;
-import android.support.annotation.Nullable;
+
 import com.android.launcher3.Utilities;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.util.PackageUserKey;
+
 import java.util.List;
 
+import androidx.annotation.Nullable;
+
 public abstract class LauncherAppsCompat {
 
     public interface OnAppsChangedCallbackCompat {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
index cc3e5a7..ba0ac6e 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -29,15 +29,18 @@
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.util.ArrayMap;
+
 import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.util.PackageUserKey;
+
 import java.util.ArrayList;
 import java.util.List;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 public class LauncherAppsCompatVL extends LauncherAppsCompat {
 
     protected final LauncherApps mLauncherApps;
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index 173d0d1..386b3b3 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -28,7 +28,6 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.UserHandle;
-import android.support.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
@@ -42,6 +41,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import androidx.annotation.Nullable;
+
 @TargetApi(26)
 public class LauncherAppsCompatVO extends LauncherAppsCompatVL {
 
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index 3270ba2..7dad7e9 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -19,11 +19,12 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageInstaller;
-import android.support.annotation.NonNull;
 
 import java.util.HashMap;
 import java.util.List;
 
+import androidx.annotation.NonNull;
+
 public abstract class PackageInstallerCompat {
 
     public static final int STATUS_INSTALLED = 0;
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..8007754 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -16,9 +16,11 @@
 
 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;
 
+import android.animation.ValueAnimator;
 import android.content.ComponentName;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -31,6 +33,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 +148,7 @@
 
         // Hide soft keyboard, if visible
         UiThreadHelper.hideKeyboardAsync(mLauncher, mWindowToken);
+        AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_DISCOVERY_BOUNCE);
 
         mOptions = options;
         if (mOptions.systemDndStartPoint != null) {
@@ -224,6 +228,12 @@
         }
     }
 
+    public void addFirstFrameAnimationHelper(ValueAnimator anim) {
+        if (mDragObject != null && mDragObject.dragView != null) {
+            mDragObject.dragView.mFirstFrameAnimatorHelper.addTo(anim);
+        }
+    }
+
     /**
      * Call this from a drag source view like this:
      *
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 551567a..6ac56eb 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;
@@ -39,26 +41,21 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
-import android.support.animation.FloatPropertyCompat;
-import android.support.animation.SpringAnimation;
-import android.support.animation.SpringForce;
 import android.view.View;
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.FirstFrameAnimatorHelper;
 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,7 +67,9 @@
 import java.util.Arrays;
 import java.util.List;
 
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
 
 public class DragView extends View {
     private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
@@ -79,8 +78,6 @@
     public static final int COLOR_CHANGE_DURATION = 120;
     public static final int VIEW_ZOOM_DURATION = 150;
 
-    @Thunk static float sDragAlpha = 1f;
-
     private boolean mDrawBitmap = true;
     private Bitmap mBitmap;
     private Bitmap mCrossFadeBitmap;
@@ -97,6 +94,7 @@
     private final Launcher mLauncher;
     private final DragLayer mDragLayer;
     @Thunk final DragController mDragController;
+    final FirstFrameAnimatorHelper mFirstFrameAnimatorHelper;
     private boolean mHasDrawn = false;
     @Thunk float mCrossFadeProgress = 0f;
     private boolean mAnimationCancelled = false;
@@ -137,6 +135,7 @@
         mLauncher = launcher;
         mDragLayer = launcher.getDragLayer();
         mDragController = launcher.getDragController();
+        mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
 
         final float scale = (bitmap.getWidth() + finalScaleDps) / bitmap.getWidth();
 
@@ -145,22 +144,14 @@
         setScaleY(initialScale);
 
         // Animate the view into the correct position
-        mAnim = LauncherAnimUtils.ofFloat(0f, 1f);
+        mAnim = ValueAnimator.ofFloat(0f, 1f);
         mAnim.setDuration(VIEW_ZOOM_DURATION);
-        mAnim.addUpdateListener(new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                final float value = (Float) animation.getAnimatedValue();
-
-                setScaleX(initialScale + (value * (scale - initialScale)));
-                setScaleY(initialScale + (value * (scale - initialScale)));
-                if (sDragAlpha != 1f) {
-                    setAlpha(sDragAlpha * value + (1f - value));
-                }
-
-                if (getParent() == null) {
-                    animation.cancel();
-                }
+        mAnim.addUpdateListener(animation -> {
+            final float value = (Float) animation.getAnimatedValue();
+            setScaleX(initialScale + (value * (scale - initialScale)));
+            setScaleY(initialScale + (value * (scale - initialScale)));
+            if (!isAttachedToWindow()) {
+                animation.cancel();
             }
         });
 
@@ -198,7 +189,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 &&
@@ -481,15 +472,12 @@
     }
 
     public void crossFade(int duration) {
-        ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
+        ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
         va.setDuration(duration);
         va.setInterpolator(Interpolators.DEACCEL_1_5);
-        va.addUpdateListener(new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                mCrossFadeProgress = animation.getAnimatedFraction();
-                invalidate();
-            }
+        va.addUpdateListener(a -> {
+            mCrossFadeProgress = a.getAnimatedFraction();
+            invalidate();
         });
         va.start();
     }
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/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 9ae3775..6ef798d 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -27,10 +27,8 @@
 import android.animation.TimeInterpolator;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
-import android.support.v4.graphics.ColorUtils;
 import android.util.Property;
 import android.view.View;
 import android.view.animation.AnimationUtils;
@@ -38,7 +36,6 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
@@ -49,6 +46,8 @@
 
 import java.util.List;
 
+import androidx.core.graphics.ColorUtils;
+
 /**
  * Manages the opening and closing animations for a {@link Folder}.
  *
@@ -170,7 +169,7 @@
         float finalRadius = Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics());
 
         // Create the animators.
-        AnimatorSet a = LauncherAnimUtils.createAnimatorSet();
+        AnimatorSet a = new AnimatorSet();
 
         // Initialize the Folder items' text.
         PropertyResetListener colorResetListener =
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index cb5d872..87a0796 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -25,10 +25,7 @@
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.Region;
 import android.graphics.drawable.Drawable;
-import android.os.Parcelable;
-import android.support.annotation.NonNull;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.LayoutInflater;
@@ -70,6 +67,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import androidx.annotation.NonNull;
+
 /**
  * An icon that can appear on in the workspace representing an {@link Folder}.
  */
@@ -77,7 +76,6 @@
     @Thunk Launcher mLauncher;
     @Thunk Folder mFolder;
     private FolderInfo mInfo;
-    @Thunk static boolean sStaticValuesDirty = true;
 
     private CheckLongPressHelper mLongPressHelper;
     private StylusEventHelper mStylusEventHelper;
@@ -185,12 +183,6 @@
         return icon;
     }
 
-    @Override
-    protected Parcelable onSaveInstanceState() {
-        sStaticValuesDirty = true;
-        return super.onSaveInstanceState();
-    }
-
     public Folder getFolder() {
         return mFolder;
     }
@@ -490,10 +482,7 @@
 
     public void drawBadge(Canvas canvas) {
         if ((mBadgeInfo != null && mBadgeInfo.hasBadge()) || mBadgeScale > 0) {
-            int offsetX = mBackground.getOffsetX();
-            int offsetY = mBackground.getOffsetY();
-            int previewSize = (int) (mBackground.previewSize * mBackground.mScale);
-            mTempBounds.set(offsetX, offsetY, offsetX + previewSize, offsetY + previewSize);
+            BubbleTextView.getIconBounds(this, mTempBounds, mLauncher.getDeviceProfile().iconSizePx);
 
             // If we are animating to the accepting state, animate the badge out.
             float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress());
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 9be71f9..8439e79 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -493,7 +493,7 @@
         int delta = scroll - getScrollX();
         if (delta != 0) {
             mScroller.setInterpolator(Interpolators.DEACCEL);
-            mScroller.startScroll(getScrollX(), 0, delta, 0, Folder.SCROLL_HINT_DURATION);
+            mScroller.startScroll(getScrollX(), delta, Folder.SCROLL_HINT_DURATION);
             invalidate();
         }
     }
diff --git a/src/com/android/launcher3/folder/FolderPreviewItemAnim.java b/src/com/android/launcher3/folder/FolderPreviewItemAnim.java
index be075bc..1e56f7d 100644
--- a/src/com/android/launcher3/folder/FolderPreviewItemAnim.java
+++ b/src/com/android/launcher3/folder/FolderPreviewItemAnim.java
@@ -17,22 +17,41 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
+import android.animation.FloatArrayEvaluator;
+import android.animation.ObjectAnimator;
+import android.util.Property;
 
-import com.android.launcher3.LauncherAnimUtils;
+import java.util.Arrays;
 
 /**
  * Animates a Folder preview item.
  */
 class FolderPreviewItemAnim {
 
+    private static final Property<FolderPreviewItemAnim, float[]> PARAMS =
+            new Property<FolderPreviewItemAnim, float[]>(float[].class, "params") {
+                @Override
+                public float[] get(FolderPreviewItemAnim anim) {
+                    sTempParamsArray[0] = anim.mParams.scale;
+                    sTempParamsArray[1] = anim.mParams.transX;
+                    sTempParamsArray[2] = anim.mParams.transY;
+                    return sTempParamsArray;
+                }
+
+                @Override
+                public void set(FolderPreviewItemAnim anim, float[] value) {
+                    anim.setParams(value);
+                }
+            };
+
     private static PreviewItemDrawingParams sTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
+    private static final float[] sTempParamsArray = new float[3];
 
-    private ValueAnimator mValueAnimator;
+    private final ObjectAnimator mAnimator;
+    private final PreviewItemManager mItemManager;
+    private final PreviewItemDrawingParams mParams;
 
-    float finalScale;
-    float finalTransX;
-    float finalTransY;
+    public final float[] finalState;
 
     /**
      * @param params layout params to animate
@@ -43,33 +62,21 @@
      * @param duration duration in ms of the animation
      * @param onCompleteRunnable runnable to execute upon animation completion
      */
-    FolderPreviewItemAnim(final PreviewItemManager previewItemManager,
-            final PreviewItemDrawingParams params, int index0, int items0, int index1, int items1,
+    FolderPreviewItemAnim(PreviewItemManager itemManager,
+            PreviewItemDrawingParams params, int index0, int items0, int index1, int items1,
             int duration, final Runnable onCompleteRunnable) {
-        previewItemManager.computePreviewItemDrawingParams(index1, items1, sTmpParams);
+        mItemManager = itemManager;
+        mParams = params;
 
-        finalScale = sTmpParams.scale;
-        finalTransX = sTmpParams.transX;
-        finalTransY = sTmpParams.transY;
+        mItemManager.computePreviewItemDrawingParams(index1, items1, sTmpParams);
+        finalState = new float[] {sTmpParams.scale, sTmpParams.transX, sTmpParams.transY};
 
-        previewItemManager.computePreviewItemDrawingParams(index0, items0, sTmpParams);
+        mItemManager.computePreviewItemDrawingParams(index0, items0, sTmpParams);
+        float[] startState = new float[] {sTmpParams.scale, sTmpParams.transX, sTmpParams.transY};
 
-        final float scale0 = sTmpParams.scale;
-        final float transX0 = sTmpParams.transX;
-        final float transY0 = sTmpParams.transY;
-
-        mValueAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
-        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float progress = animation.getAnimatedFraction();
-
-                params.transX = transX0 + progress * (finalTransX - transX0);
-                params.transY = transY0 + progress * (finalTransY - transY0);
-                params.scale = scale0 + progress * (finalScale - scale0);
-                previewItemManager.onParamsChanged();
-            }
-        });
-        mValueAnimator.addListener(new AnimatorListenerAdapter() {
+        mAnimator = ObjectAnimator.ofObject(this, PARAMS, new FloatArrayEvaluator(),
+                startState, finalState);
+        mAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 if (onCompleteRunnable != null) {
@@ -78,20 +85,26 @@
                 params.anim = null;
             }
         });
-        mValueAnimator.setDuration(duration);
+        mAnimator.setDuration(duration);
+    }
+
+    private void setParams(float[] values) {
+        mParams.scale = values[0];
+        mParams.transX = values[1];
+        mParams.transY = values[2];
+        mItemManager.onParamsChanged();
     }
 
     public void start() {
-        mValueAnimator.start();
+        mAnimator.start();
     }
 
     public void cancel() {
-        mValueAnimator.cancel();
+        mAnimator.cancel();
     }
 
     public boolean hasEqualFinalState(FolderPreviewItemAnim anim) {
-        return finalTransY == anim.finalTransY && finalTransX == anim.finalTransX &&
-                finalScale == anim.finalScale;
+        return Arrays.equals(finalState, anim.finalState);
 
     }
 }
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index 069ec4b..ceb1a8c 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -30,16 +30,17 @@
 import android.graphics.RadialGradient;
 import android.graphics.Region;
 import android.graphics.Shader;
-import android.support.v4.graphics.ColorUtils;
 import android.util.Property;
 import android.view.View;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
 import com.android.launcher3.util.Themes;
 
+import androidx.core.graphics.ColorUtils;
+
 /**
  * This object represents a FolderIcon preview background. It stores drawing / measurement
  * information, handles drawing, and animation (accept state <--> rest state).
@@ -70,6 +71,7 @@
     float mScale = 1f;
     private float mColorMultiplier = 1f;
     private int mBgColor;
+    private int mBadgeColor;
     private float mStrokeWidth;
     private int mStrokeAlpha = MAX_BG_OPACITY;
     private int mShadowAlpha = 255;
@@ -132,6 +134,7 @@
                       int availableSpaceX, int topPadding) {
         mInvalidateDelegate = invalidateDelegate;
         mBgColor = Themes.getAttrColor(launcher, android.R.attr.colorPrimary);
+        mBadgeColor = Themes.getAttrColor(launcher, R.attr.folderBadgeColor);
 
         DeviceProfile grid = launcher.getDeviceProfile();
         previewSize = grid.folderIconSizePx;
@@ -198,7 +201,7 @@
     }
 
     public int getBadgeColor() {
-        return mBgColor;
+        return mBadgeColor;
     }
 
     public void drawBackground(Canvas canvas) {
@@ -365,7 +368,7 @@
             mScaleAnimator.cancel();
         }
 
-        mScaleAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
+        mScaleAnimator = ValueAnimator.ofFloat(0f, 1.0f);
 
         mScaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
@@ -397,37 +400,19 @@
         mScaleAnimator.start();
     }
 
-    public void animateToAccept(final CellLayout cl, final int cellX, final int cellY) {
-        Runnable onStart = new Runnable() {
-            @Override
-            public void run() {
-                delegateDrawing(cl, cellX, cellY);
-            }
-        };
-        animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER, onStart, null);
+    public void animateToAccept(CellLayout cl, int cellX, int cellY) {
+        animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER,
+                () -> delegateDrawing(cl, cellX, cellY), null);
     }
 
     public void animateToRest() {
         // This can be called multiple times -- we need to make sure the drawing delegate
         // is saved and restored at the beginning of the animation, since cancelling the
         // existing animation can clear the delgate.
-        final CellLayout cl = mDrawingDelegate;
-        final int cellX = delegateCellX;
-        final int cellY = delegateCellY;
-
-        Runnable onStart = new Runnable() {
-            @Override
-            public void run() {
-                delegateDrawing(cl, cellX, cellY);
-            }
-        };
-        Runnable onEnd = new Runnable() {
-            @Override
-            public void run() {
-                clearDrawingDelegate();
-            }
-        };
-        animateScale(1f, 1f, onStart, onEnd);
+        CellLayout cl = mDrawingDelegate;
+        int cellX = delegateCellX;
+        int cellY = delegateCellY;
+        animateScale(1f, 1f, () -> delegateDrawing(cl, cellX, cellY), this::clearDrawingDelegate);
     }
 
     public int getBackgroundAlpha() {
diff --git a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
index 607b7ca..c818462 100644
--- a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
+++ b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
@@ -40,8 +40,8 @@
         // We ensure the update will not interfere with an animation on the layout params
         // If the final values differ, we cancel the animation.
         if (anim != null) {
-            if (anim.finalTransX == transX || anim.finalTransY == transY
-                    || anim.finalScale == scale) {
+            if (anim.finalState[1] == transX || anim.finalState[2] == transY
+                    || anim.finalState[0] == scale) {
                 return;
             }
             anim.cancel();
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 1f69f6e..0004e1e 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -16,13 +16,17 @@
 
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.support.annotation.NonNull;
 import android.view.View;
 import android.widget.TextView;
 
@@ -33,10 +37,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
-import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION;
+import androidx.annotation.NonNull;
 
 /**
  * Manages the drawing and animations of {@link PreviewItemDrawingParams} for a {@link FolderIcon}.
diff --git a/src/com/android/launcher3/graphics/BitmapInfo.java b/src/com/android/launcher3/graphics/BitmapInfo.java
index ab906e2..c905a78 100644
--- a/src/com/android/launcher3/graphics/BitmapInfo.java
+++ b/src/com/android/launcher3/graphics/BitmapInfo.java
@@ -35,9 +35,15 @@
     }
 
     public static BitmapInfo fromBitmap(Bitmap bitmap) {
+        return fromBitmap(bitmap, null);
+    }
+
+    public static BitmapInfo fromBitmap(Bitmap bitmap, ColorExtractor dominantColorExtractor) {
         BitmapInfo info = new BitmapInfo();
         info.icon = bitmap;
-        info.color = ColorExtractor.findDominantColorByHue(bitmap);
+        info.color = dominantColorExtractor != null
+                ? dominantColorExtractor.findDominantColorByHue(bitmap)
+                : 0;
         return info;
     }
 }
diff --git a/src/com/android/launcher3/graphics/ColorExtractor.java b/src/com/android/launcher3/graphics/ColorExtractor.java
index e9d72b7..da5da9c 100644
--- a/src/com/android/launcher3/graphics/ColorExtractor.java
+++ b/src/com/android/launcher3/graphics/ColorExtractor.java
@@ -18,22 +18,32 @@
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.util.SparseArray;
+import java.util.Arrays;
 
 /**
  * Utility class for extracting colors from a bitmap.
  */
 public class ColorExtractor {
 
-    public static int findDominantColorByHue(Bitmap bitmap) {
-        return findDominantColorByHue(bitmap, 20);
+    private final int NUM_SAMPLES = 20;
+    private final float[] mTmpHsv = new float[3];
+    private final float[] mTmpHueScoreHistogram = new float[360];
+    private final int[] mTmpPixels = new int[NUM_SAMPLES];
+    private final SparseArray<Float> mTmpRgbScores = new SparseArray<>();
+
+    /**
+     * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
+     * @param bitmap The bitmap to scan
+     */
+    public int findDominantColorByHue(Bitmap bitmap) {
+        return findDominantColorByHue(bitmap, NUM_SAMPLES);
     }
 
     /**
      * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
      * @param bitmap The bitmap to scan
-     * @param samples The approximate max number of samples to use.
      */
-    public static int findDominantColorByHue(Bitmap bitmap, int samples) {
+    public int findDominantColorByHue(Bitmap bitmap, int samples) {
         final int height = bitmap.getHeight();
         final int width = bitmap.getWidth();
         int sampleStride = (int) Math.sqrt((height * width) / samples);
@@ -42,15 +52,18 @@
         }
 
         // This is an out-param, for getting the hsv values for an rgb
-        float[] hsv = new float[3];
+        float[] hsv = mTmpHsv;
+        Arrays.fill(hsv, 0);
 
         // First get the best hue, by creating a histogram over 360 hue buckets,
         // where each pixel contributes a score weighted by saturation, value, and alpha.
-        float[] hueScoreHistogram = new float[360];
+        float[] hueScoreHistogram = mTmpHueScoreHistogram;
+        Arrays.fill(hueScoreHistogram, 0);
         float highScore = -1;
         int bestHue = -1;
 
-        int[] pixels = new int[samples];
+        int[] pixels = mTmpPixels;
+        Arrays.fill(pixels, 0);
         int pixelCount = 0;
 
         for (int y = 0; y < height; y += sampleStride) {
@@ -82,7 +95,8 @@
             }
         }
 
-        SparseArray<Float> rgbScores = new SparseArray<>();
+        SparseArray<Float> rgbScores = mTmpRgbScores;
+        rgbScores.clear();
         int bestColor = 0xff000000;
         highScore = -1;
         // Go back over the RGB colors that match the winning hue,
diff --git a/src/com/android/launcher3/graphics/ColorScrim.java b/src/com/android/launcher3/graphics/ColorScrim.java
index 96d93d8..5c1081a 100644
--- a/src/com/android/launcher3/graphics/ColorScrim.java
+++ b/src/com/android/launcher3/graphics/ColorScrim.java
@@ -17,7 +17,6 @@
 
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.support.v4.graphics.ColorUtils;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -25,6 +24,8 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 
+import androidx.core.graphics.ColorUtils;
+
 /**
  * Simple scrim which draws a color
  */
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index bbc013d..5bc81e6 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -28,9 +28,9 @@
 import android.graphics.drawable.Drawable;
 import android.os.Process;
 import android.os.UserHandle;
-import android.support.annotation.UiThread;
 import android.util.ArrayMap;
 import android.util.Log;
+
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.R;
@@ -38,6 +38,8 @@
 import com.android.launcher3.allapps.AllAppsBackgroundDrawable;
 import com.android.launcher3.util.ResourceBasedOverride;
 
+import androidx.annotation.UiThread;
+
 /**
  * Factory for creating new drawables.
  */
diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
index a2a0801..df00815 100644
--- a/src/com/android/launcher3/graphics/IconNormalizer.java
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -24,22 +24,23 @@
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PorterDuff;
-import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.util.Log;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 
 import java.nio.ByteBuffer;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 public class IconNormalizer {
 
     private static final String TAG = "IconNormalizer";
diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
index 9c3b77e..cda07c3 100644
--- a/src/com/android/launcher3/graphics/IconPalette.java
+++ b/src/com/android/launcher3/graphics/IconPalette.java
@@ -19,12 +19,13 @@
 import android.app.Notification;
 import android.content.Context;
 import android.graphics.Color;
-import android.support.v4.graphics.ColorUtils;
 import android.util.Log;
 
 import com.android.launcher3.R;
 import com.android.launcher3.util.Themes;
 
+import androidx.core.graphics.ColorUtils;
+
 /**
  * Contains colors based on the dominant color of an icon.
  */
diff --git a/src/com/android/launcher3/graphics/IconShapeOverride.java b/src/com/android/launcher3/graphics/IconShapeOverride.java
index 223243b..cadc6e3 100644
--- a/src/com/android/launcher3/graphics/IconShapeOverride.java
+++ b/src/com/android/launcher3/graphics/IconShapeOverride.java
@@ -30,7 +30,6 @@
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceChangeListener;
 import android.provider.Settings;
-import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -42,6 +41,8 @@
 
 import java.lang.reflect.Field;
 
+import androidx.annotation.NonNull;
+
 /**
  * Utility class to override shape of {@link android.graphics.drawable.AdaptiveIconDrawable}.
  */
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 333fe59..087362c 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -41,7 +41,6 @@
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
-import android.support.annotation.Nullable;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FastBitmapDrawable;
@@ -57,6 +56,8 @@
 import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Themes;
 
+import androidx.annotation.Nullable;
+
 /**
  * Helper methods for generating various launcher icons
  */
@@ -90,6 +91,7 @@
         synchronized (sPoolSync) {
             // Clear any temporary state variables
             mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
+            mDisableColorExtractor = false;
 
             next = sPool;
             sPool = this;
@@ -105,6 +107,8 @@
     private final Context mContext;
     private final Canvas mCanvas;
     private final PackageManager mPm;
+    private final ColorExtractor mColorExtractor;
+    private boolean mDisableColorExtractor;
 
     private final int mFillResIconDpi;
     private final int mIconBitmapSize;
@@ -121,6 +125,7 @@
     private LauncherIcons(Context context) {
         mContext = context.getApplicationContext();
         mPm = mContext.getPackageManager();
+        mColorExtractor = new ColorExtractor();
 
         InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
         mFillResIconDpi = idp.fillResIconDpi;
@@ -196,7 +201,7 @@
      * The bitmap is also visually normalized with other icons.
      */
     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
-            boolean isInstantApp, float [] scale) {
+            boolean isInstantApp, float[] scale) {
         if (scale == null) {
             scale = new float[1];
         }
@@ -223,7 +228,7 @@
         } else {
             result = bitmap;
         }
-        return BitmapInfo.fromBitmap(result);
+        return BitmapInfo.fromBitmap(result, mDisableColorExtractor ? null : mColorExtractor);
     }
 
     /**
@@ -245,6 +250,14 @@
         mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
     }
 
+    /**
+     * Disables the dominant color extraction for all icons loaded through this session (until
+     * this instance is recycled).
+     */
+    public void disableColorExtraction() {
+        mDisableColorExtractor = true;
+    }
+
     private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
             RectF outIconBounds, float[] outScale) {
         float scale = 1f;
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 5fbf502..d2d1699 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -27,10 +27,11 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.RectF;
-import android.support.v4.graphics.ColorUtils;
 
 import com.android.launcher3.LauncherAppState;
 
+import androidx.core.graphics.ColorUtils;
+
 /**
  * Utility class to add shadows to bitmaps.
  */
@@ -120,19 +121,19 @@
         }
 
         public Builder setupBlurForSize(int height) {
-            shadowBlur = height * 1f / 32;
+            shadowBlur = height * 1f / 24;
             keyShadowDistance = height * 1f / 16;
             return this;
         }
 
         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/graphics/TriangleShape.java b/src/com/android/launcher3/graphics/TriangleShape.java
index cce4e3c..2c15725 100644
--- a/src/com/android/launcher3/graphics/TriangleShape.java
+++ b/src/com/android/launcher3/graphics/TriangleShape.java
@@ -19,7 +19,8 @@
 import android.graphics.Outline;
 import android.graphics.Path;
 import android.graphics.drawable.shapes.PathShape;
-import android.support.annotation.NonNull;
+
+import androidx.annotation.NonNull;
 
 /**
  * Wrapper around {@link android.graphics.drawable.shapes.PathShape}
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index bc4a06d..00cc1a7 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -34,7 +34,6 @@
 import android.graphics.Region;
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
-import android.support.v4.graphics.ColorUtils;
 import android.util.DisplayMetrics;
 import android.util.Property;
 import android.view.View;
@@ -47,6 +46,8 @@
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.util.Themes;
 
+import androidx.core.graphics.ColorUtils;
+
 /**
  * View scrim which draws behind hotseat and workspace
  */
diff --git a/src/com/android/launcher3/keyboard/FocusedItemDecorator.java b/src/com/android/launcher3/keyboard/FocusedItemDecorator.java
index 05ae406..2476a6f 100644
--- a/src/com/android/launcher3/keyboard/FocusedItemDecorator.java
+++ b/src/com/android/launcher3/keyboard/FocusedItemDecorator.java
@@ -17,14 +17,15 @@
 package com.android.launcher3.keyboard;
 
 import android.graphics.Canvas;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.ItemDecoration;
-import android.support.v7.widget.RecyclerView.State;
 import android.view.View;
 import android.view.View.OnFocusChangeListener;
 
 import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
 
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
+import androidx.recyclerview.widget.RecyclerView.State;
+
 /**
  * {@link ItemDecoration} for drawing and animating focused view background.
  */
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 7087fdb..a318dc0 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -32,7 +32,6 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.SystemClock;
-import android.support.annotation.Nullable;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewParent;
@@ -55,6 +54,8 @@
 import java.util.Locale;
 import java.util.UUID;
 
+import androidx.annotation.Nullable;
+
 /**
  * Manages the creation of {@link LauncherEvent}.
  * To debug this class, execute following command before side loading a new apk.
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/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java
index f186e95..b353810 100644
--- a/src/com/android/launcher3/model/ModelPreload.java
+++ b/src/com/android/launcher3/model/ModelPreload.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.model;
 
 import android.content.Context;
-import android.support.annotation.WorkerThread;
 import android.util.Log;
 
 import com.android.launcher3.AllAppsList;
@@ -26,6 +25,8 @@
 
 import java.util.concurrent.Executor;
 
+import androidx.annotation.WorkerThread;
+
 /**
  * Utility class to preload LauncherModel
  */
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 9f8f263..448ff6c 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -8,7 +8,6 @@
 import android.content.pm.PackageManager;
 import android.os.Process;
 import android.os.UserHandle;
-import android.support.annotation.Nullable;
 import android.util.Log;
 
 import com.android.launcher3.AppFilter;
@@ -34,6 +33,8 @@
 import java.util.Iterator;
 import java.util.Map;
 
+import androidx.annotation.Nullable;
+
 /**
  * Widgets data model that is used by the adapters of the widget views and controllers.
  *
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index 1216a27..c7de5b0 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -29,7 +29,6 @@
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyListBuilder;
@@ -151,15 +150,16 @@
 
     public void animateFirstNotificationTo(Rect toBounds,
             final IconAnimationEndListener callback) {
-        AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+        AnimatorSet animation = new AnimatorSet();
         final View firstNotification = mIconRow.getChildAt(mIconRow.getChildCount() - 1);
 
         Rect fromBounds = sTempRect;
         firstNotification.getGlobalVisibleRect(fromBounds);
         float scale = (float) toBounds.height() / fromBounds.height();
-        Animator moveAndScaleIcon = LauncherAnimUtils.ofPropertyValuesHolder(firstNotification,
-                new PropertyListBuilder().scale(scale).translationY(toBounds.top - fromBounds.top
-                        + (fromBounds.height() * scale - fromBounds.height()) / 2).build());
+        Animator moveAndScaleIcon = new PropertyListBuilder().scale(scale)
+                .translationY(toBounds.top - fromBounds.top
+                        + (fromBounds.height() * scale - fromBounds.height()) / 2)
+                .build(firstNotification);
         moveAndScaleIcon.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
index bf7ae1a..508cf87 100644
--- a/src/com/android/launcher3/notification/NotificationKeyData.java
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -18,11 +18,12 @@
 
 import android.app.Notification;
 import android.service.notification.StatusBarNotification;
-import android.support.annotation.NonNull;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import androidx.annotation.NonNull;
+
 /**
  * The key data associated with the notification, used to determine what to include
  * in badges and dummy popup views before they are populated.
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index ac5aaf8..4c85c8b 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -27,7 +27,6 @@
 import android.os.Message;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
-import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
@@ -45,6 +44,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import androidx.annotation.Nullable;
+
 /**
  * A {@link NotificationListenerService} that sends updates to its
  * {@link NotificationsChangedListener} when notifications are posted or canceled,
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index e157482..3bf8bef 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -340,7 +340,7 @@
     private void animateOpen() {
         setVisibility(View.VISIBLE);
 
-        final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
+        final AnimatorSet openAnim = new AnimatorSet();
         final Resources res = getResources();
         final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
         final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
@@ -388,7 +388,7 @@
         }
         mIsOpen = false;
 
-        final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
+        final AnimatorSet closeAnim = new AnimatorSet();
         // Hide the arrow
         closeAnim.play(ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0));
         closeAnim.play(ObjectAnimator.ofFloat(mArrow, ALPHA, 0));
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/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index f1b8ec0..3faec44 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -18,7 +18,6 @@
 
 import android.content.ComponentName;
 import android.service.notification.StatusBarNotification;
-import android.support.annotation.NonNull;
 import android.util.Log;
 
 import com.android.launcher3.ItemInfo;
@@ -41,6 +40,8 @@
 import java.util.List;
 import java.util.Map;
 
+import androidx.annotation.NonNull;
+
 /**
  * Provides data for the popup menu that appears after long-clicking on apps.
  */
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index b295bb2..4500629 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -20,8 +20,6 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
@@ -40,6 +38,9 @@
 import java.util.Iterator;
 import java.util.List;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 /**
  * Contains logic relevant to populating a {@link PopupContainerWithArrow}. In particular,
  * this class determines which items appear in the container, and in what order.
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 3c1cc90..693e532 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -75,6 +75,7 @@
         public View.OnClickListener getOnClickListener(
                 BaseDraggingActivity activity, ItemInfo itemInfo) {
             return (view) -> {
+                dismissTaskMenuView(activity);
                 Rect sourceBounds = activity.getViewBounds(view);
                 Bundle opts = activity.getActivityLaunchOptionsAsBundle(view);
                 new PackageManagerHelper(activity).startDetailsActivityForInfo(
@@ -117,4 +118,9 @@
             };
         }
     }
+
+    protected static void dismissTaskMenuView(BaseDraggingActivity activity) {
+        AbstractFloatingView.closeOpenViews(activity, true,
+            AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
+    }
 }
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..fd9157e 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -39,10 +39,12 @@
 import com.android.launcher3.LauncherStateManager.AnimationComponents;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.TestProtocol;
 import com.android.launcher3.Utilities;
 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 +517,9 @@
                 logReachedState(logAction, targetState);
             }
             mLauncher.getStateManager().goToState(targetState, false /* animated */);
+
+            AccessibilityManagerCompat.sendEventToTest(
+                    mLauncher, TestProtocol.SWITCHED_TO_STATE_MESSAGE);
         }
     }
 
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/OverScroll.java b/src/com/android/launcher3/touch/OverScroll.java
index dc801ec..bf895ad 100644
--- a/src/com/android/launcher3/touch/OverScroll.java
+++ b/src/com/android/launcher3/touch/OverScroll.java
@@ -20,7 +20,7 @@
  */
 public class OverScroll {
 
-    private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
+    public static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
 
     /**
      * This curve determines how the effect of scrolling over the limits of the page diminishes
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index 703e4fd..6ffc0ef 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -19,12 +19,13 @@
 
 import android.content.Context;
 import android.graphics.PointF;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
 /**
  * One dimensional scroll/drag/swipe gesture detector.
  *
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/ListViewHighlighter.java b/src/com/android/launcher3/util/ListViewHighlighter.java
index ecad2af..360546e 100644
--- a/src/com/android/launcher3/util/ListViewHighlighter.java
+++ b/src/com/android/launcher3/util/ListViewHighlighter.java
@@ -21,7 +21,6 @@
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.support.v4.graphics.ColorUtils;
 import android.view.View;
 import android.view.View.OnLayoutChangeListener;
 import android.widget.AbsListView;
@@ -31,6 +30,8 @@
 
 import com.android.launcher3.R;
 
+import androidx.core.graphics.ColorUtils;
+
 /**
  * Utility class to scroll and highlight a list view item
  */
diff --git a/src/com/android/launcher3/util/OverScroller.java b/src/com/android/launcher3/util/OverScroller.java
new file mode 100644
index 0000000..d697ece
--- /dev/null
+++ b/src/com/android/launcher3/util/OverScroller.java
@@ -0,0 +1,779 @@
+/*
+ * 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.animation.TimeInterpolator;
+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;
+
+/**
+ * Based on {@link android.widget.OverScroller} supporting only 1-d scrolling and with more
+ * customization options.
+ */
+public class OverScroller {
+    private int mMode;
+
+    private final SplineOverScroller mScroller;
+
+    private TimeInterpolator 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.
+     */
+    public OverScroller(Context context, Interpolator interpolator, boolean flywheel) {
+        if (interpolator == null) {
+            mInterpolator = SCROLL;
+        } else {
+            mInterpolator = interpolator;
+        }
+        mFlywheel = flywheel;
+        mScroller = new SplineOverScroller(context);
+    }
+
+    public void setInterpolator(TimeInterpolator 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) {
+        mScroller.setFriction(friction);
+    }
+
+    /**
+     *
+     * Returns whether the scroller has finished scrolling.
+     *
+     * @return True if the scroller has finished scrolling, false otherwise.
+     */
+    public final boolean isFinished() {
+        return mScroller.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) {
+        mScroller.mFinished = finished;
+    }
+
+    /**
+     * Returns the current offset in the scroll.
+     *
+     * @return The new offset as an absolute distance from the origin.
+     */
+    public final int getCurrPos() {
+        return mScroller.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 mScroller.mCurrVelocity;
+    }
+
+    /**
+     * Returns the start offset in the scroll.
+     *
+     * @return The start offset as an absolute distance from the origin.
+     */
+    public final int getStartPos() {
+        return mScroller.mStart;
+    }
+
+    /**
+     * Returns where the scroll will end. Valid only for "fling" scrolls.
+     *
+     * @return The final offset as an absolute distance from the origin.
+     */
+    public final int getFinalPos() {
+        return mScroller.mFinal;
+    }
+
+    /**
+     * Returns how long the scroll event will take, in milliseconds.
+     *
+     * @return The duration of the scroll in milliseconds.
+     */
+    public final int getDuration() {
+        return mScroller.mDuration;
+    }
+
+    /**
+     * Extend the scroll animation. This allows a running animation to scroll
+     * further and longer, when used with {@link #setFinalPos(int)}.
+     *
+     * @param extend Additional time to scroll in milliseconds.
+     * @see #setFinalPos(int)
+     */
+    public void extendDuration(int extend) {
+        mScroller.extendDuration(extend);
+    }
+
+    /**
+     * Sets the final position for this scroller.
+     *
+     * @param newPos The new offset as an absolute distance from the origin.
+     * @see #extendDuration(int)
+     */
+    public void setFinalPos(int newPos) {
+        mScroller.setFinalPosition(newPos);
+    }
+
+    /**
+     * 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 - mScroller.mStartTime;
+
+                final int duration = mScroller.mDuration;
+                if (elapsedTime < duration) {
+                    final float q = mInterpolator.getInterpolation(elapsedTime / (float) duration);
+                    mScroller.updateScroll(q);
+                } else {
+                    abortAnimation();
+                }
+                break;
+
+            case FLING_MODE:
+                if (!mScroller.mFinished) {
+                    if (!mScroller.update()) {
+                        if (!mScroller.continueWhenFinished()) {
+                            mScroller.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 start Starting horizontal scroll offset in pixels. Positive
+     *        numbers will scroll the content to the left.
+     * @param delta Distance to travel. Positive numbers will scroll the
+     *        content to the left.
+     */
+    public void startScroll(int start, int delta) {
+        startScroll(start, delta, DEFAULT_DURATION);
+    }
+
+    /**
+     * Start scrolling by providing a starting point and the distance to travel.
+     *
+     * @param start Starting scroll offset in pixels. Positive
+     *        numbers will scroll the content to the left.
+     * @param delta Distance to travel. Positive numbers will scroll the
+     *        content to the left.
+     * @param duration Duration of the scroll in milliseconds.
+     */
+    public void startScroll(int start, int delta, int duration) {
+        mMode = SCROLL_MODE;
+        mScroller.startScroll(start, delta, duration);
+    }
+
+    /**
+     * Call this when you want to 'spring back' into a valid coordinate range.
+     *
+     * @param start Starting X coordinate
+     * @param min Minimum valid X value
+     * @param max Maximum valid X value
+     * @return true if a springback was initiated, false if startX and startY were
+     *          already within the valid range.
+     */
+    public boolean springBack(int start, int min, int max) {
+        mMode = FLING_MODE;
+        return mScroller.springback(start, min, max);
+    }
+
+    public void fling(int start, int velocity, int min, int max) {
+        fling(start, velocity, min, max, 0);
+    }
+
+    /**
+     * Start scrolling based on a fling gesture. The distance traveled will
+     * depend on the initial velocity of the fling.
+     *  @param start Starting point of the scroll (X)
+     * @param velocity Initial velocity of the fling (X) measured in pixels per
+     *            second.
+     * @param min 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 max 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 over Overfling range. If > 0, horizontal overfling in either
+*            direction will be possible.
+     */
+    public void fling(int start, int velocity, int min, int max, int over) {
+        // Continue a scroll or fling in progress
+        if (mFlywheel && !isFinished()) {
+            float oldVelocityX = mScroller.mCurrVelocity;
+            if (Math.signum(velocity) == Math.signum(oldVelocityX)) {
+                velocity += oldVelocityX;
+            }
+        }
+
+        mMode = FLING_MODE;
+        mScroller.fling(start, velocity, min, max, over);
+    }
+
+    /**
+     * 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 start Starting/current X position
+     * @param finalPos Desired final X position
+     * @param over Magnitude of overscroll allowed. This should be the maximum
+     */
+    public void notifyEdgeReached(int start, int finalPos, int over) {
+        mScroller.notifyEdgeReached(start, finalPos, over);
+    }
+
+    /**
+     * 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)} method.
+     *
+     * One should check this value before calling
+     * {@link #startScroll(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 (!mScroller.mFinished && mScroller.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() {
+        mScroller.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();
+        return (int) (time - mScroller.mStartTime);
+    }
+
+    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;
+            mSplineDistance = mFinal - mStart;
+            mFinished = false;
+        }
+
+        void extendDuration(int extend) {
+            final long time = AnimationUtils.currentAnimationTimeMillis();
+            final int elapsedTime = (int) (time - mStartTime);
+            mDuration  = mSplineDuration = 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/DoubleShadowBubbleTextView.java b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
index a11a8c5..64e166e 100644
--- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
+++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
@@ -20,13 +20,14 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.support.v4.graphics.ColorUtils;
 import android.util.AttributeSet;
 import android.widget.TextView;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
 
+import androidx.core.graphics.ColorUtils;
+
 /**
  * Extension of {@link BubbleTextView} which draws two shadows on the text (ambient and key shadows}
  */
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 5046639..3e58ea6 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -43,6 +43,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import androidx.annotation.VisibleForTesting;
+
 /**
  * Popup shown on long pressing an empty space in launcher
  */
@@ -133,6 +135,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..f0d6de2 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -24,7 +24,6 @@
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.MotionEvent;
@@ -35,10 +34,11 @@
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.FastScrollThumbDrawable;
 import com.android.launcher3.util.Themes;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 /**
  * The track and scrollbar that shows when you scroll the list.
  */
@@ -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/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 7066980..6fd84db 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -16,8 +16,6 @@
 package com.android.launcher3.views;
 
 import static android.content.Context.ACCESSIBILITY_SERVICE;
-import static android.support.v4.graphics.ColorUtils.compositeColors;
-import static android.support.v4.graphics.ColorUtils.setAlphaComponent;
 import static android.view.MotionEvent.ACTION_DOWN;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -25,6 +23,9 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 
+import static androidx.core.graphics.ColorUtils.compositeColors;
+import static androidx.core.graphics.ColorUtils.setAlphaComponent;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.Keyframe;
@@ -38,12 +39,6 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
-import android.support.v4.widget.ExploreByTouchHelper;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.KeyEvent;
@@ -68,6 +63,13 @@
 
 import java.util.List;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
+import androidx.customview.widget.ExploreByTouchHelper;
+
 /**
  * Simple scrim which draws a flat color
  */
diff --git a/src/com/android/launcher3/views/SpringRelativeLayout.java b/src/com/android/launcher3/views/SpringRelativeLayout.java
index b0313ce..d0ec9d7 100644
--- a/src/com/android/launcher3/views/SpringRelativeLayout.java
+++ b/src/com/android/launcher3/views/SpringRelativeLayout.java
@@ -15,24 +15,25 @@
  */
 package com.android.launcher3.views;
 
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
+
 import android.content.Context;
 import android.graphics.Canvas;
-import android.support.animation.DynamicAnimation;
-import android.support.animation.FloatPropertyCompat;
-import android.support.animation.SpringAnimation;
-import android.support.animation.SpringForce;
-import android.support.annotation.NonNull;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.EdgeEffectFactory;
 import android.util.AttributeSet;
 import android.util.SparseBooleanArray;
 import android.view.View;
 import android.widget.EdgeEffect;
 import android.widget.RelativeLayout;
 
-import static android.support.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
-import static android.support.animation.SpringForce.STIFFNESS_LOW;
-import static android.support.animation.SpringForce.STIFFNESS_MEDIUM;
+import androidx.annotation.NonNull;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory;
 
 public class SpringRelativeLayout extends RelativeLayout {
 
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/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
index d67f403..2ba672d 100644
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.widget;
 
-import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 
 import com.android.launcher3.IconCache;
@@ -26,6 +25,8 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 /**
  * Do diff on widget's tray list items and call the {@link RecyclerView.Adapter}
  * methods accordingly.
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index e94d81d..f7ff69a 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -35,6 +35,8 @@
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.TopRoundedCornerView;
 
+import androidx.annotation.VisibleForTesting;
+
 /**
  * Popup for showing the full list of available widgets
  */
@@ -159,7 +161,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 +208,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 +221,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/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 0147ea4..1016d04 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -16,8 +16,6 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.Adapter;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -30,14 +28,15 @@
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.LabelComparator;
-import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Iterator;
 import java.util.List;
 
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+
 /**
  * List view adapter for the widget tray.
  *
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 124058e..641183a 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -18,9 +18,6 @@
 
 import android.content.Context;
 import android.graphics.Point;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.OnItemTouchListener;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -28,6 +25,10 @@
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.R;
 
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
+
 /**
  * The widgets recycler view.
  */
diff --git a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/WidgetsRowViewHolder.java
index 8f269a6..d26edb6 100644
--- a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java
+++ b/src/com/android/launcher3/widget/WidgetsRowViewHolder.java
@@ -15,12 +15,13 @@
  */
 package com.android.launcher3.widget;
 
-import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.view.ViewGroup;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
 
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
 public class WidgetsRowViewHolder extends ViewHolder {
 
     public final ViewGroup cellContainer;
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 5a7e50f..0d727fd 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -48,6 +48,8 @@
 
     public static void onStart(Launcher launcher) { }
 
+    public static void onEnterAnimationComplete(Context context) {}
+
     public static void onLauncherStateOrResumeChanged(Launcher launcher) { }
 
     public static void onTrimMemory(Launcher launcher, int level) { }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
index 21b324f..5a1f9ca 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
@@ -17,9 +17,6 @@
 package com.android.launcher3.uioverrides.dynamicui;
 
 import android.graphics.Color;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.graphics.ColorUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Range;
@@ -30,6 +27,10 @@
 import java.util.LinkedList;
 import java.util.List;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.graphics.ColorUtils;
+
 /**
  * Implementation of tonal color extraction
  **/
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java
index 5c533ff..0fd0a35 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java
@@ -17,10 +17,11 @@
 package com.android.launcher3.uioverrides.dynamicui;
 
 import android.content.Context;
-import android.support.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
 
+import androidx.annotation.Nullable;
+
 public abstract class WallpaperManagerCompat {
 
     private static final Object sInstanceLock = new Object();
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java
index 4a8bbbd..7883442 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java
@@ -18,7 +18,6 @@
 import static android.app.WallpaperManager.FLAG_SYSTEM;
 
 import static com.android.launcher3.Utilities.getDevicePrefs;
-import static com.android.launcher3.graphics.ColorExtractor.findDominantColorByHue;
 
 import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
@@ -42,15 +41,17 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.ParcelFileDescriptor;
-import android.support.annotation.Nullable;
 import android.util.Log;
 import android.util.Pair;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.ColorExtractor;
 
 import java.io.IOException;
 import java.util.ArrayList;
 
+import androidx.annotation.Nullable;
+
 public class WallpaperManagerCompatVL extends WallpaperManagerCompat {
 
     private static final String TAG = "WMCompatVL";
@@ -169,6 +170,7 @@
 
         private HandlerThread mWorkerThread;
         private Handler mWorkerHandler;
+        private ColorExtractor mColorExtractor;
 
         @Override
         public void onCreate() {
@@ -176,6 +178,7 @@
             mWorkerThread = new HandlerThread("ColorExtractionService");
             mWorkerThread.start();
             mWorkerHandler = new Handler(mWorkerThread.getLooper());
+            mColorExtractor = new ColorExtractor();
         }
 
         @Override
@@ -258,7 +261,8 @@
             String value = VERSION_PREFIX + wallpaperId;
 
             if (bitmap != null) {
-                int color = findDominantColorByHue(bitmap, MAX_WALLPAPER_EXTRACTION_AREA);
+                int color = mColorExtractor.findDominantColorByHue(bitmap,
+                        MAX_WALLPAPER_EXTRACTION_AREA);
                 value += "," + color;
             }
 
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVOMR1.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVOMR1.java
index 4509e05..f34497d 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVOMR1.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVOMR1.java
@@ -21,11 +21,12 @@
 import android.app.WallpaperManager.OnColorsChangedListener;
 import android.content.Context;
 import android.graphics.Color;
-import android.support.annotation.Nullable;
 import android.util.Log;
 
 import java.lang.reflect.Method;
 
+import androidx.annotation.Nullable;
+
 @TargetApi(27)
 public class WallpaperManagerCompatVOMR1 extends WallpaperManagerCompat {
 
diff --git a/tests/Android.mk b/tests/Android.mk
index f6f02fe..03cb154 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -17,7 +17,11 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
+LOCAL_STATIC_JAVA_LIBRARIES := \
+	android-support-test \
+	ub-uiautomator \
+	mockito-target-minus-junit4 \
+	ub-launcher-aosp-tapl
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
@@ -30,3 +34,22 @@
 LOCAL_INSTRUMENTATION_FOR := Launcher3
 
 include $(BUILD_PACKAGE)
+
+#
+# Build rule for Tapl library.
+#
+include $(CLEAR_VARS)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+	androidx.annotation_annotation \
+	android-support-test \
+	ub-uiautomator \
+	libSharedSystemUI
+
+LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
+  ../quickstep/src/com/android/quickstep/SwipeUpSetting.java \
+  ../src/com/android/launcher3/TestProtocol.java
+
+LOCAL_SDK_VERSION := current
+LOCAL_MODULE := ub-launcher-aosp-tapl
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index af8b15c..89ca7f3 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -31,7 +31,6 @@
                        android:resource="@xml/appwidget_no_config" />
         </receiver>
 
-
         <receiver
             android:name="com.android.launcher3.testcomponent.AppWdigetHidden"
             android:label="Hidden widget">
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index b217847..5d5af4f 100644
--- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -18,7 +18,6 @@
 import android.graphics.Color;
 import android.os.Process;
 import android.os.UserHandle;
-import android.support.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.provider.ProviderTestRule;
 
@@ -49,6 +48,8 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 
+import androidx.annotation.NonNull;
+
 /**
  * Base class for writing tests for Model update tasks.
  */
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..46a6446 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.ui;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
 import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -36,26 +39,30 @@
 import android.util.Log;
 import android.view.MotionEvent;
 
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.MainThreadExecutor;
 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.tapl.LauncherInstrumentation;
 import com.android.launcher3.testcomponent.AppWidgetNoConfig;
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.rule.LauncherActivityRule;
 
 import org.junit.Before;
+import org.junit.Rule;
 
-import java.util.Locale;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Base class for all instrumentation tests providing various utility methods.
@@ -67,19 +74,23 @@
 
     public static final long SHORT_UI_TIMEOUT= 300;
     public static final long DEFAULT_UI_TIMEOUT = 3000;
-    public static final long LARGE_UI_TIMEOUT = 10000;
     public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
 
     protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
     protected UiDevice mDevice;
+    protected LauncherInstrumentation mLauncher;
     protected Context mTargetContext;
     protected String mTargetPackage;
 
     private static final String TAG = "AbstractLauncherUiTest";
 
+    @Rule
+    public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
+
     @Before
     public void setUp() throws Exception {
         mDevice = UiDevice.getInstance(getInstrumentation());
+        mLauncher = new LauncherInstrumentation(mDevice);
         mTargetContext = InstrumentationRegistry.getTargetContext();
         mTargetPackage = mTargetContext.getPackageName();
     }
@@ -101,19 +112,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);
     }
 
@@ -245,6 +249,33 @@
         }
     }
 
+    protected <T> T getFromLauncher(Function<Launcher, T> f) {
+        return getOnUiThread(() -> f.apply(mActivityMonitor.getActivity()));
+    }
+
+    protected void executeOnLauncher(Consumer<Launcher> f) {
+        getFromLauncher(launcher -> {
+            f.accept(launcher);
+            return null;
+        });
+    }
+
+    // Cannot be used between a Tapl call injecting a gesture and a tapl call expecting the
+    // results of that gesture because the wait can hide flakeness.
+    protected boolean waitForState(LauncherState state) {
+        return waitForLauncherCondition(launcher -> launcher.getStateManager().getState() == state);
+    }
+
+    // Cannot be used after injecting any gesture using Tapl because this can hide flakiness.
+    protected boolean waitForLauncherCondition(Function<Launcher, Boolean> condition) {
+        return Wait.atMost(new Condition() {
+            @Override
+            public boolean isTrue() {
+                return getFromLauncher(condition);
+            }
+        }, DEFAULT_ACTIVITY_TIMEOUT);
+    }
+
     /**
      * Finds a widget provider which can fit on the home screen.
      * @param hasConfigureScreen if true, a provider with a config screen is returned.
diff --git a/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java b/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java
deleted file mode 100644
index b95a850..0000000
--- a/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.android.launcher3.ui;
-
-import android.content.pm.LauncherActivityInfo;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
-
-import com.android.launcher3.util.Condition;
-import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.LauncherActivityRule;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test for verifying apps is launched from all-apps
- */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class AllAppsAppLaunchTest extends AbstractLauncherUiTest {
-
-    @Rule public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
-
-    @Test
-    public void testAppLauncher_portrait() throws Exception {
-        lockRotation(true);
-        performTest();
-    }
-
-    @Test
-    public void testAppLauncher_landscape() throws Exception {
-        lockRotation(false);
-        performTest();
-    }
-
-    private void performTest() throws Exception {
-        mActivityMonitor.startLauncher();
-
-        LauncherActivityInfo testApp = getChromeApp();
-
-        // Open all apps and wait for load complete
-        final UiObject2 appsContainer = openAllApps();
-        assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
-
-        // Open app and verify app launched
-        scrollAndFind(appsContainer, By.text(testApp.getLabel().toString())).click();
-        assertTrue(mDevice.wait(Until.hasObject(By.pkg(
-                testApp.getComponentName().getPackageName()).depth(0)), DEFAULT_UI_TIMEOUT));
-    }
-}
diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
index 00f30ad..8b9c584 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.ui;
 
+import static org.junit.Assert.assertTrue;
+
 import android.content.pm.LauncherActivityInfo;
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -9,15 +11,12 @@
 
 import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.junit.Assert.assertTrue;
-
 /**
  * Test for dragging an icon from all-apps to homescreen.
  */
@@ -25,7 +24,6 @@
 @RunWith(AndroidJUnit4.class)
 public class AllAppsIconToHomeTest extends AbstractLauncherUiTest {
 
-    @Rule public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
     @Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
 
     @Test
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
index 69f6c87..f913470 100644
--- a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
+++ b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
@@ -1,5 +1,8 @@
 package com.android.launcher3.ui;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 import android.content.pm.LauncherActivityInfo;
 import android.graphics.Point;
 import android.support.test.filters.LargeTest;
@@ -12,16 +15,12 @@
 import com.android.launcher3.R;
 import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
 /**
  * Test for verifying that shortcuts are shown and can be launched after long pressing an app
  */
@@ -29,7 +28,6 @@
 @RunWith(AndroidJUnit4.class)
 public class ShortcutsLaunchTest extends AbstractLauncherUiTest {
 
-    @Rule public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
     @Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
 
     @Test
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
index fad06a6..4f5c113 100644
--- a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
@@ -1,5 +1,8 @@
 package com.android.launcher3.ui;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 import android.content.pm.LauncherActivityInfo;
 import android.graphics.Point;
 import android.support.test.filters.LargeTest;
@@ -12,16 +15,12 @@
 import com.android.launcher3.R;
 import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
 /**
  * Test for dragging a deep shortcut to the home screen.
  */
@@ -29,7 +28,6 @@
 @RunWith(AndroidJUnit4.class)
 public class ShortcutsToHomeTest extends AbstractLauncherUiTest {
 
-    @Rule public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
     @Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
 
     @Test
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 6244434..2f867f3 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -15,15 +15,13 @@
  */
 package com.android.launcher3.ui;
 
+import static com.android.launcher3.LauncherState.ALL_APPS;
+
+import static org.junit.Assert.assertTrue;
+
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
 
-import com.android.launcher3.R;
-import com.android.launcher3.util.Condition;
-import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.After;
@@ -32,14 +30,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.junit.Assert.assertTrue;
-
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class WorkTabTest extends AbstractLauncherUiTest {
     @Rule
-    public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
-    @Rule
     public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
 
     private int mProfileUserId;
@@ -66,17 +60,13 @@
     public void workTabExists() {
         mActivityMonitor.startLauncher();
 
-        // Open all apps and wait for load complete
-        final UiObject2 appsContainer = openAllApps();
-        assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2),
-                LARGE_UI_TIMEOUT));
+        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
 
         /*
-        assertTrue("Personal tab is missing",
-                mDevice.wait(Until.hasObject(getSelectorForId(R.id.tab_personal)),
-                        LARGE_UI_TIMEOUT));
-        assertTrue("Work tab is missing",
-                mDevice.wait(Until.hasObject(getSelectorForId(R.id.tab_work)), LARGE_UI_TIMEOUT));
+        assertTrue("Personal tab is missing", waitForLauncherCondition(
+                launcher -> launcher.getAppsView().isPersonalTabVisible()));
+        assertTrue("Work tab is missing", waitForLauncherCondition(
+                launcher -> launcher.getAppsView().isWorkTabVisible()));
         */
     }
 }
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index a5c2e69..7f6dd44 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -15,6 +15,11 @@
  */
 package com.android.launcher3.ui.widget;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+
 import android.appwidget.AppWidgetManager;
 import android.content.Intent;
 import android.support.test.filters.LargeTest;
@@ -31,7 +36,6 @@
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.WidgetCell;
 
@@ -40,11 +44,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertTrue;
-
 /**
  * Test to verify widget configuration is properly shown.
  */
@@ -52,7 +51,6 @@
 @RunWith(AndroidJUnit4.class)
 public class AddConfigWidgetTest extends AbstractLauncherUiTest {
 
-    @Rule public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
     @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grandWidgetBind();
 
     private LauncherAppWidgetProviderInfo mWidgetInfo;
@@ -126,12 +124,8 @@
             assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
         } else {
             // Verify that the widget id is deleted.
-            assertTrue(Wait.atMost(new Condition() {
-                @Override
-                public boolean isTrue() throws Throwable {
-                    return mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null;
-                }
-            }, DEFAULT_ACTIVITY_TIMEOUT));
+            assertTrue(Wait.atMost(() -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null,
+                    DEFAULT_ACTIVITY_TIMEOUT));
         }
     }
 
@@ -145,8 +139,7 @@
     /**
      * Condition for searching widget id
      */
-    private class WidgetSearchCondition extends Condition
-            implements Workspace.ItemOperator {
+    private class WidgetSearchCondition implements Condition, Workspace.ItemOperator {
 
         @Override
         public boolean isTrue() throws Throwable {
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 19f7db7..d89f26e 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.ui.widget;
 
+import static org.junit.Assert.assertTrue;
+
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.uiautomator.By;
@@ -28,7 +30,6 @@
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.WidgetCell;
 
@@ -36,8 +37,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.junit.Assert.assertTrue;
-
 /**
  * Test to add widget from widget tray
  */
@@ -45,7 +44,6 @@
 @RunWith(AndroidJUnit4.class)
 public class AddWidgetTest extends AbstractLauncherUiTest {
 
-    @Rule public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
     @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grandWidgetBind();
 
     @Test
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index b557119..c8c3896 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -15,6 +15,11 @@
  */
 package com.android.launcher3.ui.widget;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
@@ -30,21 +35,20 @@
 import android.support.test.uiautomator.UiSelector;
 
 import com.android.launcher3.LauncherAppWidgetHost;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.PendingAppWidgetHostView;
 import com.android.launcher3.widget.WidgetHostViewLoader;
 
 import org.junit.After;
@@ -58,11 +62,6 @@
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
 /**
  * Tests for bind widget flow.
  *
@@ -72,7 +71,6 @@
 @RunWith(AndroidJUnit4.class)
 public class BindWidgetTest extends AbstractLauncherUiTest {
 
-    @Rule public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
     @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grandWidgetBind();
 
     private ContentResolver mResolver;
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index dcb564a..ec0a051 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.launcher3.ui.widget;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+
 import android.app.PendingIntent;
 import android.appwidget.AppWidgetManager;
 import android.content.Intent;
@@ -40,7 +44,6 @@
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.WidgetCell;
 
@@ -52,10 +55,6 @@
 
 import java.util.UUID;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertTrue;
-
 /**
  * Test to verify pin item request flow.
  */
@@ -63,7 +62,6 @@
 @RunWith(AndroidJUnit4.class)
 public class RequestPinItemTest  extends AbstractLauncherUiTest {
 
-    @Rule public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
     @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grandWidgetBind();
     @Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
 
@@ -200,7 +198,7 @@
     /**
      * Condition for for an item
      */
-    private class ItemSearchCondition extends Condition {
+    private class ItemSearchCondition implements Condition {
 
         private final ItemOperator mOp;
 
diff --git a/tests/src/com/android/launcher3/util/Condition.java b/tests/src/com/android/launcher3/util/Condition.java
index e9ee67c..78c652a 100644
--- a/tests/src/com/android/launcher3/util/Condition.java
+++ b/tests/src/com/android/launcher3/util/Condition.java
@@ -8,47 +8,36 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-public abstract class Condition {
+public interface Condition {
 
-    public abstract boolean isTrue() throws Throwable;
+    boolean isTrue() throws Throwable;
 
     /**
      * Converts the condition to be run on UI thread.
      */
-    public static Condition runOnUiThread(final Condition condition) {
+    static Condition runOnUiThread(final Condition condition) {
         final MainThreadExecutor executor = new MainThreadExecutor();
-        return new Condition() {
-            @Override
-            public boolean isTrue() throws Throwable {
-                final AtomicBoolean value = new AtomicBoolean(false);
-                final Throwable[] exceptions = new Throwable[1];
-                final CountDownLatch latch = new CountDownLatch(1);
-                executor.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            value.set(condition.isTrue());
-                        } catch (Throwable e) {
-                            exceptions[0] = e;
-                        }
-
-                    }
-                });
-                latch.await(1, TimeUnit.SECONDS);
-                if (exceptions[0] != null) {
-                    throw exceptions[0];
+        return () -> {
+            final AtomicBoolean value = new AtomicBoolean(false);
+            final Throwable[] exceptions = new Throwable[1];
+            final CountDownLatch latch = new CountDownLatch(1);
+            executor.execute(() -> {
+                try {
+                    value.set(condition.isTrue());
+                } catch (Throwable e) {
+                    exceptions[0] = e;
                 }
-                return value.get();
+
+            });
+            latch.await(1, TimeUnit.SECONDS);
+            if (exceptions[0] != null) {
+                throw exceptions[0];
             }
+            return value.get();
         };
     }
 
-    public static Condition minChildCount(final UiObject2 obj, final int childCount) {
-        return new Condition() {
-            @Override
-            public boolean isTrue() {
-                return obj.getChildCount() >= childCount;
-            }
-        };
+    static Condition minChildCount(final UiObject2 obj, final int childCount) {
+        return () -> obj.getChildCount() >= childCount;
     }
 }
diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
index 0185f13..5befeb9 100644
--- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -15,6 +15,11 @@
  */
 package com.android.launcher3.widget;
 
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -22,7 +27,6 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 
 import com.android.launcher3.IconCache;
@@ -43,10 +47,7 @@
 import java.util.ArrayList;
 import java.util.Map;
 
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
+import androidx.recyclerview.widget.RecyclerView;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
similarity index 64%
rename from tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java
rename to tests/tapl/com/android/launcher3/tapl/AllApps.java
index 02f8183..c3bc61b 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -16,36 +16,35 @@
 
 package com.android.launcher3.tapl;
 
-import android.support.annotation.NonNull;
+import static org.junit.Assert.assertTrue;
+
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.Direction;
 import android.support.test.uiautomator.UiObject2;
 
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.TestProtocol;
+
 /**
- * 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 +56,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 +76,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, TestProtocol.GET_SCROLL_MESSAGE).
+                getInt(TestProtocol.SCROLL_Y_FIELD, -1);
     }
 
     private void ensureIconVisible(UiObject2 appIcon, UiObject2 allAppsContainer) {
@@ -102,7 +110,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 +118,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 +145,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..07e73fc 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
@@ -17,29 +17,18 @@
 package com.android.launcher3.tapl;
 
 import android.graphics.Point;
-import android.support.annotation.NonNull;
 import android.support.test.uiautomator.UiObject2;
 
+import androidx.annotation.NonNull;
+
 /**
  * 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 +38,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..786f260 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -16,38 +16,23 @@
 
 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;
+
+import androidx.annotation.NonNull;
 
 /**
  * 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 +42,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..01e76f3
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -0,0 +1,350 @@
+/*
+ * 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 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.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.launcher3.TestProtocol;
+import com.android.quickstep.SwipeUpSetting;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.TimeoutException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * 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;
+    private Boolean mSwipeUpEnabledOverride = null;
+
+    /**
+     * 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_SETTING_NAME,
+                swipeUpEnabledDefault ? 1 : 0) == 1;
+        assertTrue("Device must run in a test harness", ActivityManager.isRunningInTestHarness());
+    }
+
+    // Used only by tests.
+    public void overrideSwipeUpEnabled(Boolean swipeUpEnabledOverride) {
+        mSwipeUpEnabledOverride = swipeUpEnabledOverride;
+    }
+
+    void setActiveContainer(VisibleContainer container) {
+        sActiveContainer = new WeakReference<>(container);
+    }
+
+    boolean isSwipeUpEnabled() {
+        return mSwipeUpEnabledOverride != null ? mSwipeUpEnabledOverride : 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 + TestProtocol.RESPONSE_MESSAGE_POSTFIX;
+        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 -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.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..3c0ae2d 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -16,55 +16,51 @@
 
 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;
 import android.support.test.uiautomator.UiObject2;
 
 import java.util.Collections;
 import java.util.List;
 
+import androidx.annotation.NonNull;
+
 /**
  * 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 +70,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 +82,7 @@
                 (t1, t2) -> Integer.compare(t1.getVisibleBounds().width(),
                         t2.getVisibleBounds().width()));
 
-        return new OverviewTask(mLauncher, widestTask);
+        return new OverviewTask(mLauncher, widestTask, this);
     }
 
     /**
@@ -96,15 +92,12 @@
      */
     @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(
-                "prediction_row");
-        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
+        // Swipe from navbar to the top.
+        final UiObject2 navBar = mLauncher.getSystemUiObject("navigation_bar_frame");
+        final Point start = navBar.getVisibleCenter();
+        mLauncher.swipe(start.x, start.y, start.x, 0);
 
         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..c58c244
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -0,0 +1,156 @@
+/*
+ * 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.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * 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