Merge "Split out launcherProtos from sysui_shared.jar" into ub-launcher3-master
diff --git a/.gitignore b/.gitignore
index 7240e48..694b40c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,4 +13,5 @@
 local.properties
 gradle/
 build/
-gradlew*
\ No newline at end of file
+gradlew*
+.DS_Store
diff --git a/Android.bp b/Android.bp
index e3dd5e5..c583244 100644
--- a/Android.bp
+++ b/Android.bp
@@ -29,23 +29,3 @@
     ],
     platform_apis: true,
 }
-
-
-android_library {
-    name: "icon-loader",
-    sdk_version: "28",
-    static_libs: [
-        "androidx.core_core",
-    ],
-    resource_dirs: [
-        "res",
-    ],
-    srcs: [
-        "src/com/android/launcher3/icons/BaseIconFactory.java",
-        "src/com/android/launcher3/icons/BitmapInfo.java",
-        "src/com/android/launcher3/icons/IconNormalizer.java",
-        "src/com/android/launcher3/icons/FixedScaleDrawable.java",
-        "src/com/android/launcher3/icons/ShadowGenerator.java",
-        "src/com/android/launcher3/icons/ColorExtractor.java",
-    ],
-}
diff --git a/Android.mk b/Android.mk
index ca1bfde..422c6f0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -75,7 +75,8 @@
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     androidx.recyclerview_recyclerview \
     androidx.dynamicanimation_dynamicanimation \
-    androidx.preference_preference
+    androidx.preference_preference \
+    iconloader
 
 LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib
 
@@ -109,6 +110,7 @@
 LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
+    $(call all-java-files-under, src_shortcuts_overrides) \
     $(call all-java-files-under, src_ui_overrides) \
     $(call all-java-files-under, src_flags)
 
@@ -139,7 +141,7 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, src_ui_overrides) \
-    $(call all-java-files-under, go/src_flags)
+    $(call all-java-files-under, go/src)
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/go/res
 
@@ -182,7 +184,8 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, quickstep/src) \
-    $(call all-java-files-under, src_flags)
+    $(call all-java-files-under, src_flags) \
+    $(call all-java-files-under, src_shortcuts_overrides)
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
 LOCAL_PROGUARD_ENABLED := disabled
@@ -243,7 +246,7 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, quickstep/src) \
-    $(call all-java-files-under, go/src_flags)
+    $(call all-java-files-under, go/src)
 
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/quickstep/res \
diff --git a/build.gradle b/build.gradle
index 1b9df53..33409c5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,19 +4,17 @@
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.2.1'
-        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6'
+        classpath GRADLE_CLASS_PATH
+        classpath PROTOBUF_CLASS_PATH
     }
 }
 
-final String SUPPORT_LIBS_VERSION = '1.0.0-alpha1'
-
 apply plugin: 'com.android.application'
 apply plugin: 'com.google.protobuf'
 
 android {
-    compileSdkVersion 28
-    buildToolsVersion '28.0.3'
+    compileSdkVersion COMPILE_SDK.toInteger()
+    buildToolsVersion BUILD_TOOLS_VERSION
 
     defaultConfig {
         minSdkVersion 21
@@ -72,7 +70,7 @@
     sourceSets {
         main {
             res.srcDirs = ['res']
-            java.srcDirs = ['src']
+            java.srcDirs = ['src', 'src_shortcuts_overrides']
             manifest.srcFile 'AndroidManifest-common.xml'
             proto {
                 srcDir 'protos/'
@@ -100,7 +98,7 @@
 
         l3go {
             res.srcDirs = ['go/res']
-            java.srcDirs = ['go/src_flags', "src_ui_overrides"]
+            java.srcDirs = ['go/src', "src_ui_overrides"]
             manifest.srcFile "go/AndroidManifest.xml"
         }
 
@@ -120,9 +118,15 @@
 }
 
 dependencies {
-    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'
+    implementation "androidx.dynamicanimation:dynamicanimation:${ANDROID_X_VERSION}"
+    implementation "androidx.recyclerview:recyclerview:${ANDROID_X_VERSION}"
+    implementation "androidx.preference:preference:${ANDROID_X_VERSION}"
+    implementation PROTOBUF_DEPENDENCY
+    implementation project(':IconLoader')
+
+    // This is already included in sysui_shared
+    aospImplementation fileTree(dir: "libs", include: 'plugin_core.jar')
+    l3goImplementation fileTree(dir: "libs", include: 'plugin_core.jar')
 
     quickstepImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
 
@@ -133,7 +137,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 "androidx.annotation:annotation:${SUPPORT_LIBS_VERSION}"
+    androidTestImplementation "androidx.annotation:annotation:${ANDROID_X_VERSION}"
 }
 
 protobuf {
diff --git a/go/src_flags/com/android/launcher3/config/FeatureFlags.java b/go/src/com/android/launcher3/config/FeatureFlags.java
similarity index 100%
rename from go/src_flags/com/android/launcher3/config/FeatureFlags.java
rename to go/src/com/android/launcher3/config/FeatureFlags.java
diff --git a/go/src/com/android/launcher3/model/LoaderResults.java b/go/src/com/android/launcher3/model/LoaderResults.java
new file mode 100644
index 0000000..b82f362
--- /dev/null
+++ b/go/src/com/android/launcher3/model/LoaderResults.java
@@ -0,0 +1,42 @@
+/*
+ * 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.model;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.Callbacks;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
+ */
+public class LoaderResults extends BaseLoaderResults {
+
+    public LoaderResults(LauncherAppState app, BgDataModel dataModel,
+            AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
+        super(app, dataModel, allAppsList, pageToBindFirst, callbacks);
+    }
+
+    @Override
+    public void bindDeepShortcuts() {
+    }
+
+    @Override
+    public void bindWidgets() {
+    }
+}
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
new file mode 100644
index 0000000..18f3f9d
--- /dev/null
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -0,0 +1,67 @@
+/*
+ * 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.model;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.WidgetListRowEntry;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Widgets data model that is used by the adapters of the widget views and controllers.
+ *
+ * <p> The widgets and shortcuts are organized using package name as its index.
+ */
+public class WidgetsModel {
+    private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
+
+    /**
+     * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row
+     * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s
+     * is not sorted. This list is sorted at the UI when using
+     * {@link com.android.launcher3.widget.WidgetsDiffReporter}
+     *
+     * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList)
+     */
+    public synchronized ArrayList<WidgetListRowEntry> getWidgetsList(Context context) {
+        return EMPTY_WIDGET_LIST;
+    }
+
+    /**
+     * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
+     *                    only widgets and shortcuts associated with the package/user are.
+     */
+    public List<ComponentWithLabel> update(LauncherAppState app,
+            @Nullable PackageUserKey packageUser) {
+        return Collections.emptyList();
+    }
+
+
+    public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
+            LauncherAppState app) {
+    }
+}
\ No newline at end of file
diff --git a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
new file mode 100644
index 0000000..ff0c907
--- /dev/null
+++ b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -0,0 +1,124 @@
+/*
+ * 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.shortcuts;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.launcher3.ItemInfo;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
+ */
+public class DeepShortcutManager {
+    private static DeepShortcutManager sInstance;
+    private static final Object sInstanceLock = new Object();
+
+    public static DeepShortcutManager getInstance(Context context) {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                sInstance = new DeepShortcutManager(context.getApplicationContext());
+            }
+            return sInstance;
+        }
+    }
+
+    private DeepShortcutManager(Context context) {
+    }
+
+    public static boolean supportsShortcuts(ItemInfo info) {
+        return false;
+    }
+
+    public boolean wasLastCallSuccess() {
+        return false;
+    }
+
+    public void onShortcutsChanged(List<ShortcutInfoCompat> shortcuts) {
+    }
+
+    /**
+     * Queries for the shortcuts with the package name and provided ids.
+     *
+     * This method is intended to get the full details for shortcuts when they are added or updated,
+     * because we only get "key" fields in onShortcutsChanged().
+     */
+    public List<ShortcutInfoCompat> queryForFullDetails(String packageName,
+            List<String> shortcutIds, UserHandle user) {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Gets all the manifest and dynamic shortcuts associated with the given package and user,
+     * to be displayed in the shortcuts container on long press.
+     */
+    public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
+            UserHandle user) {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Removes the given shortcut from the current list of pinned shortcuts.
+     * (Runs on background thread)
+     */
+    public void unpinShortcut(final ShortcutKey key) {
+    }
+
+    /**
+     * Adds the given shortcut to the current list of pinned shortcuts.
+     * (Runs on background thread)
+     */
+    public void pinShortcut(final ShortcutKey key) {
+    }
+
+    public void startShortcut(String packageName, String id, Rect sourceBounds,
+            Bundle startActivityOptions, UserHandle user) {
+    }
+
+    public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
+        return null;
+    }
+
+    /**
+     * Returns the id's of pinned shortcuts associated with the given package and user.
+     *
+     * If packageName is null, returns all pinned shortcuts regardless of package.
+     */
+    public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName, UserHandle user) {
+        return Collections.emptyList();
+    }
+
+    public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName,
+            List<String> shortcutIds, UserHandle user) {
+        return Collections.emptyList();
+    }
+
+    public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandle user) {
+        return Collections.emptyList();
+    }
+
+    public boolean hasHostPermission() {
+        return false;
+    }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..b299cfe
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,13 @@
+# Until all the dependencies move to android X
+android.useAndroidX = true
+android.enableJetifier = true
+
+ANDROID_X_VERSION=1.0.0-beta01
+
+GRADLE_CLASS_PATH=com.android.tools.build:gradle:3.2.0-rc03
+
+PROTOBUF_CLASS_PATH=com.google.protobuf:protobuf-gradle-plugin:0.8.6
+PROTOBUF_DEPENDENCY=com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7
+
+BUILD_TOOLS_VERSION=28.0.3
+COMPILE_SDK=28
\ No newline at end of file
diff --git a/iconloaderlib/Android.bp b/iconloaderlib/Android.bp
new file mode 100644
index 0000000..8a71f94
--- /dev/null
+++ b/iconloaderlib/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+android_library {
+    name: "iconloader",
+    sdk_version: "28",
+    min_sdk_version: "21",
+    static_libs: [
+        "androidx.core_core",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+}
diff --git a/iconloaderlib/AndroidManifest.xml b/iconloaderlib/AndroidManifest.xml
new file mode 100644
index 0000000..b30258d
--- /dev/null
+++ b/iconloaderlib/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.launcher3.icons">
+</manifest>
diff --git a/iconloaderlib/build.gradle b/iconloaderlib/build.gradle
new file mode 100644
index 0000000..d080293
--- /dev/null
+++ b/iconloaderlib/build.gradle
@@ -0,0 +1,50 @@
+buildscript {
+    repositories {
+        mavenCentral()
+        google()
+    }
+    dependencies {
+        classpath GRADLE_CLASS_PATH
+    }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion COMPILE_SDK.toInteger()
+    buildToolsVersion BUILD_TOOLS_VERSION
+    publishNonDefault true
+
+    defaultConfig {
+        minSdkVersion 21
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+    }
+
+    sourceSets {
+        main {
+            java.srcDirs = ['src']
+            manifest.srcFile 'AndroidManifest.xml'
+            res.srcDirs = ['res']
+        }
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
+    tasks.withType(JavaCompile) {
+        options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
+    }
+}
+
+
+repositories {
+    mavenCentral()
+    google()
+}
+
+dependencies {
+    implementation "androidx.core:core:${ANDROID_X_VERSION}"
+}
diff --git a/res/drawable-v26/adaptive_icon_drawable_wrapper.xml b/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
similarity index 100%
rename from res/drawable-v26/adaptive_icon_drawable_wrapper.xml
rename to iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
diff --git a/res/drawable/ic_instant_app_badge.xml b/iconloaderlib/res/drawable/ic_instant_app_badge.xml
similarity index 93%
rename from res/drawable/ic_instant_app_badge.xml
rename to iconloaderlib/res/drawable/ic_instant_app_badge.xml
index cc53230..b74317e 100644
--- a/res/drawable/ic_instant_app_badge.xml
+++ b/iconloaderlib/res/drawable/ic_instant_app_badge.xml
@@ -21,23 +21,19 @@
 
     <path
         android:fillColor="@android:color/black"
-        android:fillType="evenOdd"
         android:strokeWidth="1"
         android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
     <path
         android:fillColor="@android:color/white"
-        android:fillType="evenOdd"
         android:strokeWidth="1"
         android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
     <path
         android:fillColor="@android:color/white"
-        android:fillType="evenOdd"
         android:strokeWidth="1"
         android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
     <path
         android:fillColor="@android:color/black"
         android:fillAlpha="0.87"
-        android:fillType="evenOdd"
         android:strokeWidth="1"
         android:pathData="M 6 10.4123279 L 8.63934949 10.4123279 L 8.63934949 15.6 L 12.5577168 7.84517705 L 9.94547194 7.84517705 L 9.94547194 2 Z" />
-</vector>
\ No newline at end of file
+</vector>
diff --git a/iconloaderlib/res/values/colors.xml b/iconloaderlib/res/values/colors.xml
new file mode 100644
index 0000000..873b2fc
--- /dev/null
+++ b/iconloaderlib/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<resources>
+    <color name="legacy_icon_background">#FFFFFF</color>
+</resources>
diff --git a/iconloaderlib/res/values/dimens.xml b/iconloaderlib/res/values/dimens.xml
new file mode 100644
index 0000000..e8c0c44
--- /dev/null
+++ b/iconloaderlib/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <dimen name="profile_badge_size">24dp</dimen>
+</resources>
diff --git a/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android.launcher3/icons/BaseIconFactory.java
similarity index 99%
rename from src/com/android/launcher3/icons/BaseIconFactory.java
rename to iconloaderlib/src/com/android.launcher3/icons/BaseIconFactory.java
index cd60de5..681c03c 100644
--- a/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android.launcher3/icons/BaseIconFactory.java
@@ -18,7 +18,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 
-import com.android.launcher3.R;
+import com.android.launcher3.icons.R;
 
 import static android.graphics.Paint.DITHER_FLAG;
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
diff --git a/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android.launcher3/icons/BitmapInfo.java
similarity index 100%
rename from src/com/android/launcher3/icons/BitmapInfo.java
rename to iconloaderlib/src/com/android.launcher3/icons/BitmapInfo.java
diff --git a/src/com/android/launcher3/icons/ColorExtractor.java b/iconloaderlib/src/com/android.launcher3/icons/ColorExtractor.java
similarity index 100%
rename from src/com/android/launcher3/icons/ColorExtractor.java
rename to iconloaderlib/src/com/android.launcher3/icons/ColorExtractor.java
diff --git a/src/com/android/launcher3/icons/FixedScaleDrawable.java b/iconloaderlib/src/com/android.launcher3/icons/FixedScaleDrawable.java
similarity index 100%
rename from src/com/android/launcher3/icons/FixedScaleDrawable.java
rename to iconloaderlib/src/com/android.launcher3/icons/FixedScaleDrawable.java
diff --git a/src/com/android/launcher3/icons/IconNormalizer.java b/iconloaderlib/src/com/android.launcher3/icons/IconNormalizer.java
similarity index 98%
rename from src/com/android/launcher3/icons/IconNormalizer.java
rename to iconloaderlib/src/com/android.launcher3/icons/IconNormalizer.java
index 8eb8252..05908df 100644
--- a/src/com/android/launcher3/icons/IconNormalizer.java
+++ b/iconloaderlib/src/com/android.launcher3/icons/IconNormalizer.java
@@ -20,9 +20,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
diff --git a/src/com/android/launcher3/icons/ShadowGenerator.java b/iconloaderlib/src/com/android.launcher3/icons/ShadowGenerator.java
similarity index 100%
rename from src/com/android/launcher3/icons/ShadowGenerator.java
rename to iconloaderlib/src/com/android.launcher3/icons/ShadowGenerator.java
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 5680a67..9e85469 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -178,6 +178,14 @@
     @Override
     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
         if (hasControlRemoteAppTransitionPermission()) {
+            boolean fromRecents = mLauncher.getStateManager().getState().overviewUi
+                    && findTaskViewToLaunch(launcher, v, null) != null;
+            RecentsView recentsView = mLauncher.getOverviewPanel();
+            if (fromRecents && recentsView.getQuickScrubController().isQuickSwitch()) {
+                return ActivityOptions.makeCustomAnimation(mLauncher, R.anim.no_anim,
+                        R.anim.no_anim);
+            }
+
             RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler,
                     true /* startAtFrontOfQueue */) {
 
@@ -218,8 +226,6 @@
                 }
             };
 
-            boolean fromRecents = mLauncher.getStateManager().getState().overviewUi
-                    && findTaskViewToLaunch(launcher, v, null) != null;
             int duration = fromRecents
                     ? RECENTS_LAUNCH_DURATION
                     : APP_LAUNCH_DURATION;
@@ -414,7 +420,10 @@
             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
             mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
 
-            endListener = this::resetContentView;
+            endListener = () -> {
+                resetContentView();
+                mDragLayer.getScrim().hideSysUiScrim(false);
+            };
         }
         return new Pair<>(launcherAnimator, endListener);
     }
@@ -788,8 +797,6 @@
             workspaceAnimator.setDuration(333);
             workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
 
-            mDragLayer.getScrim().hideSysUiScrim(true);
-
             // Pause page indicator animations as they lead to layer trashing.
             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
             mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
@@ -809,7 +816,6 @@
         mDragLayerAlpha.setValue(1f);
         mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
         mDragLayer.setTranslationY(0f);
-        mDragLayer.getScrim().hideSysUiScrim(false);
     }
 
     private boolean hasControlRemoteAppTransitionPermission() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
index 2645302..1d65a54 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
@@ -35,6 +35,7 @@
      * Vertical transition of the task previews relative to the full container.
      */
     public static final float OVERVIEW_TRANSLATION_FACTOR = 0.4f;
+    public static final float OVERVIEW_CENTERED_TRANSLATION_FACTOR = 0.5f;
 
     private static final int STATE_FLAGS = FLAG_DISABLE_RESTORE | FLAG_DISABLE_INTERACTION
             | FLAG_OVERVIEW_UI | FLAG_HIDE_BACK_BUTTON | FLAG_DISABLE_ACCESSIBILITY;
@@ -60,12 +61,17 @@
         RecentsView recentsView = launcher.getOverviewPanel();
         recentsView.getTaskSize(sTempRect);
 
-        return new float[] {getOverviewScale(launcher.getDeviceProfile(), sTempRect, launcher),
-                OVERVIEW_TRANSLATION_FACTOR};
+        boolean isQuickSwitch = recentsView.getQuickScrubController().isQuickSwitch();
+        float translationYFactor = isQuickSwitch
+                ? OVERVIEW_CENTERED_TRANSLATION_FACTOR
+                : OVERVIEW_TRANSLATION_FACTOR;
+        return new float[] {getOverviewScale(launcher.getDeviceProfile(), sTempRect, launcher,
+                isQuickSwitch), translationYFactor};
     }
 
-    public static float getOverviewScale(DeviceProfile dp, Rect taskRect, Context context) {
-        if (dp.isVerticalBarLayout()) {
+    public static float getOverviewScale(DeviceProfile dp, Rect taskRect, Context context,
+            boolean isQuickSwitch) {
+        if (dp.isVerticalBarLayout() && !isQuickSwitch) {
             return 1f;
         }
 
@@ -73,6 +79,10 @@
         float usedHeight = taskRect.height() + res.getDimension(R.dimen.task_thumbnail_top_margin);
         float usedWidth = taskRect.width() + 2 * (res.getDimension(R.dimen.recents_page_spacing)
                 + res.getDimension(R.dimen.quickscrub_adjacent_visible_width));
+        if (isQuickSwitch) {
+            usedWidth = taskRect.width();
+            return Math.max(dp.availableHeightPx / usedHeight, dp.availableWidthPx / usedWidth);
+        }
         return Math.min(Math.min(dp.availableHeightPx / usedHeight,
                 dp.availableWidthPx / usedWidth), MAX_PREVIEW_SCALE_UP);
     }
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index c809e28..85eed1f 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -16,7 +16,6 @@
 package com.android.quickstep;
 
 import static android.view.View.TRANSLATION_Y;
-
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
@@ -58,6 +57,7 @@
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.uioverrides.FastOverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -192,7 +192,8 @@
                 @InteractionType int interactionType, TransformedRect outRect) {
             LayoutUtils.calculateLauncherTaskSize(context, dp, outRect.rect);
             if (interactionType == INTERACTION_QUICK_SCRUB) {
-                outRect.scale = FastOverviewState.getOverviewScale(dp, outRect.rect, context);
+                outRect.scale = FastOverviewState.getOverviewScale(dp, outRect.rect, context,
+                        FeatureFlags.QUICK_SWITCH.get());
             }
             if (dp.isVerticalBarLayout()) {
                 Rect targetInsets = dp.getInsets();
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index 3420767..c44ccd3 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -16,8 +16,18 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.util.FloatProperty;
 import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.animation.Interpolator;
@@ -37,8 +47,10 @@
  * The behavior is to evenly divide the progress into sections, each of which scrolls one page.
  * The first and last section set an alarm to auto-advance backwards or forwards, respectively.
  */
+@TargetApi(Build.VERSION_CODES.P)
 public class QuickScrubController implements OnAlarmListener {
 
+    public static final int QUICK_SWITCH_FROM_APP_START_DURATION = 0;
     public static final int QUICK_SCRUB_FROM_APP_START_DURATION = 240;
     public static final int QUICK_SCRUB_FROM_HOME_START_DURATION = 200;
     // We want the translation y to finish faster than the rest of the animation.
@@ -52,6 +64,19 @@
             0.05f, 0.20f, 0.35f, 0.50f, 0.65f, 0.80f, 0.95f
     };
 
+    private static final FloatProperty<QuickScrubController> PROGRESS
+            = new FloatProperty<QuickScrubController>("progress") {
+        @Override
+        public void setValue(QuickScrubController quickScrubController, float progress) {
+            quickScrubController.onQuickScrubProgress(progress);
+        }
+
+        @Override
+        public Float get(QuickScrubController quickScrubController) {
+            return quickScrubController.mEndProgress;
+        }
+    };
+
     private static final String TAG = "QuickScrubController";
     private static final boolean ENABLE_AUTO_ADVANCE = true;
     private static final long AUTO_ADVANCE_DELAY = 500;
@@ -72,6 +97,13 @@
     private ActivityControlHelper mActivityControlHelper;
     private TouchInteractionLog mTouchInteractionLog;
 
+    private boolean mIsQuickSwitch;
+    private float mStartProgress;
+    private float mEndProgress;
+    private float mPrevProgressDelta;
+    private float mPrevPrevProgressDelta;
+    private boolean mShouldSwitchToNext;
+
     public QuickScrubController(BaseActivity activity, RecentsView recentsView) {
         mActivity = activity;
         mRecentsView = recentsView;
@@ -91,17 +123,26 @@
         mActivityControlHelper = controlHelper;
         mTouchInteractionLog = touchInteractionLog;
 
+        if (mIsQuickSwitch) {
+            mShouldSwitchToNext = true;
+            mPrevProgressDelta = 0;
+            if (mRecentsView.getTaskViewCount() > 0) {
+                mRecentsView.getTaskViewAt(0).setFullscreen(true);
+            }
+            if (mRecentsView.getTaskViewCount() > 1) {
+                mRecentsView.getTaskViewAt(1).setFullscreen(true);
+            }
+        }
+
         snapToNextTaskIfAvailable();
         mActivity.getUserEventDispatcher().resetActionDurationMillis();
     }
 
     public void onQuickScrubEnd() {
         mInQuickScrub = false;
-        if (ENABLE_AUTO_ADVANCE) {
-            mAutoAdvanceAlarm.cancelAlarm();
-        }
-        int page = mRecentsView.getNextPage();
+
         Runnable launchTaskRunnable = () -> {
+            int page = mRecentsView.getPageNearestToCenterOfScreen();
             TaskView taskView = mRecentsView.getTaskViewAt(page);
             if (taskView != null) {
                 mWaitingForTaskLaunch = true;
@@ -118,12 +159,49 @@
                                 TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key));
                     }
                     mWaitingForTaskLaunch = false;
+                    if (mIsQuickSwitch) {
+                        mIsQuickSwitch = false;
+                        if (mRecentsView.getTaskViewCount() > 0) {
+                            mRecentsView.getTaskViewAt(0).setFullscreen(false);
+                        }
+                        if (mRecentsView.getTaskViewCount() > 1) {
+                            mRecentsView.getTaskViewAt(1).setFullscreen(false);
+                        }
+                    }
+
                 }, taskView.getHandler());
             } else {
                 breakOutOfQuickScrub();
             }
             mActivityControlHelper = null;
         };
+
+        if (mIsQuickSwitch) {
+            float progressVelocity = mPrevPrevProgressDelta / SINGLE_FRAME_MS;
+            // Move to the next frame immediately, then start the animation from the
+            // following frame since it starts a frame later.
+            float singleFrameProgress = progressVelocity * SINGLE_FRAME_MS;
+            float fromProgress = mEndProgress + singleFrameProgress;
+            onQuickScrubProgress(fromProgress);
+            fromProgress += singleFrameProgress;
+            float toProgress = mShouldSwitchToNext ? 1 : 0;
+            int duration = (int) Math.abs((toProgress - fromProgress) / progressVelocity);
+            duration = Utilities.boundToRange(duration, 80, 300);
+            Animator anim = ObjectAnimator.ofFloat(this, PROGRESS, fromProgress, toProgress);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    launchTaskRunnable.run();
+                }
+            });
+            anim.setDuration(duration).start();
+            return;
+        }
+
+        if (ENABLE_AUTO_ADVANCE) {
+            mAutoAdvanceAlarm.cancelAlarm();
+        }
+        int page = mRecentsView.getNextPage();
         int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
                 * QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
         if (mRecentsView.getChildCount() > 0 && mRecentsView.snapToPage(page, snapDuration)) {
@@ -151,19 +229,28 @@
         mLaunchingTaskId = 0;
     }
 
+    public boolean prepareQuickScrub(String tag) {
+        return prepareQuickScrub(tag, mIsQuickSwitch);
+    }
+
     /**
      * Initializes the UI for quick scrub, returns true if success.
      */
-    public boolean prepareQuickScrub(String tag) {
+    public boolean prepareQuickScrub(String tag, boolean isQuickSwitch) {
         if (mWaitingForTaskLaunch || mInQuickScrub) {
             Log.d(tag, "Waiting for last scrub to finish, will skip this interaction");
             return false;
         }
         mOnFinishedTransitionToQuickScrubRunnable = null;
         mRecentsView.setNextPageSwitchRunnable(null);
+        mIsQuickSwitch = isQuickSwitch;
         return true;
     }
 
+    public boolean isQuickSwitch() {
+        return mIsQuickSwitch;
+    }
+
     public boolean isWaitingForTaskLaunch() {
         return mWaitingForTaskLaunch;
     }
@@ -179,6 +266,40 @@
     }
 
     public void onQuickScrubProgress(float progress) {
+        if (mIsQuickSwitch) {
+            TaskView currentPage = mRecentsView.getTaskViewAt(0);
+            TaskView nextPage = mRecentsView.getTaskViewAt(1);
+            if (currentPage == null || nextPage == null) {
+                return;
+            }
+            if (!mFinishedTransitionToQuickScrub) {
+                mStartProgress = mEndProgress = progress;
+            } else {
+                float progressDelta = progress - mEndProgress;
+                mEndProgress = progress;
+                progress = Utilities.boundToRange(progress, mStartProgress, 1);
+                progress = Utilities.mapToRange(progress, mStartProgress, 1, 0, 1, LINEAR);
+                if (mInQuickScrub) {
+                    mShouldSwitchToNext = mPrevProgressDelta > 0.007f || progressDelta > 0.007f
+                            || progress >= 0.5f;
+                }
+                mPrevPrevProgressDelta = mPrevProgressDelta;
+                mPrevProgressDelta = progressDelta;
+                float scrollDiff = nextPage.getWidth() + mRecentsView.getPageSpacing();
+                int scrollDir = mRecentsView.isRtl() ? -1 : 1;
+                int linearScrollDiff = (int) (progress * scrollDiff * scrollDir);
+                float accelScrollDiff = ACCEL.getInterpolation(progress) * scrollDiff * scrollDir;
+                currentPage.setZoomScale(1 - DEACCEL_3.getInterpolation(progress)
+                        * TaskView.EDGE_SCALE_DOWN_FACTOR);
+                currentPage.setTranslationX(linearScrollDiff + accelScrollDiff);
+                nextPage.setTranslationZ(1);
+                nextPage.setTranslationY(currentPage.getTranslationY());
+                int startScroll = mRecentsView.isRtl() ? mRecentsView.getMaxScrollX() : 0;
+                mRecentsView.setScrollX(startScroll + linearScrollDiff);
+            }
+            return;
+        }
+
         int quickScrubSection = 0;
         for (float threshold : QUICK_SCRUB_THRESHOLDS) {
             if (progress < threshold) {
@@ -228,9 +349,14 @@
 
     public void snapToNextTaskIfAvailable() {
         if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
-            int duration = mStartedFromHome ? QUICK_SCRUB_FROM_HOME_START_DURATION
-                    : QUICK_SCRUB_FROM_APP_START_DURATION;
-            int pageToGoTo = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1;
+            int duration = mIsQuickSwitch
+                    ? QUICK_SWITCH_FROM_APP_START_DURATION
+                    : mStartedFromHome
+                        ? QUICK_SCRUB_FROM_HOME_START_DURATION
+                        : QUICK_SCRUB_FROM_APP_START_DURATION;
+            int pageToGoTo = mStartedFromHome || mIsQuickSwitch
+                    ? 0
+                    : mRecentsView.getNextPage() + 1;
             goToPageWithHaptic(pageToGoTo, duration, true /* forceHaptic */,
                     QUICK_SCRUB_START_INTERPOLATOR);
         }
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 9ea8884..a604da0 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION;
+import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_FROM_APP_START_DURATION;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
@@ -59,6 +60,7 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -979,12 +981,14 @@
         setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED);
 
         // Start the window animation without waiting for launcher.
-        animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR,
-                true /* goingToHome */);
+        long duration = FeatureFlags.QUICK_SWITCH.get()
+                ? QUICK_SWITCH_FROM_APP_START_DURATION
+                : QUICK_SCRUB_FROM_APP_START_DURATION;
+        animateToProgress(mCurrentShift.value, 1f, duration, LINEAR, true /* goingToHome */);
     }
 
     private void onQuickScrubStartUi() {
-        if (!mQuickScrubController.prepareQuickScrub(TAG)) {
+        if (!mQuickScrubController.prepareQuickScrub(TAG, FeatureFlags.QUICK_SWITCH.get())) {
             mQuickScrubBlocked = true;
             setStateOnUiThread(STATE_RESUME_LAST_TASK | STATE_HANDLER_INVALIDATED);
             return;
@@ -993,6 +997,7 @@
             mLauncherTransitionController.getAnimationPlayer().end();
             mLauncherTransitionController = null;
         }
+        mLayoutListener.finish();
 
         mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false,
                 mTouchInteractionLog);
@@ -1008,6 +1013,13 @@
         mQuickScrubController.onFinishedTransitionToQuickScrub();
 
         mRecentsView.animateUpRunningTaskIconScale();
+        if (mQuickScrubController.isQuickSwitch()) {
+            TaskView runningTask = mRecentsView.getRunningTaskView();
+            if (runningTask != null) {
+                runningTask.setTranslationY(-mActivity.getResources().getDimension(
+                        R.dimen.task_thumbnail_half_top_margin) * 1f / mRecentsView.getScaleX());
+            }
+        }
         RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index ce65de1..c92c8d6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -17,7 +17,6 @@
 package com.android.quickstep.views;
 
 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
-
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
@@ -33,7 +32,7 @@
 import android.util.FloatProperty;
 import android.util.Property;
 import android.view.View;
-
+import android.view.ViewGroup;
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
@@ -78,6 +77,7 @@
     private final Matrix mMatrix = new Matrix();
 
     private float mClipBottom = -1;
+    private Rect mScaledInsets = new Rect();
 
     private Task mTask;
     private ThumbnailData mThumbnailData;
@@ -179,7 +179,17 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
+        if (((TaskView) getParent()).isFullscreen()) {
+            // Draw the insets if we're being drawn fullscreen (we do this for quick switch).
+            drawOnCanvas(canvas,
+                    -mScaledInsets.left,
+                    -mScaledInsets.top,
+                    getMeasuredWidth() + mScaledInsets.right,
+                    getMeasuredHeight() + mScaledInsets.bottom,
+                    mCornerRadius);
+        } else {
+            drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
+        }
     }
 
     public float getCornerRadius() {
@@ -253,6 +263,9 @@
                         : getMeasuredWidth() / thumbnailWidth;
             }
 
+            mScaledInsets.set(thumbnailInsets);
+            Utilities.scaleRect(mScaledInsets, thumbnailScale);
+
             if (rotate) {
                 int rotationDir = profile.isVerticalBarLayout() && !profile.isSeascape() ? -1 : 1;
                 mMatrix.setRotate(90 * rotationDir);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index d34dc5b..1e787a2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -87,7 +87,7 @@
     /**
      * How much to scale down pages near the edge of the screen.
      */
-    private static final float EDGE_SCALE_DOWN_FACTOR = 0.03f;
+    public static final float EDGE_SCALE_DOWN_FACTOR = 0.03f;
 
     public static final long SCALE_ICON_DURATION = 120;
     private static final long DIM_ANIM_DURATION = 700;
@@ -142,6 +142,7 @@
     private IconView mIconView;
     private float mCurveScale;
     private float mZoomScale;
+    private boolean mIsFullscreen;
 
     private Animator mIconAndDimAnimator;
     private float mFocusTransitionProgress = 1;
@@ -512,4 +513,18 @@
         Log.w(tag, msg);
         Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show();
     }
+
+    /**
+     * Hides the icon and shows insets when this TaskView is about to be shown fullscreen.
+     */
+    public void setFullscreen(boolean isFullscreen) {
+        mIsFullscreen = isFullscreen;
+        mIconView.setVisibility(mIsFullscreen ? INVISIBLE : VISIBLE);
+        setClipChildren(!mIsFullscreen);
+        setClipToPadding(!mIsFullscreen);
+    }
+
+    public boolean isFullscreen() {
+        return mIsFullscreen;
+    }
 }
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 02d793e..b1e6f2e 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -26,7 +26,9 @@
     android:focusable="false"
     android:saveEnabled="false" >
 
-    <include layout="@layout/all_apps_rv_layout" />
+    <include
+        layout="@layout/all_apps_rv_layout"
+        android:visibility="gone" />
 
     <include layout="@layout/all_apps_floating_header" />
 
diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml
deleted file mode 100644
index 00f0b5f..0000000
--- a/res/layout/hotseat.xml
+++ /dev/null
@@ -1,28 +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.
--->
-<com.android.launcher3.Hotseat
-    android:theme="@style/HomeScreenElementTheme"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto">
-
-    <com.android.launcher3.CellLayout
-        android:id="@+id/layout"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        launcher:containerType="hotseat"
-        android:importantForAccessibility="no" />
-</com.android.launcher3.Hotseat>
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index 304d012..87078b9 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -56,22 +56,25 @@
             android:id="@+id/drop_target_bar"
             layout="@layout/drop_target_bar" />
 
-        <include android:id="@+id/scrim_view"
+        <include
+            android:id="@+id/scrim_view"
             layout="@layout/scrim_view" />
 
         <include
             android:id="@+id/apps_view"
             layout="@layout/all_apps"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:visibility="invisible" />
+            android:layout_height="match_parent" />
 
         <!-- DO NOT CHANGE THE ID -->
-        <include
+        <com.android.launcher3.Hotseat
             android:id="@+id/hotseat"
-            layout="@layout/hotseat"
             android:layout_width="match_parent"
-            android:layout_height="match_parent" />
+            android:layout_height="match_parent"
+            android:theme="@style/HomeScreenElementTheme"
+            android:importantForAccessibility="no"
+            launcher:containerType="hotseat" />
+
     </com.android.launcher3.dragndrop.DragLayer>
 
 </com.android.launcher3.LauncherRootView>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index eb207af..3c8fe1e 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -34,7 +34,6 @@
     <color name="notification_icon_default_color">#757575</color> <!-- Gray 600 -->
 
     <color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
-    <color name="legacy_icon_background">#FFFFFF</color>
 
     <color name="all_apps_bg_hand_fill">#E5E5E5</color>
     <color name="all_apps_bg_hand_fill_dark">#9AA0A6</color>
diff --git a/res/values/config.xml b/res/values/config.xml
index 4ab2274..b33cfa1 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -131,7 +131,6 @@
 
 <!-- QSB IDs. DO not change -->
     <item type="id" name="search_container_workspace" />
-    <item type="id" name="search_container_hotseat" />
     <item type="id" name="search_container_all_apps" />
 
 <!-- Recents -->
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
new file mode 100644
index 0000000..5011764
--- /dev/null
+++ b/robolectric_tests/Android.mk
@@ -0,0 +1,53 @@
+# 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.
+
+#############################################
+# Launcher Robolectric test target.         #
+#############################################
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := LauncherRoboTests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    androidx.test.runner \
+    androidx.test.rules \
+    mockito-robolectric-prebuilt \
+    truth-prebuilt
+LOCAL_JAVA_LIBRARIES := \
+    platform-robolectric-3.6.1-prebuilt
+
+LOCAL_INSTRUMENTATION_FOR := Launcher3
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+############################################
+# Target to run the previous target.       #
+############################################
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := RunLauncherRoboTests
+LOCAL_SDK_VERSION := current
+LOCAL_JAVA_LIBRARIES := \
+    LauncherRoboTests
+
+LOCAL_TEST_PACKAGE := Launcher3
+
+LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src \
+
+LOCAL_ROBOTEST_TIMEOUT := 36000
+
+include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
diff --git a/tests/src/com/android/launcher3/logging/FileLogTest.java b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
similarity index 89%
rename from tests/src/com/android/launcher3/logging/FileLogTest.java
rename to robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
index e031f1d..096db57 100644
--- a/tests/src/com/android/launcher3/logging/FileLogTest.java
+++ b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
@@ -1,13 +1,11 @@
 package com.android.launcher3.logging;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -20,8 +18,7 @@
 /**
  * Tests for {@link FileLog}
  */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
 public class FileLogTest {
 
     private File mTempDir;
@@ -30,9 +27,9 @@
     public void setUp() throws Exception {
         int count = 0;
         do {
-            mTempDir = new File(InstrumentationRegistry.getTargetContext().getCacheDir(),
+            mTempDir = new File(RuntimeEnvironment.application.getCacheDir(),
                     "log-test-" + (count++));
-        } while(!mTempDir.mkdir());
+        } while (!mTempDir.mkdir());
 
         FileLog.setDir(mTempDir);
     }
diff --git a/tests/src/com/android/launcher3/util/GridOccupancyTest.java b/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java
similarity index 92%
rename from tests/src/com/android/launcher3/util/GridOccupancyTest.java
rename to robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java
index cbf30b1..aa51ad2 100644
--- a/tests/src/com/android/launcher3/util/GridOccupancyTest.java
+++ b/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java
@@ -1,10 +1,8 @@
 package com.android.launcher3.util;
 
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -13,8 +11,7 @@
 /**
  * Unit tests for {@link GridOccupancy}
  */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
 public class GridOccupancyTest {
 
     @Test
@@ -24,7 +21,7 @@
                 0, 0, 1, 1, 0,
                 0, 0, 0, 0, 0,
                 1, 1, 0, 0, 0
-                );
+        );
 
         int[] vacant = new int[2];
         assertTrue(grid.findVacantCell(vacant, 2, 2));
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
new file mode 100644
index 0000000..faec380
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Robolectric unit tests for {@link IntSet}
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = 26)
+public class IntSetTest {
+
+    @Test
+    public void shouldBeEmptyInitially() {
+        IntSet set = new IntSet();
+        assertThat(set.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void oneElementSet() {
+        IntSet set = new IntSet();
+        set.add(2);
+        assertThat(set.size()).isEqualTo(1);
+        assertTrue(set.contains(2));
+        assertFalse(set.contains(1));
+    }
+
+
+    @Test
+    public void twoElementSet() {
+        IntSet set = new IntSet();
+        set.add(2);
+        set.add(1);
+        assertThat(set.size()).isEqualTo(2);
+        assertTrue(set.contains(2));
+        assertTrue(set.contains(1));
+    }
+
+    @Test
+    public void threeElementSet() {
+        IntSet set = new IntSet();
+        set.add(2);
+        set.add(1);
+        set.add(10);
+        assertThat(set.size()).isEqualTo(3);
+        assertEquals("1, 2, 10", set.mArray.toConcatString());
+    }
+
+
+    @Test
+    public void duplicateEntries() {
+        IntSet set = new IntSet();
+        set.add(2);
+        set.add(2);
+        assertEquals(1, set.size());
+    }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..b52bd4f
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':IconLoader'
+project(':IconLoader').projectDir = new File(rootDir, 'iconloaderlib')
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index cb354a6..6b5db30 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -27,13 +27,12 @@
 import android.widget.FrameLayout;
 
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
-public class Hotseat extends FrameLayout implements LogContainerProvider, Insettable {
+public class Hotseat extends CellLayout implements LogContainerProvider, Insettable {
 
     private final Launcher mLauncher;
-    private CellLayout mContent;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mHasVerticalHotseat;
@@ -51,38 +50,23 @@
         mLauncher = Launcher.getLauncher(context);
     }
 
-    public CellLayout getLayout() {
-        return mContent;
-    }
-
-    /* Get the orientation invariant order of the item in the hotseat for persistence. */
-    int getOrderInHotseat(int x, int y) {
-        return mHasVerticalHotseat ? (mContent.getCountY() - y - 1) : x;
-    }
-
     /* Get the orientation specific coordinates given an invariant order in the hotseat. */
     int getCellXFromOrder(int rank) {
         return mHasVerticalHotseat ? 0 : rank;
     }
 
     int getCellYFromOrder(int rank) {
-        return mHasVerticalHotseat ? (mContent.getCountY() - (rank + 1)) : 0;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mContent = findViewById(R.id.layout);
+        return mHasVerticalHotseat ? (getCountY() - (rank + 1)) : 0;
     }
 
     void resetLayout(boolean hasVerticalHotseat) {
-        mContent.removeAllViewsInLayout();
+        removeAllViewsInLayout();
         mHasVerticalHotseat = hasVerticalHotseat;
         InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
         if (hasVerticalHotseat) {
-            mContent.setGridSize(1, idp.numHotseatIcons);
+            setGridSize(1, idp.numHotseatIcons);
         } else {
-            mContent.setGridSize(idp.numHotseatIcons, 1);
+            setGridSize(idp.numHotseatIcons, 1);
         }
     }
 
@@ -90,15 +74,16 @@
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         // We don't want any clicks to go through to the hotseat unless the workspace is in
         // the normal state or an accessible drag is in progress.
-        return !mLauncher.getWorkspace().workspaceIconsCanBeDragged() &&
-                !mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
+        return (!mLauncher.getWorkspace().workspaceIconsCanBeDragged()
+                && !mLauncher.getAccessibilityDelegate().isInAccessibleDrag())
+                || super.onInterceptTouchEvent(ev);
     }
 
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         target.gridX = info.cellX;
         target.gridY = info.cellY;
-        targetParent.containerType = ContainerType.HOTSEAT;
+        targetParent.containerType = LauncherLogProto.ContainerType.HOTSEAT;
     }
 
     @Override
@@ -121,7 +106,7 @@
             lp.height = grid.hotseatBarSizePx + insets.bottom;
         }
         Rect padding = grid.getHotseatLayoutPadding();
-        getLayout().setPadding(padding.left, padding.top, padding.right, padding.bottom);
+        setPadding(padding.left, padding.top, padding.right, padding.bottom);
 
         setLayoutParams(lp);
         InsettableFrameLayout.dispatchInsets(this, insets);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 285962d..36967cd 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -207,7 +207,6 @@
     private final int[] mTmpAddItemCellCoordinates = new int[2];
 
     @Thunk Hotseat mHotseat;
-    @Nullable private View mHotseatSearchBox;
 
     private DropTargetBar mDropTargetBar;
 
@@ -914,7 +913,6 @@
         mWorkspace.initParentViews(mDragLayer);
         mOverviewPanel = findViewById(R.id.overview_panel);
         mHotseat = findViewById(R.id.hotseat);
-        mHotseatSearchBox = findViewById(R.id.search_container_hotseat);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
@@ -1159,10 +1157,6 @@
         return mHotseat;
     }
 
-    public View getHotseatSearchBox() {
-        return mHotseatSearchBox;
-    }
-
     public <T extends View> T getOverviewPanel() {
         return (T) mOverviewPanel;
     }
@@ -1654,23 +1648,15 @@
 
     boolean isHotseatLayout(View layout) {
         // TODO: Remove this method
-        return mHotseat != null && layout != null &&
-                (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
+        return mHotseat != null && (layout == mHotseat);
     }
 
     /**
      * Returns the CellLayout of the specified container at the specified screen.
      */
     public CellLayout getCellLayout(int container, int screenId) {
-        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-            if (mHotseat != null) {
-                return mHotseat.getLayout();
-            } else {
-                return null;
-            }
-        } else {
-            return mWorkspace.getScreenWithId(screenId);
-        }
+        return (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
+                ? mHotseat : mWorkspace.getScreenWithId(screenId);
     }
 
     @Override
@@ -2281,7 +2267,7 @@
             }
 
             writer.println(prefix + "  Hotseat");
-            ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets();
+            ViewGroup layout = mHotseat.getShortcutsAndWidgets();
             for (int j = 0; j < layout.getChildCount(); j++) {
                 Object tag = layout.getChildAt(j).getTag();
                 if (tag != null) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 36b9e97..9470635 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -625,6 +625,10 @@
         mMaxScrollX = computeMaxScrollX();
     }
 
+    public int getMaxScrollX() {
+        return mMaxScrollX;
+    }
+
     protected int computeMaxScrollX() {
         int childCount = getChildCount();
         if (childCount > 0) {
@@ -640,6 +644,10 @@
         requestLayout();
     }
 
+    public int getPageSpacing() {
+        return mPageSpacing;
+    }
+
     private void dispatchPageCountChanged() {
         if (mPageIndicator != null) {
             mPageIndicator.setMarkersCount(getChildCount());
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 3f7d68d..7a8d984 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -85,8 +85,8 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Thunk;
@@ -880,7 +880,7 @@
 
         final CellLayout layout;
         if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-            layout = mLauncher.getHotseat().getLayout();
+            layout = mLauncher.getHotseat();
 
             // Hide folder title in the hotseat
             if (child instanceof FolderIcon) {
@@ -1517,7 +1517,7 @@
                 @Override
                 protected void enableAccessibleDrag(boolean enable) {
                     super.enableAccessibleDrag(enable);
-                    setEnableForLayout(mLauncher.getHotseat().getLayout(),enable);
+                    setEnableForLayout(mLauncher.getHotseat(),enable);
                 }
             });
         }
@@ -1625,11 +1625,7 @@
             mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
 
             // We want the point to be mapped to the dragTarget.
-            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
-                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
-            } else {
-                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
-            }
+            mapPointFromDropLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
 
             int spanX;
             int spanY;
@@ -1831,11 +1827,7 @@
 
         // We want the point to be mapped to the dragTarget.
         if (dropTargetLayout != null) {
-            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
-                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
-            } else {
-                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
-            }
+            mapPointFromDropLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
         }
 
         boolean droppedOnOriginalCell = false;
@@ -2195,7 +2187,7 @@
     * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
     * coordinate space. The argument xy is modified with the return result.
     */
-   void mapPointFromSelfToChild(View v, float[] xy) {
+   private void mapPointFromSelfToChild(View v, float[] xy) {
        xy[0] = xy[0] - v.getLeft();
        xy[1] = xy[1] - v.getTop();
    }
@@ -2211,14 +2203,23 @@
                mTempXY[1] <= hotseat.getBottom();
    }
 
-   void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
-       mTempXY[0] = (int) xy[0];
-       mTempXY[1] = (int) xy[1];
-       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
-       mLauncher.getDragLayer().mapCoordInSelfToDescendant(hotseat.getLayout(), mTempXY);
+    /**
+     * Updates the point in {@param xy} to point to the co-ordinate space of {@param layout}
+     * @param layout either hotseat of a page in workspace
+     * @param xy the point location in workspace co-ordinate space
+     */
+   private void mapPointFromDropLayout(CellLayout layout, float[] xy) {
+       if (mLauncher.isHotseatLayout(layout)) {
+           mTempXY[0] = (int) xy[0];
+           mTempXY[1] = (int) xy[1];
+           mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
+           mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, mTempXY);
 
-       xy[0] = mTempXY[0];
-       xy[1] = mTempXY[1];
+           xy[0] = mTempXY[0];
+           xy[1] = mTempXY[1];
+       } else {
+           mapPointFromSelfToChild(layout, xy);
+       }
    }
 
     private boolean isDragWidget(DragObject d) {
@@ -2254,11 +2255,7 @@
         // Handle the drag over
         if (mDragTargetLayout != null) {
             // We want the point to be mapped to the dragTarget.
-            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
-                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
-            } else {
-                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter);
-            }
+            mapPointFromDropLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
 
             int minSpanX = item.spanX;
             int minSpanY = item.spanY;
@@ -2329,7 +2326,7 @@
         // Test to see if we are over the hotseat first
         if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
             if (isPointInSelfOverHotseat(d.x, d.y)) {
-                layout = mLauncher.getHotseat().getLayout();
+                layout = mLauncher.getHotseat();
             }
         }
 
@@ -2967,8 +2964,7 @@
      * Returns a specific CellLayout
      */
     CellLayout getParentCellLayoutForView(View v) {
-        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
-        for (CellLayout layout : layouts) {
+        for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
             if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
                 return layout;
             }
@@ -2977,56 +2973,31 @@
     }
 
     /**
-     * Returns a list of all the CellLayouts in the workspace.
+     * Returns a list of all the CellLayouts on the Homescreen.
      */
-    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
-        ArrayList<CellLayout> layouts = new ArrayList<>();
+    private CellLayout[] getWorkspaceAndHotseatCellLayouts() {
         int screenCount = getChildCount();
-        for (int screen = 0; screen < screenCount; screen++) {
-            layouts.add(((CellLayout) getChildAt(screen)));
-        }
+        final CellLayout[] layouts;
         if (mLauncher.getHotseat() != null) {
-            layouts.add(mLauncher.getHotseat().getLayout());
+            layouts = new CellLayout[screenCount + 1];
+            layouts[screenCount] = mLauncher.getHotseat();
+        } else {
+            layouts = new CellLayout[screenCount];
+        }
+        for (int screen = 0; screen < screenCount; screen++) {
+            layouts[screen] = (CellLayout) getChildAt(screen);
         }
         return layouts;
     }
 
-    /**
-     * We should only use this to search for specific children.  Do not use this method to modify
-     * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
-     * the hotseat and workspace pages
-     */
-    ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
-        ArrayList<ShortcutAndWidgetContainer> childrenLayouts = new ArrayList<>();
-        int screenCount = getChildCount();
-        for (int screen = 0; screen < screenCount; screen++) {
-            childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
-        }
-        if (mLauncher.getHotseat() != null) {
-            childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
-        }
-        return childrenLayouts;
-    }
-
     public View getHomescreenIconByItemId(final int id) {
-        return getFirstMatch(new ItemOperator() {
-
-            @Override
-            public boolean evaluate(ItemInfo info, View v) {
-                return info != null && info.id == id;
-            }
-        });
+        return getFirstMatch((info, v) -> info != null && info.id == id);
     }
 
     public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
-        return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
-
-            @Override
-            public boolean evaluate(ItemInfo info, View v) {
-                return (info instanceof LauncherAppWidgetInfo) &&
-                        ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
-            }
-        });
+        return (LauncherAppWidgetHostView) getFirstMatch((info, v) ->
+                (info instanceof LauncherAppWidgetInfo) &&
+                        ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId);
     }
 
     public View getFirstMatch(final ItemOperator operator) {
@@ -3063,8 +3034,7 @@
      * shortcuts are not removed.
      */
     public void removeItemsByMatcher(final ItemInfoMatcher matcher) {
-        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
-        for (final CellLayout layoutParent: cellLayouts) {
+        for (final CellLayout layoutParent: getWorkspaceAndHotseatCellLayouts()) {
             final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
 
             IntSparseArrayMap<View> idToViewMap = new IntSparseArrayMap<>();
@@ -3122,10 +3092,8 @@
      * @param op the operator to map over the shortcuts
      */
     public void mapOverItems(boolean recurse, ItemOperator op) {
-        ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
-        final int containerCount = containers.size();
-        for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
-            ShortcutAndWidgetContainer container = containers.get(containerIdx);
+        for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
+            ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets();
             // map over all the shortcuts on the workspace
             final int itemCount = container.getChildCount();
             for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index e734e70..3e09493 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -88,7 +88,7 @@
             Interpolator scaleInterpolator = builder.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
             propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
             float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
-            propertySetter.setViewAlpha(mLauncher.getHotseat().getLayout(), hotseatIconsAlpha,
+            propertySetter.setViewAlpha(mLauncher.getHotseat(), hotseatIconsAlpha,
                     fadeInterpolator);
             propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
                     hotseatIconsAlpha, fadeInterpolator);
@@ -105,9 +105,6 @@
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
                 scaleAndTranslation[2], translationInterpolator);
 
-        propertySetter.setViewAlpha(mLauncher.getHotseatSearchBox(),
-                (elements & HOTSEAT_SEARCH_BOX) != 0 ? 1 : 0, fadeInterpolator);
-
         // Set scrim
         WorkspaceAndHotseatScrim scrim = mLauncher.getDragLayer().getScrim();
         propertySetter.setFloat(scrim, SCRIM_PROGRESS, state.getWorkspaceScrimAlpha(mLauncher),
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index e7313e8..2d6be7b 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -91,11 +91,6 @@
         return mShiftRange;
     }
 
-    private void onProgressAnimationStart() {
-        // Initialize values that should not change until #onDragEnd
-        mAppsView.setVisibility(View.VISIBLE);
-    }
-
     @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
         mIsVerticalLayout = dp.isVerticalBarLayout();
@@ -195,16 +190,15 @@
         PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER
                 : config.getPropertySetter(builder);
         int visibleElements = toState.getVisibleElements(mLauncher);
-        boolean hasHeader = (visibleElements & ALL_APPS_HEADER) != 0;
         boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
         boolean hasContent = (visibleElements & ALL_APPS_CONTENT) != 0;
 
         Interpolator allAppsFade = builder.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
-        setter.setViewAlpha(mAppsView.getSearchView(), hasHeader ? 1 : 0, allAppsFade);
         setter.setViewAlpha(mAppsView.getContentView(), hasContent ? 1 : 0, allAppsFade);
         setter.setViewAlpha(mAppsView.getScrollBar(), hasContent ? 1 : 0, allAppsFade);
         mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasContent, setter,
                 allAppsFade);
+        mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
 
         setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
                 (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, LINEAR);
@@ -216,11 +210,6 @@
             public void onAnimationSuccess(Animator animator) {
                 onProgressAnimationEnd();
             }
-
-            @Override
-            public void onAnimationStart(Animator animation) {
-                onProgressAnimationStart();
-            }
         };
     }
 
@@ -247,13 +236,9 @@
      */
     private void onProgressAnimationEnd() {
         if (Float.compare(mProgress, 1f) == 0) {
-            mAppsView.setVisibility(View.INVISIBLE);
             mAppsView.reset(false /* animate */);
         } else if (isAllAppsExpanded()) {
-            mAppsView.setVisibility(View.VISIBLE);
             mAppsView.onScrollUpEnd();
-        } else {
-            mAppsView.setVisibility(View.VISIBLE);
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index 68193f5..51b90f7 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -16,6 +16,9 @@
 package com.android.launcher3.allapps;
 
 import android.view.KeyEvent;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.anim.PropertySetter;
 
 /**
  * Interface for controlling the Apps search UI.
@@ -37,4 +40,10 @@
      * some UI beforehand.
      */
     void preDispatchKeyEvent(KeyEvent keyEvent);
+
+    /**
+     * Called as part of state transition to update the content UI
+     */
+    void setContentVisibility(int visibleElements, PropertySetter setter,
+            Interpolator interpolator);
 }
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 15cc2ca..b1e23d4 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -19,6 +19,7 @@
 import static android.view.View.MeasureSpec.getSize;
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 
+import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 
 import android.content.Context;
@@ -32,6 +33,7 @@
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ExtendedEditText;
@@ -42,6 +44,7 @@
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AlphabeticalAppsList;
 import com.android.launcher3.allapps.SearchUiManager;
+import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.graphics.TintedDrawableSpan;
 import com.android.launcher3.util.ComponentKey;
 
@@ -214,4 +217,10 @@
                     insets.bottom + mlp.topMargin + mFixedTranslationY);
         }
     }
+
+    @Override
+    public void setContentVisibility(int visibleElements, PropertySetter setter,
+            Interpolator interpolator) {
+        setter.setViewAlpha(this, (visibleElements & ALL_APPS_HEADER) != 0 ? 1 : 0, interpolator);
+    }
 }
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 64b5652..e5a8a01 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -22,9 +22,6 @@
 import android.content.SharedPreferences;
 import android.provider.Settings;
 
-import androidx.annotation.GuardedBy;
-import androidx.annotation.Keep;
-
 import com.android.launcher3.Utilities;
 
 import java.util.ArrayList;
@@ -32,6 +29,9 @@
 import java.util.SortedMap;
 import java.util.TreeMap;
 
+import androidx.annotation.GuardedBy;
+import androidx.annotation.Keep;
+
 /**
  * Defines a set of flags used to control various launcher behaviors.
  *
@@ -87,6 +87,9 @@
     // trying to make them fit the orientation the device is in.
     public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true;
 
+    public static final TogglableFlag QUICK_SWITCH = new TogglableFlag("QUICK_SWITCH", false,
+            "Swiping right on the nav bar while in an app switches to the previous app");
+
     /**
      * Feature flag to handle define config changes dynamically instead of killing the process.
      */
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 00cc1a7..23d647b 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -121,7 +121,6 @@
 
     private Workspace mWorkspace;
 
-    private final boolean mHasSysUiScrim;
     private boolean mDrawTopScrim, mDrawBottomScrim;
 
     private final RectF mFinalMaskRect = new RectF();
@@ -149,15 +148,9 @@
 
         mMaskHeight = Utilities.pxFromDp(ALPHA_MASK_BITMAP_DP,
                 view.getResources().getDisplayMetrics());
-
-        mHasSysUiScrim = !mWallpaperColorInfo.supportsDarkText();
-        if (mHasSysUiScrim) {
-            mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim);
-            mBottomMask = createDitheredAlphaMask();
-        } else {
-            mTopScrim = null;
-            mBottomMask = null;
-        }
+        mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim);
+        mBottomMask = mTopScrim == null ? null : createDitheredAlphaMask();
+        mHideSysUiScrim = mTopScrim == null;
 
         view.addOnAttachStateChangeListener(this);
         onExtractedColorsChanged(mWallpaperColorInfo);
@@ -174,7 +167,7 @@
             mWorkspace.computeScrollWithoutInvalidation();
             CellLayout currCellLayout = mWorkspace.getCurrentDragOverlappingLayout();
             canvas.save();
-            if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
+            if (currCellLayout != null && currCellLayout != mLauncher.getHotseat()) {
                 // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
                 mLauncher.getDragLayer()
                         .getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
@@ -185,7 +178,7 @@
             canvas.restore();
         }
 
-        if (!mHideSysUiScrim && mHasSysUiScrim) {
+        if (!mHideSysUiScrim) {
             if (mSysUiProgress <= 0) {
                 mAnimateScrimOnNextDraw = false;
                 return;
@@ -213,8 +206,9 @@
     }
 
     public void onInsetsChanged(Rect insets) {
-        mDrawTopScrim = insets.top > 0;
-        mDrawBottomScrim = !mLauncher.getDeviceProfile().isVerticalBarLayout();
+        mDrawTopScrim = mTopScrim != null && insets.top > 0;
+        mDrawBottomScrim = mBottomMask != null &&
+                !mLauncher.getDeviceProfile().isVerticalBarLayout();
     }
 
     private void setScrimProgress(float progress) {
@@ -230,7 +224,7 @@
         mWallpaperColorInfo.addOnChangeListener(this);
         onExtractedColorsChanged(mWallpaperColorInfo);
 
-        if (mHasSysUiScrim) {
+        if (mTopScrim != null) {
             IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
             filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
             mRoot.getContext().registerReceiver(mReceiver, filter);
@@ -240,7 +234,7 @@
     @Override
     public void onViewDetachedFromWindow(View view) {
         mWallpaperColorInfo.removeOnChangeListener(this);
-        if (mHasSysUiScrim) {
+        if (mTopScrim != null) {
             mRoot.getContext().unregisterReceiver(mReceiver);
         }
     }
@@ -259,14 +253,14 @@
     }
 
     public void setSize(int w, int h) {
-        if (mHasSysUiScrim) {
+        if (mTopScrim != null) {
             mTopScrim.setBounds(0, 0, w, h);
             mFinalMaskRect.set(0, h - mMaskHeight, w, h);
         }
     }
 
     public void hideSysUiScrim(boolean hideSysUiScrim) {
-        mHideSysUiScrim = hideSysUiScrim;
+        mHideSysUiScrim = hideSysUiScrim || (mTopScrim == null);
         if (!hideSysUiScrim) {
             mAnimateScrimOnNextDraw = true;
         }
@@ -281,18 +275,18 @@
     }
 
     private void reapplySysUiAlpha() {
-        if (mHasSysUiScrim) {
-            reapplySysUiAlphaNoInvalidate();
-            if (!mHideSysUiScrim) {
-                invalidate();
-            }
+        reapplySysUiAlphaNoInvalidate();
+        if (!mHideSysUiScrim) {
+            invalidate();
         }
     }
 
     private void reapplySysUiAlphaNoInvalidate() {
         float factor = mSysUiProgress * mSysUiAnimMultiplier;
         mBottomMaskPaint.setAlpha(Math.round(MAX_HOTSEAT_SCRIM_ALPHA * factor));
-        mTopScrim.setAlpha(Math.round(255 * factor));
+        if (mTopScrim != null) {
+            mTopScrim.setAlpha(Math.round(255 * factor));
+        }
     }
 
     public void invalidate() {
diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
similarity index 85%
rename from src/com/android/launcher3/model/LoaderResults.java
rename to src/com/android/launcher3/model/BaseLoaderResults.java
index 1d18e76..d3dc91f 100644
--- a/src/com/android/launcher3/model/LoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -30,47 +30,44 @@
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.ViewOnDrawExecutor;
-import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.concurrent.Executor;
 
 /**
- * Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
+ * Base Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
  */
-public class LoaderResults {
+public abstract class BaseLoaderResults {
 
-    private static final String TAG = "LoaderResults";
-    private static final int INVALID_SCREEN_ID = -1;
+    protected static final String TAG = "LoaderResults";
+    protected static final int INVALID_SCREEN_ID = -1;
     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
 
-    private final Executor mUiExecutor;
+    protected final Executor mUiExecutor;
 
-    private final LauncherAppState mApp;
-    private final BgDataModel mBgDataModel;
+    protected final LauncherAppState mApp;
+    protected final BgDataModel mBgDataModel;
     private final AllAppsList mBgAllAppsList;
-    private final int mPageToBindFirst;
+    protected final int mPageToBindFirst;
 
-    private final WeakReference<Callbacks> mCallbacks;
+    protected final WeakReference<Callbacks> mCallbacks;
 
-    public LoaderResults(LauncherAppState app, BgDataModel dataModel,
+    public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
             AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
         mUiExecutor = new MainThreadExecutor();
         mApp = app;
         mBgDataModel = dataModel;
         mBgAllAppsList = allAppsList;
         mPageToBindFirst = pageToBindFirst;
-        mCallbacks = callbacks == null ? new WeakReference<Callbacks>(null) : callbacks;
+        mCallbacks = callbacks == null ? new WeakReference<>(null) : callbacks;
     }
 
     /**
@@ -152,8 +149,8 @@
 
         Executor mainExecutor = mUiExecutor;
         // Load items on the current page.
-        bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor);
-
+        bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
+        bindAppWidgets(currentAppWidgets, mainExecutor);
         // In case of validFirstPage, only bind the first screen, and defer binding the
         // remaining screens after first onDraw (and an optional the fade animation whichever
         // happens later).
@@ -173,8 +170,8 @@
             }
         });
 
-        bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor);
-
+        bindWorkspaceItems(otherWorkspaceItems, deferredExecutor);
+        bindAppWidgets(otherAppWidgets, deferredExecutor);
         // Tell the workspace that we're done binding items
         r = new Runnable() {
             public void run() {
@@ -252,7 +249,7 @@
 
     /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
      * right) */
-    private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
+    protected void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
         final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
         final int screenCols = profile.numColumns;
         final int screenCellCount = profile.numColumns * profile.numRows;
@@ -288,14 +285,12 @@
         });
     }
 
-    private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
-            final ArrayList<LauncherAppWidgetInfo> appWidgets,
+    protected void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
             final Executor executor) {
 
         if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
                 && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    android.util.Log.getStackTraceString(new Throwable()));
+            Log.d("b/117332845", Log.getStackTraceString(new Throwable()));
         }
         // Bind the workspace items
         int N = workspaceItems.size();
@@ -307,22 +302,25 @@
                 public void run() {
                     Callbacks callbacks = mCallbacks.get();
                     if (callbacks != null) {
-                        callbacks.bindItems(workspaceItems.subList(start, start+chunkSize), false);
+                        callbacks.bindItems(workspaceItems.subList(start, start + chunkSize),
+                                false);
                     }
                 }
             };
             executor.execute(r);
         }
+    }
 
-        // Bind the widgets, one at a time
+    private void bindAppWidgets(ArrayList<LauncherAppWidgetInfo> appWidgets, Executor executor) {
+        int N;// Bind the widgets, one at a time
         N = appWidgets.size();
         for (int i = 0; i < N; i++) {
             final ItemInfo widget = appWidgets.get(i);
             final Runnable r = new Runnable() {
                 public void run() {
-                    Callbacks callbacks = mCallbacks.get();
-                    if (callbacks != null) {
-                        callbacks.bindItems(Collections.singletonList(widget), false);
+                Callbacks callbacks = mCallbacks.get();
+                if (callbacks != null) {
+                    callbacks.bindItems(Collections.singletonList(widget), false);
                     }
                 }
             };
@@ -330,18 +328,7 @@
         }
     }
 
-    public void bindDeepShortcuts() {
-        final HashMap<ComponentKey, Integer> shortcutMapCopy;
-        synchronized (mBgDataModel) {
-            shortcutMapCopy = new HashMap<>(mBgDataModel.deepShortcutMap);
-        }
-        mUiExecutor.execute(() -> {
-            Callbacks callbacks = mCallbacks.get();
-            if (callbacks != null) {
-                callbacks.bindDeepShortcutMap(shortcutMapCopy);
-            }
-        });
-    }
+    public abstract void bindDeepShortcuts();
 
     public void bindAllApps() {
         // shallow copy
@@ -359,19 +346,7 @@
         mUiExecutor.execute(r);
     }
 
-    public void bindWidgets() {
-        final ArrayList<WidgetListRowEntry> widgets =
-                mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext());
-        Runnable r = new Runnable() {
-            public void run() {
-                Callbacks callbacks = mCallbacks.get();
-                if (callbacks != null) {
-                    callbacks.bindAllWidgets(widgets);
-                }
-            }
-        };
-        mUiExecutor.execute(r);
-    }
+    public abstract void bindWidgets();
 
     public LooperIdleLock newIdleLock(Object lock) {
         LooperIdleLock idleLock = new LooperIdleLock(lock, Looper.getMainLooper());
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
new file mode 100644
index 0000000..9785887
--- /dev/null
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 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.model;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.widget.WidgetListRowEntry;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
+ */
+public class LoaderResults extends BaseLoaderResults {
+
+    public LoaderResults(LauncherAppState app, BgDataModel dataModel,
+            AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
+        super(app, dataModel, allAppsList, pageToBindFirst, callbacks);
+    }
+
+    @Override
+    public void bindDeepShortcuts() {
+        final HashMap<ComponentKey, Integer> shortcutMapCopy;
+        synchronized (mBgDataModel) {
+            shortcutMapCopy = new HashMap<>(mBgDataModel.deepShortcutMap);
+        }
+        mUiExecutor.execute(() -> {
+            Callbacks callbacks = mCallbacks.get();
+            if (callbacks != null) {
+                callbacks.bindDeepShortcutMap(shortcutMapCopy);
+            }
+        });
+    }
+
+    @Override
+    public void bindWidgets() {
+        final ArrayList<WidgetListRowEntry> widgets =
+                mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext());
+        Runnable r = new Runnable() {
+            public void run() {
+                Callbacks callbacks = mCallbacks.get();
+                if (callbacks != null) {
+                    callbacks.bindAllWidgets(widgets);
+                }
+            }
+        };
+        mUiExecutor.execute(r);
+    }
+}
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
similarity index 100%
rename from src/com/android/launcher3/model/WidgetsModel.java
rename to src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
similarity index 100%
rename from src/com/android/launcher3/shortcuts/DeepShortcutManager.java
rename to src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 439058c..46b463b 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -18,6 +18,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.launcher3.tests">
 
+    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
+
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
 
diff --git a/tests/dummy_app/Android.mk b/tests/dummy_app/Android.mk
new file mode 100644
index 0000000..f4ab582
--- /dev/null
+++ b/tests/dummy_app/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := Aardwolf
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/dummy_app/AndroidManifest.xml b/tests/dummy_app/AndroidManifest.xml
new file mode 100644
index 0000000..0546015
--- /dev/null
+++ b/tests/dummy_app/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<!-- Declare the contents of this Android application.  The namespace
+     attribute brings in the Android platform namespace, and the package
+     supplies a unique name for the application.  When writing your
+     own application, the package name must be changed from "com.example.*"
+     to come from a domain that you own or have control over. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.aardwolf">
+    <uses-sdk android:targetSdkVersion="25" android:minSdkVersion="21"/>
+    <application android:label="Aardwolf">
+        <activity
+            android:name="Activity1"
+            android:icon="@mipmap/ic_launcher1"
+            android:label="Aardwolf">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/dummy_app/res/layout/empty_activity.xml b/tests/dummy_app/res/layout/empty_activity.xml
new file mode 100644
index 0000000..377c56b
--- /dev/null
+++ b/tests/dummy_app/res/layout/empty_activity.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<EditText xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:textSize="18sp"
+    android:autoText="true"
+    android:capitalize="sentences"
+    android:text="Did you enjoy the adaptive icon?" />
+
diff --git a/tests/dummy_app/res/mipmap-anydpi/ic_launcher1.xml b/tests/dummy_app/res/mipmap-anydpi/ic_launcher1.xml
new file mode 100644
index 0000000..37c8c73
--- /dev/null
+++ b/tests/dummy_app/res/mipmap-anydpi/ic_launcher1.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@mipmap/icon_back_1"/>
+    <foreground>
+        <bitmap android:src="@mipmap/icon_fore_1"/>
+    </foreground>
+</adaptive-icon>
diff --git a/tests/dummy_app/res/mipmap-anydpi/ic_launcher2.xml b/tests/dummy_app/res/mipmap-anydpi/ic_launcher2.xml
new file mode 100644
index 0000000..20b2986
--- /dev/null
+++ b/tests/dummy_app/res/mipmap-anydpi/ic_launcher2.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/background" />
+    <foreground android:drawable="@mipmap/icon_fore_1"/>
+</adaptive-icon>
diff --git a/tests/dummy_app/res/mipmap-xxhdpi/ic_launcher1.png b/tests/dummy_app/res/mipmap-xxhdpi/ic_launcher1.png
new file mode 100644
index 0000000..73bc8e6
--- /dev/null
+++ b/tests/dummy_app/res/mipmap-xxhdpi/ic_launcher1.png
Binary files differ
diff --git a/tests/dummy_app/res/mipmap-xxhdpi/ic_launcher2.png b/tests/dummy_app/res/mipmap-xxhdpi/ic_launcher2.png
new file mode 100644
index 0000000..cd40b63
--- /dev/null
+++ b/tests/dummy_app/res/mipmap-xxhdpi/ic_launcher2.png
Binary files differ
diff --git a/tests/dummy_app/res/mipmap-xxhdpi/icon_back_1.png b/tests/dummy_app/res/mipmap-xxhdpi/icon_back_1.png
new file mode 100644
index 0000000..8debef3
--- /dev/null
+++ b/tests/dummy_app/res/mipmap-xxhdpi/icon_back_1.png
Binary files differ
diff --git a/tests/dummy_app/res/mipmap-xxhdpi/icon_fore_1.png b/tests/dummy_app/res/mipmap-xxhdpi/icon_fore_1.png
new file mode 100644
index 0000000..de4079b
--- /dev/null
+++ b/tests/dummy_app/res/mipmap-xxhdpi/icon_fore_1.png
Binary files differ
diff --git a/tests/dummy_app/res/mipmap-xxxhdpi/ic_launcher1.png b/tests/dummy_app/res/mipmap-xxxhdpi/ic_launcher1.png
new file mode 100644
index 0000000..889a99c
--- /dev/null
+++ b/tests/dummy_app/res/mipmap-xxxhdpi/ic_launcher1.png
Binary files differ
diff --git a/tests/dummy_app/res/mipmap-xxxhdpi/ic_launcher2.png b/tests/dummy_app/res/mipmap-xxxhdpi/ic_launcher2.png
new file mode 100644
index 0000000..973bb79
--- /dev/null
+++ b/tests/dummy_app/res/mipmap-xxxhdpi/ic_launcher2.png
Binary files differ
diff --git a/tests/dummy_app/res/mipmap-xxxhdpi/icon_back_1.png b/tests/dummy_app/res/mipmap-xxxhdpi/icon_back_1.png
new file mode 100644
index 0000000..70c0ebd
--- /dev/null
+++ b/tests/dummy_app/res/mipmap-xxxhdpi/icon_back_1.png
Binary files differ
diff --git a/tests/dummy_app/res/mipmap-xxxhdpi/icon_fore_1.png b/tests/dummy_app/res/mipmap-xxxhdpi/icon_fore_1.png
new file mode 100644
index 0000000..9d91632
--- /dev/null
+++ b/tests/dummy_app/res/mipmap-xxxhdpi/icon_fore_1.png
Binary files differ
diff --git a/tests/dummy_app/res/values/colors.xml b/tests/dummy_app/res/values/colors.xml
new file mode 100644
index 0000000..b5ce66e
--- /dev/null
+++ b/tests/dummy_app/res/values/colors.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="background">#455A64</color>
+</resources>
\ No newline at end of file
diff --git a/tests/dummy_app/src/com/example/android/aardwolf/Activity1.java b/tests/dummy_app/src/com/example/android/aardwolf/Activity1.java
new file mode 100644
index 0000000..d4eab15
--- /dev/null
+++ b/tests/dummy_app/src/com/example/android/aardwolf/Activity1.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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.example.android.aardwolf;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+public class Activity1 extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        View view = getLayoutInflater().inflate(R.layout.empty_activity, null);
+        setContentView(view);
+    }
+}
+
diff --git a/tests/res/raw/aardwolf_dummy_app.apk b/tests/res/raw/aardwolf_dummy_app.apk
new file mode 100644
index 0000000..39fb368
--- /dev/null
+++ b/tests/res/raw/aardwolf_dummy_app.apk
Binary files differ
diff --git a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
index 04c04f5..0edb3d6 100644
--- a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
+++ b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
@@ -19,6 +19,8 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 import static android.content.pm.PackageManager.DONT_KILL_APP;
 
+import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.ContentProvider;
@@ -36,6 +38,7 @@
 
     public static final String ENABLE_TEST_LAUNCHER = "enable-test-launcher";
     public static final String DISABLE_TEST_LAUNCHER = "disable-test-launcher";
+    public static final String KILL_PROCESS = "kill-process";
 
     @Override
     public boolean onCreate() {
@@ -83,14 +86,22 @@
                         COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);
                 return null;
             }
-
+            case KILL_PROCESS: {
+                ((ActivityManager) getContext().getSystemService(Activity.ACTIVITY_SERVICE)).
+                        killBackgroundProcesses(arg);
+                return null;
+            }
         }
         return super.call(method, arg, extras);
     }
 
     public static Bundle callCommand(String command) {
+        return callCommand(command, null);
+    }
+
+    public static Bundle callCommand(String command, String arg) {
         Instrumentation inst = InstrumentationRegistry.getInstrumentation();
         Uri uri = Uri.parse("content://" + inst.getContext().getPackageName() + ".commands");
-        return inst.getTargetContext().getContentResolver().call(uri, command, null, null);
+        return inst.getTargetContext().getContentResolver().call(uri, command, arg, null);
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 532d3e8..bc5aaee 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -296,11 +296,6 @@
                         Process.myUserHandle()).get(0);
     }
 
-    protected LauncherActivityInfo getChromeApp() {
-        return LauncherAppsCompat.getInstance(mTargetContext)
-                .getActivityList("com.android.chrome", Process.myUserHandle()).get(0);
-    }
-
     /**
      * Broadcast receiver which blocks until the result is received.
      */
diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
index 9160076..9354862 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
@@ -1,18 +1,10 @@
 package com.android.launcher3.ui;
 
-import static org.junit.Assert.assertTrue;
-
 import android.content.pm.LauncherActivityInfo;
+
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.util.Condition;
-import com.android.launcher3.util.Wait;
-
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -24,37 +16,23 @@
 public class AllAppsIconToHomeTest extends AbstractLauncherUiTest {
 
     @Test
-    @Ignore
-    public void testDragIcon_portrait() throws Throwable {
-        lockRotation(true);
-        performTest();
-    }
-
-    @Test
-    @Ignore
-    public void testDragIcon_landscape() throws Throwable {
-        lockRotation(false);
-        performTest();
-    }
-
-    private void performTest() throws Throwable {
+    @PortraitLandscape
+    public void testDragIcon() throws Throwable {
         LauncherActivityInfo settingsApp = getSettingsApp();
 
         clearHomescreen();
         mDevice.pressHome();
         mDevice.waitForIdle();
 
-        // Open all apps and wait for load complete.
-        final UiObject2 appsContainer = TestViewHelpers.openAllApps();
-        Wait.atMost(null, Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT);
-
-        // Drag icon to homescreen.
-        UiObject2 icon = scrollAndFind(appsContainer, By.text(settingsApp.getLabel().toString()));
-        TestViewHelpers.dragToWorkspace(icon, true);
-
-        // Verify that the icon works on homescreen.
-        mDevice.findObject(By.text(settingsApp.getLabel().toString())).click();
-        assertTrue(mDevice.wait(Until.hasObject(By.pkg(
-                settingsApp.getComponentName().getPackageName()).depth(0)), DEFAULT_UI_TIMEOUT));
+        final String appName = settingsApp.getLabel().toString();
+        // 1. Open all apps and wait for load complete.
+        // 2. Drag icon to homescreen.
+        // 3. Verify that the icon works on homescreen.
+        mLauncher.getWorkspace().
+                switchToAllApps().
+                getAppIcon(appName).
+                dragToWorkspace().
+                getWorkspaceAppIcon(appName).
+                launch(settingsApp.getComponentName().getPackageName());
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
index d7a7f6b..1fea4d5 100644
--- a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
+++ b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
@@ -1,22 +1,18 @@
 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 androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-import android.view.MotionEvent;
 
-import com.android.launcher3.R;
-import com.android.launcher3.util.Condition;
-import com.android.launcher3.util.Wait;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.popup.ArrowPopup;
+import com.android.launcher3.tapl.AppIconMenu;
+import com.android.launcher3.tapl.AppIconMenuItem;
+import com.android.launcher3.views.OptionsPopupView;
 
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -27,47 +23,30 @@
 @RunWith(AndroidJUnit4.class)
 public class ShortcutsLaunchTest extends AbstractLauncherUiTest {
 
-    @Test
-    @Ignore
-    public void testAppLauncher_portrait() throws Exception {
-        lockRotation(true);
-        performTest();
+    private boolean isOptionsPopupVisible(Launcher launcher) {
+        final ArrowPopup popup = OptionsPopupView.getOptionsPopup(launcher);
+        return popup != null && popup.isShown();
     }
 
     @Test
-    @Ignore
-    public void testAppLauncher_landscape() throws Exception {
-        lockRotation(false);
-        performTest();
-    }
-
-    private void performTest() throws Exception {
+    @PortraitLandscape
+    public void testAppLauncher() throws Exception {
         mActivityMonitor.startLauncher();
-        LauncherActivityInfo testApp = getSettingsApp();
+        final LauncherActivityInfo testApp = getSettingsApp();
 
-        // Open all apps and wait for load complete
-        final UiObject2 appsContainer = TestViewHelpers.openAllApps();
-        Wait.atMost(null, Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT);
+        final AppIconMenu menu = mLauncher.
+                pressHome().
+                switchToAllApps().
+                getAppIcon(testApp.getLabel().toString()).
+                openMenu();
 
-        // Find settings app and verify shortcuts appear when long pressed
-        UiObject2 icon = scrollAndFind(appsContainer, By.text(testApp.getLabel().toString()));
-        // Press icon center until shortcuts appear
-        Point iconCenter = icon.getVisibleCenter();
-        TestViewHelpers.sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
-        UiObject2 deepShortcutsContainer = TestViewHelpers.findViewById(
-                R.id.deep_shortcuts_container);
-        assertNotNull(deepShortcutsContainer);
-        TestViewHelpers.sendPointer(MotionEvent.ACTION_UP, iconCenter);
+        executeOnLauncher(
+                launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
+                        isOptionsPopupVisible(launcher)));
 
-        // Verify that launching a shortcut opens a page with the same text
-        assertTrue(deepShortcutsContainer.getChildCount() > 0);
+        final AppIconMenuItem menuItem = menu.getMenuItem(1);
+        final String itemName = menuItem.getText();
 
-        // Pick second children as it starts showing shortcuts.
-        UiObject2 shortcut = deepShortcutsContainer.getChildren().get(1)
-                .findObject(TestViewHelpers.getSelectorForId(R.id.bubble_text));
-        shortcut.click();
-        assertTrue(mDevice.wait(Until.hasObject(By.pkg(
-                testApp.getComponentName().getPackageName())
-                .text(shortcut.getText())), DEFAULT_UI_TIMEOUT));
+        menuItem.launch(testApp.getComponentName().getPackageName(), itemName);
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
index 436c699..4c2c959 100644
--- a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
@@ -1,22 +1,12 @@
 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 androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-import android.view.MotionEvent;
 
-import com.android.launcher3.R;
-import com.android.launcher3.util.Condition;
-import com.android.launcher3.util.Wait;
+import com.android.launcher3.tapl.AppIconMenuItem;
 
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -28,51 +18,30 @@
 public class ShortcutsToHomeTest extends AbstractLauncherUiTest {
 
     @Test
-    @Ignore
-    public void testDragIcon_portrait() throws Throwable {
-        lockRotation(true);
-        performTest();
-    }
-
-    @Test
-    @Ignore
-    public void testDragIcon_landscape() throws Throwable {
-        lockRotation(false);
-        performTest();
-    }
-
-    private void performTest() throws Throwable {
+    @PortraitLandscape
+    public void testDragIcon() throws Throwable {
         clearHomescreen();
         mActivityMonitor.startLauncher();
 
-        LauncherActivityInfo testApp  = getSettingsApp();
+        LauncherActivityInfo testApp = getSettingsApp();
 
-        // Open all apps and wait for load complete.
-        final UiObject2 appsContainer = TestViewHelpers.openAllApps();
-        Wait.atMost(null, Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT);
+        // 1. Open all apps and wait for load complete.
+        // 2. Find the app and long press it to show shortcuts.
+        // 3. Press icon center until shortcuts appear
+        final AppIconMenuItem menuItem = mLauncher.
+                getWorkspace().
+                switchToAllApps().
+                getAppIcon(testApp.getLabel().toString()).
+                openMenu().
+                getMenuItem(0);
+        final String shortcutName = menuItem.getText();
 
-        // Find the app and long press it to show shortcuts.
-        UiObject2 icon = scrollAndFind(appsContainer, By.text(testApp.getLabel().toString()));
-        // Press icon center until shortcuts appear
-        Point iconCenter = icon.getVisibleCenter();
-        TestViewHelpers.sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
-        UiObject2 deepShortcutsContainer = TestViewHelpers.findViewById(
-                R.id.deep_shortcuts_container);
-        assertNotNull(deepShortcutsContainer);
-        TestViewHelpers.sendPointer(MotionEvent.ACTION_UP, iconCenter);
-
-        // Drag the first shortcut to the home screen.
-        assertTrue(deepShortcutsContainer.getChildCount() > 0);
-        UiObject2 shortcut = deepShortcutsContainer.getChildren().get(1)
-                .findObject(TestViewHelpers.getSelectorForId(R.id.bubble_text));
-        String shortcutName = shortcut.getText();
-        TestViewHelpers.dragToWorkspace(shortcut, false);
-
-        // Verify that the shortcut works on home screen
-        // (the app opens and has the same text as the shortcut).
-        mDevice.findObject(By.text(shortcutName)).click();
-        assertTrue(mDevice.wait(Until.hasObject(By.pkg(
-                testApp.getComponentName().getPackageName())
-                .text(shortcutName)), DEFAULT_UI_TIMEOUT));
+        // 4. Drag the first shortcut to the home screen.
+        // 5. Verify that the shortcut works on home screen
+        //    (the app opens and has the same text as the shortcut).
+        menuItem.
+                dragToWorkspace().
+                getWorkspaceAppIcon(shortcutName).
+                launch(testApp.getComponentName().getPackageName(), shortcutName);
     }
 }
diff --git a/tests/src/com/android/launcher3/util/TestUtil.java b/tests/src/com/android/launcher3/util/TestUtil.java
new file mode 100644
index 0000000..1338dcb
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/TestUtil.java
@@ -0,0 +1,51 @@
+/*
+ * 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.util;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import android.content.res.Resources;
+
+import androidx.test.uiautomator.UiDevice;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class TestUtil {
+    public static void installDummyApp() throws IOException {
+        // Copy apk from resources to a local file and install from there.
+        final Resources resources = getContext().getResources();
+        final InputStream in = resources.openRawResource(
+                resources.getIdentifier("aardwolf_dummy_app",
+                        "raw", getContext().getPackageName()));
+        final String apkFilename = getInstrumentation().getTargetContext().
+                getFilesDir().getPath() + "/dummy_app.apk";
+
+        final FileOutputStream out = new FileOutputStream(apkFilename);
+        byte[] buff = new byte[1024];
+        int read;
+
+        while ((read = in.read(buff)) > 0) {
+            out.write(buff, 0, read);
+        }
+        in.close();
+        out.close();
+
+        UiDevice.getInstance(getInstrumentation()).executeShellCommand("pm install " + apkFilename);
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index b7ae9f1..efefc0d 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,23 +16,19 @@
 
 package com.android.launcher3.tapl;
 
+import android.graphics.Point;
 import android.widget.TextView;
 
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
 
 /**
  * App icon, whether in all apps or in workspace/
  */
-public final class AppIcon {
-    private final LauncherInstrumentation mLauncher;
-    private final UiObject2 mIcon;
-
+public final class AppIcon extends Launchable {
     AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
-        mLauncher = launcher;
-        mIcon = icon;
+        super(launcher, icon);
     }
 
     static BySelector getAppIconSelector(String appName) {
@@ -40,20 +36,13 @@
     }
 
     /**
-     * Clicks the icon to launch its app.
+     * Long-clicks the icon to open its menu.
      */
-    public Background launch(String packageName) {
-        LauncherInstrumentation.log("AppIcon.launch before click " + mIcon.getVisibleCenter());
-        LauncherInstrumentation.assertTrue(
-                "Launching an app didn't open a new window: " + mIcon.getText(),
-                mIcon.clickAndWait(Until.newWindow(), LauncherInstrumentation.WAIT_TIME_MS));
-        LauncherInstrumentation.assertTrue(
-                "App didn't start: " + packageName, mLauncher.getDevice().wait(Until.hasObject(
-                        By.pkg(packageName).depth(0)), LauncherInstrumentation.WAIT_TIME_MS));
-        return new Background(mLauncher);
-    }
-
-    UiObject2 getIcon() {
-        return mIcon;
+    public AppIconMenu openMenu() {
+        final Point iconCenter = mObject.getVisibleCenter();
+        mLauncher.longTap(iconCenter.x, iconCenter.y);
+        final UiObject2 deepShortcutsContainer = mLauncher.waitForLauncherObject(
+                "deep_shortcuts_container");
+        return new AppIconMenu(mLauncher, deepShortcutsContainer);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
new file mode 100644
index 0000000..2a03f9a
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * Context menu of an app icon.
+ */
+public class AppIconMenu {
+    private final LauncherInstrumentation mLauncher;
+    private final UiObject2 mDeepShortcutsContainer;
+
+    AppIconMenu(LauncherInstrumentation launcher,
+            UiObject2 deepShortcutsContainer) {
+        mLauncher = launcher;
+        mDeepShortcutsContainer = deepShortcutsContainer;
+    }
+
+    /**
+     * Returns a menu item with a given number. Fails if it doesn't exist.
+     */
+    public AppIconMenuItem getMenuItem(int itemNumber) {
+        assertTrue(mDeepShortcutsContainer.getChildCount() > itemNumber);
+
+        final UiObject2 shortcut = mLauncher.waitForObjectInContainer(
+                mDeepShortcutsContainer.getChildren().get(itemNumber), "bubble_text");
+        return new AppIconMenuItem(mLauncher, shortcut);
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
new file mode 100644
index 0000000..c39f8d1
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -0,0 +1,35 @@
+/*
+ * 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 androidx.test.uiautomator.UiObject2;
+
+/**
+ * Menu item in an app icon menu.
+ */
+public class AppIconMenuItem extends Launchable {
+    AppIconMenuItem(LauncherInstrumentation launcher, UiObject2 shortcut) {
+        super(launcher, shortcut);
+    }
+
+    /**
+     * Returns the visible text of the menu item.
+     */
+    public String getText() {
+        return mObject.getText();
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
new file mode 100644
index 0000000..7e2c966
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -0,0 +1,81 @@
+/*
+ * 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 android.graphics.Point;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+/**
+ * Ancestor for AppIcon and AppMenuItem.
+ */
+class Launchable {
+    private static final int DRAG_SPEED = 500;
+    protected final LauncherInstrumentation mLauncher;
+
+    protected final UiObject2 mObject;
+
+    Launchable(LauncherInstrumentation launcher, UiObject2 object) {
+        mObject = object;
+        mLauncher = launcher;
+    }
+
+    UiObject2 getObject() {
+        return mObject;
+    }
+
+    /**
+     * Clicks the object to launch its app.
+     */
+    public Background launch(String expectedPackageName) {
+        return launch(expectedPackageName, By.pkg(expectedPackageName).depth(0));
+    }
+
+    /**
+     * Clicks the object to launch its app.
+     */
+    public Background launch(String expectedPackageName, String expectedAppText) {
+        return launch(expectedPackageName, By.pkg(expectedPackageName).text(expectedAppText));
+    }
+
+    private Background launch(String errorMessage, BySelector selector) {
+        LauncherInstrumentation.log("Launchable.launch before click " +
+                mObject.getVisibleCenter());
+        LauncherInstrumentation.assertTrue(
+                "Launching an app didn't open a new window: " + mObject.getText(),
+                mObject.clickAndWait(Until.newWindow(), LauncherInstrumentation.WAIT_TIME_MS));
+        LauncherInstrumentation.assertTrue(
+                "App didn't start: " + errorMessage,
+                mLauncher.getDevice().wait(Until.hasObject(selector),
+                        LauncherInstrumentation.WAIT_TIME_MS));
+        return new Background(mLauncher);
+    }
+
+    /**
+     * Drags an object to the center of homescreen.
+     */
+    public Workspace dragToWorkspace() {
+        final UiDevice device = mLauncher.getDevice();
+        mObject.drag(new Point(
+                device.getDisplayWidth() / 2, device.getDisplayHeight() / 2), DRAG_SPEED);
+        return new Workspace(mLauncher);
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 31abc53..67106f7 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -28,6 +28,13 @@
 import android.view.Surface;
 import android.view.accessibility.AccessibilityEvent;
 
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
 import com.android.launcher3.TestProtocol;
 import com.android.quickstep.SwipeUpSetting;
 
@@ -36,13 +43,6 @@
 import java.lang.ref.WeakReference;
 import java.util.concurrent.TimeoutException;
 
-import androidx.annotation.NonNull;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.BySelector;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
 /**
  * The main tapl object. The only object that can be explicitly constructed by the using code. It
  * produces all other objects.
@@ -394,6 +394,11 @@
         return mDevice;
     }
 
+    void longTap(int x, int y) {
+        mDevice.drag(x, y, x, y, 0);
+    }
+
+
     void swipe(int startX, int startY, int endX, int endY) {
         executeAndWaitForEvent(
                 () -> mDevice.swipe(startX, startY, endX, endY, 60),
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 493f26a..c63822e 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -19,12 +19,12 @@
 import static junit.framework.TestCase.assertTrue;
 
 import android.graphics.Point;
-import androidx.test.uiautomator.Direction;
-import androidx.test.uiautomator.UiObject2;
 import android.view.KeyEvent;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.test.uiautomator.Direction;
+import androidx.test.uiautomator.UiObject2;
 
 /**
  * Operations on the workspace screen.
@@ -85,6 +85,21 @@
         return icon != null ? new AppIcon(mLauncher, icon) : null;
     }
 
+
+    /**
+     * Returns an icon for the app; fails if the icon doesn't exist.
+     *
+     * @param appName name of the app
+     * @return app icon.
+     */
+    @NonNull
+    public AppIcon getWorkspaceAppIcon(String appName) {
+        return new AppIcon(mLauncher,
+                mLauncher.getObjectInContainer(
+                        verifyActiveContainer(),
+                        AppIcon.getAppIconSelector(appName)));
+    }
+
     /**
      * Ensures that workspace is scrollable. If it's not, drags an icon icons from hotseat to the
      * second screen.
@@ -111,7 +126,7 @@
     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);
+        app.getObject().drag(dest, ICON_DRAG_SPEED);
         verifyActiveContainer();
     }